tilemap-editor 4.2.0__tar.gz → 4.3.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 (226) hide show
  1. {tilemap_editor-4.2.0/src/tilemap_editor.egg-info → tilemap_editor-4.3.0}/PKG-INFO +1 -1
  2. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/pyproject.toml +1 -1
  3. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/editor.py +23 -23
  4. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/layers.py +17 -28
  5. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/tilemap_editor/assets/icons/ToolMove.svg +1 -1
  6. tilemap_editor-4.3.0/src/tilemap_editor/assets/icons/checked.svg +1 -0
  7. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/tilemap_editor/assets/icons/duplicate.svg +1 -1
  8. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/tilemap_editor/assets/icons/error.svg +1 -1
  9. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/tilemap_editor/assets/icons/file.svg +1 -1
  10. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/tilemap_editor/assets/icons/filedead.svg +1 -1
  11. tilemap_editor-4.3.0/src/tilemap_editor/assets/icons/fit.svg +1 -0
  12. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/tilemap_editor/assets/icons/folder.svg +1 -1
  13. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/tilemap_editor/assets/icons/grid.svg +1 -1
  14. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/tilemap_editor/assets/icons/image.svg +1 -1
  15. tilemap_editor-4.3.0/src/tilemap_editor/assets/icons/info.svg +1 -0
  16. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/tilemap_editor/assets/icons/load.svg +1 -1
  17. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/tilemap_editor/assets/icons/loop.svg +1 -1
  18. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/tilemap_editor/assets/icons/pan.svg +1 -1
  19. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/tilemap_editor/assets/icons/pause.svg +1 -1
  20. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/tilemap_editor/assets/icons/pencil.svg +2 -2
  21. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/tilemap_editor/assets/icons/play.svg +1 -1
  22. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/tilemap_editor/assets/icons/plus.svg +1 -1
  23. tilemap_editor-4.3.0/src/tilemap_editor/assets/icons/radio.svg +1 -0
  24. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/tilemap_editor/assets/icons/reset.svg +1 -1
  25. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/tilemap_editor/assets/icons/save.svg +1 -1
  26. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/tilemap_editor/assets/icons/stop.svg +1 -1
  27. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/tilemap_editor/assets/icons/tilemap.svg +1 -1
  28. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/tilemap_editor/assets/icons/tileset.svg +1 -1
  29. tilemap_editor-4.3.0/src/tilemap_editor/assets/icons/unchecked.svg +1 -0
  30. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/tilemap_editor/assets/icons/warning.svg +1 -1
  31. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/tilemap_editor/assets/icons/zoomin.svg +5 -5
  32. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/tilemap_editor/assets/icons/zoomout.svg +5 -5
  33. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/tilemap_editor/cli.py +6 -1
  34. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0/src/tilemap_editor.egg-info}/PKG-INFO +1 -1
  35. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/tilemap_editor.egg-info/SOURCES.txt +3 -0
  36. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/utils/icon_manager.py +51 -4
  37. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/widgets/autotiler.py +13 -0
  38. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/widgets/tile_grid.py +274 -228
  39. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/widgets/ui/button.py +27 -3
  40. tilemap_editor-4.3.0/src/widgets/ui/drag_tracker.py +62 -0
  41. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/widgets/ui/theme.py +57 -8
  42. tilemap_editor-4.3.0/src/widgets/ui/tool_manager.py +50 -0
  43. tilemap_editor-4.3.0/src/widgets/ui/toolbar.py +146 -0
  44. tilemap_editor-4.3.0/tests/test_drag_tracker.py +77 -0
  45. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/tests/test_layers_autotile.py +200 -0
  46. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/tests/test_toolbar_tools.py +50 -46
  47. tilemap_editor-4.2.0/src/tilemap_editor/assets/icons/checked.svg +0 -1
  48. tilemap_editor-4.2.0/src/tilemap_editor/assets/icons/fit.svg +0 -1
  49. tilemap_editor-4.2.0/src/tilemap_editor/assets/icons/info.svg +0 -1
  50. tilemap_editor-4.2.0/src/tilemap_editor/assets/icons/radio.svg +0 -1
  51. tilemap_editor-4.2.0/src/tilemap_editor/assets/icons/unchecked.svg +0 -1
  52. tilemap_editor-4.2.0/src/widgets/ui/toolbar.py +0 -164
  53. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/LICENSE +0 -0
  54. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/MANIFEST.in +0 -0
  55. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/README.md +0 -0
  56. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/assets/fonts/jetbrain-fonts/JetBrainsMono-Bold.ttf +0 -0
  57. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/assets/fonts/jetbrain-fonts/JetBrainsMono-BoldItalic.ttf +0 -0
  58. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/assets/fonts/jetbrain-fonts/JetBrainsMono-Italic.ttf +0 -0
  59. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/assets/fonts/jetbrain-fonts/JetBrainsMono-Regular.ttf +0 -0
  60. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/assets/fonts/noto/NotoSans-Bold.ttf +0 -0
  61. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/assets/fonts/noto/NotoSans-BoldItalic.ttf +0 -0
  62. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/assets/fonts/noto/NotoSans-Italic.ttf +0 -0
  63. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/assets/fonts/noto/NotoSans-Regular.ttf +0 -0
  64. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/assets/icons/arrow-down.svg +0 -0
  65. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/assets/icons/check.svg +0 -0
  66. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/assets/icons/checked.svg +0 -0
  67. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/assets/icons/close.svg +0 -0
  68. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/assets/icons/duplicate.svg +0 -0
  69. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/assets/icons/error.svg +0 -0
  70. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/assets/icons/file.svg +0 -0
  71. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/assets/icons/filedead.svg +0 -0
  72. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/assets/icons/fit.svg +0 -0
  73. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/assets/icons/folder.svg +0 -0
  74. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/assets/icons/grid.svg +0 -0
  75. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/assets/icons/image.svg +0 -0
  76. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/assets/icons/info.svg +0 -0
  77. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/assets/icons/load.svg +0 -0
  78. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/assets/icons/loop.svg +0 -0
  79. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/assets/icons/onion.svg +0 -0
  80. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/assets/icons/pan.svg +0 -0
  81. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/assets/icons/pause.svg +0 -0
  82. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/assets/icons/pencil.svg +0 -0
  83. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/assets/icons/play.svg +0 -0
  84. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/assets/icons/plus.svg +0 -0
  85. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/assets/icons/radio.svg +0 -0
  86. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/assets/icons/reset.svg +0 -0
  87. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/assets/icons/save.svg +0 -0
  88. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/assets/icons/skip-back.svg +0 -0
  89. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/assets/icons/skip-forward.svg +0 -0
  90. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/assets/icons/stop.svg +0 -0
  91. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/assets/icons/tilemap.svg +0 -0
  92. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/assets/icons/tileset.svg +0 -0
  93. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/assets/icons/unchecked.svg +0 -0
  94. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/assets/icons/warning.svg +0 -0
  95. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/assets/icons/zoomin.svg +0 -0
  96. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/assets/icons/zoomout.svg +0 -0
  97. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/setup.cfg +0 -0
  98. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/configs/themes.py +0 -0
  99. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/constants.py +0 -0
  100. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/event_map.py +0 -0
  101. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/main.py +0 -0
  102. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/node_manager.py +0 -0
  103. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/nodes.py +0 -0
  104. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/plugins/__init__.py +0 -0
  105. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/plugins/character_collision/__init__.py +0 -0
  106. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/plugins/character_collision/editor.py +0 -0
  107. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/plugins/character_collision/models.py +0 -0
  108. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/plugins/character_collision/protocols.py +0 -0
  109. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/plugins/character_collision/shape_editor.py +0 -0
  110. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/plugins/character_collision/standalone.py +0 -0
  111. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/plugins/object_tileset_collision/__init__.py +0 -0
  112. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/plugins/object_tileset_collision/editor.py +0 -0
  113. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/plugins/object_tileset_collision/models.py +0 -0
  114. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/plugins/object_tileset_collision/protocols.py +0 -0
  115. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/plugins/object_tileset_collision/standalone.py +0 -0
  116. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/plugins/sprite_animation/__init__.py +0 -0
  117. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/plugins/sprite_animation/__main__.py +0 -0
  118. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/plugins/sprite_animation/clipboard_util.py +0 -0
  119. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/plugins/sprite_animation/editor.py +0 -0
  120. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/plugins/sprite_animation/frame_picker.py +0 -0
  121. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/plugins/sprite_animation/models.py +0 -0
  122. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/plugins/sprite_animation/preview.py +0 -0
  123. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/plugins/sprite_animation/protocols.py +0 -0
  124. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/plugins/sprite_animation/runtime_load.py +0 -0
  125. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/plugins/sprite_animation/standalone.py +0 -0
  126. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/plugins/sprite_animation/timeline.py +0 -0
  127. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/plugins/sprite_animation/validation.py +0 -0
  128. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/plugins/sprite_editor/__init__.py +0 -0
  129. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/plugins/sprite_editor/editor.py +0 -0
  130. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/plugins/sprite_editor/standalone.py +0 -0
  131. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/plugins/tileset_collision/__init__.py +0 -0
  132. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/plugins/tileset_collision/collision_painter.py +0 -0
  133. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/plugins/tileset_collision/editor.py +0 -0
  134. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/plugins/tileset_collision/models.py +0 -0
  135. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/plugins/tileset_collision/protocols.py +0 -0
  136. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/plugins/tileset_collision/standalone.py +0 -0
  137. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/standalone_automap.py +0 -0
  138. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/standalone_error_console.py +0 -0
  139. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/standalone_filemanager.py +0 -0
  140. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/standalone_image_viewer.py +0 -0
  141. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/tilemap.py +0 -0
  142. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/tilemap_editor/__init__.py +0 -0
  143. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/tilemap_editor/__main__.py +0 -0
  144. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/tilemap_editor/assets/__init__.py +0 -0
  145. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/tilemap_editor/assets/fonts/jetbrain-fonts/JetBrainsMono-Bold.ttf +0 -0
  146. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/tilemap_editor/assets/fonts/jetbrain-fonts/JetBrainsMono-BoldItalic.ttf +0 -0
  147. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/tilemap_editor/assets/fonts/jetbrain-fonts/JetBrainsMono-Italic.ttf +0 -0
  148. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/tilemap_editor/assets/fonts/jetbrain-fonts/JetBrainsMono-Regular.ttf +0 -0
  149. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/tilemap_editor/assets/fonts/noto/NotoSans-Bold.ttf +0 -0
  150. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/tilemap_editor/assets/fonts/noto/NotoSans-BoldItalic.ttf +0 -0
  151. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/tilemap_editor/assets/fonts/noto/NotoSans-Italic.ttf +0 -0
  152. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/tilemap_editor/assets/fonts/noto/NotoSans-Regular.ttf +0 -0
  153. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/tilemap_editor/assets/icons/arrow-down.svg +0 -0
  154. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/tilemap_editor/assets/icons/check.svg +0 -0
  155. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/tilemap_editor/assets/icons/close.svg +0 -0
  156. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/tilemap_editor/assets/icons/onion.svg +0 -0
  157. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/tilemap_editor/assets/icons/skip-back.svg +0 -0
  158. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/tilemap_editor/assets/icons/skip-forward.svg +0 -0
  159. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/tilemap_editor/main.py +0 -0
  160. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/tilemap_editor/settings.py +0 -0
  161. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/tilemap_editor.egg-info/dependency_links.txt +0 -0
  162. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/tilemap_editor.egg-info/entry_points.txt +0 -0
  163. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/tilemap_editor.egg-info/requires.txt +0 -0
  164. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/tilemap_editor.egg-info/top_level.txt +0 -0
  165. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/ttypes/__init__.py +0 -0
  166. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/ttypes/tilemap.py +0 -0
  167. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/utils/__init__.py +0 -0
  168. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/utils/editor_preference.py +0 -0
  169. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/utils/error_handler.py +0 -0
  170. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/utils/font_manager.py +0 -0
  171. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/utils/history.py +0 -0
  172. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/utils/icons_cache.py +0 -0
  173. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/utils/log_capture.py +0 -0
  174. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/utils/project_paths.py +0 -0
  175. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/utils/serialization.py +0 -0
  176. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/utils/standalone.py +0 -0
  177. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/utils/validation.py +0 -0
  178. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/widgets/__init__.py +0 -0
  179. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/widgets/automap_models.py +0 -0
  180. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/widgets/autotile_template.py +0 -0
  181. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/widgets/filemanager.py +0 -0
  182. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/widgets/input.py +0 -0
  183. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/widgets/layer_selector.py +0 -0
  184. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/widgets/map_properties.py +0 -0
  185. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/widgets/mapsetup.py +0 -0
  186. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/widgets/particle_presets.py +0 -0
  187. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/widgets/particle_system.py +0 -0
  188. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/widgets/regex_automap_designer.py +0 -0
  189. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/widgets/spritesheet_grid.py +0 -0
  190. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/widgets/tile_selector.py +0 -0
  191. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/widgets/ui/checkbox.py +0 -0
  192. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/widgets/ui/collision_layer_mask.py +0 -0
  193. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/widgets/ui/collision_layer_sidebar.py +0 -0
  194. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/widgets/ui/confirm_dialog.py +0 -0
  195. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/widgets/ui/dialog_base.py +0 -0
  196. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/widgets/ui/draw_utils.py +0 -0
  197. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/widgets/ui/fileinput.py +0 -0
  198. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/widgets/ui/label.py +0 -0
  199. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/widgets/ui/layer_type_dialog.py +0 -0
  200. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/widgets/ui/menubar.py +0 -0
  201. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/widgets/ui/mode_indicator.py +0 -0
  202. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/widgets/ui/node_editor.py +0 -0
  203. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/widgets/ui/node_selector.py +0 -0
  204. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/widgets/ui/notification.py +0 -0
  205. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/widgets/ui/particle_config_dialog.py +0 -0
  206. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/widgets/ui/property_editor.py +0 -0
  207. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/widgets/ui/region_selector.py +0 -0
  208. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/widgets/ui/scrollbar.py +0 -0
  209. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/widgets/ui/sidebar_container.py +0 -0
  210. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/widgets/ui/status_bar.py +0 -0
  211. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/widgets/ui/tileset_type_dialog.py +0 -0
  212. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/widgets/ui/tooltip.py +0 -0
  213. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/src/widgets/widget_base.py +0 -0
  214. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/tests/test_animation_library_grid_offset.py +0 -0
  215. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/tests/test_collision_layer_mask.py +0 -0
  216. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/tests/test_collision_layer_sidebar.py +0 -0
  217. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/tests/test_editor_pan_mode.py +0 -0
  218. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/tests/test_gid_collision.py +0 -0
  219. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/tests/test_project_paths.py +0 -0
  220. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/tests/test_render_scale.py +0 -0
  221. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/tests/test_serialization_regression.py +0 -0
  222. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/tests/test_sprite_animation_editor_grid.py +0 -0
  223. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/tests/test_tile_grid_selection.py +0 -0
  224. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/tests/test_tile_selector_pick.py +0 -0
  225. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/tests/test_tilemap_save.py +0 -0
  226. {tilemap_editor-4.2.0 → tilemap_editor-4.3.0}/tests/test_timeline_invalidate_cache.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tilemap-editor
3
- Version: 4.2.0
3
+ Version: 4.3.0
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 = "4.2.0"
7
+ version = "4.3.0"
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"
@@ -40,6 +40,7 @@ from widgets.ui.particle_config_dialog import ParticleConfigDialog
40
40
  from widgets.ui.property_editor import PropertyEditor
41
41
  from widgets.ui.sidebar_container import SidebarContainer, ToolbarAction
42
42
  from widgets.ui.theme import COLORS, THEMES, get_theme_manager, set_theme
43
+ from widgets.ui.tool_manager import ToolKind, ToolManager
43
44
  from widgets.ui.tileset_type_dialog import TilesetTypeDialog
44
45
  from widgets.ui.toolbar import Toolbar
45
46
  from widgets.ui.tooltip import TooltipManager
@@ -138,27 +139,31 @@ _launch_standalone_module = launch_standalone
138
139
 
139
140
 
140
141
  class Editor:
141
- def __init__(self, size: Optional[Tuple[int, int]] = None, fps=60):
142
+ def __init__(
143
+ self,
144
+ size: Optional[Tuple[int, int]] = None,
145
+ fps=60,
146
+ theme: Optional[str] = None,
147
+ ):
142
148
  self.base_path, self.data_root, self.config = _load_project_config()
143
149
  logger.info(f"Project base_path: {self.base_path}")
144
150
  logger.info(f"Data root: {self.data_root}")
145
151
 
146
- theme_name = self.config.get("theme", "dark")
147
- if theme_name in THEMES:
148
- set_theme(theme_name)
149
- logger.info(f"Applied theme: {theme_name}")
152
+ theme_name = theme or self.config.get("theme", "dark")
153
+ tm = get_theme_manager()
154
+ if not tm.set_theme(theme_name):
155
+ logger.warning(f"Theme '{theme_name}' not found, falling back to dark")
156
+ tm.set_theme("dark")
150
157
 
151
158
  pygame.init()
152
159
  pygame.display.set_caption("Pure Pygame Editor")
153
160
 
154
161
  self.fps = fps
155
162
  self.running = False
156
- self.pan_mode = False
157
163
  self.autotile_mode = False
158
- self.eraser_mode = False
159
- self.select_mode = False
160
164
  self.node_editing_mode = False
161
165
  self.show_nodes = False
166
+ self.tool_manager = ToolManager()
162
167
  self._prev_tool = None
163
168
 
164
169
  if isinstance(size, tuple) and len(size) == 2:
@@ -712,6 +717,8 @@ class Editor:
712
717
  next_idx = (current_idx + 1) % len(theme_names)
713
718
  new_theme = theme_names[next_idx]
714
719
  set_theme(new_theme)
720
+ from utils.icon_manager import icon_manager
721
+ icon_manager.clear_cache()
715
722
  self.notifications.notify(f"Theme: {new_theme}")
716
723
 
717
724
  settings_file = Path.cwd() / "settings.json"
@@ -1199,9 +1206,7 @@ class Editor:
1199
1206
  self.node_editing_mode = not self.node_editing_mode
1200
1207
  if self.node_editing_mode:
1201
1208
  self.show_nodes = False
1202
- self.pan_mode = False
1203
- self.select_mode = False
1204
- self.eraser_mode = False
1209
+ self.tool_manager.deactivate()
1205
1210
  continue
1206
1211
  elif event.key == pygame.K_n and (ctrl_held or meta_held):
1207
1212
  self.open_map_setup()
@@ -1210,27 +1215,22 @@ class Editor:
1210
1215
  self.perform_load()
1211
1216
  continue
1212
1217
  elif event.key == pygame.K_SPACE and (ctrl_held or meta_held):
1213
- if self.pan_mode:
1214
- self.pan_mode = False
1215
- if getattr(self, "_prev_tool", None) == "select":
1216
- self.select_mode = True
1217
- elif getattr(self, "_prev_tool", None) == "eraser":
1218
- self.eraser_mode = True
1219
- elif getattr(self, "_prev_tool", None) == "nodes":
1218
+ if self.tool_manager.is_active(ToolKind.PAN):
1219
+ restored = self.tool_manager.restore_previous()
1220
+ if restored is None and self._prev_tool == "nodes":
1220
1221
  self.node_editing_mode = True
1222
+ self._prev_tool = None
1221
1223
  else:
1222
- if self.select_mode:
1224
+ if self.tool_manager.is_active(ToolKind.SELECT):
1223
1225
  self._prev_tool = "select"
1224
- elif self.eraser_mode:
1226
+ elif self.tool_manager.is_active(ToolKind.ERASER):
1225
1227
  self._prev_tool = "eraser"
1226
1228
  elif self.node_editing_mode:
1227
1229
  self._prev_tool = "nodes"
1228
1230
  else:
1229
1231
  self._prev_tool = None
1230
- self.pan_mode = True
1231
- self.select_mode = False
1232
- self.eraser_mode = False
1233
1232
  self.node_editing_mode = False
1233
+ self.tool_manager.activate(ToolKind.PAN)
1234
1234
  continue
1235
1235
  elif event.key == pygame.K_g and (ctrl_held or meta_held):
1236
1236
  self.toggle_grid()
@@ -4,8 +4,9 @@ Supports multiple layers with independent tile and object data.
4
4
  """
5
5
 
6
6
  import random
7
- from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Any
8
- from ttypes.tilemap import TypeTile, TypeObject
7
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple
8
+
9
+ from ttypes.tilemap import TypeObject, TypeTile
9
10
 
10
11
  if TYPE_CHECKING:
11
12
  from widgets.autotiler import AutotileRule
@@ -78,9 +79,7 @@ class Layer:
78
79
  existing_positions = [p for p in positions if p in self.tiles]
79
80
  return self._autotile_tiles(rules, existing_positions)
80
81
 
81
- def _autotile_tiles(
82
- self, rules: List["AutotileRule"], positions: List[Tuple[int, int]]
83
- ) -> int:
82
+ def _autotile_tiles(self, rules: List["AutotileRule"], positions: List[Tuple[int, int]]) -> int:
84
83
  """
85
84
  Internal helper to update specific tiles according to autotile rules.
86
85
 
@@ -97,12 +96,7 @@ class Layer:
97
96
  if not rules or not positions:
98
97
  return 0
99
98
 
100
- rules_hash = hash(
101
- tuple(
102
- (r.group_id, r.tileset_index, tuple(sorted(r.variant_ids)))
103
- for r in rules
104
- )
105
- )
99
+ rules_hash = hash(tuple((r.group_id, r.tileset_index, tuple(sorted(r.variant_ids))) for r in rules))
106
100
 
107
101
  if self._autotile_cache["rules_hash"] != rules_hash:
108
102
  variant_to_group: Dict[Tuple[int, int], str] = {}
@@ -180,21 +174,23 @@ class Layer:
180
174
  if n_group == target_group_id:
181
175
  actual_neighbors.append((dx, dy))
182
176
 
183
- neighbor_offsets_set = {
184
- n for n in actual_neighbors if n in significant_offsets
185
- }
177
+ neighbor_offsets_set = {n for n in actual_neighbors if n in significant_offsets}
186
178
 
187
179
  matched_rule: Optional["AutotileRule"] = None
180
+ new_variant: Optional[int] = None
188
181
  for rule in group_rules:
189
- if (
190
- rule.neighbors == neighbor_offsets_set
191
- and rule.tileset_index == ttype
192
- ):
182
+ if rule.neighbors == neighbor_offsets_set and rule.tileset_index == ttype:
193
183
  matched_rule = rule
194
184
  break
195
185
 
196
186
  if matched_rule and matched_rule.variant_ids:
197
- new_variant = random.choice(matched_rule.variant_ids)
187
+ # only reroll if current variant isnt already
188
+ # in the matched rule's set to prevents re-randomization
189
+ # of neighbors whose pattern hasnt actually changed.
190
+ if current_variant not in matched_rule.variant_ids:
191
+ new_variant = random.choice(matched_rule.variant_ids)
192
+ else:
193
+ new_variant = current_variant
198
194
 
199
195
  if new_variant != current_variant:
200
196
  tile["variant"] = new_variant
@@ -238,12 +234,7 @@ class Layer:
238
234
  while queue:
239
235
  curr = queue.pop(0)
240
236
 
241
- if (
242
- curr[0] < 0
243
- or curr[0] >= map_size[0]
244
- or curr[1] < 0
245
- or curr[1] >= map_size[1]
246
- ):
237
+ if curr[0] < 0 or curr[0] >= map_size[0] or curr[1] < 0 or curr[1] >= map_size[1]:
247
238
  continue
248
239
 
249
240
  curr_tile = self.tiles.get(curr)
@@ -476,9 +467,7 @@ class LayerManager:
476
467
  if not manager.layers:
477
468
  manager.create_layer("Default")
478
469
 
479
- if manager.active_layer_idx < 0 or manager.active_layer_idx >= len(
480
- manager.layers
481
- ):
470
+ if manager.active_layer_idx < 0 or manager.active_layer_idx >= len(manager.layers):
482
471
  manager.active_layer_idx = 0
483
472
 
484
473
  return manager
@@ -1 +1 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="#e0e0e0"><circle cx="8" cy="8" r="2"/><path d="M8 1a1 1 0 0 0-.691.291l-2 2 1.414 1.414 1.293-1.293 1.293 1.293 1.414-1.414-2-2A1 1 0 0 0 8 1zM3.309 5.291l-2 2a1 1 0 0 0 0 1.414l2 2 1.414-1.414L3.43 7.998l1.293-1.293-1.414-1.414zm9.414 0-1.414 1.414 1.293 1.293-1.293 1.293 1.414 1.414 2-2a1 1 0 0 0 0-1.414l-2-2zm-6 6-1.414 1.414 2 2a1 1 0 0 0 1.414 0l2-2-1.414-1.414-1.293 1.293-1.293-1.293z"/></g></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="#ffffff"><circle cx="8" cy="8" r="2"/><path d="M8 1a1 1 0 0 0-.691.291l-2 2 1.414 1.414 1.293-1.293 1.293 1.293 1.414-1.414-2-2A1 1 0 0 0 8 1zM3.309 5.291l-2 2a1 1 0 0 0 0 1.414l2 2 1.414-1.414L3.43 7.998l1.293-1.293-1.414-1.414zm9.414 0-1.414 1.414 1.293 1.293-1.293 1.293 1.414 1.414 2-2a1 1 0 0 0 0-1.414l-2-2zm-6 6-1.414 1.414 2 2a1 1 0 0 0 1.414 0l2-2-1.414-1.414-1.293 1.293-1.293-1.293z"/></g></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><rect width="14" height="14" x="1" y="1" fill="#ffffff" rx="2.33"/><path fill="#ffffff" d="M11.5 3.734 5.89 9.346 4.185 7.665l-1.5 1.499 3.204 3.18L13 5.235z"/></svg>
@@ -1 +1 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e0e0e0" d="m5 1v11h9v-6h-5v-5zm5 0v4h4zm-8 3v11h2 8v-2h-8v-9z"/></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#ffffff" d="m5 1v11h9v-6h-5v-5zm5 0v4h4zm-8 3v11h2 8v-2h-8v-9z"/></svg>
@@ -1 +1 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><circle cx="8" cy="8" r="4" fill="#ff5f5f"/></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><circle cx="8" cy="8" r="4" fill="#ffffff"/></svg>
@@ -1 +1 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e0e0e0" d="m2 1v14h12v-9h-5v-5zm8 0v4h4z"/></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#ffffff" d="m2 1v14h12v-9h-5v-5zm8 0v4h4z"/></svg>
@@ -1 +1 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#ff5f5f" d="M2 1v14h12V6H9V1zm8 0v4h4zM5 7.793l.646-.646a.353.353 0 0 1 .707.707l-.646.646.646.646a.353.353 0 0 1-.707.707L5 9.207l-.646.646a.353.353 0 0 1-.707-.706l.646-.647-.646-.646a.353.353 0 0 1 .707-.707zm6 0 .646-.646a.353.353 0 0 1 .707.707l-.646.646.646.646a.353.353 0 0 1-.706.707L11 9.207l-.646.646a.353.353 0 0 1-.707-.706l.646-.647-.646-.646a.353.353 0 0 1 .706-.707zM3.5 11h9a.5.5 0 0 1 0 1H8a2 2 0 0 1-4 0h-.5a.5.5 0 0 1 0-1zM5 12a1 1 0 0 0 2 0z"/></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#ffffff" d="M2 1v14h12V6H9V1zm8 0v4h4zM5 7.793l.646-.646a.353.353 0 0 1 .707.707l-.646.646.646.646a.353.353 0 0 1-.707.707L5 9.207l-.646.646a.353.353 0 0 1-.707-.706l.646-.647-.646-.646a.353.353 0 0 1 .707-.707zm6 0 .646-.646a.353.353 0 0 1 .707.707l-.646.646.646.646a.353.353 0 0 1-.706.707L11 9.207l-.646.646a.353.353 0 0 1-.707-.706l.646-.647-.646-.646a.353.353 0 0 1 .706-.707zM3.5 11h9a.5.5 0 0 1 0 1H8a2 2 0 0 1-4 0h-.5a.5.5 0 0 1 0-1zM5 12a1 1 0 0 0 2 0z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><circle cx="8" cy="8" r="8" fill-opacity=".4"/><path fill="#ffffff" d="M8 1a7 7 0 0 0 0 14A7 7 0 0 0 8 1zm0 3.166A1 1 0 0 1 9.526 5v7h-2V6.87l-1.445.962-1-1.6z"/></svg>
@@ -1 +1 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e0e0e0" d="M2 2a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1h-4a1 1 0 0 1-1-1V3a1 1 0 0 0-1-1z"/></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#ffffff" d="M2 2a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1h-4a1 1 0 0 1-1-1V3a1 1 0 0 0-1-1z"/></svg>
@@ -1 +1 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#8eef97" d="M3 1a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3a2 2 0 0 0-2-2zm0 2h2v2H3zm4 0h2v2H7zm4 0h2v2h-2zM3 7h2v2H3zm4 0h2v2H7zm4 0h2v2h-2zm-8 4h2v2H3zm4 0h2v2H7zm4 0h2v2h-2z"/></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#ffffff" d="M3 1a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3a2 2 0 0 0-2-2zm0 2h2v2H3zm4 0h2v2H7zm4 0h2v2h-2zM3 7h2v2H3zm4 0h2v2H7zm4 0h2v2h-2zm-8 4h2v2H3zm4 0h2v2H7zm4 0h2v2h-2z"/></svg>
@@ -1 +1 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e0e0e0" d="m2 1a1 1 0 0 0 -1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-12a1 1 0 0 0 -1-1zm1 2h10v8h-10zm6 2-1.5 2.5-.70117 1.168-.099609-.16797-.89844-1.5-.90039 1.5-.90039 1.5h1.8008.19922 1.5996 1.4004 3l-1.5-2.5-1.5-2.5z"/></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#ffffff" d="m2 1a1 1 0 0 0 -1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-12a1 1 0 0 0 -1-1zm1 2h10v8h-10zm6 2-1.5 2.5-.70117 1.168-.099609-.16797-.89844-1.5-.90039 1.5-.90039 1.5h1.8008.19922 1.5996 1.4004 3l-1.5-2.5-1.5-2.5z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#ffffff" d="M8 1a7 7 0 0 0 0 14A7 7 0 0 0 8 1zM7 4h2v2H7zm0 3h2v5H7z"/></svg>
@@ -1 +1 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e0e0e0" d="M3 2a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h9c1.105 0 1.818-.91 2-2l1-6a1 1 0 0 0-1-1H6c-.552 0-.909.455-1 1l-1 6c-.091.545-.448 1-1 1a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1 1 1 0 0 0 1 1h5a1 1 0 0 0-1-1H7a2 2 0 0 0-2-2H3z"/></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#ffffff" d="M3 2a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h9c1.105 0 1.818-.91 2-2l1-6a1 1 0 0 0-1-1H6c-.552 0-.909.455-1 1l-1 6c-.091.545-.448 1-1 1a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1 1 1 0 0 0 1 1h5a1 1 0 0 0-1-1H7a2 2 0 0 0-2-2H3z"/></svg>
@@ -1 +1 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e0e0e0" d="M8 1v2H6a5 5 0 0 0-4 8l1.414-1.414A3 3 0 0 1 6 5h2v2l4-3-4-3zm6 4-1.414 1.414A3 3 0 0 1 10 11H8V9l-4 3 4 3v-2h2a5 5 0 0 0 4-8z"/></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#ffffff" d="M8 1v2H6a5 5 0 0 0-4 8l1.414-1.414A3 3 0 0 1 6 5h2v2l4-3-4-3zm6 4-1.414 1.414A3 3 0 0 1 10 11H8V9l-4 3 4 3v-2h2a5 5 0 0 0 4-8z"/></svg>
@@ -1 +1 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e0e0e0" d="M11 2a1 1 0 0 0-2 0v6H8V3a1 1 0 0 0-2 0v8.05l-2.5-1.8A1 1 0 0 0 1.875 11L6 15h6a2 2 0 0 0 2-2V4a1 1 0 0 0-2 0v4h-1z"/></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#ffffff" d="M11 2a1 1 0 0 0-2 0v6H8V3a1 1 0 0 0-2 0v8.05l-2.5-1.8A1 1 0 0 0 1.875 11L6 15h6a2 2 0 0 0 2-2V4a1 1 0 0 0-2 0v4h-1z"/></svg>
@@ -1 +1 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e0e0e0" d="M4 3a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1zm6 0a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1z"/></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#ffffff" d="M4 3a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1zm6 0a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1z"/></svg>
@@ -1,2 +1,2 @@
1
- <?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
2
- <svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="m3.99 16.854-1.314 3.504a.75.75 0 0 0 .966.965l3.503-1.314a3 3 0 0 0 1.068-.687L18.36 9.175s-.354-1.061-1.414-2.122c-1.06-1.06-2.122-1.414-2.122-1.414L4.677 15.786a3 3 0 0 0-.687 1.068zm12.249-12.63 1.383-1.383c.248-.248.579-.406.925-.348.487.08 1.232.322 1.934 1.025.703.703.945 1.447 1.025 1.934.058.346-.1.677-.348.925L19.774 7.76s-.353-1.06-1.414-2.12c-1.06-1.062-2.121-1.415-2.121-1.415z" fill="#000000"/></svg>
1
+ <?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
2
+ <svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="m3.99 16.854-1.314 3.504a.75.75 0 0 0 .966.965l3.503-1.314a3 3 0 0 0 1.068-.687L18.36 9.175s-.354-1.061-1.414-2.122c-1.06-1.06-2.122-1.414-2.122-1.414L4.677 15.786a3 3 0 0 0-.687 1.068zm12.249-12.63 1.383-1.383c.248-.248.579-.406.925-.348.487.08 1.232.322 1.934 1.025.703.703.945 1.447 1.025 1.934.058.346-.1.677-.348.925L19.774 7.76s-.353-1.06-1.414-2.12c-1.06-1.062-2.121-1.415-2.121-1.415z" fill="#ffffff"/></svg>
@@ -1 +1 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e0e0e0" d="M4 12a1 1 0 0 0 1.555.832l6-4a1 1 0 0 0 0-1.664l-6-4A1 1 0 0 0 4 4z"/></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#ffffff" d="M4 12a1 1 0 0 0 1.555.832l6-4a1 1 0 0 0 0-1.664l-6-4A1 1 0 0 0 4 4z"/></svg>
@@ -1 +1 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e0e0e0" d="m7 1v6h-6v2h6v6h2v-6h6v-2h-6v-6z"/></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#ffffff" d="m7 1v6h-6v2h6v6h2v-6h6v-2h-6v-6z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><circle cx="8" cy="8" r="7" fill="#ffffff"/><circle cx="8" cy="8" r="4" fill="#ffffff"/></svg>
@@ -1,2 +1,2 @@
1
1
  <?xml version="1.0" ?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
2
- <svg fill="#000000" width="800px" height="800px" viewBox="0 0 512 512" data-name="Layer 1" id="Layer_1" xmlns="http://www.w3.org/2000/svg"><path d="M64,256H34A222,222,0,0,1,430,118.15V85h30V190H355V160h67.27A192.21,192.21,0,0,0,256,64C150.13,64,64,150.13,64,256Zm384,0c0,105.87-86.13,192-192,192A192.21,192.21,0,0,1,89.73,352H157V322H52V427H82V393.85A222,222,0,0,0,478,256Z"/></svg>
2
+ <svg fill="#ffffff" width="800px" height="800px" viewBox="0 0 512 512" data-name="Layer 1" id="Layer_1" xmlns="http://www.w3.org/2000/svg"><path d="M64,256H34A222,222,0,0,1,430,118.15V85h30V190H355V160h67.27A192.21,192.21,0,0,0,256,64C150.13,64,64,150.13,64,256Zm384,0c0,105.87-86.13,192-192,192A192.21,192.21,0,0,1,89.73,352H157V322H52V427H82V393.85A222,222,0,0,0,478,256Z"/></svg>
@@ -1 +1 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e0e0e0" d="M3 1a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V4l-3-3h-1v6H3zm1 0v5h3V1zm4 8a1 1 0 0 1 0 4 1 1 0 0 1 0-4z"/></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#ffffff" d="M3 1a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V4l-3-3h-1v6H3zm1 0v5h3V1zm4 8a1 1 0 0 1 0 4 1 1 0 0 1 0-4z"/></svg>
@@ -1 +1 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><rect width="10" height="10" x="3" y="3" fill="#e0e0e0" rx="1"/></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><rect width="10" height="10" x="3" y="3" fill="#ffffff" rx="1"/></svg>
@@ -1 +1 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#8da5f3" d="M1 1v2h2V1zm3 0v2h2V1zm3 0v2h2V1zm3 0v2h2V1zm3 0v2h2V1zM1 4v2h2V4zm3 0v2h2V4zm3 0v2h2V4zm3 0v2h2V4zm3 0v2h2V4zM1 7v2h2V7zm3 0v2h2V7zm3 0v2h2V7zm3 0v2h2V7zm3 0v2h2V7zM1 10v2h2v-2zm3 0v2h2v-2zm3 0v2h2v-2zm3 0v2h2v-2zm3 0v2h2v-2zM1 13v2h2v-2zm3 0v2h2v-2zm3 0v2h2v-2zm3 0v2h2v-2zm3 0v2h2v-2z"/></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#ffffff" d="M1 1v2h2V1zm3 0v2h2V1zm3 0v2h2V1zm3 0v2h2V1zm3 0v2h2V1zM1 4v2h2V4zm3 0v2h2V4zm3 0v2h2V4zm3 0v2h2V4zm3 0v2h2V4zM1 7v2h2V7zm3 0v2h2V7zm3 0v2h2V7zm3 0v2h2V7zm3 0v2h2V7zM1 10v2h2v-2zm3 0v2h2v-2zm3 0v2h2v-2zm3 0v2h2v-2zm3 0v2h2v-2zM1 13v2h2v-2zm3 0v2h2v-2zm3 0v2h2v-2zm3 0v2h2v-2zm3 0v2h2v-2z"/></svg>
@@ -1 +1 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e0e0e0" d="M1 1v2h2V1zm3 0v2h2V1zm3 0v2h2V1zm3 0v2h2V1zm3 0v2h2V1zM1 4v2h2V4zm3 0v2h2V4zm3 0v2h2V4zm3 0v2h2V4zm3 0v2h2V4zM1 7v2h2V7zm3 0v2h2V7zm5 1v7h5a1 1 0 0 0 1-1V9a1 1 0 0 0-1-1v4l-1-1-1 1V8zm-8 2v2h2v-2zm3 0v2h2v-2zm-3 3v2h2v-2zm3 0v2h2v-2z"/></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#ffffff" d="M1 1v2h2V1zm3 0v2h2V1zm3 0v2h2V1zm3 0v2h2V1zm3 0v2h2V1zM1 4v2h2V4zm3 0v2h2V4zm3 0v2h2V4zm3 0v2h2V4zm3 0v2h2V4zM1 7v2h2V7zm3 0v2h2V7zm5 1v7h5a1 1 0 0 0 1-1V9a1 1 0 0 0-1-1v4l-1-1-1 1V8zm-8 2v2h2v-2zm3 0v2h2v-2zm-3 3v2h2v-2zm3 0v2h2v-2z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><rect width="14" height="14" x="1" y="1" fill="#ffffff" rx="2.33"/><path fill="#ffffff" d="M11.5 3.734 5.89 9.346 4.185 7.665l-1.5 1.499 3.204 3.18L13 5.235z"/></svg>
@@ -1 +1 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#ffdd65" d="M8 1a7 7 0 0 0 0 14A7 7 0 0 0 8 1zM7 3h2v7H7zm0 8h2v2H7z"/></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#ffffff" d="M8 1a7 7 0 0 0 0 14A7 7 0 0 0 8 1zM7 3h2v7H7zm0 8h2v2H7z"/></svg>
@@ -1,6 +1,6 @@
1
- <?xml version="1.0" encoding="utf-8"?>
2
- <!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
3
- <svg fill="#000000" width="800px" height="800px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg">
4
- <title>zoomin</title>
5
- <path d="M16.906 20.188l5.5 5.5-2.25 2.281-5.75-5.781c-1.406 0.781-3.031 1.219-4.719 1.219-5.344 0-9.688-4.344-9.688-9.688s4.344-9.688 9.688-9.688 9.719 4.344 9.719 9.688c0 2.5-0.969 4.781-2.5 6.469zM2.688 13.719c0 3.875 3.125 6.969 7 6.969 3.844 0 7-3.094 7-6.969s-3.156-6.969-7-6.969c-3.875 0-7 3.094-7 6.969zM10.813 12.625h3.875v2.219h-3.875v3.844h-2.219v-3.844h-3.844v-2.219h3.844v-3.875h2.219v3.875z"></path>
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
3
+ <svg fill="#ffffff" width="800px" height="800px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg">
4
+ <title>zoomin</title>
5
+ <path d="M16.906 20.188l5.5 5.5-2.25 2.281-5.75-5.781c-1.406 0.781-3.031 1.219-4.719 1.219-5.344 0-9.688-4.344-9.688-9.688s4.344-9.688 9.688-9.688 9.719 4.344 9.719 9.688c0 2.5-0.969 4.781-2.5 6.469zM2.688 13.719c0 3.875 3.125 6.969 7 6.969 3.844 0 7-3.094 7-6.969s-3.156-6.969-7-6.969c-3.875 0-7 3.094-7 6.969zM10.813 12.625h3.875v2.219h-3.875v3.844h-2.219v-3.844h-3.844v-2.219h3.844v-3.875h2.219v3.875z"></path>
6
6
  </svg>
@@ -1,6 +1,6 @@
1
- <?xml version="1.0" encoding="utf-8"?>
2
- <!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
3
- <svg fill="#000000" width="800px" height="800px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg">
4
- <title>zoomout</title>
5
- <path d="M16.906 20.188l5.5 5.5-2.25 2.281-5.75-5.781c-1.406 0.781-3.031 1.219-4.719 1.219-5.344 0-9.688-4.344-9.688-9.688s4.344-9.688 9.688-9.688 9.719 4.344 9.719 9.688c0 2.5-0.969 4.781-2.5 6.469zM2.688 13.719c0 3.875 3.125 6.969 7 6.969 3.844 0 7-3.094 7-6.969s-3.156-6.969-7-6.969c-3.875 0-7 3.094-7 6.969zM4.438 12.625h10.531v2.219h-10.531v-2.219z"></path>
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
3
+ <svg fill="#ffffff" width="800px" height="800px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg">
4
+ <title>zoomout</title>
5
+ <path d="M16.906 20.188l5.5 5.5-2.25 2.281-5.75-5.781c-1.406 0.781-3.031 1.219-4.719 1.219-5.344 0-9.688-4.344-9.688-9.688s4.344-9.688 9.688-9.688 9.719 4.344 9.719 9.688c0 2.5-0.969 4.781-2.5 6.469zM2.688 13.719c0 3.875 3.125 6.969 7 6.969 3.844 0 7-3.094 7-6.969s-3.156-6.969-7-6.969c-3.875 0-7 3.094-7 6.969zM4.438 12.625h10.531v2.219h-10.531v-2.219z"></path>
6
6
  </svg>
@@ -43,6 +43,11 @@ def main() -> None:
43
43
  "--size", default="1500x900", help="Window size as WIDTHxHEIGHT"
44
44
  )
45
45
  run_parser.add_argument("--fps", type=int, default=60, help="Editor FPS")
46
+ run_parser.add_argument(
47
+ "--theme",
48
+ default=None,
49
+ help='Theme name or path to .json theme file (e.g. "molokai", "path/to/custom.json")',
50
+ )
46
51
 
47
52
  args = parser.parse_args()
48
53
 
@@ -74,7 +79,7 @@ def _run_editor(args) -> None:
74
79
  try:
75
80
  with error_context("cli_main"):
76
81
  size = _parse_size(args.size)
77
- editor = Editor(size=size, fps=max(1, args.fps))
82
+ editor = Editor(size=size, fps=max(1, args.fps), theme=args.theme)
78
83
  editor.run()
79
84
  except KeyboardInterrupt:
80
85
  print("\nEditor interrupted by user")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tilemap-editor
3
- Version: 4.2.0
3
+ Version: 4.3.0
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
@@ -179,6 +179,7 @@ src/widgets/ui/collision_layer_mask.py
179
179
  src/widgets/ui/collision_layer_sidebar.py
180
180
  src/widgets/ui/confirm_dialog.py
181
181
  src/widgets/ui/dialog_base.py
182
+ src/widgets/ui/drag_tracker.py
182
183
  src/widgets/ui/draw_utils.py
183
184
  src/widgets/ui/fileinput.py
184
185
  src/widgets/ui/label.py
@@ -196,11 +197,13 @@ src/widgets/ui/sidebar_container.py
196
197
  src/widgets/ui/status_bar.py
197
198
  src/widgets/ui/theme.py
198
199
  src/widgets/ui/tileset_type_dialog.py
200
+ src/widgets/ui/tool_manager.py
199
201
  src/widgets/ui/toolbar.py
200
202
  src/widgets/ui/tooltip.py
201
203
  tests/test_animation_library_grid_offset.py
202
204
  tests/test_collision_layer_mask.py
203
205
  tests/test_collision_layer_sidebar.py
206
+ tests/test_drag_tracker.py
204
207
  tests/test_editor_pan_mode.py
205
208
  tests/test_gid_collision.py
206
209
  tests/test_layers_autotile.py
@@ -386,6 +386,41 @@ class IconManager:
386
386
  thickness,
387
387
  )
388
388
 
389
+ elif name == "select":
390
+ thickness = max(2, size // 8)
391
+ p = padding
392
+ r = size - padding
393
+ pygame.draw.rect(surface, color, (p + 2, p + 2, r - p - 6, r - p - 6), thickness)
394
+ pygame.draw.line(surface, color, (r - 6, r - 6), (r, r), thickness + 1)
395
+
396
+ elif name == "eraser":
397
+ thickness = max(2, size // 8)
398
+ p = padding
399
+ r = size - padding
400
+ pygame.draw.rect(surface, color, (p, r // 2, r - p, r // 2 - p + 2))
401
+ pygame.draw.polygon(
402
+ surface, color,
403
+ [(r - p - 4, p + 2), (r - p, p + 2), (p + 4, r // 2), (p, r // 2)],
404
+ )
405
+
406
+ elif name == "auto":
407
+ thickness = max(2, size // 10)
408
+ cx, cy = size // 2, size // 2
409
+ pygame.draw.circle(surface, color, (cx, cy), size // 4, thickness)
410
+ pygame.draw.line(surface, color, (cx, cy), (cx, padding), thickness)
411
+ pygame.draw.line(surface, color, (cx, cy), (size - padding, cy), thickness)
412
+ pygame.draw.line(surface, color, (cx, cy), (cx, size - padding), thickness)
413
+
414
+ elif name == "nodes":
415
+ thickness = max(2, size // 10)
416
+ r = size // 8
417
+ positions = [(size // 2, size // 4), (size // 4, size * 3 // 4), (size * 3 // 4, size * 3 // 4)]
418
+ for px, py in positions:
419
+ pygame.draw.circle(surface, color, (px, py), r)
420
+ for i in range(len(positions)):
421
+ for j in range(i + 1, len(positions)):
422
+ pygame.draw.line(surface, color, positions[i], positions[j], 1)
423
+
389
424
  elif name == "pan":
390
425
  thickness = max(2, size // 8)
391
426
  cx, cy = size // 2, size // 2
@@ -476,14 +511,15 @@ class IconManager:
476
511
  Returns:
477
512
  pygame.Surface with the rendered icon
478
513
  """
514
+ svg_name = _ICON_ALIASES.get(name, name)
479
515
  cache_key = (name, size, color or (0, 0, 0))
480
516
  if cache_key in self._surface_cache:
481
517
  return self._surface_cache[cache_key]
482
518
 
483
519
  surface = None
484
520
 
485
- if self.has_icon(name):
486
- svg_path = self._icons_path / f"{name}.svg"
521
+ if self.has_icon(svg_name):
522
+ svg_path = self._icons_path / f"{svg_name}.svg"
487
523
  try:
488
524
  if hasattr(pygame.image, "load_sized_svg"):
489
525
  surface = pygame.image.load_sized_svg(str(svg_path), (size, size))
@@ -496,7 +532,7 @@ class IconManager:
496
532
 
497
533
  if surface is None:
498
534
  fallback_color = color or (200, 200, 200)
499
- surface = self._draw_fallback_icon(name, size, fallback_color)
535
+ surface = self._draw_fallback_icon(svg_name, size, fallback_color)
500
536
 
501
537
  if color and surface:
502
538
  tinted = surface.copy()
@@ -523,9 +559,16 @@ def get_icon(
523
559
  return icon_manager.get_icon(name, size, color)
524
560
 
525
561
 
562
+ _ICON_ALIASES = {
563
+ "zoom_in": "zoomin",
564
+ "zoom_out": "zoomout",
565
+ }
566
+
567
+
526
568
  def has_icon(name: str) -> bool:
527
569
  """Check if an icon exists (either as SVG or fallback)."""
528
- return icon_manager.has_icon(name) or name in [
570
+ resolved = _ICON_ALIASES.get(name, name)
571
+ return icon_manager.has_icon(resolved) or resolved in [
529
572
  "plus",
530
573
  "close",
531
574
  "x",
@@ -547,4 +590,8 @@ def has_icon(name: str) -> bool:
547
590
  "fit",
548
591
  "pan",
549
592
  "radio",
593
+ "select",
594
+ "eraser",
595
+ "auto",
596
+ "nodes",
550
597
  ]
@@ -246,6 +246,19 @@ class AutotileRuleDesigner:
246
246
  all_rules.extend(g.rules)
247
247
  return all_rules
248
248
 
249
+ @property
250
+ def variant_to_group(self):
251
+ """Map (tileset_index, variant_id) -> group_id from all rules."""
252
+ mapping = {}
253
+ for g in self.groups:
254
+ for rule in g.rules:
255
+ ts_idx = rule.tileset_index
256
+ for vid in rule.variant_ids:
257
+ key = (ts_idx, vid)
258
+ if key not in mapping:
259
+ mapping[key] = g.name
260
+ return mapping
261
+
249
262
  def handle_event(self, event) -> bool:
250
263
  if not self.visible:
251
264
  return False