tilemap-editor 2.0.1__tar.gz → 2.0.3__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 (123) hide show
  1. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/CHANGELOG.md +19 -0
  2. {tilemap_editor-2.0.1/src/tilemap_editor.egg-info → tilemap_editor-2.0.3}/PKG-INFO +1 -1
  3. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/pyproject.toml +1 -1
  4. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/editor.py +30 -9
  5. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3/src/tilemap_editor.egg-info}/PKG-INFO +1 -1
  6. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/widgets/autotile_template.py +24 -23
  7. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/widgets/autotiler.py +85 -48
  8. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/LICENSE +0 -0
  9. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/MANIFEST.in +0 -0
  10. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/README.md +0 -0
  11. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/assets/fonts/jetbrain-fonts/JetBrainsMono-Bold.ttf +0 -0
  12. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/assets/fonts/jetbrain-fonts/JetBrainsMono-BoldItalic.ttf +0 -0
  13. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/assets/fonts/jetbrain-fonts/JetBrainsMono-Italic.ttf +0 -0
  14. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/assets/fonts/jetbrain-fonts/JetBrainsMono-Regular.ttf +0 -0
  15. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/assets/fonts/noto/NotoSans-Bold.ttf +0 -0
  16. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/assets/fonts/noto/NotoSans-BoldItalic.ttf +0 -0
  17. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/assets/fonts/noto/NotoSans-Italic.ttf +0 -0
  18. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/assets/fonts/noto/NotoSans-Regular.ttf +0 -0
  19. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/assets/icons/arrow-down.svg +0 -0
  20. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/assets/icons/check.svg +0 -0
  21. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/assets/icons/checked.svg +0 -0
  22. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/assets/icons/close.svg +0 -0
  23. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/assets/icons/duplicate.svg +0 -0
  24. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/assets/icons/error.svg +0 -0
  25. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/assets/icons/file.svg +0 -0
  26. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/assets/icons/filedead.svg +0 -0
  27. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/assets/icons/fit.svg +0 -0
  28. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/assets/icons/folder.svg +0 -0
  29. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/assets/icons/grid.svg +0 -0
  30. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/assets/icons/image.svg +0 -0
  31. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/assets/icons/info.svg +0 -0
  32. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/assets/icons/load.svg +0 -0
  33. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/assets/icons/loop.svg +0 -0
  34. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/assets/icons/pan.svg +0 -0
  35. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/assets/icons/pause.svg +0 -0
  36. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/assets/icons/pencil.svg +0 -0
  37. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/assets/icons/play.svg +0 -0
  38. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/assets/icons/plus.svg +0 -0
  39. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/assets/icons/radio.svg +0 -0
  40. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/assets/icons/reset.svg +0 -0
  41. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/assets/icons/save.svg +0 -0
  42. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/assets/icons/stop.svg +0 -0
  43. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/assets/icons/tilemap.svg +0 -0
  44. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/assets/icons/tileset.svg +0 -0
  45. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/assets/icons/unchecked.svg +0 -0
  46. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/assets/icons/warning.svg +0 -0
  47. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/assets/icons/zoomin.svg +0 -0
  48. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/assets/icons/zoomout.svg +0 -0
  49. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/setup.cfg +0 -0
  50. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/configs/themes.py +0 -0
  51. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/constants.py +0 -0
  52. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/event_map.py +0 -0
  53. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/layers.py +0 -0
  54. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/main.py +0 -0
  55. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/plugins/__init__.py +0 -0
  56. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/plugins/character_collision/__init__.py +0 -0
  57. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/plugins/character_collision/editor.py +0 -0
  58. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/plugins/character_collision/models.py +0 -0
  59. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/plugins/character_collision/protocols.py +0 -0
  60. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/plugins/character_collision/shape_editor.py +0 -0
  61. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/plugins/character_collision/standalone.py +0 -0
  62. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/plugins/sprite_animation/__init__.py +0 -0
  63. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/plugins/sprite_animation/__main__.py +0 -0
  64. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/plugins/sprite_animation/clipboard_util.py +0 -0
  65. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/plugins/sprite_animation/editor.py +0 -0
  66. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/plugins/sprite_animation/frame_picker.py +0 -0
  67. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/plugins/sprite_animation/models.py +0 -0
  68. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/plugins/sprite_animation/preview.py +0 -0
  69. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/plugins/sprite_animation/protocols.py +0 -0
  70. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/plugins/sprite_animation/runtime_load.py +0 -0
  71. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/plugins/sprite_animation/standalone.py +0 -0
  72. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/plugins/sprite_animation/timeline.py +0 -0
  73. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/plugins/sprite_animation/validation.py +0 -0
  74. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/plugins/tileset_collision/__init__.py +0 -0
  75. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/plugins/tileset_collision/collision_painter.py +0 -0
  76. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/plugins/tileset_collision/editor.py +0 -0
  77. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/plugins/tileset_collision/models.py +0 -0
  78. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/plugins/tileset_collision/protocols.py +0 -0
  79. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/plugins/tileset_collision/standalone.py +0 -0
  80. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/standalone_automap.py +0 -0
  81. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/standalone_error_console.py +0 -0
  82. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/standalone_filemanager.py +0 -0
  83. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/standalone_image_viewer.py +0 -0
  84. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/tilemap.py +0 -0
  85. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/tilemap_editor/__init__.py +0 -0
  86. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/tilemap_editor/__main__.py +0 -0
  87. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/tilemap_editor/cli.py +0 -0
  88. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/tilemap_editor.egg-info/SOURCES.txt +0 -0
  89. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/tilemap_editor.egg-info/dependency_links.txt +0 -0
  90. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/tilemap_editor.egg-info/entry_points.txt +0 -0
  91. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/tilemap_editor.egg-info/requires.txt +0 -0
  92. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/tilemap_editor.egg-info/top_level.txt +0 -0
  93. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/ttypes/__init__.py +0 -0
  94. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/ttypes/tilemap.py +0 -0
  95. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/utils/__init__.py +0 -0
  96. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/utils/error_handler.py +0 -0
  97. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/utils/font_manager.py +0 -0
  98. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/utils/history.py +0 -0
  99. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/utils/icon_manager.py +0 -0
  100. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/utils/icons_cache.py +0 -0
  101. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/utils/log_capture.py +0 -0
  102. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/utils/serialization.py +0 -0
  103. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/utils/settings.py +0 -0
  104. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/utils/standalone.py +0 -0
  105. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/utils/validation.py +0 -0
  106. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/widgets/__init__.py +0 -0
  107. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/widgets/automap_models.py +0 -0
  108. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/widgets/filemanager.py +0 -0
  109. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/widgets/layer_selector.py +0 -0
  110. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/widgets/mapsetup.py +0 -0
  111. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/widgets/regex_automap_designer.py +0 -0
  112. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/widgets/tile_grid.py +0 -0
  113. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/widgets/tile_selector.py +0 -0
  114. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/widgets/ui/draw_utils.py +0 -0
  115. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/widgets/ui/fileinput.py +0 -0
  116. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/widgets/ui/layer_type_dialog.py +0 -0
  117. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/widgets/ui/menubar.py +0 -0
  118. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/widgets/ui/notification.py +0 -0
  119. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/widgets/ui/property_editor.py +0 -0
  120. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/widgets/ui/theme.py +0 -0
  121. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/widgets/ui/tileset_type_dialog.py +0 -0
  122. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/widgets/ui/toolbar.py +0 -0
  123. {tilemap_editor-2.0.1 → tilemap_editor-2.0.3}/src/widgets/ui/tooltip.py +0 -0
@@ -5,6 +5,25 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [2.0.1] - 2025-04-23
9
+
10
+ ### Added
11
+ - **Tileset Collision Plugin**: Godot-like polygon collision editor
12
+ - `CollisionPainter`: Interactive polygon drawing with edge-constrain mode
13
+ - `TilesetCollisionEditor`: Full editor with tile selector and painted tiles list
14
+ - `TilesetCollisionLibrary`: Persistent collision data management
15
+ - Edge-draw mode (`E` key + `Shift`) for precise slope/stair creation
16
+ - Help panel (`H` key) with all controls documented
17
+ - One-way collision support for platforms
18
+ - Zoom, pan, and grid controls
19
+ - **Character Collision Plugin**: Shape-based character collision editor
20
+ - Rectangle, Circle, and Capsule shape support
21
+ - Protocol-based design for easy integration
22
+
23
+ ### Changed
24
+ - Updated pyproject.toml version to 2.0.1
25
+ - Improved editor.py and tile_selector.py integration
26
+
8
27
  ## [2.0.0] - 2025-04-22
9
28
 
10
29
  ### Major Changes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tilemap-editor
3
- Version: 2.0.1
3
+ Version: 2.0.3
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 = "2.0.1"
7
+ version = "2.0.3"
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"
@@ -762,6 +762,7 @@ class Editor:
762
762
  if event.type == pygame.VIDEORESIZE:
763
763
  self.handle_resize(event.w, event.h)
764
764
 
765
+ # Priority 1: Modal dialogs and inputs (highest priority)
765
766
  if self.save_input.active:
766
767
  self.save_input.handle_event(event)
767
768
  continue
@@ -782,9 +783,20 @@ class Editor:
782
783
  if self.property_editor.handle_event(event):
783
784
  continue
784
785
 
786
+ # Priority 2: Autotiler and Regex Designer (block all events when visible)
787
+ if self.autotiler.visible:
788
+ if self.autotiler.handle_event(event):
789
+ continue
790
+
791
+ if self.regex_automap_designer.visible:
792
+ if self.regex_automap_designer.handle_event(event):
793
+ continue
794
+
795
+ # Priority 3: Menu bar
785
796
  if self.menubar.handle_event(event):
786
797
  continue
787
798
 
799
+ # Priority 4: Keyboard shortcuts
788
800
  if event.type == pygame.KEYDOWN:
789
801
  mods = pygame.key.get_mods()
790
802
  ctrl_held = mods & (pygame.KMOD_LCTRL | pygame.KMOD_RCTRL)
@@ -836,23 +848,17 @@ class Editor:
836
848
  self.tilemap.layer_manager.set_active_layer(idx)
837
849
  continue
838
850
 
851
+ # Priority 5: Toolbar
839
852
  if self.toolbar and self.toolbar.handle_event(event):
840
853
  continue
841
854
 
842
- if self.autotiler.visible:
843
- if self.autotiler.handle_event(event):
844
- continue
845
-
846
- if self.regex_automap_designer.visible:
847
- if self.regex_automap_designer.handle_event(event):
848
- continue
849
-
850
- # Route events to the dockable animation panel
855
+ # Priority 6: Dockable animation panel
851
856
  if self.left_panel_visible and self.animation_panel:
852
857
  if hasattr(self.animation_panel, "handle_event"):
853
858
  if self.animation_panel.handle_event(event):
854
859
  continue
855
860
 
861
+ # Priority 7: Side panels (tileset and layer widgets)
856
862
  consumed = False
857
863
  if self.tileset_widget and self.tileset_widget.handle_event(event):
858
864
  consumed = True
@@ -862,6 +868,8 @@ class Editor:
862
868
  and self.layer_widget.handle_event(event)
863
869
  ):
864
870
  consumed = True
871
+
872
+ # Priority 8: Main tile grid (lowest priority)
865
873
  if not consumed and self.tile_grid_widget:
866
874
  self.tile_grid_widget.handle_event(event)
867
875
 
@@ -894,9 +902,22 @@ class Editor:
894
902
  self.tileset_widget.draw(self.screen)
895
903
  if self.layer_widget:
896
904
  self.layer_widget.draw(self.screen)
905
+
906
+ # Draw autotiler and regex designer with modal overlay
897
907
  if self.autotiler:
908
+ if self.autotiler.visible:
909
+ # Dim background when autotiler is open
910
+ overlay = pygame.Surface((self.width, self.height), pygame.SRCALPHA)
911
+ overlay.fill((0, 0, 0, 100))
912
+ self.screen.blit(overlay, (0, 0))
898
913
  self.autotiler.draw(self.screen)
914
+
899
915
  if self.regex_automap_designer:
916
+ if self.regex_automap_designer.visible:
917
+ # Dim background when regex designer is open
918
+ overlay = pygame.Surface((self.width, self.height), pygame.SRCALPHA)
919
+ overlay.fill((0, 0, 0, 100))
920
+ self.screen.blit(overlay, (0, 0))
900
921
  self.regex_automap_designer.draw(self.screen)
901
922
 
902
923
  # Draw the dockable animation panel
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tilemap-editor
3
- Version: 2.0.1
3
+ Version: 2.0.3
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
@@ -166,6 +166,14 @@ class AutotileTemplateApplier:
166
166
  if not ts:
167
167
  return
168
168
 
169
+ # Check if a group is selected
170
+ if self.designer.selected_group_idx == -1:
171
+ print("Template Error: No group selected. Please select a group first.")
172
+ return
173
+
174
+ # Use the currently selected group
175
+ target_group = self.designer.groups[self.designer.selected_group_idx]
176
+
169
177
  rx, ry, rw, rh = tile_selector.selected_tile
170
178
  tile_w, tile_h = self.designer.editor.tilemap.tile_size
171
179
  sheet_cols = ts.surface.get_width() // tile_w
@@ -173,24 +181,6 @@ class AutotileTemplateApplier:
173
181
  start_col = rx // tile_w
174
182
  start_row = ry // tile_h
175
183
 
176
- # Define a unique group ID for THIS specific 3x3 selection area
177
- # (e.g. "GrassSet_12_4")
178
- set_id = f"{ts.name}_{start_col}_{start_row}"
179
-
180
- from .autotiler import AutotileGroup
181
-
182
- # Create a real group object in the designer
183
- target_group = None
184
- for g in self.designer.groups:
185
- if g.name == set_id:
186
- target_group = g
187
- break
188
-
189
- if not target_group:
190
- target_group = AutotileGroup(set_id)
191
- self.designer.groups.append(target_group)
192
- self.designer.selected_group_idx = len(self.designer.groups) - 1
193
-
194
184
  added_count = 0
195
185
  updated_count = 0
196
186
 
@@ -204,32 +194,43 @@ class AutotileTemplateApplier:
204
194
 
205
195
  ts_index = tile_selector.active_idx
206
196
 
207
- # Check for existing rule in THIS group
197
+ # Check for existing rule in THIS group with EXACT same neighbors and tileset
208
198
  matched_rule = None
209
199
  for r in target_group.rules:
200
+ # Must match both neighbors AND tileset to be considered the same rule
210
201
  if r.neighbors == neighbors and r.tileset_index == ts_index:
211
202
  matched_rule = r
212
203
  break
213
204
 
214
205
  if matched_rule:
206
+ # Only add variant if it's not already there
215
207
  if vid not in matched_rule.variant_ids:
216
208
  matched_rule.variant_ids.append(vid)
217
209
  updated_count += 1
218
210
  else:
219
- rule_name = f"{ts.name}_{start_col}_{start_row}_{rel_col}_{rel_row}"
211
+ # Create a new rule with a unique name
212
+ rule_num = len(target_group.rules) + 1
213
+ rule_name = f"Rule {rule_num}"
214
+
215
+ # Ensure unique name
216
+ existing_names = {r.name for r in target_group.rules}
217
+ while rule_name in existing_names:
218
+ rule_num += 1
219
+ rule_name = f"Rule {rule_num}"
220
+
220
221
  new_rule = AutotileRule(
221
222
  name=rule_name,
222
- neighbors=neighbors,
223
+ neighbors=set(neighbors), # Create a new set to avoid reference issues
223
224
  tileset_path=str(ts.path),
224
225
  variant_ids=[vid],
225
226
  tileset_index=ts_index,
226
- group_id=set_id,
227
+ group_id=target_group.name,
227
228
  )
228
229
  target_group.rules.append(new_rule)
229
230
  added_count += 1
230
231
 
231
232
  print(
232
- f"Template Applied: {template.name}. Group: {set_id}. {added_count} rules added, {updated_count} rules updated."
233
+ f"Template Applied: {template.name} to Group '{target_group.name}'. {added_count} rules added, {updated_count} rules updated."
233
234
  )
234
235
 
235
236
  def draw(self, screen: Surface):
@@ -87,7 +87,7 @@ class AutotileGroup:
87
87
  class AutotileRuleDesigner:
88
88
  def __init__(self, editor: "Editor", x: int, y: int):
89
89
  self.editor = editor
90
- self.rect = Rect(x, y, 600, 450)
90
+ self.rect = Rect(x, y, 600, 500) # Increased height to accommodate buttons outside list areas
91
91
  self.header_height = 30
92
92
 
93
93
  self.visible = False
@@ -102,7 +102,7 @@ class AutotileRuleDesigner:
102
102
  self.rename_text: str = ""
103
103
 
104
104
  self.scroll_offset: int = 0
105
- self.max_visible_rules: int = 10
105
+ self.max_visible_rules: int = 6 # Reduced to make scrolling more visible
106
106
  self.scroll_bar_rect: Optional[Rect] = None
107
107
  self.is_scrollbar_dragging: bool = False
108
108
 
@@ -139,9 +139,16 @@ class AutotileRuleDesigner:
139
139
  body_h = self.rect.height - self.header_height
140
140
  sidebar_w = 200
141
141
  self.list_area = Rect(self.rect.x, body_y, sidebar_w, body_h)
142
- self.group_list_area = Rect(self.rect.x, body_y, sidebar_w, body_h // 2)
142
+
143
+ # Reserve space for buttons at the bottom of each list area
144
+ button_h = 30
145
+ group_content_h = (body_h // 2) - button_h
146
+ rule_content_h = (body_h // 2) - button_h
147
+
148
+ # Group list area excludes the button
149
+ self.group_list_area = Rect(self.rect.x, body_y, sidebar_w, group_content_h)
143
150
  self.rule_list_area = Rect(
144
- self.rect.x, body_y + body_h // 2, sidebar_w, body_h // 2
151
+ self.rect.x, body_y + body_h // 2, sidebar_w, rule_content_h
145
152
  )
146
153
 
147
154
  self.edit_area = Rect(
@@ -154,15 +161,16 @@ class AutotileRuleDesigner:
154
161
  self.save_btn_rect = Rect(cx - btn_w - 5, btn_y, btn_w, 30)
155
162
  self.delete_btn_rect = Rect(cx + 5, btn_y, btn_w, 30)
156
163
 
164
+ # Position buttons OUTSIDE their respective list areas to prevent click conflicts
157
165
  self.new_group_btn_rect = Rect(
158
166
  self.group_list_area.x + 10,
159
- self.group_list_area.bottom - 30,
167
+ self.group_list_area.bottom + 5, # Below the group list area
160
168
  self.group_list_area.width - 20,
161
169
  25,
162
170
  )
163
171
  self.new_rule_btn_rect = Rect(
164
172
  self.rule_list_area.x + 10,
165
- self.rule_list_area.bottom - 30,
173
+ self.rule_list_area.bottom + 5, # Below the rule list area
166
174
  self.rule_list_area.width - 20,
167
175
  25,
168
176
  )
@@ -250,7 +258,12 @@ class AutotileRuleDesigner:
250
258
  if self._handle_group_rename(event):
251
259
  return True
252
260
 
253
- mouse_pos = pygame.mouse.get_pos()
261
+ # Get mouse position from event if available, otherwise from pygame
262
+ if hasattr(event, 'pos'):
263
+ mouse_pos = event.pos
264
+ else:
265
+ mouse_pos = pygame.mouse.get_pos()
266
+
254
267
  self._update_preview_from_selector()
255
268
 
256
269
  if event.type == pygame.KEYDOWN:
@@ -272,40 +285,36 @@ class AutotileRuleDesigner:
272
285
  )
273
286
  return True
274
287
 
275
- if self.group_list_area.collidepoint(mouse_pos):
276
- self._handle_group_list_click(mouse_pos)
277
- return True
278
-
279
- if self.rule_list_area.collidepoint(mouse_pos):
280
- self._handle_rule_list_click(mouse_pos)
281
- return True
282
-
283
- if self.save_btn_rect.collidepoint(mouse_pos):
284
- self._save_current_rule()
285
- return True
286
- if self.save_btn_rect.inflate(0, 40).collidepoint(mouse_pos):
287
- pass
288
-
288
+ # Check buttons FIRST before checking list areas (since buttons are outside)
289
289
  if self.new_group_btn_rect.collidepoint(mouse_pos):
290
290
  self._create_new_group_with_focus()
291
291
  return True
292
-
292
+
293
293
  if self.new_rule_btn_rect.collidepoint(mouse_pos):
294
294
  self._reset_selection()
295
295
  return True
296
296
 
297
- if self.edit_area.collidepoint(mouse_pos):
298
- if self._handle_grid_click(mouse_pos):
299
- return True
300
-
301
- if self.delete_btn_rect.collidepoint(mouse_pos):
302
- self._delete_current_rule()
297
+ # Then check list areas
298
+ if self.group_list_area.collidepoint(mouse_pos):
299
+ self._handle_group_list_click(mouse_pos)
303
300
  return True
304
- if self.external_btn_rect.collidepoint(mouse_pos):
305
- self._launch_external_viewer()
301
+
302
+ if self.rule_list_area.collidepoint(mouse_pos):
303
+ self._handle_rule_list_click(mouse_pos)
306
304
  return True
307
- if self.template_btn_rect.collidepoint(mouse_pos):
308
- self.template_manager.show_at(mouse_pos)
305
+
306
+ # Edit area interactions
307
+ if self.edit_area.collidepoint(mouse_pos):
308
+ if self.save_btn_rect.collidepoint(mouse_pos):
309
+ self._save_current_rule()
310
+ elif self.delete_btn_rect.collidepoint(mouse_pos):
311
+ self._delete_current_rule()
312
+ elif self.external_btn_rect.collidepoint(mouse_pos):
313
+ self._launch_external_viewer()
314
+ elif self.template_btn_rect.collidepoint(mouse_pos):
315
+ self.template_manager.show_at(mouse_pos)
316
+ else:
317
+ self._handle_grid_click(mouse_pos)
309
318
  return True
310
319
 
311
320
  elif event.type == pygame.MOUSEBUTTONUP:
@@ -318,10 +327,8 @@ class AutotileRuleDesigner:
318
327
  self._update_layout()
319
328
  return True
320
329
 
321
- if not self.rect.collidepoint(mouse_pos) and not self.is_dragging:
322
- return False
323
-
324
- return True
330
+ # Always consume events when visible to prevent clicks bleeding through
331
+ return self.rect.collidepoint(mouse_pos) or self.is_dragging
325
332
 
326
333
  def _sync_last_editor_state(self):
327
334
  tile_selector = getattr(self.editor, "tileset_widget", None)
@@ -409,7 +416,7 @@ class AutotileRuleDesigner:
409
416
  return False
410
417
 
411
418
  def _handle_group_list_click(self, mouse_pos):
412
- start_y = self.group_list_area.y + 10
419
+ start_y = self.group_list_area.y + 25
413
420
  item_h = 25
414
421
  for i, group in enumerate(self.groups):
415
422
  item_rect = Rect(
@@ -421,23 +428,37 @@ class AutotileRuleDesigner:
421
428
  if item_rect.collidepoint(mouse_pos):
422
429
  self.selected_group_idx = i
423
430
  self.selected_rule_index = -1
431
+ self.scroll_offset = 0
424
432
  return
425
433
 
426
434
  def _handle_rule_list_click(self, mouse_pos):
427
- if self.new_rule_btn_rect.collidepoint(mouse_pos):
428
- self._reset_selection()
429
- return
430
-
431
435
  if self.selected_group_idx == -1:
432
436
  return
433
437
 
434
438
  group = self.groups[self.selected_group_idx]
435
- start_y = self.rule_list_area.y + 10
439
+ start_y = self.rule_list_area.y + 25
436
440
  item_h = 25
437
- for i, rule in enumerate(group.rules):
441
+
442
+ # Only process clicks within the visible list area
443
+ list_content_area = Rect(
444
+ self.rule_list_area.x,
445
+ self.rule_list_area.y + 25,
446
+ self.rule_list_area.width,
447
+ self.rule_list_area.height - 25 # Adjusted since button is outside
448
+ )
449
+
450
+ if not list_content_area.collidepoint(mouse_pos):
451
+ return
452
+
453
+ visible_start = self.scroll_offset
454
+ visible_end = min(visible_start + self.max_visible_rules, len(group.rules))
455
+
456
+ for i in range(visible_start, visible_end):
457
+ rule = group.rules[i]
458
+ display_index = i - visible_start
438
459
  item_rect = Rect(
439
460
  self.rule_list_area.x + 5,
440
- start_y + i * item_h,
461
+ start_y + display_index * item_h,
441
462
  self.rule_list_area.width - 10,
442
463
  item_h,
443
464
  )
@@ -672,6 +693,7 @@ class AutotileRuleDesigner:
672
693
  d_name = name if len(name) < 22 else name[:19] + ".."
673
694
  screen.blit(self.font.render(d_name, True, TEXT_COLOR), (r.x + 5, r.y + 5))
674
695
 
696
+ # Draw button OUTSIDE the group list area
675
697
  pygame.draw.rect(
676
698
  screen, (80, 120, 80), self.new_group_btn_rect, border_radius=4
677
699
  )
@@ -688,6 +710,7 @@ class AutotileRuleDesigner:
688
710
 
689
711
  self._draw_scrollable_rule_list(screen)
690
712
 
713
+ # Draw button OUTSIDE the rule list area
691
714
  pygame.draw.rect(
692
715
  screen, (70, 130, 180), self.new_rule_btn_rect, border_radius=4
693
716
  )
@@ -697,7 +720,7 @@ class AutotileRuleDesigner:
697
720
  )
698
721
 
699
722
  def _draw_scrollable_rule_list(self, screen: Surface) -> None:
700
- """Draw rules with scroll indicators and scrollbar"""
723
+ """Draw rules with scroll indicators and scrollbar - with clipping to prevent overflow"""
701
724
  if self.selected_group_idx == -1:
702
725
  return
703
726
 
@@ -708,6 +731,17 @@ class AutotileRuleDesigner:
708
731
 
709
732
  self.scroll_offset = max(0, min(self.scroll_offset, max_scroll))
710
733
 
734
+ # Set clip rect to prevent rules from rendering outside the list area
735
+ # Since button is now outside, we can use more of the area
736
+ list_clip = Rect(
737
+ self.rule_list_area.x,
738
+ self.rule_list_area.y + 25,
739
+ self.rule_list_area.width,
740
+ self.rule_list_area.height - 25 # Just leave space for header
741
+ )
742
+ old_clip = screen.get_clip()
743
+ screen.set_clip(list_clip)
744
+
711
745
  visible_start = self.scroll_offset
712
746
  visible_end = min(visible_start + self.max_visible_rules, total_rules)
713
747
 
@@ -729,6 +763,9 @@ class AutotileRuleDesigner:
729
763
  d_name = rule.name if len(rule.name) < 20 else rule.name[:17] + ".."
730
764
  screen.blit(self.font.render(d_name, True, TEXT_COLOR), (r.x + 5, r.y + 5))
731
765
 
766
+ # Restore clip
767
+ screen.set_clip(old_clip)
768
+
732
769
  if total_rules > self.max_visible_rules:
733
770
  if self.scroll_offset > 0:
734
771
  arrow_up = "▲"
@@ -743,11 +780,11 @@ class AutotileRuleDesigner:
743
780
  arrow_surf = self.font.render(arrow_down, True, (150, 200, 255))
744
781
  screen.blit(
745
782
  arrow_surf,
746
- (self.rule_list_area.right - 20, self.rule_list_area.bottom - 35),
783
+ (self.rule_list_area.right - 20, self.rule_list_area.bottom - 20),
747
784
  )
748
785
 
749
786
  scrollbar_height = 60
750
- track_height = self.rule_list_area.height - 70
787
+ track_height = self.rule_list_area.height - 50
751
788
  scroll_ratio = self.scroll_offset / max_scroll if max_scroll > 0 else 0
752
789
 
753
790
  scrollbar_y = (
@@ -800,7 +837,7 @@ class AutotileRuleDesigner:
800
837
  elif event.type == pygame.MOUSEMOTION:
801
838
  if self.is_scrollbar_dragging:
802
839
  relative_y = event.pos[1] - self.rule_list_area.y - 25
803
- track_height = self.rule_list_area.height - 70
840
+ track_height = self.rule_list_area.height - 50 # Updated to match drawing
804
841
 
805
842
  if track_height > 0:
806
843
  scroll_ratio = max(0, min(1, relative_y / track_height))
File without changes
File without changes
File without changes