tilemap-editor 3.3.3__tar.gz → 3.3.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (202) hide show
  1. {tilemap_editor-3.3.3/src/tilemap_editor.egg-info → tilemap_editor-3.3.4}/PKG-INFO +1 -1
  2. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/pyproject.toml +1 -1
  3. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/plugins/sprite_editor/editor.py +24 -5
  4. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/plugins/tileset_collision/collision_painter.py +137 -34
  5. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/plugins/tileset_collision/editor.py +192 -2
  6. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4/src/tilemap_editor.egg-info}/PKG-INFO +1 -1
  7. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/tilemap_editor.egg-info/SOURCES.txt +1 -0
  8. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/widgets/filemanager.py +2 -4
  9. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/widgets/spritesheet_grid.py +190 -30
  10. tilemap_editor-3.3.4/src/widgets/ui/checkbox.py +99 -0
  11. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/LICENSE +0 -0
  12. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/MANIFEST.in +0 -0
  13. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/README.md +0 -0
  14. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/assets/fonts/jetbrain-fonts/JetBrainsMono-Bold.ttf +0 -0
  15. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/assets/fonts/jetbrain-fonts/JetBrainsMono-BoldItalic.ttf +0 -0
  16. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/assets/fonts/jetbrain-fonts/JetBrainsMono-Italic.ttf +0 -0
  17. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/assets/fonts/jetbrain-fonts/JetBrainsMono-Regular.ttf +0 -0
  18. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/assets/fonts/noto/NotoSans-Bold.ttf +0 -0
  19. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/assets/fonts/noto/NotoSans-BoldItalic.ttf +0 -0
  20. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/assets/fonts/noto/NotoSans-Italic.ttf +0 -0
  21. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/assets/fonts/noto/NotoSans-Regular.ttf +0 -0
  22. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/assets/icons/arrow-down.svg +0 -0
  23. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/assets/icons/check.svg +0 -0
  24. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/assets/icons/checked.svg +0 -0
  25. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/assets/icons/close.svg +0 -0
  26. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/assets/icons/duplicate.svg +0 -0
  27. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/assets/icons/error.svg +0 -0
  28. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/assets/icons/file.svg +0 -0
  29. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/assets/icons/filedead.svg +0 -0
  30. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/assets/icons/fit.svg +0 -0
  31. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/assets/icons/folder.svg +0 -0
  32. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/assets/icons/grid.svg +0 -0
  33. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/assets/icons/image.svg +0 -0
  34. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/assets/icons/info.svg +0 -0
  35. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/assets/icons/load.svg +0 -0
  36. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/assets/icons/loop.svg +0 -0
  37. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/assets/icons/pan.svg +0 -0
  38. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/assets/icons/pause.svg +0 -0
  39. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/assets/icons/pencil.svg +0 -0
  40. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/assets/icons/play.svg +0 -0
  41. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/assets/icons/plus.svg +0 -0
  42. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/assets/icons/radio.svg +0 -0
  43. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/assets/icons/reset.svg +0 -0
  44. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/assets/icons/save.svg +0 -0
  45. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/assets/icons/stop.svg +0 -0
  46. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/assets/icons/tilemap.svg +0 -0
  47. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/assets/icons/tileset.svg +0 -0
  48. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/assets/icons/unchecked.svg +0 -0
  49. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/assets/icons/warning.svg +0 -0
  50. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/assets/icons/zoomin.svg +0 -0
  51. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/assets/icons/zoomout.svg +0 -0
  52. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/setup.cfg +0 -0
  53. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/configs/themes.py +0 -0
  54. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/constants.py +0 -0
  55. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/editor.py +0 -0
  56. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/event_map.py +0 -0
  57. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/layers.py +0 -0
  58. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/main.py +0 -0
  59. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/node_manager.py +0 -0
  60. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/nodes.py +0 -0
  61. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/plugins/__init__.py +0 -0
  62. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/plugins/character_collision/__init__.py +0 -0
  63. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/plugins/character_collision/editor.py +0 -0
  64. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/plugins/character_collision/models.py +0 -0
  65. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/plugins/character_collision/protocols.py +0 -0
  66. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/plugins/character_collision/shape_editor.py +0 -0
  67. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/plugins/character_collision/standalone.py +0 -0
  68. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/plugins/object_tileset_collision/__init__.py +0 -0
  69. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/plugins/object_tileset_collision/editor.py +0 -0
  70. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/plugins/object_tileset_collision/models.py +0 -0
  71. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/plugins/object_tileset_collision/protocols.py +0 -0
  72. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/plugins/object_tileset_collision/standalone.py +0 -0
  73. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/plugins/sprite_animation/__init__.py +0 -0
  74. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/plugins/sprite_animation/__main__.py +0 -0
  75. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/plugins/sprite_animation/clipboard_util.py +0 -0
  76. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/plugins/sprite_animation/editor.py +0 -0
  77. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/plugins/sprite_animation/frame_picker.py +0 -0
  78. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/plugins/sprite_animation/models.py +0 -0
  79. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/plugins/sprite_animation/preview.py +0 -0
  80. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/plugins/sprite_animation/protocols.py +0 -0
  81. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/plugins/sprite_animation/runtime_load.py +0 -0
  82. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/plugins/sprite_animation/standalone.py +0 -0
  83. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/plugins/sprite_animation/timeline.py +0 -0
  84. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/plugins/sprite_animation/validation.py +0 -0
  85. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/plugins/sprite_editor/__init__.py +0 -0
  86. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/plugins/sprite_editor/standalone.py +0 -0
  87. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/plugins/tileset_collision/__init__.py +0 -0
  88. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/plugins/tileset_collision/models.py +0 -0
  89. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/plugins/tileset_collision/protocols.py +0 -0
  90. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/plugins/tileset_collision/standalone.py +0 -0
  91. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/standalone_automap.py +0 -0
  92. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/standalone_error_console.py +0 -0
  93. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/standalone_filemanager.py +0 -0
  94. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/standalone_image_viewer.py +0 -0
  95. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/tilemap.py +0 -0
  96. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/tilemap_editor/__init__.py +0 -0
  97. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/tilemap_editor/__main__.py +0 -0
  98. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/__init__.py +0 -0
  99. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/fonts/jetbrain-fonts/JetBrainsMono-Bold.ttf +0 -0
  100. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/fonts/jetbrain-fonts/JetBrainsMono-BoldItalic.ttf +0 -0
  101. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/fonts/jetbrain-fonts/JetBrainsMono-Italic.ttf +0 -0
  102. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/fonts/jetbrain-fonts/JetBrainsMono-Regular.ttf +0 -0
  103. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/fonts/noto/NotoSans-Bold.ttf +0 -0
  104. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/fonts/noto/NotoSans-BoldItalic.ttf +0 -0
  105. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/fonts/noto/NotoSans-Italic.ttf +0 -0
  106. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/fonts/noto/NotoSans-Regular.ttf +0 -0
  107. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/ToolMove.svg +0 -0
  108. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/arrow-down.svg +0 -0
  109. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/check.svg +0 -0
  110. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/checked.svg +0 -0
  111. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/close.svg +0 -0
  112. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/duplicate.svg +0 -0
  113. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/error.svg +0 -0
  114. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/file.svg +0 -0
  115. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/filedead.svg +0 -0
  116. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/fit.svg +0 -0
  117. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/folder.svg +0 -0
  118. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/grid.svg +0 -0
  119. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/image.svg +0 -0
  120. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/info.svg +0 -0
  121. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/load.svg +0 -0
  122. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/loop.svg +0 -0
  123. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/pan.svg +0 -0
  124. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/pause.svg +0 -0
  125. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/pencil.svg +0 -0
  126. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/play.svg +0 -0
  127. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/plus.svg +0 -0
  128. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/radio.svg +0 -0
  129. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/reset.svg +0 -0
  130. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/save.svg +0 -0
  131. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/stop.svg +0 -0
  132. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/tilemap.svg +0 -0
  133. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/tileset.svg +0 -0
  134. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/unchecked.svg +0 -0
  135. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/warning.svg +0 -0
  136. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/zoomin.svg +0 -0
  137. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/zoomout.svg +0 -0
  138. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/tilemap_editor/cli.py +0 -0
  139. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/tilemap_editor/main.py +0 -0
  140. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/tilemap_editor/settings.py +0 -0
  141. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/tilemap_editor.egg-info/dependency_links.txt +0 -0
  142. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/tilemap_editor.egg-info/entry_points.txt +0 -0
  143. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/tilemap_editor.egg-info/requires.txt +0 -0
  144. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/tilemap_editor.egg-info/top_level.txt +0 -0
  145. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/ttypes/__init__.py +0 -0
  146. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/ttypes/tilemap.py +0 -0
  147. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/utils/__init__.py +0 -0
  148. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/utils/editor_preference.py +0 -0
  149. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/utils/error_handler.py +0 -0
  150. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/utils/font_manager.py +0 -0
  151. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/utils/history.py +0 -0
  152. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/utils/icon_manager.py +0 -0
  153. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/utils/icons_cache.py +0 -0
  154. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/utils/log_capture.py +0 -0
  155. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/utils/project_paths.py +0 -0
  156. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/utils/serialization.py +0 -0
  157. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/utils/standalone.py +0 -0
  158. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/utils/validation.py +0 -0
  159. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/widgets/__init__.py +0 -0
  160. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/widgets/automap_models.py +0 -0
  161. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/widgets/autotile_template.py +0 -0
  162. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/widgets/autotiler.py +0 -0
  163. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/widgets/input.py +0 -0
  164. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/widgets/layer_selector.py +0 -0
  165. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/widgets/map_properties.py +0 -0
  166. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/widgets/mapsetup.py +0 -0
  167. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/widgets/particle_presets.py +0 -0
  168. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/widgets/particle_system.py +0 -0
  169. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/widgets/regex_automap_designer.py +0 -0
  170. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/widgets/tile_grid.py +0 -0
  171. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/widgets/tile_selector.py +0 -0
  172. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/widgets/ui/collision_layer_mask.py +0 -0
  173. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/widgets/ui/collision_layer_sidebar.py +0 -0
  174. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/widgets/ui/confirm_dialog.py +0 -0
  175. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/widgets/ui/draw_utils.py +0 -0
  176. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/widgets/ui/fileinput.py +0 -0
  177. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/widgets/ui/layer_type_dialog.py +0 -0
  178. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/widgets/ui/menubar.py +0 -0
  179. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/widgets/ui/mode_indicator.py +0 -0
  180. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/widgets/ui/node_editor.py +0 -0
  181. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/widgets/ui/node_selector.py +0 -0
  182. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/widgets/ui/notification.py +0 -0
  183. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/widgets/ui/particle_config_dialog.py +0 -0
  184. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/widgets/ui/property_editor.py +0 -0
  185. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/widgets/ui/region_selector.py +0 -0
  186. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/widgets/ui/status_bar.py +0 -0
  187. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/widgets/ui/theme.py +0 -0
  188. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/widgets/ui/tileset_type_dialog.py +0 -0
  189. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/widgets/ui/toolbar.py +0 -0
  190. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/src/widgets/ui/tooltip.py +0 -0
  191. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/tests/test_animation_library_grid_offset.py +0 -0
  192. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/tests/test_collision_layer_mask.py +0 -0
  193. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/tests/test_collision_layer_sidebar.py +0 -0
  194. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/tests/test_editor_pan_mode.py +0 -0
  195. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/tests/test_project_paths.py +0 -0
  196. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/tests/test_render_scale.py +0 -0
  197. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/tests/test_sprite_animation_editor_grid.py +0 -0
  198. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/tests/test_tile_grid_selection.py +0 -0
  199. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/tests/test_tile_selector_pick.py +0 -0
  200. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/tests/test_tilemap_save.py +0 -0
  201. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/tests/test_timeline_invalidate_cache.py +0 -0
  202. {tilemap_editor-3.3.3 → tilemap_editor-3.3.4}/tests/test_toolbar_tools.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tilemap-editor
3
- Version: 3.3.3
3
+ Version: 3.3.4
4
4
  Summary: Pygame tilemap editor with SVG icons, professional UI, and sprite animation tools
5
5
  Author: tilemap editor contributors
6
6
  License: Proprietary
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "tilemap-editor"
7
- version = "3.3.3"
7
+ version = "3.3.4"
8
8
  description = "Pygame tilemap editor with SVG icons, professional UI, and sprite animation tools"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -149,6 +149,24 @@ class SpriteEditor:
149
149
  if self.grid.redo():
150
150
  self.grid.selected_indices.clear()
151
151
  return True
152
+ elif event.key == pygame.K_c:
153
+ if self.grid.has_selection():
154
+ self._clipboard = self.grid.copy_selected()
155
+ self._paste_mode = False
156
+ self.grid.paste_preview_idx = -1
157
+ return True
158
+ elif event.key == pygame.K_x:
159
+ if self.grid.has_selection():
160
+ self._clipboard = self.grid.cut_selected()
161
+ self._paste_mode = False
162
+ self.grid.paste_preview_idx = -1
163
+ return True
164
+ elif event.key == pygame.K_v:
165
+ if self._clipboard:
166
+ self._paste_mode = not self._paste_mode
167
+ if not self._paste_mode:
168
+ self.grid.paste_preview_idx = -1
169
+ return True
152
170
 
153
171
  # Toolbar clicks
154
172
  if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
@@ -156,19 +174,20 @@ class SpriteEditor:
156
174
  if r.collidepoint(mouse):
157
175
  return self._handle_tool(lbl)
158
176
 
159
- # Paste mode: next grid click pastes
177
+ # Paste mode: click places (or exits if outside grid)
160
178
  if self._paste_mode and event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
161
179
  idx = self.grid.index_at_pos(mouse)
162
180
  if idx >= 0 and self.grid.rect.collidepoint(mouse):
163
181
  self.grid.paste_at(idx, self._clipboard)
164
- self._paste_mode = False
165
- self.grid.paste_preview_idx = -1
166
- return True
182
+ self._paste_mode = False
183
+ self.grid.paste_preview_idx = -1
184
+ return True
167
185
 
168
- # Paste preview on hover
186
+ # Paste preview on hover (consume event to prevent grid overlap)
169
187
  if self._paste_mode and event.type == pygame.MOUSEMOTION:
170
188
  idx = self.grid.index_at_pos(mouse)
171
189
  self.grid.paste_preview_idx = idx if idx >= 0 else -1
190
+ return True
172
191
 
173
192
  return self.grid.handle_event(event)
174
193
 
@@ -117,7 +117,8 @@ class CollisionPainter:
117
117
  self.snap_to_grid = False
118
118
  self.grid_size = 8 # pixels
119
119
  self.show_grid = True
120
- self.edge_draw_mode = False # E key - straight line drawing
120
+ self.show_angle_hints = False # show angle arcs at vertices
121
+ self.edge_draw_mode = False # Ctrl+E - straight line drawing
121
122
  self._edge_start: Optional[Tuple[int, int]] = None # Start point for edge draw
122
123
  self._shift_held = False
123
124
 
@@ -270,7 +271,66 @@ class CollisionPainter:
270
271
  def get_one_way_flags(self) -> List[bool]:
271
272
  """Get one-way collision flags for all polygons"""
272
273
  return list(self.polygon_one_way)
274
+
275
+ def toggle_one_way(self) -> None:
276
+ """Toggle one-way flag on the selected polygon"""
277
+ idx = self.selected_polygon_idx
278
+ if idx is not None and 0 <= idx < len(self.polygon_one_way):
279
+ self.polygon_one_way[idx] = not self.polygon_one_way[idx]
280
+ if self.on_polygon_modified:
281
+ self.on_polygon_modified(idx)
273
282
 
283
+ def _get_interior_angle(
284
+ self, polygon: List[Tuple[float, float]], idx: int
285
+ ) -> float:
286
+ """Compute interior angle (degrees) at polygon vertex idx."""
287
+ n = len(polygon)
288
+ if n < 3:
289
+ return 0.0
290
+ prev = polygon[(idx - 1) % n]
291
+ curr = polygon[idx]
292
+ nxt = polygon[(idx + 1) % n]
293
+ v1 = (prev[0] - curr[0], prev[1] - curr[1])
294
+ v2 = (nxt[0] - curr[0], nxt[1] - curr[1])
295
+ dot = v1[0] * v2[0] + v1[1] * v2[1]
296
+ mag1 = math.hypot(v1[0], v1[1])
297
+ mag2 = math.hypot(v2[0], v2[1])
298
+ if mag1 < 0.001 or mag2 < 0.001:
299
+ return 0.0
300
+ cos_a = max(-1.0, min(1.0, dot / (mag1 * mag2)))
301
+ return math.degrees(math.acos(cos_a))
302
+
303
+ def _draw_angle_hint(
304
+ self,
305
+ screen: pygame.Surface,
306
+ screen_pos: Tuple[int, int],
307
+ angle_deg: float,
308
+ ) -> None:
309
+ """Draw a small arc + angle text at a vertex."""
310
+ if angle_deg <= 0.5 or angle_deg >= 179.5:
311
+ return
312
+ arc_radius = 14
313
+ segments = max(4, int(angle_deg / 10))
314
+ angle_rad = math.radians(angle_deg)
315
+ pts = [screen_pos]
316
+ for i in range(segments + 1):
317
+ t = -angle_rad * i / segments
318
+ pts.append((
319
+ screen_pos[0] + arc_radius * math.cos(t),
320
+ screen_pos[1] + arc_radius * math.sin(t),
321
+ ))
322
+ if len(pts) >= 3:
323
+ wedge = pygame.Surface(self.rect.size, pygame.SRCALPHA)
324
+ pygame.draw.polygon(wedge, (*_COLORS["polygon_fill"], 60), pts)
325
+ screen.blit(wedge, self.rect.topleft)
326
+ label = self._font_sm.render(f"{angle_deg:.0f}°", True, _COLORS["text"])
327
+ label_x = screen_pos[0] + 10
328
+ label_y = screen_pos[1] - label.get_height() - 4
329
+ bg = pygame.Surface((label.get_width() + 4, label.get_height() + 2), pygame.SRCALPHA)
330
+ bg.fill((0, 0, 0, 180))
331
+ screen.blit(bg, (label_x - 2, label_y - 1))
332
+ screen.blit(label, (label_x, label_y))
333
+
274
334
  def clear_all(self) -> None:
275
335
  """Clear all polygons"""
276
336
  self.polygons = []
@@ -488,23 +548,26 @@ class CollisionPainter:
488
548
 
489
549
  # Keyboard shortcuts
490
550
  if event.type == pygame.KEYDOWN:
551
+ mods = pygame.key.get_mods()
552
+ ctrl = mods & (pygame.KMOD_LCTRL | pygame.KMOD_LMETA)
553
+
491
554
  # Track Shift state for edge draw
492
555
  if event.key in (pygame.K_LSHIFT, pygame.K_RSHIFT):
493
556
  self._shift_held = True
494
-
495
- # E - toggle edge draw mode
496
- elif event.key == pygame.K_e:
557
+
558
+ # Ctrl+E - toggle edge draw mode
559
+ elif ctrl and event.key == pygame.K_e:
497
560
  self.edge_draw_mode = not self.edge_draw_mode
498
561
  if not self.edge_draw_mode:
499
562
  self._edge_start = None
500
563
  return True
501
-
564
+
502
565
  # Enter - complete polygon
503
566
  elif event.key in (pygame.K_RETURN, pygame.K_KP_ENTER):
504
567
  if len(self.current_polygon) >= 3:
505
568
  self._complete_polygon()
506
569
  return True
507
-
570
+
508
571
  # Escape - cancel current polygon or deselect or close help
509
572
  elif event.key == pygame.K_ESCAPE:
510
573
  if self.show_help:
@@ -517,44 +580,45 @@ class CollisionPainter:
517
580
  self.selected_polygon_idx = None
518
581
  self.selected_vertex_idx = None
519
582
  return True
520
-
521
- # Delete - remove selected polygon
583
+
584
+ # Delete/Backspace - remove selected polygon or last vertex
522
585
  elif event.key in (pygame.K_DELETE, pygame.K_BACKSPACE):
523
586
  if self.selected_polygon_idx is not None:
524
587
  self._delete_polygon(self.selected_polygon_idx)
525
588
  return True
526
589
  elif self.current_polygon:
527
- # Remove last vertex
528
590
  self.current_polygon.pop()
529
591
  return True
530
-
531
- # G - toggle grid
532
- elif event.key == pygame.K_g:
592
+
593
+ # Ctrl+G - toggle grid
594
+ elif ctrl and event.key == pygame.K_g:
533
595
  self.show_grid = not self.show_grid
534
596
  return True
535
-
536
- # S - toggle snap (only if Ctrl is not held)
537
- elif event.key == pygame.K_s:
538
- mods = pygame.key.get_mods()
539
- ctrl_held = mods & (pygame.KMOD_CTRL | pygame.KMOD_LMETA)
540
- if not ctrl_held:
541
- self.snap_to_grid = not self.snap_to_grid
542
- return True
543
-
544
- # O - toggle one-way collision for selected polygon
545
- elif event.key == pygame.K_o:
597
+
598
+ # Ctrl+Shift+S - toggle snap (Ctrl+S reserved for save)
599
+ elif ctrl and event.key == pygame.K_s and (mods & pygame.KMOD_SHIFT):
600
+ self.snap_to_grid = not self.snap_to_grid
601
+ return True
602
+
603
+ # Ctrl+O - toggle one-way collision for selected polygon
604
+ elif ctrl and event.key == pygame.K_o:
546
605
  if self.selected_polygon_idx is not None:
547
606
  idx = self.selected_polygon_idx
548
607
  self.polygon_one_way[idx] = not self.polygon_one_way[idx]
549
608
  if self.on_polygon_modified:
550
609
  self.on_polygon_modified(idx)
551
610
  return True
552
-
553
- # R - reset view
554
- elif event.key == pygame.K_r:
611
+
612
+ # Ctrl+R - reset view
613
+ elif ctrl and event.key == pygame.K_r:
555
614
  self._center_view()
556
615
  self.zoom = 2.0
557
616
  return True
617
+
618
+ # Ctrl+A - toggle angle hints
619
+ elif ctrl and event.key == pygame.K_a:
620
+ self.show_angle_hints = not self.show_angle_hints
621
+ return True
558
622
 
559
623
  if event.type == pygame.KEYUP:
560
624
  if event.key in (pygame.K_LSHIFT, pygame.K_RSHIFT):
@@ -744,20 +808,21 @@ class CollisionPainter:
744
808
  ("Right-click / Enter", "Complete polygon"),
745
809
  ("Escape", "Cancel current polygon"),
746
810
  ("Shift (edge mode)", "Constrain to axis"),
747
- ("E", "Toggle edge draw mode"),
811
+ ("Ctrl+E", "Toggle edge draw mode"),
748
812
  ]),
749
813
  ("SELECTION", [
750
814
  ("Left-click polygon", "Select polygon"),
751
815
  ("Left-click vertex", "Select & drag vertex"),
752
816
  ("Delete / Backspace", "Remove selected polygon"),
753
- ("O", "Toggle one-way collision"),
817
+ ("Ctrl+O", "Toggle one-way collision"),
754
818
  ]),
755
819
  ("VIEW", [
756
820
  ("Mouse wheel", "Zoom in/out"),
757
821
  ("Middle mouse / Space+LMB", "Pan view"),
758
- ("G", "Toggle grid"),
759
- ("S", "Toggle snap to grid"),
760
- ("R", "Reset view"),
822
+ ("Ctrl+G", "Toggle grid"),
823
+ ("Ctrl+Shift+S", "Toggle snap to grid"),
824
+ ("Ctrl+R", "Reset view"),
825
+ ("Ctrl+A", "Toggle angle hints"),
761
826
  ]),
762
827
  ("MISC", [
763
828
  ("Info button (top-right)", "Toggle this help panel"),
@@ -905,6 +970,24 @@ class CollisionPainter:
905
970
  radius = VERTEX_HOVER_RADIUS if is_hovered_vertex else VERTEX_RADIUS
906
971
  pygame.draw.circle(screen, color, (px, py), radius)
907
972
  pygame.draw.circle(screen, (0, 0, 0), (px, py), radius, 1)
973
+
974
+ # One-way badge on selected polygon
975
+ if selected and one_way and len(polygon) >= 3:
976
+ cx = int(sum(p[0] for p in screen_points) / len(screen_points))
977
+ cy = int(sum(p[1] for p in screen_points) / len(screen_points))
978
+ badge = self._font_sm.render("ONE-WAY", True, (255, 255, 255))
979
+ bx = cx - badge.get_width() // 2
980
+ by = cy - badge.get_height() // 2
981
+ bg = pygame.Surface((badge.get_width() + 8, badge.get_height() + 4), pygame.SRCALPHA)
982
+ bg.fill((_COLORS["one_way"][0], _COLORS["one_way"][1], _COLORS["one_way"][2], 200))
983
+ screen.blit(bg, (bx - 4, by - 2))
984
+ screen.blit(badge, (bx, by))
985
+
986
+ # Angle hints
987
+ if self.show_angle_hints:
988
+ for i, (px, py) in enumerate(screen_points):
989
+ angle = self._get_interior_angle(polygon, i)
990
+ self._draw_angle_hint(screen, (px, py), angle)
908
991
 
909
992
  def _draw_current_polygon(self, screen: pygame.Surface) -> None:
910
993
  """Draw the polygon currently being drawn"""
@@ -934,6 +1017,25 @@ class CollisionPainter:
934
1017
  radius = VERTEX_HOVER_RADIUS if is_first else VERTEX_RADIUS
935
1018
  pygame.draw.circle(screen, color, (px, py), radius)
936
1019
  pygame.draw.circle(screen, (0, 0, 0), (px, py), radius, 1)
1020
+
1021
+ # Angle hints for vertices with both neighbors
1022
+ if self.show_angle_hints and len(self.current_polygon) >= 3:
1023
+ for i, (px, py) in enumerate(screen_points):
1024
+ if i == 0 or i == len(screen_points) - 1:
1025
+ continue
1026
+ poly = self.current_polygon
1027
+ prev = poly[i - 1]
1028
+ curr = poly[i]
1029
+ nxt = poly[i + 1]
1030
+ v1 = (prev[0] - curr[0], prev[1] - curr[1])
1031
+ v2 = (nxt[0] - curr[0], nxt[1] - curr[1])
1032
+ dot = v1[0] * v2[0] + v1[1] * v2[1]
1033
+ mag1 = math.hypot(v1[0], v1[1])
1034
+ mag2 = math.hypot(v2[0], v2[1])
1035
+ if mag1 > 0.001 and mag2 > 0.001:
1036
+ cos_a = max(-1.0, min(1.0, dot / (mag1 * mag2)))
1037
+ angle = math.degrees(math.acos(cos_a))
1038
+ self._draw_angle_hint(screen, (px, py), angle)
937
1039
 
938
1040
  def _draw_status(self, screen: pygame.Surface) -> None:
939
1041
  """Draw status text"""
@@ -949,12 +1051,13 @@ class CollisionPainter:
949
1051
  one_way = self.polygon_one_way[self.selected_polygon_idx]
950
1052
  one_way_str = " (ONE-WAY)" if one_way else ""
951
1053
  lines.append(f"Selected polygon {self.selected_polygon_idx}{one_way_str}")
952
- lines.append("Delete to remove, O to toggle one-way")
1054
+ lines.append("Delete to remove, Ctrl+O to toggle one-way")
953
1055
  else:
954
1056
  lines.append("Click to add vertices, right-click to complete")
955
1057
 
956
- lines.append(f"Zoom: {self.zoom:.1f}x | Grid: {'ON' if self.show_grid else 'OFF'} (G) | Snap: {'ON' if self.snap_to_grid else 'OFF'} (S)")
957
- lines.append(f"Polygons: {len(self.polygons)} | R: reset view")
1058
+ snap_str = f"Snap: {'ON' if self.snap_to_grid else 'OFF'} (Ctrl+Shift+S)"
1059
+ lines.append(f"Zoom: {self.zoom:.1f}x | Grid: {'ON' if self.show_grid else 'OFF'} (Ctrl+G) | {snap_str}")
1060
+ lines.append(f"Polygons: {len(self.polygons)} | Ctrl+R: reset | Ctrl+A: angle hints {'ON' if self.show_angle_hints else 'OFF'}")
958
1061
 
959
1062
  y = self.rect.y + 5
960
1063
  for line in lines:
@@ -30,6 +30,17 @@ from utils.icon_manager import icon_manager
30
30
  from utils.error_handler import error_handler, error_context
31
31
  from widgets.ui.theme import COLORS, FONTS
32
32
  from widgets.ui.draw_utils import draw_panel, draw_button
33
+ from widgets.ui.checkbox import Checkbox
34
+
35
+ # Widget panel layout constants
36
+ WP_PADDING_X = 12
37
+ WP_PADDING_Y = 38
38
+ WP_SECTION_HEIGHT = 28
39
+ WP_CHECKBOX_ROW = 30
40
+ WP_GRID_ROW = 36
41
+ WP_GRID_BTN = 28
42
+ WP_GRID_GAP = 68
43
+ WP_TITLE_Y = 10
33
44
 
34
45
 
35
46
  class TilesetCollisionEditor:
@@ -75,6 +86,9 @@ class TilesetCollisionEditor:
75
86
  # Auto-tile propagation: group_id -> [variant_ids]
76
87
  self._propagation_groups: Dict[str, List[int]] = {}
77
88
 
89
+ # Tiles the user explicitly cleared — propagation won't re-add to these
90
+ self._user_cleared_tiles: Set[int] = set()
91
+
78
92
  # Collision library
79
93
  self.library = TilesetCollisionLibrary(
80
94
  tileset_name=self._tileset_name,
@@ -91,6 +105,7 @@ class TilesetCollisionEditor:
91
105
  self.toolbar_height = 40
92
106
  self.tileset_selector_height = 250 # Resizable
93
107
  self.painted_tiles_width = 200
108
+ self.widget_panel_width = 200
94
109
  self._resizing_selector = False
95
110
  self._resize_start_y = 0
96
111
  self._resize_start_height = 0
@@ -128,6 +143,9 @@ class TilesetCollisionEditor:
128
143
  # Toolbar buttons
129
144
  self._setup_toolbar_buttons()
130
145
 
146
+ # Widget panel buttons
147
+ self._setup_widget_buttons()
148
+
131
149
  # Load collision data for first tile
132
150
  self._load_tile_collision_for_selection()
133
151
 
@@ -151,6 +169,40 @@ class TilesetCollisionEditor:
151
169
  "hovered": False,
152
170
  })
153
171
 
172
+ def _setup_widget_buttons(self) -> None:
173
+ """Setup widget panel buttons"""
174
+ self._chk_one_way = Checkbox(
175
+ Rect(0, 0, 0, 0), "One-Way",
176
+ disabled=True,
177
+ on_changed=lambda _: self.painter.toggle_one_way(),
178
+ )
179
+ self._chk_angle = Checkbox(
180
+ Rect(0, 0, 0, 0), "Angle Hints",
181
+ on_changed=lambda v: setattr(self.painter, "show_angle_hints", v),
182
+ )
183
+ self._chk_grid = Checkbox(
184
+ Rect(0, 0, 0, 0), "Grid",
185
+ checked=True,
186
+ on_changed=lambda v: setattr(self.painter, "show_grid", v),
187
+ )
188
+ self._chk_snap = Checkbox(
189
+ Rect(0, 0, 0, 0), "Snap to Grid",
190
+ on_changed=lambda v: setattr(self.painter, "snap_to_grid", v),
191
+ )
192
+
193
+ self._widget_items: List[tuple] = [
194
+ ("section", "POLYGON"),
195
+ ("checkbox", self._chk_one_way),
196
+ ("checkbox", self._chk_angle),
197
+ ("section", "DISPLAY"),
198
+ ("checkbox", self._chk_grid),
199
+ ("section", "GRID SNAP"),
200
+ ("grid_dec", None),
201
+ ("grid_val", None),
202
+ ("grid_inc", None),
203
+ ("checkbox", self._chk_snap),
204
+ ]
205
+
154
206
  def _save_collision(self) -> None:
155
207
  """Save collision via toolbar button"""
156
208
  collision_dir = self._get_collision_dir()
@@ -200,6 +252,64 @@ class TilesetCollisionEditor:
200
252
  shape_count = len(self._clipboard_polygons)
201
253
  self._show_toast(f"Pasted {shape_count} shape{'s' if shape_count != 1 else ''} to {tile_count} tile{'s' if tile_count != 1 else ''}")
202
254
 
255
+ def _layout_widget_panel(self) -> None:
256
+ """Pre-compute clickable widget rects (checkbox + grid buttons)"""
257
+ panel = self.widget_panel_rect
258
+ px = panel.x + WP_PADDING_X
259
+ pw = panel.w - WP_PADDING_X * 2
260
+ y = panel.y + WP_PADDING_Y
261
+ for kind, data in self._widget_items:
262
+ if kind == "section":
263
+ y += WP_SECTION_HEIGHT
264
+ elif kind == "checkbox":
265
+ data.rect = Rect(px, y, pw, WP_CHECKBOX_ROW)
266
+ y += WP_CHECKBOX_ROW
267
+ elif kind == "grid_dec":
268
+ self._grid_dec_rect = Rect(px, y, WP_GRID_BTN, WP_GRID_BTN)
269
+ elif kind == "grid_val":
270
+ self._grid_val_rect = Rect(px, y, 0, WP_GRID_BTN)
271
+ elif kind == "grid_inc":
272
+ self._grid_inc_rect = Rect(px + WP_GRID_GAP, y, WP_GRID_BTN, WP_GRID_BTN)
273
+ y += WP_GRID_ROW
274
+
275
+ def _sync_widget_state(self) -> None:
276
+ """Sync checkbox states from painter attributes before draw"""
277
+ p = self.painter
278
+ self._chk_grid.checked = p.show_grid
279
+ self._chk_snap.checked = p.snap_to_grid
280
+ self._chk_angle.checked = p.show_angle_hints
281
+ sel = p.selected_polygon_idx
282
+ if sel is not None and 0 <= sel < len(p.polygon_one_way):
283
+ self._chk_one_way.checked = p.polygon_one_way[sel]
284
+ self._chk_one_way.disabled = False
285
+ else:
286
+ self._chk_one_way.checked = False
287
+ self._chk_one_way.disabled = True
288
+
289
+ def _handle_widget_button_clicks(self, events: List[pygame.event.Event]) -> None:
290
+ """Handle widget panel button clicks"""
291
+ self._sync_widget_state()
292
+ self._layout_widget_panel()
293
+ painter = self.painter
294
+ mouse = pygame.mouse.get_pos()
295
+
296
+ # Pass click events to checkboxes
297
+ for event in events:
298
+ if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
299
+ pos = event.pos
300
+ if self._chk_one_way.handle_event(event):
301
+ continue
302
+ if self._chk_angle.handle_event(event):
303
+ continue
304
+ if self._chk_grid.handle_event(event):
305
+ continue
306
+ if self._chk_snap.handle_event(event):
307
+ continue
308
+ if self._grid_dec_rect.collidepoint(pos):
309
+ painter.grid_size = max(1, painter.grid_size - 1)
310
+ elif self._grid_inc_rect.collidepoint(pos):
311
+ painter.grid_size = min(64, painter.grid_size + 1)
312
+
203
313
  def _position_toolbar_buttons(self) -> None:
204
314
  """Position toolbar buttons in the toolbar area"""
205
315
  if not hasattr(self, '_toolbar_buttons'):
@@ -226,6 +336,68 @@ class TilesetCollisionEditor:
226
336
  if button["rect"].collidepoint(mouse_pos):
227
337
  button["action"]()
228
338
 
339
+ def _draw_widget_panel(self, surface: Surface) -> None:
340
+ """Draw the widget panel on the right side"""
341
+ self._sync_widget_state()
342
+ panel = self.widget_panel_rect
343
+ pygame.draw.rect(surface, COLORS.panel_alt, panel)
344
+ pygame.draw.rect(surface, COLORS.border_soft, panel, 1)
345
+
346
+ px = panel.x + WP_PADDING_X
347
+ pw = panel.w - WP_PADDING_X * 2
348
+ y = panel.y + WP_PADDING_Y
349
+
350
+ title = self._font.render("Tools", True, COLORS.text)
351
+ surface.blit(title, (px, panel.y + WP_TITLE_Y))
352
+
353
+ painter = self.painter
354
+ mouse = pygame.mouse.get_pos()
355
+
356
+ for kind, data in self._widget_items:
357
+ if kind == "section":
358
+ lbl = self._font_sm.render(data, True, (140, 140, 160))
359
+ surface.blit(lbl, (px, y + 4))
360
+ sep_y = y + lbl.get_height() + 8
361
+ pygame.draw.line(surface, (50, 50, 55), (px, sep_y), (px + pw, sep_y))
362
+ y += WP_SECTION_HEIGHT
363
+
364
+ elif kind == "checkbox":
365
+ data.rect = Rect(px, y, pw, WP_CHECKBOX_ROW)
366
+ data.draw(surface)
367
+ y += WP_CHECKBOX_ROW
368
+
369
+ elif kind == "grid_dec":
370
+ r = Rect(px, y, WP_GRID_BTN, WP_GRID_BTN)
371
+ self._grid_dec_rect = r
372
+ hovered = r.collidepoint(mouse)
373
+ bg = (55, 55, 62) if hovered else (45, 45, 50)
374
+ pygame.draw.rect(surface, bg, r, border_radius=4)
375
+ pygame.draw.rect(surface, (65, 65, 72), r, 1, border_radius=4)
376
+ lbl = self._font_sm.render("-", True, COLORS.text)
377
+ tx = r.centerx - lbl.get_width() // 2
378
+ ty = r.centery - lbl.get_height() // 2
379
+ surface.blit(lbl, (tx, ty))
380
+
381
+ elif kind == "grid_val":
382
+ self._grid_val_rect = Rect(px, y, 0, WP_GRID_BTN)
383
+ val_surf = self._font_sm.render(str(painter.grid_size), True, COLORS.text)
384
+ vx = px + WP_GRID_BTN + 8
385
+ vy = y + (WP_GRID_BTN - val_surf.get_height()) // 2
386
+ surface.blit(val_surf, (vx, vy))
387
+
388
+ elif kind == "grid_inc":
389
+ r = Rect(px + WP_GRID_GAP, y, WP_GRID_BTN, WP_GRID_BTN)
390
+ self._grid_inc_rect = r
391
+ hovered = r.collidepoint(mouse)
392
+ bg = (55, 55, 62) if hovered else (45, 45, 50)
393
+ pygame.draw.rect(surface, bg, r, border_radius=4)
394
+ pygame.draw.rect(surface, (65, 65, 72), r, 1, border_radius=4)
395
+ lbl = self._font_sm.render("+", True, COLORS.text)
396
+ tx = r.centerx - lbl.get_width() // 2
397
+ ty = r.centery - lbl.get_height() // 2
398
+ surface.blit(lbl, (tx, ty))
399
+ y += WP_GRID_ROW
400
+
229
401
  def _draw_toolbar_buttons(self, surface: Surface) -> None:
230
402
  """Draw toolbar buttons"""
231
403
  for button in self._toolbar_buttons:
@@ -272,7 +444,14 @@ class TilesetCollisionEditor:
272
444
  self.painter_rect = Rect(
273
445
  self.rect.x + self.painted_tiles_width,
274
446
  middle_y,
275
- self.rect.w - self.painted_tiles_width,
447
+ self.rect.w - self.painted_tiles_width - self.widget_panel_width,
448
+ middle_h
449
+ )
450
+
451
+ self.widget_panel_rect = Rect(
452
+ self.rect.right - self.widget_panel_width,
453
+ middle_y,
454
+ self.widget_panel_width,
276
455
  middle_h
277
456
  )
278
457
 
@@ -342,7 +521,11 @@ class TilesetCollisionEditor:
342
521
  del self.library.tiles[tile_id]
343
522
  if self.consumer:
344
523
  self.consumer.on_collision_deleted(tile_id)
524
+ # Mark as user-cleared so propagation doesn't re-add it
525
+ self._user_cleared_tiles.add(tile_id)
345
526
  else:
527
+ # User painted collision — remove from cleared set
528
+ self._user_cleared_tiles.discard(tile_id)
346
529
  # Create collision shapes
347
530
  shapes = [
348
531
  CollisionPolygon(vertices=poly, one_way=one_way)
@@ -471,7 +654,10 @@ class TilesetCollisionEditor:
471
654
  continue
472
655
 
473
656
  # Propagate to all variants in the group that lack collision
657
+ # Skip tiles the user explicitly cleared
474
658
  for vid in variant_ids:
659
+ if vid in self._user_cleared_tiles:
660
+ continue
475
661
  existing = self.library.tiles.get(vid)
476
662
  if existing is None:
477
663
  self.library.tiles[vid] = TileCollisionData(
@@ -696,6 +882,9 @@ class TilesetCollisionEditor:
696
882
  # Draw tileset selector
697
883
  self._draw_tileset_selector(screen)
698
884
 
885
+ # Draw widget panel
886
+ self._draw_widget_panel(screen)
887
+
699
888
  # Draw toast overlay
700
889
  self._draw_toast(screen)
701
890
 
@@ -924,8 +1113,9 @@ class TilesetCollisionEditor:
924
1113
 
925
1114
  self.handle_event(event)
926
1115
 
927
- # Handle toolbar button clicks
1116
+ # Handle toolbar and widget button clicks
928
1117
  self._handle_toolbar_button_clicks(events)
1118
+ self._handle_widget_button_clicks(events)
929
1119
 
930
1120
  screen.fill((20, 20, 20))
931
1121
  self.draw(screen)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tilemap-editor
3
- Version: 3.3.3
3
+ Version: 3.3.4
4
4
  Summary: Pygame tilemap editor with SVG icons, professional UI, and sprite animation tools
5
5
  Author: tilemap editor contributors
6
6
  License: Proprietary
@@ -166,6 +166,7 @@ src/widgets/regex_automap_designer.py
166
166
  src/widgets/spritesheet_grid.py
167
167
  src/widgets/tile_grid.py
168
168
  src/widgets/tile_selector.py
169
+ src/widgets/ui/checkbox.py
169
170
  src/widgets/ui/collision_layer_mask.py
170
171
  src/widgets/ui/collision_layer_sidebar.py
171
172
  src/widgets/ui/confirm_dialog.py
@@ -333,12 +333,10 @@ class ImagePreview:
333
333
  new_height = target_height
334
334
  new_width = int(target_height * aspect_ratio)
335
335
 
336
- source_surface = self.current_image.convert_alpha()
337
-
338
336
  scaled_surface = pygame.transform.smoothscale(
339
- source_surface, (new_width, new_height)
337
+ self.current_image, (new_width, new_height)
340
338
  )
341
-
339
+
342
340
  self.scaled_cache = scaled_surface
343
341
  self.cached_target_size = (target_width, target_height)
344
342