From d37e89717e6a6d9b0fa8cabb00c022a45d7ebc6d Mon Sep 17 00:00:00 2001
From: Mew Pur Pur <85438892+MewPurPur@users.noreply.github.com>
Date: Sat, 15 Mar 2025 16:33:23 +0200
Subject: [PATCH 1/2] Update from upstream and some minor fixes
Fix tab closing on web (1239)
added translation for tab bar settings (1242)
Fix CTRL + MMB zooming not keeping center at starting location (1243)
Fix opening SVGs via the CLI crashing GodSVG (1244)
Remove unused modules from third party components (1245)
Fix tabbar error (1246)
Add grid color setting (1247)
Implement quit as a shortcut (1248)
Move to Godot 4.4.1 (1250)
Make UI actions modifiable (1251)
Make Select All work for text (1252)
Fix some issues with shortcuts (1253)
filled in missing or fuzzy dutch translations (1258)
Rework shortcut handling (1259)
Fix unsaved initial tab (1260)
Simplify native menu logic (1261)
Reorganize shortcut logic in HandlerGUI (1262)
Fix MacOS checked items not updating (1263)
Minor codebase improvements (1264)
Add tab previews for unsaved files (1265)
Co-Authored-By: Mew Pur Pur <85438892+MewPurPur@users.noreply.github.com>
---
assets/icons/Quit.svg | 1 +
assets/icons/Quit.svg.import | 37 ++
assets/icons/Snap.svg | 2 +-
godot_only/icons/BetterButton.svg | 1 +
godot_only/icons/BetterButton.svg.import | 37 ++
project.godot | 11 -
src/autoload/Configs.gd | 8 +-
src/autoload/HandlerGUI.gd | 209 ++++++++---
src/autoload/State.gd | 175 +++++----
src/config_classes/SaveData.gd | 22 +-
src/config_classes/TabData.gd | 13 +-
src/ui_parts/about_menu.gd | 69 +++-
src/ui_parts/code_editor.gd | 4 +-
src/ui_parts/current_file_button.gd | 25 +-
src/ui_parts/display.gd | 98 ++---
src/ui_parts/display.tscn | 3 +-
src/ui_parts/export_menu.gd | 4 +-
src/ui_parts/global_actions.gd | 33 +-
src/ui_parts/global_actions.tscn | 14 +-
src/ui_parts/good_file_dialog.gd | 9 +-
src/ui_parts/handles_manager.gd | 1 +
src/ui_parts/mac_menu.gd | 140 +++----
src/ui_parts/root_element_editor.gd | 6 +-
src/ui_parts/root_element_editor.tscn | 2 +-
src/ui_parts/settings_menu.gd | 81 +----
src/ui_parts/shortcut_panel.gd | 2 +-
src/ui_parts/shortcut_panel_config.gd | 8 +-
src/ui_parts/tab_bar.gd | 67 ++--
src/ui_parts/viewport.gd | 4 +-
src/ui_parts/zoom_menu.gd | 15 +-
src/ui_parts/zoom_menu.tscn | 33 +-
src/ui_widgets/BetterButton.gd | 98 +++++
...oggleButton.gd.uid => BetterButton.gd.uid} | 0
src/ui_widgets/BetterLineEdit.gd | 41 ++-
src/ui_widgets/BetterTextEdit.gd | 44 +--
src/ui_widgets/BetterToggleButton.gd | 31 --
src/ui_widgets/ContextPopup.gd | 341 ++++++++++--------
src/ui_widgets/UndoRedoRef.gd | 2 +-
src/ui_widgets/camera.gd | 16 +-
src/ui_widgets/dropdown.tscn | 1 +
src/ui_widgets/good_color_picker.gd | 16 +-
src/ui_widgets/number_dropdown.tscn | 1 +
src/ui_widgets/path_command_button.gd | 3 +-
src/ui_widgets/pathdata_field.gd | 6 +-
src/ui_widgets/presented_shortcut.gd | 6 +-
src/ui_widgets/setting_shortcut.gd | 2 +-
src/ui_widgets/transform_popup.gd | 10 +-
src/utils/FileUtils.gd | 32 +-
src/utils/ShortcutUtils.gd | 149 ++++----
src/utils/TranslationUtils.gd | 147 ++++----
translations/nl.po | 127 ++++---
translations/ru.po | 8 +
52 files changed, 1201 insertions(+), 1014 deletions(-)
create mode 100644 assets/icons/Quit.svg
create mode 100644 assets/icons/Quit.svg.import
create mode 100644 godot_only/icons/BetterButton.svg
create mode 100644 godot_only/icons/BetterButton.svg.import
create mode 100644 src/ui_widgets/BetterButton.gd
rename src/ui_widgets/{BetterToggleButton.gd.uid => BetterButton.gd.uid} (100%)
delete mode 100644 src/ui_widgets/BetterToggleButton.gd
diff --git a/assets/icons/Quit.svg b/assets/icons/Quit.svg
new file mode 100644
index 0000000..ee8b477
--- /dev/null
+++ b/assets/icons/Quit.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/icons/Quit.svg.import b/assets/icons/Quit.svg.import
new file mode 100644
index 0000000..c7fefa3
--- /dev/null
+++ b/assets/icons/Quit.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cbkrorp0b7qgb"
+path="res://.godot/imported/Quit.svg-27ce495cfd421ac10bdab78eb7fc0164.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/icons/Quit.svg"
+dest_files=["res://.godot/imported/Quit.svg-27ce495cfd421ac10bdab78eb7fc0164.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/assets/icons/Snap.svg b/assets/icons/Snap.svg
index 3e13d62..8edc101 100644
--- a/assets/icons/Snap.svg
+++ b/assets/icons/Snap.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
diff --git a/godot_only/icons/BetterButton.svg b/godot_only/icons/BetterButton.svg
new file mode 100644
index 0000000..e789cb4
--- /dev/null
+++ b/godot_only/icons/BetterButton.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/godot_only/icons/BetterButton.svg.import b/godot_only/icons/BetterButton.svg.import
new file mode 100644
index 0000000..8591340
--- /dev/null
+++ b/godot_only/icons/BetterButton.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dvqpk5nveft8r"
+path="res://.godot/imported/BetterButton.svg-05d13469c50d2eb36ca24ebd664433ac.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://godot_only/icons/BetterButton.svg"
+dest_files=["res://.godot/imported/BetterButton.svg-05d13469c50d2eb36ca24ebd664433ac.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/project.godot b/project.godot
index 6a85d07..3bf64ac 100644
--- a/project.godot
+++ b/project.godot
@@ -159,17 +159,6 @@ move_down={
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"command_or_control_autoremap":true,"alt_pressed":false,"shift_pressed":false,"pressed":false,"keycode":4194322,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
-undo={
-"deadzone": 0.2,
-"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"command_or_control_autoremap":true,"alt_pressed":false,"shift_pressed":false,"pressed":false,"keycode":90,"physical_keycode":0,"key_label":0,"unicode":122,"location":0,"echo":false,"script":null)
-]
-}
-redo={
-"deadzone": 0.2,
-"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"command_or_control_autoremap":true,"alt_pressed":false,"shift_pressed":true,"pressed":false,"keycode":90,"physical_keycode":0,"key_label":0,"unicode":90,"location":0,"echo":false,"script":null)
-, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"command_or_control_autoremap":true,"alt_pressed":false,"shift_pressed":false,"pressed":false,"keycode":89,"physical_keycode":0,"key_label":0,"unicode":121,"location":0,"echo":false,"script":null)
-]
-}
duplicate={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"command_or_control_autoremap":true,"alt_pressed":false,"shift_pressed":false,"pressed":false,"keycode":68,"physical_keycode":0,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null)
diff --git a/src/autoload/Configs.gd b/src/autoload/Configs.gd
index 010e37f..8ced14a 100644
--- a/src/autoload/Configs.gd
+++ b/src/autoload/Configs.gd
@@ -18,10 +18,14 @@ signal basic_colors_changed
@warning_ignore("unused_signal")
signal handle_visuals_changed
@warning_ignore("unused_signal")
+signal grid_color_changed
+@warning_ignore("unused_signal")
signal shortcut_panel_changed
@warning_ignore("unused_signal")
signal active_tab_status_changed
@warning_ignore("unused_signal")
+signal active_tab_reference_changed
+@warning_ignore("unused_signal")
signal active_tab_changed
@warning_ignore("unused_signal")
signal tabs_changed
@@ -43,7 +47,7 @@ var default_shortcuts: Dictionary[String, Array] = {}
func _enter_tree() -> void:
# Fill up the default shortcuts dictionary before the shortcuts are loaded.
- for action in ShortcutUtils.get_all_shortcuts():
+ for action in ShortcutUtils.get_all_actions():
if InputMap.has_action(action):
default_shortcuts[action] = InputMap.action_get_events(action)
load_config()
@@ -69,7 +73,7 @@ func reset_settings() -> void:
savedata = SaveData.new()
savedata.reset_to_default()
savedata.language = "en"
- savedata.set_shortcut_panel_slots({ 0: "undo", 1: "redo", 2: "save"})
+ savedata.set_shortcut_panel_slots({ 0: "ui_undo", 1: "ui_redo" })
savedata.set_palettes([Palette.new("Pure", Palette.Preset.PURE)])
save()
diff --git a/src/autoload/HandlerGUI.gd b/src/autoload/HandlerGUI.gd
index 2f9b175..05dd393 100644
--- a/src/autoload/HandlerGUI.gd
+++ b/src/autoload/HandlerGUI.gd
@@ -196,14 +196,6 @@ func _parse_popup_overlay_event(event: InputEvent) -> void:
var last_mouse_click_double := false
func _input(event: InputEvent) -> void:
- if ShortcutUtils.is_action_pressed(event, "quit"):
- remove_all_menus()
- var confirm_dialog := ConfirmDialogScene.instantiate()
- add_menu(confirm_dialog)
- confirm_dialog.setup(Translator.translate("Quit GodSVG"),
- Translator.translate("Do you want to quit GodSVG?"),
- Translator.translate("Quit"), get_tree().quit)
-
# So, it turns out that when you double click, only the press will count as such.
# I don't like that, and it causes problems! So mark the release as double_click too.
# TODO Godot PR #92582 fixes this.
@@ -213,63 +205,142 @@ func _input(event: InputEvent) -> void:
elif last_mouse_click_double and event.is_released():
event.double_click = true
last_mouse_click_double = false
-
- # Stuff that should replace the existing overlays, or that opens separate windows.
- const CONST_ARR_1: PackedStringArray = ["about_info", "about_donate", "check_updates",
- "open_settings", "open_externally", "open_in_folder"]
- for action in CONST_ARR_1:
- if ShortcutUtils.is_action_pressed(event, action):
- remove_all_menus()
+
+
+func _unhandled_input(event: InputEvent) -> void:
+ # Clear popups or overlays.
+ if ShortcutUtils.is_action_pressed(event, "ui_cancel"):
+ if not popup_stack.is_empty():
+ get_viewport().set_input_as_handled()
+ remove_popup()
+ return
+ elif not menu_stack.is_empty():
get_viewport().set_input_as_handled()
- ShortcutUtils.fn_call(action)
+ _remove_control()
return
- # Stuff that links externally.
- const CONST_ARR_2: PackedStringArray = ["about_repo", "about_website"]
- for action in CONST_ARR_2:
+ for action in ShortcutUtils.UNIVERSAL_ACTIONS:
if ShortcutUtils.is_action_pressed(event, action):
- get_viewport().set_input_as_handled()
- ShortcutUtils.fn_call(action)
+ match action:
+ "quit": prompt_quit()
+ "about_info": open_about()
+ "about_donate": open_donate()
+ "check_updates": open_update_checker()
+ "open_settings": open_settings()
+ "about_repo": OS.shell_open("https://github.com/syntaxerror247/GodSVG-Mobile")
+ "about_website": OS.shell_open("https://godsvg.com")
+ "open_externally": FileUtils.open_svg(
+ Configs.savedata.get_active_tab().svg_file_path)
+ "open_in_folder": FileUtils.open_svg_folder(
+ Configs.savedata.get_active_tab().svg_file_path)
return
- # Stop the logic below from running if there's overlays.
- if not popup_stack.is_empty() or not menu_stack.is_empty():
+ # Stop the logic below from running if there's menu overlays.
+ if not menu_stack.is_empty():
return
- # Global actions that should happen regardless of the context.
- const CONST_ARR_3: PackedStringArray = ["import", "export", "save", "save_as",
- "close_tab", "close_tabs_to_left", "close_tabs_to_right", "close_all_other_tabs",
- "new_tab", "select_next_tab", "select_previous_tab", "copy_svg_text", "optimize",
- "reset_svg"]
- for action in CONST_ARR_3:
+ for action in ShortcutUtils.EFFECT_ACTIONS:
if ShortcutUtils.is_action_pressed(event, action):
- get_viewport().set_input_as_handled()
- ShortcutUtils.fn_call(action)
-
-func _unhandled_input(event: InputEvent) -> void:
- # Clear popups or overlays.
- if not popup_stack.is_empty() and event.is_action_pressed("ui_cancel"):
- get_viewport().set_input_as_handled()
- remove_popup()
- return
- elif not menu_stack.is_empty() and event.is_action_pressed("ui_cancel"):
- get_viewport().set_input_as_handled()
- _remove_control()
+ match action:
+ "view_show_grid": State.toggle_show_grid()
+ "view_show_handles": State.toggle_show_handles()
+ "view_rasterized_svg": State.toggle_view_rasterized()
+ "view_show_reference": State.toggle_show_reference()
+ "view_overlay_reference": State.toggle_overlay_reference()
+ "load_reference": FileUtils.open_image_import_dialog()
+ "toggle_snap": Configs.savedata.snap *= -1
+
+ if not popup_stack.is_empty():
return
- if not popup_stack.is_empty() or not menu_stack.is_empty() or\
- get_viewport().gui_is_dragging():
+ # Global actions that should happen regardless of the context.
+ for action in ShortcutUtils.EDITOR_ACTIONS:
+ if ShortcutUtils.is_action_pressed(event, action):
+ match action:
+ "import": FileUtils.open_svg_import_dialog()
+ "export": open_export()
+ "save": FileUtils.save_svg()
+ "save_as": FileUtils.save_svg_as()
+ "close_tab": FileUtils.close_tabs(Configs.savedata.get_active_tab_index())
+ "close_tabs_to_left": FileUtils.close_tabs(
+ Configs.savedata.get_active_tab_index(),
+ FileUtils.TabCloseMode.TO_LEFT)
+ "close_tabs_to_right": FileUtils.close_tabs(
+ Configs.savedata.get_active_tab_index(),
+ FileUtils.TabCloseMode.TO_RIGHT)
+ "close_all_other_tabs": FileUtils.close_tabs(
+ Configs.savedata.get_active_tab_index(),
+ FileUtils.TabCloseMode.ALL_OTHERS)
+ "new_tab": Configs.savedata.add_empty_tab()
+ "select_next_tab": Configs.savedata.set_active_tab_index(
+ posmod(Configs.savedata.get_active_tab_index() + 1,
+ Configs.savedata.get_tab_count()))
+ "select_previous_tab": Configs.savedata.set_active_tab_index(
+ posmod(Configs.savedata.get_active_tab_index() - 1,
+ Configs.savedata.get_tab_count()))
+ "copy_svg_text": DisplayServer.clipboard_set(State.svg_text)
+ "optimize": State.optimize()
+ "reset_svg": FileUtils.reset_svg()
+ "debug": State.toggle_show_debug()
+ return
+
+ # Stop the logic below from running while GUI dragging is going on.
+ if get_viewport().gui_is_dragging():
return
- const CONST_ARR: PackedStringArray = ["redo", "undo", "ui_cancel", "delete", "move_up",
- "move_down", "duplicate", "select_all"]
- for action in CONST_ARR:
+ for action in ShortcutUtils.PRISTINE_ACTIONS:
if ShortcutUtils.is_action_pressed(event, action):
- get_viewport().set_input_as_handled()
- ShortcutUtils.fn_call(action)
+ match action:
+ "ui_undo": Configs.savedata.get_active_tab().undo()
+ "ui_redo": Configs.savedata.get_active_tab().redo()
+ "ui_cancel": State.clear_all_selections()
+ "delete": State.delete_selected()
+ "move_up": State.move_up_selected()
+ "move_down": State.move_down_selected()
+ "duplicate": State.duplicate_selected()
+ "select_all": State.select_all()
return
- if event is InputEventKey:
- State.respond_to_key_input(event)
+
+ if ShortcutUtils.is_action_pressed(event, "move_absolute"):
+ State.respond_to_key_input("M")
+ elif ShortcutUtils.is_action_pressed(event, "move_relative"):
+ State.respond_to_key_input("m")
+ elif ShortcutUtils.is_action_pressed(event, "line_absolute"):
+ State.respond_to_key_input("L")
+ elif ShortcutUtils.is_action_pressed(event, "line_relative"):
+ State.respond_to_key_input("l")
+ elif ShortcutUtils.is_action_pressed(event, "horizontal_line_absolute"):
+ State.respond_to_key_input("H")
+ elif ShortcutUtils.is_action_pressed(event, "horizontal_line_relative"):
+ State.respond_to_key_input("h")
+ elif ShortcutUtils.is_action_pressed(event, "vertical_line_absolute"):
+ State.respond_to_key_input("V")
+ elif ShortcutUtils.is_action_pressed(event, "vertical_line_relative"):
+ State.respond_to_key_input("v")
+ elif ShortcutUtils.is_action_pressed(event, "close_path_absolute"):
+ State.respond_to_key_input("Z")
+ elif ShortcutUtils.is_action_pressed(event, "close_path_relative"):
+ State.respond_to_key_input("z")
+ elif ShortcutUtils.is_action_pressed(event, "elliptical_arc_absolute"):
+ State.respond_to_key_input("A")
+ elif ShortcutUtils.is_action_pressed(event, "elliptical_arc_relative"):
+ State.respond_to_key_input("a")
+ elif ShortcutUtils.is_action_pressed(event, "cubic_bezier_absolute"):
+ State.respond_to_key_input("C")
+ elif ShortcutUtils.is_action_pressed(event, "cubic_bezier_relative"):
+ State.respond_to_key_input("c")
+ elif ShortcutUtils.is_action_pressed(event, "shorthand_cubic_bezier_absolute"):
+ State.respond_to_key_input("S")
+ elif ShortcutUtils.is_action_pressed(event, "shorthand_cubic_bezier_relative"):
+ State.respond_to_key_input("s")
+ elif ShortcutUtils.is_action_pressed(event, "quadratic_bezier_absolute"):
+ State.respond_to_key_input("Q")
+ elif ShortcutUtils.is_action_pressed(event, "quadratic_bezier_relative"):
+ State.respond_to_key_input("q")
+ elif ShortcutUtils.is_action_pressed(event, "shorthand_quadratic_bezier_absolute"):
+ State.respond_to_key_input("T")
+ elif ShortcutUtils.is_action_pressed(event, "shorthand_quadratic_bezier_relative"):
+ State.respond_to_key_input("t")
func get_window_default_size() -> Vector2i:
@@ -322,7 +393,16 @@ func update_ui_scale() -> void:
window.content_scale_factor = final_scale
+func prompt_quit() -> void:
+ remove_all_menus()
+ var confirm_dialog := ConfirmDialogScene.instantiate()
+ add_menu(confirm_dialog)
+ confirm_dialog.setup(Translator.translate("Quit GodSVG"),
+ Translator.translate("Do you want to quit GodSVG?"),
+ Translator.translate("Quit"), get_tree().quit)
+
func open_update_checker() -> void:
+ remove_all_menus()
var confirmation_dialog := ConfirmDialogScene.instantiate()
add_menu(confirmation_dialog)
confirmation_dialog.setup(Translator.translate("Check for updates?"),
@@ -335,15 +415,19 @@ func _list_updates() -> void:
add_menu(update_menu_instance)
func open_settings() -> void:
+ remove_all_menus()
add_menu(SettingsMenuScene.instantiate())
func open_about() -> void:
+ remove_all_menus()
add_menu(AboutMenuScene.instantiate())
func open_donate() -> void:
+ remove_all_menus()
add_menu(DonateMenuScene.instantiate())
func open_export() -> void:
+ remove_all_menus()
var width := State.root_element.width
var height := State.root_element.height
@@ -409,3 +493,28 @@ func throw_mouse_motion_event() -> void:
# Must multiply by the final transform because the InputEvent is not yet parsed.
mm_event.position = window.get_mouse_position() * window.get_final_transform()
Input.parse_input_event.call_deferred(mm_event)
+
+# Trigger a shortcut automatically.
+func throw_action_event(action: String) -> void:
+ var events := InputMap.action_get_events(action)
+ for event in events:
+ if ShortcutUtils.is_shortcut_valid(event, action):
+ # Pressed keys.
+ var press_key_event := event.duplicate()
+ press_key_event.pressed = true
+ Input.parse_input_event(press_key_event)
+ # Released keys.
+ var release_key_event := press_key_event.duplicate()
+ release_key_event.pressed = false
+ Input.parse_input_event(release_key_event)
+ return
+
+ # Pressed action.
+ var press_action_event := InputEventAction.new()
+ press_action_event.action = action
+ press_action_event.pressed = true
+ Input.parse_input_event.call_deferred(press_action_event)
+ # Released action.
+ var release_action_event := InputEventAction.new()
+ release_action_event.action = action
+ Input.parse_input_event.call_deferred(release_action_event)
diff --git a/src/autoload/State.gd b/src/autoload/State.gd
index df73122..42327b9 100644
--- a/src/autoload/State.gd
+++ b/src/autoload/State.gd
@@ -4,19 +4,6 @@ extends Node
const OptionsDialogScene = preload("res://src/ui_widgets/options_dialog.tscn")
const PathCommandPopupScene = preload("res://src/ui_widgets/path_popup.tscn")
-const path_actions_dict: Dictionary[String, String] = {
- "move_absolute": "M", "move_relative": "m",
- "line_absolute": "L", "line_relative": "l",
- "horizontal_line_absolute": "H", "horizontal_line_relative": "h",
- "vertical_line_absolute": "V", "vertical_line_relative": "v",
- "close_path_absolute": "Z", "close_path_relative": "z",
- "elliptical_arc_absolute": "A", "elliptical_arc_relative": "a",
- "cubic_bezier_absolute": "C", "cubic_bezier_relative": "c",
- "shorthand_cubic_bezier_absolute": "S", "shorthand_cubic_bezier_relative": "s",
- "quadratic_bezier_absolute": "Q", "quadratic_bezier_relative": "q",
- "shorthand_quadratic_bezier_absolute": "T", "shorthand_quadratic_bezier_relative": "t"
-}
-
signal svg_unknown_change
signal svg_resized
@@ -78,11 +65,22 @@ func _enter_tree() -> void:
setup_from_tab.call_deferred() # Let everything load before emitting signals.
var cmdline_args := OS.get_cmdline_args()
- if not (OS.is_debug_build() and not OS.has_feature("template")) and\
- cmdline_args.size() >= 1:
- await get_tree().ready # Ensures we can add warning panels.
- FileUtils.apply_svg_from_path(cmdline_args[0])
-
+
+ # The first argument passed is always a path to the scene file when in-editor.
+ if (OS.is_debug_build() and not OS.has_feature("template")) and cmdline_args.size() >= 1:
+ cmdline_args.remove_at(0)
+
+ if cmdline_args.size() >= 1:
+ # Need to wait a frame so the import warnings panel can be added.
+ await get_tree().process_frame
+
+ var used_tab_paths := PackedStringArray()
+ for tab in Configs.savedata.get_tabs():
+ used_tab_paths.append(tab.svg_file_path)
+
+ for path in cmdline_args:
+ if path.get_extension() == "svg" and not path in used_tab_paths:
+ FileUtils.apply_svg_from_path(path)
func setup_from_tab() -> void:
var active_tab := Configs.savedata.get_active_tab()
@@ -265,25 +263,40 @@ func set_viewport_size(new_value: Vector2i) -> void:
var view_rasterized := false
var show_grid := true
var show_handles := true
+var show_reference := false
+var overlay_reference := false
+var show_debug := false
signal view_rasterized_changed
signal show_grid_changed
signal show_handles_changed
+signal show_reference_changed
+signal overlay_reference_changed
+signal show_debug_changed
-func set_view_rasterized(new_value: bool) -> void:
- if view_rasterized != new_value:
- view_rasterized = new_value
- view_rasterized_changed.emit()
+func toggle_view_rasterized() -> void:
+ view_rasterized = not view_rasterized
+ view_rasterized_changed.emit()
-func set_show_grid(new_value: bool) -> void:
- if show_grid != new_value:
- show_grid = new_value
- show_grid_changed.emit()
+func toggle_show_grid() -> void:
+ show_grid = not show_grid
+ show_grid_changed.emit()
-func set_show_handles(new_value: bool) -> void:
- if show_handles != new_value:
- show_handles = new_value
- show_handles_changed.emit()
+func toggle_show_handles() -> void:
+ show_handles = not show_handles
+ show_handles_changed.emit()
+
+func toggle_show_reference() -> void:
+ show_reference = not show_reference
+ show_reference_changed.emit()
+
+func toggle_overlay_reference() -> void:
+ overlay_reference = not overlay_reference
+ overlay_reference_changed.emit()
+
+func toggle_show_debug() -> void:
+ show_debug = not show_debug
+ show_debug_changed.emit()
# Override the selected elements with a single new selected element.
@@ -514,13 +527,13 @@ func is_hovered(xid: PackedInt32Array, inner_idx := -1, propagate := false) -> b
if inner_idx == -1:
return XIDUtils.is_parent_or_self(hovered_xid, xid)
else:
- return XIDUtils.is_parent_or_self(hovered_xid, xid) or\
- (semi_hovered_xid == xid and inner_hovered == inner_idx)
+ return (inner_hovered == inner_idx and semi_hovered_xid == xid) or\
+ XIDUtils.is_parent_or_self(hovered_xid, xid)
else:
if inner_idx == -1:
return hovered_xid == xid
else:
- return semi_hovered_xid == xid and inner_hovered == inner_idx
+ return inner_hovered == inner_idx and semi_hovered_xid == xid
# Returns whether the given element or inner editor is selected.
func is_selected(xid: PackedInt32Array, inner_idx := -1, propagate := false) -> bool:
@@ -619,47 +632,38 @@ func _on_xnodes_moved_to(xids: Array[PackedInt32Array], location: PackedInt32Arr
selection_changed.emit()
-func respond_to_key_input(event: InputEventKey) -> void:
- # Path commands using keys.
- if inner_selections.is_empty() or event.is_command_or_control_pressed():
+# Path commands using keys.
+func respond_to_key_input(path_cmd_char: String) -> void:
+ if inner_selections.is_empty():
# If a single path element is selected, add the new command at the end.
if selected_xids.size() == 1:
var xnode_ref := root_element.get_xnode(selected_xids[0])
if xnode_ref is ElementPath:
var path_attrib: AttributePathdata = xnode_ref.get_attribute("d")
- for action_name in path_actions_dict.keys():
- if ShortcutUtils.is_action_pressed(event, action_name):
- var path_cmd_count := path_attrib.get_command_count()
- var path_cmd_char := path_actions_dict[action_name]
- # Z after a Z is syntactically invalid.
- if (path_cmd_count == 0 and not path_cmd_char in "Mm") or\
- (path_cmd_char in "Zz" and path_cmd_count > 0 and\
- path_attrib.get_command(path_cmd_count - 1) is\
- PathCommand.CloseCommand):
- return
- path_attrib.insert_command(path_cmd_count, path_cmd_char, Vector2.ZERO)
- normal_select(selected_xids[0], path_cmd_count)
- handle_added.emit()
- break
- return
- # If path commands are selected, insert after the last one.
- for action_name in path_actions_dict.keys():
- var element_ref := root_element.get_xnode(semi_selected_xid)
- if element_ref.name == "path":
- if ShortcutUtils.is_action_pressed(event, action_name):
- var path_attrib: AttributePathdata = element_ref.get_attribute("d")
- var path_cmd_char := path_actions_dict[action_name]
- var last_selection: int = inner_selections.max()
+ var path_cmd_count := path_attrib.get_command_count()
# Z after a Z is syntactically invalid.
- if path_cmd_char in "Zz" and (path_attrib.get_command(last_selection) is\
- PathCommand.CloseCommand or (path_attrib.get_command_count() >\
- last_selection + 1 and path_attrib.get_command(last_selection + 1) is\
- PathCommand.CloseCommand)):
+ if (path_cmd_count == 0 and not path_cmd_char in "Mm") or\
+ (path_cmd_char in "Zz" and path_cmd_count > 0 and\
+ path_attrib.get_command(path_cmd_count - 1) is PathCommand.CloseCommand):
return
- path_attrib.insert_command(last_selection + 1, path_cmd_char, Vector2.ZERO)
- normal_select(semi_selected_xid, last_selection + 1)
+ path_attrib.insert_command(path_cmd_count, path_cmd_char, Vector2.ZERO)
+ normal_select(selected_xids[0], path_cmd_count)
handle_added.emit()
- break
+ else:
+ # If path commands are selected, insert after the last one.
+ var xnode_ref := root_element.get_xnode(semi_selected_xid)
+ if xnode_ref is ElementPath:
+ var path_attrib: AttributePathdata = xnode_ref.get_attribute("d")
+ var last_selection: int = inner_selections.max()
+ # Z after a Z is syntactically invalid.
+ if path_cmd_char in "Zz" and (path_attrib.get_command(last_selection) is\
+ PathCommand.CloseCommand or (path_attrib.get_command_count() >\
+ last_selection + 1 and path_attrib.get_command(last_selection + 1) is\
+ PathCommand.CloseCommand)):
+ return
+ path_attrib.insert_command(last_selection + 1, path_cmd_char, Vector2.ZERO)
+ normal_select(semi_selected_xid, last_selection + 1)
+ handle_added.emit()
# Operations on selected elements.
@@ -761,10 +765,8 @@ func get_selection_context(popup_method: Callable, context: Context) -> ContextP
btn_arr.append(ContextPopup.create_button(Translator.translate("View in List"),
view_in_list.bind(selected_xids[0]), false,
load("res://assets/icons/ViewInList.svg")))
-
- btn_arr.append(ContextPopup.create_button(Translator.translate("Duplicate"),
- duplicate_selected, false, load("res://assets/icons/Duplicate.svg"),
- "duplicate"))
+
+ btn_arr.append(ContextPopup.create_shortcut_button("duplicate"))
var xnode := root_element.get_xnode(selected_xids[0])
if selected_xids.size() == 1 and ((not xnode.is_element() and\
@@ -776,18 +778,11 @@ func get_selection_context(popup_method: Callable, context: Context) -> ContextP
load("res://assets/icons/Reload.svg")))
if can_move_up:
- btn_arr.append(ContextPopup.create_button(
- Translator.translate("Move Up"),
- move_up_selected, false,
- load("res://assets/icons/MoveUp.svg"), "move_up"))
+ btn_arr.append(ContextPopup.create_shortcut_button("move_up"))
if can_move_down:
- btn_arr.append(ContextPopup.create_button(
- Translator.translate("Move Down"),
- move_down_selected, false,
- load("res://assets/icons/MoveDown.svg"), "move_down"))
+ btn_arr.append(ContextPopup.create_shortcut_button("move_down"))
- btn_arr.append(ContextPopup.create_button(Translator.translate("Delete"),
- delete_selected, false, load("res://assets/icons/Delete.svg"), "delete"))
+ btn_arr.append(ContextPopup.create_shortcut_button("delete"))
elif not inner_selections.is_empty() and not semi_selected_xid.is_empty():
var element_ref := root_element.get_xnode(semi_selected_xid)
@@ -818,24 +813,18 @@ func get_selection_context(popup_method: Callable, context: Context) -> ContextP
var can_move_up := false
var can_move_down := false
if can_move_up:
- btn_arr.append(ContextPopup.create_button(
- Translator.translate("Move Up"), # Change to "Move Subpath Up"
- move_up_selected, false,
- load("res://visual/icons/MoveUp.svg"), "move_up"))
+ btn_arr.append(ContextPopup.create_shortcut_button("move_up"))
+ # , "Move Subpath Up"
if can_move_down:
- btn_arr.append(ContextPopup.create_button(
- Translator.translate("Move Down"), # Change to "Move Subpath Down"
- move_down_selected, false,
- load("res://visual/icons/MoveDown.svg"), "move_down"))
+ btn_arr.append(ContextPopup.create_shortcut_button("move_down"))
+ # , "Move Subpath Down"
"polygon", "polyline":
if inner_selections.size() == 1:
btn_arr.append(ContextPopup.create_button(
- Translator.translate("Insert After"),
- insert_point_after_selection, false,
- load("res://assets/icons/Plus.svg")))
+ Translator.translate("Insert After"), insert_point_after_selection,
+ false, load("res://assets/icons/Plus.svg")))
- btn_arr.append(ContextPopup.create_button(Translator.translate("Delete"),
- delete_selected, false, load("res://assets/icons/Delete.svg"), "delete"))
+ btn_arr.append(ContextPopup.create_shortcut_button("delete"))
var element_context := ContextPopup.new()
element_context.setup(btn_arr, true)
diff --git a/src/config_classes/SaveData.gd b/src/config_classes/SaveData.gd
index 9f93421..30452c9 100644
--- a/src/config_classes/SaveData.gd
+++ b/src/config_classes/SaveData.gd
@@ -23,6 +23,7 @@ func get_setting_default(setting: String) -> Variant:
"handle_selected_color": return Color("46f")
"handle_hovered_selected_color": return Color("f44")
"background_color": return Color(0.12, 0.132, 0.2, 1)
+ "grid_color": return Color(0.5, 0.5, 0.5)
"basic_color_valid": return Color("9f9")
"basic_color_error": return Color("f99")
"basic_color_warning": return Color("ee5")
@@ -172,6 +173,13 @@ const CURRENT_VERSION = 1
emit_changed()
Configs.change_background_color.call_deferred()
+@export var grid_color := Color(0.5, 0.5, 0.5):
+ set(new_value):
+ if grid_color != new_value:
+ grid_color = new_value
+ emit_changed()
+ Configs.grid_color_changed.emit()
+
@export var basic_color_valid := Color("9f9"):
set(new_value):
if basic_color_valid != new_value:
@@ -241,7 +249,7 @@ const HANDLE_SIZE_MAX = 4.0
Configs.handle_visuals_changed.emit()
enum ScalingApproach {AUTO, CONSTANT_075, CONSTANT_100, CONSTANT_125, CONSTANT_150,
- CONSTANT_175, CONSTANT_200, CONSTANT_300, CONSTANT_400, MAX}
+ CONSTANT_175, CONSTANT_200, CONSTANT_250, CONSTANT_300, CONSTANT_400, MAX}
@export var ui_scale := ScalingApproach.AUTO:
set(new_value):
# Validation
@@ -378,7 +386,7 @@ func _action_sync_inputmap(action: String) -> void:
func update_shortcut_validities() -> void:
_shortcut_validities.clear()
- for action in ShortcutUtils.get_all_shortcuts():
+ for action in ShortcutUtils.get_all_actions():
for shortcut: InputEventKey in InputMap.action_get_events(action):
var shortcut_id := shortcut.get_keycode_with_modifiers()
# If the key already exists, set validity to false, otherwise set to true.
@@ -398,7 +406,7 @@ func get_actions_with_shortcut(shortcut: InputEventKey) -> PackedStringArray:
return PackedStringArray()
var actions_with_shortcut := PackedStringArray()
- for action in ShortcutUtils.get_all_shortcuts():
+ for action in ShortcutUtils.get_all_actions():
for action_shortcut: InputEventKey in InputMap.action_get_events(action):
if action_shortcut.get_keycode_with_modifiers() == shortcut_id:
actions_with_shortcut.append(action)
@@ -520,7 +528,7 @@ const SHORTCUT_PANEL_MAX_SLOTS = 6
# Validation
for key in new_value:
if key < 0 or key >= SHORTCUT_PANEL_MAX_SLOTS or\
- not new_value[key] in ShortcutUtils.get_all_shortcuts():
+ not new_value[key] in ShortcutUtils.get_all_actions():
new_value.erase(key)
# Main part
if _shortcut_panel_slots != new_value:
@@ -576,6 +584,7 @@ const MAX_TABS = 50
for tab in _tabs:
tab.changed.connect(emit_changed)
tab.status_changed.connect(_on_tab_status_changed.bind(tab.id))
+ tab.reference_changed.connect(_on_tab_reference_changed.bind(tab.id))
emit_changed()
if _tabs.is_empty():
_add_new_tab()
@@ -599,6 +608,10 @@ func _on_tab_status_changed(id: int) -> void:
Configs.active_tab_status_changed.emit()
Configs.tabs_changed.emit()
+func _on_tab_reference_changed(id: int) -> void:
+ if id == _tabs[_active_tab_index].id:
+ Configs.active_tab_reference_changed.emit()
+
func has_tabs() -> bool:
return not _tabs.is_empty()
@@ -650,6 +663,7 @@ func _add_new_tab() -> void:
new_tab.fully_loaded = false
new_tab.changed.connect(emit_changed)
new_tab.status_changed.connect(_on_tab_status_changed.bind(new_id))
+ new_tab.reference_changed.connect(_on_tab_reference_changed.bind(new_id))
# Clear file path for the new tab.
var new_tab_path := new_tab.get_edited_file_path()
diff --git a/src/config_classes/TabData.gd b/src/config_classes/TabData.gd
index eb280ac..77c8577 100644
--- a/src/config_classes/TabData.gd
+++ b/src/config_classes/TabData.gd
@@ -7,6 +7,8 @@ const DEFAULT_SVG = '