tilemap-editor 3.2.1__tar.gz → 3.2.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.
- {tilemap_editor-3.2.1/src/tilemap_editor.egg-info → tilemap_editor-3.2.3}/PKG-INFO +1 -1
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/pyproject.toml +1 -1
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/editor.py +68 -3
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/plugins/character_collision/editor.py +91 -15
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/plugins/sprite_animation/editor.py +46 -2
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/plugins/sprite_animation/models.py +3 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/plugins/sprite_animation/timeline.py +4 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3/src/tilemap_editor.egg-info}/PKG-INFO +1 -1
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/widgets/tile_grid.py +699 -65
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/widgets/tile_selector.py +38 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/widgets/ui/toolbar.py +23 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/LICENSE +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/MANIFEST.in +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/README.md +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/assets/fonts/jetbrain-fonts/JetBrainsMono-Bold.ttf +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/assets/fonts/jetbrain-fonts/JetBrainsMono-BoldItalic.ttf +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/assets/fonts/jetbrain-fonts/JetBrainsMono-Italic.ttf +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/assets/fonts/jetbrain-fonts/JetBrainsMono-Regular.ttf +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/assets/fonts/noto/NotoSans-Bold.ttf +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/assets/fonts/noto/NotoSans-BoldItalic.ttf +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/assets/fonts/noto/NotoSans-Italic.ttf +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/assets/fonts/noto/NotoSans-Regular.ttf +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/assets/icons/arrow-down.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/assets/icons/check.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/assets/icons/checked.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/assets/icons/close.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/assets/icons/duplicate.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/assets/icons/error.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/assets/icons/file.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/assets/icons/filedead.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/assets/icons/fit.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/assets/icons/folder.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/assets/icons/grid.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/assets/icons/image.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/assets/icons/info.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/assets/icons/load.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/assets/icons/loop.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/assets/icons/pan.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/assets/icons/pause.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/assets/icons/pencil.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/assets/icons/play.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/assets/icons/plus.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/assets/icons/radio.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/assets/icons/reset.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/assets/icons/save.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/assets/icons/stop.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/assets/icons/tilemap.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/assets/icons/tileset.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/assets/icons/unchecked.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/assets/icons/warning.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/assets/icons/zoomin.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/assets/icons/zoomout.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/setup.cfg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/configs/themes.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/constants.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/event_map.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/layers.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/main.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/plugins/__init__.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/plugins/character_collision/__init__.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/plugins/character_collision/models.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/plugins/character_collision/protocols.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/plugins/character_collision/shape_editor.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/plugins/character_collision/standalone.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/plugins/object_tileset_collision/__init__.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/plugins/object_tileset_collision/editor.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/plugins/object_tileset_collision/models.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/plugins/object_tileset_collision/protocols.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/plugins/object_tileset_collision/standalone.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/plugins/sprite_animation/__init__.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/plugins/sprite_animation/__main__.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/plugins/sprite_animation/clipboard_util.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/plugins/sprite_animation/frame_picker.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/plugins/sprite_animation/preview.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/plugins/sprite_animation/protocols.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/plugins/sprite_animation/runtime_load.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/plugins/sprite_animation/standalone.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/plugins/sprite_animation/validation.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/plugins/sprite_editor/__init__.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/plugins/sprite_editor/editor.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/plugins/sprite_editor/standalone.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/plugins/tileset_collision/__init__.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/plugins/tileset_collision/collision_painter.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/plugins/tileset_collision/editor.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/plugins/tileset_collision/models.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/plugins/tileset_collision/protocols.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/plugins/tileset_collision/standalone.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/standalone_automap.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/standalone_error_console.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/standalone_filemanager.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/standalone_image_viewer.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/tilemap.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/tilemap_editor/__init__.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/tilemap_editor/__main__.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/tilemap_editor/assets/__init__.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/tilemap_editor/assets/fonts/jetbrain-fonts/JetBrainsMono-Bold.ttf +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/tilemap_editor/assets/fonts/jetbrain-fonts/JetBrainsMono-BoldItalic.ttf +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/tilemap_editor/assets/fonts/jetbrain-fonts/JetBrainsMono-Italic.ttf +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/tilemap_editor/assets/fonts/jetbrain-fonts/JetBrainsMono-Regular.ttf +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/tilemap_editor/assets/fonts/noto/NotoSans-Bold.ttf +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/tilemap_editor/assets/fonts/noto/NotoSans-BoldItalic.ttf +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/tilemap_editor/assets/fonts/noto/NotoSans-Italic.ttf +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/tilemap_editor/assets/fonts/noto/NotoSans-Regular.ttf +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/tilemap_editor/assets/icons/ToolMove.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/tilemap_editor/assets/icons/arrow-down.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/tilemap_editor/assets/icons/check.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/tilemap_editor/assets/icons/checked.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/tilemap_editor/assets/icons/close.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/tilemap_editor/assets/icons/duplicate.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/tilemap_editor/assets/icons/error.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/tilemap_editor/assets/icons/file.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/tilemap_editor/assets/icons/filedead.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/tilemap_editor/assets/icons/fit.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/tilemap_editor/assets/icons/folder.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/tilemap_editor/assets/icons/grid.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/tilemap_editor/assets/icons/image.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/tilemap_editor/assets/icons/info.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/tilemap_editor/assets/icons/load.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/tilemap_editor/assets/icons/loop.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/tilemap_editor/assets/icons/pan.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/tilemap_editor/assets/icons/pause.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/tilemap_editor/assets/icons/pencil.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/tilemap_editor/assets/icons/play.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/tilemap_editor/assets/icons/plus.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/tilemap_editor/assets/icons/radio.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/tilemap_editor/assets/icons/reset.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/tilemap_editor/assets/icons/save.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/tilemap_editor/assets/icons/stop.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/tilemap_editor/assets/icons/tilemap.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/tilemap_editor/assets/icons/tileset.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/tilemap_editor/assets/icons/unchecked.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/tilemap_editor/assets/icons/warning.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/tilemap_editor/assets/icons/zoomin.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/tilemap_editor/assets/icons/zoomout.svg +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/tilemap_editor/cli.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/tilemap_editor/main.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/tilemap_editor/settings.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/tilemap_editor.egg-info/SOURCES.txt +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/tilemap_editor.egg-info/dependency_links.txt +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/tilemap_editor.egg-info/entry_points.txt +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/tilemap_editor.egg-info/requires.txt +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/tilemap_editor.egg-info/top_level.txt +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/ttypes/__init__.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/ttypes/tilemap.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/utils/__init__.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/utils/editor_preference.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/utils/error_handler.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/utils/font_manager.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/utils/history.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/utils/icon_manager.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/utils/icons_cache.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/utils/log_capture.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/utils/project_paths.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/utils/serialization.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/utils/standalone.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/utils/validation.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/widgets/__init__.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/widgets/automap_models.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/widgets/autotile_template.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/widgets/autotiler.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/widgets/filemanager.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/widgets/input.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/widgets/layer_selector.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/widgets/map_properties.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/widgets/mapsetup.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/widgets/regex_automap_designer.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/widgets/spritesheet_grid.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/widgets/ui/collision_layer_mask.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/widgets/ui/collision_layer_sidebar.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/widgets/ui/draw_utils.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/widgets/ui/fileinput.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/widgets/ui/layer_type_dialog.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/widgets/ui/menubar.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/widgets/ui/mode_indicator.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/widgets/ui/notification.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/widgets/ui/property_editor.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/widgets/ui/region_selector.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/widgets/ui/status_bar.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/widgets/ui/theme.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/widgets/ui/tileset_type_dialog.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/src/widgets/ui/tooltip.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/tests/test_collision_layer_mask.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/tests/test_collision_layer_sidebar.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/tests/test_project_paths.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/tests/test_render_scale.py +0 -0
- {tilemap_editor-3.2.1 → tilemap_editor-3.2.3}/tests/test_tilemap_save.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "tilemap-editor"
|
|
7
|
-
version = "3.2.
|
|
7
|
+
version = "3.2.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"
|
|
@@ -150,6 +150,9 @@ class Editor:
|
|
|
150
150
|
self.running = False
|
|
151
151
|
self.pan_mode = False
|
|
152
152
|
self.autotile_mode = False
|
|
153
|
+
self.eraser_mode = False
|
|
154
|
+
self.select_mode = False
|
|
155
|
+
self._prev_tool = None
|
|
153
156
|
|
|
154
157
|
if isinstance(size, tuple) and len(size) == 2:
|
|
155
158
|
self.screen = pygame.display.set_mode(size, pygame.RESIZABLE)
|
|
@@ -684,13 +687,17 @@ class Editor:
|
|
|
684
687
|
self.open_file_manager(
|
|
685
688
|
on_select=self._launch_animation_editor_with_image,
|
|
686
689
|
initial_dir=self.data_root,
|
|
687
|
-
allowed_exts=[".png", ".jpg", ".jpeg"],
|
|
690
|
+
allowed_exts=[".png", ".jpg", ".jpeg", ".json"],
|
|
688
691
|
mode="open",
|
|
689
692
|
)
|
|
690
693
|
|
|
691
694
|
def _launch_animation_editor_with_image(self, path: Path):
|
|
692
|
-
"""Launch animation editor subprocess with selected image."""
|
|
695
|
+
"""Launch animation editor subprocess with selected image or JSON."""
|
|
693
696
|
try:
|
|
697
|
+
if path.suffix.lower() == ".json":
|
|
698
|
+
self._launch_animation_editor_with_json(path)
|
|
699
|
+
return
|
|
700
|
+
|
|
694
701
|
tile_size = "32x32"
|
|
695
702
|
if hasattr(self.tilemap, "tile_size") and self.tilemap.tile_size:
|
|
696
703
|
tw, th = self.tilemap.tile_size
|
|
@@ -711,6 +718,47 @@ class Editor:
|
|
|
711
718
|
except Exception as e:
|
|
712
719
|
error_handler.capture(e, context="launch_animation_editor")
|
|
713
720
|
|
|
721
|
+
def _launch_animation_editor_with_json(self, path: Path) -> None:
|
|
722
|
+
"""Launch animation editor from a saved .anim.json file."""
|
|
723
|
+
try:
|
|
724
|
+
from plugins.sprite_animation.models import AnimationLibrary
|
|
725
|
+
from utils.project_paths import resolve_project_path
|
|
726
|
+
|
|
727
|
+
lib = AnimationLibrary.load(path)
|
|
728
|
+
spritesheet_path = lib.spritesheet_path
|
|
729
|
+
tile_size = lib.tile_size
|
|
730
|
+
|
|
731
|
+
if spritesheet_path:
|
|
732
|
+
resolved = resolve_project_path(
|
|
733
|
+
spritesheet_path,
|
|
734
|
+
path.parent,
|
|
735
|
+
fallback_roots=[self.base_path] if self.base_path else None,
|
|
736
|
+
must_exist=True,
|
|
737
|
+
)
|
|
738
|
+
else:
|
|
739
|
+
resolved = None
|
|
740
|
+
|
|
741
|
+
if not resolved or not resolved.exists():
|
|
742
|
+
print(f"Could not locate spritesheet for animation file: {path.name}")
|
|
743
|
+
return
|
|
744
|
+
|
|
745
|
+
args = [
|
|
746
|
+
str(resolved),
|
|
747
|
+
"--tile-size", f"{tile_size[0]}x{tile_size[1]}",
|
|
748
|
+
"--load", str(path),
|
|
749
|
+
"--data-root", str(self.data_root),
|
|
750
|
+
]
|
|
751
|
+
process = launch_standalone(
|
|
752
|
+
"plugins.sprite_animation.standalone",
|
|
753
|
+
args,
|
|
754
|
+
cwd=self.base_path,
|
|
755
|
+
text=True,
|
|
756
|
+
)
|
|
757
|
+
self.child_processes.append(process)
|
|
758
|
+
print(f"Launched animation editor with: {path.name} (spritesheet: {resolved.name})")
|
|
759
|
+
except Exception as e:
|
|
760
|
+
error_handler.capture(e, context="launch_animation_editor")
|
|
761
|
+
|
|
714
762
|
def launch_sprite_editor(self):
|
|
715
763
|
"""Launch the sprite editor in a new window."""
|
|
716
764
|
self.open_file_manager(
|
|
@@ -1018,7 +1066,24 @@ class Editor:
|
|
|
1018
1066
|
self.perform_load()
|
|
1019
1067
|
continue
|
|
1020
1068
|
elif event.key == pygame.K_SPACE:
|
|
1021
|
-
|
|
1069
|
+
if self.pan_mode:
|
|
1070
|
+
# Restoring: turn off pan, re-enable previous tool
|
|
1071
|
+
self.pan_mode = False
|
|
1072
|
+
if getattr(self, "_prev_tool", None) == "select":
|
|
1073
|
+
self.select_mode = True
|
|
1074
|
+
elif getattr(self, "_prev_tool", None) == "eraser":
|
|
1075
|
+
self.eraser_mode = True
|
|
1076
|
+
else:
|
|
1077
|
+
# Entering pan: save current tool, turn off others
|
|
1078
|
+
if self.select_mode:
|
|
1079
|
+
self._prev_tool = "select"
|
|
1080
|
+
elif self.eraser_mode:
|
|
1081
|
+
self._prev_tool = "eraser"
|
|
1082
|
+
else:
|
|
1083
|
+
self._prev_tool = None
|
|
1084
|
+
self.pan_mode = True
|
|
1085
|
+
self.select_mode = False
|
|
1086
|
+
self.eraser_mode = False
|
|
1022
1087
|
continue
|
|
1023
1088
|
elif event.mod & pygame.KMOD_CTRL and event.key == pygame.K_g:
|
|
1024
1089
|
self.toggle_grid()
|
|
@@ -3,7 +3,7 @@ Character Collision Editor — Define collision shapes for character sprites.
|
|
|
3
3
|
|
|
4
4
|
Layout:
|
|
5
5
|
+----------------------------------------------------+
|
|
6
|
-
| Toolbar: [
|
|
6
|
+
| Toolbar: [Name: _______] [Shape Type Buttons] |
|
|
7
7
|
+----------------------------------------------------+
|
|
8
8
|
| |
|
|
9
9
|
| Shape Editor |
|
|
@@ -16,6 +16,7 @@ Layout:
|
|
|
16
16
|
|
|
17
17
|
from __future__ import annotations
|
|
18
18
|
|
|
19
|
+
import re
|
|
19
20
|
from typing import Optional, Tuple, Dict, Any
|
|
20
21
|
from pathlib import Path
|
|
21
22
|
|
|
@@ -30,6 +31,7 @@ from utils.error_handler import error_handler, error_context
|
|
|
30
31
|
from widgets.ui.theme import COLORS, FONTS
|
|
31
32
|
from widgets.ui.draw_utils import draw_panel, draw_button
|
|
32
33
|
from widgets.ui.collision_layer_sidebar import CollisionLayerSidebar
|
|
34
|
+
from widgets.input import InlineTextInput
|
|
33
35
|
|
|
34
36
|
|
|
35
37
|
class CharacterCollisionEditor:
|
|
@@ -73,6 +75,9 @@ class CharacterCollisionEditor:
|
|
|
73
75
|
self._font = font_manager.get_font(FONTS.name, FONTS.size_md, FontWeight.REGULAR)
|
|
74
76
|
self._font_sm = font_manager.get_font(FONTS.name, FONTS.size_sm, FontWeight.REGULAR)
|
|
75
77
|
|
|
78
|
+
# Editable name input
|
|
79
|
+
self._name_input = InlineTextInput("char_name", default_val=self.character_name)
|
|
80
|
+
|
|
76
81
|
# Shape type buttons
|
|
77
82
|
self.shape_buttons: Dict[ShapeType, Rect] = {}
|
|
78
83
|
self._init_shape_buttons()
|
|
@@ -106,6 +111,18 @@ class CharacterCollisionEditor:
|
|
|
106
111
|
|
|
107
112
|
self._init_shape_buttons()
|
|
108
113
|
|
|
114
|
+
def _get_save_name(self) -> str:
|
|
115
|
+
"""Build filename from the name input.
|
|
116
|
+
|
|
117
|
+
Returns "<safe-name>-character.collision.json" if non-empty,
|
|
118
|
+
"character.collision.json" otherwise.
|
|
119
|
+
"""
|
|
120
|
+
name = self._name_input.text.strip()
|
|
121
|
+
if name:
|
|
122
|
+
safe = re.sub(r'[^\w\-]+', '-', name).strip('-').lower()
|
|
123
|
+
return f"{safe}-character.collision.json"
|
|
124
|
+
return "character.collision.json"
|
|
125
|
+
|
|
109
126
|
def get_collision_data(self) -> CharacterCollisionData:
|
|
110
127
|
"""Get the current collision data"""
|
|
111
128
|
shape_data = self.shape_editor.get_shape_data()
|
|
@@ -145,7 +162,7 @@ class CharacterCollisionEditor:
|
|
|
145
162
|
raise ValueError(f"Unknown shape type: {shape_type}")
|
|
146
163
|
|
|
147
164
|
return CharacterCollisionData(
|
|
148
|
-
name=self.character_name,
|
|
165
|
+
name=self._name_input.text.strip() or self.character_name,
|
|
149
166
|
shape=shape,
|
|
150
167
|
properties={
|
|
151
168
|
"collision_layer": self.layer_sidebar.get_layer(),
|
|
@@ -156,12 +173,28 @@ class CharacterCollisionEditor:
|
|
|
156
173
|
def load_collision_data(self, data: CharacterCollisionData) -> None:
|
|
157
174
|
"""Load collision data"""
|
|
158
175
|
self.character_name = data.name
|
|
176
|
+
self._name_input.text = data.name
|
|
177
|
+
self._name_input.cursor_pos = len(data.name)
|
|
159
178
|
self.shape_editor.load_shape_data(data.shape.to_dict())
|
|
160
179
|
layer = data.properties.get("collision_layer", 1)
|
|
161
180
|
mask = data.properties.get("collision_mask", 0xFFFF)
|
|
162
181
|
self.layer_sidebar.set_layer(layer)
|
|
163
182
|
self.layer_sidebar.set_mask(mask)
|
|
164
183
|
|
|
184
|
+
def _get_save_path(self) -> Path:
|
|
185
|
+
"""Full path for saving/loading collision data."""
|
|
186
|
+
collision_dir = self._get_collision_dir()
|
|
187
|
+
return collision_dir / self._get_save_name()
|
|
188
|
+
|
|
189
|
+
def _get_load_path(self) -> Path:
|
|
190
|
+
"""Path to load from — name-priority, fallback to default."""
|
|
191
|
+
collision_dir = self._get_collision_dir()
|
|
192
|
+
name_path = collision_dir / self._get_save_name()
|
|
193
|
+
if name_path.exists():
|
|
194
|
+
return name_path
|
|
195
|
+
# Fallback to default
|
|
196
|
+
return collision_dir / "character.collision.json"
|
|
197
|
+
|
|
165
198
|
def _get_collision_dir(self) -> Path:
|
|
166
199
|
"""Get collision data directory (data_root/character_collision)"""
|
|
167
200
|
if self._data_root is None:
|
|
@@ -189,11 +222,17 @@ class CharacterCollisionEditor:
|
|
|
189
222
|
except Exception as e:
|
|
190
223
|
error_handler.capture(e, context="load_character_collision")
|
|
191
224
|
|
|
225
|
+
def _name_input_rect(self) -> Rect:
|
|
226
|
+
"""Screen rect for the editable name field in the toolbar."""
|
|
227
|
+
return Rect(self.rect.x + 62, self.rect.y + 10, 175, 28)
|
|
228
|
+
|
|
192
229
|
def handle_event(self, event: pygame.event.Event) -> bool:
|
|
193
230
|
"""Handle input events"""
|
|
194
231
|
if not self.visible:
|
|
195
232
|
return False
|
|
196
233
|
|
|
234
|
+
mouse = pygame.mouse.get_pos()
|
|
235
|
+
|
|
197
236
|
# Layer/mask sidebar handles events when open
|
|
198
237
|
if self.layer_sidebar.handle_event(event):
|
|
199
238
|
return True
|
|
@@ -202,19 +241,40 @@ class CharacterCollisionEditor:
|
|
|
202
241
|
if self.layer_sidebar.handle_toggle_event(event):
|
|
203
242
|
return True
|
|
204
243
|
|
|
205
|
-
# Keyboard shortcut: L to toggle sidebar
|
|
206
|
-
if event.type == pygame.KEYDOWN and event.key == pygame.K_l:
|
|
244
|
+
# Keyboard shortcut: L to toggle sidebar (skip when name input is focused)
|
|
245
|
+
if event.type == pygame.KEYDOWN and event.key == pygame.K_l and not self._name_input.is_focused:
|
|
207
246
|
mods = pygame.key.get_mods()
|
|
208
247
|
if not (mods & (pygame.KMOD_CTRL | pygame.KMOD_LMETA)):
|
|
209
248
|
self.layer_sidebar.toggle()
|
|
210
249
|
return True
|
|
211
250
|
|
|
251
|
+
# Name input handling
|
|
252
|
+
if self._name_input.is_focused:
|
|
253
|
+
if event.type == pygame.KEYDOWN:
|
|
254
|
+
if event.key == pygame.K_RETURN:
|
|
255
|
+
self._name_input.is_focused = False
|
|
256
|
+
return True
|
|
257
|
+
elif event.key == pygame.K_ESCAPE:
|
|
258
|
+
self._name_input.is_focused = False
|
|
259
|
+
return True
|
|
260
|
+
if self._name_input.handle_event(event, self._font):
|
|
261
|
+
return True
|
|
262
|
+
# Tab/click outside handled below
|
|
263
|
+
|
|
264
|
+
# Click-to-focus name input
|
|
265
|
+
if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
|
|
266
|
+
if self._name_input_rect().collidepoint(mouse):
|
|
267
|
+
self._name_input.is_focused = True
|
|
268
|
+
self._name_input.cursor_pos = len(self._name_input.text)
|
|
269
|
+
self._name_input.selection_start = None
|
|
270
|
+
return True
|
|
271
|
+
elif self._name_input.is_focused:
|
|
272
|
+
self._name_input.is_focused = False
|
|
273
|
+
|
|
212
274
|
# Let shape editor handle events next
|
|
213
275
|
if self.shape_editor.handle_event(event):
|
|
214
276
|
return True
|
|
215
277
|
|
|
216
|
-
mouse = pygame.mouse.get_pos()
|
|
217
|
-
|
|
218
278
|
# Shape type button clicks
|
|
219
279
|
if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
|
|
220
280
|
for shape_type, button_rect in self.shape_buttons.items():
|
|
@@ -249,13 +309,30 @@ class CharacterCollisionEditor:
|
|
|
249
309
|
toolbar_rect = Rect(self.rect.x, self.rect.y, self.rect.w, self.toolbar_height)
|
|
250
310
|
draw_panel(screen, toolbar_rect, COLORS.header, COLORS.border)
|
|
251
311
|
|
|
252
|
-
#
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
312
|
+
# "Name:" label
|
|
313
|
+
label = self._font_sm.render("Name:", True, COLORS.text)
|
|
314
|
+
screen.blit(label, (toolbar_rect.x + 10, toolbar_rect.y + 17))
|
|
315
|
+
|
|
316
|
+
# Editable name field
|
|
317
|
+
name_rect = self._name_input_rect()
|
|
318
|
+
bg_color = COLORS.panel if self._name_input.is_focused else COLORS.panel_alt
|
|
319
|
+
pygame.draw.rect(screen, bg_color, name_rect, border_radius=4)
|
|
320
|
+
pygame.draw.rect(
|
|
321
|
+
screen, COLORS.accent if self._name_input.is_focused else COLORS.border,
|
|
322
|
+
name_rect, 1, border_radius=4,
|
|
257
323
|
)
|
|
258
|
-
|
|
324
|
+
|
|
325
|
+
# Render input text
|
|
326
|
+
display_name = self._name_input.text or "character"
|
|
327
|
+
txt = self._font_sm.render(display_name, True, COLORS.text)
|
|
328
|
+
screen.blit(txt, (name_rect.x + 4, name_rect.y + 5))
|
|
329
|
+
|
|
330
|
+
# Cursor when focused
|
|
331
|
+
if self._name_input.is_focused and (pygame.time.get_ticks() // 400) % 2:
|
|
332
|
+
pre = self._font_sm.render(self._name_input.text[:self._name_input.cursor_pos], True, (0, 0, 0))
|
|
333
|
+
cx = name_rect.x + 4 + pre.get_width()
|
|
334
|
+
cy = name_rect.y + 5
|
|
335
|
+
pygame.draw.line(screen, COLORS.text, (cx, cy), (cx, cy + txt.get_height()), 1)
|
|
259
336
|
|
|
260
337
|
# Shape type buttons
|
|
261
338
|
for shape_type, button_rect in self.shape_buttons.items():
|
|
@@ -352,15 +429,14 @@ class CharacterCollisionEditor:
|
|
|
352
429
|
# Ctrl+S / Cmd+S to save
|
|
353
430
|
collision_dir = self._get_collision_dir()
|
|
354
431
|
collision_dir.mkdir(parents=True, exist_ok=True)
|
|
355
|
-
save_path =
|
|
432
|
+
save_path = self._get_save_path()
|
|
356
433
|
self.save_to_file(save_path)
|
|
357
434
|
print(f"Saved collision data to {save_path}")
|
|
358
435
|
elif event.key == pygame.K_l and (
|
|
359
436
|
pygame.key.get_mods() & (pygame.KMOD_LCTRL | pygame.KMOD_LMETA)
|
|
360
437
|
):
|
|
361
438
|
# Ctrl+L / Cmd+L to load
|
|
362
|
-
|
|
363
|
-
load_path = collision_dir / "character_collision.json"
|
|
439
|
+
load_path = self._get_load_path()
|
|
364
440
|
if load_path.exists():
|
|
365
441
|
self.load_from_file(load_path)
|
|
366
442
|
print(f"Loaded collision data from {load_path}")
|
|
@@ -1367,7 +1367,7 @@ class SpriteAnimationEditor:
|
|
|
1367
1367
|
self.preview.set_surface(self._surface, self._tile_size)
|
|
1368
1368
|
self.timeline.surface = self._surface
|
|
1369
1369
|
self.timeline.tile_size = self._tile_size
|
|
1370
|
-
self.timeline.
|
|
1370
|
+
self.timeline.invalidate_cache()
|
|
1371
1371
|
|
|
1372
1372
|
# Update input fields to show clamped values
|
|
1373
1373
|
self._frame_width_input = str(width)
|
|
@@ -1404,7 +1404,7 @@ class SpriteAnimationEditor:
|
|
|
1404
1404
|
if hasattr(self.timeline, "grid_offset_x"):
|
|
1405
1405
|
self.timeline.grid_offset_x = offset_x
|
|
1406
1406
|
self.timeline.grid_offset_y = offset_y
|
|
1407
|
-
self.timeline.
|
|
1407
|
+
self.timeline.invalidate_cache()
|
|
1408
1408
|
|
|
1409
1409
|
# Update preview with offset (if it has the attribute)
|
|
1410
1410
|
if hasattr(self.preview, "grid_offset_x"):
|
|
@@ -1420,6 +1420,43 @@ class SpriteAnimationEditor:
|
|
|
1420
1420
|
self._offset_x_input = str(self._grid_offset_x)
|
|
1421
1421
|
self._offset_y_input = str(self._grid_offset_y)
|
|
1422
1422
|
|
|
1423
|
+
def _apply_library_grid_settings(self) -> None:
|
|
1424
|
+
"""Sync tile_size and grid_offset from the loaded library to all widgets."""
|
|
1425
|
+
tw, th = self.library.tile_size
|
|
1426
|
+
self._tile_size = (tw, th)
|
|
1427
|
+
self.library.tile_size = (tw, th)
|
|
1428
|
+
|
|
1429
|
+
# Update frame picker
|
|
1430
|
+
self.frame_picker.set_surface(self._surface, self._tile_size)
|
|
1431
|
+
|
|
1432
|
+
# Update preview and timeline
|
|
1433
|
+
self.preview.set_surface(self._surface, self._tile_size)
|
|
1434
|
+
self.timeline.surface = self._surface
|
|
1435
|
+
self.timeline.tile_size = self._tile_size
|
|
1436
|
+
self.timeline.invalidate_cache()
|
|
1437
|
+
|
|
1438
|
+
# Update frame size input fields
|
|
1439
|
+
self._frame_width_input = str(tw)
|
|
1440
|
+
self._frame_height_input = str(th)
|
|
1441
|
+
|
|
1442
|
+
# Sync grid offset from library
|
|
1443
|
+
gx, gy = self.library.grid_offset
|
|
1444
|
+
self._grid_offset_x = gx
|
|
1445
|
+
self._grid_offset_y = gy
|
|
1446
|
+
self._offset_x_input = str(gx)
|
|
1447
|
+
self._offset_y_input = str(gy)
|
|
1448
|
+
|
|
1449
|
+
# Propagate offset to all widgets
|
|
1450
|
+
if hasattr(self.frame_picker, "set_grid_offset"):
|
|
1451
|
+
self.frame_picker.set_grid_offset(gx, gy)
|
|
1452
|
+
if hasattr(self.timeline, "grid_offset_x"):
|
|
1453
|
+
self.timeline.grid_offset_x = gx
|
|
1454
|
+
self.timeline.grid_offset_y = gy
|
|
1455
|
+
self.timeline.invalidate_cache()
|
|
1456
|
+
if hasattr(self.preview, "grid_offset_x"):
|
|
1457
|
+
self.preview.grid_offset_x = gx
|
|
1458
|
+
self.preview.grid_offset_y = gy
|
|
1459
|
+
|
|
1423
1460
|
def _sync_active_animation(self) -> None:
|
|
1424
1461
|
"""Push current animation data to sub-widgets."""
|
|
1425
1462
|
anim = self._get_active()
|
|
@@ -1843,6 +1880,7 @@ class SpriteAnimationEditor:
|
|
|
1843
1880
|
print(f"Warning: Could not import FileManager: {e}")
|
|
1844
1881
|
path = self._default_save_path()
|
|
1845
1882
|
try:
|
|
1883
|
+
self.library.grid_offset = (self._grid_offset_x, self._grid_offset_y)
|
|
1846
1884
|
self.library.save(path, base_path=path.parent)
|
|
1847
1885
|
self._last_saved_path = path
|
|
1848
1886
|
print(f"Animations saved to {path}")
|
|
@@ -1888,6 +1926,7 @@ class SpriteAnimationEditor:
|
|
|
1888
1926
|
if self._last_saved_path:
|
|
1889
1927
|
# Save to existing path
|
|
1890
1928
|
try:
|
|
1929
|
+
self.library.grid_offset = (self._grid_offset_x, self._grid_offset_y)
|
|
1891
1930
|
self.library.save(
|
|
1892
1931
|
self._last_saved_path,
|
|
1893
1932
|
base_path=self._last_saved_path.parent,
|
|
@@ -1913,6 +1952,7 @@ class SpriteAnimationEditor:
|
|
|
1913
1952
|
try:
|
|
1914
1953
|
self.library = AnimationLibrary.load(path)
|
|
1915
1954
|
self._resolve_library_paths(path)
|
|
1955
|
+
self._apply_library_grid_settings()
|
|
1916
1956
|
names = self.library.animation_names()
|
|
1917
1957
|
self._active_anim_name = names[0] if names else None
|
|
1918
1958
|
if not names:
|
|
@@ -1948,6 +1988,8 @@ class SpriteAnimationEditor:
|
|
|
1948
1988
|
def _on_save_file_selected(self, path: Path) -> None:
|
|
1949
1989
|
"""Callback when user selects a file to save to."""
|
|
1950
1990
|
try:
|
|
1991
|
+
# Sync current grid offset into library before saving
|
|
1992
|
+
self.library.grid_offset = (self._grid_offset_x, self._grid_offset_y)
|
|
1951
1993
|
self.library.save(path, base_path=path.parent)
|
|
1952
1994
|
self._last_saved_path = path # Track for quick save
|
|
1953
1995
|
print(f"Animations saved to {path}")
|
|
@@ -1968,6 +2010,7 @@ class SpriteAnimationEditor:
|
|
|
1968
2010
|
self.library = AnimationLibrary.load(path)
|
|
1969
2011
|
self._resolve_library_paths(path)
|
|
1970
2012
|
self._last_saved_path = path # Track loaded file as save location
|
|
2013
|
+
self._apply_library_grid_settings()
|
|
1971
2014
|
names = self.library.animation_names()
|
|
1972
2015
|
self._active_anim_name = names[0] if names else None
|
|
1973
2016
|
if not names:
|
|
@@ -2110,6 +2153,7 @@ class SpriteAnimationEditor:
|
|
|
2110
2153
|
def load_animation_data(self, data: dict) -> None:
|
|
2111
2154
|
"""Load animation library from a dict (e.g. from project JSON)."""
|
|
2112
2155
|
self.library = AnimationLibrary.from_dict(data)
|
|
2156
|
+
self._apply_library_grid_settings()
|
|
2113
2157
|
names = self.library.animation_names()
|
|
2114
2158
|
self._active_anim_name = names[0] if names else None
|
|
2115
2159
|
if not names:
|
|
@@ -167,6 +167,7 @@ class AnimationLibrary:
|
|
|
167
167
|
animations: Dict[str, Animation] = field(default_factory=dict)
|
|
168
168
|
spritesheet_path: Optional[str] = None
|
|
169
169
|
tile_size: Tuple[int, int] = (32, 32)
|
|
170
|
+
grid_offset: Tuple[int, int] = (0, 0)
|
|
170
171
|
|
|
171
172
|
def add_animation(self, anim: Animation) -> None:
|
|
172
173
|
self.animations[anim.name] = anim
|
|
@@ -199,6 +200,7 @@ class AnimationLibrary:
|
|
|
199
200
|
return {
|
|
200
201
|
"spritesheet_path": spritesheet_path,
|
|
201
202
|
"tile_size": list(self.tile_size),
|
|
203
|
+
"grid_offset": list(self.grid_offset),
|
|
202
204
|
"animations": {
|
|
203
205
|
name: anim.to_dict() for name, anim in self.animations.items()
|
|
204
206
|
},
|
|
@@ -209,6 +211,7 @@ class AnimationLibrary:
|
|
|
209
211
|
lib = AnimationLibrary(
|
|
210
212
|
spritesheet_path=data.get("spritesheet_path"),
|
|
211
213
|
tile_size=tuple(data.get("tile_size", [32, 32])),
|
|
214
|
+
grid_offset=tuple(data.get("grid_offset", [0, 0])),
|
|
212
215
|
)
|
|
213
216
|
for name, anim_data in data.get("animations", {}).items():
|
|
214
217
|
lib.animations[name] = Animation.from_dict(anim_data)
|
|
@@ -140,6 +140,10 @@ class Timeline:
|
|
|
140
140
|
self.tile_size = tile_size
|
|
141
141
|
self._thumb_cache.clear()
|
|
142
142
|
|
|
143
|
+
def invalidate_cache(self) -> None:
|
|
144
|
+
"""Clear the thumbnail cache. Call after external changes to surface, tile_size, or grid offset."""
|
|
145
|
+
self._thumb_cache.clear()
|
|
146
|
+
|
|
143
147
|
def resize(self, rect: Rect) -> None:
|
|
144
148
|
self.rect = rect
|
|
145
149
|
|