tilemap-editor 3.2.6__tar.gz → 3.2.8__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 (198) hide show
  1. {tilemap_editor-3.2.6/src/tilemap_editor.egg-info → tilemap_editor-3.2.8}/PKG-INFO +1 -1
  2. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/pyproject.toml +1 -1
  3. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/editor.py +28 -5
  4. tilemap_editor-3.2.8/src/node_manager.py +147 -0
  5. tilemap_editor-3.2.8/src/nodes.py +53 -0
  6. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/plugins/character_collision/shape_editor.py +3 -3
  7. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/plugins/sprite_animation/editor.py +7 -0
  8. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/plugins/sprite_animation/preview.py +9 -2
  9. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/tilemap.py +8 -0
  10. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/tilemap_editor/settings.py +1 -0
  11. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8/src/tilemap_editor.egg-info}/PKG-INFO +1 -1
  12. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/tilemap_editor.egg-info/SOURCES.txt +4 -0
  13. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/widgets/layer_selector.py +63 -1
  14. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/widgets/tile_grid.py +249 -11
  15. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/widgets/tile_selector.py +43 -1
  16. tilemap_editor-3.2.8/src/widgets/ui/node_editor.py +337 -0
  17. tilemap_editor-3.2.8/src/widgets/ui/node_selector.py +602 -0
  18. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/widgets/ui/toolbar.py +12 -1
  19. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/LICENSE +0 -0
  20. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/MANIFEST.in +0 -0
  21. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/README.md +0 -0
  22. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/assets/fonts/jetbrain-fonts/JetBrainsMono-Bold.ttf +0 -0
  23. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/assets/fonts/jetbrain-fonts/JetBrainsMono-BoldItalic.ttf +0 -0
  24. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/assets/fonts/jetbrain-fonts/JetBrainsMono-Italic.ttf +0 -0
  25. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/assets/fonts/jetbrain-fonts/JetBrainsMono-Regular.ttf +0 -0
  26. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/assets/fonts/noto/NotoSans-Bold.ttf +0 -0
  27. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/assets/fonts/noto/NotoSans-BoldItalic.ttf +0 -0
  28. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/assets/fonts/noto/NotoSans-Italic.ttf +0 -0
  29. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/assets/fonts/noto/NotoSans-Regular.ttf +0 -0
  30. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/assets/icons/arrow-down.svg +0 -0
  31. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/assets/icons/check.svg +0 -0
  32. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/assets/icons/checked.svg +0 -0
  33. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/assets/icons/close.svg +0 -0
  34. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/assets/icons/duplicate.svg +0 -0
  35. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/assets/icons/error.svg +0 -0
  36. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/assets/icons/file.svg +0 -0
  37. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/assets/icons/filedead.svg +0 -0
  38. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/assets/icons/fit.svg +0 -0
  39. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/assets/icons/folder.svg +0 -0
  40. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/assets/icons/grid.svg +0 -0
  41. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/assets/icons/image.svg +0 -0
  42. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/assets/icons/info.svg +0 -0
  43. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/assets/icons/load.svg +0 -0
  44. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/assets/icons/loop.svg +0 -0
  45. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/assets/icons/pan.svg +0 -0
  46. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/assets/icons/pause.svg +0 -0
  47. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/assets/icons/pencil.svg +0 -0
  48. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/assets/icons/play.svg +0 -0
  49. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/assets/icons/plus.svg +0 -0
  50. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/assets/icons/radio.svg +0 -0
  51. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/assets/icons/reset.svg +0 -0
  52. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/assets/icons/save.svg +0 -0
  53. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/assets/icons/stop.svg +0 -0
  54. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/assets/icons/tilemap.svg +0 -0
  55. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/assets/icons/tileset.svg +0 -0
  56. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/assets/icons/unchecked.svg +0 -0
  57. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/assets/icons/warning.svg +0 -0
  58. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/assets/icons/zoomin.svg +0 -0
  59. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/assets/icons/zoomout.svg +0 -0
  60. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/setup.cfg +0 -0
  61. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/configs/themes.py +0 -0
  62. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/constants.py +0 -0
  63. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/event_map.py +0 -0
  64. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/layers.py +0 -0
  65. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/main.py +0 -0
  66. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/plugins/__init__.py +0 -0
  67. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/plugins/character_collision/__init__.py +0 -0
  68. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/plugins/character_collision/editor.py +0 -0
  69. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/plugins/character_collision/models.py +0 -0
  70. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/plugins/character_collision/protocols.py +0 -0
  71. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/plugins/character_collision/standalone.py +0 -0
  72. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/plugins/object_tileset_collision/__init__.py +0 -0
  73. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/plugins/object_tileset_collision/editor.py +0 -0
  74. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/plugins/object_tileset_collision/models.py +0 -0
  75. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/plugins/object_tileset_collision/protocols.py +0 -0
  76. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/plugins/object_tileset_collision/standalone.py +0 -0
  77. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/plugins/sprite_animation/__init__.py +0 -0
  78. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/plugins/sprite_animation/__main__.py +0 -0
  79. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/plugins/sprite_animation/clipboard_util.py +0 -0
  80. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/plugins/sprite_animation/frame_picker.py +0 -0
  81. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/plugins/sprite_animation/models.py +0 -0
  82. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/plugins/sprite_animation/protocols.py +0 -0
  83. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/plugins/sprite_animation/runtime_load.py +0 -0
  84. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/plugins/sprite_animation/standalone.py +0 -0
  85. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/plugins/sprite_animation/timeline.py +0 -0
  86. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/plugins/sprite_animation/validation.py +0 -0
  87. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/plugins/sprite_editor/__init__.py +0 -0
  88. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/plugins/sprite_editor/editor.py +0 -0
  89. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/plugins/sprite_editor/standalone.py +0 -0
  90. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/plugins/tileset_collision/__init__.py +0 -0
  91. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/plugins/tileset_collision/collision_painter.py +0 -0
  92. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/plugins/tileset_collision/editor.py +0 -0
  93. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/plugins/tileset_collision/models.py +0 -0
  94. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/plugins/tileset_collision/protocols.py +0 -0
  95. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/plugins/tileset_collision/standalone.py +0 -0
  96. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/standalone_automap.py +0 -0
  97. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/standalone_error_console.py +0 -0
  98. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/standalone_filemanager.py +0 -0
  99. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/standalone_image_viewer.py +0 -0
  100. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/tilemap_editor/__init__.py +0 -0
  101. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/tilemap_editor/__main__.py +0 -0
  102. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/tilemap_editor/assets/__init__.py +0 -0
  103. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/tilemap_editor/assets/fonts/jetbrain-fonts/JetBrainsMono-Bold.ttf +0 -0
  104. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/tilemap_editor/assets/fonts/jetbrain-fonts/JetBrainsMono-BoldItalic.ttf +0 -0
  105. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/tilemap_editor/assets/fonts/jetbrain-fonts/JetBrainsMono-Italic.ttf +0 -0
  106. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/tilemap_editor/assets/fonts/jetbrain-fonts/JetBrainsMono-Regular.ttf +0 -0
  107. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/tilemap_editor/assets/fonts/noto/NotoSans-Bold.ttf +0 -0
  108. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/tilemap_editor/assets/fonts/noto/NotoSans-BoldItalic.ttf +0 -0
  109. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/tilemap_editor/assets/fonts/noto/NotoSans-Italic.ttf +0 -0
  110. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/tilemap_editor/assets/fonts/noto/NotoSans-Regular.ttf +0 -0
  111. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/tilemap_editor/assets/icons/ToolMove.svg +0 -0
  112. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/tilemap_editor/assets/icons/arrow-down.svg +0 -0
  113. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/tilemap_editor/assets/icons/check.svg +0 -0
  114. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/tilemap_editor/assets/icons/checked.svg +0 -0
  115. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/tilemap_editor/assets/icons/close.svg +0 -0
  116. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/tilemap_editor/assets/icons/duplicate.svg +0 -0
  117. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/tilemap_editor/assets/icons/error.svg +0 -0
  118. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/tilemap_editor/assets/icons/file.svg +0 -0
  119. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/tilemap_editor/assets/icons/filedead.svg +0 -0
  120. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/tilemap_editor/assets/icons/fit.svg +0 -0
  121. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/tilemap_editor/assets/icons/folder.svg +0 -0
  122. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/tilemap_editor/assets/icons/grid.svg +0 -0
  123. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/tilemap_editor/assets/icons/image.svg +0 -0
  124. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/tilemap_editor/assets/icons/info.svg +0 -0
  125. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/tilemap_editor/assets/icons/load.svg +0 -0
  126. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/tilemap_editor/assets/icons/loop.svg +0 -0
  127. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/tilemap_editor/assets/icons/pan.svg +0 -0
  128. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/tilemap_editor/assets/icons/pause.svg +0 -0
  129. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/tilemap_editor/assets/icons/pencil.svg +0 -0
  130. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/tilemap_editor/assets/icons/play.svg +0 -0
  131. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/tilemap_editor/assets/icons/plus.svg +0 -0
  132. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/tilemap_editor/assets/icons/radio.svg +0 -0
  133. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/tilemap_editor/assets/icons/reset.svg +0 -0
  134. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/tilemap_editor/assets/icons/save.svg +0 -0
  135. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/tilemap_editor/assets/icons/stop.svg +0 -0
  136. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/tilemap_editor/assets/icons/tilemap.svg +0 -0
  137. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/tilemap_editor/assets/icons/tileset.svg +0 -0
  138. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/tilemap_editor/assets/icons/unchecked.svg +0 -0
  139. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/tilemap_editor/assets/icons/warning.svg +0 -0
  140. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/tilemap_editor/assets/icons/zoomin.svg +0 -0
  141. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/tilemap_editor/assets/icons/zoomout.svg +0 -0
  142. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/tilemap_editor/cli.py +0 -0
  143. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/tilemap_editor/main.py +0 -0
  144. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/tilemap_editor.egg-info/dependency_links.txt +0 -0
  145. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/tilemap_editor.egg-info/entry_points.txt +0 -0
  146. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/tilemap_editor.egg-info/requires.txt +0 -0
  147. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/tilemap_editor.egg-info/top_level.txt +0 -0
  148. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/ttypes/__init__.py +0 -0
  149. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/ttypes/tilemap.py +0 -0
  150. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/utils/__init__.py +0 -0
  151. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/utils/editor_preference.py +0 -0
  152. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/utils/error_handler.py +0 -0
  153. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/utils/font_manager.py +0 -0
  154. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/utils/history.py +0 -0
  155. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/utils/icon_manager.py +0 -0
  156. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/utils/icons_cache.py +0 -0
  157. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/utils/log_capture.py +0 -0
  158. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/utils/project_paths.py +0 -0
  159. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/utils/serialization.py +0 -0
  160. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/utils/standalone.py +0 -0
  161. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/utils/validation.py +0 -0
  162. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/widgets/__init__.py +0 -0
  163. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/widgets/automap_models.py +0 -0
  164. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/widgets/autotile_template.py +0 -0
  165. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/widgets/autotiler.py +0 -0
  166. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/widgets/filemanager.py +0 -0
  167. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/widgets/input.py +0 -0
  168. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/widgets/map_properties.py +0 -0
  169. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/widgets/mapsetup.py +0 -0
  170. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/widgets/regex_automap_designer.py +0 -0
  171. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/widgets/spritesheet_grid.py +0 -0
  172. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/widgets/ui/collision_layer_mask.py +0 -0
  173. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/widgets/ui/collision_layer_sidebar.py +0 -0
  174. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/widgets/ui/confirm_dialog.py +0 -0
  175. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/widgets/ui/draw_utils.py +0 -0
  176. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/widgets/ui/fileinput.py +0 -0
  177. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/widgets/ui/layer_type_dialog.py +0 -0
  178. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/widgets/ui/menubar.py +0 -0
  179. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/widgets/ui/mode_indicator.py +0 -0
  180. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/widgets/ui/notification.py +0 -0
  181. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/widgets/ui/property_editor.py +0 -0
  182. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/widgets/ui/region_selector.py +0 -0
  183. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/widgets/ui/status_bar.py +0 -0
  184. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/widgets/ui/theme.py +0 -0
  185. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/widgets/ui/tileset_type_dialog.py +0 -0
  186. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/src/widgets/ui/tooltip.py +0 -0
  187. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/tests/test_animation_library_grid_offset.py +0 -0
  188. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/tests/test_collision_layer_mask.py +0 -0
  189. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/tests/test_collision_layer_sidebar.py +0 -0
  190. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/tests/test_editor_pan_mode.py +0 -0
  191. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/tests/test_project_paths.py +0 -0
  192. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/tests/test_render_scale.py +0 -0
  193. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/tests/test_sprite_animation_editor_grid.py +0 -0
  194. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/tests/test_tile_grid_selection.py +0 -0
  195. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/tests/test_tile_selector_pick.py +0 -0
  196. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/tests/test_tilemap_save.py +0 -0
  197. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/tests/test_timeline_invalidate_cache.py +0 -0
  198. {tilemap_editor-3.2.6 → tilemap_editor-3.2.8}/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.2.6
3
+ Version: 3.2.8
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.2.6"
7
+ version = "3.2.8"
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"
@@ -31,6 +31,8 @@ from widgets.ui.confirm_dialog import ConfirmDialog
31
31
  from widgets.ui.layer_type_dialog import LayerTypeDialog
32
32
  from widgets.ui.menubar import MenuBar
33
33
  from widgets.ui.toolbar import Toolbar
34
+ from widgets.ui.node_selector import NodeSelector
35
+ from widgets.ui.node_editor import NodeEditor
34
36
  from widgets.ui.notification import NotificationManager
35
37
  from widgets.ui.property_editor import PropertyEditor
36
38
  from widgets.ui.tooltip import TooltipManager
@@ -38,6 +40,7 @@ from widgets.ui.theme import get_theme_manager, set_theme, THEMES
38
40
  from utils.log_capture import setup_console_log
39
41
  from utils.standalone import launch_standalone
40
42
  from utils import error_handler, error_context
43
+ from node_manager import NodeManager
41
44
 
42
45
  if TYPE_CHECKING:
43
46
  from plugins.sprite_animation import SpriteAnimationEditor
@@ -153,6 +156,7 @@ class Editor:
153
156
  self.autotile_mode = False
154
157
  self.eraser_mode = False
155
158
  self.select_mode = False
159
+ self.node_editing_mode = False
156
160
  self._prev_tool = None
157
161
 
158
162
  if isinstance(size, tuple) and len(size) == 2:
@@ -240,6 +244,9 @@ class Editor:
240
244
  self.map_setup_widget = MapSetup(self, Rect(center_x, center_y, 400, 400))
241
245
  self.map_setup_widget.visible = False
242
246
  self.map_properties_dialog = MapPropertiesDialog(self, Rect(center_x, center_y, 400, 260))
247
+ self.node_manager = NodeManager(self)
248
+ self.node_selector = NodeSelector(self, 0, 65, 260, 240)
249
+ self.node_editor = NodeEditor(self, 0, 310, 260, 190)
243
250
 
244
251
  def open_file_manager(
245
252
  self,
@@ -1126,7 +1133,7 @@ class Editor:
1126
1133
  elif event.key == pygame.K_o and (ctrl_held or meta_held):
1127
1134
  self.perform_load()
1128
1135
  continue
1129
- elif event.key == pygame.K_SPACE:
1136
+ elif event.key == pygame.K_SPACE and (ctrl_held or meta_held):
1130
1137
  if self.pan_mode:
1131
1138
  # Restoring: turn off pan, re-enable previous tool
1132
1139
  self.pan_mode = False
@@ -1134,17 +1141,22 @@ class Editor:
1134
1141
  self.select_mode = True
1135
1142
  elif getattr(self, "_prev_tool", None) == "eraser":
1136
1143
  self.eraser_mode = True
1144
+ elif getattr(self, "_prev_tool", None) == "nodes":
1145
+ self.node_editing_mode = True
1137
1146
  else:
1138
1147
  # Entering pan: save current tool, turn off others
1139
1148
  if self.select_mode:
1140
1149
  self._prev_tool = "select"
1141
1150
  elif self.eraser_mode:
1142
1151
  self._prev_tool = "eraser"
1152
+ elif self.node_editing_mode:
1153
+ self._prev_tool = "nodes"
1143
1154
  else:
1144
1155
  self._prev_tool = None
1145
1156
  self.pan_mode = True
1146
1157
  self.select_mode = False
1147
1158
  self.eraser_mode = False
1159
+ self.node_editing_mode = False
1148
1160
  continue
1149
1161
  elif event.mod & pygame.KMOD_CTRL and event.key == pygame.K_g:
1150
1162
  self.toggle_grid()
@@ -1156,15 +1168,22 @@ class Editor:
1156
1168
  self.toggle_animation_panel()
1157
1169
  continue
1158
1170
  elif pygame.K_1 <= event.key <= pygame.K_9:
1159
- idx = event.key - pygame.K_1
1160
- if idx < self.tilemap.layer_manager.get_layer_count():
1161
- self.tilemap.layer_manager.set_active_layer(idx)
1162
- continue
1171
+ if not (self.node_editor and self.node_editor.visible and self.node_editor.editing_field):
1172
+ idx = event.key - pygame.K_1
1173
+ if idx < self.tilemap.layer_manager.get_layer_count():
1174
+ self.tilemap.layer_manager.set_active_layer(idx)
1175
+ continue
1163
1176
 
1164
1177
  # Priority 5: Toolbar
1165
1178
  if self.toolbar and self.toolbar.handle_event(event):
1166
1179
  continue
1167
1180
 
1181
+ # Priority 5.5: Node selector & editor (when node mode active)
1182
+ if self.node_selector and self.node_selector.handle_event(event):
1183
+ continue
1184
+ if self.node_editor and self.node_editor.handle_event(event):
1185
+ continue
1186
+
1168
1187
  # Priority 6: Dockable animation panel
1169
1188
  if self.left_panel_visible and self.animation_panel:
1170
1189
  if hasattr(self.animation_panel, "handle_event"):
@@ -1303,6 +1322,10 @@ class Editor:
1303
1322
 
1304
1323
  if self.toolbar:
1305
1324
  self.toolbar.draw(self.screen)
1325
+ if self.node_selector:
1326
+ self.node_selector.draw(self.screen)
1327
+ if self.node_editor:
1328
+ self.node_editor.draw(self.screen)
1306
1329
  self.menubar.draw(self.screen)
1307
1330
  self.tooltip.draw(self.screen)
1308
1331
 
@@ -0,0 +1,147 @@
1
+ import json
2
+ import uuid
3
+ from pathlib import Path
4
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple
5
+
6
+ from nodes import Node, NodeRect
7
+
8
+ if TYPE_CHECKING:
9
+ from editor import Editor
10
+
11
+
12
+ def create_node_id() -> str:
13
+ return str(uuid.uuid4())
14
+
15
+
16
+ SIDECAR_VERSION = 1
17
+
18
+
19
+ class NodeManager:
20
+ def __init__(self, editor: "Editor") -> None:
21
+ self.editor = editor
22
+ self.nodes: Dict[str, Node] = {}
23
+ self.active_node_id: Optional[str] = None
24
+ self.active_group_name: Optional[str] = None
25
+ self.groups: List[str] = []
26
+ self.default_node_type: str = "area"
27
+ self._nodes_dir: Optional[Path] = None
28
+
29
+ @property
30
+ def nodes_dir(self) -> Path:
31
+ if self._nodes_dir is None:
32
+ config = getattr(self.editor, "config", {})
33
+ rel = config.get("nodes_path", "nodes")
34
+ self._nodes_dir = self.editor.data_root / rel
35
+ return self._nodes_dir
36
+
37
+ def _sidecar_path_for(self, map_path: Path) -> Path:
38
+ return self.nodes_dir / f"{map_path.stem}.nodes.json"
39
+
40
+ def load(self, map_path: Path) -> None:
41
+ self.nodes.clear()
42
+ self.groups.clear()
43
+ self.active_node_id = None
44
+ self.active_group_name = None
45
+ sidecar = self._sidecar_path_for(map_path)
46
+ if not sidecar.is_file():
47
+ return
48
+ try:
49
+ raw = json.loads(sidecar.read_text(encoding="utf-8"))
50
+ if not isinstance(raw, dict):
51
+ return
52
+ self.groups = list(raw.get("groups", []))
53
+ for item in raw.get("nodes", []):
54
+ if isinstance(item, dict):
55
+ node = Node.from_dict(item)
56
+ self.nodes[node.node_id] = node
57
+ except (json.JSONDecodeError, KeyError, ValueError, TypeError):
58
+ import logging
59
+ logging.warning(f"Failed to load nodes from {sidecar}")
60
+
61
+ def save(self, map_path: Path) -> None:
62
+ sidecar = self._sidecar_path_for(map_path)
63
+ self.nodes_dir.mkdir(parents=True, exist_ok=True)
64
+ data: Dict[str, Any] = {
65
+ "version": SIDECAR_VERSION,
66
+ "groups": self.groups,
67
+ "nodes": [node.to_dict() for node in self.nodes.values()],
68
+ }
69
+ sidecar.write_text(json.dumps(data, indent=2), encoding="utf-8")
70
+
71
+ def add_node(self, node: Node) -> str:
72
+ self.nodes[node.node_id] = node
73
+ return node.node_id
74
+
75
+ def remove_node(self, node_id: str) -> None:
76
+ self.nodes.pop(node_id, None)
77
+ if self.active_node_id == node_id:
78
+ self.active_node_id = None
79
+
80
+ def get_node(self, node_id: str) -> Optional[Node]:
81
+ return self.nodes.get(node_id)
82
+
83
+ def get_active_node(self) -> Optional[Node]:
84
+ if self.active_node_id is None:
85
+ return None
86
+ return self.nodes.get(self.active_node_id)
87
+
88
+ def set_active_node(self, node_id: Optional[str]) -> None:
89
+ self.active_node_id = node_id
90
+ if node_id is not None:
91
+ self.active_group_name = None
92
+
93
+ def set_active_group(self, group_name: Optional[str]) -> None:
94
+ self.active_group_name = group_name
95
+ if group_name is not None:
96
+ self.active_node_id = None
97
+
98
+ def get_nodes_for_layer(self, layer_name: str) -> List[Node]:
99
+ return [n for n in self.nodes.values() if n.layer_name == layer_name]
100
+
101
+ def create_default_node(self, layer_name: str, node_type: str = "area") -> Node:
102
+ count = len(self.nodes) + 1
103
+ return Node(
104
+ node_id=create_node_id(),
105
+ name=f"{node_type.capitalize()} {count}",
106
+ node_type=node_type,
107
+ area=NodeRect(x=0, y=0, w=64, h=64),
108
+ layer_name=layer_name,
109
+ )
110
+
111
+ def rename_group(self, old_name: str, new_name: str) -> bool:
112
+ if not new_name or new_name == old_name:
113
+ return False
114
+ if new_name in self.groups:
115
+ return False # Already exists
116
+ if old_name not in self.groups:
117
+ return False
118
+
119
+ # Update groups list
120
+ idx = self.groups.index(old_name)
121
+ self.groups[idx] = new_name
122
+
123
+ # Update child nodes
124
+ for node in self.nodes.values():
125
+ if node.group == old_name:
126
+ node.group = new_name
127
+
128
+ if self.active_group_name == old_name:
129
+ self.active_group_name = new_name
130
+ return True
131
+
132
+ def reorder_node(self, node_id: str, target_node_id: str, before: bool = True) -> None:
133
+ if node_id not in self.nodes or target_node_id not in self.nodes:
134
+ return
135
+ node = self.nodes[node_id]
136
+ keys = list(self.nodes.keys())
137
+ keys.remove(node_id)
138
+ target_idx = keys.index(target_node_id)
139
+ if not before:
140
+ target_idx += 1
141
+ keys.insert(target_idx, node_id)
142
+
143
+ # Rebuild dictionary
144
+ new_nodes = {}
145
+ for k in keys:
146
+ new_nodes[k] = self.nodes[k]
147
+ self.nodes = new_nodes
@@ -0,0 +1,53 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import Any, Dict, Optional
5
+
6
+
7
+ @dataclass
8
+ class NodeRect:
9
+ x: int
10
+ y: int
11
+ w: int
12
+ h: int
13
+
14
+ def to_dict(self) -> Dict[str, int]:
15
+ return {"x": self.x, "y": self.y, "w": self.w, "h": self.h}
16
+
17
+ @classmethod
18
+ def from_dict(cls, data: Dict[str, Any]) -> "NodeRect":
19
+ return cls(x=int(data["x"]), y=int(data["y"]), w=int(data["w"]), h=int(data["h"]))
20
+
21
+
22
+ @dataclass
23
+ class Node:
24
+ node_id: str
25
+ name: str
26
+ node_type: str
27
+ area: NodeRect
28
+ layer_name: str
29
+ properties: Dict[str, Any] = field(default_factory=dict)
30
+ group: Optional[str] = None
31
+
32
+ def to_dict(self) -> Dict[str, Any]:
33
+ return {
34
+ "node_id": self.node_id,
35
+ "name": self.name,
36
+ "node_type": self.node_type,
37
+ "area": self.area.to_dict(),
38
+ "layer_name": self.layer_name,
39
+ "properties": dict(self.properties),
40
+ "group": self.group,
41
+ }
42
+
43
+ @classmethod
44
+ def from_dict(cls, data: Dict[str, Any]) -> "Node":
45
+ return cls(
46
+ node_id=str(data["node_id"]),
47
+ name=str(data["name"]),
48
+ node_type=str(data.get("node_type", "area")),
49
+ area=NodeRect.from_dict(data["area"]),
50
+ layer_name=str(data.get("layer_name", "")),
51
+ properties=dict(data.get("properties", {})),
52
+ group=data.get("group"),
53
+ )
@@ -294,8 +294,8 @@ class ShapeEditor:
294
294
  self._dragging_shape = False
295
295
  return True
296
296
 
297
- # Check if clicking on move icon
298
- if self._is_on_move_icon(mouse) and not self._pan_mode:
297
+ # Check if clicking inside the shape body (but not on a handle)
298
+ if self._is_inside_shape(mouse) and not self._pan_mode:
299
299
  self._dragging_shape = True
300
300
  self._drag_start = mouse
301
301
  self._save_shape_positions()
@@ -462,7 +462,7 @@ class ShapeEditor:
462
462
  elif self.shape_type == "circle":
463
463
  return self._sprite_to_screen((self.circle_x, self.circle_y - self.circle_radius))
464
464
  elif self.shape_type == "capsule":
465
- return self._sprite_to_screen((self.capsule_x, self.capsule_y))
465
+ return self._sprite_to_screen((self.capsule_x, self.capsule_y + self.capsule_height / 2))
466
466
  elif self.shape_type == "polygon" and len(self.polygon_vertices) >= 3:
467
467
  # Top-center of polygon bounding box
468
468
  min_y = min(v[1] for v in self.polygon_vertices)
@@ -168,6 +168,7 @@ class SpriteAnimationEditor:
168
168
  self.timeline.on_frames_changed = self._on_frames_changed
169
169
  self.timeline.on_markers_changed = self._on_markers_changed
170
170
  self.preview.on_playback_fps_changed = self._on_preview_fps_changed
171
+ self.preview.on_loop_changed = self._on_preview_loop_changed
171
172
 
172
173
  # Create a default animation (now safe — widgets exist)
173
174
  self._create_new_animation("idle")
@@ -1566,6 +1567,12 @@ class SpriteAnimationEditor:
1566
1567
  self.preview.authoring_fps = float(fps)
1567
1568
  self._notify_animation_modified()
1568
1569
 
1570
+ def _on_preview_loop_changed(self, loop: bool) -> None:
1571
+ anim = self._get_active()
1572
+ if anim:
1573
+ anim.loop = loop
1574
+ self._notify_animation_modified()
1575
+
1569
1576
  def _metadata_apply_add(self, anim: Animation) -> None:
1570
1577
  key = self._meta_key_input.strip()
1571
1578
  if not key:
@@ -58,10 +58,13 @@ class AnimationPreview:
58
58
  self.frames: List[AnimationFrame] = []
59
59
  self.playing = False
60
60
  self.loop = True
61
+ self.grid_offset_x: int = 0
62
+ self.grid_offset_y: int = 0
61
63
  self.playback_fps: float = 60.0
62
64
  self.show_onion = False
63
65
 
64
66
  self.on_playback_fps_changed: Optional[Callable[[float], None]] = None
67
+ self.on_loop_changed: Optional[Callable[[bool], None]] = None
65
68
  self.authoring_fps: float = 60.0
66
69
 
67
70
  self.current_frame: int = 0
@@ -221,6 +224,8 @@ class AnimationPreview:
221
224
  return True
222
225
  if self._btn_loop.collidepoint(mouse):
223
226
  self.loop = not self.loop
227
+ if self.on_loop_changed:
228
+ self.on_loop_changed(self.loop)
224
229
  return True
225
230
  if self._btn_onion.collidepoint(mouse):
226
231
  self.show_onion = not self.show_onion
@@ -411,10 +416,12 @@ class AnimationPreview:
411
416
 
412
417
  def _extract_tile(self, variant_id: int) -> Optional[pygame.Surface]:
413
418
  tw, th = self.tile_size
414
- cols = max(1, self.surface.get_width() // tw)
419
+ ox, oy = self.grid_offset_x, self.grid_offset_y
420
+ available_w = self.surface.get_width() - ox
421
+ cols = max(1, available_w // tw)
415
422
  col = variant_id % cols
416
423
  row = variant_id // cols
417
- src = Rect(col * tw, row * th, tw, th)
424
+ src = Rect(ox + col * tw, oy + row * th, tw, th)
418
425
  if self.surface.get_rect().contains(src):
419
426
  return self.surface.subsurface(src).copy()
420
427
  return None
@@ -394,6 +394,9 @@ class Tilemap:
394
394
  with open(target_path, "w") as f:
395
395
  JSONDump(save_data, f, indent=2)
396
396
 
397
+ if hasattr(self.editor, "node_manager"):
398
+ self.editor.node_manager.save(target_path)
399
+
397
400
  print(f"Saved to {target_path}")
398
401
 
399
402
  def _project_base_path(self) -> Path:
@@ -654,6 +657,11 @@ class Tilemap:
654
657
  if active_layer:
655
658
  active_layer.tiles[pos] = tile_data
656
659
 
660
+ if hasattr(self.editor, "node_manager"):
661
+ self.editor.node_manager.load(path)
662
+ if hasattr(self.editor, "node_selector"):
663
+ self.editor.node_selector._rebuild_filter()
664
+
657
665
  self.initialized = True
658
666
 
659
667
  def _object_tileset_indices_from_payload(self, payload: dict) -> set[int]:
@@ -23,6 +23,7 @@ def init_settings(generate_main: bool = False) -> None:
23
23
  "tileset": "collision",
24
24
  "character": "character_collision"
25
25
  },
26
+ "nodes_path": "nodes",
26
27
  "error_handler": {
27
28
  "log_path": "errors.log",
28
29
  "max_recent_errors": 50,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tilemap-editor
3
- Version: 3.2.6
3
+ Version: 3.2.8
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
@@ -45,6 +45,8 @@ src/editor.py
45
45
  src/event_map.py
46
46
  src/layers.py
47
47
  src/main.py
48
+ src/node_manager.py
49
+ src/nodes.py
48
50
  src/standalone_automap.py
49
51
  src/standalone_error_console.py
50
52
  src/standalone_filemanager.py
@@ -170,6 +172,8 @@ src/widgets/ui/fileinput.py
170
172
  src/widgets/ui/layer_type_dialog.py
171
173
  src/widgets/ui/menubar.py
172
174
  src/widgets/ui/mode_indicator.py
175
+ src/widgets/ui/node_editor.py
176
+ src/widgets/ui/node_selector.py
173
177
  src/widgets/ui/notification.py
174
178
  src/widgets/ui/property_editor.py
175
179
  src/widgets/ui/region_selector.py
@@ -44,6 +44,8 @@ class LayerSelector:
44
44
  self.rename_text: str = ""
45
45
  self.rename_original_name: str = ""
46
46
 
47
+ self._adjusting_opacity_idx: Optional[int] = None
48
+
47
49
  btn_h = 25
48
50
  btn_w = 25
49
51
  btn_y = self.footer_rect.y + 5
@@ -113,6 +115,19 @@ class LayerSelector:
113
115
  layer.locked = not layer.locked
114
116
  return True
115
117
 
118
+ opacity_rect = self._get_opacity_bar_rect(layer_idx)
119
+ if opacity_rect and opacity_rect.collidepoint(mouse_pos):
120
+ layer = self.editor.tilemap.layer_manager.get_layer(
121
+ layer_idx
122
+ )
123
+ if layer:
124
+ self._adjusting_opacity_idx = layer_idx
125
+ rel_x = mouse_pos[0] - opacity_rect.x
126
+ layer.opacity = max(
127
+ 0.0, min(1.0, rel_x / opacity_rect.width)
128
+ )
129
+ return True
130
+
116
131
  self.dragging_layer_idx = layer_idx
117
132
  self.drag_start_y = mouse_pos[1]
118
133
 
@@ -153,6 +168,9 @@ class LayerSelector:
153
168
  return True
154
169
 
155
170
  elif event.type == pygame.MOUSEBUTTONUP:
171
+ if event.button == 1:
172
+ self._adjusting_opacity_idx = None
173
+
156
174
  if event.button == 1 and self.dragging_layer_idx is not None:
157
175
  layer_idx = self._get_layer_at_pos(mouse_pos)
158
176
  if layer_idx is not None and layer_idx != self.dragging_layer_idx:
@@ -164,6 +182,19 @@ class LayerSelector:
164
182
  return True
165
183
 
166
184
  elif event.type == pygame.MOUSEMOTION:
185
+ if self._adjusting_opacity_idx is not None:
186
+ layer = self.editor.tilemap.layer_manager.get_layer(
187
+ self._adjusting_opacity_idx
188
+ )
189
+ if layer:
190
+ opacity_rect = self._get_opacity_bar_rect(
191
+ self._adjusting_opacity_idx
192
+ )
193
+ if opacity_rect:
194
+ rel_x = max(0, min(opacity_rect.width, mouse_pos[0] - opacity_rect.x))
195
+ layer.opacity = max(0.0, min(1.0, rel_x / opacity_rect.width))
196
+ return True
197
+
167
198
  if self.list_rect.collidepoint(mouse_pos):
168
199
  if self.dragging_layer_idx is None:
169
200
  self.hover_idx = self._get_layer_at_pos(mouse_pos)
@@ -188,7 +219,13 @@ class LayerSelector:
188
219
  self._cancel_rename()
189
220
  return True
190
221
  elif event.key == pygame.K_BACKSPACE:
191
- self.rename_text = self.rename_text[:-1]
222
+ pressed = pygame.key.get_pressed()
223
+ meta_down = pressed[pygame.K_LMETA] or pressed[pygame.K_RMETA]
224
+ ctrl_down = pressed[pygame.K_LCTRL] or pressed[pygame.K_RCTRL]
225
+ if meta_down or ctrl_down:
226
+ self.rename_text = ""
227
+ else:
228
+ self.rename_text = self.rename_text[:-1]
192
229
  return True
193
230
  else:
194
231
  if event.unicode.isprintable():
@@ -219,6 +256,19 @@ class LayerSelector:
219
256
 
220
257
  return None
221
258
 
259
+ def _get_opacity_bar_rect(self, layer_idx: int) -> Optional[Rect]:
260
+ """Get the clickable rect for the opacity bar of a layer."""
261
+ item_y = self.list_rect.y + (layer_idx * self.item_h) - self.scroll_offset
262
+
263
+ if item_y + self.item_h < self.list_rect.y or item_y > self.list_rect.bottom:
264
+ return None
265
+
266
+ bar_w = 46
267
+ bar_h = 6
268
+ bar_x = self.list_rect.x + self.list_rect.width - 80
269
+ bar_y = item_y + self.item_h - bar_h - 3
270
+ return Rect(bar_x, bar_y, bar_w, bar_h)
271
+
222
272
  def _get_eye_icon_rect(self, layer_idx: int, mouse_pos) -> Optional[Rect]:
223
273
  """Get the clickable rect for the eye icon of a layer."""
224
274
  if layer_idx is None:
@@ -404,6 +454,18 @@ class LayerSelector:
404
454
  name_txt = self.font_layer.render(layer.name, True, self.text_color)
405
455
  screen.blit(name_txt, (item_rect.x + 5, item_rect.y + 5))
406
456
 
457
+ opacity_bar = self._get_opacity_bar_rect(i)
458
+ if opacity_bar:
459
+ bg_rect = Rect(opacity_bar.x, opacity_bar.y, opacity_bar.width, opacity_bar.height)
460
+ pygame.draw.rect(screen, (60, 60, 60), bg_rect, border_radius=2)
461
+ fill_w = int(opacity_bar.width * layer.opacity)
462
+ if fill_w > 0:
463
+ fill_rect = Rect(opacity_bar.x, opacity_bar.y, fill_w, opacity_bar.height)
464
+ green = int(180 * layer.opacity) + 40
465
+ pygame.draw.rect(screen, (40, green, 40), fill_rect, border_radius=2)
466
+ pct_txt = self.font_layer.render(f"{int(layer.opacity * 100)}%", True, self.text_muted)
467
+ screen.blit(pct_txt, (opacity_bar.x - 32, opacity_bar.y - 2))
468
+
407
469
  eye_x = item_rect.right - 25
408
470
  eye_y = item_rect.y + 7
409
471
  if layer.visible: