tilemap-editor 4.3.9__tar.gz → 4.4.0__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 (248) hide show
  1. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/CHANGELOG.md +10 -0
  2. {tilemap_editor-4.3.9/src/tilemap_editor.egg-info → tilemap_editor-4.4.0}/PKG-INFO +1 -1
  3. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/pyproject.toml +1 -1
  4. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/editor.py +2 -0
  5. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0/src/tilemap_editor.egg-info}/PKG-INFO +1 -1
  6. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap_editor.egg-info/SOURCES.txt +3 -0
  7. tilemap_editor-4.4.0/src/utils/context_dispatch.py +83 -0
  8. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/widgets/layer_selector.py +19 -11
  9. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/widgets/tile_grid.py +40 -2
  10. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/widgets/tile_selector.py +88 -17
  11. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/widgets/ui/button.py +17 -3
  12. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/widgets/ui/node_editor.py +20 -8
  13. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/widgets/ui/property_editor.py +76 -20
  14. tilemap_editor-4.4.0/tests/test_context_dispatch.py +287 -0
  15. tilemap_editor-4.4.0/tests/test_object_properties_context.py +123 -0
  16. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/tests/test_tile_selector_pick.py +41 -0
  17. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/LICENSE +0 -0
  18. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/MANIFEST.in +0 -0
  19. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/README.md +0 -0
  20. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/assets/fonts/jetbrain-fonts/JetBrainsMono-Bold.ttf +0 -0
  21. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/assets/fonts/jetbrain-fonts/JetBrainsMono-BoldItalic.ttf +0 -0
  22. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/assets/fonts/jetbrain-fonts/JetBrainsMono-Italic.ttf +0 -0
  23. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/assets/fonts/jetbrain-fonts/JetBrainsMono-Regular.ttf +0 -0
  24. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/assets/fonts/noto/NotoSans-Bold.ttf +0 -0
  25. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/assets/fonts/noto/NotoSans-BoldItalic.ttf +0 -0
  26. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/assets/fonts/noto/NotoSans-Italic.ttf +0 -0
  27. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/assets/fonts/noto/NotoSans-Regular.ttf +0 -0
  28. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/assets/icons/arrow-down.svg +0 -0
  29. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/assets/icons/check.svg +0 -0
  30. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/assets/icons/checked.svg +0 -0
  31. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/assets/icons/close.svg +0 -0
  32. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/assets/icons/duplicate.svg +0 -0
  33. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/assets/icons/error.svg +0 -0
  34. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/assets/icons/file.svg +0 -0
  35. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/assets/icons/filedead.svg +0 -0
  36. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/assets/icons/fit.svg +0 -0
  37. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/assets/icons/folder.svg +0 -0
  38. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/assets/icons/grid.svg +0 -0
  39. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/assets/icons/image.svg +0 -0
  40. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/assets/icons/info.svg +0 -0
  41. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/assets/icons/load.svg +0 -0
  42. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/assets/icons/loop.svg +0 -0
  43. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/assets/icons/onion.svg +0 -0
  44. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/assets/icons/pan.svg +0 -0
  45. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/assets/icons/pause.svg +0 -0
  46. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/assets/icons/pencil.svg +0 -0
  47. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/assets/icons/play.svg +0 -0
  48. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/assets/icons/plus.svg +0 -0
  49. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/assets/icons/radio.svg +0 -0
  50. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/assets/icons/reset.svg +0 -0
  51. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/assets/icons/save.svg +0 -0
  52. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/assets/icons/skip-back.svg +0 -0
  53. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/assets/icons/skip-forward.svg +0 -0
  54. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/assets/icons/stop.svg +0 -0
  55. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/assets/icons/tilemap.svg +0 -0
  56. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/assets/icons/tileset.svg +0 -0
  57. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/assets/icons/unchecked.svg +0 -0
  58. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/assets/icons/warning.svg +0 -0
  59. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/assets/icons/zoomin.svg +0 -0
  60. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/assets/icons/zoomout.svg +0 -0
  61. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/setup.cfg +0 -0
  62. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/constants.py +0 -0
  63. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/event_map.py +0 -0
  64. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/layers.py +0 -0
  65. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/main.py +0 -0
  66. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/node_manager.py +0 -0
  67. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/nodes.py +0 -0
  68. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/plugins/__init__.py +0 -0
  69. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/plugins/character_collision/__init__.py +0 -0
  70. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/plugins/character_collision/editor.py +0 -0
  71. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/plugins/character_collision/models.py +0 -0
  72. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/plugins/character_collision/protocols.py +0 -0
  73. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/plugins/character_collision/shape_editor.py +0 -0
  74. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/plugins/character_collision/standalone.py +0 -0
  75. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/plugins/object_tileset_collision/__init__.py +0 -0
  76. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/plugins/object_tileset_collision/editor.py +0 -0
  77. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/plugins/object_tileset_collision/models.py +0 -0
  78. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/plugins/object_tileset_collision/protocols.py +0 -0
  79. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/plugins/object_tileset_collision/standalone.py +0 -0
  80. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/plugins/sprite_animation/__init__.py +0 -0
  81. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/plugins/sprite_animation/__main__.py +0 -0
  82. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/plugins/sprite_animation/clipboard_util.py +0 -0
  83. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/plugins/sprite_animation/editor.py +0 -0
  84. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/plugins/sprite_animation/frame_picker.py +0 -0
  85. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/plugins/sprite_animation/models.py +0 -0
  86. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/plugins/sprite_animation/preview.py +0 -0
  87. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/plugins/sprite_animation/protocols.py +0 -0
  88. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/plugins/sprite_animation/runtime_load.py +0 -0
  89. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/plugins/sprite_animation/standalone.py +0 -0
  90. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/plugins/sprite_animation/timeline.py +0 -0
  91. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/plugins/sprite_animation/validation.py +0 -0
  92. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/plugins/sprite_editor/__init__.py +0 -0
  93. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/plugins/sprite_editor/camera.py +0 -0
  94. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/plugins/sprite_editor/clipboard.py +0 -0
  95. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/plugins/sprite_editor/commands.py +0 -0
  96. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/plugins/sprite_editor/dialogs.py +0 -0
  97. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/plugins/sprite_editor/document.py +0 -0
  98. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/plugins/sprite_editor/editor.py +0 -0
  99. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/plugins/sprite_editor/overlays.py +0 -0
  100. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/plugins/sprite_editor/region_export.py +0 -0
  101. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/plugins/sprite_editor/selection.py +0 -0
  102. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/plugins/sprite_editor/standalone.py +0 -0
  103. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/plugins/sprite_editor/tools.py +0 -0
  104. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/plugins/sprite_editor/viewport.py +0 -0
  105. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/plugins/tileset_collision/__init__.py +0 -0
  106. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/plugins/tileset_collision/collision_painter.py +0 -0
  107. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/plugins/tileset_collision/editor.py +0 -0
  108. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/plugins/tileset_collision/models.py +0 -0
  109. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/plugins/tileset_collision/protocols.py +0 -0
  110. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/plugins/tileset_collision/standalone.py +0 -0
  111. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/run.py +0 -0
  112. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/standalone_automap.py +0 -0
  113. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/standalone_error_console.py +0 -0
  114. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/standalone_filemanager.py +0 -0
  115. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/standalone_image_viewer.py +0 -0
  116. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap.py +0 -0
  117. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap_editor/__init__.py +0 -0
  118. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap_editor/__main__.py +0 -0
  119. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap_editor/assets/__init__.py +0 -0
  120. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap_editor/assets/fonts/jetbrain-fonts/JetBrainsMono-Bold.ttf +0 -0
  121. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap_editor/assets/fonts/jetbrain-fonts/JetBrainsMono-BoldItalic.ttf +0 -0
  122. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap_editor/assets/fonts/jetbrain-fonts/JetBrainsMono-Italic.ttf +0 -0
  123. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap_editor/assets/fonts/jetbrain-fonts/JetBrainsMono-Regular.ttf +0 -0
  124. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap_editor/assets/fonts/noto/NotoSans-Bold.ttf +0 -0
  125. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap_editor/assets/fonts/noto/NotoSans-BoldItalic.ttf +0 -0
  126. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap_editor/assets/fonts/noto/NotoSans-Italic.ttf +0 -0
  127. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap_editor/assets/fonts/noto/NotoSans-Regular.ttf +0 -0
  128. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap_editor/assets/icons/ToolMove.svg +0 -0
  129. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap_editor/assets/icons/arrow-down.svg +0 -0
  130. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap_editor/assets/icons/check.svg +0 -0
  131. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap_editor/assets/icons/checked.svg +0 -0
  132. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap_editor/assets/icons/close.svg +0 -0
  133. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap_editor/assets/icons/duplicate.svg +0 -0
  134. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap_editor/assets/icons/error.svg +0 -0
  135. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap_editor/assets/icons/file.svg +0 -0
  136. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap_editor/assets/icons/filedead.svg +0 -0
  137. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap_editor/assets/icons/fit.svg +0 -0
  138. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap_editor/assets/icons/fold_down_arrow.svg +0 -0
  139. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap_editor/assets/icons/folder.svg +0 -0
  140. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap_editor/assets/icons/gear.svg +0 -0
  141. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap_editor/assets/icons/grid.svg +0 -0
  142. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap_editor/assets/icons/image.svg +0 -0
  143. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap_editor/assets/icons/info.svg +0 -0
  144. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap_editor/assets/icons/load.svg +0 -0
  145. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap_editor/assets/icons/loop.svg +0 -0
  146. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap_editor/assets/icons/miniobj.svg +0 -0
  147. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap_editor/assets/icons/onion.svg +0 -0
  148. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap_editor/assets/icons/pan.svg +0 -0
  149. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap_editor/assets/icons/pause.svg +0 -0
  150. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap_editor/assets/icons/pencil.svg +0 -0
  151. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap_editor/assets/icons/play.svg +0 -0
  152. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap_editor/assets/icons/plus.svg +0 -0
  153. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap_editor/assets/icons/radio.svg +0 -0
  154. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap_editor/assets/icons/reset.svg +0 -0
  155. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap_editor/assets/icons/save.svg +0 -0
  156. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap_editor/assets/icons/skip-back.svg +0 -0
  157. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap_editor/assets/icons/skip-forward.svg +0 -0
  158. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap_editor/assets/icons/stop.svg +0 -0
  159. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap_editor/assets/icons/tilemap.svg +0 -0
  160. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap_editor/assets/icons/tileset.svg +0 -0
  161. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap_editor/assets/icons/unchecked.svg +0 -0
  162. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap_editor/assets/icons/unfold_right_arrow.svg +0 -0
  163. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap_editor/assets/icons/warning.svg +0 -0
  164. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap_editor/assets/icons/zoomin.svg +0 -0
  165. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap_editor/assets/icons/zoomout.svg +0 -0
  166. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap_editor/cli.py +0 -0
  167. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap_editor/main.py +0 -0
  168. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap_editor/settings.py +0 -0
  169. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap_editor.egg-info/dependency_links.txt +0 -0
  170. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap_editor.egg-info/entry_points.txt +0 -0
  171. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap_editor.egg-info/requires.txt +0 -0
  172. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/tilemap_editor.egg-info/top_level.txt +0 -0
  173. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/ttypes/__init__.py +0 -0
  174. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/ttypes/tilemap.py +0 -0
  175. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/utils/__init__.py +0 -0
  176. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/utils/editor_preference.py +0 -0
  177. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/utils/error_handler.py +0 -0
  178. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/utils/font_manager.py +0 -0
  179. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/utils/history.py +0 -0
  180. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/utils/icon_manager.py +0 -0
  181. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/utils/icons_cache.py +0 -0
  182. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/utils/log_capture.py +0 -0
  183. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/utils/natural_sort.py +0 -0
  184. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/utils/project_paths.py +0 -0
  185. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/utils/property_suggestions.py +0 -0
  186. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/utils/serialization.py +0 -0
  187. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/utils/standalone.py +0 -0
  188. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/utils/validation.py +0 -0
  189. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/widgets/__init__.py +0 -0
  190. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/widgets/automap_models.py +0 -0
  191. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/widgets/autotile_template.py +0 -0
  192. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/widgets/autotiler.py +0 -0
  193. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/widgets/filemanager.py +0 -0
  194. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/widgets/input.py +0 -0
  195. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/widgets/map_properties.py +0 -0
  196. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/widgets/mapsetup.py +0 -0
  197. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/widgets/particle_presets.py +0 -0
  198. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/widgets/particle_system.py +0 -0
  199. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/widgets/regex_automap_designer.py +0 -0
  200. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/widgets/ui/checkbox.py +0 -0
  201. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/widgets/ui/collision_layer_mask.py +0 -0
  202. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/widgets/ui/collision_layer_sidebar.py +0 -0
  203. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/widgets/ui/confirm_dialog.py +0 -0
  204. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/widgets/ui/dialog_base.py +0 -0
  205. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/widgets/ui/drag_tracker.py +0 -0
  206. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/widgets/ui/draw_utils.py +0 -0
  207. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/widgets/ui/fileinput.py +0 -0
  208. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/widgets/ui/label.py +0 -0
  209. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/widgets/ui/layer_type_dialog.py +0 -0
  210. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/widgets/ui/menubar.py +0 -0
  211. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/widgets/ui/mode_indicator.py +0 -0
  212. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/widgets/ui/node_selector.py +0 -0
  213. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/widgets/ui/notification.py +0 -0
  214. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/widgets/ui/particle_config_dialog.py +0 -0
  215. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/widgets/ui/region_selector.py +0 -0
  216. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/widgets/ui/resize_map_dialog.py +0 -0
  217. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/widgets/ui/scrollbar.py +0 -0
  218. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/widgets/ui/sidebar_container.py +0 -0
  219. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/widgets/ui/splitter.py +0 -0
  220. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/widgets/ui/status_bar.py +0 -0
  221. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/widgets/ui/theme.py +0 -0
  222. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/widgets/ui/tileset_type_dialog.py +0 -0
  223. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/widgets/ui/toast.py +0 -0
  224. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/widgets/ui/tool_manager.py +0 -0
  225. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/widgets/ui/toolbar.py +0 -0
  226. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/widgets/ui/tooltip.py +0 -0
  227. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/widgets/ui/tree_widget.py +0 -0
  228. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/src/widgets/widget_base.py +0 -0
  229. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/tests/test_animation_library_grid_offset.py +0 -0
  230. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/tests/test_collision_layer_mask.py +0 -0
  231. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/tests/test_collision_layer_sidebar.py +0 -0
  232. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/tests/test_drag_tracker.py +0 -0
  233. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/tests/test_editor_pan_mode.py +0 -0
  234. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/tests/test_gid_collision.py +0 -0
  235. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/tests/test_layers_autotile.py +0 -0
  236. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/tests/test_map_resize_model.py +0 -0
  237. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/tests/test_natural_sort.py +0 -0
  238. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/tests/test_project_paths.py +0 -0
  239. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/tests/test_render_scale.py +0 -0
  240. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/tests/test_serialization_regression.py +0 -0
  241. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/tests/test_sprite_animation_editor_grid.py +0 -0
  242. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/tests/test_sprite_editor_commands.py +0 -0
  243. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/tests/test_sprite_editor_interaction.py +0 -0
  244. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/tests/test_sprite_editor_models.py +0 -0
  245. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/tests/test_tile_grid_selection.py +0 -0
  246. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/tests/test_tilemap_save.py +0 -0
  247. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/tests/test_timeline_invalidate_cache.py +0 -0
  248. {tilemap_editor-4.3.9 → tilemap_editor-4.4.0}/tests/test_toolbar_tools.py +0 -0
@@ -1,6 +1,16 @@
1
1
  # Changelog
2
2
 
3
3
  <!-- towncrier release notes start -->
4
+ # tilemap_editor 4.4.0 (2026-08-19)
5
+
6
+ ## Features
7
+
8
+ - Property editor: added a remove button for individual properties and object-level property support. Right-clicking a map object, tileset image, or tree row now opens the property editor through a context dispatch system, and object properties can be edited on the placed object directly. (context-property-editor)
9
+
10
+ ## Bug Fixes
11
+
12
+ - Object picking on object layers now hits the visually topmost overlapping object: right-click property open and left-click variant select test objects in reverse draw order. (object-pick-order)
13
+
4
14
  # tilemap_editor 4.3.9 (2026-08-04)
5
15
 
6
16
  ## Bug Fixes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tilemap-editor
3
- Version: 4.3.9
3
+ Version: 4.4.0
4
4
  Summary: Pygame tilemap editor with collision, animation, sprite & particle 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 = "4.3.9"
7
+ version = "4.4.0"
8
8
  description = "Pygame tilemap editor with collision, animation, sprite & particle tools"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -21,6 +21,7 @@ from constants import BASE_PATH
21
21
  from node_manager import NodeManager
22
22
  from tilemap import Tilemap
23
23
  from utils import error_context, error_handler
24
+ from utils.context_dispatch import PropertyContextDispatcher
24
25
  from utils.property_suggestions import PropertySuggestionRegistry
25
26
  from utils.standalone import launch_standalone
26
27
  from widgets.autotiler import AutotileRuleDesigner
@@ -214,6 +215,7 @@ class Editor:
214
215
  self.notifications = NotificationManager(self)
215
216
  self.tooltip = TooltipManager()
216
217
  self.suggestion_registry = PropertySuggestionRegistry()
218
+ self.context_dispatch = PropertyContextDispatcher()
217
219
  self.error_console_process: subprocess.Popen | None = None
218
220
  self.property_editor: PropertyEditor | None = None
219
221
  self.particle_config_dialog: ParticleConfigDialog | None = None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tilemap-editor
3
- Version: 4.3.9
3
+ Version: 4.4.0
4
4
  Summary: Pygame tilemap editor with collision, animation, sprite & particle tools
5
5
  Author: tilemap editor contributors
6
6
  License: Proprietary
@@ -161,6 +161,7 @@ src/tilemap_editor/assets/icons/zoomout.svg
161
161
  src/ttypes/__init__.py
162
162
  src/ttypes/tilemap.py
163
163
  src/utils/__init__.py
164
+ src/utils/context_dispatch.py
164
165
  src/utils/editor_preference.py
165
166
  src/utils/error_handler.py
166
167
  src/utils/font_manager.py
@@ -223,12 +224,14 @@ src/widgets/ui/tree_widget.py
223
224
  tests/test_animation_library_grid_offset.py
224
225
  tests/test_collision_layer_mask.py
225
226
  tests/test_collision_layer_sidebar.py
227
+ tests/test_context_dispatch.py
226
228
  tests/test_drag_tracker.py
227
229
  tests/test_editor_pan_mode.py
228
230
  tests/test_gid_collision.py
229
231
  tests/test_layers_autotile.py
230
232
  tests/test_map_resize_model.py
231
233
  tests/test_natural_sort.py
234
+ tests/test_object_properties_context.py
232
235
  tests/test_project_paths.py
233
236
  tests/test_render_scale.py
234
237
  tests/test_serialization_regression.py
@@ -0,0 +1,83 @@
1
+ """Context-based property event dispatch.
2
+
3
+ Godot-style "context slots": widgets emit a ``PropertyContext`` describing
4
+ what was clicked (right-click on map object, tileset image, tree row, ...);
5
+ the dispatcher routes it to the registered opener (shows the PropertyEditor)
6
+ or saver (applies the saved properties to the context target).
7
+
8
+ Same input event everywhere, the context decides the polymorphic handler.
9
+ Unregistered kinds no-op cleanly (Qt-style propagation fallback).
10
+
11
+ Example::
12
+
13
+ editor.context_dispatch.open(PropertyContext(ContextKind.TILESET, ts))
14
+ """
15
+
16
+ from collections.abc import Callable
17
+ from dataclasses import dataclass, field
18
+ from enum import Enum, auto
19
+ from typing import Any
20
+
21
+
22
+ class ContextKind(Enum):
23
+ """What was clicked / is being edited."""
24
+
25
+ TILESET = auto() # whole-tileset properties
26
+ TILE_VARIANT = auto() # per-tile properties (grid tilesets)
27
+ MAP_OBJECT = auto() # placed object on an object layer
28
+ NODE = auto() # node (area / particle_emitter / group)
29
+ LAYER = auto() # layer
30
+
31
+
32
+ @dataclass
33
+ class PropertyContext:
34
+ """Context for an open/save property event.
35
+
36
+ ``target`` is the typed object the event refers to (TilesetData, Node,
37
+ Layer, ...). ``extra`` carries kind-specific data (e.g. variant_ids).
38
+ """
39
+
40
+ kind: ContextKind
41
+ target: Any = None
42
+ extra: dict[str, Any] = field(default_factory=dict)
43
+
44
+
45
+ Opener = Callable[[PropertyContext], None]
46
+ Saver = Callable[[PropertyContext, dict[str, Any]], None]
47
+
48
+
49
+ # TODO(dispatcher): temporary shared callback channel. Planned refactor: route
50
+ # property open/save through the Editor reference (self.editor) and direct
51
+ # widget/manager calls instead of registered openers/savers.
52
+ class PropertyContextDispatcher:
53
+ """Routes property open/save events by context kind.
54
+
55
+ One registered handler per kind; dispatch is a no-op for unregistered
56
+ kinds so new contexts can be introduced without breaking old ones.
57
+ """
58
+
59
+ def __init__(self):
60
+ self._openers: dict[ContextKind, Opener] = {}
61
+ self._savers: dict[ContextKind, Saver] = {}
62
+
63
+ def register_opener(self, kind: ContextKind, opener: Opener) -> None:
64
+ self._openers[kind] = opener
65
+
66
+ def register_saver(self, kind: ContextKind, saver: Saver) -> None:
67
+ self._savers[kind] = saver
68
+
69
+ def open(self, ctx: PropertyContext) -> bool:
70
+ """Show the property editor for the context. Returns True if handled."""
71
+ opener = self._openers.get(ctx.kind)
72
+ if opener is None:
73
+ return False
74
+ opener(ctx)
75
+ return True
76
+
77
+ def save(self, ctx: PropertyContext, props: dict[str, Any]) -> bool:
78
+ """Apply saved properties to the context target. Returns True if handled."""
79
+ saver = self._savers.get(ctx.kind)
80
+ if saver is None:
81
+ return False
82
+ saver(ctx, props)
83
+ return True
@@ -8,7 +8,7 @@ from typing import TYPE_CHECKING
8
8
  import pygame
9
9
  from pygame import Rect, Surface
10
10
 
11
- from layers import Layer
11
+ from utils.context_dispatch import ContextKind, PropertyContext
12
12
  from widgets.ui.button import Button
13
13
  from widgets.ui.property_editor import PropertyEditor
14
14
  from widgets.ui.theme import COLORS, FONTS, SHAPE
@@ -65,6 +65,10 @@ class LayerSelector:
65
65
  self.font_header = FONTS.get_bold_font(FONTS.size_md)
66
66
  self.font_layer = FONTS.get_small_font()
67
67
 
68
+ d = self.editor.context_dispatch
69
+ d.register_opener(ContextKind.LAYER, self._open_layer_properties)
70
+ d.register_saver(ContextKind.LAYER, self._save_layer_properties)
71
+
68
72
  def resize(self, x: int, y: int, w: int, h: int):
69
73
  self.rect = Rect(x, y, w, h)
70
74
  self.header_rect = Rect(x, y, w, self.header_h)
@@ -149,14 +153,8 @@ class LayerSelector:
149
153
  if layer_idx is not None:
150
154
  layer = self.editor.tilemap.layer_manager.get_layer(layer_idx)
151
155
  if layer:
152
- self.editor.property_editor = PropertyEditor(
153
- self.editor,
154
- f"Layer Properties: {layer.name}",
155
- layer.properties,
156
- on_save=lambda props: self._save_layer_properties(
157
- layer, props
158
- ),
159
- on_close=lambda: None,
156
+ self.editor.context_dispatch.open(
157
+ PropertyContext(ContextKind.LAYER, layer)
160
158
  )
161
159
  return True
162
160
 
@@ -406,7 +404,17 @@ class LayerSelector:
406
404
  self.rename_text = ""
407
405
  self.rename_original_name = ""
408
406
 
409
- def _save_layer_properties(self, layer: Layer, props: dict):
407
+ def _open_layer_properties(self, ctx: PropertyContext) -> None:
408
+ layer = ctx.target
409
+ self.editor.property_editor = PropertyEditor(
410
+ self.editor,
411
+ f"Layer Properties: {layer.name}",
412
+ layer.properties,
413
+ context=ctx,
414
+ )
415
+
416
+ def _save_layer_properties(self, ctx: PropertyContext, props: dict):
417
+ layer = ctx.target
410
418
  layer.properties = props
411
419
  self.editor.suggestion_registry.refresh(self.editor)
412
420
  print(f"Saved properties for layer: {layer.name}")
@@ -547,7 +555,7 @@ class LayerSelector:
547
555
  if ysort_rect:
548
556
  ysort_color = COLORS.accent if layer.y_sort else COLORS.text_dim
549
557
  pygame.draw.rect(
550
- screen, ysort_color, ysort_rect,
558
+ screen, ysort_color, ysort_rect,
551
559
  border_radius=SHAPE.radius_sm,
552
560
  )
553
561
  ysort_txt = self.font_layer.render("Y", True, COLORS.text)
@@ -5,6 +5,7 @@ from pygame import K_LEFT, K_RIGHT, K_UP, Rect, Surface
5
5
 
6
6
  from nodes import NodeRect
7
7
  from ttypes.tilemap import TypeObject, TypeTile
8
+ from utils.context_dispatch import ContextKind, PropertyContext
8
9
  from widgets.particle_system import MAX_DT, ParticlePreview
9
10
  from widgets.ui.drag_tracker import DragTracker
10
11
  from widgets.ui.theme import COLORS
@@ -429,7 +430,7 @@ class TileGrid:
429
430
  return True
430
431
 
431
432
  if event.button == 3:
432
- if is_hovering and self.hover_cell:
433
+ if is_hovering and self.hover_cell and not self._open_object_properties_if_hit(mouse_pos):
433
434
  self.pick_tile_at(self.hover_cell)
434
435
  return True
435
436
 
@@ -777,7 +778,7 @@ class TileGrid:
777
778
  world_pos = self.screen_to_world(mouse_pos)
778
779
  rs = self.editor.tilemap.render_scale
779
780
  pick_pos = (int(world_pos[0] / rs), int(world_pos[1] / rs)) if rs else world_pos
780
- for _obj_id, obj in active_layer.get_all_objects().items():
781
+ for _obj_id, obj in reversed(list(active_layer.get_all_objects().items())):
781
782
  area = obj["area"]
782
783
  obj_rect = Rect(area["x"], area["y"], area["w"], area["h"])
783
784
  if obj_rect.collidepoint(pick_pos):
@@ -786,6 +787,43 @@ class TileGrid:
786
787
  ts_widget.select_tile_by_variant(ttype, variant)
787
788
  return
788
789
 
790
+ def _open_object_properties_if_hit(self, mouse_pos: tuple[int, int]) -> bool:
791
+ """Open the placed object's own properties under the cursor.
792
+
793
+ Returns True when an object was hit (event consumed), False otherwise
794
+ so the caller can fall back to the eyedropper behavior.
795
+ """
796
+ active_layer = self.editor.tilemap.layer_manager.get_active_layer()
797
+ if not active_layer or active_layer.layer_type != "object":
798
+ return False
799
+
800
+ ts_widget = self.editor.tileset_widget
801
+ if not ts_widget:
802
+ return False
803
+
804
+ world_pos = self.screen_to_world(mouse_pos)
805
+ rs = self.editor.tilemap.render_scale
806
+ pick_pos = (int(world_pos[0] / rs), int(world_pos[1] / rs)) if rs else world_pos
807
+
808
+ for obj_id, obj in reversed(list(active_layer.get_all_objects().items())):
809
+ area = obj["area"]
810
+ obj_rect = Rect(area["x"], area["y"], area["w"], area["h"])
811
+ if obj_rect.collidepoint(pick_pos):
812
+ ts = ts_widget.get_tileset_by_index(int(obj["ttype"]))
813
+ self.editor.context_dispatch.open(
814
+ PropertyContext(
815
+ ContextKind.MAP_OBJECT,
816
+ obj,
817
+ {
818
+ "obj_id": obj_id,
819
+ "layer_name": active_layer.name,
820
+ "tileset_name": ts.name if ts else None,
821
+ },
822
+ )
823
+ )
824
+ return True
825
+ return False
826
+
789
827
  def _point_in_selection(self, grid_pos: tuple[int, int]) -> bool:
790
828
  """Check if a grid position is inside the current selection."""
791
829
  if not self.selection_rect:
@@ -4,6 +4,7 @@ from typing import TYPE_CHECKING, Any
4
4
  import pygame
5
5
  from pygame import Rect
6
6
 
7
+ from utils.context_dispatch import ContextKind, PropertyContext
7
8
  from utils.error_handler import error_handler
8
9
  from utils.validation import is_image_multipleof
9
10
  from widgets.ui.button import Button
@@ -77,6 +78,17 @@ class TileSelector(WidgetBase):
77
78
  self.zoom: float = 1.0
78
79
  self.font = FONTS.get_medium_font()
79
80
 
81
+ self._register_context_handlers()
82
+
83
+ def _register_context_handlers(self):
84
+ d = self.editor.context_dispatch
85
+ d.register_opener(ContextKind.TILESET, self._open_tileset_properties)
86
+ d.register_saver(ContextKind.TILESET, self._save_tileset_properties)
87
+ d.register_opener(ContextKind.TILE_VARIANT, self._open_tile_variant_properties)
88
+ d.register_saver(ContextKind.TILE_VARIANT, self._save_tile_properties_multi)
89
+ d.register_opener(ContextKind.MAP_OBJECT, self._open_object_properties)
90
+ d.register_saver(ContextKind.MAP_OBJECT, self._save_object_properties)
91
+
80
92
  def _add_folder(self) -> None:
81
93
  self._folder_id_counter += 1
82
94
  folder = TreeNode(
@@ -113,14 +125,52 @@ class TileSelector(WidgetBase):
113
125
  ts = self._ts_node(item_id)
114
126
  if ts is None:
115
127
  return
128
+ self.editor.context_dispatch.open(PropertyContext(ContextKind.TILESET, ts))
129
+
130
+ def _open_tileset_properties(self, ctx: PropertyContext) -> None:
131
+ ts = ctx.target
116
132
  self.editor.property_editor = PropertyEditor(
117
133
  self.editor,
118
134
  f"Tileset Properties: {ts.name}",
119
135
  ts.properties,
120
- on_save=lambda props: self._save_tileset_properties(ts, props),
121
- on_close=lambda: None,
136
+ context=ctx,
137
+ )
138
+
139
+ def _open_tile_variant_properties(self, ctx: PropertyContext) -> None:
140
+ ts = ctx.target
141
+ variant_ids = ctx.extra.get("variant_ids", [])
142
+ if not variant_ids:
143
+ return
144
+ variant_id = variant_ids[0]
145
+ self.editor.property_editor = PropertyEditor(
146
+ self.editor,
147
+ f"Tile Properties: {ts.name} (ID: {variant_id})",
148
+ ts.tile_properties.get(variant_id, {}),
149
+ context=ctx,
122
150
  )
123
151
 
152
+ def _open_object_properties(self, ctx: PropertyContext) -> None:
153
+ obj = ctx.target
154
+ if obj is None:
155
+ return
156
+ title = f"Object Properties: {ctx.extra.get('layer_name', '')} #{ctx.extra.get('obj_id', '?')}"
157
+ ts_name = ctx.extra.get("tileset_name")
158
+ if ts_name:
159
+ title = f"{title} ({ts_name})"
160
+ self.editor.property_editor = PropertyEditor(
161
+ self.editor,
162
+ title,
163
+ obj.get("properties", {}),
164
+ context=ctx,
165
+ )
166
+
167
+ def _save_object_properties(self, ctx: PropertyContext, props: dict) -> None:
168
+ obj = ctx.target
169
+ if obj is None:
170
+ return
171
+ obj["properties"] = props
172
+ self.editor.suggestion_registry.refresh(self.editor)
173
+
124
174
  def _on_tree_structure_changed(self) -> None:
125
175
  pass
126
176
 
@@ -221,11 +271,14 @@ class TileSelector(WidgetBase):
221
271
 
222
272
  if event.type == pygame.MOUSEBUTTONDOWN and event.button == 3:
223
273
  if self.view_rect.collidepoint(mouse_pos) and self.active_idx != -1:
274
+ ts = self.tilesets[self.active_idx]
275
+ if ts.tileset_type == "object":
276
+ self.editor.context_dispatch.open(
277
+ PropertyContext(ContextKind.TILESET, ts)
278
+ )
279
+ return True
224
280
  if self.selected_tile:
225
281
  sx, sy, sw, sh = self.selected_tile
226
- ts = self.tilesets[self.active_idx]
227
- if ts.tileset_type == "object":
228
- return True
229
282
  img_x = self.view_rect.x + ts.offset[0]
230
283
  img_y = self.view_rect.y + ts.offset[1]
231
284
  sel_rect = Rect(img_x + sx, img_y + sy, sw, sh)
@@ -235,20 +288,18 @@ class TileSelector(WidgetBase):
235
288
  if not variant_ids:
236
289
  return True
237
290
 
238
- variant_id = variant_ids[0]
239
-
240
- self.editor.property_editor = PropertyEditor(
241
- self.editor,
242
- f"Tile Properties: {ts.name} (ID: {variant_id})",
243
- ts.tile_properties.get(variant_id, {}),
244
- on_save=lambda props: self._save_tile_properties_multi(ts, variant_ids, props),
245
- on_close=lambda: None,
291
+ self.editor.context_dispatch.open(
292
+ PropertyContext(
293
+ ContextKind.TILE_VARIANT,
294
+ ts,
295
+ {"variant_ids": variant_ids},
296
+ )
246
297
  )
247
298
  return True
248
299
 
249
300
  self.is_panning = True
250
301
  self.pan_start = mouse_pos
251
- self.pan_start_offset = tuple(self.tilesets[self.active_idx].offset)
302
+ self.pan_start_offset = tuple(ts.offset)
252
303
  return True
253
304
 
254
305
  elif event.type == pygame.MOUSEBUTTONUP and event.button == 3:
@@ -289,9 +340,19 @@ class TileSelector(WidgetBase):
289
340
  ts = self.tilesets[self.active_idx]
290
341
  img_x = self.view_rect.x + ts.offset[0]
291
342
  img_y = self.view_rect.y + ts.offset[1]
292
-
293
343
  rel_x = (mouse_pos[0] - img_x) / self.zoom
294
344
  rel_y = (mouse_pos[1] - img_y) / self.zoom
345
+ if ts.tileset_type == "object":
346
+ if (
347
+ 0 <= rel_x < ts.surface.get_width()
348
+ and 0 <= rel_y < ts.surface.get_height()
349
+ ):
350
+ self.hover_pos = (0, 0)
351
+ else:
352
+ self.hover_pos = None
353
+ self.is_selecting = False
354
+ self.selection_start_grid = None
355
+ return True
295
356
 
296
357
  if 0 <= rel_x < ts.surface.get_width() and 0 <= rel_y < ts.surface.get_height():
297
358
  tw, th = self.editor.tilemap.tile_size
@@ -574,7 +635,8 @@ class TileSelector(WidgetBase):
574
635
 
575
636
  self.editor.launch_collision_editor(ts.tileset_type)
576
637
 
577
- def _save_tileset_properties(self, ts: TilesetData, props: dict):
638
+ def _save_tileset_properties(self, ctx: PropertyContext, props: dict):
639
+ ts = ctx.target
578
640
  ts.properties = props
579
641
  self.editor.suggestion_registry.refresh(self.editor)
580
642
  print(f"Saved properties for tileset: {ts.name}")
@@ -583,7 +645,9 @@ class TileSelector(WidgetBase):
583
645
  ts.tile_properties[variant_id] = props
584
646
  print(f"Saved properties for tile {variant_id} in tileset: {ts.name}")
585
647
 
586
- def _save_tile_properties_multi(self, ts: TilesetData, variant_ids: list[int], props: dict):
648
+ def _save_tile_properties_multi(self, ctx: PropertyContext, props: dict):
649
+ ts = ctx.target
650
+ variant_ids = ctx.extra.get("variant_ids", [])
587
651
  for vid in variant_ids:
588
652
  ts.tile_properties[vid] = props.copy()
589
653
  self.editor.suggestion_registry.refresh(self.editor)
@@ -613,6 +677,11 @@ class TileSelector(WidgetBase):
613
677
  return None
614
678
  return self.tilesets[self.active_idx]
615
679
 
680
+ def get_tileset_by_index(self, index: int) -> TilesetData | None:
681
+ if index < 0 or index >= len(self.tilesets):
682
+ return None
683
+ return self.tilesets[index]
684
+
616
685
  def select_tile_by_variant(self, tileset_index: int, variant_id: int) -> bool:
617
686
  if tileset_index < 0 or tileset_index >= len(self.tilesets):
618
687
  return False
@@ -802,6 +871,8 @@ class TileSelector(WidgetBase):
802
871
  def draw_hover(self, screen, ts: TilesetData, img_x: int, img_y: int):
803
872
  if self.hover_pos is None:
804
873
  return
874
+ if ts.tileset_type == "object":
875
+ return
805
876
  tw, th = self.editor.tilemap.tile_size
806
877
  col, row = self.hover_pos
807
878
  ztw = int(tw * self.zoom)
@@ -20,6 +20,7 @@ class Button(WidgetBase):
20
20
  font=None,
21
21
  text_color=None,
22
22
  accent=False,
23
+ danger=False,
23
24
  bg=None,
24
25
  border_color=None,
25
26
  border_radius=None,
@@ -43,6 +44,7 @@ class Button(WidgetBase):
43
44
  self.font = font or FONTS.get_medium_font()
44
45
  self._text_color = text_color
45
46
  self.accent = accent
47
+ self.danger = danger
46
48
  self.on_click = on_click
47
49
  self._active = False
48
50
  self._hovered = False
@@ -92,18 +94,30 @@ class Button(WidgetBase):
92
94
  border = COLORS.border
93
95
  text_color = self._text_color or COLORS.text
94
96
  elif self._pressed:
95
- bg = COLORS.accent_active if self.accent else COLORS.header
97
+ bg = (
98
+ COLORS.accent_active
99
+ if self.accent
100
+ else (COLORS.danger_hover if self.danger else COLORS.header)
101
+ )
96
102
  border = COLORS.border
97
103
  text_color = self._text_color or COLORS.text
98
104
  elif self._hovered:
99
- bg = COLORS.accent_hover if self.accent else COLORS.hover
105
+ bg = (
106
+ COLORS.accent_hover
107
+ if self.accent
108
+ else (COLORS.danger_hover if self.danger else COLORS.hover)
109
+ )
100
110
  border = COLORS.border
101
111
  text_color = self._text_color or COLORS.text
102
112
  else:
103
113
  bg = (
104
114
  self._bg
105
115
  if self._bg is not None
106
- else (COLORS.accent if self.accent else COLORS.panel_alt)
116
+ else (
117
+ COLORS.accent
118
+ if self.accent
119
+ else COLORS.danger if self.danger else COLORS.panel_alt
120
+ )
107
121
  )
108
122
  border = self._border if self._border is not None else COLORS.border_soft
109
123
  text_color = self._text_color or COLORS.text
@@ -5,6 +5,7 @@ from pygame import Rect
5
5
 
6
6
  if TYPE_CHECKING:
7
7
  from editor import Editor
8
+ from utils.context_dispatch import ContextKind, PropertyContext
8
9
  from utils.font_manager import FontWeight, font_manager
9
10
  from widgets.particle_presets import PRESETS, get_preset_config, get_preset_names
10
11
  from widgets.ui.draw_utils import draw_panel
@@ -37,6 +38,10 @@ class NodeEditor:
37
38
  self._preset_names: list[str] = get_preset_names()
38
39
  self._is_particle = False
39
40
 
41
+ d = self.editor.context_dispatch
42
+ d.register_opener(ContextKind.NODE, self._open_node_properties)
43
+ d.register_saver(ContextKind.NODE, self._save_props)
44
+
40
45
  def resize(self, x: int, y: int, w: int):
41
46
  self.rect = Rect(x, y, w, self.rect.height)
42
47
 
@@ -215,12 +220,8 @@ class NodeEditor:
215
220
  if action == "props":
216
221
  node = mgr.get_active_node()
217
222
  if node:
218
- self.editor.property_editor = PropertyEditor(
219
- self.editor,
220
- f"Node Properties: {node.name}",
221
- dict(node.properties),
222
- on_save=lambda props: self._save_props(props),
223
- on_close=lambda: None,
223
+ self.editor.context_dispatch.open(
224
+ PropertyContext(ContextKind.NODE, node)
224
225
  )
225
226
  elif action == "particle":
226
227
  node = mgr.get_active_node()
@@ -352,8 +353,19 @@ class NodeEditor:
352
353
  except (ValueError, TypeError):
353
354
  return default
354
355
 
355
- def _save_props(self, props: dict):
356
- node = self.editor.node_manager.get_active_node()
356
+ def _open_node_properties(self, ctx: PropertyContext) -> None:
357
+ node = ctx.target
358
+ if node is None:
359
+ return
360
+ self.editor.property_editor = PropertyEditor(
361
+ self.editor,
362
+ f"Node Properties: {node.name}",
363
+ dict(node.properties),
364
+ context=ctx,
365
+ )
366
+
367
+ def _save_props(self, ctx: PropertyContext, props: dict):
368
+ node = ctx.target
357
369
  if node:
358
370
  node.properties = props
359
371
  self.editor.suggestion_registry.refresh(self.editor)