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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (202) hide show
  1. {tilemap_editor-3.3.3/src/tilemap_editor.egg-info → tilemap_editor-3.3.5}/PKG-INFO +1 -1
  2. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/pyproject.toml +1 -1
  3. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/sprite_animation/editor.py +51 -6
  4. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/sprite_animation/frame_picker.py +32 -8
  5. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/sprite_editor/editor.py +24 -5
  6. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/tileset_collision/collision_painter.py +137 -34
  7. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/tileset_collision/editor.py +192 -2
  8. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5/src/tilemap_editor.egg-info}/PKG-INFO +1 -1
  9. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor.egg-info/SOURCES.txt +1 -0
  10. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/filemanager.py +2 -4
  11. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/spritesheet_grid.py +190 -30
  12. tilemap_editor-3.3.5/src/widgets/ui/checkbox.py +99 -0
  13. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/LICENSE +0 -0
  14. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/MANIFEST.in +0 -0
  15. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/README.md +0 -0
  16. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/fonts/jetbrain-fonts/JetBrainsMono-Bold.ttf +0 -0
  17. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/fonts/jetbrain-fonts/JetBrainsMono-BoldItalic.ttf +0 -0
  18. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/fonts/jetbrain-fonts/JetBrainsMono-Italic.ttf +0 -0
  19. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/fonts/jetbrain-fonts/JetBrainsMono-Regular.ttf +0 -0
  20. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/fonts/noto/NotoSans-Bold.ttf +0 -0
  21. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/fonts/noto/NotoSans-BoldItalic.ttf +0 -0
  22. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/fonts/noto/NotoSans-Italic.ttf +0 -0
  23. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/fonts/noto/NotoSans-Regular.ttf +0 -0
  24. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/arrow-down.svg +0 -0
  25. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/check.svg +0 -0
  26. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/checked.svg +0 -0
  27. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/close.svg +0 -0
  28. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/duplicate.svg +0 -0
  29. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/error.svg +0 -0
  30. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/file.svg +0 -0
  31. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/filedead.svg +0 -0
  32. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/fit.svg +0 -0
  33. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/folder.svg +0 -0
  34. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/grid.svg +0 -0
  35. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/image.svg +0 -0
  36. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/info.svg +0 -0
  37. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/load.svg +0 -0
  38. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/loop.svg +0 -0
  39. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/pan.svg +0 -0
  40. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/pause.svg +0 -0
  41. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/pencil.svg +0 -0
  42. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/play.svg +0 -0
  43. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/plus.svg +0 -0
  44. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/radio.svg +0 -0
  45. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/reset.svg +0 -0
  46. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/save.svg +0 -0
  47. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/stop.svg +0 -0
  48. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/tilemap.svg +0 -0
  49. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/tileset.svg +0 -0
  50. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/unchecked.svg +0 -0
  51. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/warning.svg +0 -0
  52. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/zoomin.svg +0 -0
  53. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/zoomout.svg +0 -0
  54. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/setup.cfg +0 -0
  55. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/configs/themes.py +0 -0
  56. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/constants.py +0 -0
  57. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/editor.py +0 -0
  58. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/event_map.py +0 -0
  59. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/layers.py +0 -0
  60. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/main.py +0 -0
  61. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/node_manager.py +0 -0
  62. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/nodes.py +0 -0
  63. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/__init__.py +0 -0
  64. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/character_collision/__init__.py +0 -0
  65. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/character_collision/editor.py +0 -0
  66. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/character_collision/models.py +0 -0
  67. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/character_collision/protocols.py +0 -0
  68. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/character_collision/shape_editor.py +0 -0
  69. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/character_collision/standalone.py +0 -0
  70. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/object_tileset_collision/__init__.py +0 -0
  71. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/object_tileset_collision/editor.py +0 -0
  72. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/object_tileset_collision/models.py +0 -0
  73. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/object_tileset_collision/protocols.py +0 -0
  74. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/object_tileset_collision/standalone.py +0 -0
  75. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/sprite_animation/__init__.py +0 -0
  76. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/sprite_animation/__main__.py +0 -0
  77. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/sprite_animation/clipboard_util.py +0 -0
  78. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/sprite_animation/models.py +0 -0
  79. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/sprite_animation/preview.py +0 -0
  80. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/sprite_animation/protocols.py +0 -0
  81. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/sprite_animation/runtime_load.py +0 -0
  82. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/sprite_animation/standalone.py +0 -0
  83. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/sprite_animation/timeline.py +0 -0
  84. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/sprite_animation/validation.py +0 -0
  85. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/sprite_editor/__init__.py +0 -0
  86. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/sprite_editor/standalone.py +0 -0
  87. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/tileset_collision/__init__.py +0 -0
  88. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/tileset_collision/models.py +0 -0
  89. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/tileset_collision/protocols.py +0 -0
  90. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/tileset_collision/standalone.py +0 -0
  91. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/standalone_automap.py +0 -0
  92. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/standalone_error_console.py +0 -0
  93. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/standalone_filemanager.py +0 -0
  94. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/standalone_image_viewer.py +0 -0
  95. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap.py +0 -0
  96. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/__init__.py +0 -0
  97. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/__main__.py +0 -0
  98. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/__init__.py +0 -0
  99. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/fonts/jetbrain-fonts/JetBrainsMono-Bold.ttf +0 -0
  100. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/fonts/jetbrain-fonts/JetBrainsMono-BoldItalic.ttf +0 -0
  101. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/fonts/jetbrain-fonts/JetBrainsMono-Italic.ttf +0 -0
  102. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/fonts/jetbrain-fonts/JetBrainsMono-Regular.ttf +0 -0
  103. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/fonts/noto/NotoSans-Bold.ttf +0 -0
  104. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/fonts/noto/NotoSans-BoldItalic.ttf +0 -0
  105. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/fonts/noto/NotoSans-Italic.ttf +0 -0
  106. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/fonts/noto/NotoSans-Regular.ttf +0 -0
  107. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/ToolMove.svg +0 -0
  108. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/arrow-down.svg +0 -0
  109. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/check.svg +0 -0
  110. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/checked.svg +0 -0
  111. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/close.svg +0 -0
  112. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/duplicate.svg +0 -0
  113. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/error.svg +0 -0
  114. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/file.svg +0 -0
  115. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/filedead.svg +0 -0
  116. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/fit.svg +0 -0
  117. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/folder.svg +0 -0
  118. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/grid.svg +0 -0
  119. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/image.svg +0 -0
  120. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/info.svg +0 -0
  121. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/load.svg +0 -0
  122. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/loop.svg +0 -0
  123. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/pan.svg +0 -0
  124. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/pause.svg +0 -0
  125. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/pencil.svg +0 -0
  126. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/play.svg +0 -0
  127. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/plus.svg +0 -0
  128. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/radio.svg +0 -0
  129. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/reset.svg +0 -0
  130. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/save.svg +0 -0
  131. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/stop.svg +0 -0
  132. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/tilemap.svg +0 -0
  133. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/tileset.svg +0 -0
  134. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/unchecked.svg +0 -0
  135. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/warning.svg +0 -0
  136. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/zoomin.svg +0 -0
  137. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/zoomout.svg +0 -0
  138. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/cli.py +0 -0
  139. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/main.py +0 -0
  140. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/settings.py +0 -0
  141. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor.egg-info/dependency_links.txt +0 -0
  142. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor.egg-info/entry_points.txt +0 -0
  143. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor.egg-info/requires.txt +0 -0
  144. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor.egg-info/top_level.txt +0 -0
  145. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/ttypes/__init__.py +0 -0
  146. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/ttypes/tilemap.py +0 -0
  147. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/utils/__init__.py +0 -0
  148. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/utils/editor_preference.py +0 -0
  149. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/utils/error_handler.py +0 -0
  150. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/utils/font_manager.py +0 -0
  151. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/utils/history.py +0 -0
  152. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/utils/icon_manager.py +0 -0
  153. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/utils/icons_cache.py +0 -0
  154. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/utils/log_capture.py +0 -0
  155. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/utils/project_paths.py +0 -0
  156. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/utils/serialization.py +0 -0
  157. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/utils/standalone.py +0 -0
  158. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/utils/validation.py +0 -0
  159. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/__init__.py +0 -0
  160. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/automap_models.py +0 -0
  161. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/autotile_template.py +0 -0
  162. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/autotiler.py +0 -0
  163. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/input.py +0 -0
  164. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/layer_selector.py +0 -0
  165. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/map_properties.py +0 -0
  166. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/mapsetup.py +0 -0
  167. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/particle_presets.py +0 -0
  168. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/particle_system.py +0 -0
  169. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/regex_automap_designer.py +0 -0
  170. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/tile_grid.py +0 -0
  171. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/tile_selector.py +0 -0
  172. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/ui/collision_layer_mask.py +0 -0
  173. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/ui/collision_layer_sidebar.py +0 -0
  174. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/ui/confirm_dialog.py +0 -0
  175. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/ui/draw_utils.py +0 -0
  176. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/ui/fileinput.py +0 -0
  177. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/ui/layer_type_dialog.py +0 -0
  178. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/ui/menubar.py +0 -0
  179. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/ui/mode_indicator.py +0 -0
  180. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/ui/node_editor.py +0 -0
  181. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/ui/node_selector.py +0 -0
  182. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/ui/notification.py +0 -0
  183. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/ui/particle_config_dialog.py +0 -0
  184. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/ui/property_editor.py +0 -0
  185. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/ui/region_selector.py +0 -0
  186. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/ui/status_bar.py +0 -0
  187. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/ui/theme.py +0 -0
  188. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/ui/tileset_type_dialog.py +0 -0
  189. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/ui/toolbar.py +0 -0
  190. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/ui/tooltip.py +0 -0
  191. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/tests/test_animation_library_grid_offset.py +0 -0
  192. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/tests/test_collision_layer_mask.py +0 -0
  193. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/tests/test_collision_layer_sidebar.py +0 -0
  194. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/tests/test_editor_pan_mode.py +0 -0
  195. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/tests/test_project_paths.py +0 -0
  196. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/tests/test_render_scale.py +0 -0
  197. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/tests/test_sprite_animation_editor_grid.py +0 -0
  198. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/tests/test_tile_grid_selection.py +0 -0
  199. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/tests/test_tile_selector_pick.py +0 -0
  200. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/tests/test_tilemap_save.py +0 -0
  201. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/tests/test_timeline_invalidate_cache.py +0 -0
  202. {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/tests/test_toolbar_tools.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tilemap-editor
3
- Version: 3.3.3
3
+ Version: 3.3.5
4
4
  Summary: Pygame tilemap editor with SVG icons, professional UI, and sprite animation tools
5
5
  Author: tilemap editor contributors
6
6
  License: Proprietary
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "tilemap-editor"
7
- version = "3.3.3"
7
+ version = "3.3.5"
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"
@@ -310,11 +310,10 @@ class SpriteAnimationEditor:
310
310
  )
311
311
  elif event.type == pygame.KEYDOWN:
312
312
  if event.key == pygame.K_ESCAPE:
313
- if self._info_tooltip_pinned:
314
- self._info_tooltip_pinned = False
313
+ if self._file_manager is not None:
314
+ self.handle_event(event)
315
315
  continue
316
- # Don't close editor if file manager is open - let file manager handle ESC
317
- if self._file_manager is None:
316
+ if not self.handle_event(event):
318
317
  running = False
319
318
  continue
320
319
  self.handle_event(event)
@@ -383,6 +382,11 @@ class SpriteAnimationEditor:
383
382
  self._info_tooltip_pinned = False
384
383
  return True
385
384
 
385
+ # Close dropdown on Escape
386
+ if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE and self._dropdown_open:
387
+ self._dropdown_open = False
388
+ return True
389
+
386
390
  if self._meta_panel_open and self._route_meta_panel_event(event):
387
391
  return True
388
392
 
@@ -427,6 +431,14 @@ class SpriteAnimationEditor:
427
431
  self._start_rename()
428
432
  return True
429
433
 
434
+ # [ / ]: Cycle animations
435
+ elif event.key == pygame.K_LEFTBRACKET:
436
+ self._cycle_animation(-1)
437
+ return True
438
+ elif event.key == pygame.K_RIGHTBRACKET:
439
+ self._cycle_animation(1)
440
+ return True
441
+
430
442
  # Close dropdown on outside click
431
443
  if event.type == pygame.MOUSEBUTTONDOWN and self._dropdown_open:
432
444
  mouse = pygame.mouse.get_pos()
@@ -1240,13 +1252,32 @@ class SpriteAnimationEditor:
1240
1252
  screen.blit(text_surf, (tooltip_rect.x + padding, y))
1241
1253
  y += line_height
1242
1254
 
1255
+ def _compute_dropdown_width(self, names: List[str]) -> int:
1256
+ capped = self._active_anim_name or "(none)"
1257
+ if len(capped) > 16:
1258
+ capped = capped[:14] + ".."
1259
+ cap_w = self._font.size(f" {capped}")[0]
1260
+ max_name_w = 0
1261
+ max_badge_w = 0
1262
+ for name in names:
1263
+ w = self._font.size(name)[0]
1264
+ if w > max_name_w:
1265
+ max_name_w = w
1266
+ anim = self.library.get_animation(name)
1267
+ if anim:
1268
+ bw = self._font_sm.size(f"{anim.frame_count()}f")[0]
1269
+ if bw > max_badge_w:
1270
+ max_badge_w = bw
1271
+ dd_w = max(cap_w, max_name_w + max_badge_w + 24)
1272
+ return min(dd_w, 400)
1273
+
1243
1274
  def _draw_dropdown(self, screen: pygame.Surface) -> None:
1244
1275
  names = self.library.animation_names()
1245
1276
  if not names:
1246
1277
  return
1247
1278
 
1248
1279
  item_h = 26
1249
- dd_w = self._btn_anim_selector.w
1280
+ dd_w = self._compute_dropdown_width(names)
1250
1281
  dd_h = len(names) * item_h + 4
1251
1282
  dd_x = self._btn_anim_selector.x
1252
1283
  dd_y = self._btn_anim_selector.bottom + 2
@@ -1281,7 +1312,7 @@ class SpriteAnimationEditor:
1281
1312
  screen, _COLORS["dropdown_hover"], item_rect, border_radius=3
1282
1313
  )
1283
1314
 
1284
- display = name if len(name) < 18 else name[:16] + ".."
1315
+ display = name
1285
1316
  color = _COLORS["accent"] if is_active else _COLORS["text"]
1286
1317
  lbl = self._font.render(display, True, color)
1287
1318
  screen.blit(lbl, (item_rect.x + 8, item_rect.y + 4))
@@ -1458,6 +1489,20 @@ class SpriteAnimationEditor:
1458
1489
  self.preview.grid_offset_x = gx
1459
1490
  self.preview.grid_offset_y = gy
1460
1491
 
1492
+ def _cycle_animation(self, delta: int) -> None:
1493
+ """Switch to next (delta=1) or previous (delta=-1) animation."""
1494
+ names = self.library.animation_names()
1495
+ if not names:
1496
+ return
1497
+ if self._active_anim_name is None or self._active_anim_name not in names:
1498
+ idx = 0 if delta > 0 else len(names) - 1
1499
+ else:
1500
+ idx = (names.index(self._active_anim_name) + delta) % len(names)
1501
+ self._active_anim_name = names[idx]
1502
+ self._sync_active_animation()
1503
+ if self._dropdown_open:
1504
+ self._dropdown_hover_idx = idx
1505
+
1461
1506
  def _sync_active_animation(self) -> None:
1462
1507
  """Push current animation data to sub-widgets."""
1463
1508
  anim = self._get_active()
@@ -81,9 +81,13 @@ class FramePicker:
81
81
  self._filter_input_rect = Rect(0, 0, 0, 0)
82
82
  self._btn_unused_rect = Rect(0, 0, 0, 0)
83
83
 
84
- # Callback fired when user clicks a tile
84
+ # Callback fired when user clicks or drags over a tile
85
85
  self.on_frame_clicked: Optional[Callable[[int], None]] = None
86
86
 
87
+ # Drag selection state
88
+ self._selecting = False
89
+ self._last_drag_idx = -1
90
+
87
91
  # Fonts (created lazily so pygame.init() can happen anytime before draw)
88
92
  self._font: Optional[pygame.font.Font] = None
89
93
  self._font_sm: Optional[pygame.font.Font] = None
@@ -206,19 +210,39 @@ class FramePicker:
206
210
  self.offset_y = self._pan_start_offset[1] + dy
207
211
  return True
208
212
 
209
- # Left-click to select a frame
213
+ # Left-click to select a frame (starts drag for multi-select)
210
214
  if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
211
215
  if (
212
216
  self.rect.collidepoint(mouse)
213
217
  and mouse[1] >= self.rect.y + TOP_BAR_TOTAL
214
218
  ):
215
219
  idx = self._index_at(mouse)
216
- if (
217
- idx >= 0
218
- and self._tile_matches_filter(idx)
219
- and self.on_frame_clicked
220
- ):
221
- self.on_frame_clicked(idx)
220
+ if idx >= 0 and self._tile_matches_filter(idx):
221
+ self._selecting = True
222
+ self._last_drag_idx = idx
223
+ if self.on_frame_clicked:
224
+ self.on_frame_clicked(idx)
225
+ return True
226
+
227
+ # Drag to select/deselect multiple frames (toggles like single-click)
228
+ if event.type == pygame.MOUSEMOTION and self._selecting:
229
+ if pygame.mouse.get_pressed()[0]:
230
+ if self.rect.collidepoint(mouse) and mouse[1] >= self.rect.y + TOP_BAR_TOTAL:
231
+ idx = self._index_at(mouse)
232
+ if idx != self._last_drag_idx and idx >= 0 and self._tile_matches_filter(idx):
233
+ self._last_drag_idx = idx
234
+ if self.on_frame_clicked:
235
+ self.on_frame_clicked(idx)
236
+ return True
237
+ else:
238
+ self._selecting = False
239
+ self._last_drag_idx = -1
240
+
241
+ # End drag selection
242
+ if event.type == pygame.MOUSEBUTTONUP and event.button == 1:
243
+ if self._selecting:
244
+ self._selecting = False
245
+ self._last_drag_idx = -1
222
246
  return True
223
247
 
224
248
  # Scroll to pan / Ctrl+scroll to zoom
@@ -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: