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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (202) hide show
  1. {tilemap_editor-3.3.2/src/tilemap_editor.egg-info → tilemap_editor-3.3.4}/PKG-INFO +1 -1
  2. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/pyproject.toml +1 -1
  3. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/editor.py +19 -4
  4. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/node_manager.py +11 -1
  5. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/plugins/sprite_animation/preview.py +2 -1
  6. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/plugins/sprite_editor/editor.py +24 -5
  7. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/plugins/tileset_collision/collision_painter.py +137 -34
  8. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/plugins/tileset_collision/editor.py +192 -2
  9. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/tilemap.py +4 -2
  10. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4/src/tilemap_editor.egg-info}/PKG-INFO +1 -1
  11. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/tilemap_editor.egg-info/SOURCES.txt +4 -0
  12. tilemap_editor-3.3.4/src/widgets/particle_presets.py +663 -0
  13. tilemap_editor-3.3.4/src/widgets/particle_system.py +436 -0
  14. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/widgets/spritesheet_grid.py +190 -30
  15. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/widgets/tile_grid.py +67 -12
  16. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/widgets/tile_selector.py +19 -8
  17. tilemap_editor-3.3.4/src/widgets/ui/checkbox.py +99 -0
  18. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/widgets/ui/node_editor.py +114 -24
  19. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/widgets/ui/node_selector.py +15 -4
  20. tilemap_editor-3.3.4/src/widgets/ui/particle_config_dialog.py +712 -0
  21. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/LICENSE +0 -0
  22. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/MANIFEST.in +0 -0
  23. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/README.md +0 -0
  24. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/assets/fonts/jetbrain-fonts/JetBrainsMono-Bold.ttf +0 -0
  25. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/assets/fonts/jetbrain-fonts/JetBrainsMono-BoldItalic.ttf +0 -0
  26. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/assets/fonts/jetbrain-fonts/JetBrainsMono-Italic.ttf +0 -0
  27. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/assets/fonts/jetbrain-fonts/JetBrainsMono-Regular.ttf +0 -0
  28. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/assets/fonts/noto/NotoSans-Bold.ttf +0 -0
  29. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/assets/fonts/noto/NotoSans-BoldItalic.ttf +0 -0
  30. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/assets/fonts/noto/NotoSans-Italic.ttf +0 -0
  31. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/assets/fonts/noto/NotoSans-Regular.ttf +0 -0
  32. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/assets/icons/arrow-down.svg +0 -0
  33. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/assets/icons/check.svg +0 -0
  34. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/assets/icons/checked.svg +0 -0
  35. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/assets/icons/close.svg +0 -0
  36. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/assets/icons/duplicate.svg +0 -0
  37. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/assets/icons/error.svg +0 -0
  38. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/assets/icons/file.svg +0 -0
  39. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/assets/icons/filedead.svg +0 -0
  40. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/assets/icons/fit.svg +0 -0
  41. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/assets/icons/folder.svg +0 -0
  42. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/assets/icons/grid.svg +0 -0
  43. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/assets/icons/image.svg +0 -0
  44. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/assets/icons/info.svg +0 -0
  45. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/assets/icons/load.svg +0 -0
  46. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/assets/icons/loop.svg +0 -0
  47. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/assets/icons/pan.svg +0 -0
  48. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/assets/icons/pause.svg +0 -0
  49. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/assets/icons/pencil.svg +0 -0
  50. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/assets/icons/play.svg +0 -0
  51. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/assets/icons/plus.svg +0 -0
  52. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/assets/icons/radio.svg +0 -0
  53. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/assets/icons/reset.svg +0 -0
  54. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/assets/icons/save.svg +0 -0
  55. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/assets/icons/stop.svg +0 -0
  56. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/assets/icons/tilemap.svg +0 -0
  57. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/assets/icons/tileset.svg +0 -0
  58. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/assets/icons/unchecked.svg +0 -0
  59. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/assets/icons/warning.svg +0 -0
  60. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/assets/icons/zoomin.svg +0 -0
  61. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/assets/icons/zoomout.svg +0 -0
  62. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/setup.cfg +0 -0
  63. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/configs/themes.py +0 -0
  64. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/constants.py +0 -0
  65. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/event_map.py +0 -0
  66. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/layers.py +0 -0
  67. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/main.py +0 -0
  68. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/nodes.py +0 -0
  69. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/plugins/__init__.py +0 -0
  70. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/plugins/character_collision/__init__.py +0 -0
  71. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/plugins/character_collision/editor.py +0 -0
  72. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/plugins/character_collision/models.py +0 -0
  73. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/plugins/character_collision/protocols.py +0 -0
  74. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/plugins/character_collision/shape_editor.py +0 -0
  75. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/plugins/character_collision/standalone.py +0 -0
  76. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/plugins/object_tileset_collision/__init__.py +0 -0
  77. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/plugins/object_tileset_collision/editor.py +0 -0
  78. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/plugins/object_tileset_collision/models.py +0 -0
  79. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/plugins/object_tileset_collision/protocols.py +0 -0
  80. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/plugins/object_tileset_collision/standalone.py +0 -0
  81. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/plugins/sprite_animation/__init__.py +0 -0
  82. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/plugins/sprite_animation/__main__.py +0 -0
  83. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/plugins/sprite_animation/clipboard_util.py +0 -0
  84. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/plugins/sprite_animation/editor.py +0 -0
  85. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/plugins/sprite_animation/frame_picker.py +0 -0
  86. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/plugins/sprite_animation/models.py +0 -0
  87. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/plugins/sprite_animation/protocols.py +0 -0
  88. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/plugins/sprite_animation/runtime_load.py +0 -0
  89. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/plugins/sprite_animation/standalone.py +0 -0
  90. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/plugins/sprite_animation/timeline.py +0 -0
  91. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/plugins/sprite_animation/validation.py +0 -0
  92. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/plugins/sprite_editor/__init__.py +0 -0
  93. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/plugins/sprite_editor/standalone.py +0 -0
  94. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/plugins/tileset_collision/__init__.py +0 -0
  95. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/plugins/tileset_collision/models.py +0 -0
  96. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/plugins/tileset_collision/protocols.py +0 -0
  97. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/plugins/tileset_collision/standalone.py +0 -0
  98. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/standalone_automap.py +0 -0
  99. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/standalone_error_console.py +0 -0
  100. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/standalone_filemanager.py +0 -0
  101. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/standalone_image_viewer.py +0 -0
  102. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/tilemap_editor/__init__.py +0 -0
  103. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/tilemap_editor/__main__.py +0 -0
  104. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/__init__.py +0 -0
  105. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/fonts/jetbrain-fonts/JetBrainsMono-Bold.ttf +0 -0
  106. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/fonts/jetbrain-fonts/JetBrainsMono-BoldItalic.ttf +0 -0
  107. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/fonts/jetbrain-fonts/JetBrainsMono-Italic.ttf +0 -0
  108. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/fonts/jetbrain-fonts/JetBrainsMono-Regular.ttf +0 -0
  109. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/fonts/noto/NotoSans-Bold.ttf +0 -0
  110. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/fonts/noto/NotoSans-BoldItalic.ttf +0 -0
  111. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/fonts/noto/NotoSans-Italic.ttf +0 -0
  112. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/fonts/noto/NotoSans-Regular.ttf +0 -0
  113. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/ToolMove.svg +0 -0
  114. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/arrow-down.svg +0 -0
  115. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/check.svg +0 -0
  116. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/checked.svg +0 -0
  117. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/close.svg +0 -0
  118. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/duplicate.svg +0 -0
  119. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/error.svg +0 -0
  120. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/file.svg +0 -0
  121. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/filedead.svg +0 -0
  122. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/fit.svg +0 -0
  123. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/folder.svg +0 -0
  124. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/grid.svg +0 -0
  125. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/image.svg +0 -0
  126. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/info.svg +0 -0
  127. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/load.svg +0 -0
  128. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/loop.svg +0 -0
  129. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/pan.svg +0 -0
  130. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/pause.svg +0 -0
  131. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/pencil.svg +0 -0
  132. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/play.svg +0 -0
  133. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/plus.svg +0 -0
  134. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/radio.svg +0 -0
  135. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/reset.svg +0 -0
  136. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/save.svg +0 -0
  137. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/stop.svg +0 -0
  138. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/tilemap.svg +0 -0
  139. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/tileset.svg +0 -0
  140. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/unchecked.svg +0 -0
  141. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/warning.svg +0 -0
  142. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/zoomin.svg +0 -0
  143. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/tilemap_editor/assets/icons/zoomout.svg +0 -0
  144. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/tilemap_editor/cli.py +0 -0
  145. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/tilemap_editor/main.py +0 -0
  146. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/tilemap_editor/settings.py +0 -0
  147. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/tilemap_editor.egg-info/dependency_links.txt +0 -0
  148. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/tilemap_editor.egg-info/entry_points.txt +0 -0
  149. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/tilemap_editor.egg-info/requires.txt +0 -0
  150. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/tilemap_editor.egg-info/top_level.txt +0 -0
  151. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/ttypes/__init__.py +0 -0
  152. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/ttypes/tilemap.py +0 -0
  153. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/utils/__init__.py +0 -0
  154. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/utils/editor_preference.py +0 -0
  155. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/utils/error_handler.py +0 -0
  156. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/utils/font_manager.py +0 -0
  157. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/utils/history.py +0 -0
  158. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/utils/icon_manager.py +0 -0
  159. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/utils/icons_cache.py +0 -0
  160. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/utils/log_capture.py +0 -0
  161. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/utils/project_paths.py +0 -0
  162. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/utils/serialization.py +0 -0
  163. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/utils/standalone.py +0 -0
  164. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/utils/validation.py +0 -0
  165. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/widgets/__init__.py +0 -0
  166. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/widgets/automap_models.py +0 -0
  167. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/widgets/autotile_template.py +0 -0
  168. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/widgets/autotiler.py +0 -0
  169. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/widgets/filemanager.py +0 -0
  170. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/widgets/input.py +0 -0
  171. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/widgets/layer_selector.py +0 -0
  172. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/widgets/map_properties.py +0 -0
  173. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/widgets/mapsetup.py +0 -0
  174. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/widgets/regex_automap_designer.py +0 -0
  175. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/widgets/ui/collision_layer_mask.py +0 -0
  176. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/widgets/ui/collision_layer_sidebar.py +0 -0
  177. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/widgets/ui/confirm_dialog.py +0 -0
  178. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/widgets/ui/draw_utils.py +0 -0
  179. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/widgets/ui/fileinput.py +0 -0
  180. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/widgets/ui/layer_type_dialog.py +0 -0
  181. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/widgets/ui/menubar.py +0 -0
  182. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/widgets/ui/mode_indicator.py +0 -0
  183. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/widgets/ui/notification.py +0 -0
  184. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/widgets/ui/property_editor.py +0 -0
  185. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/widgets/ui/region_selector.py +0 -0
  186. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/widgets/ui/status_bar.py +0 -0
  187. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/widgets/ui/theme.py +0 -0
  188. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/widgets/ui/tileset_type_dialog.py +0 -0
  189. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/widgets/ui/toolbar.py +0 -0
  190. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/src/widgets/ui/tooltip.py +0 -0
  191. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/tests/test_animation_library_grid_offset.py +0 -0
  192. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/tests/test_collision_layer_mask.py +0 -0
  193. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/tests/test_collision_layer_sidebar.py +0 -0
  194. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/tests/test_editor_pan_mode.py +0 -0
  195. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/tests/test_project_paths.py +0 -0
  196. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/tests/test_render_scale.py +0 -0
  197. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/tests/test_sprite_animation_editor_grid.py +0 -0
  198. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/tests/test_tile_grid_selection.py +0 -0
  199. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/tests/test_tile_selector_pick.py +0 -0
  200. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/tests/test_tilemap_save.py +0 -0
  201. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/tests/test_timeline_invalidate_cache.py +0 -0
  202. {tilemap_editor-3.3.2 → tilemap_editor-3.3.4}/tests/test_toolbar_tools.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tilemap-editor
3
- Version: 3.3.2
3
+ Version: 3.3.4
4
4
  Summary: Pygame tilemap editor with SVG icons, professional UI, and sprite animation tools
5
5
  Author: tilemap editor contributors
6
6
  License: Proprietary
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "tilemap-editor"
7
- version = "3.3.2"
7
+ version = "3.3.4"
8
8
  description = "Pygame tilemap editor with SVG icons, professional UI, and sprite animation tools"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -35,6 +35,7 @@ from widgets.ui.node_selector import NodeSelector
35
35
  from widgets.ui.node_editor import NodeEditor
36
36
  from widgets.ui.notification import NotificationManager
37
37
  from widgets.ui.property_editor import PropertyEditor
38
+ from widgets.ui.particle_config_dialog import ParticleConfigDialog
38
39
  from widgets.ui.tooltip import TooltipManager
39
40
  from widgets.ui.theme import get_theme_manager, set_theme, THEMES
40
41
  from utils.log_capture import setup_console_log
@@ -192,6 +193,7 @@ class Editor:
192
193
  self.tooltip = TooltipManager()
193
194
  self.error_console_process: Optional[subprocess.Popen] = None
194
195
  self.property_editor: Optional[PropertyEditor] = None
196
+ self.particle_config_dialog: Optional[ParticleConfigDialog] = None
195
197
 
196
198
  self.child_processes: List[subprocess.Popen] = []
197
199
  self.file_manager_process: Optional[subprocess.Popen] = None
@@ -246,7 +248,7 @@ class Editor:
246
248
  self.map_properties_dialog = MapPropertiesDialog(self, Rect(center_x, center_y, 400, 260))
247
249
  self.node_manager = NodeManager(self)
248
250
  self.node_selector = NodeSelector(self, 0, 65, 260, 240)
249
- self.node_editor = NodeEditor(self, 0, 310, 260, 190)
251
+ self.node_editor = NodeEditor(self, 0, 310, 260, 230)
250
252
 
251
253
  def open_file_manager(
252
254
  self,
@@ -443,6 +445,9 @@ class Editor:
443
445
  error_handler.capture(e, context="save_map_selected")
444
446
 
445
447
  def export_selection_as_png(self):
448
+ if self.tile_grid_widget is None:
449
+ self.notifications.notify("No tile grid available", duration=2.0)
450
+ return
446
451
  rect = self.tile_grid_widget.selection_rect
447
452
  if rect is None:
448
453
  self.notifications.notify("No region selected", duration=2.0)
@@ -458,6 +463,9 @@ class Editor:
458
463
  rect = self.tile_grid_widget.selection_rect
459
464
  if rect is None:
460
465
  return
466
+ if self.tileset_widget is None:
467
+ self.notifications.notify("No tileset available", duration=2.0)
468
+ return
461
469
  x1, y1, x2, y2 = rect
462
470
  tw, th = self.tilemap.tile_size
463
471
  pw = (x2 - x1 + 1) * tw
@@ -811,7 +819,7 @@ class Editor:
811
819
  resolved = None
812
820
 
813
821
  if not resolved or not resolved.exists():
814
- print(f"Could not locate spritesheet for animation file: {path.name}")
822
+ self.notifications.notify(f"Could not locate spritesheet: {path.name}", duration=2.0)
815
823
  return
816
824
 
817
825
  args = [
@@ -1135,6 +1143,10 @@ class Editor:
1135
1143
  if self.property_editor.handle_event(event):
1136
1144
  continue
1137
1145
 
1146
+ if self.particle_config_dialog and self.particle_config_dialog.active:
1147
+ if self.particle_config_dialog.handle_event(event):
1148
+ continue
1149
+
1138
1150
  # Priority 2: Autotiler and Regex Designer (block all events when visible)
1139
1151
  if self.autotiler.visible:
1140
1152
  if self.autotiler.handle_event(event):
@@ -1354,8 +1366,6 @@ class Editor:
1354
1366
  self.screen.blit(overlay, (0, 0))
1355
1367
  self.layer_type_dialog.draw(self.screen)
1356
1368
 
1357
- if self.property_editor and self.property_editor.active:
1358
- self.property_editor.draw(self.screen)
1359
1369
  if self.loading_state["active"]:
1360
1370
  overlay = pygame.Surface((self.width, self.height), pygame.SRCALPHA)
1361
1371
  overlay.fill((0, 0, 0, 180))
@@ -1381,6 +1391,11 @@ class Editor:
1381
1391
  self.node_selector.draw(self.screen)
1382
1392
  if self.node_editor:
1383
1393
  self.node_editor.draw(self.screen)
1394
+ # Modal dialogs draw on top of everything
1395
+ if self.property_editor and self.property_editor.active:
1396
+ self.property_editor.draw(self.screen)
1397
+ if self.particle_config_dialog and self.particle_config_dialog.active:
1398
+ self.particle_config_dialog.draw(self.screen)
1384
1399
  self.menubar.draw(self.screen)
1385
1400
  self.tooltip.draw(self.screen)
1386
1401
 
@@ -100,12 +100,20 @@ class NodeManager:
100
100
 
101
101
  def create_default_node(self, layer_name: str, node_type: str = "area") -> Node:
102
102
  count = len(self.nodes) + 1
103
+ props: Dict[str, Any] = {}
104
+ if node_type == "particle_emitter":
105
+ from widgets.particle_system import get_default_config
106
+ props = get_default_config()
107
+ name = f"Emitter {count}"
108
+ else:
109
+ name = f"{node_type.capitalize()} {count}"
103
110
  return Node(
104
111
  node_id=create_node_id(),
105
- name=f"{node_type.capitalize()} {count}",
112
+ name=name,
106
113
  node_type=node_type,
107
114
  area=NodeRect(x=0, y=0, w=64, h=64),
108
115
  layer_name=layer_name,
116
+ properties=props,
109
117
  )
110
118
 
111
119
  def rename_group(self, old_name: str, new_name: str) -> bool:
@@ -132,6 +140,8 @@ class NodeManager:
132
140
  def reorder_node(self, node_id: str, target_node_id: str, before: bool = True) -> None:
133
141
  if node_id not in self.nodes or target_node_id not in self.nodes:
134
142
  return
143
+ if node_id == target_node_id:
144
+ return
135
145
  node = self.nodes[node_id]
136
146
  keys = list(self.nodes.keys())
137
147
  keys.remove(node_id)
@@ -416,7 +416,8 @@ class AnimationPreview:
416
416
 
417
417
  def _extract_tile(self, variant_id: int) -> Optional[pygame.Surface]:
418
418
  tw, th = self.tile_size
419
- ox, oy = self.grid_offset_x, self.grid_offset_y
419
+ ox = self.grid_offset_x
420
+ oy = self.grid_offset_y
420
421
  available_w = self.surface.get_width() - ox
421
422
  cols = max(1, available_w // tw)
422
423
  col = variant_id % cols
@@ -149,6 +149,24 @@ class SpriteEditor:
149
149
  if self.grid.redo():
150
150
  self.grid.selected_indices.clear()
151
151
  return True
152
+ elif event.key == pygame.K_c:
153
+ if self.grid.has_selection():
154
+ self._clipboard = self.grid.copy_selected()
155
+ self._paste_mode = False
156
+ self.grid.paste_preview_idx = -1
157
+ return True
158
+ elif event.key == pygame.K_x:
159
+ if self.grid.has_selection():
160
+ self._clipboard = self.grid.cut_selected()
161
+ self._paste_mode = False
162
+ self.grid.paste_preview_idx = -1
163
+ return True
164
+ elif event.key == pygame.K_v:
165
+ if self._clipboard:
166
+ self._paste_mode = not self._paste_mode
167
+ if not self._paste_mode:
168
+ self.grid.paste_preview_idx = -1
169
+ return True
152
170
 
153
171
  # Toolbar clicks
154
172
  if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
@@ -156,19 +174,20 @@ class SpriteEditor:
156
174
  if r.collidepoint(mouse):
157
175
  return self._handle_tool(lbl)
158
176
 
159
- # Paste mode: next grid click pastes
177
+ # Paste mode: click places (or exits if outside grid)
160
178
  if self._paste_mode and event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
161
179
  idx = self.grid.index_at_pos(mouse)
162
180
  if idx >= 0 and self.grid.rect.collidepoint(mouse):
163
181
  self.grid.paste_at(idx, self._clipboard)
164
- self._paste_mode = False
165
- self.grid.paste_preview_idx = -1
166
- return True
182
+ self._paste_mode = False
183
+ self.grid.paste_preview_idx = -1
184
+ return True
167
185
 
168
- # Paste preview on hover
186
+ # Paste preview on hover (consume event to prevent grid overlap)
169
187
  if self._paste_mode and event.type == pygame.MOUSEMOTION:
170
188
  idx = self.grid.index_at_pos(mouse)
171
189
  self.grid.paste_preview_idx = idx if idx >= 0 else -1
190
+ return True
172
191
 
173
192
  return self.grid.handle_event(event)
174
193
 
@@ -117,7 +117,8 @@ class CollisionPainter:
117
117
  self.snap_to_grid = False
118
118
  self.grid_size = 8 # pixels
119
119
  self.show_grid = True
120
- self.edge_draw_mode = False # E key - straight line drawing
120
+ self.show_angle_hints = False # show angle arcs at vertices
121
+ self.edge_draw_mode = False # Ctrl+E - straight line drawing
121
122
  self._edge_start: Optional[Tuple[int, int]] = None # Start point for edge draw
122
123
  self._shift_held = False
123
124
 
@@ -270,7 +271,66 @@ class CollisionPainter:
270
271
  def get_one_way_flags(self) -> List[bool]:
271
272
  """Get one-way collision flags for all polygons"""
272
273
  return list(self.polygon_one_way)
274
+
275
+ def toggle_one_way(self) -> None:
276
+ """Toggle one-way flag on the selected polygon"""
277
+ idx = self.selected_polygon_idx
278
+ if idx is not None and 0 <= idx < len(self.polygon_one_way):
279
+ self.polygon_one_way[idx] = not self.polygon_one_way[idx]
280
+ if self.on_polygon_modified:
281
+ self.on_polygon_modified(idx)
273
282
 
283
+ def _get_interior_angle(
284
+ self, polygon: List[Tuple[float, float]], idx: int
285
+ ) -> float:
286
+ """Compute interior angle (degrees) at polygon vertex idx."""
287
+ n = len(polygon)
288
+ if n < 3:
289
+ return 0.0
290
+ prev = polygon[(idx - 1) % n]
291
+ curr = polygon[idx]
292
+ nxt = polygon[(idx + 1) % n]
293
+ v1 = (prev[0] - curr[0], prev[1] - curr[1])
294
+ v2 = (nxt[0] - curr[0], nxt[1] - curr[1])
295
+ dot = v1[0] * v2[0] + v1[1] * v2[1]
296
+ mag1 = math.hypot(v1[0], v1[1])
297
+ mag2 = math.hypot(v2[0], v2[1])
298
+ if mag1 < 0.001 or mag2 < 0.001:
299
+ return 0.0
300
+ cos_a = max(-1.0, min(1.0, dot / (mag1 * mag2)))
301
+ return math.degrees(math.acos(cos_a))
302
+
303
+ def _draw_angle_hint(
304
+ self,
305
+ screen: pygame.Surface,
306
+ screen_pos: Tuple[int, int],
307
+ angle_deg: float,
308
+ ) -> None:
309
+ """Draw a small arc + angle text at a vertex."""
310
+ if angle_deg <= 0.5 or angle_deg >= 179.5:
311
+ return
312
+ arc_radius = 14
313
+ segments = max(4, int(angle_deg / 10))
314
+ angle_rad = math.radians(angle_deg)
315
+ pts = [screen_pos]
316
+ for i in range(segments + 1):
317
+ t = -angle_rad * i / segments
318
+ pts.append((
319
+ screen_pos[0] + arc_radius * math.cos(t),
320
+ screen_pos[1] + arc_radius * math.sin(t),
321
+ ))
322
+ if len(pts) >= 3:
323
+ wedge = pygame.Surface(self.rect.size, pygame.SRCALPHA)
324
+ pygame.draw.polygon(wedge, (*_COLORS["polygon_fill"], 60), pts)
325
+ screen.blit(wedge, self.rect.topleft)
326
+ label = self._font_sm.render(f"{angle_deg:.0f}°", True, _COLORS["text"])
327
+ label_x = screen_pos[0] + 10
328
+ label_y = screen_pos[1] - label.get_height() - 4
329
+ bg = pygame.Surface((label.get_width() + 4, label.get_height() + 2), pygame.SRCALPHA)
330
+ bg.fill((0, 0, 0, 180))
331
+ screen.blit(bg, (label_x - 2, label_y - 1))
332
+ screen.blit(label, (label_x, label_y))
333
+
274
334
  def clear_all(self) -> None:
275
335
  """Clear all polygons"""
276
336
  self.polygons = []
@@ -488,23 +548,26 @@ class CollisionPainter:
488
548
 
489
549
  # Keyboard shortcuts
490
550
  if event.type == pygame.KEYDOWN:
551
+ mods = pygame.key.get_mods()
552
+ ctrl = mods & (pygame.KMOD_LCTRL | pygame.KMOD_LMETA)
553
+
491
554
  # Track Shift state for edge draw
492
555
  if event.key in (pygame.K_LSHIFT, pygame.K_RSHIFT):
493
556
  self._shift_held = True
494
-
495
- # E - toggle edge draw mode
496
- elif event.key == pygame.K_e:
557
+
558
+ # Ctrl+E - toggle edge draw mode
559
+ elif ctrl and event.key == pygame.K_e:
497
560
  self.edge_draw_mode = not self.edge_draw_mode
498
561
  if not self.edge_draw_mode:
499
562
  self._edge_start = None
500
563
  return True
501
-
564
+
502
565
  # Enter - complete polygon
503
566
  elif event.key in (pygame.K_RETURN, pygame.K_KP_ENTER):
504
567
  if len(self.current_polygon) >= 3:
505
568
  self._complete_polygon()
506
569
  return True
507
-
570
+
508
571
  # Escape - cancel current polygon or deselect or close help
509
572
  elif event.key == pygame.K_ESCAPE:
510
573
  if self.show_help:
@@ -517,44 +580,45 @@ class CollisionPainter:
517
580
  self.selected_polygon_idx = None
518
581
  self.selected_vertex_idx = None
519
582
  return True
520
-
521
- # Delete - remove selected polygon
583
+
584
+ # Delete/Backspace - remove selected polygon or last vertex
522
585
  elif event.key in (pygame.K_DELETE, pygame.K_BACKSPACE):
523
586
  if self.selected_polygon_idx is not None:
524
587
  self._delete_polygon(self.selected_polygon_idx)
525
588
  return True
526
589
  elif self.current_polygon:
527
- # Remove last vertex
528
590
  self.current_polygon.pop()
529
591
  return True
530
-
531
- # G - toggle grid
532
- elif event.key == pygame.K_g:
592
+
593
+ # Ctrl+G - toggle grid
594
+ elif ctrl and event.key == pygame.K_g:
533
595
  self.show_grid = not self.show_grid
534
596
  return True
535
-
536
- # S - toggle snap (only if Ctrl is not held)
537
- elif event.key == pygame.K_s:
538
- mods = pygame.key.get_mods()
539
- ctrl_held = mods & (pygame.KMOD_CTRL | pygame.KMOD_LMETA)
540
- if not ctrl_held:
541
- self.snap_to_grid = not self.snap_to_grid
542
- return True
543
-
544
- # O - toggle one-way collision for selected polygon
545
- elif event.key == pygame.K_o:
597
+
598
+ # Ctrl+Shift+S - toggle snap (Ctrl+S reserved for save)
599
+ elif ctrl and event.key == pygame.K_s and (mods & pygame.KMOD_SHIFT):
600
+ self.snap_to_grid = not self.snap_to_grid
601
+ return True
602
+
603
+ # Ctrl+O - toggle one-way collision for selected polygon
604
+ elif ctrl and event.key == pygame.K_o:
546
605
  if self.selected_polygon_idx is not None:
547
606
  idx = self.selected_polygon_idx
548
607
  self.polygon_one_way[idx] = not self.polygon_one_way[idx]
549
608
  if self.on_polygon_modified:
550
609
  self.on_polygon_modified(idx)
551
610
  return True
552
-
553
- # R - reset view
554
- elif event.key == pygame.K_r:
611
+
612
+ # Ctrl+R - reset view
613
+ elif ctrl and event.key == pygame.K_r:
555
614
  self._center_view()
556
615
  self.zoom = 2.0
557
616
  return True
617
+
618
+ # Ctrl+A - toggle angle hints
619
+ elif ctrl and event.key == pygame.K_a:
620
+ self.show_angle_hints = not self.show_angle_hints
621
+ return True
558
622
 
559
623
  if event.type == pygame.KEYUP:
560
624
  if event.key in (pygame.K_LSHIFT, pygame.K_RSHIFT):
@@ -744,20 +808,21 @@ class CollisionPainter:
744
808
  ("Right-click / Enter", "Complete polygon"),
745
809
  ("Escape", "Cancel current polygon"),
746
810
  ("Shift (edge mode)", "Constrain to axis"),
747
- ("E", "Toggle edge draw mode"),
811
+ ("Ctrl+E", "Toggle edge draw mode"),
748
812
  ]),
749
813
  ("SELECTION", [
750
814
  ("Left-click polygon", "Select polygon"),
751
815
  ("Left-click vertex", "Select & drag vertex"),
752
816
  ("Delete / Backspace", "Remove selected polygon"),
753
- ("O", "Toggle one-way collision"),
817
+ ("Ctrl+O", "Toggle one-way collision"),
754
818
  ]),
755
819
  ("VIEW", [
756
820
  ("Mouse wheel", "Zoom in/out"),
757
821
  ("Middle mouse / Space+LMB", "Pan view"),
758
- ("G", "Toggle grid"),
759
- ("S", "Toggle snap to grid"),
760
- ("R", "Reset view"),
822
+ ("Ctrl+G", "Toggle grid"),
823
+ ("Ctrl+Shift+S", "Toggle snap to grid"),
824
+ ("Ctrl+R", "Reset view"),
825
+ ("Ctrl+A", "Toggle angle hints"),
761
826
  ]),
762
827
  ("MISC", [
763
828
  ("Info button (top-right)", "Toggle this help panel"),
@@ -905,6 +970,24 @@ class CollisionPainter:
905
970
  radius = VERTEX_HOVER_RADIUS if is_hovered_vertex else VERTEX_RADIUS
906
971
  pygame.draw.circle(screen, color, (px, py), radius)
907
972
  pygame.draw.circle(screen, (0, 0, 0), (px, py), radius, 1)
973
+
974
+ # One-way badge on selected polygon
975
+ if selected and one_way and len(polygon) >= 3:
976
+ cx = int(sum(p[0] for p in screen_points) / len(screen_points))
977
+ cy = int(sum(p[1] for p in screen_points) / len(screen_points))
978
+ badge = self._font_sm.render("ONE-WAY", True, (255, 255, 255))
979
+ bx = cx - badge.get_width() // 2
980
+ by = cy - badge.get_height() // 2
981
+ bg = pygame.Surface((badge.get_width() + 8, badge.get_height() + 4), pygame.SRCALPHA)
982
+ bg.fill((_COLORS["one_way"][0], _COLORS["one_way"][1], _COLORS["one_way"][2], 200))
983
+ screen.blit(bg, (bx - 4, by - 2))
984
+ screen.blit(badge, (bx, by))
985
+
986
+ # Angle hints
987
+ if self.show_angle_hints:
988
+ for i, (px, py) in enumerate(screen_points):
989
+ angle = self._get_interior_angle(polygon, i)
990
+ self._draw_angle_hint(screen, (px, py), angle)
908
991
 
909
992
  def _draw_current_polygon(self, screen: pygame.Surface) -> None:
910
993
  """Draw the polygon currently being drawn"""
@@ -934,6 +1017,25 @@ class CollisionPainter:
934
1017
  radius = VERTEX_HOVER_RADIUS if is_first else VERTEX_RADIUS
935
1018
  pygame.draw.circle(screen, color, (px, py), radius)
936
1019
  pygame.draw.circle(screen, (0, 0, 0), (px, py), radius, 1)
1020
+
1021
+ # Angle hints for vertices with both neighbors
1022
+ if self.show_angle_hints and len(self.current_polygon) >= 3:
1023
+ for i, (px, py) in enumerate(screen_points):
1024
+ if i == 0 or i == len(screen_points) - 1:
1025
+ continue
1026
+ poly = self.current_polygon
1027
+ prev = poly[i - 1]
1028
+ curr = poly[i]
1029
+ nxt = poly[i + 1]
1030
+ v1 = (prev[0] - curr[0], prev[1] - curr[1])
1031
+ v2 = (nxt[0] - curr[0], nxt[1] - curr[1])
1032
+ dot = v1[0] * v2[0] + v1[1] * v2[1]
1033
+ mag1 = math.hypot(v1[0], v1[1])
1034
+ mag2 = math.hypot(v2[0], v2[1])
1035
+ if mag1 > 0.001 and mag2 > 0.001:
1036
+ cos_a = max(-1.0, min(1.0, dot / (mag1 * mag2)))
1037
+ angle = math.degrees(math.acos(cos_a))
1038
+ self._draw_angle_hint(screen, (px, py), angle)
937
1039
 
938
1040
  def _draw_status(self, screen: pygame.Surface) -> None:
939
1041
  """Draw status text"""
@@ -949,12 +1051,13 @@ class CollisionPainter:
949
1051
  one_way = self.polygon_one_way[self.selected_polygon_idx]
950
1052
  one_way_str = " (ONE-WAY)" if one_way else ""
951
1053
  lines.append(f"Selected polygon {self.selected_polygon_idx}{one_way_str}")
952
- lines.append("Delete to remove, O to toggle one-way")
1054
+ lines.append("Delete to remove, Ctrl+O to toggle one-way")
953
1055
  else:
954
1056
  lines.append("Click to add vertices, right-click to complete")
955
1057
 
956
- lines.append(f"Zoom: {self.zoom:.1f}x | Grid: {'ON' if self.show_grid else 'OFF'} (G) | Snap: {'ON' if self.snap_to_grid else 'OFF'} (S)")
957
- lines.append(f"Polygons: {len(self.polygons)} | R: reset view")
1058
+ snap_str = f"Snap: {'ON' if self.snap_to_grid else 'OFF'} (Ctrl+Shift+S)"
1059
+ lines.append(f"Zoom: {self.zoom:.1f}x | Grid: {'ON' if self.show_grid else 'OFF'} (Ctrl+G) | {snap_str}")
1060
+ lines.append(f"Polygons: {len(self.polygons)} | Ctrl+R: reset | Ctrl+A: angle hints {'ON' if self.show_angle_hints else 'OFF'}")
958
1061
 
959
1062
  y = self.rect.y + 5
960
1063
  for line in lines: