tilemap-editor 2.0.0__tar.gz → 2.0.2__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.0 → tilemap_editor-2.0.2}/CHANGELOG.md +19 -0
  2. {tilemap_editor-2.0.0/src/tilemap_editor.egg-info → tilemap_editor-2.0.2}/PKG-INFO +54 -5
  3. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/README.md +53 -4
  4. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/pyproject.toml +1 -1
  5. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/editor.py +85 -0
  6. tilemap_editor-2.0.2/src/plugins/character_collision/__init__.py +24 -0
  7. tilemap_editor-2.0.2/src/plugins/character_collision/editor.py +320 -0
  8. tilemap_editor-2.0.2/src/plugins/character_collision/models.py +171 -0
  9. tilemap_editor-2.0.2/src/plugins/character_collision/protocols.py +169 -0
  10. tilemap_editor-2.0.2/src/plugins/character_collision/shape_editor.py +567 -0
  11. tilemap_editor-2.0.2/src/plugins/character_collision/standalone.py +125 -0
  12. tilemap_editor-2.0.2/src/plugins/tileset_collision/__init__.py +17 -0
  13. tilemap_editor-2.0.2/src/plugins/tileset_collision/collision_painter.py +759 -0
  14. tilemap_editor-2.0.2/src/plugins/tileset_collision/editor.py +702 -0
  15. tilemap_editor-2.0.2/src/plugins/tileset_collision/models.py +113 -0
  16. tilemap_editor-2.0.2/src/plugins/tileset_collision/protocols.py +56 -0
  17. tilemap_editor-2.0.2/src/plugins/tileset_collision/standalone.py +140 -0
  18. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2/src/tilemap_editor.egg-info}/PKG-INFO +54 -5
  19. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/tilemap_editor.egg-info/SOURCES.txt +12 -0
  20. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/widgets/autotiler.py +22 -10
  21. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/widgets/tile_selector.py +31 -1
  22. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/widgets/ui/menubar.py +1 -0
  23. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/LICENSE +0 -0
  24. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/MANIFEST.in +0 -0
  25. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/assets/fonts/jetbrain-fonts/JetBrainsMono-Bold.ttf +0 -0
  26. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/assets/fonts/jetbrain-fonts/JetBrainsMono-BoldItalic.ttf +0 -0
  27. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/assets/fonts/jetbrain-fonts/JetBrainsMono-Italic.ttf +0 -0
  28. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/assets/fonts/jetbrain-fonts/JetBrainsMono-Regular.ttf +0 -0
  29. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/assets/fonts/noto/NotoSans-Bold.ttf +0 -0
  30. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/assets/fonts/noto/NotoSans-BoldItalic.ttf +0 -0
  31. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/assets/fonts/noto/NotoSans-Italic.ttf +0 -0
  32. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/assets/fonts/noto/NotoSans-Regular.ttf +0 -0
  33. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/assets/icons/arrow-down.svg +0 -0
  34. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/assets/icons/check.svg +0 -0
  35. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/assets/icons/checked.svg +0 -0
  36. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/assets/icons/close.svg +0 -0
  37. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/assets/icons/duplicate.svg +0 -0
  38. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/assets/icons/error.svg +0 -0
  39. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/assets/icons/file.svg +0 -0
  40. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/assets/icons/filedead.svg +0 -0
  41. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/assets/icons/fit.svg +0 -0
  42. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/assets/icons/folder.svg +0 -0
  43. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/assets/icons/grid.svg +0 -0
  44. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/assets/icons/image.svg +0 -0
  45. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/assets/icons/info.svg +0 -0
  46. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/assets/icons/load.svg +0 -0
  47. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/assets/icons/loop.svg +0 -0
  48. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/assets/icons/pan.svg +0 -0
  49. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/assets/icons/pause.svg +0 -0
  50. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/assets/icons/pencil.svg +0 -0
  51. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/assets/icons/play.svg +0 -0
  52. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/assets/icons/plus.svg +0 -0
  53. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/assets/icons/radio.svg +0 -0
  54. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/assets/icons/reset.svg +0 -0
  55. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/assets/icons/save.svg +0 -0
  56. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/assets/icons/stop.svg +0 -0
  57. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/assets/icons/tilemap.svg +0 -0
  58. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/assets/icons/tileset.svg +0 -0
  59. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/assets/icons/unchecked.svg +0 -0
  60. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/assets/icons/warning.svg +0 -0
  61. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/assets/icons/zoomin.svg +0 -0
  62. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/assets/icons/zoomout.svg +0 -0
  63. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/setup.cfg +0 -0
  64. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/configs/themes.py +0 -0
  65. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/constants.py +0 -0
  66. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/event_map.py +0 -0
  67. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/layers.py +0 -0
  68. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/main.py +0 -0
  69. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/plugins/__init__.py +0 -0
  70. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/plugins/sprite_animation/__init__.py +0 -0
  71. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/plugins/sprite_animation/__main__.py +0 -0
  72. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/plugins/sprite_animation/clipboard_util.py +0 -0
  73. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/plugins/sprite_animation/editor.py +0 -0
  74. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/plugins/sprite_animation/frame_picker.py +0 -0
  75. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/plugins/sprite_animation/models.py +0 -0
  76. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/plugins/sprite_animation/preview.py +0 -0
  77. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/plugins/sprite_animation/protocols.py +0 -0
  78. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/plugins/sprite_animation/runtime_load.py +0 -0
  79. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/plugins/sprite_animation/standalone.py +0 -0
  80. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/plugins/sprite_animation/timeline.py +0 -0
  81. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/plugins/sprite_animation/validation.py +0 -0
  82. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/standalone_automap.py +0 -0
  83. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/standalone_error_console.py +0 -0
  84. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/standalone_filemanager.py +0 -0
  85. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/standalone_image_viewer.py +0 -0
  86. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/tilemap.py +0 -0
  87. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/tilemap_editor/__init__.py +0 -0
  88. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/tilemap_editor/__main__.py +0 -0
  89. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/tilemap_editor/cli.py +0 -0
  90. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/tilemap_editor.egg-info/dependency_links.txt +0 -0
  91. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/tilemap_editor.egg-info/entry_points.txt +0 -0
  92. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/tilemap_editor.egg-info/requires.txt +0 -0
  93. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/tilemap_editor.egg-info/top_level.txt +0 -0
  94. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/ttypes/__init__.py +0 -0
  95. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/ttypes/tilemap.py +0 -0
  96. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/utils/__init__.py +0 -0
  97. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/utils/error_handler.py +0 -0
  98. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/utils/font_manager.py +0 -0
  99. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/utils/history.py +0 -0
  100. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/utils/icon_manager.py +0 -0
  101. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/utils/icons_cache.py +0 -0
  102. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/utils/log_capture.py +0 -0
  103. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/utils/serialization.py +0 -0
  104. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/utils/settings.py +0 -0
  105. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/utils/standalone.py +0 -0
  106. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/utils/validation.py +0 -0
  107. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/widgets/__init__.py +0 -0
  108. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/widgets/automap_models.py +0 -0
  109. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/widgets/autotile_template.py +0 -0
  110. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/widgets/filemanager.py +0 -0
  111. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/widgets/layer_selector.py +0 -0
  112. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/widgets/mapsetup.py +0 -0
  113. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/widgets/regex_automap_designer.py +0 -0
  114. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/widgets/tile_grid.py +0 -0
  115. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/widgets/ui/draw_utils.py +0 -0
  116. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/widgets/ui/fileinput.py +0 -0
  117. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/widgets/ui/layer_type_dialog.py +0 -0
  118. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/widgets/ui/notification.py +0 -0
  119. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/widgets/ui/property_editor.py +0 -0
  120. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/widgets/ui/theme.py +0 -0
  121. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/widgets/ui/tileset_type_dialog.py +0 -0
  122. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/src/widgets/ui/toolbar.py +0 -0
  123. {tilemap_editor-2.0.0 → tilemap_editor-2.0.2}/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.0
3
+ Version: 2.0.2
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
@@ -16,25 +16,29 @@ Dynamic: license-file
16
16
 
17
17
  # tilemap-editor
18
18
 
19
+ > [!WARNING]
20
+ > This is mostly vibe coded as working with ui isnt so easy in pygame. Somehow it works for me. Though it took decent time as this was initially too buggy(which still is as expected). Just an experiment and keep customizing for my need.
21
+ PRs are welcome.
22
+
19
23
  `tilemap-editor` is a pygame-based map editor focused on fast iteration for 2D games.
24
+ `tilemap-parser` to parse this editor. (https://pypi.org/project/tilemap-parser/)[https://pypi.org/project/tilemap-parser/]
20
25
 
21
26
  ## What it includes
22
27
 
23
28
  - **Multi-layer tile/object map editing** with visual layer management
24
- - **Professional UI** with SVG icons, consistent fonts, and modern design
29
+ - **Decent enough UI** with SVG icons, consistent fonts, and modern design
25
30
  - **Sprite animation editor** (`tilemap-anim-editor`) with frame-by-frame editing
26
31
  - **Built-in tools**: File manager, error console, image viewer, automapper
27
32
  - **Font management** with auto-selected coding fonts for clarity
28
33
  - **Icon system** with 26+ SVG icons and geometric fallbacks
29
34
  - **JSON save/load** compatible with this editor
35
+ - **Decent console** toggled with ctrl + ` for error/warning/logs/debugging
30
36
 
31
37
  ## Key Features
32
38
 
33
39
  ### UI Improvements
34
- - Crisp SVG icons throughout the interface
40
+ - SVG icons throughout the interface
35
41
  - Bold, auto-selected coding fonts (JetBrains Mono, Fira Code, Consolas)
36
- - Professional color scheme and consistent styling
37
- - Dynamic icon loading - icons auto-load by button name
38
42
 
39
43
  ### Animation Editor
40
44
  - Frame picker with visual thumbnails
@@ -52,8 +56,53 @@ Dynamic: license-file
52
56
 
53
57
  ## Install (local/dev)
54
58
 
59
+ ### Using Virtual Environment (Recommended)
60
+
61
+ For Linux and macOS:
62
+
55
63
  ```bash
64
+ # Create virtual environment
65
+ python3 -m venv .venv
66
+
67
+ # Activate virtual environment
68
+ source .venv/bin/activate
69
+
70
+ # Install in development mode
71
+ pip install -e .
72
+
73
+ # Deactivate when done
74
+ deactivate
75
+ ```
76
+
77
+ For Windows:
78
+
79
+ ```bash
80
+ # Create virtual environment
81
+ python -m venv .venv
82
+
83
+ # Activate virtual environment
84
+ .venv\Scripts\activate
85
+
86
+ # Install in development mode
56
87
  pip install -e .
88
+
89
+ # Deactivate when done
90
+ deactivate
91
+ ```
92
+
93
+ ### Direct Install (Not Recommended)
94
+
95
+ ```bash
96
+ pip install -e .
97
+ ```
98
+
99
+ ## Package installtion from pypi
100
+
101
+ ```bash
102
+ pip install tilemap-editor
103
+ ```
104
+ ```bash
105
+ pip install tilemap-parser
57
106
  ```
58
107
 
59
108
  ## Run editor
@@ -1,24 +1,28 @@
1
1
  # tilemap-editor
2
2
 
3
+ > [!WARNING]
4
+ > This is mostly vibe coded as working with ui isnt so easy in pygame. Somehow it works for me. Though it took decent time as this was initially too buggy(which still is as expected). Just an experiment and keep customizing for my need.
5
+ PRs are welcome.
6
+
3
7
  `tilemap-editor` is a pygame-based map editor focused on fast iteration for 2D games.
8
+ `tilemap-parser` to parse this editor. (https://pypi.org/project/tilemap-parser/)[https://pypi.org/project/tilemap-parser/]
4
9
 
5
10
  ## What it includes
6
11
 
7
12
  - **Multi-layer tile/object map editing** with visual layer management
8
- - **Professional UI** with SVG icons, consistent fonts, and modern design
13
+ - **Decent enough UI** with SVG icons, consistent fonts, and modern design
9
14
  - **Sprite animation editor** (`tilemap-anim-editor`) with frame-by-frame editing
10
15
  - **Built-in tools**: File manager, error console, image viewer, automapper
11
16
  - **Font management** with auto-selected coding fonts for clarity
12
17
  - **Icon system** with 26+ SVG icons and geometric fallbacks
13
18
  - **JSON save/load** compatible with this editor
19
+ - **Decent console** toggled with ctrl + ` for error/warning/logs/debugging
14
20
 
15
21
  ## Key Features
16
22
 
17
23
  ### UI Improvements
18
- - Crisp SVG icons throughout the interface
24
+ - SVG icons throughout the interface
19
25
  - Bold, auto-selected coding fonts (JetBrains Mono, Fira Code, Consolas)
20
- - Professional color scheme and consistent styling
21
- - Dynamic icon loading - icons auto-load by button name
22
26
 
23
27
  ### Animation Editor
24
28
  - Frame picker with visual thumbnails
@@ -36,8 +40,53 @@
36
40
 
37
41
  ## Install (local/dev)
38
42
 
43
+ ### Using Virtual Environment (Recommended)
44
+
45
+ For Linux and macOS:
46
+
39
47
  ```bash
48
+ # Create virtual environment
49
+ python3 -m venv .venv
50
+
51
+ # Activate virtual environment
52
+ source .venv/bin/activate
53
+
54
+ # Install in development mode
55
+ pip install -e .
56
+
57
+ # Deactivate when done
58
+ deactivate
59
+ ```
60
+
61
+ For Windows:
62
+
63
+ ```bash
64
+ # Create virtual environment
65
+ python -m venv .venv
66
+
67
+ # Activate virtual environment
68
+ .venv\Scripts\activate
69
+
70
+ # Install in development mode
40
71
  pip install -e .
72
+
73
+ # Deactivate when done
74
+ deactivate
75
+ ```
76
+
77
+ ### Direct Install (Not Recommended)
78
+
79
+ ```bash
80
+ pip install -e .
81
+ ```
82
+
83
+ ## Package installtion from pypi
84
+
85
+ ```bash
86
+ pip install tilemap-editor
87
+ ```
88
+ ```bash
89
+ pip install tilemap-parser
41
90
  ```
42
91
 
43
92
  ## Run editor
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "tilemap-editor"
7
- version = "2.0.0"
7
+ version = "2.0.2"
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"
@@ -490,6 +490,9 @@ class Editor:
490
490
  self._init_animation_panel()
491
491
  if self.animation_panel is not None:
492
492
  self.left_panel_visible = True
493
+ else:
494
+ self.left_panel_visible = False
495
+
493
496
 
494
497
  def _init_animation_panel(self):
495
498
  """Initialize the animation panel without loading any tileset."""
@@ -605,6 +608,87 @@ class Editor:
605
608
  except Exception as e:
606
609
  error_handler.capture(e, context="launch_animation_editor")
607
610
 
611
+ def launch_collision_editor(self):
612
+ """Launch the tileset collision editor in a new window.
613
+
614
+ Uses the active tileset image when one is loaded. If no tileset is
615
+ selected, shows a notification.
616
+ """
617
+ sheet = self._active_tileset_image_path()
618
+ if sheet is not None:
619
+ self._launch_collision_editor_with_image(sheet)
620
+ return
621
+
622
+ self.notifications.notify("No tileset loaded. Please load a tileset first.")
623
+
624
+ def _launch_collision_editor_with_image(self, path: Path):
625
+ """Launch collision editor subprocess with selected tileset."""
626
+ try:
627
+ tile_size = "32x32"
628
+ if hasattr(self.tilemap, "tile_size") and self.tilemap.tile_size:
629
+ tw, th = self.tilemap.tile_size
630
+ tile_size = f"{tw}x{th}"
631
+
632
+ args = [str(path), "--tile-size", tile_size]
633
+
634
+ # Check if collision data file exists and load it
635
+ collision_path = path.with_suffix('.collision.json')
636
+ if collision_path.exists():
637
+ args.extend(["--load", str(collision_path)])
638
+
639
+ process = launch_standalone(
640
+ "plugins.tileset_collision.standalone",
641
+ args,
642
+ cwd=BASE_PATH,
643
+ text=True,
644
+ )
645
+ self.child_processes.append(process)
646
+
647
+ print(
648
+ f"Launched collision editor with: {path.name} (tile size: {tile_size})"
649
+ )
650
+ except Exception as e:
651
+ error_handler.capture(e, context="launch_collision_editor")
652
+
653
+ def launch_character_collision_editor(self):
654
+ """Launch the character collision editor in a new window.
655
+
656
+ Opens a file picker to select a character sprite image.
657
+ """
658
+ self.open_file_manager(
659
+ on_select=self._launch_character_collision_editor_with_image,
660
+ initial_dir=BASE_PATH / "data",
661
+ allowed_exts=[".png", ".jpg", ".jpeg"],
662
+ mode="open",
663
+ )
664
+
665
+ def _launch_character_collision_editor_with_image(self, path: Path):
666
+ """Launch character collision editor subprocess with selected image."""
667
+ try:
668
+ # Use filename (without extension) as default character name
669
+ character_name = path.stem
670
+
671
+ args = [str(path), "--name", character_name]
672
+
673
+ # Check if collision data file exists and load it
674
+ collision_path = path.with_suffix('.collision.json')
675
+ if collision_path.exists():
676
+ args.extend(["--load", str(collision_path)])
677
+
678
+ process = launch_standalone(
679
+ "plugins.character_collision.standalone",
680
+ args,
681
+ cwd=BASE_PATH,
682
+ text=True,
683
+ )
684
+ self.child_processes.append(process)
685
+
686
+ print(
687
+ f"Launched character collision editor with: {path.name} (character: {character_name})"
688
+ )
689
+ except Exception as e:
690
+ error_handler.capture(e, context="launch_character_collision_editor")
691
+
608
692
  def launch_error_console(self):
609
693
  """Launch the error console as a subprocess."""
610
694
  if self.error_console_process and self.error_console_process.poll() is None:
@@ -670,6 +754,7 @@ class Editor:
670
754
  self.child_processes = [p for p in self.child_processes if p.poll() is None]
671
755
 
672
756
  def handle_events(self):
757
+ """Process all pygame events."""
673
758
  for event in pygame.event.get():
674
759
  if event.type == pygame.QUIT:
675
760
  self.running = False
@@ -0,0 +1,24 @@
1
+ """
2
+ Character Collision Editor Plugin
3
+
4
+ Editor for defining collision shapes for character sprites.
5
+ Supports rectangle, circle, capsule, and polygon shapes.
6
+ """
7
+
8
+ from .models import (
9
+ CharacterCollisionData,
10
+ RectangleCollisionData,
11
+ CircleCollisionData,
12
+ CapsuleCollisionData,
13
+ PolygonCollisionData,
14
+ )
15
+ from .protocols import CollisionShapeType
16
+
17
+ __all__ = [
18
+ "CharacterCollisionData",
19
+ "RectangleCollisionData",
20
+ "CircleCollisionData",
21
+ "CapsuleCollisionData",
22
+ "PolygonCollisionData",
23
+ "CollisionShapeType",
24
+ ]
@@ -0,0 +1,320 @@
1
+ """
2
+ Character Collision Editor — Define collision shapes for character sprites.
3
+
4
+ Layout:
5
+ +----------------------------------------------------+
6
+ | Toolbar: [Character Name] [Shape Type Buttons] |
7
+ +----------------------------------------------------+
8
+ | |
9
+ | Shape Editor |
10
+ | (visual shape editing) |
11
+ | |
12
+ +----------------------------------------------------+
13
+ | Properties: [Width/Height/Radius/etc.] |
14
+ +----------------------------------------------------+
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ from typing import Optional, Tuple, Dict, Any
20
+ from pathlib import Path
21
+
22
+ import pygame
23
+ from pygame import Rect, Surface
24
+
25
+ from .models import CharacterCollisionData
26
+ from .shape_editor import ShapeEditor, ShapeType
27
+ from utils.font_manager import font_manager, FontWeight
28
+ from utils.icon_manager import icon_manager
29
+ from utils.error_handler import error_handler, error_context
30
+ from widgets.ui.theme import COLORS, FONTS
31
+ from widgets.ui.draw_utils import draw_panel, draw_button
32
+
33
+
34
+ class CharacterCollisionEditor:
35
+ """Main editor for character collision shapes."""
36
+
37
+ def __init__(
38
+ self,
39
+ rect: Rect,
40
+ sprite_surface: Surface,
41
+ character_name: str = "Character",
42
+ ):
43
+ self.rect = rect
44
+ self.sprite_surface = sprite_surface
45
+ self.character_name = character_name
46
+ self.visible = True
47
+
48
+ # UI layout
49
+ self.toolbar_height = 50
50
+ self.properties_height = 100
51
+
52
+ shape_editor_rect = Rect(
53
+ rect.x,
54
+ rect.y + self.toolbar_height,
55
+ rect.w,
56
+ rect.h - self.toolbar_height - self.properties_height
57
+ )
58
+
59
+ # Shape editor
60
+ self.shape_editor = ShapeEditor(shape_editor_rect, sprite_surface)
61
+
62
+ # Fonts
63
+ self._font = font_manager.get_font(FONTS.name, FONTS.size_md, FontWeight.REGULAR)
64
+ self._font_sm = font_manager.get_font(FONTS.name, FONTS.size_sm, FontWeight.REGULAR)
65
+
66
+ # Shape type buttons
67
+ self.shape_buttons: Dict[ShapeType, Rect] = {}
68
+ self._init_shape_buttons()
69
+
70
+ def _init_shape_buttons(self) -> None:
71
+ """Initialize shape type button rects"""
72
+ button_w = 100
73
+ button_h = 30
74
+ button_spacing = 10
75
+ start_x = self.rect.x + 250
76
+
77
+ shapes: list[ShapeType] = ["rectangle", "circle", "capsule"]
78
+ for i, shape in enumerate(shapes):
79
+ x = start_x + i * (button_w + button_spacing)
80
+ y = self.rect.y + 10
81
+ self.shape_buttons[shape] = Rect(x, y, button_w, button_h)
82
+
83
+ def resize(self, rect: Rect) -> None:
84
+ """Resize the editor"""
85
+ self.rect = rect
86
+
87
+ shape_editor_rect = Rect(
88
+ rect.x,
89
+ rect.y + self.toolbar_height,
90
+ rect.w,
91
+ rect.h - self.toolbar_height - self.properties_height
92
+ )
93
+
94
+ self.shape_editor.resize(shape_editor_rect)
95
+ self._init_shape_buttons()
96
+
97
+ def get_collision_data(self) -> CharacterCollisionData:
98
+ """Get the current collision data"""
99
+ shape_data = self.shape_editor.get_shape_data()
100
+
101
+ # Import shape data classes
102
+ from .models import (
103
+ RectangleCollisionData,
104
+ CircleCollisionData,
105
+ CapsuleCollisionData,
106
+ PolygonCollisionData,
107
+ )
108
+
109
+ shape_type = shape_data["type"]
110
+ if shape_type == "rectangle":
111
+ shape = RectangleCollisionData(
112
+ width=shape_data["width"],
113
+ height=shape_data["height"],
114
+ offset=shape_data["offset"],
115
+ )
116
+ elif shape_type == "circle":
117
+ shape = CircleCollisionData(
118
+ radius=shape_data["radius"],
119
+ offset=shape_data["offset"],
120
+ )
121
+ elif shape_type == "capsule":
122
+ shape = CapsuleCollisionData(
123
+ radius=shape_data["radius"],
124
+ height=shape_data["height"],
125
+ offset=shape_data["offset"],
126
+ )
127
+ elif shape_type == "polygon":
128
+ shape = PolygonCollisionData(
129
+ vertices=shape_data["vertices"],
130
+ offset=shape_data["offset"],
131
+ )
132
+ else:
133
+ raise ValueError(f"Unknown shape type: {shape_type}")
134
+
135
+ return CharacterCollisionData(
136
+ name=self.character_name,
137
+ shape=shape,
138
+ )
139
+
140
+ def load_collision_data(self, data: CharacterCollisionData) -> None:
141
+ """Load collision data"""
142
+ self.character_name = data.name
143
+ self.shape_editor.load_shape_data(data.shape.to_dict())
144
+
145
+ def save_to_file(self, path: Path) -> None:
146
+ """Save collision data to file"""
147
+ try:
148
+ import json
149
+ data = self.get_collision_data()
150
+ with open(path, 'w') as f:
151
+ json.dump(data.to_dict(), f, indent=2)
152
+ except Exception as e:
153
+ error_handler.capture(e, context="save_character_collision")
154
+
155
+ def load_from_file(self, path: Path) -> None:
156
+ """Load collision data from file"""
157
+ try:
158
+ import json
159
+ with open(path, 'r') as f:
160
+ data_dict = json.load(f)
161
+ data = CharacterCollisionData.from_dict(data_dict)
162
+ self.load_collision_data(data)
163
+ except Exception as e:
164
+ error_handler.capture(e, context="load_character_collision")
165
+
166
+ def handle_event(self, event: pygame.event.Event) -> bool:
167
+ """Handle input events"""
168
+ if not self.visible:
169
+ return False
170
+
171
+ # Let shape editor handle events first
172
+ if self.shape_editor.handle_event(event):
173
+ return True
174
+
175
+ mouse = pygame.mouse.get_pos()
176
+
177
+ # Shape type button clicks
178
+ if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
179
+ for shape_type, button_rect in self.shape_buttons.items():
180
+ if button_rect.collidepoint(mouse):
181
+ self.shape_editor.set_shape_type(shape_type)
182
+ return True
183
+
184
+ return False
185
+
186
+ def draw(self, screen: Surface) -> None:
187
+ """Draw the editor"""
188
+ if not self.visible:
189
+ return
190
+
191
+ # Draw toolbar
192
+ self._draw_toolbar(screen)
193
+
194
+ # Draw shape editor
195
+ self.shape_editor.draw(screen)
196
+
197
+ # Draw properties panel
198
+ self._draw_properties(screen)
199
+
200
+ def _draw_toolbar(self, screen: Surface) -> None:
201
+ """Draw the toolbar"""
202
+ toolbar_rect = Rect(self.rect.x, self.rect.y, self.rect.w, self.toolbar_height)
203
+ draw_panel(screen, toolbar_rect, COLORS.header, COLORS.border)
204
+
205
+ # Title
206
+ title = self._font.render(
207
+ f"Character Collision Editor — {self.character_name}",
208
+ True,
209
+ COLORS.text
210
+ )
211
+ screen.blit(title, (toolbar_rect.x + 10, toolbar_rect.y + 15))
212
+
213
+ # Shape type buttons
214
+ for shape_type, button_rect in self.shape_buttons.items():
215
+ is_active = (self.shape_editor.shape_type == shape_type)
216
+
217
+ # Render label
218
+ label_surf = self._font_sm.render(shape_type.capitalize(), True, COLORS.text)
219
+
220
+ draw_button(screen, button_rect, label_surf, active=is_active)
221
+
222
+ def _draw_properties(self, screen: Surface) -> None:
223
+ """Draw the properties panel"""
224
+ props_rect = Rect(
225
+ self.rect.x,
226
+ self.rect.bottom - self.properties_height,
227
+ self.rect.w,
228
+ self.properties_height
229
+ )
230
+ draw_panel(screen, props_rect, COLORS.panel, COLORS.border)
231
+
232
+ # Display current shape properties
233
+ shape_data = self.shape_editor.get_shape_data()
234
+ y = props_rect.y + 10
235
+ x = props_rect.x + 10
236
+
237
+ title = self._font.render("Properties:", True, COLORS.text)
238
+ screen.blit(title, (x, y))
239
+ y += 25
240
+
241
+ if shape_data["type"] == "rectangle":
242
+ text = self._font_sm.render(
243
+ f"Width: {shape_data['width']:.1f} Height: {shape_data['height']:.1f} Offset: ({shape_data['offset'][0]:.1f}, {shape_data['offset'][1]:.1f})",
244
+ True,
245
+ COLORS.text_dim
246
+ )
247
+ screen.blit(text, (x, y))
248
+ elif shape_data["type"] == "circle":
249
+ text = self._font_sm.render(
250
+ f"Radius: {shape_data['radius']:.1f} Offset: ({shape_data['offset'][0]:.1f}, {shape_data['offset'][1]:.1f})",
251
+ True,
252
+ COLORS.text_dim
253
+ )
254
+ screen.blit(text, (x, y))
255
+ elif shape_data["type"] == "capsule":
256
+ text = self._font_sm.render(
257
+ f"Radius: {shape_data['radius']:.1f} Height: {shape_data['height']:.1f} Offset: ({shape_data['offset'][0]:.1f}, {shape_data['offset'][1]:.1f})",
258
+ True,
259
+ COLORS.text_dim
260
+ )
261
+ screen.blit(text, (x, y))
262
+ elif shape_data["type"] == "polygon":
263
+ vertex_count = len(shape_data["vertices"])
264
+ text = self._font_sm.render(
265
+ f"Vertices: {vertex_count}",
266
+ True,
267
+ COLORS.text_dim
268
+ )
269
+ screen.blit(text, (x, y))
270
+
271
+ @classmethod
272
+ def from_path(
273
+ cls,
274
+ sprite_path: Path,
275
+ window_size: Tuple[int, int] = (1000, 800),
276
+ character_name: str = "Character",
277
+ ) -> "CharacterCollisionEditor":
278
+ """Create editor from sprite image path (for standalone use)"""
279
+ surface = pygame.image.load(sprite_path).convert_alpha()
280
+ rect = Rect(0, 0, window_size[0], window_size[1])
281
+ return cls(rect, surface, character_name)
282
+
283
+ def run(self) -> None:
284
+ """Run standalone editor (for standalone use)"""
285
+ screen = pygame.display.get_surface()
286
+ if screen is None:
287
+ raise RuntimeError("pygame display not initialized")
288
+
289
+ clock = pygame.time.Clock()
290
+ running = True
291
+
292
+ while running:
293
+ for event in pygame.event.get():
294
+ if event.type == pygame.QUIT:
295
+ running = False
296
+ elif event.type == pygame.KEYDOWN:
297
+ if event.key == pygame.K_ESCAPE:
298
+ running = False
299
+ elif event.key == pygame.K_s and (
300
+ pygame.key.get_mods() & (pygame.KMOD_LCTRL | pygame.KMOD_LMETA)
301
+ ):
302
+ # Ctrl+S / Cmd+S to save
303
+ save_path = Path("character_collision.json")
304
+ self.save_to_file(save_path)
305
+ print(f"Saved collision data to {save_path}")
306
+ elif event.key == pygame.K_l and (
307
+ pygame.key.get_mods() & (pygame.KMOD_LCTRL | pygame.KMOD_LMETA)
308
+ ):
309
+ # Ctrl+L / Cmd+L to load
310
+ load_path = Path("character_collision.json")
311
+ if load_path.exists():
312
+ self.load_from_file(load_path)
313
+ print(f"Loaded collision data from {load_path}")
314
+
315
+ self.handle_event(event)
316
+
317
+ screen.fill((20, 20, 20))
318
+ self.draw(screen)
319
+ pygame.display.flip()
320
+ clock.tick(60)