tilemap-editor 3.3.3__tar.gz → 3.3.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {tilemap_editor-3.3.3/src/tilemap_editor.egg-info → tilemap_editor-3.3.5}/PKG-INFO +1 -1
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/pyproject.toml +1 -1
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/sprite_animation/editor.py +51 -6
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/sprite_animation/frame_picker.py +32 -8
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/sprite_editor/editor.py +24 -5
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/tileset_collision/collision_painter.py +137 -34
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/tileset_collision/editor.py +192 -2
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5/src/tilemap_editor.egg-info}/PKG-INFO +1 -1
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor.egg-info/SOURCES.txt +1 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/filemanager.py +2 -4
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/spritesheet_grid.py +190 -30
- tilemap_editor-3.3.5/src/widgets/ui/checkbox.py +99 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/LICENSE +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/MANIFEST.in +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/README.md +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/fonts/jetbrain-fonts/JetBrainsMono-Bold.ttf +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/fonts/jetbrain-fonts/JetBrainsMono-BoldItalic.ttf +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/fonts/jetbrain-fonts/JetBrainsMono-Italic.ttf +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/fonts/jetbrain-fonts/JetBrainsMono-Regular.ttf +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/fonts/noto/NotoSans-Bold.ttf +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/fonts/noto/NotoSans-BoldItalic.ttf +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/fonts/noto/NotoSans-Italic.ttf +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/fonts/noto/NotoSans-Regular.ttf +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/arrow-down.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/check.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/checked.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/close.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/duplicate.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/error.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/file.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/filedead.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/fit.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/folder.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/grid.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/image.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/info.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/load.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/loop.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/pan.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/pause.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/pencil.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/play.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/plus.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/radio.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/reset.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/save.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/stop.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/tilemap.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/tileset.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/unchecked.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/warning.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/zoomin.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/assets/icons/zoomout.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/setup.cfg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/configs/themes.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/constants.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/editor.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/event_map.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/layers.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/main.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/node_manager.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/nodes.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/__init__.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/character_collision/__init__.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/character_collision/editor.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/character_collision/models.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/character_collision/protocols.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/character_collision/shape_editor.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/character_collision/standalone.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/object_tileset_collision/__init__.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/object_tileset_collision/editor.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/object_tileset_collision/models.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/object_tileset_collision/protocols.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/object_tileset_collision/standalone.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/sprite_animation/__init__.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/sprite_animation/__main__.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/sprite_animation/clipboard_util.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/sprite_animation/models.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/sprite_animation/preview.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/sprite_animation/protocols.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/sprite_animation/runtime_load.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/sprite_animation/standalone.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/sprite_animation/timeline.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/sprite_animation/validation.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/sprite_editor/__init__.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/sprite_editor/standalone.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/tileset_collision/__init__.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/tileset_collision/models.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/tileset_collision/protocols.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/tileset_collision/standalone.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/standalone_automap.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/standalone_error_console.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/standalone_filemanager.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/standalone_image_viewer.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/__init__.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/__main__.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/__init__.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/fonts/jetbrain-fonts/JetBrainsMono-Bold.ttf +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/fonts/jetbrain-fonts/JetBrainsMono-BoldItalic.ttf +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/fonts/jetbrain-fonts/JetBrainsMono-Italic.ttf +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/fonts/jetbrain-fonts/JetBrainsMono-Regular.ttf +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/fonts/noto/NotoSans-Bold.ttf +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/fonts/noto/NotoSans-BoldItalic.ttf +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/fonts/noto/NotoSans-Italic.ttf +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/fonts/noto/NotoSans-Regular.ttf +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/ToolMove.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/arrow-down.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/check.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/checked.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/close.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/duplicate.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/error.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/file.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/filedead.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/fit.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/folder.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/grid.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/image.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/info.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/load.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/loop.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/pan.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/pause.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/pencil.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/play.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/plus.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/radio.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/reset.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/save.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/stop.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/tilemap.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/tileset.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/unchecked.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/warning.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/zoomin.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/assets/icons/zoomout.svg +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/cli.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/main.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor/settings.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor.egg-info/dependency_links.txt +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor.egg-info/entry_points.txt +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor.egg-info/requires.txt +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/tilemap_editor.egg-info/top_level.txt +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/ttypes/__init__.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/ttypes/tilemap.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/utils/__init__.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/utils/editor_preference.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/utils/error_handler.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/utils/font_manager.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/utils/history.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/utils/icon_manager.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/utils/icons_cache.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/utils/log_capture.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/utils/project_paths.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/utils/serialization.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/utils/standalone.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/utils/validation.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/__init__.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/automap_models.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/autotile_template.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/autotiler.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/input.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/layer_selector.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/map_properties.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/mapsetup.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/particle_presets.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/particle_system.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/regex_automap_designer.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/tile_grid.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/tile_selector.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/ui/collision_layer_mask.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/ui/collision_layer_sidebar.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/ui/confirm_dialog.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/ui/draw_utils.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/ui/fileinput.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/ui/layer_type_dialog.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/ui/menubar.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/ui/mode_indicator.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/ui/node_editor.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/ui/node_selector.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/ui/notification.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/ui/particle_config_dialog.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/ui/property_editor.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/ui/region_selector.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/ui/status_bar.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/ui/theme.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/ui/tileset_type_dialog.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/ui/toolbar.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/widgets/ui/tooltip.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/tests/test_animation_library_grid_offset.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/tests/test_collision_layer_mask.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/tests/test_collision_layer_sidebar.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/tests/test_editor_pan_mode.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/tests/test_project_paths.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/tests/test_render_scale.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/tests/test_sprite_animation_editor_grid.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/tests/test_tile_grid_selection.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/tests/test_tile_selector_pick.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/tests/test_tilemap_save.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/tests/test_timeline_invalidate_cache.py +0 -0
- {tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/tests/test_toolbar_tools.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.3.
|
|
7
|
+
version = "3.3.5"
|
|
8
8
|
description = "Pygame tilemap editor with SVG icons, professional UI, and sprite animation tools"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -310,11 +310,10 @@ class SpriteAnimationEditor:
|
|
|
310
310
|
)
|
|
311
311
|
elif event.type == pygame.KEYDOWN:
|
|
312
312
|
if event.key == pygame.K_ESCAPE:
|
|
313
|
-
if self.
|
|
314
|
-
self.
|
|
313
|
+
if self._file_manager is not None:
|
|
314
|
+
self.handle_event(event)
|
|
315
315
|
continue
|
|
316
|
-
|
|
317
|
-
if self._file_manager is None:
|
|
316
|
+
if not self.handle_event(event):
|
|
318
317
|
running = False
|
|
319
318
|
continue
|
|
320
319
|
self.handle_event(event)
|
|
@@ -383,6 +382,11 @@ class SpriteAnimationEditor:
|
|
|
383
382
|
self._info_tooltip_pinned = False
|
|
384
383
|
return True
|
|
385
384
|
|
|
385
|
+
# Close dropdown on Escape
|
|
386
|
+
if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE and self._dropdown_open:
|
|
387
|
+
self._dropdown_open = False
|
|
388
|
+
return True
|
|
389
|
+
|
|
386
390
|
if self._meta_panel_open and self._route_meta_panel_event(event):
|
|
387
391
|
return True
|
|
388
392
|
|
|
@@ -427,6 +431,14 @@ class SpriteAnimationEditor:
|
|
|
427
431
|
self._start_rename()
|
|
428
432
|
return True
|
|
429
433
|
|
|
434
|
+
# [ / ]: Cycle animations
|
|
435
|
+
elif event.key == pygame.K_LEFTBRACKET:
|
|
436
|
+
self._cycle_animation(-1)
|
|
437
|
+
return True
|
|
438
|
+
elif event.key == pygame.K_RIGHTBRACKET:
|
|
439
|
+
self._cycle_animation(1)
|
|
440
|
+
return True
|
|
441
|
+
|
|
430
442
|
# Close dropdown on outside click
|
|
431
443
|
if event.type == pygame.MOUSEBUTTONDOWN and self._dropdown_open:
|
|
432
444
|
mouse = pygame.mouse.get_pos()
|
|
@@ -1240,13 +1252,32 @@ class SpriteAnimationEditor:
|
|
|
1240
1252
|
screen.blit(text_surf, (tooltip_rect.x + padding, y))
|
|
1241
1253
|
y += line_height
|
|
1242
1254
|
|
|
1255
|
+
def _compute_dropdown_width(self, names: List[str]) -> int:
|
|
1256
|
+
capped = self._active_anim_name or "(none)"
|
|
1257
|
+
if len(capped) > 16:
|
|
1258
|
+
capped = capped[:14] + ".."
|
|
1259
|
+
cap_w = self._font.size(f" {capped}")[0]
|
|
1260
|
+
max_name_w = 0
|
|
1261
|
+
max_badge_w = 0
|
|
1262
|
+
for name in names:
|
|
1263
|
+
w = self._font.size(name)[0]
|
|
1264
|
+
if w > max_name_w:
|
|
1265
|
+
max_name_w = w
|
|
1266
|
+
anim = self.library.get_animation(name)
|
|
1267
|
+
if anim:
|
|
1268
|
+
bw = self._font_sm.size(f"{anim.frame_count()}f")[0]
|
|
1269
|
+
if bw > max_badge_w:
|
|
1270
|
+
max_badge_w = bw
|
|
1271
|
+
dd_w = max(cap_w, max_name_w + max_badge_w + 24)
|
|
1272
|
+
return min(dd_w, 400)
|
|
1273
|
+
|
|
1243
1274
|
def _draw_dropdown(self, screen: pygame.Surface) -> None:
|
|
1244
1275
|
names = self.library.animation_names()
|
|
1245
1276
|
if not names:
|
|
1246
1277
|
return
|
|
1247
1278
|
|
|
1248
1279
|
item_h = 26
|
|
1249
|
-
dd_w = self.
|
|
1280
|
+
dd_w = self._compute_dropdown_width(names)
|
|
1250
1281
|
dd_h = len(names) * item_h + 4
|
|
1251
1282
|
dd_x = self._btn_anim_selector.x
|
|
1252
1283
|
dd_y = self._btn_anim_selector.bottom + 2
|
|
@@ -1281,7 +1312,7 @@ class SpriteAnimationEditor:
|
|
|
1281
1312
|
screen, _COLORS["dropdown_hover"], item_rect, border_radius=3
|
|
1282
1313
|
)
|
|
1283
1314
|
|
|
1284
|
-
display = name
|
|
1315
|
+
display = name
|
|
1285
1316
|
color = _COLORS["accent"] if is_active else _COLORS["text"]
|
|
1286
1317
|
lbl = self._font.render(display, True, color)
|
|
1287
1318
|
screen.blit(lbl, (item_rect.x + 8, item_rect.y + 4))
|
|
@@ -1458,6 +1489,20 @@ class SpriteAnimationEditor:
|
|
|
1458
1489
|
self.preview.grid_offset_x = gx
|
|
1459
1490
|
self.preview.grid_offset_y = gy
|
|
1460
1491
|
|
|
1492
|
+
def _cycle_animation(self, delta: int) -> None:
|
|
1493
|
+
"""Switch to next (delta=1) or previous (delta=-1) animation."""
|
|
1494
|
+
names = self.library.animation_names()
|
|
1495
|
+
if not names:
|
|
1496
|
+
return
|
|
1497
|
+
if self._active_anim_name is None or self._active_anim_name not in names:
|
|
1498
|
+
idx = 0 if delta > 0 else len(names) - 1
|
|
1499
|
+
else:
|
|
1500
|
+
idx = (names.index(self._active_anim_name) + delta) % len(names)
|
|
1501
|
+
self._active_anim_name = names[idx]
|
|
1502
|
+
self._sync_active_animation()
|
|
1503
|
+
if self._dropdown_open:
|
|
1504
|
+
self._dropdown_hover_idx = idx
|
|
1505
|
+
|
|
1461
1506
|
def _sync_active_animation(self) -> None:
|
|
1462
1507
|
"""Push current animation data to sub-widgets."""
|
|
1463
1508
|
anim = self._get_active()
|
|
@@ -81,9 +81,13 @@ class FramePicker:
|
|
|
81
81
|
self._filter_input_rect = Rect(0, 0, 0, 0)
|
|
82
82
|
self._btn_unused_rect = Rect(0, 0, 0, 0)
|
|
83
83
|
|
|
84
|
-
# Callback fired when user clicks a tile
|
|
84
|
+
# Callback fired when user clicks or drags over a tile
|
|
85
85
|
self.on_frame_clicked: Optional[Callable[[int], None]] = None
|
|
86
86
|
|
|
87
|
+
# Drag selection state
|
|
88
|
+
self._selecting = False
|
|
89
|
+
self._last_drag_idx = -1
|
|
90
|
+
|
|
87
91
|
# Fonts (created lazily so pygame.init() can happen anytime before draw)
|
|
88
92
|
self._font: Optional[pygame.font.Font] = None
|
|
89
93
|
self._font_sm: Optional[pygame.font.Font] = None
|
|
@@ -206,19 +210,39 @@ class FramePicker:
|
|
|
206
210
|
self.offset_y = self._pan_start_offset[1] + dy
|
|
207
211
|
return True
|
|
208
212
|
|
|
209
|
-
# Left-click to select a frame
|
|
213
|
+
# Left-click to select a frame (starts drag for multi-select)
|
|
210
214
|
if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
|
|
211
215
|
if (
|
|
212
216
|
self.rect.collidepoint(mouse)
|
|
213
217
|
and mouse[1] >= self.rect.y + TOP_BAR_TOTAL
|
|
214
218
|
):
|
|
215
219
|
idx = self._index_at(mouse)
|
|
216
|
-
if (
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
220
|
+
if idx >= 0 and self._tile_matches_filter(idx):
|
|
221
|
+
self._selecting = True
|
|
222
|
+
self._last_drag_idx = idx
|
|
223
|
+
if self.on_frame_clicked:
|
|
224
|
+
self.on_frame_clicked(idx)
|
|
225
|
+
return True
|
|
226
|
+
|
|
227
|
+
# Drag to select/deselect multiple frames (toggles like single-click)
|
|
228
|
+
if event.type == pygame.MOUSEMOTION and self._selecting:
|
|
229
|
+
if pygame.mouse.get_pressed()[0]:
|
|
230
|
+
if self.rect.collidepoint(mouse) and mouse[1] >= self.rect.y + TOP_BAR_TOTAL:
|
|
231
|
+
idx = self._index_at(mouse)
|
|
232
|
+
if idx != self._last_drag_idx and idx >= 0 and self._tile_matches_filter(idx):
|
|
233
|
+
self._last_drag_idx = idx
|
|
234
|
+
if self.on_frame_clicked:
|
|
235
|
+
self.on_frame_clicked(idx)
|
|
236
|
+
return True
|
|
237
|
+
else:
|
|
238
|
+
self._selecting = False
|
|
239
|
+
self._last_drag_idx = -1
|
|
240
|
+
|
|
241
|
+
# End drag selection
|
|
242
|
+
if event.type == pygame.MOUSEBUTTONUP and event.button == 1:
|
|
243
|
+
if self._selecting:
|
|
244
|
+
self._selecting = False
|
|
245
|
+
self._last_drag_idx = -1
|
|
222
246
|
return True
|
|
223
247
|
|
|
224
248
|
# Scroll to pan / Ctrl+scroll to zoom
|
|
@@ -149,6 +149,24 @@ class SpriteEditor:
|
|
|
149
149
|
if self.grid.redo():
|
|
150
150
|
self.grid.selected_indices.clear()
|
|
151
151
|
return True
|
|
152
|
+
elif event.key == pygame.K_c:
|
|
153
|
+
if self.grid.has_selection():
|
|
154
|
+
self._clipboard = self.grid.copy_selected()
|
|
155
|
+
self._paste_mode = False
|
|
156
|
+
self.grid.paste_preview_idx = -1
|
|
157
|
+
return True
|
|
158
|
+
elif event.key == pygame.K_x:
|
|
159
|
+
if self.grid.has_selection():
|
|
160
|
+
self._clipboard = self.grid.cut_selected()
|
|
161
|
+
self._paste_mode = False
|
|
162
|
+
self.grid.paste_preview_idx = -1
|
|
163
|
+
return True
|
|
164
|
+
elif event.key == pygame.K_v:
|
|
165
|
+
if self._clipboard:
|
|
166
|
+
self._paste_mode = not self._paste_mode
|
|
167
|
+
if not self._paste_mode:
|
|
168
|
+
self.grid.paste_preview_idx = -1
|
|
169
|
+
return True
|
|
152
170
|
|
|
153
171
|
# Toolbar clicks
|
|
154
172
|
if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
|
|
@@ -156,19 +174,20 @@ class SpriteEditor:
|
|
|
156
174
|
if r.collidepoint(mouse):
|
|
157
175
|
return self._handle_tool(lbl)
|
|
158
176
|
|
|
159
|
-
# Paste mode:
|
|
177
|
+
# Paste mode: click places (or exits if outside grid)
|
|
160
178
|
if self._paste_mode and event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
|
|
161
179
|
idx = self.grid.index_at_pos(mouse)
|
|
162
180
|
if idx >= 0 and self.grid.rect.collidepoint(mouse):
|
|
163
181
|
self.grid.paste_at(idx, self._clipboard)
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
182
|
+
self._paste_mode = False
|
|
183
|
+
self.grid.paste_preview_idx = -1
|
|
184
|
+
return True
|
|
167
185
|
|
|
168
|
-
# Paste preview on hover
|
|
186
|
+
# Paste preview on hover (consume event to prevent grid overlap)
|
|
169
187
|
if self._paste_mode and event.type == pygame.MOUSEMOTION:
|
|
170
188
|
idx = self.grid.index_at_pos(mouse)
|
|
171
189
|
self.grid.paste_preview_idx = idx if idx >= 0 else -1
|
|
190
|
+
return True
|
|
172
191
|
|
|
173
192
|
return self.grid.handle_event(event)
|
|
174
193
|
|
{tilemap_editor-3.3.3 → tilemap_editor-3.3.5}/src/plugins/tileset_collision/collision_painter.py
RENAMED
|
@@ -117,7 +117,8 @@ class CollisionPainter:
|
|
|
117
117
|
self.snap_to_grid = False
|
|
118
118
|
self.grid_size = 8 # pixels
|
|
119
119
|
self.show_grid = True
|
|
120
|
-
self.
|
|
120
|
+
self.show_angle_hints = False # show angle arcs at vertices
|
|
121
|
+
self.edge_draw_mode = False # Ctrl+E - straight line drawing
|
|
121
122
|
self._edge_start: Optional[Tuple[int, int]] = None # Start point for edge draw
|
|
122
123
|
self._shift_held = False
|
|
123
124
|
|
|
@@ -270,7 +271,66 @@ class CollisionPainter:
|
|
|
270
271
|
def get_one_way_flags(self) -> List[bool]:
|
|
271
272
|
"""Get one-way collision flags for all polygons"""
|
|
272
273
|
return list(self.polygon_one_way)
|
|
274
|
+
|
|
275
|
+
def toggle_one_way(self) -> None:
|
|
276
|
+
"""Toggle one-way flag on the selected polygon"""
|
|
277
|
+
idx = self.selected_polygon_idx
|
|
278
|
+
if idx is not None and 0 <= idx < len(self.polygon_one_way):
|
|
279
|
+
self.polygon_one_way[idx] = not self.polygon_one_way[idx]
|
|
280
|
+
if self.on_polygon_modified:
|
|
281
|
+
self.on_polygon_modified(idx)
|
|
273
282
|
|
|
283
|
+
def _get_interior_angle(
|
|
284
|
+
self, polygon: List[Tuple[float, float]], idx: int
|
|
285
|
+
) -> float:
|
|
286
|
+
"""Compute interior angle (degrees) at polygon vertex idx."""
|
|
287
|
+
n = len(polygon)
|
|
288
|
+
if n < 3:
|
|
289
|
+
return 0.0
|
|
290
|
+
prev = polygon[(idx - 1) % n]
|
|
291
|
+
curr = polygon[idx]
|
|
292
|
+
nxt = polygon[(idx + 1) % n]
|
|
293
|
+
v1 = (prev[0] - curr[0], prev[1] - curr[1])
|
|
294
|
+
v2 = (nxt[0] - curr[0], nxt[1] - curr[1])
|
|
295
|
+
dot = v1[0] * v2[0] + v1[1] * v2[1]
|
|
296
|
+
mag1 = math.hypot(v1[0], v1[1])
|
|
297
|
+
mag2 = math.hypot(v2[0], v2[1])
|
|
298
|
+
if mag1 < 0.001 or mag2 < 0.001:
|
|
299
|
+
return 0.0
|
|
300
|
+
cos_a = max(-1.0, min(1.0, dot / (mag1 * mag2)))
|
|
301
|
+
return math.degrees(math.acos(cos_a))
|
|
302
|
+
|
|
303
|
+
def _draw_angle_hint(
|
|
304
|
+
self,
|
|
305
|
+
screen: pygame.Surface,
|
|
306
|
+
screen_pos: Tuple[int, int],
|
|
307
|
+
angle_deg: float,
|
|
308
|
+
) -> None:
|
|
309
|
+
"""Draw a small arc + angle text at a vertex."""
|
|
310
|
+
if angle_deg <= 0.5 or angle_deg >= 179.5:
|
|
311
|
+
return
|
|
312
|
+
arc_radius = 14
|
|
313
|
+
segments = max(4, int(angle_deg / 10))
|
|
314
|
+
angle_rad = math.radians(angle_deg)
|
|
315
|
+
pts = [screen_pos]
|
|
316
|
+
for i in range(segments + 1):
|
|
317
|
+
t = -angle_rad * i / segments
|
|
318
|
+
pts.append((
|
|
319
|
+
screen_pos[0] + arc_radius * math.cos(t),
|
|
320
|
+
screen_pos[1] + arc_radius * math.sin(t),
|
|
321
|
+
))
|
|
322
|
+
if len(pts) >= 3:
|
|
323
|
+
wedge = pygame.Surface(self.rect.size, pygame.SRCALPHA)
|
|
324
|
+
pygame.draw.polygon(wedge, (*_COLORS["polygon_fill"], 60), pts)
|
|
325
|
+
screen.blit(wedge, self.rect.topleft)
|
|
326
|
+
label = self._font_sm.render(f"{angle_deg:.0f}°", True, _COLORS["text"])
|
|
327
|
+
label_x = screen_pos[0] + 10
|
|
328
|
+
label_y = screen_pos[1] - label.get_height() - 4
|
|
329
|
+
bg = pygame.Surface((label.get_width() + 4, label.get_height() + 2), pygame.SRCALPHA)
|
|
330
|
+
bg.fill((0, 0, 0, 180))
|
|
331
|
+
screen.blit(bg, (label_x - 2, label_y - 1))
|
|
332
|
+
screen.blit(label, (label_x, label_y))
|
|
333
|
+
|
|
274
334
|
def clear_all(self) -> None:
|
|
275
335
|
"""Clear all polygons"""
|
|
276
336
|
self.polygons = []
|
|
@@ -488,23 +548,26 @@ class CollisionPainter:
|
|
|
488
548
|
|
|
489
549
|
# Keyboard shortcuts
|
|
490
550
|
if event.type == pygame.KEYDOWN:
|
|
551
|
+
mods = pygame.key.get_mods()
|
|
552
|
+
ctrl = mods & (pygame.KMOD_LCTRL | pygame.KMOD_LMETA)
|
|
553
|
+
|
|
491
554
|
# Track Shift state for edge draw
|
|
492
555
|
if event.key in (pygame.K_LSHIFT, pygame.K_RSHIFT):
|
|
493
556
|
self._shift_held = True
|
|
494
|
-
|
|
495
|
-
# E - toggle edge draw mode
|
|
496
|
-
elif event.key == pygame.K_e:
|
|
557
|
+
|
|
558
|
+
# Ctrl+E - toggle edge draw mode
|
|
559
|
+
elif ctrl and event.key == pygame.K_e:
|
|
497
560
|
self.edge_draw_mode = not self.edge_draw_mode
|
|
498
561
|
if not self.edge_draw_mode:
|
|
499
562
|
self._edge_start = None
|
|
500
563
|
return True
|
|
501
|
-
|
|
564
|
+
|
|
502
565
|
# Enter - complete polygon
|
|
503
566
|
elif event.key in (pygame.K_RETURN, pygame.K_KP_ENTER):
|
|
504
567
|
if len(self.current_polygon) >= 3:
|
|
505
568
|
self._complete_polygon()
|
|
506
569
|
return True
|
|
507
|
-
|
|
570
|
+
|
|
508
571
|
# Escape - cancel current polygon or deselect or close help
|
|
509
572
|
elif event.key == pygame.K_ESCAPE:
|
|
510
573
|
if self.show_help:
|
|
@@ -517,44 +580,45 @@ class CollisionPainter:
|
|
|
517
580
|
self.selected_polygon_idx = None
|
|
518
581
|
self.selected_vertex_idx = None
|
|
519
582
|
return True
|
|
520
|
-
|
|
521
|
-
# Delete - remove selected polygon
|
|
583
|
+
|
|
584
|
+
# Delete/Backspace - remove selected polygon or last vertex
|
|
522
585
|
elif event.key in (pygame.K_DELETE, pygame.K_BACKSPACE):
|
|
523
586
|
if self.selected_polygon_idx is not None:
|
|
524
587
|
self._delete_polygon(self.selected_polygon_idx)
|
|
525
588
|
return True
|
|
526
589
|
elif self.current_polygon:
|
|
527
|
-
# Remove last vertex
|
|
528
590
|
self.current_polygon.pop()
|
|
529
591
|
return True
|
|
530
|
-
|
|
531
|
-
# G - toggle grid
|
|
532
|
-
elif event.key == pygame.K_g:
|
|
592
|
+
|
|
593
|
+
# Ctrl+G - toggle grid
|
|
594
|
+
elif ctrl and event.key == pygame.K_g:
|
|
533
595
|
self.show_grid = not self.show_grid
|
|
534
596
|
return True
|
|
535
|
-
|
|
536
|
-
# S - toggle snap (
|
|
537
|
-
elif event.key == pygame.K_s:
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
# O - toggle one-way collision for selected polygon
|
|
545
|
-
elif event.key == pygame.K_o:
|
|
597
|
+
|
|
598
|
+
# Ctrl+Shift+S - toggle snap (Ctrl+S reserved for save)
|
|
599
|
+
elif ctrl and event.key == pygame.K_s and (mods & pygame.KMOD_SHIFT):
|
|
600
|
+
self.snap_to_grid = not self.snap_to_grid
|
|
601
|
+
return True
|
|
602
|
+
|
|
603
|
+
# Ctrl+O - toggle one-way collision for selected polygon
|
|
604
|
+
elif ctrl and event.key == pygame.K_o:
|
|
546
605
|
if self.selected_polygon_idx is not None:
|
|
547
606
|
idx = self.selected_polygon_idx
|
|
548
607
|
self.polygon_one_way[idx] = not self.polygon_one_way[idx]
|
|
549
608
|
if self.on_polygon_modified:
|
|
550
609
|
self.on_polygon_modified(idx)
|
|
551
610
|
return True
|
|
552
|
-
|
|
553
|
-
# R - reset view
|
|
554
|
-
elif event.key == pygame.K_r:
|
|
611
|
+
|
|
612
|
+
# Ctrl+R - reset view
|
|
613
|
+
elif ctrl and event.key == pygame.K_r:
|
|
555
614
|
self._center_view()
|
|
556
615
|
self.zoom = 2.0
|
|
557
616
|
return True
|
|
617
|
+
|
|
618
|
+
# Ctrl+A - toggle angle hints
|
|
619
|
+
elif ctrl and event.key == pygame.K_a:
|
|
620
|
+
self.show_angle_hints = not self.show_angle_hints
|
|
621
|
+
return True
|
|
558
622
|
|
|
559
623
|
if event.type == pygame.KEYUP:
|
|
560
624
|
if event.key in (pygame.K_LSHIFT, pygame.K_RSHIFT):
|
|
@@ -744,20 +808,21 @@ class CollisionPainter:
|
|
|
744
808
|
("Right-click / Enter", "Complete polygon"),
|
|
745
809
|
("Escape", "Cancel current polygon"),
|
|
746
810
|
("Shift (edge mode)", "Constrain to axis"),
|
|
747
|
-
("E", "Toggle edge draw mode"),
|
|
811
|
+
("Ctrl+E", "Toggle edge draw mode"),
|
|
748
812
|
]),
|
|
749
813
|
("SELECTION", [
|
|
750
814
|
("Left-click polygon", "Select polygon"),
|
|
751
815
|
("Left-click vertex", "Select & drag vertex"),
|
|
752
816
|
("Delete / Backspace", "Remove selected polygon"),
|
|
753
|
-
("O", "Toggle one-way collision"),
|
|
817
|
+
("Ctrl+O", "Toggle one-way collision"),
|
|
754
818
|
]),
|
|
755
819
|
("VIEW", [
|
|
756
820
|
("Mouse wheel", "Zoom in/out"),
|
|
757
821
|
("Middle mouse / Space+LMB", "Pan view"),
|
|
758
|
-
("G", "Toggle grid"),
|
|
759
|
-
("S", "Toggle snap to grid"),
|
|
760
|
-
("R", "Reset view"),
|
|
822
|
+
("Ctrl+G", "Toggle grid"),
|
|
823
|
+
("Ctrl+Shift+S", "Toggle snap to grid"),
|
|
824
|
+
("Ctrl+R", "Reset view"),
|
|
825
|
+
("Ctrl+A", "Toggle angle hints"),
|
|
761
826
|
]),
|
|
762
827
|
("MISC", [
|
|
763
828
|
("Info button (top-right)", "Toggle this help panel"),
|
|
@@ -905,6 +970,24 @@ class CollisionPainter:
|
|
|
905
970
|
radius = VERTEX_HOVER_RADIUS if is_hovered_vertex else VERTEX_RADIUS
|
|
906
971
|
pygame.draw.circle(screen, color, (px, py), radius)
|
|
907
972
|
pygame.draw.circle(screen, (0, 0, 0), (px, py), radius, 1)
|
|
973
|
+
|
|
974
|
+
# One-way badge on selected polygon
|
|
975
|
+
if selected and one_way and len(polygon) >= 3:
|
|
976
|
+
cx = int(sum(p[0] for p in screen_points) / len(screen_points))
|
|
977
|
+
cy = int(sum(p[1] for p in screen_points) / len(screen_points))
|
|
978
|
+
badge = self._font_sm.render("ONE-WAY", True, (255, 255, 255))
|
|
979
|
+
bx = cx - badge.get_width() // 2
|
|
980
|
+
by = cy - badge.get_height() // 2
|
|
981
|
+
bg = pygame.Surface((badge.get_width() + 8, badge.get_height() + 4), pygame.SRCALPHA)
|
|
982
|
+
bg.fill((_COLORS["one_way"][0], _COLORS["one_way"][1], _COLORS["one_way"][2], 200))
|
|
983
|
+
screen.blit(bg, (bx - 4, by - 2))
|
|
984
|
+
screen.blit(badge, (bx, by))
|
|
985
|
+
|
|
986
|
+
# Angle hints
|
|
987
|
+
if self.show_angle_hints:
|
|
988
|
+
for i, (px, py) in enumerate(screen_points):
|
|
989
|
+
angle = self._get_interior_angle(polygon, i)
|
|
990
|
+
self._draw_angle_hint(screen, (px, py), angle)
|
|
908
991
|
|
|
909
992
|
def _draw_current_polygon(self, screen: pygame.Surface) -> None:
|
|
910
993
|
"""Draw the polygon currently being drawn"""
|
|
@@ -934,6 +1017,25 @@ class CollisionPainter:
|
|
|
934
1017
|
radius = VERTEX_HOVER_RADIUS if is_first else VERTEX_RADIUS
|
|
935
1018
|
pygame.draw.circle(screen, color, (px, py), radius)
|
|
936
1019
|
pygame.draw.circle(screen, (0, 0, 0), (px, py), radius, 1)
|
|
1020
|
+
|
|
1021
|
+
# Angle hints for vertices with both neighbors
|
|
1022
|
+
if self.show_angle_hints and len(self.current_polygon) >= 3:
|
|
1023
|
+
for i, (px, py) in enumerate(screen_points):
|
|
1024
|
+
if i == 0 or i == len(screen_points) - 1:
|
|
1025
|
+
continue
|
|
1026
|
+
poly = self.current_polygon
|
|
1027
|
+
prev = poly[i - 1]
|
|
1028
|
+
curr = poly[i]
|
|
1029
|
+
nxt = poly[i + 1]
|
|
1030
|
+
v1 = (prev[0] - curr[0], prev[1] - curr[1])
|
|
1031
|
+
v2 = (nxt[0] - curr[0], nxt[1] - curr[1])
|
|
1032
|
+
dot = v1[0] * v2[0] + v1[1] * v2[1]
|
|
1033
|
+
mag1 = math.hypot(v1[0], v1[1])
|
|
1034
|
+
mag2 = math.hypot(v2[0], v2[1])
|
|
1035
|
+
if mag1 > 0.001 and mag2 > 0.001:
|
|
1036
|
+
cos_a = max(-1.0, min(1.0, dot / (mag1 * mag2)))
|
|
1037
|
+
angle = math.degrees(math.acos(cos_a))
|
|
1038
|
+
self._draw_angle_hint(screen, (px, py), angle)
|
|
937
1039
|
|
|
938
1040
|
def _draw_status(self, screen: pygame.Surface) -> None:
|
|
939
1041
|
"""Draw status text"""
|
|
@@ -949,12 +1051,13 @@ class CollisionPainter:
|
|
|
949
1051
|
one_way = self.polygon_one_way[self.selected_polygon_idx]
|
|
950
1052
|
one_way_str = " (ONE-WAY)" if one_way else ""
|
|
951
1053
|
lines.append(f"Selected polygon {self.selected_polygon_idx}{one_way_str}")
|
|
952
|
-
lines.append("Delete to remove, O to toggle one-way")
|
|
1054
|
+
lines.append("Delete to remove, Ctrl+O to toggle one-way")
|
|
953
1055
|
else:
|
|
954
1056
|
lines.append("Click to add vertices, right-click to complete")
|
|
955
1057
|
|
|
956
|
-
|
|
957
|
-
lines.append(f"
|
|
1058
|
+
snap_str = f"Snap: {'ON' if self.snap_to_grid else 'OFF'} (Ctrl+Shift+S)"
|
|
1059
|
+
lines.append(f"Zoom: {self.zoom:.1f}x | Grid: {'ON' if self.show_grid else 'OFF'} (Ctrl+G) | {snap_str}")
|
|
1060
|
+
lines.append(f"Polygons: {len(self.polygons)} | Ctrl+R: reset | Ctrl+A: angle hints {'ON' if self.show_angle_hints else 'OFF'}")
|
|
958
1061
|
|
|
959
1062
|
y = self.rect.y + 5
|
|
960
1063
|
for line in lines:
|