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 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 "Устройства ввода"