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 = ' void: else: marked_unsaved = true - elif SVGParser.text_check_is_root_empty(get_true_svg_text()): + elif not FileAccess.file_exists(get_edited_file_path()) or\ + SVGParser.text_check_is_root_empty(get_true_svg_text()): empty_unsaved = true marked_unsaved = false presented_name = "[ %s ]" % Translator.translate("Empty") diff --git a/src/ui_parts/about_menu.gd b/src/ui_parts/about_menu.gd index 552281b..b4f5f95 100644 --- a/src/ui_parts/about_menu.gd +++ b/src/ui_parts/about_menu.gd @@ -43,6 +43,9 @@ func _on_tab_changed(idx: int) -> void: developers_list.items = app_info.authors + for child in translators_vbox.get_children(): + child.queue_free() + # There can be multiple translators for a single locale. for locale in TranslationServer.get_loaded_locales(): var credits := TranslationServer.get_translation_object(locale).get_message( @@ -108,10 +111,9 @@ func _on_tab_changed(idx: int) -> void: past_diamond_donors_list.items.append("%d anonymous" % app_info.past_anonymous_diamond_donors) 2: # This part doesn't need to be translated. - var licenses_dict := Engine.get_license_info() - %LicenseLabel.text = "MIT License\n\nCopyright (c) 2025 Anish Mishra\n" +\ - "Copyright (c) 2023-present GodSVG contributors\n\n" + licenses_dict["Expat"] + "Copyright (c) 2023-present GodSVG contributors\n\n" +\ + Engine.get_license_info()["Expat"] 3: for child in %GodSVGParts.get_children(): child.queue_free() @@ -120,12 +122,13 @@ func _on_tab_changed(idx: int) -> void: for child in %LicenseTexts.get_children(): child.queue_free() + # This part doesn't need to be translated. var godsvg_parts_label := Label.new() - godsvg_parts_label.text = "GodSVG parts" + godsvg_parts_label.text = "GodSVG components" var godot_parts_label := Label.new() - godot_parts_label.text = "Godot parts" + godot_parts_label.text = "Godot components" var license_texts_label := Label.new() - license_texts_label.text = "License texts" + license_texts_label.text = "Licenses" for label: Label in [godsvg_parts_label, godot_parts_label, license_texts_label]: label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER label.add_theme_font_size_override("font_size", 16) @@ -133,8 +136,6 @@ func _on_tab_changed(idx: int) -> void: %GodotParts.add_child(godot_parts_label) %LicenseTexts.add_child(license_texts_label) - # This part doesn't need to be translated. - var licenses_dict := Engine.get_license_info() var godot_copyright_info := Engine.get_copyright_info() var godot_engine_copyright: Dictionary for dict in godot_copyright_info: @@ -168,12 +169,6 @@ func _on_tab_changed(idx: int) -> void: ] for copyright_info in godsvg_copyright_info: - var vbox := VBoxContainer.new() - var name_label := Label.new() - name_label.add_theme_font_size_override("font_size", 14) - name_label.text = copyright_info["name"] - vbox.add_child(name_label) - var label := Label.new() label.add_theme_font_size_override("font_size", 11) for part in copyright_info["parts"]: @@ -181,16 +176,43 @@ func _on_tab_changed(idx: int) -> void: label.text += "Files:\n- %s\n" % "\n- ".join(part["files"]) label.text += "© %s\nLicense: %s" % ["\n© ".join( part["copyright"]), part["license"]] - vbox.add_child(label) - %GodSVGParts.add_child(vbox) - - for copyright_info in godot_copyright_info: var vbox := VBoxContainer.new() var name_label := Label.new() name_label.add_theme_font_size_override("font_size", 14) name_label.text = copyright_info["name"] vbox.add_child(name_label) - + vbox.add_child(label) + %GodSVGParts.add_child(vbox) + + # Clean up Godot's copyright info from some stripped modules + # to show more relevant components and load the UI faster. + var used_licenses: PackedStringArray + const unused_module_paths: PackedStringArray = ["modules/betsy", + "modules/godot_physics_2d", "modules/godot_physics_3d", + "modules/jolt_physics", "modules/lightmapper_rd", "thirdparty/brotli", + "thirdparty/cvtt", "thirdparty/basis_universal", "thirdparty/d3d12", + "thirdparty/etcpak", "thirdparty/graphite", "thirdparty/meshoptimizer", + "thirdparty/minimp3", "thirdparty/minizip", "thirdparty/openxr", + "thirdparty/tinyexr", "thirdparty/vhacd", "thirdparty/volk", + "thirdparty/vulkan", "thirdparty/xatlas"] + for copyright_info_idx in range(godot_copyright_info.size() - 1, -1, -1): + var copyright_info: Dictionary = godot_copyright_info[copyright_info_idx] + for part_idx in range(copyright_info["parts"].size() -1, -1, -1): + var part: Dictionary = copyright_info["parts"][part_idx] + if part.has("files"): + for i in range(part["files"].size() - 1, -1, -1): + for module_path in unused_module_paths: + if module_path in part["files"][i]: + part["files"].remove_at(i) + break + if part["files"].is_empty(): + godot_copyright_info.erase(copyright_info) + else: + var used_license: String = part["license"] + if not used_license in used_licenses: + used_licenses.append(used_license) + + for copyright_info in godot_copyright_info: var label := Label.new() label.add_theme_font_size_override("font_size", 11) for part in copyright_info["parts"]: @@ -198,12 +220,21 @@ func _on_tab_changed(idx: int) -> void: label.text += "Files:\n- %s\n" % "\n- ".join(part["files"]) label.text += "© %s\nLicense: %s" % ["\n© ".join( part["copyright"]), part["license"]] + var vbox := VBoxContainer.new() + var name_label := Label.new() + name_label.add_theme_font_size_override("font_size", 14) + name_label.text = copyright_info["name"] + vbox.add_child(name_label) vbox.add_child(label) %GodotParts.add_child(vbox) + var licenses_dict := Engine.get_license_info() for license_name in licenses_dict: + if not license_name in used_licenses: + continue var license_vbox := VBoxContainer.new() var license_title := Label.new() + license_title.add_theme_font_size_override("font_size", 14) license_title.text = license_name license_vbox.add_child(license_title) var license_text := Label.new() diff --git a/src/ui_parts/code_editor.gd b/src/ui_parts/code_editor.gd index 8ec4d3d..82c31ae 100644 --- a/src/ui_parts/code_editor.gd +++ b/src/ui_parts/code_editor.gd @@ -104,9 +104,7 @@ func _on_svg_code_edit_focus_entered() -> void: func _on_options_button_pressed() -> void: var btn_array: Array[Button] = [] - btn_array.append(ContextPopup.create_button( - Translator.translate("Copy all text"), ShortcutUtils.fn("copy_svg_text"), - false, load("res://assets/icons/Copy.svg"), "copy_svg_text")) + btn_array.append(ContextPopup.create_shortcut_button("copy_svg_text")) var context_popup := ContextPopup.new() context_popup.setup(btn_array, true) HandlerGUI.popup_under_rect_center(context_popup, options_button.get_global_rect(), diff --git a/src/ui_parts/current_file_button.gd b/src/ui_parts/current_file_button.gd index afc817b..59362ee 100644 --- a/src/ui_parts/current_file_button.gd +++ b/src/ui_parts/current_file_button.gd @@ -21,22 +21,15 @@ func _make_custom_tooltip(_for_text: String) -> Object: func _on_file_button_pressed() -> void: var btn_array: Array[Button] = [] - btn_array.append(ContextPopup.create_button(Translator.translate("Save SVG"), - FileUtils.save_svg, false, load("res://assets/icons/Save.svg"), "save")) - btn_array.append(ContextPopup.create_button(Translator.translate("Save SVG as…"), - FileUtils.save_svg_as, false, load("res://assets/icons/Save.svg"), "save_as")) - btn_array.append(ContextPopup.create_button(Translator.translate("Reset SVG"), - ShortcutUtils.fn("reset_svg"), - FileUtils.compare_svg_to_disk_contents() != FileUtils.FileState.DIFFERENT, - load("res://assets/icons/Reload.svg"), "reset_svg")) - btn_array.append(ContextPopup.create_button(Translator.translate("Open externally"), - ShortcutUtils.fn("open_externally"), - not FileAccess.file_exists(Configs.savedata.get_active_tab().svg_file_path), - load("res://assets/icons/OpenFile.svg"), "open_externally")) - btn_array.append(ContextPopup.create_button(Translator.translate("Show in File Manager"), - ShortcutUtils.fn("open_in_folder"), - not FileAccess.file_exists(Configs.savedata.get_active_tab().svg_file_path), - load("res://assets/icons/OpenFolder.svg"), "open_in_folder")) + btn_array.append(ContextPopup.create_shortcut_button("save")) + btn_array.append(ContextPopup.create_shortcut_button("save_as", false, + Translator.translate("Save SVG as…"))) + btn_array.append(ContextPopup.create_shortcut_button("reset_svg", + FileUtils.compare_svg_to_disk_contents() != FileUtils.FileState.DIFFERENT)) + btn_array.append(ContextPopup.create_shortcut_button("open_externally", + not FileAccess.file_exists(Configs.savedata.get_active_tab().svg_file_path))) + btn_array.append(ContextPopup.create_shortcut_button("open_in_folder", + not FileAccess.file_exists(Configs.savedata.get_active_tab().svg_file_path))) var context_popup := ContextPopup.new() context_popup.setup(btn_array, true, size.x, -1, PackedInt32Array([2])) diff --git a/src/ui_parts/display.gd b/src/ui_parts/display.gd index f257a71..d5eab21 100644 --- a/src/ui_parts/display.gd +++ b/src/ui_parts/display.gd @@ -7,7 +7,7 @@ const NumberEdit = preload("res://src/ui_widgets/number_edit.gd") @onready var reference_button: Button = %LeftMenu/Reference @onready var visuals_button: Button = %LeftMenu/Visuals @onready var snapper: NumberEdit = %LeftMenu/Snapping/SnapNumberEdit -@onready var snap_button: BetterToggleButton = %LeftMenu/Snapping/SnapButton +@onready var snap_button: BetterButton = %LeftMenu/Snapping/SnapButton @onready var viewport_panel: PanelContainer = $ViewportPanel @onready var debug_container: MarginContainer = $ViewportPanel/DebugMargins @onready var debug_label: Label = %DebugContainer/DebugLabel @@ -21,41 +21,19 @@ func _ready() -> void: Configs.snap_changed.connect(update_snap_config) Configs.theme_changed.connect(update_theme) Configs.active_tab_changed.connect(sync_reference_image) + Configs.active_tab_reference_changed.connect(sync_reference_image) + State.show_reference_changed.connect(_on_show_reference_updated) + State.overlay_reference_changed.connect(_on_overlay_reference_updated) + State.show_debug_changed.connect(_on_show_debug_changed) update_translations() update_theme() update_snap_config() get_window().window_input.connect(_update_input_debug) -func _unhandled_input(event: InputEvent) -> void: - if Input.is_action_pressed("debug"): - if debug_container.visible: - debug_container.hide() - else: - debug_container.show() - update_debug() - input_debug_label.text = "" - elif ShortcutUtils.is_action_pressed(event, "load_reference"): - FileUtils.open_image_import_dialog(finish_reference_import) - elif ShortcutUtils.is_action_pressed(event, "view_show_grid"): - toggle_grid_visuals() - elif ShortcutUtils.is_action_pressed(event, "view_show_handles"): - toggle_handles_visuals() - elif ShortcutUtils.is_action_pressed(event, "view_rasterized_svg"): - toggle_rasterization() - elif ShortcutUtils.is_action_pressed(event, "view_show_reference"): - toggle_reference_image() - elif ShortcutUtils.is_action_pressed(event, "view_overlay_reference"): - toggle_reference_overlay() - elif ShortcutUtils.is_action_pressed(event, "toggle_snap"): - toggle_snap() - func update_translations() -> void: %LeftMenu/Visuals.tooltip_text = Translator.translate("Visuals") - %LeftMenu/Snapping/SnapButton.tooltip_text =\ - TranslationUtils.get_shortcut_description("toggle_snap") - %LeftMenu/Snapping/SnapNumberEdit.tooltip_text = Translator.translate( - "Snap size") + %LeftMenu/Snapping/SnapNumberEdit.tooltip_text = Translator.translate("Snap size") func update_theme() -> void: var toolbar_stylebox := StyleBoxFlat.new() @@ -82,13 +60,9 @@ func update_snap_config() -> void: func _on_reference_pressed() -> void: var btn_arr: Array[Button] = [ - ContextPopup.create_button(Translator.translate("Load reference image"), - FileUtils.open_image_import_dialog.bind(finish_reference_import), false, - load("res://assets/icons/Reference.svg"), "load_reference"), - ContextPopup.create_checkbox(Translator.translate("Show reference"), - toggle_reference_image, reference_texture.visible, "view_show_reference"), - ContextPopup.create_checkbox(Translator.translate("Overlay reference"), - toggle_reference_overlay, reference_overlay, "view_overlay_reference") + ContextPopup.create_shortcut_button("load_reference"), + ContextPopup.create_shortcut_checkbox("view_show_reference", reference_texture.visible), + ContextPopup.create_shortcut_checkbox("view_overlay_reference", reference_overlay) ] var reference_popup := ContextPopup.new() @@ -98,12 +72,10 @@ func _on_reference_pressed() -> void: func _on_visuals_button_pressed() -> void: var btn_arr: Array[Button] = [ - ContextPopup.create_checkbox(Translator.translate("Show grid"), - toggle_grid_visuals, State.show_grid, "view_show_grid"), - ContextPopup.create_checkbox(Translator.translate("Show handles"), - toggle_handles_visuals, State.show_handles, "view_show_handles"), - ContextPopup.create_checkbox(Translator.translate("Rasterized SVG"), - toggle_rasterization, State.view_rasterized, "view_rasterized_svg")] + ContextPopup.create_shortcut_checkbox("view_show_grid", State.show_grid), + ContextPopup.create_shortcut_checkbox("view_show_handles", State.show_handles), + ContextPopup.create_shortcut_checkbox("view_rasterized_svg", State.view_rasterized) + ] var visuals_popup := ContextPopup.new() visuals_popup.setup(btn_arr, true) @@ -111,36 +83,15 @@ func _on_visuals_button_pressed() -> void: get_viewport()) -func toggle_grid_visuals() -> void: - State.set_show_grid(not State.show_grid) - -func toggle_handles_visuals() -> void: - State.set_show_handles(not State.show_handles) - -func toggle_rasterization() -> void: - State.set_view_rasterized(not State.view_rasterized) - -func toggle_reference_image() -> void: - reference_texture.visible = not reference_texture.visible +func _on_show_reference_updated() -> void: + reference_texture.visible = State.show_reference -func toggle_reference_overlay() -> void: - reference_overlay = not reference_overlay - if reference_overlay: +func _on_overlay_reference_updated() -> void: + if State.overlay_reference: viewport.move_child(reference_texture, viewport.get_child_count() - 1) else: viewport.move_child(reference_texture, 0) -func finish_reference_import(data: Variant, file_path: String) -> void: - var img := Image.new() - match file_path.get_extension().to_lower(): - "svg": img.load_svg_from_string(data) - "png": img.load_png_from_buffer(data) - "jpg", "jpeg": img.load_jpg_from_buffer(data) - "webp": img.load_webp_from_buffer(data) - var image_texture := ImageTexture.create_from_image(img) - Configs.savedata.get_active_tab().reference_image = image_texture - sync_reference_image() - func sync_reference_image() -> void: var reference := Configs.savedata.get_active_tab().reference_image if is_instance_valid(reference): @@ -150,12 +101,6 @@ func sync_reference_image() -> void: reference_texture.texture = null reference_texture.hide() -func toggle_snap() -> void: - snap_button.button_pressed = not snap_button.button_pressed - -func set_snap_amount(snap_value: float) -> void: - snapper.set_value(snap_value) - func _on_snap_button_toggled(toggled_on: bool) -> void: Configs.savedata.snap = absf(Configs.savedata.snap) if toggled_on\ else -absf(Configs.savedata.snap) @@ -163,6 +108,15 @@ func _on_snap_button_toggled(toggled_on: bool) -> void: func _on_snap_number_edit_value_changed(new_value: float) -> void: Configs.savedata.snap = new_value * signf(Configs.savedata.snap) + +func _on_show_debug_changed() -> void: + if State.show_debug: + debug_container.show() + update_debug() + input_debug_label.text = "" + else: + debug_container.hide() + # The strings here are intentionally not localized. func update_debug() -> void: var debug_text := "" diff --git a/src/ui_parts/display.tscn b/src/ui_parts/display.tscn index 6f062e6..104746c 100644 --- a/src/ui_parts/display.tscn +++ b/src/ui_parts/display.tscn @@ -4,7 +4,7 @@ [ext_resource type="Texture2D" uid="uid://iglrqrqyg4kn" path="res://assets/icons/Reference.svg" id="4_2hiq7"] [ext_resource type="Texture2D" uid="uid://kkxyv1gyrjgj" path="res://assets/icons/Visuals.svg" id="4_n3qjt"] [ext_resource type="Texture2D" uid="uid://buire51l0mifg" path="res://assets/icons/Snap.svg" id="5_1k2cq"] -[ext_resource type="Script" uid="uid://ynx3s1jc6bwq" path="res://src/ui_widgets/BetterToggleButton.gd" id="6_3v3ve"] +[ext_resource type="Script" uid="uid://ynx3s1jc6bwq" path="res://src/ui_widgets/BetterButton.gd" id="6_3v3ve"] [ext_resource type="PackedScene" uid="uid://dad7fkhmsooc6" path="res://src/ui_widgets/number_edit.tscn" id="7_wrrfr"] [ext_resource type="PackedScene" uid="uid://oltvrf01xrxl" path="res://src/ui_parts/zoom_menu.tscn" id="8_xtdmn"] [ext_resource type="Script" uid="uid://b6pmlbnl76wmm" path="res://src/ui_parts/viewport.gd" id="9_4xrk7"] @@ -145,6 +145,7 @@ toggle_mode = true icon = ExtResource("5_1k2cq") script = ExtResource("6_3v3ve") hover_pressed_stylebox = SubResource("StyleBoxFlat_eujxa") +action = "toggle_snap" [node name="SnapNumberEdit" parent="ViewportPanel/VBoxContainer/Toolbar/ViewportOptions/LeftMenu/Snapping" instance=ExtResource("7_wrrfr")] custom_minimum_size = Vector2(48, 22) diff --git a/src/ui_parts/export_menu.gd b/src/ui_parts/export_menu.gd index 90c30e7..0594f4b 100644 --- a/src/ui_parts/export_menu.gd +++ b/src/ui_parts/export_menu.gd @@ -171,11 +171,11 @@ func _input(event: InputEvent) -> void: if not visible: return - if ShortcutUtils.is_action_pressed(event, "redo"): + if ShortcutUtils.is_action_pressed(event, "ui_redo"): if undo_redo.has_redo(): undo_redo.redo() accept_event() - elif ShortcutUtils.is_action_pressed(event, "undo"): + elif ShortcutUtils.is_action_pressed(event, "ui_undo"): if undo_redo.has_undo(): undo_redo.undo() accept_event() diff --git a/src/ui_parts/global_actions.gd b/src/ui_parts/global_actions.gd index e2cbd5d..374a82b 100644 --- a/src/ui_parts/global_actions.gd +++ b/src/ui_parts/global_actions.gd @@ -1,19 +1,9 @@ extends HBoxContainer -@onready var import_button: Button = $RightSide/ImportButton -@onready var export_button: Button = $RightSide/ExportButton @onready var more_options: Button = $LeftSide/MoreOptions -@onready var settings_button: Button = $LeftSide/SettingsButton @onready var size_button: Button = $RightSide/SizeButton -func update_translations() -> void: - import_button.tooltip_text = Translator.translate("Import") - export_button.tooltip_text = Translator.translate("Export") - settings_button.tooltip_text = Translator.translate("Settings") - func _ready() -> void: - Configs.language_changed.connect(update_translations) - update_translations() State.svg_changed.connect(update_size_button) Configs.basic_colors_changed.connect(update_size_button_colors) @@ -27,18 +17,13 @@ func _ready() -> void: size_button.add_theme_stylebox_override(theme_type, stylebox) size_button.end_bulk_theme_override() - import_button.pressed.connect(ShortcutUtils.fn("import")) - export_button.pressed.connect(ShortcutUtils.fn("export")) more_options.pressed.connect(_on_more_options_pressed) size_button.pressed.connect(_on_size_button_pressed) - settings_button.pressed.connect(ShortcutUtils.fn_call.bind("open_settings")) func _on_size_button_pressed() -> void: var btn_array: Array[Button] = [ - ContextPopup.create_button(Translator.translate("Optimize"), - ShortcutUtils.fn("optimize"), false, load("res://assets/icons/Compress.svg"), - "optimize")] + ContextPopup.create_shortcut_button("optimize")] var context_popup := ContextPopup.new() context_popup.setup(btn_array, true) HandlerGUI.popup_under_rect_center(context_popup, size_button.get_global_rect(), @@ -48,26 +33,18 @@ func _on_more_options_pressed() -> void: var can_show_savedata_folder := DisplayServer.has_feature( DisplayServer.FEATURE_NATIVE_DIALOG_FILE) var buttons_arr: Array[Button] = [] - buttons_arr.append(ContextPopup.create_button(Translator.translate( - "Check for updates"), ShortcutUtils.fn("check_updates"), false, - load("res://assets/icons/Reload.svg"), "check_updates")) + buttons_arr.append(ContextPopup.create_shortcut_button("check_updates")) if can_show_savedata_folder: buttons_arr.append(ContextPopup.create_button(Translator.translate( "View savedata"), open_savedata_folder , false, load("res://assets/icons/OpenFolder.svg"))) - var about_btn := ContextPopup.create_button(Translator.translate("About…"), - ShortcutUtils.fn("about_info"), false, - load("res://assets/logos/icon.svg"), "about_info") + var about_btn := ContextPopup.create_shortcut_button("about_info", false, "", + load("res://assets/logos/icon.svg")) about_btn.expand_icon = true buttons_arr.append(about_btn) - buttons_arr.append(ContextPopup.create_button(Translator.translate( - "GodSVG repository"), ShortcutUtils.fn("about_godsvg_repo"), false, - load("res://assets/icons/Link.svg"), "")) - buttons_arr.append(ContextPopup.create_button(Translator.translate( - "GodSVG-Mobile repository"), ShortcutUtils.fn("about_repo"), false, - load("res://assets/icons/Link.svg"), "about_repo")) + buttons_arr.append(ContextPopup.create_shortcut_button("about_repo")) var separator_indices := PackedInt32Array([1, 3]) if can_show_savedata_folder: separator_indices = PackedInt32Array([2, 4]) diff --git a/src/ui_parts/global_actions.tscn b/src/ui_parts/global_actions.tscn index f45b3ae..cf48168 100644 --- a/src/ui_parts/global_actions.tscn +++ b/src/ui_parts/global_actions.tscn @@ -1,10 +1,11 @@ -[gd_scene load_steps=8 format=3 uid="uid://cxmrx6t4jkhyj"] +[gd_scene load_steps=9 format=3 uid="uid://cxmrx6t4jkhyj"] [ext_resource type="Script" uid="uid://cgbgw4ok5jxk5" path="res://src/ui_parts/global_actions.gd" id="1_x4rqo"] [ext_resource type="Texture2D" uid="uid://ccbta5q43jobk" path="res://assets/icons/More.svg" id="2_71075"] [ext_resource type="Texture2D" uid="uid://6ymbl3jqersp" path="res://assets/icons/Import.svg" id="2_giwu1"] [ext_resource type="Texture2D" uid="uid://d0uvwj0t44n6v" path="res://assets/icons/Export.svg" id="3_4ckhj"] [ext_resource type="Texture2D" uid="uid://ckkkgof1hcbld" path="res://assets/icons/Gear.svg" id="3_xl5uh"] +[ext_resource type="Script" uid="uid://ynx3s1jc6bwq" path="res://src/ui_widgets/BetterButton.gd" id="4_f81d5"] [ext_resource type="FontFile" uid="uid://dc0w4sx0h0fui" path="res://assets/fonts/FontBold.ttf" id="4_xl5uh"] [ext_resource type="Script" uid="uid://41g64ussxcbn" path="res://src/ui_parts/current_file_button.gd" id="5_14xct"] @@ -24,7 +25,6 @@ focus_mode = 0 mouse_default_cursor_shape = 2 theme_type_variation = &"IconButton" icon = ExtResource("2_71075") -icon_alignment = 1 [node name="SettingsButton" type="Button" parent="LeftSide"] layout_mode = 2 @@ -33,7 +33,9 @@ focus_mode = 0 mouse_default_cursor_shape = 2 theme_type_variation = &"IconButton" icon = ExtResource("3_xl5uh") -icon_alignment = 1 +script = ExtResource("4_f81d5") +action = "open_settings" +metadata/_custom_type_script = "uid://ynx3s1jc6bwq" [node name="RightSide" type="HBoxContainer" parent="."] layout_mode = 2 @@ -60,6 +62,9 @@ focus_mode = 0 mouse_default_cursor_shape = 2 theme_type_variation = &"IconButton" icon = ExtResource("2_giwu1") +script = ExtResource("4_f81d5") +action = "import" +metadata/_custom_type_script = "uid://ynx3s1jc6bwq" [node name="ExportButton" type="Button" parent="RightSide"] layout_mode = 2 @@ -67,3 +72,6 @@ focus_mode = 0 mouse_default_cursor_shape = 2 theme_type_variation = &"IconButton" icon = ExtResource("3_4ckhj") +script = ExtResource("4_f81d5") +action = "export" +metadata/_custom_type_script = "uid://ynx3s1jc6bwq" diff --git a/src/ui_parts/good_file_dialog.gd b/src/ui_parts/good_file_dialog.gd index 790b8ac..c20ea44 100644 --- a/src/ui_parts/good_file_dialog.gd +++ b/src/ui_parts/good_file_dialog.gd @@ -311,9 +311,8 @@ func _on_create_folder_finished(text: String) -> void: func open_dir_context(dir: String) -> void: var context_popup := ContextPopup.new() var btn_arr: Array[Button] = [ - ContextPopup.create_button(Translator.translate("Open"), - enter_dir.bind(dir), false, load("res://assets/icons/OpenFolder.svg"), - "ui_accept"), + ContextPopup.create_shortcut_button("ui_accept", false, + Translator.translate("Open"), load("res://assets/icons/OpenFolder.svg")), ContextPopup.create_button(Translator.translate("Copy path"), DisplayServer.clipboard_set.bind(dir), false, load("res://assets/icons/Copy.svg"))] @@ -324,8 +323,8 @@ func open_dir_context(dir: String) -> void: func open_file_context(file: String) -> void: focus_file(file) var btn_arr: Array[Button] = [ - ContextPopup.create_button(special_button.text, - select_file, false, load("res://assets/icons/OpenFile.svg"), "ui_accept"), + ContextPopup.create_shortcut_button("ui_accept", false, special_button.text, + load("res://assets/icons/OpenFile.svg")), ContextPopup.create_button(Translator.translate("Copy path"), copy_file_path, false, load("res://assets/icons/Copy.svg"))] var context_popup := ContextPopup.new() diff --git a/src/ui_parts/handles_manager.gd b/src/ui_parts/handles_manager.gd index d8c9493..ba8df15 100644 --- a/src/ui_parts/handles_manager.gd +++ b/src/ui_parts/handles_manager.gd @@ -28,6 +28,7 @@ func _exit_tree() -> void: RenderingServer.free_rid(surface) RenderingServer.free_rid(selections_surface) +# Generate the procedural handle textures. func render_handle_textures() -> void: normal_color = Configs.savedata.handle_color hovered_color = Configs.savedata.handle_hovered_color diff --git a/src/ui_parts/mac_menu.gd b/src/ui_parts/mac_menu.gd index 711d924..77a9ebd 100644 --- a/src/ui_parts/mac_menu.gd +++ b/src/ui_parts/mac_menu.gd @@ -20,6 +20,9 @@ var view_idx: int var view_show_grid_idx: int var view_show_handles_idx: int var view_rasterized_svg_idx: int +var view_show_reference_idx: int +var view_overlay_reference_idx: int +var view_show_debug_idx: int var snap_rid: RID var snap_idx: int @@ -42,21 +45,27 @@ func _enter_tree() -> void: _setup_menu_items() # Updates Configs.language_changed.connect(_reset_menus) - Configs.shortcuts_changed.connect(_setup_menu_items) + Configs.shortcuts_changed.connect(_reset_menus) + # For now only keep check items up to date. Disabling things reliably is complicated. Configs.snap_changed.connect(_on_snap_changed) - _on_snap_changed() State.view_rasterized_changed.connect(_on_view_rasterized_changed) - _on_view_rasterized_changed() State.show_grid_changed.connect(_on_show_grid_changed) - _on_show_grid_changed() State.show_handles_changed.connect(_on_show_handles_changed) + State.show_reference_changed.connect(_on_show_reference_changed) + State.overlay_reference_changed.connect(_on_overlay_reference_changed) + State.show_debug_changed.connect(_on_show_debug_changed) + # Updating checked items didn't work without the await. + await get_tree().process_frame + _on_snap_changed() + _on_view_rasterized_changed() + _on_show_grid_changed() _on_show_handles_changed() - State.svg_changed.connect(_on_svg_changed) - _on_svg_changed() + _on_show_reference_changed() + _on_overlay_reference_changed() + _on_show_debug_changed() func _reset_menus() -> void: - _clear_menu_items() NativeMenu.remove_item(global_rid, snap_idx) NativeMenu.remove_item(global_rid, view_idx) NativeMenu.remove_item(global_rid, tool_idx) @@ -100,35 +109,36 @@ func _clear_menu_items() -> void: func _setup_menu_items() -> void: - # Included App and Help menus. - _add_action(appl_rid, "open_settings") - _add_icon_item(help_rid, "open_settings", load("res://assets/icons/Gear.svg")) - _add_icon_item(help_rid, "about_repo", load("res://assets/icons/Link.svg")) - _add_icon_item(help_rid, "about_info", load("res://assets/logos/icon.svg")) - _add_icon_item(help_rid, "about_donate", load("res://assets/icons/Heart.svg")) - _add_icon_item(help_rid, "about_website", load("res://assets/icons/Link.svg")) - _add_icon_item(help_rid, "check_updates", load("res://assets/icons/Reload.svg")) + _clear_menu_items() + _add_item(appl_rid, "open_settings") + # Help menu. + _add_icon_item(help_rid, "open_settings") + _add_icon_item(help_rid, "about_repo") + _add_icon_item(help_rid, "about_info") + _add_icon_item(help_rid, "about_donate") + _add_icon_item(help_rid, "about_website") + _add_icon_item(help_rid, "check_updates") # File menu. - _add_action(file_rid, "import") - _add_action(file_rid, "export") - _add_action(file_rid, "save") - _add_action(file_rid, "save_as") + _add_many_items(file_rid, PackedStringArray(["import", "export", "save", "save_as"])) NativeMenu.add_separator(file_rid) - _add_action(file_rid, "copy_svg_text") - file_optimize_idx = _add_action(file_rid, "optimize") + _add_item(file_rid, "copy_svg_text") + file_optimize_idx = _add_item(file_rid, "optimize") NativeMenu.add_separator(file_rid) - file_reset_svg_idx = _add_action(file_rid, "reset_svg") + file_reset_svg_idx = _add_item(file_rid, "reset_svg") # Edit and Tool menus. - _add_many_actions(edit_rid, ShortcutUtils.get_shortcuts("edit")) - _add_many_actions(tool_rid, ShortcutUtils.get_shortcuts("tool")) + _add_many_items(edit_rid, ShortcutUtils.get_actions("edit")) + _add_many_items(tool_rid, ShortcutUtils.get_actions("tool")) # View menu. view_show_grid_idx = _add_check_item(view_rid, "view_show_grid") view_show_handles_idx = _add_check_item(view_rid, "view_show_handles") view_rasterized_svg_idx = _add_check_item(view_rid, "view_rasterized_svg") NativeMenu.add_separator(view_rid) - _add_action(view_rid, "zoom_in") - _add_action(view_rid, "zoom_out") - _add_action(view_rid, "zoom_reset") + view_show_reference_idx = _add_item(view_rid, "load_reference") + view_show_reference_idx = _add_check_item(view_rid, "view_show_reference") + view_overlay_reference_idx = _add_check_item(view_rid, "view_overlay_reference") + view_show_debug_idx = _add_check_item(view_rid, "view_show_debug") + NativeMenu.add_separator(view_rid) + _add_many_items(view_rid, PackedStringArray(["zoom_in", "zoom_out", "zoom_reset"])) # Snap menu. snap_enable_idx = _add_check_item(snap_rid, "toggle_snap") NativeMenu.add_separator(snap_rid) @@ -140,50 +150,39 @@ func _setup_menu_items() -> void: snap_4_idx = NativeMenu.add_radio_check_item(snap_rid, "4", _set_snap, _set_snap, 4) -func _add_many_actions(menu_rid: RID, actions: PackedStringArray) -> void: - for action in actions: - _add_action(menu_rid, action) - - -func _add_action(menu_rid: RID, action_name: StringName) -> int: - var display_name := _get_action_display_name(action_name) - var key := _get_keycode_for_events(InputMap.action_get_events(action_name)) - return NativeMenu.add_item(menu_rid, display_name, _action_call, _action_call, action_name, key) - - -func _add_check_item(menu_rid: RID, action_name: StringName) -> int: - var display_name := _get_action_display_name(action_name) - return NativeMenu.add_check_item(menu_rid, display_name, _action_call, _action_call, action_name) +func _add_item(menu_rid: RID, action_name: String) -> int: + return NativeMenu.add_item(menu_rid, + TranslationUtils.get_action_description(action_name), + HandlerGUI.throw_action_event, HandlerGUI.throw_action_event, action_name, + _get_action_keycode(action_name)) +func _add_many_items(menu_rid: RID, actions: PackedStringArray) -> void: + for action in actions: + _add_item(menu_rid, action) -func _add_icon_item(menu_rid: RID, action_name: StringName, icon: Texture2D) -> int: - var display_name := _get_action_display_name(action_name) - return NativeMenu.add_icon_item(menu_rid, icon, display_name, _action_call, _action_call, action_name) +func _add_check_item(menu_rid: RID, action_name: String) -> int: + return NativeMenu.add_check_item(menu_rid, + TranslationUtils.get_action_description(action_name), + HandlerGUI.throw_action_event, HandlerGUI.throw_action_event, action_name) +func _add_many_icon_items(menu_rid: RID, actions: PackedStringArray) -> void: + for action in actions: + _add_icon_item(menu_rid, action) -func _get_action_display_name(action_name: StringName) -> String: - var display_name := TranslationUtils.get_shortcut_description(action_name) - if display_name.is_empty(): - display_name = action_name.capitalize().replace("Svg", "SVG") - return display_name +func _add_icon_item(menu_rid: RID, action_name: String) -> int: + return NativeMenu.add_icon_item(menu_rid, + ShortcutUtils.get_action_icon(action_name), + TranslationUtils.get_action_description(action_name), + HandlerGUI.throw_action_event, HandlerGUI.throw_action_event, action_name) -func _get_keycode_for_events(input_events: Array[InputEvent]) -> Key: - for input_event in input_events: - if input_event is InputEventKey: - var key: Key = input_event.get_keycode_with_modifiers() - if key != KEY_NONE: - return key - key = input_event.get_physical_keycode_with_modifiers() - if key != KEY_NONE: - return key +func _get_action_keycode(action: String) -> Key: + var shortcut := ShortcutUtils.get_action_first_valid_shortcut(action) + if is_instance_valid(shortcut): + return shortcut.get_keycode_with_modifiers() return KEY_NONE -func _on_svg_changed() -> void: - NativeMenu.set_item_disabled(file_rid, file_reset_svg_idx, - FileUtils.compare_svg_to_disk_contents() == FileUtils.FileState.DIFFERENT) - func _on_view_rasterized_changed() -> void: NativeMenu.set_item_checked(view_rid, view_rasterized_svg_idx, State.view_rasterized) @@ -193,6 +192,16 @@ func _on_show_grid_changed() -> void: func _on_show_handles_changed() -> void: NativeMenu.set_item_checked(view_rid, view_show_handles_idx, State.show_handles) +func _on_show_reference_changed() -> void: + NativeMenu.set_item_checked(view_rid, view_show_reference_idx, State.show_reference) + +func _on_overlay_reference_changed() -> void: + NativeMenu.set_item_checked(view_rid, view_overlay_reference_idx, State.overlay_reference) + +func _on_show_debug_changed() -> void: + NativeMenu.set_item_checked(view_rid, view_show_debug_idx, State.show_debug) + + func _on_snap_changed() -> void: var snap_amount := absf(Configs.savedata.snap) NativeMenu.set_item_checked(snap_rid, snap_enable_idx, Configs.savedata.snap > 0) @@ -205,10 +214,3 @@ func _on_snap_changed() -> void: func _set_snap(amount: float) -> void: Configs.savedata.snap = amount - - -func _action_call(tag: StringName) -> void: - var a := InputEventAction.new() - a.action = tag - a.pressed = true - Input.parse_input_event(a) diff --git a/src/ui_parts/root_element_editor.gd b/src/ui_parts/root_element_editor.gd index dc69f50..362b85e 100644 --- a/src/ui_parts/root_element_editor.gd +++ b/src/ui_parts/root_element_editor.gd @@ -6,9 +6,9 @@ const NumberEdit = preload("res://src/ui_widgets/number_edit.gd") # use NumberEdit, rather than NumberField. Viewbox is a list and it also doesn't have a # default value, so it uses 4 NumberEdits. -@onready var width_button: Button = %Size/Width/WidthButton -@onready var height_button: Button = %Size/Height/HeightButton -@onready var viewbox_button: Button = %Viewbox/ViewboxButton +@onready var width_button: BetterButton = %Size/Width/WidthButton +@onready var height_button: BetterButton = %Size/Height/HeightButton +@onready var viewbox_button: BetterButton = %Viewbox/ViewboxButton @onready var width_edit: NumberEdit = %Size/Width/WidthEdit @onready var height_edit: NumberEdit = %Size/Height/HeightEdit @onready var viewbox_edit_x: NumberEdit = %Viewbox/Rect/ViewboxEditX diff --git a/src/ui_parts/root_element_editor.tscn b/src/ui_parts/root_element_editor.tscn index ce2d828..07398f8 100644 --- a/src/ui_parts/root_element_editor.tscn +++ b/src/ui_parts/root_element_editor.tscn @@ -3,7 +3,7 @@ [ext_resource type="Script" uid="uid://beukt3a23d5ug" path="res://src/ui_parts/root_element_editor.gd" id="1_xgyg0"] [ext_resource type="FontFile" uid="uid://depydd16jq777" path="res://assets/fonts/FontMono.ttf" id="2_fm5sa"] [ext_resource type="PackedScene" uid="uid://dad7fkhmsooc6" path="res://src/ui_widgets/number_edit.tscn" id="3_1gu7n"] -[ext_resource type="Script" uid="uid://ynx3s1jc6bwq" path="res://src/ui_widgets/BetterToggleButton.gd" id="4_7r848"] +[ext_resource type="Script" uid="uid://ynx3s1jc6bwq" path="res://src/ui_widgets/BetterButton.gd" id="4_7r848"] [node name="RootElementEditor" type="VBoxContainer"] offset_right = 452.0 diff --git a/src/ui_parts/settings_menu.gd b/src/ui_parts/settings_menu.gd index b13e715..2c7a486 100644 --- a/src/ui_parts/settings_menu.gd +++ b/src/ui_parts/settings_menu.gd @@ -190,6 +190,8 @@ func setup_content() -> void: add_section(Translator.translate("Basic colors")) current_setup_setting = "background_color" add_color_edit(Translator.translate("Background color"), false) + current_setup_setting = "grid_color" + add_color_edit(Translator.translate("Grid color"), false) current_setup_setting = "basic_color_valid" add_color_edit(Translator.translate("Valid color")) current_setup_setting = "basic_color_error" @@ -233,18 +235,6 @@ func setup_content() -> void: add_advice(Translator.translate( "Changes the visual size and grabbing area of handles.")) - # Temporarily hiding settings to change scale. - #current_setup_setting = "ui_scale" - #add_number_dropdown(Translator.translate("UI scale"), - #[0.75, 1.0, 1.25, 1.5, 1.75, 2.0, 2.5, 3.0, 4.0], false, false, - #SaveData.UI_SCALE_MIN, SaveData.UI_SCALE_MAX) - #add_advice(Translator.translate( - #"Changes the scale of the visual user interface.")) - #current_setup_setting = "auto_ui_scale" - #add_checkbox(Translator.translate("Auto UI scale")) - #add_advice(Translator.translate( - #"Scales the user interface based on the screen size.")) - # Disable mouse wrap if not available. if not DisplayServer.has_feature(DisplayServer.FEATURE_MOUSE_WARP): wraparound_panning.permanent_disable_checkbox(false) @@ -351,64 +341,15 @@ func _on_language_pressed() -> void: var translation_obj := TranslationServer.get_translation_object(locale) var translated_count := 2 * translation_obj.get_message_count() -\ strings_count - translation_obj.get_translated_message_list().count("") - var percentage :=\ - Utils.num_simple(translated_count * 100.0 / strings_count, 1) + "%" - - var new_btn := ContextPopup.create_button( - TranslationUtils.get_locale_display(locale), Callable(), is_current_locale) - - var ret_button := Button.new() - ret_button.theme_type_variation = "ContextButton" - ret_button.focus_mode = Control.FOCUS_NONE - if is_current_locale: - new_btn.disabled = true - ret_button.disabled = true - else: - ret_button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND - - new_btn.begin_bulk_theme_override() - const CONST_ARR: PackedStringArray = ["normal", "hover", "pressed", "disabled"] - for theme_type in CONST_ARR: - new_btn.add_theme_stylebox_override(theme_type, - new_btn.get_theme_stylebox("normal", "ContextButton")) - new_btn.end_bulk_theme_override() - - var internal_hbox := HBoxContainer.new() - new_btn.mouse_filter = Control.MOUSE_FILTER_IGNORE # Unpressable. - internal_hbox.add_theme_constant_override("separation", 12) - new_btn.add_theme_color_override("icon_normal_color", - ret_button.get_theme_color("icon_normal_color", "ContextButton")) - var label_margin := MarginContainer.new() - label_margin.add_theme_constant_override("margin_right", - int(ret_button.get_theme_stylebox("normal").content_margin_right)) - var label := Label.new() - label.text = percentage - label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT - var shortcut_text_color := ThemeUtils.common_subtle_text_color - if is_current_locale: - shortcut_text_color.a *= 0.75 - label.add_theme_color_override("font_color", shortcut_text_color) - label.add_theme_font_size_override("font_size", - new_btn.get_theme_font_size("font_size")) - - ret_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL - internal_hbox.set_anchors_and_offsets_preset(Control.PRESET_TOP_WIDE) - label_margin.size_flags_horizontal = Control.SIZE_EXPAND_FILL - label.size_flags_horizontal = Control.SIZE_FILL - internal_hbox.add_child(new_btn) - label_margin.add_child(label) - internal_hbox.add_child(label_margin) - ret_button.add_child(internal_hbox) - ret_button.pressed.connect(_on_language_chosen.bind(locale)) - ret_button.pressed.connect(HandlerGUI.remove_popup) - - btn_arr.append(ret_button) + btn_arr.append(ContextPopup.create_button( + TranslationUtils.get_locale_display(locale), + _on_language_chosen.bind(locale), is_current_locale, + null, Utils.num_simple(translated_count * 100.0 / strings_count, 1) + "%")) else: - var new_btn := ContextPopup.create_button( + btn_arr.append(ContextPopup.create_button( TranslationUtils.get_locale_display(locale), - _on_language_chosen.bind(locale), is_current_locale) - btn_arr.append(new_btn) + _on_language_chosen.bind(locale), is_current_locale)) var lang_popup := ContextPopup.new() lang_popup.setup(btn_arr, true) @@ -610,13 +551,13 @@ func show_shortcuts(category: String) -> void: for child in shortcuts_container.get_children(): child.queue_free() - for action in ShortcutUtils.get_shortcuts(category): + for action in ShortcutUtils.get_actions(category): var shortcut_config := ShortcutConfigWidgetScene.instantiate() if\ - ShortcutUtils.is_shortcut_modifiable(action) else\ + ShortcutUtils.is_action_modifiable(action) else\ ShortcutShowcaseWidgetScene.instantiate() shortcuts_container.add_child(shortcut_config) - shortcut_config.label.text = TranslationUtils.get_shortcut_description(action) + shortcut_config.label.text = TranslationUtils.get_action_description(action) shortcut_config.setup(action) func create_setting_container() -> void: diff --git a/src/ui_parts/shortcut_panel.gd b/src/ui_parts/shortcut_panel.gd index b058d5d..bafee80 100644 --- a/src/ui_parts/shortcut_panel.gd +++ b/src/ui_parts/shortcut_panel.gd @@ -129,7 +129,7 @@ func update_layout() -> void: button.custom_minimum_size = Vector2(30, 30) button.size_flags_horizontal = Control.SIZE_SHRINK_CENTER button.size_flags_vertical = Control.SIZE_SHRINK_CENTER - button.icon = ShortcutUtils.get_shortcut_icon(shortcut) + button.icon = ShortcutUtils.get_action_icon(shortcut) button.icon_alignment = HORIZONTAL_ALIGNMENT_CENTER buttons_container.add_child(button) button.pressed.connect(simulate_key_press.bind(shortcut)) diff --git a/src/ui_parts/shortcut_panel_config.gd b/src/ui_parts/shortcut_panel_config.gd index 87ff138..e55d777 100644 --- a/src/ui_parts/shortcut_panel_config.gd +++ b/src/ui_parts/shortcut_panel_config.gd @@ -24,8 +24,8 @@ func _ready() -> void: func update_shortcut_slots() -> void: var shortcut_texts: Dictionary[String, String] = {} # action: action_description - for shortcut in ShortcutUtils.get_all_shortcuts(): - shortcut_texts[shortcut] = TranslationUtils.get_shortcut_description(shortcut) + for shortcut in ShortcutUtils.get_all_actions(): + shortcut_texts[shortcut] = TranslationUtils.get_action_description(shortcut) for child in slot_container.get_children(): child.queue_free() @@ -41,7 +41,7 @@ func update_shortcut_slots() -> void: if not current_shortcut.is_empty(): var icon := TextureRect.new() icon.stretch_mode = TextureRect.STRETCH_KEEP_CENTERED - icon.texture = ShortcutUtils.get_shortcut_icon(current_shortcut) + icon.texture = ShortcutUtils.get_action_icon(current_shortcut) icon_presentation.add_child(icon) hbox.add_child(icon_presentation) @@ -50,7 +50,7 @@ func update_shortcut_slots() -> void: dropdown.size_flags_horizontal = Control.SIZE_EXPAND_FILL dropdown.align_left = true dropdown.value_text_map = shortcut_texts - dropdown.values = ShortcutUtils.get_all_shortcuts() + dropdown.values = ShortcutUtils.get_all_actions() dropdown.disabled_values = Configs.savedata.get_shortcut_panel_slots().values() dropdown.set_value(current_shortcut, false) dropdown.value_changed.connect(_on_dropdown_value_changed.bind(i)) diff --git a/src/ui_parts/tab_bar.gd b/src/ui_parts/tab_bar.gd index c2c2e5f..70af386 100644 --- a/src/ui_parts/tab_bar.gd +++ b/src/ui_parts/tab_bar.gd @@ -25,9 +25,6 @@ var proposed_drop_idx := -1: proposed_drop_idx = new_value queue_redraw() -func _exit_tree() -> void: - RenderingServer.free_rid(ci) - func _ready() -> void: Configs.active_tab_changed.connect(activate) Configs.tabs_changed.connect(activate) @@ -193,42 +190,26 @@ func _gui_input(event: InputEvent) -> void: var btn_arr: Array[Button] = [] if hovered_idx == -1: - btn_arr.append(ContextPopup.create_button( - Translator.translate("Create tab"), Configs.savedata.add_empty_tab, - false, load("res://assets/icons/CreateTab.svg"), "new_tab")) + btn_arr.append(ContextPopup.create_shortcut_button("new_tab")) else: var new_active_tab := Configs.savedata.get_tab(hovered_idx) + var file_absent := not FileAccess.file_exists(new_active_tab.svg_file_path) + var tab_count := Configs.savedata.get_tab_count() - btn_arr.append(ContextPopup.create_button( - Translator.translate("Close tab"), - FileUtils.close_tabs.bind(hovered_idx), false, null, "close_tab")) + btn_arr.append(ContextPopup.create_shortcut_button_without_icon( + "close_tab")) # TODO Unify into "Close multiple tabs" - btn_arr.append(ContextPopup.create_button( - TranslationUtils.get_shortcut_description("close_all_other_tabs"), - FileUtils.close_tabs.bind(hovered_idx, - FileUtils.TabCloseMode.ALL_OTHERS), - Configs.savedata.get_tab_count() < 2, null, "close_all_other_tabs")) - btn_arr.append(ContextPopup.create_button( - TranslationUtils.get_shortcut_description("close_tabs_to_left"), - FileUtils.close_tabs.bind(hovered_idx, - FileUtils.TabCloseMode.TO_LEFT), - hovered_idx == 0, null, "close_tabs_to_left")) - btn_arr.append(ContextPopup.create_button( - TranslationUtils.get_shortcut_description("close_tabs_to_right"), - FileUtils.close_tabs.bind(hovered_idx, - FileUtils.TabCloseMode.TO_RIGHT), - hovered_idx == Configs.savedata.get_tab_count() - 1, - null, "close_tabs_to_right")) - btn_arr.append(ContextPopup.create_button( - Translator.translate("Open externally"), - ShortcutUtils.fn("open_externally"), - not FileAccess.file_exists(new_active_tab.svg_file_path), - load("res://assets/icons/OpenFile.svg"), "open_externally")) - btn_arr.append(ContextPopup.create_button( - Translator.translate("Show in File Manager"), - ShortcutUtils.fn("open_in_folder"), - not FileAccess.file_exists(new_active_tab.svg_file_path), - load("res://assets/icons/OpenFolder.svg"), "open_in_folder")) + btn_arr.append(ContextPopup.create_shortcut_button_without_icon( + "close_all_other_tabs", tab_count < 2)) + btn_arr.append(ContextPopup.create_shortcut_button_without_icon( + "close_tabs_to_left", hovered_idx == 0)) + btn_arr.append(ContextPopup.create_shortcut_button_without_icon( + "close_tabs_to_right", hovered_idx == tab_count - 1)) + + btn_arr.append(ContextPopup.create_shortcut_button("open_externally", + file_absent)) + btn_arr.append(ContextPopup.create_shortcut_button("open_in_folder", + file_absent)) var tab_popup := ContextPopup.new() tab_popup.setup(btn_arr, true, -1, -1, PackedInt32Array([4])) @@ -424,12 +405,10 @@ func _get_tooltip(at_position: Vector2) -> String: return "" var current_tab := Configs.savedata.get_tab(hovered_tab_idx) - if current_tab.svg_file_path.is_empty(): - return Translator.translate("This SVG is not bound to a file location yet.") # We have to pass some metadata to the tooltip. # Since "*" isn't valid in filepaths, we use it as a delimiter. - elif hovered_tab_idx == Configs.savedata.get_active_tab_index(): - return "%s*hovered" % current_tab.get_presented_svg_file_path() + if hovered_tab_idx == Configs.savedata.get_active_tab_index(): + return "%s*active" % current_tab.get_presented_svg_file_path() return "%s*%d" % [current_tab.get_presented_svg_file_path(), current_tab.id] @@ -438,16 +417,20 @@ func _make_custom_tooltip(for_text: String) -> Object: if asterisk_pos == -1: return null + var current_tab := Configs.savedata.get_tab(get_hovered_index()) + var is_saved := not current_tab.svg_file_path.is_empty() + var path := for_text.left(asterisk_pos) var label := Label.new() - label.add_theme_font_override("font", ThemeUtils.mono_font) + label.add_theme_font_override("font", ThemeUtils.mono_font if is_saved else ThemeUtils.regular_font) label.add_theme_font_size_override("font_size", 12) label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART - label.text = path + label.text = path if is_saved else Translator.translate("This SVG is not bound to a file location yet.") Utils.set_max_text_width(label, 192.0, 4.0) + # If the tab is active, no need for an SVG preview. var metadata := for_text.right(-asterisk_pos - 1) - if metadata == "hovered": + if metadata == "active": return label var id := metadata.to_int() diff --git a/src/ui_parts/viewport.gd b/src/ui_parts/viewport.gd index ed2c6df..3ba6537 100644 --- a/src/ui_parts/viewport.gd +++ b/src/ui_parts/viewport.gd @@ -140,7 +140,9 @@ func _unhandled_input(event: InputEvent) -> void: else: if not event.is_echo(): - _zoom_to = Vector2.ZERO # Reset Ctrl + MMB zoom position if released. + # Filter out fake mouse movement events. + if not (event is InputEventMouseMotion and event.relative == Vector2.ZERO): + _zoom_to = Vector2.ZERO # Reset Ctrl + MMB zoom position if released. func _on_zoom_changed(new_zoom_level: float, offset: Vector2) -> void: diff --git a/src/ui_parts/zoom_menu.gd b/src/ui_parts/zoom_menu.gd index 17dca06..9f54853 100644 --- a/src/ui_parts/zoom_menu.gd +++ b/src/ui_parts/zoom_menu.gd @@ -13,25 +13,16 @@ signal zoom_reset_pressed var _zoom_level: float -func update_translation() -> void: - zoom_out_button.tooltip_text = Translator.translate("Zoom out") - zoom_in_button.tooltip_text = Translator.translate("Zoom in") - zoom_reset_button.tooltip_text = Translator.translate("Zoom reset") - -func _ready() -> void: - zoom_out_button.pressed.connect(zoom_out) - zoom_in_button.pressed.connect(zoom_in) - zoom_reset_button.pressed.connect(zoom_reset) - Configs.language_changed.connect(update_translation) - update_translation() - func _unhandled_input(event: InputEvent) -> void: if ShortcutUtils.is_action_pressed(event, "zoom_in"): zoom_in() + accept_event() elif ShortcutUtils.is_action_pressed(event, "zoom_out"): zoom_out() + accept_event() elif ShortcutUtils.is_action_pressed(event, "zoom_reset"): zoom_reset() + accept_event() func set_zoom(new_value: float, offset := Vector2(0.5, 0.5)) -> void: diff --git a/src/ui_parts/zoom_menu.tscn b/src/ui_parts/zoom_menu.tscn index 0c60559..67d5537 100644 --- a/src/ui_parts/zoom_menu.tscn +++ b/src/ui_parts/zoom_menu.tscn @@ -1,26 +1,9 @@ -[gd_scene load_steps=10 format=3 uid="uid://oltvrf01xrxl"] +[gd_scene load_steps=5 format=3 uid="uid://oltvrf01xrxl"] [ext_resource type="Texture2D" uid="uid://c2h5snkvemm4p" path="res://assets/icons/Minus.svg" id="1_8ggy2"] [ext_resource type="Script" uid="uid://dj2q7wnto3uqp" path="res://src/ui_parts/zoom_menu.gd" id="1_18ab8"] [ext_resource type="Texture2D" uid="uid://eif2ioi0mw17" path="res://assets/icons/Plus.svg" id="2_284x5"] - -[sub_resource type="InputEventAction" id="InputEventAction_mnex0"] -action = &"zoom_out" - -[sub_resource type="Shortcut" id="Shortcut_ntgv0"] -events = [SubResource("InputEventAction_mnex0")] - -[sub_resource type="InputEventAction" id="InputEventAction_20462"] -action = &"zoom_reset" - -[sub_resource type="Shortcut" id="Shortcut_4v7wx"] -events = [SubResource("InputEventAction_20462")] - -[sub_resource type="InputEventAction" id="InputEventAction_kt076"] -action = &"zoom_in" - -[sub_resource type="Shortcut" id="Shortcut_y6ouu"] -events = [SubResource("InputEventAction_kt076")] +[ext_resource type="Script" path="res://src/ui_widgets/BetterButton.gd" id="3_vgfv3"] [node name="ZoomMenu" type="HBoxContainer"] offset_right = 114.0 @@ -33,26 +16,26 @@ layout_mode = 2 focus_mode = 0 mouse_default_cursor_shape = 2 theme_type_variation = &"IconButton" -shortcut = SubResource("Shortcut_ntgv0") -shortcut_in_tooltip = false icon = ExtResource("1_8ggy2") icon_alignment = 1 +script = ExtResource("3_vgfv3") +action = "zoom_out" [node name="ZoomReset" type="Button" parent="."] custom_minimum_size = Vector2(58, 0) layout_mode = 2 focus_mode = 0 mouse_default_cursor_shape = 2 -shortcut = SubResource("Shortcut_4v7wx") -shortcut_in_tooltip = false text = "100%" +script = ExtResource("3_vgfv3") +action = "zoom_reset" [node name="ZoomIn" type="Button" parent="."] layout_mode = 2 focus_mode = 0 mouse_default_cursor_shape = 2 theme_type_variation = &"IconButton" -shortcut = SubResource("Shortcut_y6ouu") -shortcut_in_tooltip = false icon = ExtResource("2_284x5") icon_alignment = 1 +script = ExtResource("3_vgfv3") +action = "zoom_in" diff --git a/src/ui_widgets/BetterButton.gd b/src/ui_widgets/BetterButton.gd new file mode 100644 index 0000000..e189e31 --- /dev/null +++ b/src/ui_widgets/BetterButton.gd @@ -0,0 +1,98 @@ +@icon("res://godot_only/icons/BetterButton.svg") +class_name BetterButton extends Button +## A regular Button with some helpers for hover + press theming situations and shortcuts. + +const HIGHLIGHT_TIME = 0.2 + +var just_pressed := false +var timer: SceneTreeTimer + +var _hovered := false + +## Overlaid on top when the Button is hovered while pressed. +@export var hover_pressed_stylebox: StyleBox +## Overlaid on top when the Button is hovered while pressed. +@export var hover_pressed_font_color := Color.TRANSPARENT + +## A shortcut that corresponds to the same action that this button does. +@export var action := "" + + +func _ready() -> void: + if not action.is_empty() and not toggle_mode: + pressed.connect(_on_pressed) + + mouse_entered.connect(_on_mouse_entered) + mouse_exited.connect(_on_mouse_exited) + add_theme_color_override("font_hover_color", get_theme_color( + "font_hover_color", "Button").blend(hover_pressed_font_color)) + +func _on_mouse_entered() -> void: + _hovered = true + if not disabled and hover_pressed_font_color != Color.BLACK: + add_theme_color_override("font_pressed_color", get_theme_color( + "font_pressed_color", "Button").blend(hover_pressed_font_color)) + queue_redraw() + +func _on_mouse_exited() -> void: + _hovered = false + remove_theme_color_override("font_pressed_color") + queue_redraw() + +func _draw() -> void: + if _hovered and not disabled and button_pressed and\ + is_instance_valid(hover_pressed_stylebox): + draw_style_box(hover_pressed_stylebox, Rect2(Vector2.ZERO, size)) + + +func _make_custom_tooltip(_for_text: String) -> Object: + if action.is_empty(): + return null + + var action_showcase_text := ShortcutUtils.get_action_showcase_text(action) + + var main_label := Label.new() + main_label.add_theme_font_size_override("font_size", + get_theme_font_size("font_size", "TooltipLabel")) + main_label.add_theme_color_override("font_color", + get_theme_color("font_color", "TooltipLabel")) + main_label.text = TranslationUtils.get_action_description(action, true) + + if action_showcase_text.is_empty(): + return main_label + + var shortcut_label := Label.new() + shortcut_label.add_theme_font_size_override("font_size", + get_theme_font_size("font_size", "TooltipLabel")) + shortcut_label.add_theme_color_override("font_color", + ThemeUtils.common_subtle_text_color) + shortcut_label.text = "(%s)" % action_showcase_text + + var hbox := HBoxContainer.new() + hbox.add_theme_constant_override("separation", 10) + hbox.add_child(main_label) + hbox.add_child(shortcut_label) + return hbox + +func _on_pressed() -> void: + just_pressed = true + set_deferred("just_pressed", false) + HandlerGUI.throw_action_event(action) + +func _unhandled_input(event: InputEvent) -> void: + if action.is_empty() or toggle_mode: + return + + if not just_pressed and ShortcutUtils.is_action_pressed(event, action) and\ + not is_instance_valid(timer): + add_theme_color_override("icon_normal_color", get_theme_color("icon_pressed_color")) + add_theme_color_override("icon_hover_color", get_theme_color("icon_pressed_color")) + add_theme_stylebox_override("normal", get_theme_stylebox("pressed")) + timer = get_tree().create_timer(HIGHLIGHT_TIME) + timer.timeout.connect(end_highlight) + +func end_highlight() -> void: + remove_theme_color_override("icon_normal_color") + remove_theme_color_override("icon_hover_color") + remove_theme_stylebox_override("normal") + timer = null diff --git a/src/ui_widgets/BetterToggleButton.gd.uid b/src/ui_widgets/BetterButton.gd.uid similarity index 100% rename from src/ui_widgets/BetterToggleButton.gd.uid rename to src/ui_widgets/BetterButton.gd.uid diff --git a/src/ui_widgets/BetterLineEdit.gd b/src/ui_widgets/BetterLineEdit.gd index 48967cf..189636a 100644 --- a/src/ui_widgets/BetterLineEdit.gd +++ b/src/ui_widgets/BetterLineEdit.gd @@ -18,6 +18,10 @@ func _set(property: StringName, value: Variant) -> bool: return false func _init() -> void: + # Solves an issue where Ctrl+S would type an "s" and handle the input. + # We want anything with Ctrl to not be handled, but other keys to still be handled. + set_process_unhandled_key_input(false) + context_menu_enabled = false caret_blink = true caret_blink_interval = 0.6 @@ -95,6 +99,15 @@ func _input(event: InputEvent) -> void: select_all() func _gui_input(event: InputEvent) -> void: + if event.is_action_pressed("select_all"): + menu_option(MENU_SELECT_ALL) + accept_event() + return + + if event.is_action_pressed("ui_cancel"): + release_focus() + return + mouse_filter = Utils.mouse_filter_pass_non_drag_events(event) if event is InputEventMouseMotion and event.button_mask == 0: @@ -105,28 +118,20 @@ func _gui_input(event: InputEvent) -> void: grab_focus() var btn_arr: Array[Button] = [] var separator_arr: Array[int] = [] + + var is_text_empty := text.is_empty() + if editable: - btn_arr.append(ContextPopup.create_button(Translator.translate("Undo"), - menu_option.bind(LineEdit.MENU_UNDO), not has_undo(), - load("res://assets/icons/Undo.svg"), "ui_undo")) - btn_arr.append(ContextPopup.create_button(Translator.translate("Redo"), - menu_option.bind(LineEdit.MENU_REDO), not has_redo(), - load("res://assets/icons/Redo.svg"), "ui_redo")) + btn_arr.append(ContextPopup.create_shortcut_button("ui_undo", not has_undo())) + btn_arr.append(ContextPopup.create_shortcut_button("ui_redo", not has_redo())) if DisplayServer.has_feature(DisplayServer.FEATURE_CLIPBOARD): separator_arr = [2] - btn_arr.append(ContextPopup.create_button(Translator.translate("Cut"), - menu_option.bind(LineEdit.MENU_CUT), text.is_empty(), - load("res://assets/icons/Cut.svg"), "ui_cut")) - btn_arr.append(ContextPopup.create_button(Translator.translate("Copy"), - menu_option.bind(LineEdit.MENU_COPY), text.is_empty(), - load("res://assets/icons/Copy.svg"), "ui_copy")) - btn_arr.append(ContextPopup.create_button(Translator.translate("Paste"), - menu_option.bind(LineEdit.MENU_PASTE), !Utils.has_clipboard_web_safe(), - load("res://assets/icons/Paste.svg"), "ui_paste")) + btn_arr.append(ContextPopup.create_shortcut_button("ui_cut", is_text_empty)) + btn_arr.append(ContextPopup.create_shortcut_button("ui_copy", is_text_empty)) + btn_arr.append(ContextPopup.create_shortcut_button("ui_paste", + not Utils.has_clipboard_web_safe())) else: - btn_arr.append(ContextPopup.create_button( Translator.translate("Copy"), - menu_option.bind(LineEdit.MENU_COPY), text.is_empty(), - load("res://assets/icons/Copy.svg"), "ui_copy")) + btn_arr.append(ContextPopup.create_shortcut_button("ui_copy", is_text_empty)) var vp := get_viewport() var context_popup := ContextPopup.new() diff --git a/src/ui_widgets/BetterTextEdit.gd b/src/ui_widgets/BetterTextEdit.gd index 61854ad..914e820 100644 --- a/src/ui_widgets/BetterTextEdit.gd +++ b/src/ui_widgets/BetterTextEdit.gd @@ -12,6 +12,10 @@ var _is_caret_queued_for_redraw := false var _hovered := false func _init() -> void: + # Solves an issue where Ctrl+S would type an "s" and handle the input. + # We want anything with Ctrl to not be handled, but other keys to still be handled. + set_process_unhandled_key_input(false) + context_menu_enabled = false wrap_mode = TextEdit.LINE_WRAPPING_BOUNDARY scroll_smooth = true @@ -118,6 +122,11 @@ func _input(event: InputEvent) -> void: release_focus() func _gui_input(event: InputEvent) -> void: + if event.is_action_pressed("select_all"): + select_all() + accept_event() + return + if event.is_action_pressed("ui_cancel"): release_focus() return @@ -127,34 +136,25 @@ func _gui_input(event: InputEvent) -> void: if event is InputEventMouseMotion and event.button_mask == 0: _hovered = true queue_redraw() - if event is InputEventMouseButton: + elif event is InputEventMouseButton: if event.button_index == MOUSE_BUTTON_RIGHT and event.is_pressed(): grab_focus() var btn_arr: Array[Button] = [] var separator_arr := PackedInt32Array() + + var is_text_empty := text.is_empty() + if editable: - btn_arr.append(ContextPopup.create_button( - Translator.translate("Undo"), undo, - not has_undo(), load("res://assets/icons/Undo.svg"), "ui_undo")) - btn_arr.append(ContextPopup.create_button( - Translator.translate("Redo"), redo, - not has_redo(), load("res://assets/icons/Redo.svg"), "ui_redo")) + btn_arr.append(ContextPopup.create_shortcut_button("ui_undo", not has_undo())) + btn_arr.append(ContextPopup.create_shortcut_button("ui_redo", not has_redo())) if DisplayServer.has_feature(DisplayServer.FEATURE_CLIPBOARD): separator_arr = PackedInt32Array([2]) - btn_arr.append(ContextPopup.create_button( - Translator.translate("Cut"), cut, - text.is_empty(), load("res://assets/icons/Cut.svg"), "ui_cut")) - btn_arr.append(ContextPopup.create_button( - Translator.translate("Copy"), copy, - text.is_empty(), load("res://assets/icons/Copy.svg"), "ui_copy")) - btn_arr.append(ContextPopup.create_button( - Translator.translate("Paste"), paste, - !Utils.has_clipboard_web_safe(), - load("res://assets/icons/Paste.svg"), "ui_paste")) + btn_arr.append(ContextPopup.create_shortcut_button("ui_cut", is_text_empty)) + btn_arr.append(ContextPopup.create_shortcut_button("ui_copy", is_text_empty)) + btn_arr.append(ContextPopup.create_shortcut_button("ui_paste", + not Utils.has_clipboard_web_safe())) else: - btn_arr.append(ContextPopup.create_button( - Translator.translate("Copy"), copy, - text.is_empty(), load("res://assets/icons/Copy.svg"), "ui_copy")) + btn_arr.append(ContextPopup.create_shortcut_button("ui_copy", is_text_empty)) var context_popup := ContextPopup.new() context_popup.setup(btn_arr, true, -1, -1, separator_arr) @@ -166,11 +166,11 @@ func _gui_input(event: InputEvent) -> void: set_caret_column(click_pos.x, false) else: # Set these inputs as handled, so the default UndoRedo doesn't eat them. - if ShortcutUtils.is_action_pressed(event, "redo"): + if ShortcutUtils.is_action_pressed(event, "ui_redo"): if has_redo(): redo() accept_event() - elif ShortcutUtils.is_action_pressed(event, "undo"): + elif ShortcutUtils.is_action_pressed(event, "ui_undo"): if has_undo(): undo() accept_event() diff --git a/src/ui_widgets/BetterToggleButton.gd b/src/ui_widgets/BetterToggleButton.gd deleted file mode 100644 index d8bbff9..0000000 --- a/src/ui_widgets/BetterToggleButton.gd +++ /dev/null @@ -1,31 +0,0 @@ -class_name BetterToggleButton extends Button -## A regular Button that overlays a stylebox when hovered while pressed. - -var _hovered := false - -# Overlayed on top when the Button is hovered while pressed. -@export var hover_pressed_stylebox: StyleBox -@export var hover_pressed_font_color: Color - -func _ready() -> void: - mouse_entered.connect(_on_mouse_entered) - mouse_exited.connect(_on_mouse_exited) - add_theme_color_override("font_hover_color", get_theme_color( - "font_hover_color", "Button").blend(hover_pressed_font_color)) - -func _on_mouse_entered() -> void: - _hovered = true - if not disabled and hover_pressed_font_color != Color.BLACK: - add_theme_color_override("font_pressed_color", get_theme_color( - "font_pressed_color", "Button").blend(hover_pressed_font_color)) - queue_redraw() - -func _on_mouse_exited() -> void: - _hovered = false - remove_theme_color_override("font_pressed_color") - queue_redraw() - -func _draw() -> void: - if not disabled and button_pressed and _hovered and\ - is_instance_valid(hover_pressed_stylebox): - draw_style_box(hover_pressed_stylebox, Rect2(Vector2.ZERO, size)) diff --git a/src/ui_widgets/ContextPopup.gd b/src/ui_widgets/ContextPopup.gd index 2238074..3a5b141 100644 --- a/src/ui_widgets/ContextPopup.gd +++ b/src/ui_widgets/ContextPopup.gd @@ -7,79 +7,104 @@ func _init() -> void: mouse_filter = Control.MOUSE_FILTER_STOP +static func create_shortcut_button(action: String, disabled := false, +custom_text := "", custom_icon: Texture2D = null) -> Button: + if not InputMap.has_action(action): + push_error("Non-existent shortcut was passed.") + return + + if custom_text.is_empty(): + custom_text = TranslationUtils.get_action_description(action, true) + if not is_instance_valid(custom_icon): + custom_icon = ShortcutUtils.get_action_icon(action) + var btn := create_button(custom_text, HandlerGUI.throw_action_event.bind(action), + disabled, custom_icon, ShortcutUtils.get_action_showcase_text(action)) + + var shortcut_events := ShortcutUtils.get_action_all_valid_shortcuts(action) + if not shortcut_events.is_empty(): + var shortcut_obj := Shortcut.new() + shortcut_obj.events = shortcut_events + btn.shortcut = shortcut_obj + btn.shortcut_feedback = false + + return btn + +static func create_shortcut_button_without_icon(action: String, disabled := false, +custom_text := "") -> Button: + if not InputMap.has_action(action): + push_error("Non-existent shortcut was passed.") + return + + if custom_text.is_empty(): + custom_text = TranslationUtils.get_action_description(action, true) + var btn := create_button(custom_text, HandlerGUI.throw_action_event.bind(action), + disabled, null, ShortcutUtils.get_action_showcase_text(action)) + + var shortcut_events := ShortcutUtils.get_action_all_valid_shortcuts(action) + if not shortcut_events.is_empty(): + var shortcut_obj := Shortcut.new() + shortcut_obj.events = shortcut_events + btn.shortcut = shortcut_obj + btn.shortcut_feedback = false + + return btn + static func create_button(text: String, press_callback: Callable, disabled := false, -icon: Texture2D = null, shortcut := "") -> Button: +icon: Texture2D = null, dim_text := "") -> Button: # Create main button. var main_button := Button.new() main_button.text = text if is_instance_valid(icon): main_button.icon = icon - - if not shortcut.is_empty(): - if not InputMap.has_action(shortcut): - push_error("Non-existent shortcut was passed to ContextPopup.create_button().") + + if not dim_text.is_empty(): + # Add button with dim text. + var ret_button := Button.new() + ret_button.theme_type_variation = "ContextButton" + ret_button.focus_mode = Control.FOCUS_NONE + ret_button.shortcut_in_tooltip = false + if disabled: + main_button.disabled = true + ret_button.disabled = true else: - var events := InputMap.action_get_events(shortcut) - var showcased_event: InputEventKey - for event in events: - if Configs.savedata.is_shortcut_valid(event): - showcased_event = event - - if is_instance_valid(showcased_event): - # Add button with a shortcut. - var ret_button := Button.new() - ret_button.theme_type_variation = "ContextButton" - ret_button.focus_mode = Control.FOCUS_NONE - ret_button.shortcut_in_tooltip = false - if disabled: - main_button.disabled = true - ret_button.disabled = true - else: - ret_button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND - - const CONST_ARR: PackedStringArray = ["normal", "hover", "pressed", "disabled"] - main_button.begin_bulk_theme_override() - for theme_type in CONST_ARR: - main_button.add_theme_stylebox_override(theme_type, - main_button.get_theme_stylebox("normal", "ContextButton")) - main_button.end_bulk_theme_override() - - var internal_hbox := HBoxContainer.new() - main_button.mouse_filter = Control.MOUSE_FILTER_IGNORE # Unpressable. - internal_hbox.add_theme_constant_override("separation", 12) - main_button.add_theme_color_override("icon_normal_color", - ret_button.get_theme_color("icon_normal_color", "ContextButton")) - var label_margin := MarginContainer.new() - label_margin.add_theme_constant_override("margin_right", - int(ret_button.get_theme_stylebox("normal").content_margin_right)) - var label := Label.new() - label.text = showcased_event.as_text_keycode() - label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT - var shortcut_text_color := ThemeUtils.common_subtle_text_color - if disabled: - shortcut_text_color.a *= 0.75 - label.add_theme_color_override("font_color", shortcut_text_color) - label.add_theme_font_size_override("font_size", - main_button.get_theme_font_size("font_size")) - - ret_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL - internal_hbox.set_anchors_and_offsets_preset(Control.PRESET_TOP_WIDE) - label_margin.size_flags_horizontal = Control.SIZE_EXPAND_FILL - label.size_flags_horizontal = Control.SIZE_FILL - internal_hbox.add_child(main_button) - label_margin.add_child(label) - internal_hbox.add_child(label_margin) - ret_button.add_child(internal_hbox) - ret_button.pressed.connect(press_callback) - ret_button.pressed.connect(HandlerGUI.remove_popup) - - var shortcut_obj := Shortcut.new() - var action_obj := InputEventAction.new() - action_obj.action = shortcut - shortcut_obj.events.append(action_obj) - ret_button.shortcut = shortcut_obj - ret_button.shortcut_feedback = false - return ret_button + ret_button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND + + const CONST_ARR: PackedStringArray = ["normal", "hover", "pressed", "disabled"] + main_button.begin_bulk_theme_override() + for theme_type in CONST_ARR: + main_button.add_theme_stylebox_override(theme_type, + main_button.get_theme_stylebox("normal", "ContextButton")) + main_button.end_bulk_theme_override() + + var internal_hbox := HBoxContainer.new() + main_button.mouse_filter = Control.MOUSE_FILTER_IGNORE # Unpressable. + internal_hbox.add_theme_constant_override("separation", 12) + main_button.add_theme_color_override("icon_normal_color", + ret_button.get_theme_color("icon_normal_color", "ContextButton")) + var label_margin := MarginContainer.new() + label_margin.add_theme_constant_override("margin_right", + int(ret_button.get_theme_stylebox("normal").content_margin_right)) + var label := Label.new() + label.text = dim_text + label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT + var shortcut_text_color := ThemeUtils.common_subtle_text_color + if disabled: + shortcut_text_color.a *= 0.75 + label.add_theme_color_override("font_color", shortcut_text_color) + label.add_theme_font_size_override("font_size", + main_button.get_theme_font_size("font_size")) + + ret_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL + internal_hbox.set_anchors_and_offsets_preset(Control.PRESET_TOP_WIDE) + label_margin.size_flags_horizontal = Control.SIZE_EXPAND_FILL + label.size_flags_horizontal = Control.SIZE_FILL + internal_hbox.add_child(main_button) + label_margin.add_child(label) + internal_hbox.add_child(label_margin) + ret_button.add_child(internal_hbox) + ret_button.pressed.connect(press_callback) + ret_button.pressed.connect(HandlerGUI.remove_popup) + return ret_button # Finish setting up the main button and return it if there's no shortcut. main_button.theme_type_variation = "ContextButton" main_button.focus_mode = Control.FOCUS_NONE @@ -92,75 +117,71 @@ icon: Texture2D = null, shortcut := "") -> Button: main_button.pressed.connect(HandlerGUI.remove_popup) return main_button + +static func create_shortcut_checkbox(action: String, start_pressed: bool, +custom_text := "") -> CheckBox: + if not InputMap.has_action(action): + push_error("Non-existent shortcut was passed.") + return + + if custom_text.is_empty(): + custom_text = TranslationUtils.get_action_description(action, true) + + return create_checkbox(custom_text, HandlerGUI.throw_action_event.bind(action), + start_pressed, ShortcutUtils.get_action_showcase_text(action)) + static func create_checkbox(text: String, toggle_action: Callable, -start_pressed: bool, shortcut := "") -> CheckBox: +start_pressed: bool, dim_text := "") -> CheckBox: # Create main checkbox. var checkbox := CheckBox.new() checkbox.text = text checkbox.button_pressed = start_pressed checkbox.toggled.connect(toggle_action.unbind(1)) - if not shortcut.is_empty(): - if not InputMap.has_action(shortcut): - push_error("Non-existent shortcut was passed to ContextPopup.create_checkbox().") - else: - var events := InputMap.action_get_events(shortcut) - var showcased_event: InputEventKey - for event in events: - if Configs.savedata.is_shortcut_valid(event): - showcased_event = event - - if is_instance_valid(showcased_event): - # Add button with a shortcut. - var ret_button := Button.new() - ret_button.theme_type_variation = "ContextButton" - ret_button.focus_mode = Control.FOCUS_NONE - ret_button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND - ret_button.shortcut_in_tooltip = false - - checkbox.begin_bulk_theme_override() - const CONST_ARR: PackedStringArray = ["normal", "pressed"] - for theme_type in CONST_ARR: - checkbox.add_theme_stylebox_override(theme_type, - checkbox.get_theme_stylebox("normal", "ContextButton")) - checkbox.end_bulk_theme_override() - - var internal_hbox := HBoxContainer.new() - checkbox.mouse_filter = Control.MOUSE_FILTER_IGNORE # Unpressable. - internal_hbox.add_theme_constant_override("separation", 12) - checkbox.add_theme_color_override("icon_normal_color", - ret_button.get_theme_color("icon_normal_color", "ContextButton")) - var label_margin := MarginContainer.new() - label_margin.add_theme_constant_override("margin_right", - int(ret_button.get_theme_stylebox("normal").content_margin_right)) - var label := Label.new() - label.text = showcased_event.as_text_keycode() - label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT - var shortcut_text_color := ThemeUtils.common_subtle_text_color - #if disabled: - #shortcut_text_color.a *= 0.75 - label.add_theme_color_override("font_color", shortcut_text_color) - label.add_theme_font_size_override("font_size", - checkbox.get_theme_font_size("font_size")) - - ret_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL - internal_hbox.set_anchors_and_offsets_preset(Control.PRESET_TOP_WIDE) - label_margin.size_flags_horizontal = Control.SIZE_EXPAND_FILL - label.size_flags_horizontal = Control.SIZE_FILL - internal_hbox.add_child(checkbox) - label_margin.add_child(label) - internal_hbox.add_child(label_margin) - ret_button.add_child(internal_hbox) - ret_button.pressed.connect( - func() -> void: checkbox.button_pressed = not checkbox.button_pressed) - - var shortcut_obj := Shortcut.new() - var action_obj := InputEventAction.new() - action_obj.action = shortcut - shortcut_obj.events.append(action_obj) - ret_button.shortcut = shortcut_obj - ret_button.shortcut_feedback = false - return ret_button + if not dim_text.is_empty(): + # Add button with dim text. + var ret_button := Button.new() + ret_button.theme_type_variation = "ContextButton" + ret_button.focus_mode = Control.FOCUS_NONE + ret_button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND + ret_button.shortcut_in_tooltip = false + + checkbox.begin_bulk_theme_override() + const CONST_ARR: PackedStringArray = ["normal", "pressed"] + for theme_type in CONST_ARR: + checkbox.add_theme_stylebox_override(theme_type, + checkbox.get_theme_stylebox("normal", "ContextButton")) + checkbox.end_bulk_theme_override() + + var internal_hbox := HBoxContainer.new() + checkbox.mouse_filter = Control.MOUSE_FILTER_IGNORE # Unpressable. + internal_hbox.add_theme_constant_override("separation", 12) + checkbox.add_theme_color_override("icon_normal_color", + ret_button.get_theme_color("icon_normal_color", "ContextButton")) + var label_margin := MarginContainer.new() + label_margin.add_theme_constant_override("margin_right", + int(ret_button.get_theme_stylebox("normal").content_margin_right)) + var label := Label.new() + label.text = dim_text + label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT + var shortcut_text_color := ThemeUtils.common_subtle_text_color + #if disabled: + #shortcut_text_color.a *= 0.75 + label.add_theme_color_override("font_color", shortcut_text_color) + label.add_theme_font_size_override("font_size", + checkbox.get_theme_font_size("font_size")) + + ret_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL + internal_hbox.set_anchors_and_offsets_preset(Control.PRESET_TOP_WIDE) + label_margin.size_flags_horizontal = Control.SIZE_EXPAND_FILL + label.size_flags_horizontal = Control.SIZE_FILL + internal_hbox.add_child(checkbox) + label_margin.add_child(label) + internal_hbox.add_child(label_margin) + ret_button.add_child(internal_hbox) + ret_button.pressed.connect( + func() -> void: checkbox.button_pressed = not checkbox.button_pressed) + return ret_button # Finish setting up the checkbox and return it if there's no shortcut. checkbox.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND checkbox.focus_mode = Control.FOCUS_NONE @@ -216,38 +237,38 @@ min_width := -1.0, max_height := -1.0, separator_indices := PackedInt32Array()) # Add the buttons. if buttons.is_empty(): return - else: - # Setup the title. - var title_container := PanelContainer.new() - var stylebox := StyleBoxFlat.new() - stylebox.bg_color = Color("0003") - stylebox.content_margin_bottom = 3 - stylebox.content_margin_left = 8 - stylebox.content_margin_right = 8 - stylebox.border_width_bottom = 2 - stylebox.border_color = ThemeUtils.common_panel_border_color - title_container.add_theme_stylebox_override("panel", stylebox) - var title_label := Label.new() - title_label.text = top_title - title_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER - title_label.begin_bulk_theme_override() - title_label.add_theme_color_override("font_color", Color("def")) - title_label.add_theme_font_size_override("font_size", 14) - title_label.end_bulk_theme_override() - title_container.add_child(title_label) - main_container.add_child(title_container) - # Continue with regular setup logic. - for idx in buttons.size(): - if idx in separator_indices: - var separator := HSeparator.new() - separator.theme_type_variation = "SmallHSeparator" - main_container.add_child(separator) - main_container.add_child(_setup_button(buttons[idx], align_left)) - if min_width > 0: - custom_minimum_size.x = min_width - if max_height > 0 and max_height < get_minimum_size().y: - custom_minimum_size.y = max_height - main_container.get_parent().vertical_scroll_mode = ScrollContainer.SCROLL_MODE_AUTO + + # Setup the title. + var title_container := PanelContainer.new() + var stylebox := StyleBoxFlat.new() + stylebox.bg_color = Color("0003") + stylebox.content_margin_bottom = 3 + stylebox.content_margin_left = 8 + stylebox.content_margin_right = 8 + stylebox.border_width_bottom = 2 + stylebox.border_color = ThemeUtils.common_panel_border_color + title_container.add_theme_stylebox_override("panel", stylebox) + var title_label := Label.new() + title_label.text = top_title + title_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + title_label.begin_bulk_theme_override() + title_label.add_theme_color_override("font_color", Color("def")) + title_label.add_theme_font_size_override("font_size", 14) + title_label.end_bulk_theme_override() + title_container.add_child(title_label) + main_container.add_child(title_container) + # Continue with regular setup logic. + for idx in buttons.size(): + if idx in separator_indices: + var separator := HSeparator.new() + separator.theme_type_variation = "SmallHSeparator" + main_container.add_child(separator) + main_container.add_child(_setup_button(buttons[idx], align_left)) + if min_width > 0: + custom_minimum_size.x = min_width + if max_height > 0 and max_height < get_minimum_size().y: + custom_minimum_size.y = max_height + main_container.get_parent().vertical_scroll_mode = ScrollContainer.SCROLL_MODE_AUTO # Helper. diff --git a/src/ui_widgets/UndoRedoRef.gd b/src/ui_widgets/UndoRedoRef.gd index 9ef765d..dcbbf59 100644 --- a/src/ui_widgets/UndoRedoRef.gd +++ b/src/ui_widgets/UndoRedoRef.gd @@ -49,5 +49,5 @@ func has_redo() -> bool: func _notification(what: int) -> void: if what == NOTIFICATION_PREDELETE: - if _undo_redo: + if is_instance_valid(_undo_redo): _undo_redo.free() diff --git a/src/ui_widgets/camera.gd b/src/ui_widgets/camera.gd index 36eb134..498b1f7 100644 --- a/src/ui_widgets/camera.gd +++ b/src/ui_widgets/camera.gd @@ -1,10 +1,11 @@ extends Control -const axis_line_color = Color(0.5, 0.5, 0.5, 0.75) -const major_grid_color = Color(0.5, 0.5, 0.5, 0.35) -const minor_grid_color = Color(0.5, 0.5, 0.5, 0.15) const ticks_interval = 4 +var axis_line_color: Color +var major_grid_color: Color +var minor_grid_color: Color + var limit_left := 0.0 var limit_right := 0.0 var limit_top := 0.0 @@ -18,6 +19,8 @@ var unsnapped_position: Vector2 func _ready() -> void: + Configs.grid_color_changed.connect(setup_grid_color) + setup_grid_color() RenderingServer.canvas_item_set_parent(surface, ci) State.svg_resized.connect(queue_redraw) State.zoom_changed.connect(change_zoom) @@ -117,3 +120,10 @@ func _draw() -> void: draw_multiline(major_points, major_grid_color) if not minor_points.is_empty(): draw_multiline(minor_points, minor_grid_color) + + +func setup_grid_color() -> void: + axis_line_color = Color(Configs.savedata.grid_color, 0.75) + major_grid_color = Color(Configs.savedata.grid_color, 0.35) + minor_grid_color = Color(Configs.savedata.grid_color, 0.15) + queue_redraw() diff --git a/src/ui_widgets/dropdown.tscn b/src/ui_widgets/dropdown.tscn index a65bb90..6c3e063 100644 --- a/src/ui_widgets/dropdown.tscn +++ b/src/ui_widgets/dropdown.tscn @@ -26,6 +26,7 @@ custom_minimum_size = Vector2(15, 0) layout_mode = 2 size_flags_horizontal = 8 focus_mode = 0 +mouse_filter = 1 mouse_default_cursor_shape = 2 theme_type_variation = &"LeftConnectedButton" icon = ExtResource("2_4oygd") diff --git a/src/ui_widgets/good_color_picker.gd b/src/ui_widgets/good_color_picker.gd index 1e26014..260a311 100644 --- a/src/ui_widgets/good_color_picker.gd +++ b/src/ui_widgets/good_color_picker.gd @@ -49,7 +49,7 @@ var slider_mode: SliderMode: var color_wheel_surface := RenderingServer.canvas_item_create() -# 0 is the side slider, 1-3 are the remaining sliders. +# 0 is the side slider, 1-3 are the remaining sliders, 4 is the alpha slider. var sliders_dragged: Array[bool] = [false, false, false, false, false] # Tracks are the color rects of the sliders. @onready var tracks_arr: Array[ColorRect] = [ @@ -464,6 +464,16 @@ func update_color_button() -> void: reset_color_button.end_bulk_theme_override() func hex(col: Color) -> String: + # Removing the saturation and hue clamping fixes hex conversion in edge cases. + # e.g., H = 0.0001, S = 0.0001, V = 0.5 --> Color(0.5, 0.4999, 0.4999) --> "807f7f". + if col.s < 0.001: + col.s = 0.0 + + if col.h < 0.001: + col.h = 0.0 + elif col.h > 0.999: + col.h = 1.0 + return col.to_html(alpha_enabled and col.a != 1.0) @@ -471,11 +481,11 @@ func _input(event: InputEvent) -> void: if not visible: return - if ShortcutUtils.is_action_pressed(event, "redo"): + if ShortcutUtils.is_action_pressed(event, "ui_redo"): if undo_redo.has_redo(): undo_redo.redo() accept_event() - elif ShortcutUtils.is_action_pressed(event, "undo"): + elif ShortcutUtils.is_action_pressed(event, "ui_undo"): if undo_redo.has_undo(): undo_redo.undo() accept_event() diff --git a/src/ui_widgets/number_dropdown.tscn b/src/ui_widgets/number_dropdown.tscn index d415eca..2e7aacc 100644 --- a/src/ui_widgets/number_dropdown.tscn +++ b/src/ui_widgets/number_dropdown.tscn @@ -24,6 +24,7 @@ script = ExtResource("3_y7lt6") custom_minimum_size = Vector2(15, 0) layout_mode = 2 focus_mode = 0 +mouse_filter = 1 mouse_default_cursor_shape = 2 theme_type_variation = &"LeftConnectedButton" icon = ExtResource("4_vet1k") diff --git a/src/ui_widgets/path_command_button.gd b/src/ui_widgets/path_command_button.gd index 7c240b3..8787b5f 100644 --- a/src/ui_widgets/path_command_button.gd +++ b/src/ui_widgets/path_command_button.gd @@ -35,7 +35,8 @@ func _draw() -> void: var right_margin := get_theme_stylebox("normal").content_margin_right var max_size := size.x - left_margin - right_margin var bold_text := command_char + ":" - var normal_text := " " + TranslationUtils.get_command_description(command_char) + var normal_text := " " + TranslationUtils.get_path_command_description(command_char, + true) # Try with font size 13. text_obj.add_string(bold_text, ThemeUtils.bold_font, 13) text_obj.add_string(normal_text, ThemeUtils.regular_font, 13) diff --git a/src/ui_widgets/pathdata_field.gd b/src/ui_widgets/pathdata_field.gd index c023d38..f2ea075 100644 --- a/src/ui_widgets/pathdata_field.gd +++ b/src/ui_widgets/pathdata_field.gd @@ -395,10 +395,8 @@ func setup_path_command_controls(idx: int) -> Control: relative_button.add_theme_stylebox_override("pressed", relative_button_pressed) relative_button.end_bulk_theme_override() relative_button.text = cmd_char - relative_button.tooltip_text = "%s (%s)" %\ - [TranslationUtils.get_command_description(cmd_char), - Translator.translate("Absolute") if is_absolute\ - else Translator.translate("Relative")] + relative_button.tooltip_text =\ + TranslationUtils.get_path_command_description(cmd_char) container.add_child(relative_button) relative_button.pressed.connect(_on_relative_button_pressed) relative_button.gui_input.connect(_eat_double_clicks.bind(relative_button)) diff --git a/src/ui_widgets/presented_shortcut.gd b/src/ui_widgets/presented_shortcut.gd index d164e2e..0dd8240 100644 --- a/src/ui_widgets/presented_shortcut.gd +++ b/src/ui_widgets/presented_shortcut.gd @@ -34,14 +34,14 @@ func check_shortcuts_validity() -> void: for i in events.size(): var shortcut_btn := shortcut_container.get_child(i) if not Configs.savedata.is_shortcut_valid(events[i]): - var error_color := Color(Configs.savedata.basic_color_error, 0.8) - shortcut_btn.add_theme_color_override("font_disabled_color", error_color) + var warning_color := Color(Configs.savedata.basic_color_warning, 0.8) + shortcut_btn.add_theme_color_override("font_disabled_color", warning_color) var conflicts := Configs.savedata.get_actions_with_shortcut(events[i]) var action_pos := conflicts.find(action) if action_pos != -1: conflicts.remove_at(action_pos) for ii in conflicts.size(): - conflicts[ii] = TranslationUtils.get_shortcut_description(conflicts[ii]) + conflicts[ii] = TranslationUtils.get_action_description(conflicts[ii]) if conflicts.size() > 8: conflicts.resize(8) conflicts.append("...") diff --git a/src/ui_widgets/setting_shortcut.gd b/src/ui_widgets/setting_shortcut.gd index acaf075..5d90d6e 100644 --- a/src/ui_widgets/setting_shortcut.gd +++ b/src/ui_widgets/setting_shortcut.gd @@ -215,7 +215,7 @@ func check_shortcuts_validity() -> void: if action_pos != -1: conflicts.remove_at(action_pos) for ii in conflicts.size(): - conflicts[ii] = TranslationUtils.get_shortcut_description(conflicts[ii]) + conflicts[ii] = TranslationUtils.get_action_description(conflicts[ii]) if conflicts.size() > 8: conflicts.resize(8) conflicts.append("...") diff --git a/src/ui_widgets/transform_popup.gd b/src/ui_widgets/transform_popup.gd index 95360c6..c13a785 100644 --- a/src/ui_widgets/transform_popup.gd +++ b/src/ui_widgets/transform_popup.gd @@ -171,7 +171,8 @@ func popup_new_transform_context(idx: int, control: Control) -> void: "skewX", "skewY"] for transform_type in CONST_ARR: var btn := ContextPopup.create_button(transform_type, - insert_transform.bind(idx, transform_type), false, _icons_dict[transform_type]) + insert_transform.bind(idx, transform_type), false, + _icons_dict[transform_type]) btn.add_theme_font_override("font", ThemeUtils.mono_font) btn_array.append(btn) @@ -183,10 +184,10 @@ func popup_new_transform_context(idx: int, control: Control) -> void: func _unhandled_input(event: InputEvent) -> void: - if ShortcutUtils.is_action_pressed(event, "redo"): + if ShortcutUtils.is_action_pressed(event, "ui_redo"): if undo_redo.has_redo(): undo_redo.redo() - elif ShortcutUtils.is_action_pressed(event, "undo"): + elif ShortcutUtils.is_action_pressed(event, "ui_undo"): if undo_redo.has_undo(): undo_redo.undo() @@ -196,8 +197,7 @@ func get_transform_list() -> Array[Transform]: var t_list: Array[Transform] = [] for t in attribute_ref.get_transform_list(): if t is Transform.TransformMatrix: - t_list.append(Transform.TransformMatrix.new( - t.x1, t.x2, t.y1, t.y2, t.o1, t.o2)) + t_list.append(Transform.TransformMatrix.new(t.x1, t.x2, t.y1, t.y2, t.o1, t.o2)) elif t is Transform.TransformTranslate: t_list.append(Transform.TransformTranslate.new(t.x, t.y)) elif t is Transform.TransformRotate: diff --git a/src/utils/FileUtils.gd b/src/utils/FileUtils.gd index d662e75..c903224 100644 --- a/src/utils/FileUtils.gd +++ b/src/utils/FileUtils.gd @@ -52,12 +52,14 @@ static func save_svg_as() -> void: static func open_export_dialog(export_data: ImageExportData, final_callback := Callable()) -> void: if OS.has_feature("web"): - var web_format_name := ImageExportData.web_formats[export_data.format] + var buffer: PackedByteArray if export_data.format == "svg": - _web_save(ImageExportData.svg_to_buffer(), web_format_name) + buffer = ImageExportData.svg_to_buffer() else: - var img := export_data.generate_image() - _web_save(export_data.image_to_buffer(img), web_format_name) + buffer = export_data.image_to_buffer(export_data.generate_image()) + _web_save(buffer, ImageExportData.web_formats[export_data.format]) + if final_callback.is_valid(): + final_callback.call() else: if _is_native_preferred(): var native_callback :=\ @@ -138,6 +140,16 @@ static func _finish_xml_export(file_path: String, xml: String) -> void: FileAccess.open(file_path, FileAccess.WRITE).store_string(xml) HandlerGUI.remove_all_menus() +static func _finish_reference_load(data: Variant, file_path: String) -> void: + var img := Image.new() + match file_path.get_extension().to_lower(): + "svg": img.load_svg_from_string(data) + "png": img.load_png_from_buffer(data) + "jpg", "jpeg": img.load_jpg_from_buffer(data) + "webp": img.load_webp_from_buffer(data) + var image_texture := ImageTexture.create_from_image(img) + Configs.savedata.get_active_tab().reference_image = image_texture + static func _is_native_preferred() -> bool: return DisplayServer.has_feature(DisplayServer.FEATURE_NATIVE_DIALOG_FILE) and\ @@ -151,9 +163,9 @@ static func _choose_file_name() -> String: static func open_svg_import_dialog() -> void: _open_import_dialog(PackedStringArray(["svg"]), _apply_svg) -static func open_image_import_dialog(completion_callback: Callable) -> void: +static func open_image_import_dialog() -> void: _open_import_dialog(PackedStringArray(["png", "jpg", "jpeg", "webp", "svg"]), - completion_callback, Translator.translate("Load an image file")) + _finish_reference_load, Translator.translate("Load an image file")) static func open_xml_import_dialog(completion_callback: Callable) -> void: _open_import_dialog(PackedStringArray(["xml"]), completion_callback) @@ -205,12 +217,6 @@ allowed_extensions: PackedStringArray) -> Error: Configs.savedata.add_recent_dir(file_path.get_base_dir()) - if file_extension == "tscn": - # I asked kiisu about why he wrote this special case. He said: - # "I think when running from the editor it would give the specific scene - # run as first argument", - # TODO understand what he meant and if it's still relevant. - return ERR_FILE_CANT_OPEN if not file_extension in allowed_extensions: error = TranslationUtils.get_bad_extension_alert_text(file_extension, allowed_extensions) @@ -247,7 +253,7 @@ static func _apply_svg(data: Variant, file_path: String) -> void: if compare_svg_to_disk_contents() == FileState.DIFFERENT: alert_message += "\n\n" + Translator.translate( "If you want to revert your edits since the last save, use {reset_svg}.").format( - {"reset_svg": TranslationUtils.get_shortcut_description("reset_svg")}) + {"reset_svg": TranslationUtils.get_action_description("reset_svg")}) var alert_dialog := AlertDialogScene.instantiate() HandlerGUI.add_menu(alert_dialog) diff --git a/src/utils/ShortcutUtils.gd b/src/utils/ShortcutUtils.gd index be49d3d..ed65405 100644 --- a/src/utils/ShortcutUtils.gd +++ b/src/utils/ShortcutUtils.gd @@ -1,7 +1,28 @@ class_name ShortcutUtils extends RefCounted -# The bool after each action is for whether the shortcut can be modified. -const _shortcut_categories_dict: Dictionary[String, Dictionary] = { +# Can be activated in all contexts. +const UNIVERSAL_ACTIONS: PackedStringArray = ["quit", "about_info", "about_donate", + "check_updates", "open_settings", "about_repo", "about_website", "open_externally", + "open_in_folder"] + +# Requires there being no dialogs. +const EFFECT_ACTIONS: PackedStringArray = ["view_show_grid", "view_show_handles", + "view_rasterized_svg", "view_show_reference", "view_overlay_reference", + "load_reference", "toggle_snap"] + +# Requires there being no popups either. +const EDITOR_ACTIONS: 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", "debug"] + +# Requires no drag-and-drop actions ongoing. +const PRISTINE_ACTIONS: PackedStringArray = ["ui_undo", "ui_redo", "ui_cancel", "delete", + "move_up", "move_down", "duplicate", "select_all"] + + +# The bool after each action is for whether the action can be modified. +const _action_categories_dict: Dictionary[String, Dictionary] = { "file": { "import": true, "export": true, @@ -21,8 +42,11 @@ const _shortcut_categories_dict: Dictionary[String, Dictionary] = { "open_in_folder": true, }, "edit": { - "undo": true, - "redo": true, + "ui_undo": true, + "ui_redo": true, + "ui_copy": true, + "ui_paste": true, + "ui_cut": true, "select_all": true, "duplicate": true, "move_up": true, @@ -76,71 +100,21 @@ const _shortcut_categories_dict: Dictionary[String, Dictionary] = { } } -static func fn_call(shortcut: String) -> void: - fn(shortcut).call() - -# The methods that should be called if these shortcuts aren't handled. -# Should bind only constants, otherwise the binds can get outdated before being used. -static func fn(shortcut: String) -> Callable: - match shortcut: - "save": return FileUtils.save_svg - "save_as": return FileUtils.save_svg_as - "export": return HandlerGUI.open_export - "import": return FileUtils.open_svg_import_dialog - "close_tab": return FileUtils.close_tabs.bind( - Configs.savedata.get_active_tab_index()) - "close_tabs_to_left": return FileUtils.close_tabs.bind( - Configs.savedata.get_active_tab_index(), FileUtils.TabCloseMode.TO_LEFT) - "close_tabs_to_right": return FileUtils.close_tabs.bind( - Configs.savedata.get_active_tab_index(), FileUtils.TabCloseMode.TO_RIGHT) - "close_all_other_tabs": return FileUtils.close_tabs.bind( - Configs.savedata.get_active_tab_index(), FileUtils.TabCloseMode.ALL_OTHERS) - "new_tab": return Configs.savedata.add_empty_tab - "select_next_tab": return func() -> void: Configs.savedata.set_active_tab_index( - posmod(Configs.savedata.get_active_tab_index() + 1, - Configs.savedata.get_tab_count())) - "select_previous_tab": return func() -> void: Configs.savedata.set_active_tab_index( - posmod(Configs.savedata.get_active_tab_index() - 1, - Configs.savedata.get_tab_count())) - "copy_svg_text": return DisplayServer.clipboard_set.bind(State.svg_text) - "optimize": return State.optimize - "reset_svg": return FileUtils.reset_svg - "open_externally": return func() -> void: FileUtils.open_svg( - Configs.savedata.get_active_tab().svg_file_path) - "open_in_folder": return func() -> void: FileUtils.open_svg_folder( - Configs.savedata.get_active_tab().svg_file_path) - "redo": return Configs.savedata.get_active_tab().redo - "undo": return Configs.savedata.get_active_tab().undo - "ui_cancel": return State.clear_all_selections - "delete": return State.delete_selected - "move_up": return State.move_up_selected - "move_down": return State.move_down_selected - "duplicate": return State.duplicate_selected - "select_all": return State.select_all - "about_info": return HandlerGUI.open_about - "about_donate": return HandlerGUI.open_donate - "about_repo": return OS.shell_open.bind("https://github.com/syntaxerror247/GodSVG-Mobile") - "about_godsvg_repo": return OS.shell_open.bind("https://github.com/MewPurPur/GodSVG") - "about_website": return OS.shell_open.bind("https://godsvg.com") - "check_updates": return HandlerGUI.open_update_checker - "open_settings": return HandlerGUI.open_settings - "toggle_snap": return Callable() - _: return Callable() - -static func get_shortcut_icon(shortcut: String) -> CompressedTexture2D: - match shortcut: +static func get_action_icon(action: String) -> Texture2D: + match action: + "ui_paste": return load("res://assets/icons/Paste.svg") + "ui_cut": return load("res://assets/icons/Cut.svg") "import": return load("res://assets/icons/Import.svg") "export": return load("res://assets/icons/Export.svg") - "save": return load("res://assets/icons/Save.svg") - "save_as": return load("res://assets/icons/Save.svg") + "save", "save_as": return load("res://assets/icons/Save.svg") "new_tab": return load("res://assets/icons/CreateTab.svg") - "copy_svg_text": return load("res://assets/icons/Copy.svg") + "copy_svg_text", "ui_copy": return load("res://assets/icons/Copy.svg") "optimize": return load("res://assets/icons/Compress.svg") - "reset_svg", "zoom_reset": return load("res://assets/icons/Reload.svg") + "reset_svg", "zoom_reset", "check_updates": return load("res://assets/icons/Reload.svg") "open_externally": return load("res://assets/icons/OpenFile.svg") "open_in_folder": return load("res://assets/icons/OpenFolder.svg") - "undo": return load("res://assets/icons/Undo.svg") - "redo": return load("res://assets/icons/Redo.svg") + "ui_undo": return load("res://assets/icons/Undo.svg") + "ui_redo": return load("res://assets/icons/Redo.svg") "duplicate": return load("res://assets/icons/Duplicate.svg") "move_up": return load("res://assets/icons/MoveUp.svg") "move_down": return load("res://assets/icons/MoveDown.svg") @@ -150,28 +124,57 @@ static func get_shortcut_icon(shortcut: String) -> CompressedTexture2D: "zoom_out": return load("res://assets/icons/Minus.svg") "debug": return load("res://assets/icons/Debug.svg") "toggle_snap": return load("res://assets/icons/Snap.svg") + "quit": return load("res://assets/icons/Quit.svg") "open_settings": return load("res://assets/icons/Gear.svg") + "about_donate": return load("res://assets/icons/Heart.svg") + "about_repo", "about_website": return load("res://assets/icons/Link.svg") + "load_reference": return load("res://assets/icons/Reference.svg") _: return load("res://assets/icons/Placeholder.svg") -static func get_shortcuts(category: String) -> PackedStringArray: - return _shortcut_categories_dict[category].keys() +static func get_actions(category: String) -> PackedStringArray: + return _action_categories_dict[category].keys() -static func get_all_shortcuts() -> PackedStringArray: +static func get_all_actions() -> PackedStringArray: var shortcuts := PackedStringArray() - for category in _shortcut_categories_dict: - shortcuts += get_shortcuts(category) + for category in _action_categories_dict.keys(): + shortcuts += get_actions(category) return shortcuts -static func is_shortcut_modifiable(shortcut: String) -> bool: - for category in _shortcut_categories_dict: - if _shortcut_categories_dict[category].has(shortcut): - return _shortcut_categories_dict[category][shortcut] +static func is_action_modifiable(shortcut: String) -> bool: + for category in _action_categories_dict.keys(): + if _action_categories_dict[category].has(shortcut): + return _action_categories_dict[category][shortcut] return false +static func get_action_showcase_text(action: String) -> String: + var shortcut := get_action_first_valid_shortcut(action) + if is_instance_valid(shortcut): + return shortcut.as_text_keycode() + return "" + +static func get_action_all_valid_shortcuts(action: String) -> Array[InputEventKey]: + var shortcuts: Array[InputEventKey] = [] + for event in InputMap.action_get_events(action): + if event is InputEventKey and is_shortcut_valid(event, action): + shortcuts.append(event.duplicate()) + return shortcuts + +static func get_action_first_valid_shortcut(action: String) -> InputEventKey: + for event in InputMap.action_get_events(action): + if event is InputEventKey and is_shortcut_valid(event, action): + return event + return null + +static func is_shortcut_valid(shortcut: InputEventKey, action: String) -> bool: + return not is_action_modifiable(action) or Configs.savedata.is_shortcut_valid(shortcut) + static func is_action_pressed(event: InputEvent, action: String) -> bool: # TODO Sometimes MacOS gives us an InputEventAction here. # This doesn't happen on my Linux laptop. I don't know which platform's behavior # is the correct one... But it should be handled gracefully. if event is InputEventAction: + if event.event_index == -1: + # The action has no associated shortcut, so we don't need to check validity. + return event.pressed and event.action == action event = InputMap.action_get_events(event.action)[event.event_index] - return event.is_action_pressed(action) and Configs.savedata.is_shortcut_valid(event) + return event.is_action_pressed(action, false, true) and is_shortcut_valid(event, action) diff --git a/src/utils/TranslationUtils.gd b/src/utils/TranslationUtils.gd index ea7b2fa..bc2a096 100644 --- a/src/utils/TranslationUtils.gd +++ b/src/utils/TranslationUtils.gd @@ -16,31 +16,43 @@ static func get_locale_display(locale: String) -> String: return "%s (%s)" % [_get_locale_name(locale), get_locale_string(locale)] -static func get_shortcut_description(action_name: String) -> String: +static func get_action_description(action_name: String, for_button := false) -> String: match action_name: "export": return Translator.translate("Export") "import": return Translator.translate("Import") - "save": return Translator.translate("Save") - "save_as": return Translator.translate("Save as") + "save": return Translator.translate("Save SVG") + "save_as": return Translator.translate("Save SVG as") "close_tab": return Translator.translate("Close tab") "close_tabs_to_left": return Translator.translate("Close tabs to the left") "close_tabs_to_right": return Translator.translate("Close tabs to the right") "close_all_other_tabs": return Translator.translate("Close all other tabs") - "new_tab": return Translator.translate("Create a new tab") + "new_tab": return Translator.translate("Create tab") if\ + for_button else Translator.translate("Create a new tab") "select_next_tab": return Translator.translate("Select the next tab") "select_previous_tab": return Translator.translate("Select the previous tab") - "optimize": return Translator.translate("Optimize") - "copy_svg_text": return Translator.translate("Copy all text") + "optimize": return Translator.translate("Optimize") if\ + for_button else Translator.translate("Optimize SVG") + "copy_svg_text": return Translator.translate("Copy all text") if\ + for_button else Translator.translate("Copy the SVG text") "reset_svg": return Translator.translate("Reset SVG") - "open_externally": return Translator.translate("Open SVG externally") - "open_in_folder": return Translator.translate("Show SVG in File Manager") - "undo": return Translator.translate("Undo") - "redo": return Translator.translate("Redo") + "open_externally": return Translator.translate("Open externally") if\ + for_button else Translator.translate("Open SVG externally") + "open_in_folder": return Translator.translate("Show in File Manager") if\ + for_button else Translator.translate("Show SVG in File Manager") + "ui_undo": return Translator.translate("Undo") + "ui_redo": return Translator.translate("Redo") + "ui_copy": return Translator.translate("Copy") + "ui_paste": return Translator.translate("Paste") + "ui_cut": return Translator.translate("Cut") "select_all": return Translator.translate("Select all") - "duplicate": return Translator.translate("Duplicate the selection") - "delete": return Translator.translate("Delete the selection") - "move_up": return Translator.translate("Move the selection up") - "move_down": return Translator.translate("Move the selection down") + "duplicate": return Translator.translate("Duplicate") if\ + for_button else Translator.translate("Duplicate the selection") + "delete": return Translator.translate("Delete") if\ + for_button else Translator.translate("Delete the selection") + "move_up": return Translator.translate("Move up") if\ + for_button else Translator.translate("Move the selection up") + "move_down": return Translator.translate("Move down") if\ + for_button else Translator.translate("Move the selection down") "find": return Translator.translate("Find") "zoom_in": return Translator.translate("Zoom in") "zoom_out": return Translator.translate("Zoom out") @@ -53,69 +65,64 @@ static func get_shortcut_description(action_name: String) -> String: "view_show_reference": return Translator.translate("Show reference image") "view_overlay_reference": return Translator.translate("Overlay reference image") "debug": return Translator.translate("View debug information") - "move_relative": return "%s (%s)" %\ - [get_command_description("M"), Translator.translate("Relative")] - "move_absolute": return "%s (%s)" %\ - [get_command_description("M"), Translator.translate("Absolute")] - "line_relative": return "%s (%s)" %\ - [get_command_description("L"), Translator.translate("Relative")] - "line_absolute": return "%s (%s)" %\ - [get_command_description("L"), Translator.translate("Absolute")] - "horizontal_line_relative": return "%s (%s)" %\ - [get_command_description("H"), Translator.translate("Relative")] - "horizontal_line_absolute": return "%s (%s)" %\ - [get_command_description("H"), Translator.translate("Absolute")] - "vertical_line_relative": return "%s (%s)" %\ - [get_command_description("V"), Translator.translate("Relative")] - "vertical_line_absolute": return "%s (%s)" %\ - [get_command_description("V"), Translator.translate("Absolute")] - "close_path_relative": return "%s (%s)" %\ - [get_command_description("Z"), Translator.translate("Relative")] - "close_path_absolute": return "%s (%s)" %\ - [get_command_description("Z"), Translator.translate("Absolute")] - "elliptical_arc_relative": return "%s (%s)" %\ - [get_command_description("A"), Translator.translate("Relative")] - "elliptical_arc_absolute": return "%s (%s)" %\ - [get_command_description("A"), Translator.translate("Absolute")] - "quadratic_bezier_relative": return "%s (%s)" %\ - [get_command_description("Q"), Translator.translate("Relative")] - "quadratic_bezier_absolute": return "%s (%s)" %\ - [get_command_description("Q"), Translator.translate("Absolute")] - "shorthand_quadratic_bezier_relative": return "%s (%s)" %\ - [get_command_description("T"), Translator.translate("Relative")] - "shorthand_quadratic_bezier_absolute": return "%s (%s)" %\ - [get_command_description("T"), Translator.translate("Absolute")] - "cubic_bezier_relative": return "%s (%s)" %\ - [get_command_description("C"), Translator.translate("Relative")] - "cubic_bezier_absolute": return "%s (%s)" %\ - [get_command_description("C"), Translator.translate("Absolute")] - "shorthand_cubic_bezier_relative": return "%s (%s)" %\ - [get_command_description("S"), Translator.translate("Relative")] - "shorthand_cubic_bezier_absolute": return "%s (%s)" %\ - [get_command_description("S"), Translator.translate("Absolute")] - "open_settings": return Translator.translate("Open Settings menu") - "about_info": return Translator.translate("Open About menu") - "about_donate": return Translator.translate("Open Donate menu") - "about_repo": return Translator.translate("Open GodSVG repository") - "about_website": return Translator.translate("Open GodSVG website") + "move_relative": return get_path_command_description("m") + "move_absolute": return get_path_command_description("M") + "line_relative": return get_path_command_description("l") + "line_absolute": return get_path_command_description("L") + "horizontal_line_relative": return get_path_command_description("h") + "horizontal_line_absolute": return get_path_command_description("H") + "vertical_line_relative": return get_path_command_description("v") + "vertical_line_absolute": return get_path_command_description("V") + "close_path_relative": return get_path_command_description("z") + "close_path_absolute": return get_path_command_description("Z") + "elliptical_arc_relative": return get_path_command_description("a") + "elliptical_arc_absolute": return get_path_command_description("A") + "quadratic_bezier_relative": return get_path_command_description("q") + "quadratic_bezier_absolute": return get_path_command_description("Q") + "shorthand_quadratic_bezier_relative": return get_path_command_description("t") + "shorthand_quadratic_bezier_absolute": return get_path_command_description("T") + "cubic_bezier_relative": return get_path_command_description("c") + "cubic_bezier_absolute": return get_path_command_description("C") + "shorthand_cubic_bezier_relative": return get_path_command_description("s") + "shorthand_cubic_bezier_absolute": return get_path_command_description("S") + "open_settings": return Translator.translate("Settings") if\ + for_button else Translator.translate("Open Settings menu") + "about_info": return Translator.translate("About…") if\ + for_button else Translator.translate("Open About menu") + "about_donate": return Translator.translate("Donate…") if\ + for_button else Translator.translate("Open Donate menu") + "about_repo": return Translator.translate("GodSVG repository") if\ + for_button else Translator.translate("Open GodSVG repository") + "about_website": return Translator.translate("GodSVG website") if\ + for_button else Translator.translate("Open GodSVG website") "check_updates": return Translator.translate("Check for updates") "quit": return Translator.translate("Quit the application") _: return action_name -static func get_command_description(command_char: String) -> String: +static func get_path_command_description(command_char: String, +omit_relativity := false) -> String: + var description: String match command_char: - "M", "m": return Translator.translate("Move to") - "L", "l": return Translator.translate("Line to") - "H", "h": return Translator.translate("Horizontal Line to") - "V", "v": return Translator.translate("Vertical Line to") - "Z", "z": return Translator.translate("Close Path") - "A", "a": return Translator.translate("Elliptical Arc to") - "Q", "q": return Translator.translate("Quadratic Bezier to") - "T", "t": return Translator.translate("Shorthand Quadratic Bezier to") - "C", "c": return Translator.translate("Cubic Bezier to") - "S", "s": return Translator.translate("Shorthand Cubic Bezier to") + "M", "m": description = Translator.translate("Move to") + "L", "l": description = Translator.translate("Line to") + "H", "h": description = Translator.translate("Horizontal Line to") + "V", "v": description = Translator.translate("Vertical Line to") + "Z", "z": description = Translator.translate("Close Path") + "A", "a": description = Translator.translate("Elliptical Arc to") + "Q", "q": description = Translator.translate("Quadratic Bezier to") + "T", "t": description = Translator.translate("Shorthand Quadratic Bezier to") + "C", "c": description = Translator.translate("Cubic Bezier to") + "S", "s": description = Translator.translate("Shorthand Cubic Bezier to") _: return command_char + + if omit_relativity: + return description + elif Utils.is_string_lower(command_char): + return description + " (" + Translator.translate("Relative") + ")" + else: + return description + " (" + Translator.translate("Absolute") + ")" + static func get_bad_extension_alert_text(extension: String, allowed_extensions: PackedStringArray) -> String: diff --git a/translations/nl.po b/translations/nl.po index 4512e12..ddcc1e7 100644 --- a/translations/nl.po +++ b/translations/nl.po @@ -36,15 +36,16 @@ msgid "" "This will connect to github.com to compare version numbers. No other data is " "collected or transmitted." msgstr "" +"Dit zal een verbinding maken naar github.com om de versienummers te vergelijken. " +"Er wordt geen andere data verzameld of verzonden." #: src/autoload/HandlerGUI.gd src/ui_widgets/alert_dialog.gd msgid "OK" msgstr "OK" #: src/autoload/HandlerGUI.gd -#, fuzzy msgid "Do you want to proceed?" -msgstr "Wil je GodSVG afsluiten?" +msgstr "Wil je voortgaan?" #: src/autoload/HandlerGUI.gd msgid "Export SVG" @@ -56,21 +57,19 @@ msgid "Export" msgstr "Exporteren" #: src/autoload/HandlerGUI.gd -#, fuzzy msgid "" "The graphic can be exported only as SVG because its proportions are too " "extreme." msgstr "" -"Het beeld kan alleen maar als SVG geëxporteerd worden want zijn maat is niet " -"gedefiniëerd. Wil je verder gaan?" +"De afbeelding kan alleen maar als SVG geëxporteerd worden door zijn extreme " +"verhoudingen." #: src/autoload/HandlerGUI.gd -#, fuzzy msgid "" "The graphic can be exported only as SVG because its size is not defined." msgstr "" -"Het beeld kan alleen maar als SVG geëxporteerd worden want zijn maat is niet " -"gedefiniëerd. Wil je verder gaan?" +"De afbeelding kan alleen maar als SVG geëxporteerd worden want zijn maat is niet " +"gedefiniëerd." #: src/autoload/State.gd src/ui_parts/good_file_dialog.gd #: src/ui_widgets/alert_dialog.gd @@ -78,13 +77,12 @@ msgid "Alert!" msgstr "Aandacht!" #: src/autoload/State.gd src/ui_parts/tab_bar.gd src/utils/TranslationUtils.gd -#, fuzzy msgid "Close tab" -msgstr "Pad sluiten" +msgstr "Tabblad sluiten" #: src/autoload/State.gd msgid "Restore" -msgstr "" +msgstr "Herstel" #: src/autoload/State.gd msgid "View in List" @@ -117,13 +115,15 @@ msgstr "Erna Invoegen" #: src/autoload/State.gd msgid "The last edited state of this tab could not be found." -msgstr "" +msgstr "De laatst aangepaste stand van dit tabblad kon niet gevonden worden." #: src/autoload/State.gd msgid "" "The tab is bound to the file path {file_path}. Do you want to restore the " "SVG from this path?" msgstr "" +"Het tablad is gebonden aan het bestandspad {file_path}. Wil je de SVG " +"herstellen vanuit dit pad?" #: src/config_classes/Formatter.gd msgid "Compact" @@ -163,11 +163,11 @@ msgstr "6-cijferige hex" #: src/config_classes/TabData.gd msgid "Empty" -msgstr "" +msgstr "Leeg" #: src/config_classes/TabData.gd msgid "Unsaved" -msgstr "" +msgstr "Niet opgeslagen" #: src/data_classes/BasicXNode.gd msgid "Comment" @@ -260,9 +260,8 @@ msgid "Save SVG" msgstr "SVG opslaan" #: src/ui_parts/current_file_button.gd -#, fuzzy msgid "Save SVG as…" -msgstr "SVG opslaan" +msgstr "SVG opslaan als…" #: src/ui_parts/current_file_button.gd src/utils/TranslationUtils.gd msgid "Reset SVG" @@ -270,11 +269,11 @@ msgstr "SVG Resetten" #: src/ui_parts/current_file_button.gd src/ui_parts/tab_bar.gd msgid "Open externally" -msgstr "" +msgstr "Extern openen" #: src/ui_parts/current_file_button.gd src/ui_parts/tab_bar.gd msgid "Show in File Manager" -msgstr "" +msgstr "Laat zien in Bestandsenverkenner" #: src/ui_parts/display.gd msgid "Visuals" @@ -532,6 +531,10 @@ msgstr "Snelkoppelingen" msgid "Theming" msgstr "Thematiek" +#: src/ui_parts/settings_menu.gd +msgid "Tab bar" +msgstr "Tabladbalk" + #: src/ui_parts/settings_menu.gd msgid "Other" msgstr "Overige" @@ -612,6 +615,10 @@ msgstr "Geldige kleur" msgid "Warning color" msgstr "Waarschuwingskleur" +#: src/ui_parts/settings_menu.gd +msgid "Close tabs with middle mouse button" +msgstr "Sluit tabbladen met de middelste muisknop" + #: src/ui_parts/settings_menu.gd msgid "Input" msgstr "Invoer" @@ -622,7 +629,7 @@ msgstr "inzoomrichting tegenstellen" #: src/ui_parts/settings_menu.gd msgid "Wrap-around panning" -msgstr "" +msgstr "Omwikkeld rondkijken" #: src/ui_parts/settings_menu.gd msgid "Use CTRL for zooming" @@ -649,9 +656,8 @@ msgid "UI scale" msgstr "Gebruiksinterface maat" #: src/ui_parts/settings_menu.gd -#, fuzzy msgid "Changes the scale factor for the interface." -msgstr "Verandert the schaal van de visuele gebruikersinterface." +msgstr "Verandert het maat-factor van de gebruikersinterface." #: src/ui_parts/settings_menu.gd msgid "Language" @@ -783,13 +789,15 @@ msgstr "Verwijder onnodige parameters" #: src/ui_parts/settings_menu.gd msgid "Swaps the scroll directions for zooming in and zooming out." -msgstr "" +msgstr "Verwisselt de scroll richtingen voor inzoomen en uitzoomen." #: src/ui_parts/settings_menu.gd msgid "" "Warps the cursor to the opposite side whenever it reaches a viewport " "boundary while panning." msgstr "" +"Verplaatst de cursur naar de overkant wanneer het buiten de viewport " +"gaat tijdens het rondkijken." #: src/ui_parts/settings_menu.gd msgid "" @@ -817,9 +825,8 @@ msgstr "" "achteloos van het huidige bestand." #: src/ui_parts/settings_menu.gd -#, fuzzy msgid "Changes the visual size and grabbing area of handles." -msgstr "Verandert de visuele grootte en grijpgebied van Handvaten." +msgstr "verandert de visuele grootte en grijpgebieden van handvaten." #: src/ui_parts/shortcut_panel.gd msgid "Horizontal strip" @@ -842,31 +849,28 @@ msgid "Layout" msgstr "Indeling" #: src/ui_parts/tab_bar.gd -#, fuzzy msgid "Create tab" -msgstr "Creëren" +msgstr "Tabblad creëren" #: src/ui_parts/tab_bar.gd src/utils/TranslationUtils.gd -#, fuzzy msgid "Create a new tab" -msgstr "Nieuwe map creëren" +msgstr "Een nieuw tabblad creëren" #: src/ui_parts/tab_bar.gd msgid "Scroll backwards" -msgstr "" +msgstr "Scroll achteruit" #: src/ui_parts/tab_bar.gd msgid "Scroll forwards" -msgstr "" +msgstr "Scroll vooruit" #: src/ui_parts/tab_bar.gd msgid "This SVG is not bound to a file location yet." -msgstr "" +msgstr "Deze SVG is nog niet gebonden aan een bestandslocatie." #: src/ui_parts/update_menu.gd -#, fuzzy msgid "Show prereleases" -msgstr "Voorpublicaties includen" +msgstr "Laat prereleases zien" #: src/ui_parts/update_menu.gd msgid "Retry" @@ -950,14 +954,13 @@ msgstr "Kleur verwijderen" msgid "Unnamed" msgstr "Onbenoemd" -#: src/ui_widgets/good_color_picker.gd -#, fuzzy +#: src/ui_widgets/good_color_picker.gd- msgid "Color keywords" -msgstr "Kleurenplukker" +msgstr "kleur trefwoorden" #: src/ui_widgets/good_color_picker.gd msgid "Eyedropper" -msgstr "" +msgstr "Druppelaar" #: src/ui_widgets/palette_config.gd msgid "Unnamed palettes won't be shown." @@ -984,9 +987,8 @@ msgid "Copy as XML" msgstr "Als XML kopiëren" #: src/ui_widgets/palette_config.gd -#, fuzzy msgid "Save as XML" -msgstr "Als XML kopiëren" +msgstr "Als XML opslaan" #: src/ui_widgets/path_popup.gd src/ui_widgets/pathdata_field.gd #: src/utils/TranslationUtils.gd @@ -1066,97 +1068,90 @@ msgid "Check if the file still exists in the selected file path." msgstr "Kijk na of het bestand nog steeds bestaat in het gekozen bestandspad" #: src/utils/FileUtils.gd -#, fuzzy msgid "Save the file?" -msgstr "Bewaar het .\"{format}\" bestand" +msgstr "sla het bestand op?" #: src/utils/FileUtils.gd -#, fuzzy msgid "Do you want to save this file?" -msgstr "Wil je GodSVG afsluiten?" +msgstr "Wil je dit bestand opslaan?" #: src/utils/FileUtils.gd msgid "Save the changes?" -msgstr "" +msgstr "sla de aanpassingen op?" #: src/utils/FileUtils.gd msgid "Your changes will be lost if you don't save them." -msgstr "" +msgstr "Uw aanpassingen zullen verdwijnen als u ze niet opslaat." #: src/utils/FileUtils.gd msgid "Don't save" -msgstr "" +msgstr "Niet opslaan." #: src/utils/FileUtils.gd msgid "The imported file is already being edited inside GodSVG." -msgstr "" +msgstr "Het geïmporteerde bestand is al geöpend in GodSVG" #: src/utils/FileUtils.gd msgid "If you want to revert your edits since the last save, use {reset_svg}." msgstr "" +"Als u uw aanpassingen van de vorige opslag wilt ongedaanmaken, gebruik " +"{reset_svg}" #: src/utils/FileUtils.gd msgid "Do you want to save the changes made to {file_name}?" -msgstr "" +msgstr "Wilt u de aanpassingen opslaan als {file_name}?" #: src/utils/TranslationUtils.gd -#, fuzzy msgid "Save as" -msgstr "Opslaan" +msgstr "Opslaan als" #: src/utils/TranslationUtils.gd msgid "Close tabs to the left" -msgstr "" +msgstr "Sluit linker tabbladen" #: src/utils/TranslationUtils.gd msgid "Close tabs to the right" -msgstr "" +msgstr "Sluit rechter tabbladen" #: src/utils/TranslationUtils.gd -#, fuzzy msgid "Close all other tabs" msgstr "Kopiëer alle tekst" #: src/utils/TranslationUtils.gd msgid "Select the next tab" -msgstr "" +msgstr "Selecteer het volgende tabblad" #: src/utils/TranslationUtils.gd msgid "Select the previous tab" -msgstr "" +msgstr "Selecteer het vorige tabblad" #: src/utils/TranslationUtils.gd -#, fuzzy msgid "Open SVG externally" -msgstr "Open GodSVG opslagplaats" +msgstr "SVG extern openen" #: src/utils/TranslationUtils.gd msgid "Show SVG in File Manager" -msgstr "" +msgstr "Laat SVG zien in Bestandsverkenner" #: src/utils/TranslationUtils.gd -#, fuzzy msgid "Select all" -msgstr "Selecteren" +msgstr "Alles selecteren" #: src/utils/TranslationUtils.gd -#, fuzzy msgid "Duplicate the selection" -msgstr "Verwijder de selectie" +msgstr "Dupliceer de selectie" #: src/utils/TranslationUtils.gd msgid "Delete the selection" msgstr "Verwijder de selectie" #: src/utils/TranslationUtils.gd -#, fuzzy msgid "Move the selection up" -msgstr "Beweeg the gekozen elementen omhoog" +msgstr "Beweeg de selectie omhoog" #: src/utils/TranslationUtils.gd -#, fuzzy msgid "Move the selection down" -msgstr "Beweeg the gekozen elementen omlaag" +msgstr "Beweeg de selectie omlaag" #: src/utils/TranslationUtils.gd msgid "Find" diff --git a/translations/ru.po b/translations/ru.po index 378d39c..f8e76a0 100644 --- a/translations/ru.po +++ b/translations/ru.po @@ -534,6 +534,10 @@ msgstr "Сочетания клавиш" msgid "Theming" msgstr "Тема" +#: src/ui_parts/settings_menu.gd +msgid "Tab bar" +msgstr "Вкладки" + #: src/ui_parts/settings_menu.gd msgid "Other" msgstr "Другие" @@ -614,6 +618,10 @@ msgstr "Цвет валидности" msgid "Warning color" msgstr "Цвет предупреждения" +#: src/ui_parts/settings_menu.gd +msgid "Close tabs with middle mouse button" +msgstr "Закрывать вкладки средней клавишей мыши" + #: src/ui_parts/settings_menu.gd msgid "Input" msgstr "Устройства ввода" From f06b0724b544790af16fab51ba9cef0cca8a2c77 Mon Sep 17 00:00:00 2001 From: Anish Mishra Date: Tue, 6 May 2025 15:15:59 +0530 Subject: [PATCH 2/2] Update build.yml --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b035353..88e526d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,7 +11,7 @@ env: # Which godot version to use for exporting. GODOT_VERSION: 4.4.1 # Which godot release to use for exporting. (stable/rc/beta/alpha) - GODOT_RELEASE: rc1 + GODOT_RELEASE: stable # Used in the editor config file name. Do not change this for patch releases. GODOT_FEATURE_VERSION: 4.4 # Commit hash