diff --git a/assets/icons/Close.svg b/assets/icons/Close.svg new file mode 100644 index 00000000..4879911e --- /dev/null +++ b/assets/icons/Close.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/Close.svg.import b/assets/icons/Close.svg.import new file mode 100644 index 00000000..ffddd081 --- /dev/null +++ b/assets/icons/Close.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b0y4h5tuyrais" +path="res://.godot/imported/Close.svg-ec226890f15a36af010397a4558772f4.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/icons/Close.svg" +dest_files=["res://.godot/imported/Close.svg-ec226890f15a36af010397a4558772f4.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/CreateTab.svg b/assets/icons/CreateTab.svg new file mode 100644 index 00000000..2110ccd1 --- /dev/null +++ b/assets/icons/CreateTab.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/CreateTab.svg.import b/assets/icons/CreateTab.svg.import new file mode 100644 index 00000000..1a75ed23 --- /dev/null +++ b/assets/icons/CreateTab.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bnl24bflj771n" +path="res://.godot/imported/CreateTab.svg-5a4a2c79f40bbfe654b40ae41510476b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/icons/CreateTab.svg" +dest_files=["res://.godot/imported/CreateTab.svg-5a4a2c79f40bbfe654b40ae41510476b.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 68ce864d..1ef92d6f 100644 --- a/project.godot +++ b/project.godot @@ -31,14 +31,13 @@ driver/driver="Dummy" [autoload] Configs="*res://src/autoload/Configs.gd" -SVG="*res://src/autoload/SVG.gd" -Indications="*res://src/autoload/Indications.gd" +State="*res://src/autoload/State.gd" HandlerGUI="*res://src/autoload/HandlerGUI.gd" [display] -window/size/viewport_width=1024 -window/size/viewport_height=640 +window/size/viewport_width=1040 +window/size/viewport_height=650 window/size/mode=2 window/energy_saving/keep_screen_on=false mouse_cursor/tooltip_position_offset=Vector2(0, 10) @@ -55,62 +54,74 @@ timers/tooltip_delay_sec=0.4 [input] optimize={ -"deadzone": 0.5, +"deadzone": 0.2, "events": [] } open_settings={ -"deadzone": 0.5, +"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":44,"physical_keycode":0,"key_label":0,"unicode":44,"location":0,"echo":false,"script":null) ] } import={ -"deadzone": 0.5, +"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":73,"physical_keycode":0,"key_label":0,"unicode":105,"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":79,"physical_keycode":0,"key_label":0,"unicode":111,"location":0,"echo":false,"script":null) ] } export={ -"deadzone": 0.5, +"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":69,"physical_keycode":0,"key_label":0,"unicode":101,"location":0,"echo":false,"script":null) ] } save={ -"deadzone": 0.5, +"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":83,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) ] } copy_svg_text={ -"deadzone": 0.5, -"events": [] -} -clear_svg={ -"deadzone": 0.5, -"events": [] -} -clear_file_path={ -"deadzone": 0.5, +"deadzone": 0.2, "events": [] } reset_svg={ -"deadzone": 0.5, +"deadzone": 0.2, "events": [] } open_svg={ -"deadzone": 0.5, +"deadzone": 0.2, "events": [] } +close_tab={ +"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":87,"physical_keycode":0,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null) +] +} +new_tab={ +"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":84,"physical_keycode":0,"key_label":0,"unicode":116,"location":0,"echo":false,"script":null) +] +} +select_next_tab={ +"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":4194306,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +] +} +select_previous_tab={ +"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":4194306,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +] +} delete={ -"deadzone": 0.5, +"deadzone": 0.2, "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194312,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) ] } zoom_in={ -"deadzone": 0.5, +"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":61,"physical_keycode":0,"key_label":0,"unicode":61,"location":0,"echo":false,"script":null) ] } zoom_out={ -"deadzone": 0.5, +"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":45,"physical_keycode":0,"key_label":0,"unicode":45,"location":0,"echo":false,"script":null) ] } @@ -120,200 +131,200 @@ zoom_reset={ ] } move_up={ -"deadzone": 0.5, +"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":4194320,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) ] } move_down={ -"deadzone": 0.5, +"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":4194322,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) ] } undo={ -"deadzone": 0.5, +"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.5, +"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.5, +"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) ] } select_all={ -"deadzone": 0.5, +"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":65,"physical_keycode":0,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null) ] } view_show_grid={ -"deadzone": 0.5, +"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":71,"physical_keycode":0,"key_label":0,"unicode":71,"location":0,"echo":false,"script":null) ] } view_show_handles={ -"deadzone": 0.5, +"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":72,"physical_keycode":0,"key_label":0,"unicode":72,"location":0,"echo":false,"script":null) ] } view_rasterized_svg={ -"deadzone": 0.5, +"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":82,"physical_keycode":0,"key_label":0,"unicode":82,"location":0,"echo":false,"script":null) ] } about_info={ -"deadzone": 0.5, +"deadzone": 0.2, "events": [] } about_donate={ -"deadzone": 0.5, +"deadzone": 0.2, "events": [] } about_repo={ -"deadzone": 0.5, +"deadzone": 0.2, "events": [] } about_website={ -"deadzone": 0.5, +"deadzone": 0.2, "events": [] } move_relative={ -"deadzone": 0.5, +"deadzone": 0.2, "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":77,"physical_keycode":0,"key_label":0,"unicode":109,"location":0,"echo":false,"script":null) ] } move_absolute={ -"deadzone": 0.5, +"deadzone": 0.2, "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":true,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":77,"physical_keycode":0,"key_label":0,"unicode":77,"location":0,"echo":false,"script":null) ] } line_relative={ -"deadzone": 0.5, +"deadzone": 0.2, "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":76,"physical_keycode":0,"key_label":0,"unicode":108,"location":0,"echo":false,"script":null) ] } line_absolute={ -"deadzone": 0.5, +"deadzone": 0.2, "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":true,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":76,"physical_keycode":0,"key_label":0,"unicode":76,"location":0,"echo":false,"script":null) ] } horizontal_line_relative={ -"deadzone": 0.5, +"deadzone": 0.2, "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":72,"physical_keycode":0,"key_label":0,"unicode":104,"location":0,"echo":false,"script":null) ] } horizontal_line_absolute={ -"deadzone": 0.5, +"deadzone": 0.2, "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":true,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":72,"physical_keycode":0,"key_label":0,"unicode":72,"location":0,"echo":false,"script":null) ] } vertical_line_relative={ -"deadzone": 0.5, +"deadzone": 0.2, "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":86,"physical_keycode":0,"key_label":0,"unicode":118,"location":0,"echo":false,"script":null) ] } vertical_line_absolute={ -"deadzone": 0.5, +"deadzone": 0.2, "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":true,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":86,"physical_keycode":0,"key_label":0,"unicode":86,"location":0,"echo":false,"script":null) ] } close_path_relative={ -"deadzone": 0.5, +"deadzone": 0.2, "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":90,"physical_keycode":0,"key_label":0,"unicode":122,"location":0,"echo":false,"script":null) ] } close_path_absolute={ -"deadzone": 0.5, +"deadzone": 0.2, "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":true,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":90,"physical_keycode":0,"key_label":0,"unicode":90,"location":0,"echo":false,"script":null) ] } elliptical_arc_relative={ -"deadzone": 0.5, +"deadzone": 0.2, "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":65,"physical_keycode":0,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null) ] } elliptical_arc_absolute={ -"deadzone": 0.5, +"deadzone": 0.2, "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":true,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":65,"physical_keycode":0,"key_label":0,"unicode":65,"location":0,"echo":false,"script":null) ] } quadratic_bezier_relative={ -"deadzone": 0.5, +"deadzone": 0.2, "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":81,"physical_keycode":0,"key_label":0,"unicode":113,"location":0,"echo":false,"script":null) ] } quadratic_bezier_absolute={ -"deadzone": 0.5, +"deadzone": 0.2, "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":true,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":81,"physical_keycode":0,"key_label":0,"unicode":81,"location":0,"echo":false,"script":null) ] } shorthand_quadratic_bezier_relative={ -"deadzone": 0.5, +"deadzone": 0.2, "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":84,"physical_keycode":0,"key_label":0,"unicode":116,"location":0,"echo":false,"script":null) ] } shorthand_quadratic_bezier_absolute={ -"deadzone": 0.5, +"deadzone": 0.2, "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":true,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":84,"physical_keycode":0,"key_label":0,"unicode":84,"location":0,"echo":false,"script":null) ] } cubic_bezier_relative={ -"deadzone": 0.5, +"deadzone": 0.2, "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":67,"physical_keycode":0,"key_label":0,"unicode":99,"location":0,"echo":false,"script":null) ] } cubic_bezier_absolute={ -"deadzone": 0.5, +"deadzone": 0.2, "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":true,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":67,"physical_keycode":0,"key_label":0,"unicode":67,"location":0,"echo":false,"script":null) ] } shorthand_cubic_bezier_relative={ -"deadzone": 0.5, +"deadzone": 0.2, "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":83,"physical_keycode":0,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null) ] } shorthand_cubic_bezier_absolute={ -"deadzone": 0.5, +"deadzone": 0.2, "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":true,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":83,"physical_keycode":0,"key_label":0,"unicode":83,"location":0,"echo":false,"script":null) ] } debug={ -"deadzone": 0.5, +"deadzone": 0.2, "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194334,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) ] } check_updates={ -"deadzone": 0.5, +"deadzone": 0.2, "events": [] } quit={ -"deadzone": 0.5, +"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":81,"physical_keycode":0,"key_label":0,"unicode":113,"location":0,"echo":false,"script":null) ] } load_reference={ -"deadzone": 0.5, +"deadzone": 0.2, "events": [] } view_show_reference={ -"deadzone": 0.5, +"deadzone": 0.2, "events": [] } view_overlay_reference={ -"deadzone": 0.5, +"deadzone": 0.2, "events": [] } find={ -"deadzone": 0.5, +"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":70,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) ] } toggle_snap={ -"deadzone": 0.5, +"deadzone": 0.2, "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":true,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":83,"physical_keycode":0,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null) ] } @@ -325,6 +336,7 @@ pointing/android/enable_pan_and_scale_gestures=true [internationalization] +rendering/root_node_auto_translate=false locale/translations=PackedStringArray("res://assets/translations/bg.po", "res://assets/translations/de.po", "res://assets/translations/en.po", "res://assets/translations/fr.po", "res://assets/translations/nl.po", "res://assets/translations/ru.po", "res://assets/translations/uk.po", "res://assets/translations/zh.po") [physics] diff --git a/src/autoload/Configs.gd b/src/autoload/Configs.gd index f6b3b931..e7eeb1ce 100644 --- a/src/autoload/Configs.gd +++ b/src/autoload/Configs.gd @@ -1,8 +1,6 @@ # This singleton handles session data and settings. extends Node -@warning_ignore("unused_signal") -signal file_path_changed @warning_ignore("unused_signal") signal highlighting_colors_changed @warning_ignore("unused_signal") @@ -21,6 +19,12 @@ signal basic_colors_changed signal handle_visuals_changed @warning_ignore("unused_signal") signal shortcut_panel_changed +@warning_ignore("unused_signal") +signal active_tab_file_path_changed +@warning_ignore("unused_signal") +signal active_tab_changed +@warning_ignore("unused_signal") +signal tabs_changed const savedata_path = "user://savedata.tres" var savedata: SaveData: @@ -30,13 +34,6 @@ var savedata: SaveData: savedata.validate() savedata.changed_deferred.connect(save) -var svg_text := "": - set(new_value): - if new_value != svg_text: - svg_text = new_value - FileAccess.open(svg_path, FileAccess.WRITE).store_string(svg_text) - -const svg_path = "user://save.svg" func save() -> void: ResourceSaver.save(savedata, savedata_path) @@ -50,9 +47,7 @@ func _enter_tree() -> void: if InputMap.has_action(action): default_shortcuts[action] = InputMap.action_get_events(action) load_config() - load_svg_text() ThemeUtils.generate_and_apply_theme() - update_window_title() func load_config() -> void: @@ -65,14 +60,10 @@ func load_config() -> void: reset_settings() return - update_window_title() + savedata.get_active_tab().activate() change_background_color() change_locale() -func load_svg_text() -> void: - var fa := FileAccess.open(svg_path, FileAccess.READ) - if fa != null: - svg_text = fa.get_as_text() func reset_settings() -> void: savedata = SaveData.new() @@ -80,6 +71,7 @@ func reset_settings() -> void: savedata.language = "en" savedata.set_shortcut_panel_slots({ 0: "undo", 1: "redo" }) savedata.set_palettes([Palette.new("Pure", Palette.Preset.PURE)]) + savedata.add_empty_tab() save() @@ -98,14 +90,11 @@ func generate_highlighter() -> SVGHighlighter: # Global effects from settings. Some of them should also be used on launch. -func update_window_title() -> void: - if savedata.use_filename_for_window_title and !savedata.current_file_path.is_empty(): - get_window().title = savedata.current_file_path.get_file() + " - GodSVG" - else: - get_window().title = "GodSVG" - func change_background_color() -> void: RenderingServer.set_default_clear_color(savedata.background_color) func change_locale() -> void: - TranslationServer.set_locale(savedata.language) + if not savedata.language in TranslationServer.get_loaded_locales(): + savedata.language = "en" + else: + TranslationServer.set_locale(savedata.language) diff --git a/src/autoload/HandlerGUI.gd b/src/autoload/HandlerGUI.gd index 2b1401ed..983e61b6 100644 --- a/src/autoload/HandlerGUI.gd +++ b/src/autoload/HandlerGUI.gd @@ -1,8 +1,8 @@ extends Node # Not a good idea to preload scenes inside a singleton. -const AlertDialog = preload("res://src/ui_parts/alert_dialog.tscn") -const ConfirmDialog = preload("res://src/ui_parts/confirm_dialog.tscn") +const AlertDialog = preload("res://src/ui_widgets/alert_dialog.tscn") +const ConfirmDialog = preload("res://src/ui_widgets/confirm_dialog.tscn") const SettingsMenu = preload("res://src/ui_parts/settings_menu.tscn") const AboutMenu = preload("res://src/ui_parts/about_menu.tscn") const DonateMenu = preload("res://src/ui_parts/donate_menu.tscn") @@ -24,6 +24,10 @@ func _enter_tree() -> void: window.size_changed.connect(remove_all_popups) func _ready() -> void: + Configs.active_tab_changed.connect(update_window_title) + Configs.active_tab_file_path_changed.connect(update_window_title) + update_window_title() + Configs.ui_scale_changed.connect(update_ui_scale) await get_tree().process_frame # Helps make things more consistent. update_ui_scale() @@ -54,7 +58,7 @@ func add_dialog(new_dialog: Control) -> void: _add_control(new_dialog) func _add_control(new_control: Control) -> void: - # FIXME subpar workaround to drag & drop not able to be cancelled manually. + # FIXME subpar workaround to drag & drop not able to be canceled manually. get_tree().root.propagate_notification(NOTIFICATION_DRAG_END) remove_all_popups() @@ -224,8 +228,8 @@ func _input(event: InputEvent) -> void: return # Global actions that should happen regardless of the context. - for action in ["import", "export", "save", "copy_svg_text", "clear_svg", "optimize", - "clear_file_path", "reset_svg"]: + for action in ["import", "export", "save", "close_tab", "new_tab", "select_next_tab", + "select_previous_tab", "copy_svg_text", "optimize", "reset_svg"]: if ShortcutUtils.is_action_pressed(event, action): get_viewport().set_input_as_handled() ShortcutUtils.fn_call(action) @@ -252,7 +256,7 @@ func _unhandled_input(event: InputEvent) -> void: ShortcutUtils.fn_call(action) return if event is InputEventKey: - Indications.respond_to_key_input(event) + State.respond_to_key_input(event) func update_ui_scale() -> void: @@ -320,8 +324,8 @@ func open_donate() -> void: add_menu(DonateMenu.instantiate()) func open_export() -> void: - var width := SVG.root_element.width - var height := SVG.root_element.height + var width := State.root_element.width + var height := State.root_element.height if is_finite(width) and is_finite(height) and width > 0.0 and height > 0.0: add_menu(ExportMenu.instantiate()) else: @@ -333,6 +337,15 @@ func open_export() -> void: Translator.translate("Export"), FileUtils.open_export_dialog.bind(svg_export_data)) +func update_window_title() -> void: + if Configs.savedata.use_filename_for_window_title and\ + not Configs.savedata.get_active_tab().svg_file_path.is_empty(): + get_window().title = Configs.savedata.get_active_tab().get_presented_name() +\ + " - GodSVG" + else: + get_window().title = "GodSVG" + + # Helpers # Used to trigger a mouse motion event, which can be used to update some things, diff --git a/src/autoload/SVG.gd b/src/autoload/SVG.gd deleted file mode 100644 index 671d4b1f..00000000 --- a/src/autoload/SVG.gd +++ /dev/null @@ -1,160 +0,0 @@ -# This singleton handles the two representations of the SVG: -# The SVG text, and the native ElementSVG representation. -extends Node - -signal changed_unknown -signal resized - -# These signals copy the ones in ElementRoot. -# ElementRoot is not persistent, while these signals can be connected to reliably. -signal any_attribute_changed(xid: PackedInt32Array) -signal xnodes_added(xids: Array[PackedInt32Array]) -signal xnodes_deleted(xids: Array[PackedInt32Array]) -signal xnodes_moved_in_parent(parent_xid: PackedInt32Array, old_indices: Array[int]) -signal xnodes_moved_to(xids: Array[PackedInt32Array], location: PackedInt32Array) -signal xnode_layout_changed # Emitted together with any of the above 4. -signal basic_xnode_text_changed -signal basic_xnode_rendered_text_changed - -signal parsing_finished(error_id: SVGParser.ParseError) -signal changed # Should only connect to persistent parts of the UI. - -const DEFAULT = '' - -var _current_size := Vector2.ZERO - -var _update_pending := false -var _save_pending := false - -# "unstable_text" is the current state, which might have errors (i.e., while using the -# code editor). "text" is the last state without errors. -# These both differ from "Configs.svg_text" which is the state as saved to file, -# which doesn't happen while dragging handles or typing in the code editor for example. -var unstable_text := "" -var text := "" -var root_element: ElementRoot - -var UR := UndoRedo.new() - -func _enter_tree() -> void: - root_element = ElementRoot.new(Configs.savedata.editor_formatter) - -func _ready() -> void: - changed_unknown.connect(queue_update) - xnode_layout_changed.connect(queue_update) - any_attribute_changed.connect(queue_update.unbind(1)) - basic_xnode_text_changed.connect(queue_update) - basic_xnode_rendered_text_changed.connect(queue_update) - - var cmdline_args := OS.get_cmdline_args() - var load_cmdl := false - if not (OS.is_debug_build() and not OS.has_feature("template")) and\ - cmdline_args.size() >= 1: - load_cmdl = true - - await get_tree().root.ready # Await tree ready to be able to add error dialogs. - - # Guarantee a proper SVG text first, as the import warnings dialog - # that might pop up from command line file opening is cancellable. - if not Configs.svg_text.is_empty(): - apply_svg_text(Configs.svg_text) - else: - apply_svg_text(DEFAULT) - - if load_cmdl: - FileUtils.apply_svg_from_path(cmdline_args[0]) - - UR.clear_history() - -func _exit_tree() -> void: - UR.free() - -# Syncs text to the elements. -func queue_update() -> void: - _update.call_deferred() - _update_pending = true - -func queue_save() -> void: - _save.call_deferred() - _save_pending = true - -func _update() -> void: - if not _update_pending: - return - _update_pending = false - text = SVGParser.root_to_text(root_element, Configs.savedata.editor_formatter) - changed.emit() - -func _save() -> void: - if not _save_pending: - return - _save_pending = false - - unstable_text = "" - var saved_text := Configs.svg_text - if saved_text == text: - return - UR.create_action("") - UR.add_do_property(Configs, "svg_text", text) - UR.add_undo_property(Configs, "svg_text", saved_text) - UR.add_do_property(self, "text", text) - UR.add_undo_property(self, "text", saved_text) - UR.commit_action() - - -func sync_elements() -> void: - var text_to_parse := text if unstable_text.is_empty() else unstable_text - var svg_parse_result := SVGParser.text_to_root(text_to_parse, - Configs.savedata.editor_formatter) - parsing_finished.emit(svg_parse_result.error) - if svg_parse_result.error == SVGParser.ParseError.OK: - text = unstable_text - unstable_text = "" - root_element = svg_parse_result.svg - root_element.any_attribute_changed.connect(any_attribute_changed.emit) - root_element.xnodes_added.connect(xnodes_added.emit) - root_element.xnodes_deleted.connect(xnodes_deleted.emit) - root_element.xnodes_moved_in_parent.connect(xnodes_moved_in_parent.emit) - root_element.xnodes_moved_to.connect(xnodes_moved_to.emit) - root_element.xnode_layout_changed.connect(xnode_layout_changed.emit) - root_element.attribute_changed.connect(_on_root_attribute_changed) - root_element.basic_xnode_text_changed.connect(basic_xnode_text_changed.emit) - root_element.basic_xnode_rendered_text_changed.connect( - basic_xnode_rendered_text_changed.emit) - changed_unknown.emit() - _update_current_size() - - -func _on_root_attribute_changed(attribute_name: String) -> void: - if attribute_name in ["width", "height", "viewBox"]: - _update_current_size() - -func _update_current_size() -> void: - if _current_size != root_element.get_size(): - _current_size = root_element.get_size() - resized.emit() - - -func undo() -> void: - if UR.has_undo(): - UR.undo() - SVG.sync_elements() - -func redo() -> void: - if UR.has_redo(): - UR.redo() - SVG.sync_elements() - - -func apply_svg_text(new_text: String, save := true) -> void: - unstable_text = new_text - sync_elements() - if save: - queue_save() - -func optimize() -> void: - SVG.root_element.optimize() - SVG.queue_save() - -func get_export_text() -> String: - return SVGParser.root_to_text(root_element, Configs.savedata.export_formatter) diff --git a/src/autoload/SVG.gd.uid b/src/autoload/SVG.gd.uid deleted file mode 100644 index 3cda3136..00000000 --- a/src/autoload/SVG.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://d1g3tf6hqcyv1 diff --git a/src/autoload/Indications.gd b/src/autoload/State.gd similarity index 73% rename from src/autoload/Indications.gd rename to src/autoload/State.gd index 452ad336..587a6c71 100644 --- a/src/autoload/Indications.gd +++ b/src/autoload/State.gd @@ -1,9 +1,11 @@ -# This singleton handles temporary editor information like zoom level and selections. +# This singleton handles information that's session-wide, but not saved. extends Node -# Not a good idea to preload scenes inside a singleton. +const OptionsDialog = preload("res://src/ui_widgets/options_dialog.tscn") const PathCommandPopup = preload("res://src/ui_widgets/path_popup.tscn") +const DEFAULT_SVG = '' + const path_actions_dict: Dictionary[String, String] = { "move_absolute": "M", "move_relative": "m", "line_absolute": "L", "line_relative": "l", @@ -17,6 +19,180 @@ const path_actions_dict: Dictionary[String, String] = { "shorthand_quadratic_bezier_absolute": "T", "shorthand_quadratic_bezier_relative": "t" } + +signal svg_unknown_change +signal svg_resized + +# These signals copy the ones in ElementRoot. +# ElementRoot is not persistent, while these signals can be connected to reliably. +signal any_attribute_changed(xid: PackedInt32Array) +signal xnodes_added(xids: Array[PackedInt32Array]) +signal xnodes_deleted(xids: Array[PackedInt32Array]) +signal xnodes_moved_in_parent(parent_xid: PackedInt32Array, old_indices: Array[int]) +signal xnodes_moved_to(xids: Array[PackedInt32Array], location: PackedInt32Array) +signal xnode_layout_changed # Emitted together with any of the above 4. +signal basic_xnode_text_changed +signal basic_xnode_rendered_text_changed + +signal parsing_finished(error_id: SVGParser.ParseError) +signal svg_changed # Should only connect to persistent parts of the UI. + +var _svg_current_size := Vector2.ZERO + +var _update_pending := false + +# "unstable_text" is the current state, which might have errors (i.e., while using the +# code editor). "text" is the last state without errors. +# These both differ from "Configs.svg_text" which is the state as saved to file, +# which doesn't happen while dragging handles or typing in the code editor for example. +var unstable_svg_text := "" +var svg_text := "" +var root_element: ElementRoot + +# Temporary unsaved tab, set to the file path string when importing an SVG. +var transient_tab_path := "": + set(new_value): + if transient_tab_path != new_value: + transient_tab_path = new_value + Configs.tabs_changed.emit() + Configs.active_tab_file_path_changed.emit() + setup_from_tab() + +func _enter_tree() -> void: + root_element = ElementRoot.new(Configs.savedata.editor_formatter) + + get_window().mouse_exited.connect(clear_all_hovered) + + xnodes_added.connect(_on_xnodes_added) + xnodes_deleted.connect(_on_xnodes_deleted) + xnodes_moved_in_parent.connect(_on_xnodes_moved_in_parent) + xnodes_moved_to.connect(_on_xnodes_moved_to) + svg_unknown_change.connect(clear_all_selections) + + svg_unknown_change.connect(queue_update) + xnode_layout_changed.connect(queue_update) + any_attribute_changed.connect(queue_update.unbind(1)) + basic_xnode_text_changed.connect(queue_update) + basic_xnode_rendered_text_changed.connect(queue_update) + + Configs.active_tab_changed.connect(setup_from_tab) + 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]) + + +func setup_from_tab() -> void: + var active_tab := Configs.savedata.get_active_tab() + var new_text := active_tab.get_svg_text() + + if not transient_tab_path.is_empty(): + apply_svg_text(DEFAULT_SVG, false) + return + + if not new_text.is_empty(): + apply_svg_text(new_text) + return + + if not active_tab.is_new and not FileAccess.file_exists(active_tab.get_edited_file_path()): + var user_facing_path := active_tab.svg_file_path + var message := Translator.translate( + "The last edited state of this tab could not be found.") + + var options_dialog := OptionsDialog.instantiate() + HandlerGUI.add_dialog(options_dialog) + if user_facing_path.is_empty() or not FileAccess.file_exists(user_facing_path): + options_dialog.setup(Translator.translate("Alert!"), message) + options_dialog.add_option(Translator.translate("Close tab"), + Configs.savedata.remove_active_tab) + else: + options_dialog.setup(Translator.translate("Alert!"), + message + "\n\n" + Translator.translate( + "The tab is bound to the file path {file_path}. Do you want to restore from this path?").\ + format({"file_path": user_facing_path})) + options_dialog.add_option(Translator.translate("Close tab"), + Configs.savedata.remove_active_tab) + options_dialog.add_option(Translator.translate("Restore"), + FileUtils.reset_svg, true) + apply_svg_text(DEFAULT_SVG, false) + return + + active_tab.setup_svg_text(DEFAULT_SVG) + sync_elements() + + +# Syncs text to the elements. +func queue_update() -> void: + _update.call_deferred() + _update_pending = true + +func _update() -> void: + if not _update_pending: + return + _update_pending = false + svg_text = SVGParser.root_to_text(root_element, Configs.savedata.editor_formatter) + svg_changed.emit() + +# Ensure the save happens after the update. +func queue_svg_save() -> void: + _svg_save.call_deferred() + +func _svg_save() -> void: + unstable_svg_text = "" + Configs.savedata.get_active_tab().set_svg_text(svg_text) + + +func sync_elements() -> void: + var text_to_parse := svg_text if unstable_svg_text.is_empty() else unstable_svg_text + var svg_parse_result := SVGParser.text_to_root(text_to_parse, + Configs.savedata.editor_formatter) + parsing_finished.emit(svg_parse_result.error) + if svg_parse_result.error == SVGParser.ParseError.OK: + svg_text = unstable_svg_text + unstable_svg_text = "" + root_element = svg_parse_result.svg + root_element.any_attribute_changed.connect(any_attribute_changed.emit) + root_element.xnodes_added.connect(xnodes_added.emit) + root_element.xnodes_deleted.connect(xnodes_deleted.emit) + root_element.xnodes_moved_in_parent.connect(xnodes_moved_in_parent.emit) + root_element.xnodes_moved_to.connect(xnodes_moved_to.emit) + root_element.xnode_layout_changed.connect(xnode_layout_changed.emit) + root_element.attribute_changed.connect(_on_root_attribute_changed) + root_element.basic_xnode_text_changed.connect(basic_xnode_text_changed.emit) + root_element.basic_xnode_rendered_text_changed.connect( + basic_xnode_rendered_text_changed.emit) + svg_unknown_change.emit() + _update_svg_current_size() + + +func _on_root_attribute_changed(attribute_name: String) -> void: + if attribute_name in ["width", "height", "viewBox"]: + _update_svg_current_size() + +func _update_svg_current_size() -> void: + if _svg_current_size != root_element.get_size(): + _svg_current_size = root_element.get_size() + svg_resized.emit() + + +func apply_svg_text(new_text: String, save := true) -> void: + unstable_svg_text = new_text + sync_elements() + if save: + queue_svg_save() + +func optimize() -> void: + root_element.optimize() + queue_svg_save() + +func get_export_text() -> String: + return SVGParser.root_to_text(root_element, Configs.savedata.export_formatter) + + + signal hover_changed signal selection_changed signal proposed_drop_changed @@ -64,14 +240,6 @@ func set_viewport_size(new_value: Vector2i) -> void: viewport_size_changed.emit() -func _ready() -> void: - SVG.xnodes_added.connect(_on_xnodes_added) - SVG.xnodes_deleted.connect(_on_xnodes_deleted) - SVG.xnodes_moved_in_parent.connect(_on_xnodes_moved_in_parent) - SVG.xnodes_moved_to.connect(_on_xnodes_moved_to) - SVG.changed_unknown.connect(clear_all_selections) - - # Override the selected elements with a single new selected element. # If inner_idx is given, this will be an inner selection. func normal_select(xid: PackedInt32Array, inner_idx := -1) -> void: @@ -196,7 +364,7 @@ func shift_select(xid: PackedInt32Array, inner_idx := -1) -> void: # Select all elements. func select_all() -> void: _clear_inner_selection_no_signal() - var xnode_list: Array[XNode] = SVG.root_element.get_all_xnode_descendants() + var xnode_list: Array[XNode] = root_element.get_all_xnode_descendants() var xid_list: Array = xnode_list.map(func(xnode): return xnode.xid) # The order might not be the same, so ensure like this. if XIDUtils.are_xid_lists_same(xid_list, selected_xids): @@ -285,6 +453,13 @@ func clear_inner_hovered() -> void: semi_hovered_xid.clear() hover_changed.emit() +func clear_all_hovered() -> void: + if not hovered_xid.is_empty() or inner_hovered != -1: + hovered_xid.clear() + inner_hovered = -1 + semi_hovered_xid.clear() + hover_changed.emit() + # Returns whether the given element or inner editor is hovered. func is_hovered(xid: PackedInt32Array, inner_idx := -1, propagate := false) -> bool: if propagate: @@ -397,7 +572,7 @@ func respond_to_key_input(event: InputEventKey) -> void: if inner_selections.is_empty() or event.is_command_or_control_pressed(): # If a single path element is selected, add the new command at the end. if selected_xids.size() == 1: - var xnode_ref := SVG.root_element.get_xnode(selected_xids[0]) + 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(): @@ -418,7 +593,7 @@ func respond_to_key_input(event: InputEventKey) -> void: return # If path commands are selected, insert after the last one. for action_name in path_actions_dict.keys(): - var element_ref := SVG.root_element.get_xnode(semi_selected_xid) + 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") @@ -438,12 +613,12 @@ func respond_to_key_input(event: InputEventKey) -> void: func delete_selected() -> void: if not selected_xids.is_empty(): - SVG.root_element.delete_xnodes(selected_xids) - SVG.queue_save() + root_element.delete_xnodes(selected_xids) + queue_svg_save() elif not inner_selections.is_empty() and not semi_selected_xid.is_empty(): inner_selections.sort() inner_selections.reverse() - var element_ref := SVG.root_element.get_xnode(semi_selected_xid) + var element_ref := root_element.get_xnode(semi_selected_xid) match element_ref.name: "path": element_ref.get_attribute("d").delete_commands(inner_selections) "polygon", "polyline": @@ -454,15 +629,15 @@ func delete_selected() -> void: element_ref.get_attribute("points").delete_elements(indices_to_delete) clear_inner_selection() clear_inner_hovered() - SVG.queue_save() + queue_svg_save() func move_up_selected() -> void: - SVG.root_element.move_xnodes_in_parent(selected_xids, false) - SVG.queue_save() + root_element.move_xnodes_in_parent(selected_xids, false) + queue_svg_save() func move_down_selected() -> void: - SVG.root_element.move_xnodes_in_parent(selected_xids, true) - SVG.queue_save() + root_element.move_xnodes_in_parent(selected_xids, true) + queue_svg_save() func view_in_list(xid: PackedInt32Array) -> void: if xid.is_empty(): @@ -470,11 +645,11 @@ func view_in_list(xid: PackedInt32Array) -> void: requested_scroll_to_element_editor.emit(xid) func duplicate_selected() -> void: - SVG.root_element.duplicate_xnodes(selected_xids) - SVG.queue_save() + root_element.duplicate_xnodes(selected_xids) + queue_svg_save() func insert_path_command_after_selection(new_command: String) -> void: - var path_attrib: AttributePathdata = SVG.root_element.get_xnode( + var path_attrib: AttributePathdata = root_element.get_xnode( semi_selected_xid).get_attribute("d") var last_selection: int = inner_selections.max() # Z after a Z is syntactically invalid. @@ -483,15 +658,15 @@ func insert_path_command_after_selection(new_command: String) -> void: return path_attrib.insert_command(last_selection + 1, new_command) normal_select(semi_selected_xid, last_selection + 1) - SVG.queue_save() + queue_svg_save() func insert_point_after_selection() -> void: - var element_ref: Element = SVG.root_element.get_xnode(semi_selected_xid) + var element_ref: Element = root_element.get_xnode(semi_selected_xid) var last_selection_next: int = inner_selections.max() + 1 element_ref.get_attribute("points").insert_element(last_selection_next * 2, 0.0) element_ref.get_attribute("points").insert_element(last_selection_next * 2, 0.0) normal_select(semi_selected_xid, last_selection_next) - SVG.queue_save() + queue_svg_save() enum Context { @@ -517,7 +692,7 @@ func get_selection_context(popup_method: Callable, context: Context) -> ContextP can_move_up = false var parent_xid := XIDUtils.get_parent_xid(filtered_xids[0]) var filtered_count := filtered_xids.size() - var parent_child_count: int = SVG.root_element.get_xnode(parent_xid).get_child_count() + var parent_child_count: int = root_element.get_xnode(parent_xid).get_child_count() for base_xid in filtered_xids: if not can_move_up and base_xid[-1] >= filtered_count: can_move_up = true @@ -533,7 +708,7 @@ func get_selection_context(popup_method: Callable, context: Context) -> ContextP duplicate_selected, false, load("res://assets/icons/Duplicate.svg"), "duplicate")) - var xnode := SVG.root_element.get_xnode(selected_xids[0]) + var xnode := root_element.get_xnode(selected_xids[0]) if (selected_xids.size() == 1 and not xnode.is_element()) or\ (xnode.is_element() and not xnode.possible_conversions.is_empty()): btn_arr.append(ContextPopup.create_button( @@ -556,7 +731,7 @@ func get_selection_context(popup_method: Callable, context: Context) -> ContextP delete_selected, false, load("res://assets/icons/Delete.svg"), "delete")) elif not inner_selections.is_empty() and not semi_selected_xid.is_empty(): - var element_ref := SVG.root_element.get_xnode(semi_selected_xid) + var element_ref := root_element.get_xnode(semi_selected_xid) if context == Context.VIEWPORT: btn_arr.append(ContextPopup.create_button( @@ -594,7 +769,7 @@ func popup_convert_to_context(popup_method: Callable) -> void: # The "Convert To" context popup. if not selected_xids.is_empty(): var btn_arr: Array[Button] = [] - var xnode := SVG.root_element.get_xnode(selected_xids[0]) + var xnode := root_element.get_xnode(selected_xids[0]) if not xnode.is_element(): for xnode_type in xnode.get_possible_conversions(): var btn := ContextPopup.create_button(BasicXNode.get_type_string(xnode_type), @@ -613,8 +788,8 @@ func popup_convert_to_context(popup_method: Callable) -> void: context_popup.setup(btn_arr, true) popup_method.call(context_popup) elif not inner_selections.is_empty() and not semi_selected_xid.is_empty(): - var path_attrib: AttributePathdata =\ - SVG.root_element.get_xnode(semi_selected_xid).get_attribute("d") + var path_attrib: AttributePathdata = root_element.get_xnode( + semi_selected_xid).get_attribute("d") var selection_idx: int = inner_selections.max() var cmd_char := path_attrib.get_command(selection_idx).command_char @@ -637,8 +812,8 @@ func popup_convert_to_context(popup_method: Callable) -> void: command_picker.path_command_picked.connect(convert_selected_command_to) func popup_insert_command_after_context(popup_method: Callable) -> void: - var path_attrib: AttributePathdata =\ - SVG.root_element.get_xnode(semi_selected_xid).get_attribute("d") + var path_attrib: AttributePathdata = root_element.get_xnode( + semi_selected_xid).get_attribute("d") var selection_idx: int = inner_selections.max() var cmd_char := path_attrib.get_command(selection_idx).command_char @@ -664,17 +839,17 @@ func popup_insert_command_after_context(popup_method: Callable) -> void: func convert_selected_element_to(element_name: String) -> void: var xid := selected_xids[0] - SVG.root_element.replace_xnode(xid, - SVG.root_element.get_xnode(xid).get_replacement(element_name)) - SVG.queue_save() + root_element.replace_xnode(xid, + root_element.get_xnode(xid).get_replacement(element_name)) + queue_svg_save() func convert_selected_xnode_to(xnode_type: BasicXNode.NodeType) -> void: var xid := selected_xids[0] - SVG.root_element.replace_xnode(xid, - SVG.root_element.get_xnode(xid).get_replacement(xnode_type)) - SVG.queue_save() + root_element.replace_xnode(xid, + root_element.get_xnode(xid).get_replacement(xnode_type)) + queue_svg_save() func convert_selected_command_to(cmd_type: String) -> void: - SVG.root_element.get_xnode(semi_selected_xid).get_attribute("d").convert_command( + root_element.get_xnode(semi_selected_xid).get_attribute("d").convert_command( inner_selections[0], cmd_type) - SVG.queue_save() + queue_svg_save() diff --git a/src/autoload/Indications.gd.uid b/src/autoload/State.gd.uid similarity index 100% rename from src/autoload/Indications.gd.uid rename to src/autoload/State.gd.uid diff --git a/src/config_classes/Formatter.gd b/src/config_classes/Formatter.gd index 64bbb0a2..05e2a4ea 100644 --- a/src/config_classes/Formatter.gd +++ b/src/config_classes/Formatter.gd @@ -1,4 +1,4 @@ -# A resource for the color palettes that are listed in the color picker. +# A resource used to determine how to structure the XML and represent attributes. class_name Formatter extends ConfigResource enum Preset {COMPACT, PRETTY} diff --git a/src/config_classes/SaveData.gd b/src/config_classes/SaveData.gd index c5809424..51adcb2e 100644 --- a/src/config_classes/SaveData.gd +++ b/src/config_classes/SaveData.gd @@ -65,8 +65,6 @@ const CURRENT_VERSION = 1 @export var language := "": set(new_value): - if not language in TranslationServer.get_loaded_locales(): - new_value = "en" if language != new_value: language = new_value emit_changed() @@ -224,7 +222,7 @@ const CURRENT_VERSION = 1 if use_filename_for_window_title != new_value: use_filename_for_window_title = new_value emit_changed() - Configs.update_window_title.call_deferred() + HandlerGUI.update_window_title.call_deferred() const HANDLE_SIZE_MIN = 0.5 const HANDLE_SIZE_MAX = 4.0 @@ -298,14 +296,6 @@ const MAX_SNAP = 16384 file_dialog_show_hidden = new_value emit_changed() -@export var current_file_path := "": - set(new_value): - if current_file_path != new_value: - current_file_path = new_value - emit_changed() - Configs.update_window_title.call_deferred() - Configs.file_path_changed.emit() - @export var shortcut_panel_layout := ShortcutPanel.Layout.HORIZONTAL_STRIP: set(new_value): # Validation @@ -335,7 +325,7 @@ func _validate_recent_dirs() -> void: # Remove non-existent dirs. for i in range(unique_dirs.size() - 1, -1, -1): if not DirAccess.dir_exists_absolute(unique_dirs[i]): - _recent_dirs.remove_at(i) + unique_dirs.remove_at(i) # Remove dirs above the maximum. if unique_dirs.size() > MAX_RECENT_DIRS: unique_dirs.resize(MAX_RECENT_DIRS) @@ -345,13 +335,6 @@ func get_recent_dirs() -> PackedStringArray: _validate_recent_dirs() return _recent_dirs -func get_last_dir() -> String: - _validate_recent_dirs() - if _recent_dirs.is_empty() or not DirAccess.dir_exists_absolute(_recent_dirs[0]): - return OS.get_system_dir(OS.SYSTEM_DIR_PICTURES) - else: - return _recent_dirs[0] - func add_recent_dir(dir: String) -> void: _validate_recent_dirs() # Remove occurrences of this dir in the array. @@ -368,7 +351,10 @@ func add_recent_dir(dir: String) -> void: if _shortcuts != new_value: _shortcuts = new_value for action in _shortcuts: - _action_sync_inputmap(action) + if InputMap.has_action(action): + _action_sync_inputmap(action) + else: + _shortcuts.erase(action) update_shortcut_validities() emit_changed() Configs.shortcuts_changed.emit() @@ -480,6 +466,7 @@ func replace_palette(idx: int, new_palette: Palette) -> void: if _palettes.size() <= idx: return _palettes[idx] = new_palette + new_palette.changed.connect(emit_changed) _update_palette_validities() emit_changed() @@ -513,7 +500,7 @@ func set_palettes(new_palettes: Array[Palette]) -> void: editor_formatter = new_value emit_changed() editor_formatter.changed.connect(emit_changed) - editor_formatter.changed_deferred.connect(SVG.sync_elements) + editor_formatter.changed_deferred.connect(State.sync_elements) @export var export_formatter: Formatter = null: set(new_value): @@ -562,8 +549,192 @@ func erase_shortcut_panel_slot(slot: int) -> void: Configs.shortcut_panel_changed.emit() +const MAX_TABS = 5 +@export var _tabs: Array[TabData] = []: + set(new_value): + # Validation + var used_ids := PackedInt32Array() + for idx in range(new_value.size() - 1, -1, -1): + var tab = new_value[idx] + if not is_instance_valid(tab) or tab.id in used_ids: + new_value.remove_at(idx) + else: + used_ids.append(tab.id) + + if new_value.size() > MAX_TABS: + new_value.resize(MAX_TABS) + # Main part + if _tabs != new_value: + _tabs = new_value + if _active_tab_index >= _tabs.size(): + set_active_tab_index(0) + + for tab in _tabs: + tab.changed.connect(emit_changed) + tab.file_path_changed.connect(_on_tab_file_path_changed.bind(tab.id)) + emit_changed() + if _tabs.is_empty(): + _add_new_tab() + +@export var _active_tab_index := 0: + set(new_value): + # Validation + if _tabs.is_empty(): + _add_new_tab() + + new_value = clampi(new_value, 0, _tabs.size() - 1) + if is_nan(new_value): + new_value = 0 + # Main part + if _active_tab_index != new_value: + _active_tab_index = new_value + emit_changed() + +func _on_tab_file_path_changed(id: int): + if id == _tabs[_active_tab_index].id: + Configs.active_tab_file_path_changed.emit() + +func has_tabs() -> bool: + return not _tabs.is_empty() + +func get_tab_count() -> int: + return _tabs.size() + +func get_tab(idx: int) -> TabData: + return _tabs[idx] if (idx < _tabs.size() and idx >= 0) else null + +func get_active_tab() -> TabData: + return get_tab(_active_tab_index) + +func get_tabs() -> Array[TabData]: + return _tabs + + +func get_active_tab_index() -> int: + return _active_tab_index + +func set_active_tab_index(new_index: int) -> void: + if _active_tab_index == new_index: + return + + if new_index >= _tabs.size() or new_index < 0: + return + + if _active_tab_index >= 0 and _active_tab_index < _tabs.size(): + _tabs[_active_tab_index].deactivate() + var old_id := _tabs[_active_tab_index].id + _active_tab_index = new_index + _tabs[_active_tab_index].activate() + if old_id != _tabs[_active_tab_index].id: + Configs.active_tab_changed.emit() + +func _add_new_tab() -> void: + if _tabs.size() >= MAX_TABS: + return + + var used_ids := PackedInt32Array() + for tab in _tabs: + used_ids.append(tab.id) + var new_id := 1 + while true: + if not new_id in used_ids: + var new_tab := TabData.new(new_id) + new_tab.is_new = true + new_tab.changed.connect(emit_changed) + new_tab.file_path_changed.connect(_on_tab_file_path_changed.bind(new_id)) + _tabs.append(new_tab) + return + new_id += 1 + +func add_empty_tab() -> void: + _add_new_tab() + emit_changed() + Configs.tabs_changed.emit() + set_active_tab_index(_tabs.size() - 1) + +# Adds a new path with the given path, unless something with the path already exists. +func add_tab_with_path(new_file_path: String) -> void: + for idx in _tabs.size(): + if _tabs[idx].svg_file_path == new_file_path: + set_active_tab_index(idx) + return + _add_new_tab() + _tabs[-1].svg_file_path = new_file_path + emit_changed() + Configs.tabs_changed.emit() + set_active_tab_index(_tabs.size() - 1) + +func remove_tabs(indices: PackedInt32Array) -> void: + # Validate the passed indices. + var indices_to_remove := PackedInt32Array() + for idx in indices: + if idx >= 0 and idx < _tabs.size() and not idx in indices_to_remove: + indices_to_remove.append(idx) + + if indices_to_remove.is_empty(): + return + + var new_active_tab_index := _active_tab_index + # For each index, remove the tab. If there are no tabs in the end, add one. + for idx in range(_tabs.size() - 1, -1, -1): + if idx in indices_to_remove: + _tabs.remove_at(idx) + if idx < _active_tab_index: + new_active_tab_index -= 1 + + # Clear unnecessary files. + var used_file_paths := PackedStringArray() + for tab in _tabs: + used_file_paths.append(tab.get_edited_file_path()) + + for file_name in DirAccess.get_files_at(TabData.EDITED_FILES_DIR): + var full_path := TabData.EDITED_FILES_DIR.path_join(file_name) + if not full_path in used_file_paths: + DirAccess.remove_absolute(TabData.EDITED_FILES_DIR.path_join(file_name)) + + if _tabs.is_empty(): + _add_new_tab() + + emit_changed() + Configs.tabs_changed.emit() + var has_tab_changed := (_active_tab_index in indices_to_remove) + _active_tab_index = clampi(new_active_tab_index, 0, _tabs.size() - 1) + _tabs[_active_tab_index].activate() + if has_tab_changed: + Configs.active_tab_changed.emit() + +func remove_active_tab() -> void: + remove_tabs(PackedInt32Array([_active_tab_index])) + +func move_tab(old_idx: int, new_idx: int) -> void: + if old_idx == new_idx or old_idx < 0 or old_idx > get_tab_count() or\ + new_idx < 0 or new_idx > get_tab_count(): + return + + var tab: TabData = _tabs.pop_at(old_idx) + var adjusted_index := (new_idx - 1) if (old_idx < new_idx) else new_idx + _tabs.insert(adjusted_index, tab) + emit_changed() + set_active_tab_index(adjusted_index) + Configs.tabs_changed.emit() + + # Utility func get_validity_color(error_condition: bool, warning_condition := false) -> Color: return basic_color_error if error_condition else\ basic_color_warning if warning_condition else basic_color_valid + +func get_active_tab_dir() -> String: + var tab := get_active_tab() + if tab.svg_file_path.is_empty(): + return get_last_dir() + else: + return tab.svg_file_path.get_base_dir() + +func get_last_dir() -> String: + _validate_recent_dirs() + if _recent_dirs.is_empty() or not DirAccess.dir_exists_absolute(_recent_dirs[0]): + return OS.get_system_dir(OS.SYSTEM_DIR_PICTURES) + else: + return _recent_dirs[0] diff --git a/src/config_classes/TabData.gd b/src/config_classes/TabData.gd new file mode 100644 index 00000000..f5b25f1f --- /dev/null +++ b/src/config_classes/TabData.gd @@ -0,0 +1,92 @@ +# A resource that keeps track of the tabs. +class_name TabData extends ConfigResource + +const EDITED_FILES_DIR = "user://edited" + +signal file_path_changed + +var is_new := false +var undo_redo: UndoRedo +var reference_image: Texture2D + +# This variable represents the saved state of the SVG. Intermediate operations such as +# dragging a handle or editing the code shouldn't affect this variable. +var _svg_text := "" + +func set_svg_text(new_text: String) -> void: + if new_text == _svg_text: + return + + if not is_instance_valid(undo_redo): + undo_redo = UndoRedo.new() + var old_value := _svg_text + undo_redo.create_action("") + undo_redo.add_do_property(self, "_svg_text", new_text) + undo_redo.add_undo_property(self, "_svg_text", old_value) + undo_redo.add_do_property(State, "svg_text", new_text) + undo_redo.add_undo_property(State, "svg_text", old_value) + undo_redo.add_do_method(_save_svg_text) + undo_redo.add_undo_method(_save_svg_text) + undo_redo.commit_action() + +func _save_svg_text() -> void: + if not FileAccess.file_exists(get_edited_file_path()): + DirAccess.make_dir_recursive_absolute(get_edited_file_path().get_base_dir()) + FileAccess.open(get_edited_file_path(), FileAccess.WRITE).store_string(_svg_text) + +func setup_svg_text(new_text: String) -> void: + _svg_text = new_text + State.svg_text = new_text + _save_svg_text() + is_new = false + +func get_svg_text() -> String: + return _svg_text + + +@export var svg_file_path: String: + set(new_value): + if svg_file_path != new_value: + svg_file_path = new_value + emit_changed() + file_path_changed.emit() + +@export var id := -1: + set(new_value): + if id != new_value: + id = new_value + emit_changed() + +func _init(new_id := -1) -> void: + id = new_id + super() + +func get_edited_file_path() -> String: + return "%s/save%d.svg" % [EDITED_FILES_DIR, id] + + +func _notification(what: int) -> void: + if what == NOTIFICATION_PREDELETE: + if is_instance_valid(undo_redo): + undo_redo.free() + +func undo() -> void: + if is_instance_valid(undo_redo) and undo_redo.has_undo(): + undo_redo.undo() + State.sync_elements() + +func redo() -> void: + if is_instance_valid(undo_redo) and undo_redo.has_redo(): + undo_redo.redo() + State.sync_elements() + +func get_presented_name() -> String: + return svg_file_path.get_file() if not svg_file_path.is_empty() else\ + "[ %s ]" % Translator.translate("Unsaved") + + +func activate() -> void: + _svg_text = FileAccess.get_file_as_string(get_edited_file_path()) + +func deactivate() -> void: + _svg_text = "" diff --git a/src/config_classes/TabData.gd.uid b/src/config_classes/TabData.gd.uid new file mode 100644 index 00000000..dece06af --- /dev/null +++ b/src/config_classes/TabData.gd.uid @@ -0,0 +1 @@ +uid://doyq1jmdgfuk8 diff --git a/src/ui_parts/about_menu.gd b/src/ui_parts/about_menu.gd index d2edeeca..c425b995 100644 --- a/src/ui_parts/about_menu.gd +++ b/src/ui_parts/about_menu.gd @@ -94,6 +94,7 @@ func _ready() -> void: translations_list.add_child(list) close_button.pressed.connect(queue_free) + close_button.text = Translator.translate("Close") %ProjectFounder/Label.text = Translator.translate("Project Founder and Manager") %Developers/Label.text = Translator.translate("Developers") diff --git a/src/ui_parts/about_menu.tscn b/src/ui_parts/about_menu.tscn index d1c3284c..db5ee43e 100644 --- a/src/ui_parts/about_menu.tscn +++ b/src/ui_parts/about_menu.tscn @@ -1,11 +1,11 @@ [gd_scene load_steps=8 format=3 uid="uid://mhfp37lr7q4f"] -[ext_resource type="Script" path="res://src/ui_parts/about_menu.gd" id="1_xxltt"] +[ext_resource type="Script" uid="uid://ys8g367cpqc2" path="res://src/ui_parts/about_menu.gd" id="1_xxltt"] [ext_resource type="Texture2D" uid="uid://barsurula6j8n" path="res://assets/logos/icon.svg" id="2_t7fbd"] [ext_resource type="FontFile" uid="uid://depydd16jq777" path="res://assets/fonts/FontMono.ttf" id="3_e8i1t"] [ext_resource type="FontFile" uid="uid://dc0w4sx0h0fui" path="res://assets/fonts/FontBold.ttf" id="4_n6gp0"] [ext_resource type="Texture2D" uid="uid://cgxpm1e3v0i3v" path="res://assets/icons/Link.svg" id="6_hbk78"] -[ext_resource type="Script" path="res://src/ui_widgets/GridDrawingControl.gd" id="7_nvctb"] +[ext_resource type="Script" uid="uid://ci44864moadn" path="res://src/ui_widgets/GridDrawingControl.gd" id="7_nvctb"] [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_jtvwe"] content_margin_left = 6.0 diff --git a/src/ui_parts/display.gd b/src/ui_parts/display.gd index a2d3fb45..72526ace 100644 --- a/src/ui_parts/display.gd +++ b/src/ui_parts/display.gd @@ -7,7 +7,7 @@ const NumberEditType = preload("res://src/ui_widgets/number_edit.gd") const BetterToggleButtonType = preload("res://src/ui_widgets/BetterToggleButton.gd") const NumberField = preload("res://src/ui_widgets/number_field.tscn") -const ConfirmDialog := preload("res://src/ui_parts/confirm_dialog.tscn") +const ConfirmDialog := preload("res://src/ui_widgets/confirm_dialog.tscn") @onready var viewport: SubViewport = %Viewport @onready var controls: Control = %Viewport/Controls @@ -15,10 +15,8 @@ const ConfirmDialog := preload("res://src/ui_parts/confirm_dialog.tscn") @onready var reference_texture = %Viewport/ReferenceTexture @onready var reference_button = %LeftMenu/Reference @onready var visuals_button: Button = %LeftMenu/Visuals -@onready var more_button: Button = %LeftMenu/MoreOptions @onready var snapper: NumberEditType = %LeftMenu/Snapping/SnapNumberEdit @onready var snap_button: BetterToggleButtonType = %LeftMenu/Snapping/SnapButton -@onready var panel_container: PanelContainer = $PanelContainer @onready var viewport_panel: PanelContainer = $ViewportPanel @onready var debug_container: MarginContainer = $ViewportPanel/DebugMargins @onready var debug_label: Label = %DebugContainer/DebugLabel @@ -30,6 +28,7 @@ func _ready() -> void: Configs.language_changed.connect(update_translations) Configs.snap_changed.connect(update_snap_config) Configs.theme_changed.connect(update_theme) + Configs.active_tab_changed.connect(update_reference_image) update_translations() update_theme() update_snap_config() @@ -62,7 +61,6 @@ func _unhandled_input(event: InputEvent) -> void: func update_translations() -> void: - %LeftMenu/Settings.tooltip_text = Translator.translate("Settings") %LeftMenu/Visuals.tooltip_text = Translator.translate("Visuals") %LeftMenu/Snapping/SnapButton.tooltip_text =\ TranslationUtils.get_shortcut_description("toggle_snap") @@ -70,10 +68,6 @@ func update_translations() -> void: "Snap size") func update_theme() -> void: - var stylebox := StyleBoxFlat.new() - stylebox.bg_color = ThemeUtils.overlay_panel_inner_color - stylebox.set_content_margin_all(6) - panel_container.add_theme_stylebox_override("panel", stylebox) var frame := StyleBoxFlat.new() frame.draw_center = false frame.border_width_left = 2 @@ -91,12 +85,8 @@ func update_snap_config() -> void: snapper.set_value(absf(snap_config)) snap_settings_updated.emit(snap_enabled, absf(snap_config)) - -func _on_settings_pressed() -> void: - ShortcutUtils.fn_call("open_settings") - -func open_savedata_folder() -> void: - OS.shell_show_in_file_manager(ProjectSettings.globalize_path("user://")) +func update_reference_image() -> void: + apply_reference(Configs.savedata.get_active_tab().reference_image) func _on_reference_pressed() -> void: @@ -130,42 +120,6 @@ func _on_visuals_button_pressed() -> void: HandlerGUI.popup_under_rect_center(visuals_popup, visuals_button.get_global_rect(), get_viewport()) -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")) - - 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.png"), - "about_info") - about_btn.expand_icon = true - buttons_arr.append(about_btn) - buttons_arr.append(ContextPopup.create_button(Translator.translate( - "Donate…"), ShortcutUtils.fn("about_donate"), false, - load("res://assets/icons/Heart.svg"), "about_donate")) - buttons_arr.append(ContextPopup.create_button(Translator.translate( - "GodSVG repository"), ShortcutUtils.fn("about_repo"), false, - load("res://assets/icons/Link.svg"), "about_repo")) - buttons_arr.append(ContextPopup.create_button(Translator.translate( - "GodSVG website"), ShortcutUtils.fn("about_website"), false, - load("res://assets/icons/Link.svg"), "about_website")) - var separator_indices := PackedInt32Array([1, 3]) - if can_show_savedata_folder: - separator_indices = PackedInt32Array([2, 4]) - - var more_popup := ContextPopup.new() - more_popup.setup(buttons_arr, true, -1, -1, separator_indices) - HandlerGUI.popup_under_rect_center(more_popup, more_button.get_global_rect(), - get_viewport()) - func toggle_grid_visuals() -> void: grid_visuals.visible = not grid_visuals.visible @@ -199,8 +153,16 @@ func finish_reference_import(data: Variant, file_path: String) -> void: "png": img.load_png_from_buffer(data) "jpg", "jpeg": img.load_jpg_from_buffer(data) "webp": img.load_webp_from_buffer(data) - reference_texture.texture = ImageTexture.create_from_image(img) - reference_texture.show() + var image_texture := ImageTexture.create_from_image(img) + Configs.savedata.get_active_tab().reference_image = image_texture + apply_reference(image_texture) + +func apply_reference(reference: Texture2D) -> void: + if is_instance_valid(reference): + reference_texture.texture = reference + reference_texture.show() + else: + reference_texture.hide() func toggle_snap() -> void: snap_button.button_pressed = not snap_button.button_pressed @@ -218,12 +180,12 @@ func _on_snap_number_edit_value_changed(new_value: float) -> void: # The strings here are intentionally not localized. func update_debug() -> void: var debug_text := "" - debug_text += "FPS: %s\n" % Performance.get_monitor(Performance.TIME_FPS) + debug_text += "FPS: %d\n" % Performance.get_monitor(Performance.TIME_FPS) debug_text += "Static Mem: %s\n" % String.humanize_size(int(Performance.get_monitor( Performance.MEMORY_STATIC))) - debug_text += "Nodes: %s\n" % Performance.get_monitor(Performance.OBJECT_NODE_COUNT) - debug_text += "Stray nodes: %s\n" % Performance.get_monitor(Performance.OBJECT_ORPHAN_NODE_COUNT) - debug_text += "Objects: %s\n" % Performance.get_monitor(Performance.OBJECT_COUNT) + debug_text += "Nodes: %d\n" % Performance.get_monitor(Performance.OBJECT_NODE_COUNT) + debug_text += "Stray nodes: %d\n" % Performance.get_monitor(Performance.OBJECT_ORPHAN_NODE_COUNT) + debug_text += "Objects: %d\n" % Performance.get_monitor(Performance.OBJECT_COUNT) debug_label.text = debug_text # Set up the next update if the container is still visible. if debug_container.visible: diff --git a/src/ui_parts/display.tscn b/src/ui_parts/display.tscn index e56c0011..d57ba70c 100644 --- a/src/ui_parts/display.tscn +++ b/src/ui_parts/display.tscn @@ -1,20 +1,19 @@ -[gd_scene load_steps=18 format=3 uid="uid://bvrncl7e6yn5b"] +[gd_scene load_steps=17 format=3 uid="uid://bvrncl7e6yn5b"] -[ext_resource type="Script" path="res://src/ui_parts/display.gd" id="1_oib5g"] -[ext_resource type="Texture2D" uid="uid://ccbta5q43jobk" path="res://assets/icons/More.svg" id="2_3wliq"] -[ext_resource type="Texture2D" uid="uid://ckkkgof1hcbld" path="res://assets/icons/Gear.svg" id="3_0w618"] +[ext_resource type="Script" uid="uid://bxmb134e3sqpr" path="res://src/ui_parts/display.gd" id="1_oib5g"] [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" path="res://src/ui_widgets/BetterToggleButton.gd" id="6_3v3ve"] +[ext_resource type="Script" uid="uid://ynx3s1jc6bwq" path="res://src/ui_widgets/BetterToggleButton.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" path="res://src/ui_parts/viewport.gd" id="9_4xrk7"] -[ext_resource type="Shader" path="res://src/shaders/zoom_shader.gdshader" id="10_x7ybk"] +[ext_resource type="Script" uid="uid://b6pmlbnl76wmm" path="res://src/ui_parts/viewport.gd" id="9_4xrk7"] +[ext_resource type="Script" uid="uid://rqrxhe8wa6fn" path="res://src/ui_parts/tab_bar.gd" id="9_rll1m"] +[ext_resource type="Shader" uid="uid://i2y5pyhcgra2" path="res://src/shaders/zoom_shader.gdshader" id="10_x7ybk"] [ext_resource type="Texture2D" uid="uid://c68og6bsqt0lb" path="res://assets/icons/backgrounds/Checkerboard.svg" id="11_1bm1s"] -[ext_resource type="Script" path="res://src/ui_parts/display_texture.gd" id="12_qi23s"] -[ext_resource type="Script" path="res://src/ui_parts/handles_manager.gd" id="13_lwhwy"] -[ext_resource type="Script" path="res://src/ui_parts/camera.gd" id="15_hevpa"] +[ext_resource type="Script" uid="uid://dtplje5mhdmrj" path="res://src/ui_parts/display_texture.gd" id="12_qi23s"] +[ext_resource type="Script" uid="uid://csqewpxr21ywy" path="res://src/ui_parts/handles_manager.gd" id="13_lwhwy"] +[ext_resource type="Script" uid="uid://cm5033meho5vr" path="res://src/ui_widgets/camera.gd" id="15_hevpa"] [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_eujxa"] bg_color = Color(0.866667, 0.933333, 1, 0.133333) @@ -34,61 +33,61 @@ grow_vertical = 2 theme_override_constants/separation = 0 script = ExtResource("1_oib5g") -[node name="PanelContainer" type="PanelContainer" parent="."] +[node name="TabBar" type="Control" parent="."] +clip_contents = true +custom_minimum_size = Vector2(0, 24) layout_mode = 2 -size_flags_vertical = 0 +size_flags_horizontal = 3 +script = ExtResource("9_rll1m") -[node name="HBoxContainer" type="HBoxContainer" parent="PanelContainer"] +[node name="ViewportPanel" type="PanelContainer" parent="."] layout_mode = 2 -alignment = 2 +size_flags_vertical = 3 -[node name="LeftMenu" type="HBoxContainer" parent="PanelContainer/HBoxContainer"] -unique_name_in_owner = true +[node name="VBoxContainer" type="VBoxContainer" parent="ViewportPanel"] layout_mode = 2 -size_flags_horizontal = 2 -theme_override_constants/separation = 5 +theme_override_constants/separation = 0 -[node name="MoreOptions" type="Button" parent="PanelContainer/HBoxContainer/LeftMenu"] +[node name="MarginContainer" type="MarginContainer" parent="ViewportPanel/VBoxContainer"] layout_mode = 2 -size_flags_horizontal = 2 -focus_mode = 0 -mouse_default_cursor_shape = 2 -theme_type_variation = &"IconButton" -icon = ExtResource("2_3wliq") -icon_alignment = 1 +theme_override_constants/margin_left = 4 +theme_override_constants/margin_top = 4 +theme_override_constants/margin_right = 4 +theme_override_constants/margin_bottom = 4 -[node name="Settings" type="Button" parent="PanelContainer/HBoxContainer/LeftMenu"] +[node name="ViewportOptions" type="HBoxContainer" parent="ViewportPanel/VBoxContainer/MarginContainer"] +layout_mode = 2 +alignment = 2 + +[node name="LeftMenu" type="HBoxContainer" parent="ViewportPanel/VBoxContainer/MarginContainer/ViewportOptions"] +unique_name_in_owner = true layout_mode = 2 size_flags_horizontal = 2 -focus_mode = 0 -mouse_default_cursor_shape = 2 -theme_type_variation = &"IconButton" -icon = ExtResource("3_0w618") -icon_alignment = 1 +theme_override_constants/separation = 5 -[node name="Reference" type="Button" parent="PanelContainer/HBoxContainer/LeftMenu"] +[node name="Visuals" type="Button" parent="ViewportPanel/VBoxContainer/MarginContainer/ViewportOptions/LeftMenu"] layout_mode = 2 size_flags_horizontal = 2 focus_mode = 0 mouse_default_cursor_shape = 2 theme_type_variation = &"IconButton" -icon = ExtResource("4_2hiq7") +icon = ExtResource("4_n3qjt") icon_alignment = 1 -[node name="Visuals" type="Button" parent="PanelContainer/HBoxContainer/LeftMenu"] +[node name="Reference" type="Button" parent="ViewportPanel/VBoxContainer/MarginContainer/ViewportOptions/LeftMenu"] layout_mode = 2 size_flags_horizontal = 2 focus_mode = 0 mouse_default_cursor_shape = 2 theme_type_variation = &"IconButton" -icon = ExtResource("4_n3qjt") +icon = ExtResource("4_2hiq7") icon_alignment = 1 -[node name="Snapping" type="HBoxContainer" parent="PanelContainer/HBoxContainer/LeftMenu"] +[node name="Snapping" type="HBoxContainer" parent="ViewportPanel/VBoxContainer/MarginContainer/ViewportOptions/LeftMenu"] layout_mode = 2 theme_override_constants/separation = 0 -[node name="SnapButton" type="Button" parent="PanelContainer/HBoxContainer/LeftMenu/Snapping"] +[node name="SnapButton" type="Button" parent="ViewportPanel/VBoxContainer/MarginContainer/ViewportOptions/LeftMenu/Snapping"] layout_mode = 2 focus_mode = 0 mouse_default_cursor_shape = 2 @@ -98,7 +97,7 @@ icon = ExtResource("5_1k2cq") script = ExtResource("6_3v3ve") hover_pressed_stylebox = SubResource("StyleBoxFlat_eujxa") -[node name="SnapNumberEdit" parent="PanelContainer/HBoxContainer/LeftMenu/Snapping" instance=ExtResource("7_wrrfr")] +[node name="SnapNumberEdit" parent="ViewportPanel/VBoxContainer/MarginContainer/ViewportOptions/LeftMenu/Snapping" instance=ExtResource("7_wrrfr")] custom_minimum_size = Vector2(48, 22) layout_mode = 2 theme_type_variation = &"LeftConnectedLineEdit" @@ -107,38 +106,34 @@ editable = false min_value = 0.001 allow_lower = false -[node name="ZoomMenu" parent="PanelContainer/HBoxContainer" instance=ExtResource("8_xtdmn")] +[node name="ZoomMenu" parent="ViewportPanel/VBoxContainer/MarginContainer/ViewportOptions" instance=ExtResource("8_xtdmn")] unique_name_in_owner = true layout_mode = 2 -[node name="ViewportPanel" type="PanelContainer" parent="."] -layout_mode = 2 -size_flags_vertical = 3 - -[node name="ViewportContainer" type="SubViewportContainer" parent="ViewportPanel"] +[node name="ViewportContainer" type="SubViewportContainer" parent="ViewportPanel/VBoxContainer"] custom_minimum_size = Vector2(450, 0) layout_mode = 2 size_flags_vertical = 3 stretch = true -[node name="Viewport" type="SubViewport" parent="ViewportPanel/ViewportContainer"] +[node name="Viewport" type="SubViewport" parent="ViewportPanel/VBoxContainer/ViewportContainer"] unique_name_in_owner = true disable_3d = true handle_input_locally = false gui_snap_controls_to_pixels = false -size = Vector2i(450, 2) +size = Vector2i(1040, 587) size_2d_override_stretch = true render_target_update_mode = 4 script = ExtResource("9_4xrk7") -[node name="ReferenceTexture" type="TextureRect" parent="ViewportPanel/ViewportContainer/Viewport"] +[node name="ReferenceTexture" type="TextureRect" parent="ViewportPanel/VBoxContainer/ViewportContainer/Viewport"] visible = false offset_right = 128.0 offset_bottom = 128.0 expand_mode = 1 stretch_mode = 5 -[node name="Checkerboard" type="TextureRect" parent="ViewportPanel/ViewportContainer/Viewport"] +[node name="Checkerboard" type="TextureRect" parent="ViewportPanel/VBoxContainer/ViewportContainer/Viewport"] texture_filter = 1 material = SubResource("ShaderMaterial_kqplg") clip_contents = true @@ -146,7 +141,7 @@ texture = ExtResource("11_1bm1s") expand_mode = 1 stretch_mode = 1 -[node name="DisplayTexture" type="TextureRect" parent="ViewportPanel/ViewportContainer/Viewport/Checkerboard"] +[node name="DisplayTexture" type="TextureRect" parent="ViewportPanel/VBoxContainer/ViewportContainer/Viewport/Checkerboard"] clip_contents = true layout_mode = 1 anchors_preset = 15 @@ -157,14 +152,13 @@ grow_vertical = 2 expand_mode = 1 script = ExtResource("12_qi23s") -[node name="Controls" type="Control" parent="ViewportPanel/ViewportContainer/Viewport"] -custom_minimum_size = Vector2(16384, 16384) +[node name="Controls" type="Control" parent="ViewportPanel/VBoxContainer/ViewportContainer/Viewport"] layout_mode = 3 anchors_preset = 0 mouse_filter = 1 script = ExtResource("13_lwhwy") -[node name="Camera" type="Control" parent="ViewportPanel/ViewportContainer/Viewport"] +[node name="Camera" type="Control" parent="ViewportPanel/VBoxContainer/ViewportContainer/Viewport"] layout_mode = 3 anchors_preset = 0 mouse_filter = 2 @@ -176,13 +170,14 @@ layout_mode = 2 size_flags_horizontal = 8 size_flags_vertical = 0 mouse_filter = 2 -theme_override_constants/margin_top = 4 -theme_override_constants/margin_right = 12 +theme_override_constants/margin_top = 36 +theme_override_constants/margin_right = 10 [node name="DebugContainer" type="VBoxContainer" parent="ViewportPanel/DebugMargins"] unique_name_in_owner = true layout_mode = 2 mouse_filter = 2 +theme_override_constants/separation = -18 [node name="DebugLabel" type="Label" parent="ViewportPanel/DebugMargins/DebugContainer"] layout_mode = 2 @@ -202,12 +197,10 @@ theme_override_constants/outline_size = 4 theme_override_font_sizes/font_size = 14 horizontal_alignment = 2 -[connection signal="pressed" from="PanelContainer/HBoxContainer/LeftMenu/MoreOptions" to="." method="_on_more_options_pressed"] -[connection signal="pressed" from="PanelContainer/HBoxContainer/LeftMenu/Settings" to="." method="_on_settings_pressed"] -[connection signal="pressed" from="PanelContainer/HBoxContainer/LeftMenu/Reference" to="." method="_on_reference_pressed"] -[connection signal="pressed" from="PanelContainer/HBoxContainer/LeftMenu/Visuals" to="." method="_on_visuals_button_pressed"] -[connection signal="toggled" from="PanelContainer/HBoxContainer/LeftMenu/Snapping/SnapButton" to="." method="_on_snap_button_toggled"] -[connection signal="value_changed" from="PanelContainer/HBoxContainer/LeftMenu/Snapping/SnapNumberEdit" to="." method="_on_snap_number_edit_value_changed"] -[connection signal="zoom_changed" from="PanelContainer/HBoxContainer/ZoomMenu" to="ViewportPanel/ViewportContainer/Viewport" method="_on_zoom_changed"] -[connection signal="zoom_reset_pressed" from="PanelContainer/HBoxContainer/ZoomMenu" to="ViewportPanel/ViewportContainer/Viewport" method="center_frame"] -[connection signal="size_changed" from="ViewportPanel/ViewportContainer/Viewport" to="ViewportPanel/ViewportContainer/Viewport" method="_on_size_changed"] +[connection signal="pressed" from="ViewportPanel/VBoxContainer/MarginContainer/ViewportOptions/LeftMenu/Visuals" to="." method="_on_visuals_button_pressed"] +[connection signal="pressed" from="ViewportPanel/VBoxContainer/MarginContainer/ViewportOptions/LeftMenu/Reference" to="." method="_on_reference_pressed"] +[connection signal="toggled" from="ViewportPanel/VBoxContainer/MarginContainer/ViewportOptions/LeftMenu/Snapping/SnapButton" to="." method="_on_snap_button_toggled"] +[connection signal="value_changed" from="ViewportPanel/VBoxContainer/MarginContainer/ViewportOptions/LeftMenu/Snapping/SnapNumberEdit" to="." method="_on_snap_number_edit_value_changed"] +[connection signal="zoom_changed" from="ViewportPanel/VBoxContainer/MarginContainer/ViewportOptions/ZoomMenu" to="ViewportPanel/VBoxContainer/ViewportContainer/Viewport" method="_on_zoom_changed"] +[connection signal="zoom_reset_pressed" from="ViewportPanel/VBoxContainer/MarginContainer/ViewportOptions/ZoomMenu" to="ViewportPanel/VBoxContainer/ViewportContainer/Viewport" method="center_frame"] +[connection signal="size_changed" from="ViewportPanel/VBoxContainer/ViewportContainer/Viewport" to="ViewportPanel/VBoxContainer/ViewportContainer/Viewport" method="_on_size_changed"] diff --git a/src/ui_parts/display_texture.gd b/src/ui_parts/display_texture.gd index 0fba42f2..f13b9537 100644 --- a/src/ui_parts/display_texture.gd +++ b/src/ui_parts/display_texture.gd @@ -10,14 +10,14 @@ var rasterized := false: set(new_value): if new_value != rasterized: rasterized = new_value - if Indications.zoom != 1.0: + if State.zoom != 1.0: queue_update() var _update_pending := false func _ready() -> void: - SVG.changed.connect(queue_update) - Indications.zoom_changed.connect(queue_update) + State.svg_changed.connect(queue_update) + State.zoom_changed.connect(queue_update) queue_update() @@ -31,7 +31,7 @@ func _update() -> void: _update_pending = false - var image_zoom := 1.0 if rasterized and Indications.zoom > 1.0 else Indications.zoom + var image_zoom := 1.0 if rasterized and State.zoom > 1.0 else State.zoom var pixel_size := 1 / image_zoom # Translate to canvas coords. @@ -40,12 +40,12 @@ func _update() -> void: display_rect.position.x = maxf(display_rect.position.x, 0.0) display_rect.position.y = maxf(display_rect.position.y, 0.0) display_rect.size = display_rect.size.snapped(Vector2(pixel_size, pixel_size)) - display_rect.end.x = minf(display_rect.end.x, ceili(SVG.root_element.width)) - display_rect.end.y = minf(display_rect.end.y, ceili(SVG.root_element.height)) + display_rect.end.x = minf(display_rect.end.x, ceili(State.root_element.width)) + display_rect.end.y = minf(display_rect.end.y, ceili(State.root_element.height)) - var svg_text := SVGParser.root_cutout_to_text(SVG.root_element, display_rect.size.x, - display_rect.size.y, Rect2(SVG.root_element.world_to_canvas(display_rect.position), - display_rect.size / SVG.root_element.canvas_transform.get_scale())) + var svg_text := SVGParser.root_cutout_to_text(State.root_element, display_rect.size.x, + display_rect.size.y, Rect2(State.root_element.world_to_canvas(display_rect.position), + display_rect.size / State.root_element.canvas_transform.get_scale())) var img := Image.new() var err := img.load_svg_from_string(svg_text, image_zoom) if err == OK: diff --git a/src/ui_parts/donate_menu.gd b/src/ui_parts/donate_menu.gd index 2082aafa..94c020be 100644 --- a/src/ui_parts/donate_menu.gd +++ b/src/ui_parts/donate_menu.gd @@ -4,6 +4,7 @@ extends PanelContainer @onready var close_button: Button = $VBoxContainer/CloseButton func _ready() -> void: + close_button.text = Translator.translate("Cancel") close_button.pressed.connect(queue_free) reset_clarifications() diff --git a/src/ui_parts/donate_menu.tscn b/src/ui_parts/donate_menu.tscn index a932dad3..76a0f1f5 100644 --- a/src/ui_parts/donate_menu.tscn +++ b/src/ui_parts/donate_menu.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=5 format=3 uid="uid://dhydn476cr0pv"] -[ext_resource type="Script" path="res://src/ui_parts/donate_menu.gd" id="1_yjfkr"] +[ext_resource type="Script" uid="uid://pj5ax4gste0l" path="res://src/ui_parts/donate_menu.gd" id="1_yjfkr"] [ext_resource type="Texture2D" uid="uid://ccc0q21h8owg1" path="res://assets/icons/foreign_logos/GithubLogo.svg" id="2_3gj3j"] [ext_resource type="Texture2D" uid="uid://dcn1rq4e0p2jt" path="res://assets/icons/foreign_logos/KoFiLogo.svg" id="3_5q1ti"] [ext_resource type="Texture2D" uid="uid://dq1muwo84c6yv" path="res://assets/icons/foreign_logos/PatreonLogo.svg" id="4_0irlu"] @@ -25,7 +25,7 @@ script = ExtResource("1_yjfkr") layout_mode = 2 theme_override_constants/separation = 8 -[node name="Label" type="Label" parent="VBoxContainer"] +[node name="TitleLabel" type="Label" parent="VBoxContainer"] layout_mode = 2 text = "Links to donation platforms" horizontal_alignment = 1 @@ -88,7 +88,6 @@ size_flags_horizontal = 4 size_flags_vertical = 8 focus_mode = 0 mouse_default_cursor_shape = 2 -text = "Close" [connection signal="mouse_exited" from="VBoxContainer/VBoxContainer/MarginContainer" to="." method="_on_link_mouse_exited"] [connection signal="mouse_entered" from="VBoxContainer/VBoxContainer/MarginContainer/HBoxContainer/GithubLink" to="." method="_on_github_link_mouse_entered"] diff --git a/src/ui_parts/editor_scene.gd b/src/ui_parts/editor_scene.gd index 2da627b2..5796e54f 100644 --- a/src/ui_parts/editor_scene.gd +++ b/src/ui_parts/editor_scene.gd @@ -1,6 +1,6 @@ extends HBoxContainer -const MacMenu = preload("res://src/ui_parts/global_menu.tscn") +const MacMenu = preload("res://src/ui_parts/mac_menu.tscn") @onready var panel_container: PanelContainer = $PanelContainer diff --git a/src/ui_parts/editor_scene.tscn b/src/ui_parts/editor_scene.tscn index 1cc3f544..784829be 100644 --- a/src/ui_parts/editor_scene.tscn +++ b/src/ui_parts/editor_scene.tscn @@ -1,12 +1,13 @@ -[gd_scene load_steps=6 format=3 uid="uid://ce6j54x27pom"] +[gd_scene load_steps=7 format=3 uid="uid://ce6j54x27pom"] [ext_resource type="Script" uid="uid://b14gd6s3wl4us" path="res://src/ui_parts/editor_scene.gd" id="1_78d5d"] [ext_resource type="Texture2D" uid="uid://co75w07yqmcro" path="res://assets/icons/theme/SplitGrabber2.svg" id="2_852uu"] -[ext_resource type="PackedScene" uid="uid://cr1fdlmbknnko" path="res://src/ui_parts/code_editor.tscn" id="3_5ris2"] +[ext_resource type="PackedScene" uid="uid://cr1fdlmbknnko" path="res://src/ui_widgets/code_editor.tscn" id="3_5ris2"] +[ext_resource type="PackedScene" uid="uid://cxmrx6t4jkhyj" path="res://src/ui_parts/global_actions.tscn" id="3_852uu"] [ext_resource type="PackedScene" uid="uid://ccynisiuyn5qn" path="res://src/ui_parts/inspector.tscn" id="4_podmt"] [ext_resource type="PackedScene" uid="uid://bvrncl7e6yn5b" path="res://src/ui_parts/display.tscn" id="5_4vrq4"] -[node name="MainScene" type="HBoxContainer"] +[node name="EditorScene" type="HBoxContainer"] anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 @@ -33,18 +34,29 @@ theme_override_constants/margin_top = 6 theme_override_constants/margin_right = 0 theme_override_constants/margin_bottom = 6 -[node name="MainContainer" type="VSplitContainer" parent="PanelContainer/HSplitContainer/MarginContainer"] +[node name="VBoxContainer" type="VBoxContainer" parent="PanelContainer/HSplitContainer/MarginContainer"] layout_mode = 2 + +[node name="GlobalActions" parent="PanelContainer/HSplitContainer/MarginContainer/VBoxContainer" instance=ExtResource("3_852uu")] +layout_mode = 2 + +[node name="MainContainer" type="VSplitContainer" parent="PanelContainer/HSplitContainer/MarginContainer/VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 theme_override_constants/separation = 10 split_offset = -400 -[node name="CodeEditor" parent="PanelContainer/HSplitContainer/MarginContainer/MainContainer" instance=ExtResource("3_5ris2")] +[node name="CodeEditor" parent="PanelContainer/HSplitContainer/MarginContainer/VBoxContainer/MainContainer" instance=ExtResource("3_5ris2")] layout_mode = 2 -[node name="Inspector" parent="PanelContainer/HSplitContainer/MarginContainer/MainContainer" instance=ExtResource("4_podmt")] +[node name="Inspector" parent="PanelContainer/HSplitContainer/MarginContainer/VBoxContainer/MainContainer" instance=ExtResource("4_podmt")] layout_mode = 2 -[node name="Display" parent="PanelContainer/HSplitContainer" instance=ExtResource("5_4vrq4")] +[node name="MarginContainer2" type="MarginContainer" parent="PanelContainer/HSplitContainer"] +layout_mode = 2 +theme_override_constants/margin_top = 6 + +[node name="Display" parent="PanelContainer/HSplitContainer/MarginContainer2" instance=ExtResource("5_4vrq4")] unique_name_in_owner = true layout_mode = 2 size_flags_horizontal = 3 diff --git a/src/ui_parts/element_container.gd b/src/ui_parts/element_container.gd index b56af60e..86282563 100644 --- a/src/ui_parts/element_container.gd +++ b/src/ui_parts/element_container.gd @@ -9,10 +9,10 @@ const autoscroll_speed = 1500.0 @onready var covering_rect: Control = $MoveToOverlay func _ready(): - Indications.requested_scroll_to_element_editor.connect(scroll_to_view_element_editor) + State.requested_scroll_to_element_editor.connect(scroll_to_view_element_editor) func _process(delta: float) -> void: - if Indications.proposed_drop_xid.is_empty(): + if State.proposed_drop_xid.is_empty(): return # Autoscroll when the dragged object is near the edge of the screen. @@ -39,10 +39,10 @@ func update_proposed_xid() -> void: var prev_xid := PackedInt32Array([-1]) var prev_y := -INF # Keep track of the first element editor whose end is after y_pos. - var next_xid := PackedInt32Array([SVG.root_element.get_child_count()]) + var next_xid := PackedInt32Array([State.root_element.get_child_count()]) var next_y := INF - for xnode in SVG.root_element.get_all_xnode_descendants(): + for xnode in State.root_element.get_all_xnode_descendants(): var xnode_rect := get_xnode_editor_rect(xnode.xid) var xnode_start := xnode_rect.position.y var xnode_end := xnode_rect.end.y @@ -60,17 +60,17 @@ func update_proposed_xid() -> void: in_top_buffer = true # Set the proposed drop XID based on what the previous and next element editors are. if in_top_buffer: - Indications.set_proposed_drop_xid(prev_xid) + State.set_proposed_drop_xid(prev_xid) elif in_bottom_buffer: - Indications.set_proposed_drop_xid(XIDUtils.get_parent_xid(next_xid) +\ + State.set_proposed_drop_xid(XIDUtils.get_parent_xid(next_xid) +\ PackedInt32Array([next_xid[-1] + 1])) - elif next_xid[0] >= SVG.root_element.get_child_count(): - Indications.set_proposed_drop_xid(next_xid) + elif next_xid[0] >= State.root_element.get_child_count(): + State.set_proposed_drop_xid(next_xid) elif XIDUtils.is_parent_or_self(prev_xid, next_xid): for i in range(prev_xid.size(), next_xid.size()): if next_xid[i] != 0: return - Indications.set_proposed_drop_xid(prev_xid + PackedInt32Array([0])) + State.set_proposed_drop_xid(prev_xid + PackedInt32Array([0])) var dragged_xnode_editors: Array[Control] = [] @@ -79,7 +79,7 @@ func _notification(what: int) -> void: if is_inside_tree() and HandlerGUI.menu_stack.is_empty(): if what == NOTIFICATION_DRAG_BEGIN: covering_rect.show() - for selected_xid in Indications.selected_xids: + for selected_xid in State.selected_xids: var xnode_editor := get_xnode_editor(selected_xid) dragged_xnode_editors.append(xnode_editor) xnode_editor.modulate.a = 0.55 @@ -89,18 +89,18 @@ func _notification(what: int) -> void: for xnode_editor in dragged_xnode_editors: xnode_editor.modulate.a = 1.0 dragged_xnode_editors.clear() - Indications.clear_proposed_drop_xid() + State.clear_proposed_drop_xid() func _gui_input(event: InputEvent) -> void: if event is InputEventMouseButton: if event.button_index == MOUSE_BUTTON_LEFT and event.is_pressed() and\ not (event.ctrl_pressed or event.shift_pressed): - Indications.clear_all_selections() + State.clear_all_selections() elif event.button_index == MOUSE_BUTTON_RIGHT and event.is_pressed(): # Find where the new element should be added. var location := 0 var y_pos := get_local_mouse_position().y + scroll_container.scroll_vertical - while location < SVG.root_element.get_child_count() and\ + while location < State.root_element.get_child_count() and\ get_xnode_editor_rect(PackedInt32Array([location])).end.y < y_pos: location += 1 # Create the context popup. @@ -122,9 +122,9 @@ func _gui_input(event: InputEvent) -> void: HandlerGUI.popup_under_pos(add_popup, vp.get_mouse_position(), vp) func add_element(element_name: String, element_idx: int) -> void: - SVG.root_element.add_xnode(DB.element_with_setup(element_name), + State.root_element.add_xnode(DB.element_with_setup(element_name), PackedInt32Array([element_idx])) - SVG.queue_save() + State.queue_svg_save() func get_xnode_editor(xid: PackedInt32Array) -> Control: if xid.is_empty(): diff --git a/src/ui_parts/export_menu.gd b/src/ui_parts/export_menu.gd index 945f2511..7e7712aa 100644 --- a/src/ui_parts/export_menu.gd +++ b/src/ui_parts/export_menu.gd @@ -39,13 +39,13 @@ func _ready() -> void: lossless_checkbox.toggled.connect(_on_lossless_check_box_toggled) format_dropdown.value_changed.connect(_on_dropdown_value_changed) - dimensions = SVG.root_element.get_size() + dimensions = State.root_element.get_size() var bigger_dimension := maxf(dimensions.x, dimensions.y) scale_edit.min_value = 1 / minf(dimensions.x, dimensions.y) scale_edit.max_value = 16384 / bigger_dimension # Update dimensions label. - dimensions = SVG.root_element.get_size() + dimensions = State.root_element.get_size() dimensions_label.text = Translator.translate("Dimensions") + ": " +\ get_dimensions_text(dimensions) update() @@ -57,12 +57,12 @@ func _ready() -> void: "Preview image size is limited to {dimensions}").format( {"dimensions": get_dimensions_text(dimensions * scaling_factor, true)}) - if Utils.get_file_name(Configs.savedata.current_file_path).is_empty(): + if Configs.savedata.get_active_tab().svg_file_path.is_empty(): file_title.add_theme_color_override("font_color", ThemeUtils.common_subtle_text_color) - file_title.text = Translator.translate("Unnamed") + file_title.text = Configs.savedata.get_active_tab().get_presented_name() final_size_label.text = Translator.translate("Size") + ": " +\ - String.humanize_size(SVG.get_export_text().length()) + String.humanize_size(State.get_export_text().length()) %TitleLabel.text = Translator.translate("Export Configuration") %FormatHBox/Label.text = Translator.translate("Format") + ":" %LosslessCheckBox.text = Translator.translate("Lossless") @@ -146,13 +146,13 @@ func update() -> void: final_size_label.visible = (export_data.format == "svg") - var file_path := Utils.get_file_name(Configs.savedata.current_file_path) - if not file_path.is_empty(): - file_title.text = file_path + "." + export_data.format + var file_name := Utils.get_file_name(Configs.savedata.get_active_tab().svg_file_path) + if not file_name.is_empty(): + file_title.text = file_name + "." + export_data.format # Display the texture and the warning for inaccurate previews. if export_data.format == "svg": - texture_preview.setup_svg(SVG.get_export_text(), dimensions) + texture_preview.setup_svg(State.get_export_text(), dimensions) else: texture_preview.setup_image(export_data) # Sync width, height, and scale without affecting the upscale amount. diff --git a/src/ui_parts/global_actions.gd b/src/ui_parts/global_actions.gd new file mode 100644 index 00000000..d5e4f1b5 --- /dev/null +++ b/src/ui_parts/global_actions.gd @@ -0,0 +1,60 @@ +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 + +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() + import_button.pressed.connect(ShortcutUtils.fn("import")) + export_button.pressed.connect(ShortcutUtils.fn("export")) + more_options.pressed.connect(_on_more_options_pressed) + settings_button.pressed.connect(ShortcutUtils.fn_call.bind("open_settings")) + + +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")) + + 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.png"), + "about_info") + about_btn.expand_icon = true + buttons_arr.append(about_btn) + buttons_arr.append(ContextPopup.create_button(Translator.translate( + "Donate…"), ShortcutUtils.fn("about_donate"), false, + load("res://assets/icons/Heart.svg"), "about_donate")) + buttons_arr.append(ContextPopup.create_button(Translator.translate( + "GodSVG repository"), ShortcutUtils.fn("about_repo"), false, + load("res://assets/icons/Link.svg"), "about_repo")) + buttons_arr.append(ContextPopup.create_button(Translator.translate( + "GodSVG website"), ShortcutUtils.fn("about_website"), false, + load("res://assets/icons/Link.svg"), "about_website")) + var separator_indices := PackedInt32Array([1, 3]) + if can_show_savedata_folder: + separator_indices = PackedInt32Array([2, 4]) + + var more_popup := ContextPopup.new() + more_popup.setup(buttons_arr, true, -1, -1, separator_indices) + HandlerGUI.popup_under_rect_center(more_popup, more_options.get_global_rect(), + get_viewport()) + + +func open_savedata_folder() -> void: + OS.shell_show_in_file_manager(ProjectSettings.globalize_path("user://")) diff --git a/src/ui_parts/global_actions.gd.uid b/src/ui_parts/global_actions.gd.uid new file mode 100644 index 00000000..bf601841 --- /dev/null +++ b/src/ui_parts/global_actions.gd.uid @@ -0,0 +1 @@ +uid://cgbgw4ok5jxk5 diff --git a/src/ui_parts/global_actions.tscn b/src/ui_parts/global_actions.tscn new file mode 100644 index 00000000..9a680e63 --- /dev/null +++ b/src/ui_parts/global_actions.tscn @@ -0,0 +1,51 @@ +[gd_scene load_steps=6 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"] + +[node name="GlobalActions" type="HBoxContainer"] +offset_right = 52.0 +offset_bottom = 24.0 +script = ExtResource("1_x4rqo") + +[node name="LeftSide" type="HBoxContainer" parent="."] +layout_mode = 2 +size_flags_horizontal = 2 + +[node name="MoreOptions" type="Button" parent="LeftSide"] +layout_mode = 2 +size_flags_horizontal = 2 +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 +size_flags_horizontal = 2 +focus_mode = 0 +mouse_default_cursor_shape = 2 +theme_type_variation = &"IconButton" +icon = ExtResource("3_xl5uh") +icon_alignment = 1 + +[node name="RightSide" type="HBoxContainer" parent="."] +layout_mode = 2 + +[node name="ImportButton" type="Button" parent="RightSide"] +layout_mode = 2 +focus_mode = 0 +mouse_default_cursor_shape = 2 +theme_type_variation = &"IconButton" +icon = ExtResource("2_giwu1") + +[node name="ExportButton" type="Button" parent="RightSide"] +layout_mode = 2 +focus_mode = 0 +mouse_default_cursor_shape = 2 +theme_type_variation = &"IconButton" +icon = ExtResource("3_4ckhj") diff --git a/src/ui_parts/global_menu.tscn b/src/ui_parts/global_menu.tscn deleted file mode 100644 index a2a34843..00000000 --- a/src/ui_parts/global_menu.tscn +++ /dev/null @@ -1,6 +0,0 @@ -[gd_scene load_steps=2 format=3 uid="uid://dqrvfi76ak512"] - -[ext_resource type="Script" path="res://src/ui_parts/global_menu.gd" id="1_rns86"] - -[node name="GlobalMenu" type="Node"] -script = ExtResource("1_rns86") diff --git a/src/ui_parts/good_file_dialog.gd b/src/ui_parts/good_file_dialog.gd index 94d07430..e203e28f 100644 --- a/src/ui_parts/good_file_dialog.gd +++ b/src/ui_parts/good_file_dialog.gd @@ -1,9 +1,9 @@ # A fallback file dialog, always used if the native file dialog is not available. extends PanelContainer -const ChooseNameDialog = preload("res://src/ui_parts/choose_name_dialog.tscn") -const ConfirmDialog = preload("res://src/ui_parts/confirm_dialog.tscn") -const AlertDialog = preload("res://src/ui_parts/alert_dialog.tscn") +const ChooseNameDialog = preload("res://src/ui_widgets/choose_name_dialog.tscn") +const ConfirmDialog = preload("res://src/ui_widgets/confirm_dialog.tscn") +const AlertDialog = preload("res://src/ui_widgets/alert_dialog.tscn") signal file_selected(path: String) @@ -245,8 +245,9 @@ func _setup_file_images() -> void: if !is_instance_valid(img) or img.is_empty(): file_list.set_item_icon(item_idx, broken_file_icon) else: - img.load_svg_from_buffer(svg_buffer, - item_height / maxf(img.get_width(), img.get_height())) + var factor := item_height / maxf(img.get_width(), img.get_height()) + if not is_equal_approx(factor, 1.0): + img.load_svg_from_buffer(svg_buffer, factor) file_list.set_item_icon(item_idx, ImageTexture.create_from_image(img)) _: var img := Image.load_from_file(current_dir.path_join(file)) @@ -441,11 +442,11 @@ func get_drive_icon(path: String) -> Texture2D: else: return folder_icon -func _input(event: InputEvent) -> void: +func _unhandled_input(event: InputEvent) -> void: if ShortcutUtils.is_action_pressed(event, "find"): search_button.button_pressed = true accept_event() - elif Input.is_action_pressed("ui_accept"): + elif event.is_action_pressed("ui_accept"): var selected_item_indices := file_list.get_selected_items() if not selected_item_indices.is_empty(): call_activation_callback(file_list.get_item_metadata(selected_item_indices[0])) diff --git a/src/ui_parts/handles_manager.gd b/src/ui_parts/handles_manager.gd index b48ff7c3..f2fcc0d1 100644 --- a/src/ui_parts/handles_manager.gd +++ b/src/ui_parts/handles_manager.gd @@ -86,13 +86,13 @@ func _ready() -> void: c.material = stroke_material add_child(c, false, InternalMode.INTERNAL_MODE_BACK) - SVG.any_attribute_changed.connect(sync_handles) - SVG.xnode_layout_changed.connect(queue_update_handles) - SVG.changed_unknown.connect(queue_update_handles) - Indications.selection_changed.connect(queue_redraw) - Indications.hover_changed.connect(queue_redraw) - Indications.zoom_changed.connect(queue_redraw) - Indications.handle_added.connect(_on_handle_added) + State.any_attribute_changed.connect(sync_handles) + State.xnode_layout_changed.connect(queue_update_handles) + State.svg_unknown_change.connect(queue_update_handles) + State.selection_changed.connect(queue_redraw) + State.hover_changed.connect(queue_redraw) + State.zoom_changed.connect(queue_redraw) + State.handle_added.connect(_on_handle_added) queue_update_handles() @@ -106,7 +106,7 @@ func update_handles() -> void: _handles_update_pending = false handles.clear() - for element in SVG.root_element.get_all_element_descendants(): + for element in State.root_element.get_all_element_descendants(): match element.name: "circle": handles.append(XYHandle.new(element, "cx", "cy")) @@ -131,7 +131,7 @@ func update_handles() -> void: queue_redraw() func sync_handles(xid: PackedInt32Array) -> void: - var element := SVG.root_element.get_xnode(xid) + var element := State.root_element.get_xnode(xid) if not (element is ElementPath or element is ElementPolygon or element is ElementPolyline): queue_redraw() return @@ -183,10 +183,10 @@ func _draw() -> void: var hovered_multiline := PackedVector2Array() var hovered_selected_multiline := PackedVector2Array() - for element: Element in SVG.root_element.get_all_element_descendants(): + for element: Element in State.root_element.get_all_element_descendants(): # Determine if the element is hovered/selected or has a hovered/selected parent. - var element_hovered := Indications.is_hovered(element.xid, -1, true) - var element_selected := Indications.is_selected(element.xid, -1, true) + var element_hovered := State.is_hovered(element.xid, -1, true) + var element_selected := State.is_selected(element.xid, -1, true) match element.name: "circle": @@ -334,10 +334,10 @@ func _draw() -> void: var current_mode := Utils.InteractionType.NONE for idx in range(1, point_list.size()): current_mode = Utils.InteractionType.NONE - if Indications.is_hovered(element.xid, idx, true): + if State.is_hovered(element.xid, idx, true): @warning_ignore("int_as_enum_without_cast") current_mode += Utils.InteractionType.HOVERED - if Indications.is_selected(element.xid, idx, true): + if State.is_selected(element.xid, idx, true): @warning_ignore("int_as_enum_without_cast") current_mode += Utils.InteractionType.SELECTED @@ -355,10 +355,10 @@ func _draw() -> void: if element.name == "polygon" and point_list.size() > 2: current_mode = Utils.InteractionType.NONE - if Indications.is_hovered(element.xid, 0, true): + if State.is_hovered(element.xid, 0, true): @warning_ignore("int_as_enum_without_cast") current_mode += Utils.InteractionType.HOVERED - if Indications.is_selected(element.xid, 0, true): + if State.is_selected(element.xid, 0, true): @warning_ignore("int_as_enum_without_cast") current_mode += Utils.InteractionType.SELECTED @@ -390,10 +390,10 @@ func _draw() -> void: var relative := cmd.relative current_mode = Utils.InteractionType.NONE - if Indications.is_hovered(element.xid, cmd_idx, true): + if State.is_hovered(element.xid, cmd_idx, true): @warning_ignore("int_as_enum_without_cast") current_mode += Utils.InteractionType.HOVERED - if Indications.is_selected(element.xid, cmd_idx, true): + if State.is_selected(element.xid, cmd_idx, true): @warning_ignore("int_as_enum_without_cast") current_mode += Utils.InteractionType.SELECTED @@ -580,9 +580,9 @@ func _draw() -> void: hovered_selected_polylines.append(points) hovered_selected_multiline += tangent_points - draw_set_transform_matrix(SVG.root_element.canvas_transform) + draw_set_transform_matrix(State.root_element.canvas_transform) RenderingServer.canvas_item_set_transform(surface, Transform2D(0.0, - Vector2(1, 1) / Indications.zoom, 0.0, Vector2.ZERO)) + Vector2(1, 1) / State.zoom, 0.0, Vector2.ZERO)) # First gather all handles in 4 categories, to then draw them in the right order. var normal_handles: Array[Handle] = [] @@ -595,8 +595,8 @@ func _draw() -> void: inner_idx = handle.command_index elif handle is PolyHandle: inner_idx = handle.point_index - var is_hovered := Indications.is_hovered(handle.element.xid, inner_idx, true) - var is_selected := Indications.is_selected(handle.element.xid, inner_idx, true) + var is_hovered := State.is_hovered(handle.element.xid, inner_idx, true) + var is_selected := State.is_selected(handle.element.xid, inner_idx, true) if is_hovered and is_selected: hovered_selected_handles.append(handle) @@ -620,15 +620,15 @@ func _draw() -> void: hovered_selected_multiline, hovered_selected_handles, hovered_selected_handle_textures) - for xid in Indications.selected_xids: - var xnode := SVG.root_element.get_xnode(xid) + for xid in State.selected_xids: + var xnode := State.root_element.get_xnode(xid) if xnode.is_element() and DB.is_attribute_recognized(xnode.name, "transform"): var bounding_box: Rect2 = xnode.get_bounding_box() if bounding_box.has_area(): RenderingServer.canvas_item_add_set_transform(selections_surface, - xnode.get_transform() * SVG.root_element.canvas_transform) + xnode.get_transform() * State.root_element.canvas_transform) RenderingServer.canvas_item_add_rect(selections_surface, - bounding_box.grow(4.0 / Indications.zoom), Color.WHITE) + bounding_box.grow(4.0 / State.zoom), Color.WHITE) func draw_objects_of_type(color: Color, polylines: Array[PackedVector2Array], multiline: PackedVector2Array, handles_array: Array[Handle], @@ -638,12 +638,12 @@ handle_texture_dictionary: Dictionary[Handle.Display, Texture2D]) -> void: color_array.resize(polyline.size()) color_array.fill(color) for idx in polyline.size(): - polyline[idx] = SVG.root_element.canvas_to_world(polyline[idx]) * Indications.zoom + polyline[idx] = State.root_element.canvas_to_world(polyline[idx]) * State.zoom RenderingServer.canvas_item_add_polyline(surface, polyline, color_array, CONTOUR_WIDTH, true) if not multiline.is_empty(): for idx in multiline.size(): - multiline[idx] = SVG.root_element.canvas_to_world(multiline[idx]) * Indications.zoom + multiline[idx] = State.root_element.canvas_to_world(multiline[idx]) * State.zoom var color_array := PackedColorArray() color_array.resize(int(multiline.size() / 2.0)) color_array.fill(Color(color, TANGENT_ALPHA)) @@ -651,8 +651,8 @@ handle_texture_dictionary: Dictionary[Handle.Display, Texture2D]) -> void: color_array, TANGENT_WIDTH, true) for handle in handles_array: var texture := handle_texture_dictionary[handle.display_mode] - texture.draw(surface, SVG.root_element.canvas_to_world( - handle.transform * handle.pos) * Indications.zoom - texture.get_size() / 2) + texture.draw(surface, State.root_element.canvas_to_world( + handle.transform * handle.pos) * State.zoom - texture.get_size() / 2) var dragged_handle: Handle = null @@ -663,8 +663,7 @@ var should_deselect_all := false func _unhandled_input(event: InputEvent) -> void: if not visible: hovered_handle = null - Indications.clear_hovered() - Indications.clear_inner_hovered() + State.clear_all_hovered() return # Set the nearest handle as hovered, if any handles are within range. @@ -672,20 +671,19 @@ func _unhandled_input(event: InputEvent) -> void: event.button_mask == 0) or (event is InputEventMouseButton and\ (event.button_index in [MOUSE_BUTTON_LEFT, MOUSE_BUTTON_RIGHT, MOUSE_BUTTON_WHEEL_DOWN, MOUSE_BUTTON_WHEEL_UP, MOUSE_BUTTON_WHEEL_LEFT, MOUSE_BUTTON_WHEEL_RIGHT])): - var nearest_handle := find_nearest_handle(event.position / Indications.zoom +\ + var nearest_handle := find_nearest_handle(event.position / State.zoom +\ get_parent().view.position) if is_instance_valid(nearest_handle): hovered_handle = nearest_handle if hovered_handle is PathHandle: - Indications.set_hovered(hovered_handle.element.xid, hovered_handle.command_index) + State.set_hovered(hovered_handle.element.xid, hovered_handle.command_index) elif hovered_handle is PolyHandle: - Indications.set_hovered(hovered_handle.element.xid, hovered_handle.point_index) + State.set_hovered(hovered_handle.element.xid, hovered_handle.point_index) else: - Indications.set_hovered(hovered_handle.element.xid) + State.set_hovered(hovered_handle.element.xid) else: hovered_handle = null - Indications.clear_hovered() - Indications.clear_inner_hovered() + State.clear_all_hovered() if event is InputEventMouseMotion: # Allow moving view while dragging handle. @@ -698,7 +696,7 @@ func _unhandled_input(event: InputEvent) -> void: var event_pos := get_event_pos(event) var new_pos := Utils64Bit.transform_vector_mult( Utils64Bit.get_transform_affine_inverse(dragged_handle.precise_transform), - SVG.root_element.world_to_canvas_64_bit(event_pos)) + State.root_element.world_to_canvas_64_bit(event_pos)) dragged_handle.set_pos(new_pos) was_handle_moved = true accept_event() @@ -721,37 +719,37 @@ func _unhandled_input(event: InputEvent) -> void: if dragged_handle is PathHandle: var subpath_range: Vector2i =\ dragged_handle.element.get_attribute("d").get_subpath(inner_idx) - Indications.normal_select(dragged_xid, subpath_range.x) - Indications.shift_select(dragged_xid, subpath_range.y) + State.normal_select(dragged_xid, subpath_range.x) + State.shift_select(dragged_xid, subpath_range.y) elif dragged_handle is PolyHandle: - Indications.normal_select(dragged_xid, 0) - Indications.shift_select(dragged_xid, + State.normal_select(dragged_xid, 0) + State.shift_select(dragged_xid, dragged_handle.element.get_attribute("points").get_list_size() / 2) elif event.is_command_or_control_pressed(): - Indications.ctrl_select(dragged_xid, inner_idx) + State.ctrl_select(dragged_xid, inner_idx) elif event.shift_pressed: - Indications.shift_select(dragged_xid, inner_idx) + State.shift_select(dragged_xid, inner_idx) else: - Indications.normal_select(dragged_xid, inner_idx) + State.normal_select(dragged_xid, inner_idx) elif is_instance_valid(dragged_handle) and event.is_released(): if was_handle_moved: var new_pos := Utils64Bit.transform_vector_mult( Utils64Bit.get_transform_affine_inverse(dragged_handle.precise_transform), - SVG.root_element.world_to_canvas_64_bit(event_pos)) + State.root_element.world_to_canvas_64_bit(event_pos)) dragged_handle.set_pos(new_pos) - SVG.queue_save() + State.queue_svg_save() was_handle_moved = false dragged_handle = null elif !is_instance_valid(hovered_handle) and event.is_pressed(): should_deselect_all = true elif !is_instance_valid(hovered_handle) and event.is_released() and should_deselect_all: dragged_handle = null - Indications.clear_all_selections() + State.clear_all_selections() elif event.button_index == MOUSE_BUTTON_RIGHT and event.is_pressed(): var vp := get_viewport() var popup_pos := vp.get_mouse_position() if !is_instance_valid(hovered_handle): - Indications.clear_all_selections() + State.clear_all_selections() var vec2_pos := Vector2(event_pos[0], event_pos[1]) HandlerGUI.popup_under_pos(create_element_context(vec2_pos), popup_pos, vp) else: @@ -762,22 +760,22 @@ func _unhandled_input(event: InputEvent) -> void: if hovered_handle is PolyHandle: inner_idx = hovered_handle.point_index - if (Indications.semi_selected_xid != hovered_xid or\ - not inner_idx in Indications.inner_selections) and\ - not hovered_xid in Indications.selected_xids: - Indications.normal_select(hovered_xid, inner_idx) - HandlerGUI.popup_under_pos(Indications.get_selection_context( + if (State.semi_selected_xid != hovered_xid or\ + not inner_idx in State.inner_selections) and\ + not hovered_xid in State.selected_xids: + State.normal_select(hovered_xid, inner_idx) + HandlerGUI.popup_under_pos(State.get_selection_context( HandlerGUI.popup_under_pos.bind(popup_pos, vp), - Indications.Context.VIEWPORT), popup_pos, vp) + State.Context.VIEWPORT), popup_pos, vp) func find_nearest_handle(event_pos: Vector2) -> Handle: var nearest_handle: Handle = null var nearest_dist_squared := DEFAULT_GRAB_DISTANCE_SQUARED *\ (Configs.savedata.handle_size * Configs.savedata.handle_size) /\ - (Indications.zoom * Indications.zoom) + (State.zoom * State.zoom) for handle in handles: var dist_to_handle_squared := event_pos.distance_squared_to( - SVG.root_element.canvas_to_world(handle.transform * handle.pos)) + State.root_element.canvas_to_world(handle.transform * handle.pos)) if dist_to_handle_squared < nearest_dist_squared: nearest_dist_squared = dist_to_handle_squared nearest_handle = handle @@ -785,10 +783,10 @@ func find_nearest_handle(event_pos: Vector2) -> Handle: # Two 64-bit coordinates instead of a Vector2. func get_event_pos(event: InputEvent) -> PackedFloat64Array: - return apply_snap(event.position / Indications.zoom + get_parent().view.position) + return apply_snap(event.position / State.zoom + get_parent().view.position) func apply_snap(pos: Vector2) -> PackedFloat64Array: - var precision_snap := 0.1 ** maxi(ceili(-log(1.0 / Indications.zoom) / log(10)), 0) + var precision_snap := 0.1 ** maxi(ceili(-log(1.0 / State.zoom) / log(10)), 0) var configured_snap := absf(Configs.savedata.snap) var snap_size: float # To be used for the snap. @@ -805,28 +803,28 @@ func apply_snap(pos: Vector2) -> PackedFloat64Array: func _on_handle_added() -> void: if not get_viewport_rect().has_point(get_viewport().get_mouse_position()): - if not Indications.semi_selected_xid.is_empty(): - SVG.root_element.get_xnode(Indications.semi_selected_xid).get_attribute("d").\ + if not State.semi_selected_xid.is_empty(): + State.root_element.get_xnode(State.semi_selected_xid).get_attribute("d").\ sync_after_commands_change() - SVG.queue_save() + State.queue_svg_save() return update_handles() - if SVG.root_element.get_xnode(Indications.semi_selected_xid).get_attribute("d").\ - get_commands()[Indications.inner_selections[0]].command_char in "Zz": - SVG.queue_save() + if State.root_element.get_xnode(State.semi_selected_xid).get_attribute("d").\ + get_commands()[State.inner_selections[0]].command_char in "Zz": + State.queue_svg_save() return for handle in handles: - if handle is PathHandle and handle.element.xid == Indications.semi_selected_xid and\ - handle.command_index == Indications.inner_selections[0]: - Indications.set_hovered(handle.element.xid, handle.command_index) + if handle is PathHandle and handle.element.xid == State.semi_selected_xid and\ + handle.command_index == State.inner_selections[0]: + State.set_hovered(handle.element.xid, handle.command_index) dragged_handle = handle # Move the handle that's being dragged. var mouse_pos := apply_snap(get_global_mouse_position()) var new_pos := Utils64Bit.transform_vector_mult( Utils64Bit.get_transform_affine_inverse(dragged_handle.precise_transform), - SVG.root_element.world_to_canvas_64_bit(mouse_pos)) + State.root_element.world_to_canvas_64_bit(mouse_pos)) dragged_handle.set_pos(new_pos) was_handle_moved = true return @@ -846,6 +844,6 @@ func create_element_context(pos: Vector2) -> ContextPopup: return element_context func add_shape_at_pos(element_name: String, pos: Vector2) -> void: - SVG.root_element.add_xnode(DB.element_with_setup(element_name, apply_snap(pos)), - PackedInt32Array([SVG.root_element.get_child_count()])) - SVG.queue_save() + State.root_element.add_xnode(DB.element_with_setup(element_name, apply_snap(pos)), + PackedInt32Array([State.root_element.get_child_count()])) + State.queue_svg_save() diff --git a/src/ui_parts/import_warning_menu.gd b/src/ui_parts/import_warning_menu.gd index 164b6ada..89768c65 100644 --- a/src/ui_parts/import_warning_menu.gd +++ b/src/ui_parts/import_warning_menu.gd @@ -1,6 +1,7 @@ extends PanelContainer signal imported +signal canceled @onready var warnings_label: RichTextLabel = %WarningsLabel @onready var texture_preview: CenterContainer = %TexturePreview @@ -13,6 +14,8 @@ var imported_text := "" func _ready() -> void: imported.connect(queue_free) ok_button.pressed.connect(imported.emit) + canceled.connect(queue_free) + cancel_button.pressed.connect(canceled.emit) # Convert forward and backward to show how GodSVG would display the given SVG. var imported_text_parse_result := SVGParser.text_to_root(imported_text, @@ -45,7 +48,6 @@ func _ready() -> void: for warning in svg_warnings: warnings_label.text += warning + "\n" ok_button.grab_focus() - cancel_button.pressed.connect(queue_free) $VBoxContainer/Title.text = Translator.translate("Import Problems") ok_button.text = Translator.translate("Import") cancel_button.text = Translator.translate("Cancel") diff --git a/src/ui_parts/import_warning_menu.tscn b/src/ui_parts/import_warning_menu.tscn index e4e6cee1..4b17a50d 100644 --- a/src/ui_parts/import_warning_menu.tscn +++ b/src/ui_parts/import_warning_menu.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=4 format=3 uid="uid://bhskf8yrulqtj"] -[ext_resource type="Script" path="res://src/ui_parts/import_warning_menu.gd" id="1_1rv5w"] +[ext_resource type="Script" uid="uid://d1mdyvr7majfe" path="res://src/ui_parts/import_warning_menu.gd" id="1_1rv5w"] [ext_resource type="PackedScene" uid="uid://xh26qa68xed4" path="res://src/ui_widgets/preview_rect.tscn" id="2_j1v8v"] [ext_resource type="FontFile" uid="uid://depydd16jq777" path="res://assets/fonts/FontMono.ttf" id="4_rpfrk"] @@ -70,7 +70,6 @@ alignment = 1 layout_mode = 2 size_flags_horizontal = 6 mouse_default_cursor_shape = 2 -text = "Cancel" [node name="OKButton" type="Button" parent="VBoxContainer/ButtonContainer"] layout_mode = 2 diff --git a/src/ui_parts/inspector.gd b/src/ui_parts/inspector.gd index f3bb98a4..f8438366 100644 --- a/src/ui_parts/inspector.gd +++ b/src/ui_parts/inspector.gd @@ -12,8 +12,8 @@ func _ready() -> void: Configs.language_changed.connect(update_translation) update_theme() update_translation() - SVG.xnode_layout_changed.connect(full_rebuild) - SVG.changed_unknown.connect(full_rebuild) + State.xnode_layout_changed.connect(full_rebuild) + State.svg_unknown_change.connect(full_rebuild) add_button.pressed.connect(_on_add_button_pressed) @@ -29,7 +29,7 @@ func update_translation() -> void: func full_rebuild() -> void: for node in xnodes_container.get_children(): node.queue_free() - for xnode_editor in XNodeChildrenBuilder.create(SVG.root_element): + for xnode_editor in XNodeChildrenBuilder.create(State.root_element): xnodes_container.add_child(xnode_editor) func add_element(element_name: String) -> void: @@ -38,9 +38,9 @@ func add_element(element_name: String) -> void: if element_name in ["linearGradient", "radialGradient", "stop"]: loc = PackedInt32Array([0]) else: - loc = PackedInt32Array([SVG.root_element.get_child_count()]) - SVG.root_element.add_xnode(new_element, loc) - SVG.queue_save() + loc = PackedInt32Array([State.root_element.get_child_count()]) + State.root_element.add_xnode(new_element, loc) + State.queue_svg_save() func _on_add_button_pressed() -> void: diff --git a/src/ui_parts/global_menu.gd b/src/ui_parts/mac_menu.gd similarity index 93% rename from src/ui_parts/global_menu.gd rename to src/ui_parts/mac_menu.gd index 00ae4359..21d1d29e 100644 --- a/src/ui_parts/global_menu.gd +++ b/src/ui_parts/mac_menu.gd @@ -7,9 +7,7 @@ var help_rid: RID var file_rid: RID var file_idx: int -var file_clear_svg_idx: int var file_optimize_idx: int -var file_clear_association_idx: int var file_reset_svg_idx: int var edit_rid: RID @@ -44,8 +42,7 @@ func _enter_tree() -> void: # Custom menus. _generate_main_menus() _setup_menu_items() - SVG.changed.connect(_on_svg_changed) - Configs.file_path_changed.connect(_on_file_path_changed) + State.svg_changed.connect(_on_svg_changed) func _reset_menus() -> void: @@ -111,13 +108,10 @@ func _setup_menu_items() -> void: _add_action(file_rid, "save") NativeMenu.add_separator(file_rid) _add_action(file_rid, "copy_svg_text") - file_clear_svg_idx = _add_action(file_rid, "clear_svg") file_optimize_idx = _add_action(file_rid, "optimize") NativeMenu.add_separator(file_rid) - file_clear_association_idx = _add_action(file_rid, "clear_file_path") file_reset_svg_idx = _add_action(file_rid, "reset_svg") _on_svg_changed() - _on_file_path_changed() # Edit and Tool menus. _add_many_actions(edit_rid, ShortcutUtils.get_shortcuts("edit")) _add_many_actions(tool_rid, ShortcutUtils.get_shortcuts("tool")) @@ -182,14 +176,9 @@ func _get_keycode_for_events(input_events: Array[InputEvent]) -> Key: func _on_svg_changed() -> void: - NativeMenu.set_item_disabled(file_rid, file_clear_svg_idx, SVG.text == SVG.DEFAULT) NativeMenu.set_item_disabled(file_rid, file_reset_svg_idx, FileUtils.compare_svg_to_disk_contents() == FileUtils.FileState.DIFFERENT) -func _on_file_path_changed() -> void: - NativeMenu.set_item_disabled(file_rid, file_clear_association_idx, - Configs.savedata.current_file_path.is_empty()) - func _on_display_view_settings_updated(show_grid: bool, show_handles: bool, rasterized_svg: bool) -> void: NativeMenu.set_item_checked(view_rid, view_show_grid_idx, show_grid) NativeMenu.set_item_checked(view_rid, view_show_handles_idx, show_handles) diff --git a/src/ui_parts/global_menu.gd.uid b/src/ui_parts/mac_menu.gd.uid similarity index 100% rename from src/ui_parts/global_menu.gd.uid rename to src/ui_parts/mac_menu.gd.uid diff --git a/src/ui_parts/mac_menu.tscn b/src/ui_parts/mac_menu.tscn new file mode 100644 index 00000000..2f252f13 --- /dev/null +++ b/src/ui_parts/mac_menu.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3 uid="uid://dqrvfi76ak512"] + +[ext_resource type="Script" uid="uid://cjkc40a5x7j4k" path="res://src/ui_parts/mac_menu.gd" id="1_r2kng"] + +[node name="MacMenu" type="Node"] +script = ExtResource("1_r2kng") diff --git a/src/ui_parts/move_to_overlay.gd b/src/ui_parts/move_to_overlay.gd index 38416e58..538abe10 100644 --- a/src/ui_parts/move_to_overlay.gd +++ b/src/ui_parts/move_to_overlay.gd @@ -6,14 +6,14 @@ func _can_drop_data(_at_position: Vector2, data: Variant) -> bool: return false get_parent().update_proposed_xid() for xid in data: - if XIDUtils.is_parent(xid, Indications.proposed_drop_xid): + if XIDUtils.is_parent(xid, State.proposed_drop_xid): return false - if Indications.proposed_drop_xid.is_empty(): + if State.proposed_drop_xid.is_empty(): return false return true # Runs when you drop the XIDs. func _drop_data(_at_position: Vector2, data: Variant) -> void: if data is Array[PackedInt32Array]: - SVG.root_element.move_xnodes_to(data, Indications.proposed_drop_xid) - SVG.queue_save() + State.root_element.move_xnodes_to(data, State.proposed_drop_xid) + State.queue_svg_save() diff --git a/src/ui_parts/root_element_editor.gd b/src/ui_parts/root_element_editor.gd index b00faf9f..5c41b031 100644 --- a/src/ui_parts/root_element_editor.gd +++ b/src/ui_parts/root_element_editor.gd @@ -26,8 +26,8 @@ const NumberEditType = preload("res://src/ui_widgets/number_edit.gd") @onready var unknown_container: MarginContainer func _ready() -> void: - SVG.any_attribute_changed.connect(_on_any_attribute_changed) - SVG.changed_unknown.connect(update_attributes) + State.any_attribute_changed.connect(_on_any_attribute_changed) + State.svg_unknown_change.connect(update_attributes) update_attributes() width_edit.value_changed.connect(_on_width_edit_value_changed) height_edit.value_changed.connect(_on_height_edit_value_changed) @@ -50,7 +50,7 @@ func update_attributes() -> void: for child in unknown_container.get_children(): child.queue_free() var has_unrecognized_attributes := false - for attribute in SVG.root_element.get_all_attributes(): + for attribute in State.root_element.get_all_attributes(): # TODO separate unrecognized attributes from global defaults. if not attribute.name in ["width", "height", "viewBox", "xmlns"]: if not has_unrecognized_attributes: @@ -65,7 +65,7 @@ func update_attributes() -> void: add_child(unknown_container) move_child(unknown_container, 0) - var input_field := AttributeFieldBuilder.create(attribute.name, SVG.root_element) + var input_field := AttributeFieldBuilder.create(attribute.name, State.root_element) unknown_container.get_child(0).add_child(input_field) if not has_unrecognized_attributes and is_instance_valid(unknown_container): unknown_container.queue_free() @@ -73,17 +73,17 @@ func update_attributes() -> void: func update_editable() -> void: - width_edit.set_value(SVG.root_element.width, false) - height_edit.set_value(SVG.root_element.height, false) - viewbox_edit_x.set_value(SVG.root_element.viewbox.position.x, false) - viewbox_edit_y.set_value(SVG.root_element.viewbox.position.y, false) - viewbox_edit_w.set_value(SVG.root_element.viewbox.size.x, false) - viewbox_edit_h.set_value(SVG.root_element.viewbox.size.y, false) + width_edit.set_value(State.root_element.width, false) + height_edit.set_value(State.root_element.height, false) + viewbox_edit_x.set_value(State.root_element.viewbox.position.x, false) + viewbox_edit_y.set_value(State.root_element.viewbox.position.y, false) + viewbox_edit_w.set_value(State.root_element.viewbox.size.x, false) + viewbox_edit_h.set_value(State.root_element.viewbox.size.y, false) - var is_width_valid := SVG.root_element.has_attribute("width") - var is_height_valid := SVG.root_element.has_attribute("height") - var is_viewbox_valid: bool = SVG.root_element.has_attribute("viewBox") and\ - SVG.root_element.get_attribute("viewBox").get_list_size() >= 4 + var is_width_valid := State.root_element.has_attribute("width") + var is_height_valid := State.root_element.has_attribute("height") + var is_viewbox_valid: bool = State.root_element.has_attribute("viewBox") and\ + State.root_element.get_attribute("viewBox").get_list_size() >= 4 width_button.set_pressed_no_signal(is_width_valid) height_button.set_pressed_no_signal(is_height_valid) @@ -98,78 +98,78 @@ func update_editable() -> void: func _on_width_edit_value_changed(new_value: float) -> void: - if is_finite(new_value) and SVG.root_element.get_attribute_num("width") != new_value: - SVG.root_element.width = new_value - SVG.root_element.set_attribute("width", new_value) + if is_finite(new_value) and State.root_element.get_attribute_num("width") != new_value: + State.root_element.width = new_value + State.root_element.set_attribute("width", new_value) else: - SVG.root_element.set_attribute("width", SVG.root_element.width) - SVG.queue_save() + State.root_element.set_attribute("width", State.root_element.width) + State.queue_svg_save() func _on_height_edit_value_changed(new_value: float) -> void: - if is_finite(new_value) and SVG.root_element.get_attribute_num("height") != new_value: - SVG.root_element.height = new_value - SVG.root_element.set_attribute("height", new_value) + if is_finite(new_value) and State.root_element.get_attribute_num("height") != new_value: + State.root_element.height = new_value + State.root_element.set_attribute("height", new_value) else: - SVG.root_element.set_attribute("height", SVG.root_element.height) - SVG.queue_save() + State.root_element.set_attribute("height", State.root_element.height) + State.queue_svg_save() func _on_viewbox_edit_x_value_changed(new_value: float) -> void: - if SVG.root_element.has_attribute("viewBox"): - SVG.root_element.viewbox.position.x = new_value - SVG.root_element.get_attribute("viewBox").set_list_element(0, new_value) - SVG.queue_save() + if State.root_element.has_attribute("viewBox"): + State.root_element.viewbox.position.x = new_value + State.root_element.get_attribute("viewBox").set_list_element(0, new_value) + State.queue_svg_save() func _on_viewbox_edit_y_value_changed(new_value: float) -> void: - if SVG.root_element.has_attribute("viewBox"): - SVG.root_element.viewbox.position.y = new_value - SVG.root_element.get_attribute("viewBox").set_list_element(1, new_value) - SVG.queue_save() + if State.root_element.has_attribute("viewBox"): + State.root_element.viewbox.position.y = new_value + State.root_element.get_attribute("viewBox").set_list_element(1, new_value) + State.queue_svg_save() func _on_viewbox_edit_w_value_changed(new_value: float) -> void: - if SVG.root_element.has_attribute("viewBox") and\ - SVG.root_element.get_attribute("viewBox").get_list_element(2) != new_value: - SVG.root_element.viewbox.size.x = new_value - SVG.root_element.get_attribute("viewBox").set_list_element(2, new_value) - SVG.queue_save() + if State.root_element.has_attribute("viewBox") and\ + State.root_element.get_attribute("viewBox").get_list_element(2) != new_value: + State.root_element.viewbox.size.x = new_value + State.root_element.get_attribute("viewBox").set_list_element(2, new_value) + State.queue_svg_save() func _on_viewbox_edit_h_value_changed(new_value: float) -> void: - if SVG.root_element.has_attribute("viewBox") and\ - SVG.root_element.get_attribute("viewBox").get_list_element(3) != new_value: - SVG.root_element.viewbox.size.y = new_value - SVG.root_element.get_attribute("viewBox").set_list_element(3, new_value) - SVG.queue_save() + if State.root_element.has_attribute("viewBox") and\ + State.root_element.get_attribute("viewBox").get_list_element(3) != new_value: + State.root_element.viewbox.size.y = new_value + State.root_element.get_attribute("viewBox").set_list_element(3, new_value) + State.queue_svg_save() func _on_width_button_toggled(toggled_on: bool) -> void: if toggled_on: - SVG.root_element.set_attribute("width", SVG.root_element.width) - SVG.queue_save() + State.root_element.set_attribute("width", State.root_element.width) + State.queue_svg_save() else: - if SVG.root_element.get_attribute("viewBox").get_list_size() == 4: - SVG.root_element.set_attribute("width", "") - SVG.queue_save() + if State.root_element.get_attribute("viewBox").get_list_size() == 4: + State.root_element.set_attribute("width", "") + State.queue_svg_save() else: width_button.set_pressed_no_signal(true) func _on_height_button_toggled(toggled_on: bool) -> void: if toggled_on: - SVG.root_element.set_attribute("height", SVG.root_element.height) - SVG.queue_save() + State.root_element.set_attribute("height", State.root_element.height) + State.queue_svg_save() else: - if SVG.root_element.get_attribute("viewBox").get_list_size() == 4: - SVG.root_element.set_attribute("height", "") - SVG.queue_save() + if State.root_element.get_attribute("viewBox").get_list_size() == 4: + State.root_element.set_attribute("height", "") + State.queue_svg_save() else: height_button.set_pressed_no_signal(true) func _on_viewbox_button_toggled(toggled_on: bool) -> void: if toggled_on: - SVG.root_element.set_attribute("viewBox", - ListParser.rect_to_list(SVG.root_element.viewbox)) - SVG.queue_save() + State.root_element.set_attribute("viewBox", + ListParser.rect_to_list(State.root_element.viewbox)) + State.queue_svg_save() else: - if SVG.root_element.has_attribute("width") and\ - SVG.root_element.has_attribute("height"): - SVG.root_element.set_attribute("viewBox", "") - SVG.queue_save() + if State.root_element.has_attribute("width") and\ + State.root_element.has_attribute("height"): + State.root_element.set_attribute("viewBox", "") + State.queue_svg_save() else: viewbox_button.set_pressed_no_signal(true) diff --git a/src/ui_parts/settings_menu.gd b/src/ui_parts/settings_menu.gd index e0bc2cb6..960e241c 100644 --- a/src/ui_parts/settings_menu.gd +++ b/src/ui_parts/settings_menu.gd @@ -100,6 +100,7 @@ func setup_content() -> void: btn.text = get_translated_formatter_tab(tab_idx) btn.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND btn.focus_mode = Control.FOCUS_NONE + btn.action_mode = BaseButton.ACTION_MODE_BUTTON_PRESS categories.add_child(btn) vbox.add_child(categories) create_setting_container() @@ -128,6 +129,7 @@ func setup_content() -> void: btn.text = get_translated_shortcut_tab(tab_idx) btn.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND btn.focus_mode = Control.FOCUS_NONE + btn.action_mode = BaseButton.ACTION_MODE_BUTTON_PRESS categories.add_child(btn) vbox.add_child(categories) var shortcuts := VBoxContainer.new() diff --git a/src/ui_parts/tab_bar.gd b/src/ui_parts/tab_bar.gd new file mode 100644 index 00000000..4211239b --- /dev/null +++ b/src/ui_parts/tab_bar.gd @@ -0,0 +1,274 @@ +extends Control + +const plus_icon = preload("res://assets/icons/Plus.svg") +const close_icon = preload("res://assets/icons/Close.svg") + +const TAB_WIDTH = 120.0 +const CLOSE_BUTTON_MARGIN = 2 + +var active_controls: Array[Control] = [] + +var proposed_drop_idx := -1: + set(new_value): + if proposed_drop_idx != new_value: + proposed_drop_idx = new_value + queue_redraw() + +func _ready() -> void: + Configs.active_tab_file_path_changed.connect(queue_redraw) + Configs.active_tab_changed.connect(activate) + Configs.tabs_changed.connect(activate) + Configs.language_changed.connect(queue_redraw) + mouse_entered.connect(_on_mouse_entered) + mouse_exited.connect(_on_mouse_exited) + +func _draw() -> void: + var background_stylebox: StyleBoxFlat =\ + get_theme_stylebox("tab_unselected", "TabContainer").duplicate() + background_stylebox.corner_radius_top_left += 1 + background_stylebox.corner_radius_top_right += 1 + background_stylebox.bg_color = Color(ThemeUtils.common_panel_inner_color, 0.4) + draw_style_box(background_stylebox, get_rect()) + + for tab_index in Configs.savedata.get_tab_count() + 1: + var has_transient_tab := not State.transient_tab_path.is_empty() + var drawing_transient_tab := tab_index == Configs.savedata.get_tab_count() + if drawing_transient_tab and not has_transient_tab: + break + + var current_tab_name := State.transient_tab_path.get_file() if\ + drawing_transient_tab else Configs.savedata.get_tab(tab_index).get_presented_name() + + var rect := get_tab_rect(tab_index) + var text_line := TextLine.new() + text_line.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS + text_line.add_string(current_tab_name, ThemeUtils.regular_font, 13) + if (has_transient_tab and drawing_transient_tab) or\ + (not has_transient_tab and tab_index == Configs.savedata.get_active_tab_index()): + var close_rect := get_close_button_rect() + text_line.width = TAB_WIDTH - close_rect.size.x - CLOSE_BUTTON_MARGIN * 2 - 4 + draw_style_box(get_theme_stylebox("tab_selected", "TabContainer"), rect) + text_line.draw(get_canvas_item(), rect.position + Vector2(4, 3)) + var close_icon_size := close_icon.get_size() + draw_texture_rect(close_icon, Rect2(close_rect.position +\ + (close_rect.size - close_icon_size) / 2.0, close_icon_size), false) + else: + text_line.width = TAB_WIDTH - 8 + var is_hovered := rect.has_point(get_local_mouse_position()) + var tab_style := "tab_hovered" if is_hovered else "tab_unselected" + var text_color := ThemeUtils.common_text_color if is_hovered else\ + (ThemeUtils.common_subtle_text_color + ThemeUtils.common_text_color) / 2 + draw_style_box(get_theme_stylebox(tab_style, "TabContainer"), rect) + text_line.draw(get_canvas_item(), rect.position + Vector2(4, 3), text_color) + if Configs.savedata.get_tab_count() < SaveData.MAX_TABS: + var plus_rect := get_add_button_rect() + var plus_icon_size := plus_icon.get_size() + draw_texture_rect(plus_icon, Rect2(plus_rect.position +\ + (plus_rect.size - plus_icon_size) / 2.0, plus_icon_size), false) + + if proposed_drop_idx != -1: + draw_line(Vector2(TAB_WIDTH * proposed_drop_idx, 0), + Vector2(TAB_WIDTH * proposed_drop_idx, size.y), + Configs.savedata.basic_color_valid, 4) + + +func _gui_input(event: InputEvent) -> void: + if not event is InputEventMouse: + return + + queue_redraw() + if event is InputEventMouseButton and event.is_pressed(): + if event.button_index in [MOUSE_BUTTON_LEFT, MOUSE_BUTTON_RIGHT]: + var hovered_idx := get_hovered_index() + if hovered_idx != -1: + Configs.savedata.set_active_tab_index(hovered_idx) + + if event.button_index == MOUSE_BUTTON_LEFT: + return + + 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"), "close_tab")) + else: + btn_arr.append(ContextPopup.create_button(Translator.translate("Close tab"), + close_tab.bind(hovered_idx), false, null, "close_tab")) + # TODO Unify into "Close multiple tabs" + btn_arr.append(ContextPopup.create_button( + Translator.translate("Close all other tabs"), + close_other_tabs.bind(hovered_idx), + Configs.savedata.get_tab_count() == 1, null)) + btn_arr.append(ContextPopup.create_button( + Translator.translate("Close tabs to the left"), + close_tabs_to_left.bind(hovered_idx), hovered_idx == 0, null)) + btn_arr.append(ContextPopup.create_button( + Translator.translate("Close tabs to the right"), + close_tabs_to_right.bind(hovered_idx), + hovered_idx == Configs.savedata.get_tab_count() - 1, null)) + var tab_popup := ContextPopup.new() + tab_popup.setup(btn_arr, true) + + if hovered_idx != -1: + var tab_global_rect := get_tab_rect(hovered_idx) + tab_global_rect.position += get_global_rect().position + HandlerGUI.popup_under_rect(tab_popup, tab_global_rect, get_viewport()) + else: + HandlerGUI.popup_under_pos(tab_popup, get_global_mouse_position(), get_viewport()) + + +func close_tab(idx: int) -> void: + Configs.savedata.remove_tabs(PackedInt32Array([idx])) + +func close_other_tabs(idx: int) -> void: + Configs.savedata.remove_tabs(PackedInt32Array(range(0, idx) +\ + range(idx + 1, Configs.savedata.get_tab_count()))) + +func close_tabs_to_left(idx: int) -> void: + Configs.savedata.remove_tabs(PackedInt32Array(range(0, idx))) + +func close_tabs_to_right(idx: int) -> void: + Configs.savedata.remove_tabs(PackedInt32Array( + range(idx + 1, Configs.savedata.get_tab_count()))) + + +func _on_mouse_entered() -> void: + activate() + +func _on_mouse_exited() -> void: + cleanup() + +func cleanup() -> void: + for control in active_controls: + control.queue_free() + active_controls = [] + queue_redraw() + + +func get_tab_rect(idx: int) -> Rect2: + return Rect2(TAB_WIDTH * idx, 0, TAB_WIDTH, size.y) + +func get_close_button_rect() -> Rect2: + var active_index := Configs.savedata.get_active_tab_index() if\ + State.transient_tab_path.is_empty() else Configs.savedata.get_tab_count() + var side := size.y - CLOSE_BUTTON_MARGIN * 2 + return Rect2(TAB_WIDTH * (active_index + 1) - CLOSE_BUTTON_MARGIN - side, + CLOSE_BUTTON_MARGIN, side, side) + +func get_add_button_rect() -> Rect2: + var tab_count := Configs.savedata.get_tab_count() + if not State.transient_tab_path.is_empty(): + tab_count += 1 + return Rect2(TAB_WIDTH * tab_count, 0, size.y, size.y) + +func get_hovered_index() -> int: + var mouse_pos := get_local_mouse_position() + if get_close_button_rect().has_point(mouse_pos): + return -1 + + for idx in Configs.savedata.get_tab_count(): + if get_tab_rect(idx).has_point(mouse_pos): + return idx + return -1 + + +func activate() -> void: + cleanup() + + var close_rect := get_close_button_rect() + var close_button := Button.new() + close_button.theme_type_variation = "FlatButton" + close_button.focus_mode = Control.FOCUS_NONE + close_button.position = close_rect.position + close_button.size = close_rect.size + close_button.mouse_filter = Control.MOUSE_FILTER_PASS + add_child(close_button) + active_controls.append(close_button) + close_button.pressed.connect(Configs.savedata.remove_active_tab) + + if Configs.savedata.get_tab_count() >= SaveData.MAX_TABS: + return + + var add_rect := get_add_button_rect() + var add_button := Button.new() + add_button.theme_type_variation = "FlatButton" + add_button.focus_mode = Control.FOCUS_NONE + add_button.position = add_rect.position + add_button.size = add_rect.size + add_button.mouse_filter = Control.MOUSE_FILTER_PASS + add_button.tooltip_text = Translator.translate("Add new tab") + add_child(add_button) + active_controls.append(add_button) + add_button.pressed.connect(Configs.savedata.add_empty_tab) + + +func _get_tooltip(at_position: Vector2) -> String: + var hovered_tab_idx := get_tab_index_at(at_position) + if hovered_tab_idx == -1: + 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 location on the computer yet.") + return current_tab.svg_file_path + + +func get_tab_index_at(pos: Vector2) -> int: + if not get_close_button_rect().has_point(pos): + for tab_index in Configs.savedata.get_tab_count(): + if get_tab_rect(tab_index).has_point(pos): + return tab_index + return -1 + + +class TabDropData extends RefCounted: + var index := -1 + func _init(new_index: int) -> void: + index = new_index + +func get_drop_index_at(pos: Vector2) -> int: + for idx in Configs.savedata.get_tab_count(): + if get_tab_rect(idx).get_center().x > pos.x: + return idx + return Configs.savedata.get_tab_count() + +func _get_drag_data(at_position: Vector2) -> Variant: + # Roughly mimics the tab drawing. + var preview := Panel.new() + preview.modulate = Color(1, 1, 1, 0.85) + preview.custom_minimum_size = Vector2(TAB_WIDTH, size.y) + preview.add_theme_stylebox_override("panel", + get_theme_stylebox("tab_selected", "TabContainer")) + var label := Label.new() + label.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS + label.add_theme_font_size_override("font_size", 13) + label.text = Configs.savedata.get_active_tab().get_presented_name() + preview.add_child(label) + label.position = Vector2(4, 3) + label.size.x = TAB_WIDTH - 8 + + set_drag_preview(preview) + return TabDropData.new(get_tab_index_at(at_position)) + +func _can_drop_data(at_position: Vector2, data: Variant) -> bool: + if not data is TabDropData: + proposed_drop_idx = -1 + return false + var current_drop_idx = get_drop_index_at(at_position) + if current_drop_idx in [data.index, data.index + 1]: + proposed_drop_idx = -1 + return false + else: + proposed_drop_idx = current_drop_idx + return true + +func _drop_data(at_position: Vector2, data: Variant) -> void: + if not data is TabDropData: + return + Configs.savedata.move_tab(data.index, get_drop_index_at(at_position)) + +func _notification(what: int) -> void: + if what == NOTIFICATION_DRAG_END: + proposed_drop_idx = -1 diff --git a/src/ui_parts/tab_bar.gd.uid b/src/ui_parts/tab_bar.gd.uid new file mode 100644 index 00000000..3f20792d --- /dev/null +++ b/src/ui_parts/tab_bar.gd.uid @@ -0,0 +1 @@ +uid://rqrxhe8wa6fn diff --git a/src/ui_parts/viewport.gd b/src/ui_parts/viewport.gd index 219b2348..dac977e5 100644 --- a/src/ui_parts/viewport.gd +++ b/src/ui_parts/viewport.gd @@ -18,22 +18,22 @@ var _zoom_to: Vector2 func _ready() -> void: zoom_menu.zoom_changed.connect(view.update.unbind(2)) - SVG.resized.connect(resize) - Indications.viewport_size_changed.connect(adjust_view) + State.svg_resized.connect(resize) + State.viewport_size_changed.connect(adjust_view) resize() await get_tree().process_frame zoom_menu.zoom_reset() # Top left corner. func set_view(new_position: Vector2) -> void: - var scaled_size := size / Indications.zoom + var scaled_size := size / State.zoom view.position = new_position.clamp(Vector2(view.limit_left, view.limit_top), Vector2(view.limit_right, view.limit_bottom) - scaled_size) var stripped_left := maxf(view.position.x, 0.0) var stripped_top := maxf(view.position.y, 0.0) - var stripped_right := minf(view.position.x + scaled_size.x, SVG.root_element.width) - var stripped_bottom := minf(view.position.y + scaled_size.y, SVG.root_element.height) + var stripped_right := minf(view.position.x + scaled_size.x, State.root_element.width) + var stripped_bottom := minf(view.position.y + scaled_size.y, State.root_element.height) display_texture.view_rect = Rect2(stripped_left, stripped_top, stripped_right - stripped_left, stripped_bottom - stripped_top) view.update() @@ -41,25 +41,25 @@ func set_view(new_position: Vector2) -> void: # Adjust the SVG dimensions. func resize() -> void: - if SVG.root_element.get_size().is_finite(): - display.size = SVG.root_element.get_size() - reference_texture.size = SVG.root_element.get_size() + if State.root_element.get_size().is_finite(): + display.size = State.root_element.get_size() + reference_texture.size = State.root_element.get_size() zoom_menu.zoom_reset() func center_frame() -> void: var available_size := size * ZOOM_RESET_BUFFER - var w_ratio := available_size.x / SVG.root_element.width - var h_ratio := available_size.y / SVG.root_element.height + var w_ratio := available_size.x / State.root_element.width + var h_ratio := available_size.y / State.root_element.height if is_finite(w_ratio) and is_finite(h_ratio): zoom_menu.set_zoom(nearest_po2(ceili(minf(w_ratio, h_ratio) * 32)) / 64.0) else: zoom_menu.set_zoom(1.0) adjust_view() - set_view((SVG.root_element.get_size() - size / Indications.zoom) / 2) + set_view((State.root_element.get_size() - size / State.zoom) / 2) func _unhandled_input(event: InputEvent) -> void: - if Indications.get_viewport().gui_is_dragging(): + if State.get_viewport().gui_is_dragging(): return if event is InputEventMouseMotion and\ @@ -68,7 +68,7 @@ func _unhandled_input(event: InputEvent) -> void: if event.ctrl_pressed and event.button_mask == MOUSE_BUTTON_MASK_MIDDLE: if _zoom_to == Vector2.ZERO: # Set zoom position if starting action. _zoom_to = get_mouse_position() / (size * 1.0) - zoom_menu.set_zoom(Indications.zoom * (1.0 +\ + zoom_menu.set_zoom(State.zoom * (1.0 +\ (1 if Configs.savedata.invert_zoom else -1) *\ (wrap_mouse(event.relative).y if Configs.savedata.wrap_mouse else\ event.relative.y) / 128.0), _zoom_to) @@ -76,18 +76,18 @@ func _unhandled_input(event: InputEvent) -> void: # without dragging the things on it. else: set_view(view.position - (wrap_mouse(event.relative) if\ - Configs.savedata.wrap_mouse else event.relative) / Indications.zoom) + Configs.savedata.wrap_mouse else event.relative) / State.zoom) elif event is InputEventPanGesture and not DisplayServer.get_name() == "Android": # Zooming with Ctrl + touch? if event.ctrl_pressed: - zoom_menu.set_zoom(Indications.zoom * (1 + event.delta.y / 2)) + zoom_menu.set_zoom(State.zoom * (1 + event.delta.y / 2)) # Panning with touch. else: - set_view(view.position + event.delta * 32 / Indications.zoom) + set_view(view.position + event.delta * 32 / State.zoom) # Zooming with touch. elif event is InputEventMagnifyGesture: - zoom_menu.set_zoom(Indications.zoom * event.factor) + zoom_menu.set_zoom(State.zoom * event.factor) # Actions with scrolling. elif event is InputEventMouseButton and event.is_pressed(): var move_vec := Vector2.ZERO @@ -130,7 +130,7 @@ func _unhandled_input(event: InputEvent) -> void: elif zoom_dir == -1: zoom_menu.zoom_out(factor, mouse_offset) - set_view(view.position + move_vec * factor / Indications.zoom * 32) + set_view(view.position + move_vec * factor / State.zoom * 32) else: if not event.is_echo(): @@ -138,33 +138,33 @@ func _unhandled_input(event: InputEvent) -> void: func _on_zoom_changed(new_zoom_level: float, offset: Vector2) -> void: - Indications.set_zoom(new_zoom_level) + State.set_zoom(new_zoom_level) adjust_view(offset) display.material.set_shader_parameter("uv_scale", - nearest_po2(int(Indications.zoom * 32.0)) / 32.0) + nearest_po2(int(State.zoom * 32.0)) / 32.0) -var last_size_adjusted := size / Indications.zoom +var last_size_adjusted := size / State.zoom func adjust_view(offset := Vector2(0.5, 0.5)) -> void: var old_size := last_size_adjusted - last_size_adjusted = size / Indications.zoom + last_size_adjusted = size / State.zoom - var svg_w := SVG.root_element.width if\ - SVG.root_element.has_attribute("width") else 16384.0 - var svg_h := SVG.root_element.height if\ - SVG.root_element.has_attribute("height") else 16384.0 + var svg_w := State.root_element.width if\ + State.root_element.has_attribute("width") else 16384.0 + var svg_h := State.root_element.height if\ + State.root_element.has_attribute("height") else 16384.0 - var zoomed_size := BUFFER_VIEW_SPACE * size / Indications.zoom + var zoomed_size := BUFFER_VIEW_SPACE * size / State.zoom view.limit_left = -zoomed_size.x view.limit_right = zoomed_size.x + svg_w view.limit_top = -zoomed_size.y view.limit_bottom = zoomed_size.y + svg_h set_view(Vector2(lerpf(view.position.x, view.position.x + old_size.x -\ - size.x / Indications.zoom, offset.x), lerpf(view.position.y, - view.position.y + old_size.y - size.y / Indications.zoom, offset.y))) + size.x / State.zoom, offset.x), lerpf(view.position.y, + view.position.y + old_size.y - size.y / State.zoom, offset.y))) func _on_size_changed() -> void: - Indications.set_viewport_size(size) + State.set_viewport_size(size) func wrap_mouse(relative: Vector2) -> Vector2: var view_rect := get_visible_rect().grow(-1.0) diff --git a/src/ui_parts/zoom_menu.gd b/src/ui_parts/zoom_menu.gd index 3943b689..3fa62ecc 100644 --- a/src/ui_parts/zoom_menu.gd +++ b/src/ui_parts/zoom_menu.gd @@ -13,6 +13,15 @@ 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: + Configs.language_changed.connect(update_translation) + update_translation() + func _unhandled_input(event: InputEvent) -> void: if ShortcutUtils.is_action_pressed(event, "zoom_in"): zoom_in() diff --git a/src/ui_parts/zoom_menu.tscn b/src/ui_parts/zoom_menu.tscn index a0750056..d53187ac 100644 --- a/src/ui_parts/zoom_menu.tscn +++ b/src/ui_parts/zoom_menu.tscn @@ -1,7 +1,7 @@ [gd_scene load_steps=10 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" path="res://src/ui_parts/zoom_menu.gd" id="1_18ab8"] +[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"] @@ -30,7 +30,6 @@ script = ExtResource("1_18ab8") [node name="ZoomOut" type="Button" parent="."] layout_mode = 2 -tooltip_text = "Zoom Out" focus_mode = 0 mouse_default_cursor_shape = 2 theme_type_variation = &"IconButton" @@ -42,7 +41,6 @@ icon_alignment = 1 [node name="ZoomReset" type="Button" parent="."] custom_minimum_size = Vector2(58, 0) layout_mode = 2 -tooltip_text = "Zoom Reset" focus_mode = 0 mouse_default_cursor_shape = 2 theme_override_font_sizes/font_size = 13 @@ -52,7 +50,6 @@ text = "100%" [node name="ZoomIn" type="Button" parent="."] layout_mode = 2 -tooltip_text = "Zoom In" focus_mode = 0 mouse_default_cursor_shape = 2 theme_type_variation = &"IconButton" diff --git a/src/ui_widgets/BetterLineEdit.gd b/src/ui_widgets/BetterLineEdit.gd index 9777ae55..55ad3141 100644 --- a/src/ui_widgets/BetterLineEdit.gd +++ b/src/ui_widgets/BetterLineEdit.gd @@ -51,6 +51,7 @@ func _on_base_class_focus_entered() -> void: func _on_base_class_focus_exited() -> void: first_click = false + deselect() if Input.is_action_pressed("ui_cancel"): text = text_before_focus text_change_canceled.emit() diff --git a/src/ui_parts/alert_dialog.gd b/src/ui_widgets/alert_dialog.gd similarity index 100% rename from src/ui_parts/alert_dialog.gd rename to src/ui_widgets/alert_dialog.gd diff --git a/src/ui_parts/alert_dialog.gd.uid b/src/ui_widgets/alert_dialog.gd.uid similarity index 100% rename from src/ui_parts/alert_dialog.gd.uid rename to src/ui_widgets/alert_dialog.gd.uid diff --git a/src/ui_parts/alert_dialog.tscn b/src/ui_widgets/alert_dialog.tscn similarity index 91% rename from src/ui_parts/alert_dialog.tscn rename to src/ui_widgets/alert_dialog.tscn index 927399e8..5f89e327 100644 --- a/src/ui_parts/alert_dialog.tscn +++ b/src/ui_widgets/alert_dialog.tscn @@ -1,7 +1,7 @@ [gd_scene load_steps=3 format=3 uid="uid://c0x44loihhyyo"] [ext_resource type="FontFile" uid="uid://dc0w4sx0h0fui" path="res://assets/fonts/FontBold.ttf" id="1_3yrpq"] -[ext_resource type="Script" path="res://src/ui_parts/alert_dialog.gd" id="1_qntyo"] +[ext_resource type="Script" uid="uid://dlsd0uctldklk" path="res://src/ui_widgets/alert_dialog.gd" id="1_qntyo"] [node name="AlertDialog" type="PanelContainer"] anchors_preset = 8 diff --git a/src/ui_widgets/basic_xnode_frame.gd b/src/ui_widgets/basic_xnode_frame.gd index bc2ecce9..fb971271 100644 --- a/src/ui_widgets/basic_xnode_frame.gd +++ b/src/ui_widgets/basic_xnode_frame.gd @@ -11,9 +11,9 @@ var surface := RenderingServer.canvas_item_create() # Used for the drop indicat func _ready() -> void: RenderingServer.canvas_item_set_parent(surface, get_canvas_item()) RenderingServer.canvas_item_set_z_index(surface, 1) - Indications.selection_changed.connect(determine_selection_highlight) - Indications.hover_changed.connect(determine_selection_highlight) - Indications.proposed_drop_changed.connect(queue_redraw) + State.selection_changed.connect(determine_selection_highlight) + State.hover_changed.connect(determine_selection_highlight) + State.proposed_drop_changed.connect(queue_redraw) title_bar.draw.connect(_on_title_bar_draw) mouse_exited.connect(_on_mouse_exited) determine_selection_highlight() @@ -27,11 +27,11 @@ func _exit_tree() -> void: # Logic for dragging. func _get_drag_data(_at_position: Vector2) -> Variant: - if Indications.selected_xids.is_empty(): + if State.selected_xids.is_empty(): return null var data: Array[PackedInt32Array] = XIDUtils.filter_descendants( - Indications.selected_xids.duplicate(true)) + State.selected_xids.duplicate(true)) set_drag_preview(XNodeChildrenBuilder.generate_drag_preview(data)) return data @@ -43,51 +43,51 @@ func _notification(what: int) -> void: func _on_title_button_pressed() -> void: # Update the selection immediately, since if this xnode editor is # in a multi-selection, only the mouse button release would change the selection. - Indications.normal_select(xnode.xid) + State.normal_select(xnode.xid) var viewport := get_viewport() var rect := title_bar.get_global_rect() - HandlerGUI.popup_under_rect_center(Indications.get_selection_context( - HandlerGUI.popup_under_rect_center.bind(rect, viewport), - Indications.Context.LIST), rect, viewport) + HandlerGUI.popup_under_rect_center(State.get_selection_context( + HandlerGUI.popup_under_rect_center.bind(rect, viewport), State.Context.LIST), + rect, viewport) func _gui_input(event: InputEvent) -> void: if event is InputEventMouseMotion and event.button_mask == 0: - if Indications.semi_hovered_xid != xnode.xid and\ - not XIDUtils.is_parent(xnode.xid, Indications.hovered_xid): - Indications.set_hovered(xnode.xid) + if State.semi_hovered_xid != xnode.xid and\ + not XIDUtils.is_parent(xnode.xid, State.hovered_xid): + State.set_hovered(xnode.xid) elif event is InputEventMouseButton: if event.button_index == MOUSE_BUTTON_LEFT: if event.is_pressed(): if event.shift_pressed: - Indications.shift_select(xnode.xid) + State.shift_select(xnode.xid) elif event.is_command_or_control_pressed(): - Indications.ctrl_select(xnode.xid) - elif not xnode.xid in Indications.selected_xids: - Indications.normal_select(xnode.xid) + State.ctrl_select(xnode.xid) + elif not xnode.xid in State.selected_xids: + State.normal_select(xnode.xid) elif event.is_released() and not event.shift_pressed and\ not event.is_command_or_control_pressed() and\ - Indications.selected_xids.size() > 1 and xnode.xid in Indications.selected_xids: - Indications.normal_select(xnode.xid) + State.selected_xids.size() > 1 and xnode.xid in State.selected_xids: + State.normal_select(xnode.xid) accept_event() elif event.button_index == MOUSE_BUTTON_RIGHT and event.is_pressed(): - if not xnode.xid in Indications.selected_xids: - Indications.normal_select(xnode.xid) + if not xnode.xid in State.selected_xids: + State.normal_select(xnode.xid) var viewport := get_viewport() var popup_pos := viewport.get_mouse_position() - HandlerGUI.popup_under_pos(Indications.get_selection_context( - HandlerGUI.popup_under_pos.bind(popup_pos, viewport), - Indications.Context.LIST), popup_pos, viewport) + HandlerGUI.popup_under_pos(State.get_selection_context( + HandlerGUI.popup_under_pos.bind(popup_pos, viewport), State.Context.LIST), + popup_pos, viewport) accept_event() func _on_mouse_exited() -> void: - Indications.remove_hovered(xnode.xid) + State.remove_hovered(xnode.xid) determine_selection_highlight() func determine_selection_highlight() -> void: - var is_selected := xnode.xid in Indications.selected_xids - var is_hovered := Indications.hovered_xid == xnode.xid + var is_selected := xnode.xid in State.selected_xids + var is_hovered := State.hovered_xid == xnode.xid if is_selected: if is_hovered: @@ -118,21 +118,21 @@ func _draw() -> void: RenderingServer.canvas_item_clear(surface) # There's only stuff to draw if there are drag-and-drop actions. - if Indications.proposed_drop_xid.is_empty(): + if State.proposed_drop_xid.is_empty(): return - for selected_xid in Indications.selected_xids: + for selected_xid in State.selected_xids: if XIDUtils.is_parent_or_self(selected_xid, xnode.xid): return var parent_xid := XIDUtils.get_parent_xid(xnode.xid) # Draw the indicator of drag and drop actions. var drop_sb := StyleBoxFlat.new() - var drop_xid := Indications.proposed_drop_xid + var drop_xid := State.proposed_drop_xid var drop_element := xnode.root.get_xnode(XIDUtils.get_parent_xid(drop_xid)) var are_all_children_valid := true - for xid in Indications.selected_xids: + for xid in State.selected_xids: var selected_xnode := xnode.root.get_xnode(xid) if not selected_xnode.is_element(): continue diff --git a/src/ui_parts/camera.gd b/src/ui_widgets/camera.gd similarity index 94% rename from src/ui_parts/camera.gd rename to src/ui_widgets/camera.gd index 16191295..a26a2f8e 100644 --- a/src/ui_parts/camera.gd +++ b/src/ui_widgets/camera.gd @@ -17,15 +17,15 @@ var surface := RenderingServer.canvas_item_create() # Used for drawing the numb func _ready() -> void: RenderingServer.canvas_item_set_parent(surface, ci) - SVG.resized.connect(queue_redraw) - Indications.zoom_changed.connect(change_zoom) - Indications.zoom_changed.connect(queue_redraw) + State.svg_resized.connect(queue_redraw) + State.zoom_changed.connect(change_zoom) + State.zoom_changed.connect(queue_redraw) func exit_tree() -> void: RenderingServer.free_rid(surface) func change_zoom() -> void: - zoom = Indications.zoom + zoom = State.zoom func update() -> void: @@ -37,7 +37,7 @@ func update() -> void: # Don't ask me to explain this. func _draw() -> void: - var grid_size: Vector2 = Indications.viewport_size * 1.0 / zoom + var grid_size: Vector2 = State.viewport_size * 1.0 / zoom RenderingServer.canvas_item_add_line(ci, Vector2(-position.x, 0), Vector2(-position.x, grid_size.y), axis_line_color) RenderingServer.canvas_item_add_line(ci, diff --git a/src/ui_parts/camera.gd.uid b/src/ui_widgets/camera.gd.uid similarity index 100% rename from src/ui_parts/camera.gd.uid rename to src/ui_widgets/camera.gd.uid diff --git a/src/ui_parts/choose_name_dialog.gd b/src/ui_widgets/choose_name_dialog.gd similarity index 100% rename from src/ui_parts/choose_name_dialog.gd rename to src/ui_widgets/choose_name_dialog.gd diff --git a/src/ui_parts/choose_name_dialog.gd.uid b/src/ui_widgets/choose_name_dialog.gd.uid similarity index 100% rename from src/ui_parts/choose_name_dialog.gd.uid rename to src/ui_widgets/choose_name_dialog.gd.uid diff --git a/src/ui_parts/choose_name_dialog.tscn b/src/ui_widgets/choose_name_dialog.tscn similarity index 87% rename from src/ui_parts/choose_name_dialog.tscn rename to src/ui_widgets/choose_name_dialog.tscn index 61840d22..6b6dce2a 100644 --- a/src/ui_parts/choose_name_dialog.tscn +++ b/src/ui_widgets/choose_name_dialog.tscn @@ -1,8 +1,8 @@ [gd_scene load_steps=4 format=3 uid="uid://2vlktxj118su"] -[ext_resource type="Script" path="res://src/ui_parts/choose_name_dialog.gd" id="1_qr08l"] +[ext_resource type="Script" uid="uid://qiuaih0hajks" path="res://src/ui_widgets/choose_name_dialog.gd" id="1_qr08l"] [ext_resource type="FontFile" uid="uid://dc0w4sx0h0fui" path="res://assets/fonts/FontBold.ttf" id="2_h3hxy"] -[ext_resource type="Script" path="res://src/ui_widgets/BetterLineEdit.gd" id="3_q0a2q"] +[ext_resource type="Script" uid="uid://1hox6gd5pxku" path="res://src/ui_widgets/BetterLineEdit.gd" id="3_q0a2q"] [node name="ChooseNameDialog" type="PanelContainer"] anchors_preset = 8 diff --git a/src/ui_parts/code_editor.gd b/src/ui_widgets/code_editor.gd similarity index 85% rename from src/ui_parts/code_editor.gd rename to src/ui_widgets/code_editor.gd index 0c8d02d9..f598c821 100644 --- a/src/ui_parts/code_editor.gd +++ b/src/ui_widgets/code_editor.gd @@ -8,22 +8,19 @@ extends VBoxContainer @onready var file_button: Button = %FileButton @onready var options_button: Button = %MetaActions/OptionsButton -@onready var import_button: Button = %MetaActions/ImportButton -@onready var export_button: Button = %MetaActions/ExportButton func _ready() -> void: Configs.theme_changed.connect(setup_theme) - SVG.parsing_finished.connect(update_error) + State.parsing_finished.connect(update_error) Configs.highlighting_colors_changed.connect(update_syntax_highlighter) update_file_button() setup_theme() update_syntax_highlighter() code_edit.clear_undo_history() - SVG.changed.connect(auto_update_text) - Configs.file_path_changed.connect(update_file_button) + State.svg_changed.connect(auto_update_text) + Configs.active_tab_file_path_changed.connect(update_file_button) + Configs.active_tab_changed.connect(update_file_button) Configs.basic_colors_changed.connect(update_size_button_colors) - import_button.pressed.connect(ShortcutUtils.fn("import")) - export_button.pressed.connect(ShortcutUtils.fn("export")) # Fix the size button sizing. size_button.begin_bulk_theme_override() for theming in ["normal", "hover", "pressed", "disabled"]: @@ -36,7 +33,7 @@ func _ready() -> void: func auto_update_text() -> void: if not code_edit.has_focus(): - code_edit.text = SVG.text + code_edit.text = State.svg_text code_edit.clear_undo_history() update_size_button() @@ -113,10 +110,10 @@ func setup_theme() -> void: func update_size_button() -> void: - var svg_text_size := SVG.text.length() + var svg_text_size := State.svg_text.length() size_button.text = String.humanize_size(svg_text_size) size_button.tooltip_text = String.num_uint64(svg_text_size) + " B" - if SVG.root_element.optimize(true): + if State.root_element.optimize(true): size_button.disabled = false size_button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND update_size_button_colors() @@ -133,23 +130,24 @@ func update_size_button_colors() -> void: size_button.end_bulk_theme_override() func update_file_button() -> void: - var file_path := Configs.savedata.current_file_path - file_button.visible = !file_path.is_empty() - file_button.text = file_path.get_file() - file_button.tooltip_text = file_path.get_file() + var file_name := State.transient_tab_path.get_file() if\ + not State.transient_tab_path.is_empty() else\ + Configs.savedata.get_active_tab().get_presented_name() + file_button.text = file_name + file_button.tooltip_text = file_name Utils.set_max_text_width(file_button, 140.0, 12.0) func _on_svg_code_edit_text_changed() -> void: - SVG.apply_svg_text(code_edit.text, false) + State.apply_svg_text(code_edit.text, false) func _on_svg_code_edit_focus_exited() -> void: - SVG.queue_save() - code_edit.text = SVG.text + State.queue_svg_save() + code_edit.text = State.svg_text update_error(SVGParser.ParseError.OK) func _on_svg_code_edit_focus_entered() -> void: - Indications.clear_all_selections() + State.clear_all_selections() func _on_file_button_pressed() -> void: @@ -158,16 +156,12 @@ func _on_file_button_pressed() -> void: FileUtils.save_svg, false, load("res://assets/icons/Save.svg"), "save")) btn_array.append(ContextPopup.create_button(Translator.translate("Open file"), ShortcutUtils.fn("open_svg"), - not FileAccess.file_exists(Configs.savedata.current_file_path), + not FileAccess.file_exists(Configs.savedata.get_active_tab().svg_file_path), load("res://assets/icons/OpenFile.svg"), "open_svg")) 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("Clear saving path"), - ShortcutUtils.fn("clear_file_path"), false, load("res://assets/icons/Clear.svg"), - "clear_file_path")) var context_popup := ContextPopup.new() context_popup.setup(btn_array, true, file_button.size.x) HandlerGUI.popup_under_rect_center(context_popup, file_button.get_global_rect(), @@ -189,9 +183,6 @@ func _on_options_button_pressed() -> void: 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_button( - Translator.translate("Clear SVG"), ShortcutUtils.fn("clear_svg"), - SVG.text == SVG.DEFAULT, load("res://assets/icons/Clear.svg"), "clear_svg")) 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/code_editor.gd.uid b/src/ui_widgets/code_editor.gd.uid similarity index 100% rename from src/ui_parts/code_editor.gd.uid rename to src/ui_widgets/code_editor.gd.uid diff --git a/src/ui_parts/code_editor.tscn b/src/ui_widgets/code_editor.tscn similarity index 80% rename from src/ui_parts/code_editor.tscn rename to src/ui_widgets/code_editor.tscn index 7b0ded5d..c23238a8 100644 --- a/src/ui_parts/code_editor.tscn +++ b/src/ui_widgets/code_editor.tscn @@ -1,12 +1,10 @@ -[gd_scene load_steps=10 format=3 uid="uid://cr1fdlmbknnko"] +[gd_scene load_steps=8 format=3 uid="uid://cr1fdlmbknnko"] -[ext_resource type="Script" path="res://src/ui_parts/code_editor.gd" id="1_nffk0"] +[ext_resource type="Script" uid="uid://c3q5dvxm6ro1m" path="res://src/ui_widgets/code_editor.gd" id="1_nffk0"] [ext_resource type="FontFile" uid="uid://dc0w4sx0h0fui" path="res://assets/fonts/FontBold.ttf" id="2_hl52o"] [ext_resource type="FontFile" uid="uid://depydd16jq777" path="res://assets/fonts/FontMono.ttf" id="2_p4nol"] -[ext_resource type="Texture2D" uid="uid://6ymbl3jqersp" path="res://assets/icons/Import.svg" id="4_cuhac"] [ext_resource type="Texture2D" uid="uid://dthdjf4v2vlvg" path="res://assets/icons/CodeOptions.svg" id="4_sos04"] -[ext_resource type="Texture2D" uid="uid://d0uvwj0t44n6v" path="res://assets/icons/Export.svg" id="5_pgurh"] -[ext_resource type="Script" path="res://src/ui_widgets/BetterTextEdit.gd" id="8_ser4i"] +[ext_resource type="Script" uid="uid://dh5mir6i27u4u" path="res://src/ui_widgets/BetterTextEdit.gd" id="8_ser4i"] [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_q56qh"] content_margin_left = 8.0 @@ -77,22 +75,6 @@ theme_type_variation = &"IconButton" icon = ExtResource("4_sos04") icon_alignment = 1 -[node name="ImportButton" type="Button" parent="PanelContainer/CodeButtons/MetaActions"] -layout_mode = 2 -tooltip_text = "Import" -focus_mode = 0 -mouse_default_cursor_shape = 2 -theme_type_variation = &"IconButton" -icon = ExtResource("4_cuhac") - -[node name="ExportButton" type="Button" parent="PanelContainer/CodeButtons/MetaActions"] -layout_mode = 2 -tooltip_text = "Export" -focus_mode = 0 -mouse_default_cursor_shape = 2 -theme_type_variation = &"IconButton" -icon = ExtResource("5_pgurh") - [node name="ScriptEditor" type="VBoxContainer" parent="."] layout_mode = 2 size_flags_horizontal = 3 diff --git a/src/ui_widgets/color_field.gd b/src/ui_widgets/color_field.gd index 8e98b5e5..9eddebe1 100644 --- a/src/ui_widgets/color_field.gd +++ b/src/ui_widgets/color_field.gd @@ -30,7 +30,7 @@ func set_value(new_value: String, save := false) -> void: sync(element.get_attribute(attribute_name).format(new_value)) element.set_attribute(attribute_name, new_value) if save: - SVG.queue_save() + State.queue_svg_save() func setup_placeholder() -> void: placeholder_text = element.get_default(attribute_name).trim_prefix("#") @@ -92,7 +92,7 @@ func _on_pressed() -> void: for element_depth in range(0, element.xid.size()): var checked_xid := element.xid.duplicate() checked_xid.resize(element_depth) - if SVG.root_element.get_xnode(checked_xid).has_attribute("color"): + if State.root_element.get_xnode(checked_xid).has_attribute("color"): has_color_attribute_parent = true break color_popup.current_color_availability =\ @@ -114,7 +114,7 @@ func _draw() -> void: var color_value := element.get_attribute_value(attribute_name, false) if cached_allow_url and ColorParser.is_valid_url(color_value): var id := color_value.substr(5, color_value.length() - 6) - var gradient_element := SVG.root_element.get_element_by_id(id) + var gradient_element := State.root_element.get_element_by_id(id) if DB.is_element_gradient(gradient_element): # Complex drawing logic, because StyleBoxTexture isn't advanced enough. var points := PackedVector2Array() @@ -184,7 +184,7 @@ func update_gradient_texture() -> void: var color_value := element.get_attribute_value(attribute_name, false) if ColorParser.is_valid_url(color_value): var id := color_value.substr(5, color_value.length() - 6) - var gradient_element := SVG.root_element.get_element_by_id(id) + var gradient_element := State.root_element.get_element_by_id(id) if DB.is_element_gradient(gradient_element): gradient_texture = gradient_element.generate_texture() else: diff --git a/src/ui_widgets/color_popup.gd b/src/ui_widgets/color_popup.gd index b13f5666..343c5ae9 100644 --- a/src/ui_widgets/color_popup.gd +++ b/src/ui_widgets/color_popup.gd @@ -81,7 +81,7 @@ func update_palettes(search_text := "") -> void: reserved_colors.append("currentColor") reserved_color_names.append("Current color") if show_url: - for element in SVG.root_element.get_all_element_descendants(): + for element in State.root_element.get_all_element_descendants(): if element.has_attribute("id"): if element is ElementLinearGradient: reserved_color_names.append("Linear gradient") diff --git a/src/ui_widgets/color_swatch.gd b/src/ui_widgets/color_swatch.gd index 1480d239..9eb7283b 100644 --- a/src/ui_widgets/color_swatch.gd +++ b/src/ui_widgets/color_swatch.gd @@ -17,7 +17,7 @@ func _ready() -> void: # TODO remove this when #25296 is fixed. if ColorParser.is_valid_url(color): var id := color.substr(5, color.length() - 6) - var gradient_element := SVG.root_element.get_element_by_id(id) + var gradient_element := State.root_element.get_element_by_id(id) if DB.is_element_gradient(gradient_element): gradient_texture = gradient_element.generate_texture() @@ -26,7 +26,7 @@ func _draw() -> void: if ColorParser.is_valid_url(color): checkerboard.draw_rect(ci, inside_rect, false) var id := color.substr(5, color.length() - 6) - var gradient_element := SVG.root_element.get_element_by_id(id) + var gradient_element := State.root_element.get_element_by_id(id) if gradient_element != null: gradient_texture.draw_rect(ci, inside_rect, false) else: diff --git a/src/ui_parts/confirm_dialog.gd b/src/ui_widgets/confirm_dialog.gd similarity index 95% rename from src/ui_parts/confirm_dialog.gd rename to src/ui_widgets/confirm_dialog.gd index f19c6eeb..d53dda98 100644 --- a/src/ui_parts/confirm_dialog.gd +++ b/src/ui_widgets/confirm_dialog.gd @@ -17,4 +17,3 @@ func setup(title: String, message: String, action_text: String, action: Callable action_button.text = action_text action_button.pressed.connect(action) action_button.grab_focus() - label.custom_minimum_size.x = 300.0 diff --git a/src/ui_parts/confirm_dialog.gd.uid b/src/ui_widgets/confirm_dialog.gd.uid similarity index 100% rename from src/ui_parts/confirm_dialog.gd.uid rename to src/ui_widgets/confirm_dialog.gd.uid diff --git a/src/ui_parts/confirm_dialog.tscn b/src/ui_widgets/confirm_dialog.tscn similarity index 87% rename from src/ui_parts/confirm_dialog.tscn rename to src/ui_widgets/confirm_dialog.tscn index d3f0a490..5d8981e9 100644 --- a/src/ui_parts/confirm_dialog.tscn +++ b/src/ui_widgets/confirm_dialog.tscn @@ -1,9 +1,9 @@ [gd_scene load_steps=3 format=3 uid="uid://ywarfvqdho0"] -[ext_resource type="Script" path="res://src/ui_parts/confirm_dialog.gd" id="1_g3djf"] +[ext_resource type="Script" uid="uid://3gwwpcy3jctv" path="res://src/ui_widgets/confirm_dialog.gd" id="1_g3djf"] [ext_resource type="FontFile" uid="uid://dc0w4sx0h0fui" path="res://assets/fonts/FontBold.ttf" id="2_drhgn"] -[node name="AlertDialog" type="PanelContainer"] +[node name="ConfirmDialog" type="PanelContainer"] anchors_preset = 8 anchor_left = 0.5 anchor_top = 0.5 @@ -33,7 +33,7 @@ theme_override_font_sizes/font_size = 16 horizontal_alignment = 1 [node name="Label" type="RichTextLabel" parent="MainContainer/TextContainer"] -custom_minimum_size = Vector2(180, 0) +custom_minimum_size = Vector2(300, 0) layout_mode = 2 theme_override_font_sizes/normal_font_size = 12 fit_content = true diff --git a/src/ui_widgets/element_content_basic_shape.gd b/src/ui_widgets/element_content_basic_shape.gd index a337ef44..e2623b55 100644 --- a/src/ui_widgets/element_content_basic_shape.gd +++ b/src/ui_widgets/element_content_basic_shape.gd @@ -7,5 +7,5 @@ var element: Element func _ready() -> void: for attribute in DB.get_recognized_attributes(element.name): var input_field := AttributeFieldBuilder.create(attribute, element) - input_field.focus_entered.connect(Indications.normal_select.bind(element.xid)) + input_field.focus_entered.connect(State.normal_select.bind(element.xid)) attribute_container.add_child(input_field) diff --git a/src/ui_widgets/element_content_g.gd b/src/ui_widgets/element_content_g.gd index a337ef44..e2623b55 100644 --- a/src/ui_widgets/element_content_g.gd +++ b/src/ui_widgets/element_content_g.gd @@ -7,5 +7,5 @@ var element: Element func _ready() -> void: for attribute in DB.get_recognized_attributes(element.name): var input_field := AttributeFieldBuilder.create(attribute, element) - input_field.focus_entered.connect(Indications.normal_select.bind(element.xid)) + input_field.focus_entered.connect(State.normal_select.bind(element.xid)) attribute_container.add_child(input_field) diff --git a/src/ui_widgets/element_content_linear_gradient.gd b/src/ui_widgets/element_content_linear_gradient.gd index b7c0a25c..2fc69195 100644 --- a/src/ui_widgets/element_content_linear_gradient.gd +++ b/src/ui_widgets/element_content_linear_gradient.gd @@ -7,5 +7,5 @@ var element: Element func _ready() -> void: for attribute in DB.get_recognized_attributes("linearGradient"): var input_field := AttributeFieldBuilder.create(attribute, element) - input_field.focus_entered.connect(Indications.normal_select.bind(element.xid)) + input_field.focus_entered.connect(State.normal_select.bind(element.xid)) attribute_container.add_child(input_field) diff --git a/src/ui_widgets/element_content_path.gd b/src/ui_widgets/element_content_path.gd index 0eb460c4..0bc2eeb9 100644 --- a/src/ui_widgets/element_content_path.gd +++ b/src/ui_widgets/element_content_path.gd @@ -8,12 +8,12 @@ var element: Element func _ready() -> void: path_field.element = element path_field.setup() - path_field.focused.connect(Indications.normal_select.bind(element.xid)) + path_field.focused.connect(State.normal_select.bind(element.xid)) for attribute in DB.get_recognized_attributes("path"): if attribute == "d": continue var input_field := AttributeFieldBuilder.create(attribute, element) # Focused signal for pathdata attribute. - input_field.focus_entered.connect(Indications.normal_select.bind(element.xid)) + input_field.focus_entered.connect(State.normal_select.bind(element.xid)) attribute_container.add_child(input_field) diff --git a/src/ui_widgets/element_content_polyshape.gd b/src/ui_widgets/element_content_polyshape.gd index e4933f00..b88fc574 100644 --- a/src/ui_widgets/element_content_polyshape.gd +++ b/src/ui_widgets/element_content_polyshape.gd @@ -8,12 +8,12 @@ var element: Element func _ready() -> void: points_field.element = element points_field.setup() - points_field.focused.connect(Indications.normal_select.bind(element.xid)) + points_field.focused.connect(State.normal_select.bind(element.xid)) for attribute in DB.get_recognized_attributes(element.name): if attribute == "points": continue var input_field := AttributeFieldBuilder.create(attribute, element) # Focused signal for pathdata attribute. - input_field.focus_entered.connect(Indications.normal_select.bind(element.xid)) + input_field.focus_entered.connect(State.normal_select.bind(element.xid)) attribute_container.add_child(input_field) diff --git a/src/ui_widgets/element_content_radial_gradient.gd b/src/ui_widgets/element_content_radial_gradient.gd index 7f5ebab5..7a20459a 100644 --- a/src/ui_widgets/element_content_radial_gradient.gd +++ b/src/ui_widgets/element_content_radial_gradient.gd @@ -7,5 +7,5 @@ var element: Element func _ready() -> void: for attribute in DB.get_recognized_attributes("radialGradient"): var input_field := AttributeFieldBuilder.create(attribute, element) - input_field.focus_entered.connect(Indications.normal_select.bind(element.xid)) + input_field.focus_entered.connect(State.normal_select.bind(element.xid)) attribute_container.add_child(input_field) diff --git a/src/ui_widgets/element_frame.gd b/src/ui_widgets/element_frame.gd index 619934f8..4b8b3f75 100644 --- a/src/ui_widgets/element_frame.gd +++ b/src/ui_widgets/element_frame.gd @@ -32,9 +32,9 @@ var suppress_drag: bool = false func _ready() -> void: RenderingServer.canvas_item_set_parent(surface, get_canvas_item()) RenderingServer.canvas_item_set_z_index(surface, 1) - Indications.selection_changed.connect(determine_selection_highlight) - Indications.hover_changed.connect(determine_selection_highlight) - Indications.proposed_drop_changed.connect(queue_redraw) + State.selection_changed.connect(determine_selection_highlight) + State.hover_changed.connect(determine_selection_highlight) + State.proposed_drop_changed.connect(queue_redraw) title_bar.draw.connect(_on_title_bar_draw) mouse_entered.connect(_on_mouse_entered) mouse_exited.connect(_on_mouse_exited) @@ -78,11 +78,11 @@ func _exit_tree() -> void: # Logic for dragging. func _get_drag_data(_at_position: Vector2) -> Variant: - if suppress_drag or Indications.selected_xids.is_empty(): + if suppress_drag or State.selected_xids.is_empty(): return null var data: Array[PackedInt32Array] = XIDUtils.filter_descendants( - Indications.selected_xids.duplicate(true)) + State.selected_xids.duplicate(true)) set_drag_preview(XNodeChildrenBuilder.generate_drag_preview(data)) return data @@ -94,41 +94,41 @@ func _notification(what: int) -> void: func _on_title_button_pressed() -> void: # Update the selection immediately, since if this element editor is # in a multi-selection, only the mouse button release would change the selection. - Indications.normal_select(element.xid) + State.normal_select(element.xid) var viewport := get_viewport() var rect := title_bar.get_global_rect() - HandlerGUI.popup_under_rect_center(Indications.get_selection_context( - HandlerGUI.popup_under_rect_center.bind(rect, viewport), - Indications.Context.LIST), rect, viewport) + HandlerGUI.popup_under_rect_center(State.get_selection_context( + HandlerGUI.popup_under_rect_center.bind(rect, viewport), State.Context.LIST), + rect, viewport) func _gui_input(event: InputEvent) -> void: if event is InputEventMouseMotion and event.button_mask == 0: - if Indications.semi_hovered_xid != element.xid and\ - not XIDUtils.is_parent(element.xid, Indications.hovered_xid): - Indications.set_hovered(element.xid) + if State.semi_hovered_xid != element.xid and\ + not XIDUtils.is_parent(element.xid, State.hovered_xid): + State.set_hovered(element.xid) elif event is InputEventMouseButton: if event.button_index == MOUSE_BUTTON_LEFT: if event.is_pressed(): if event.shift_pressed: - Indications.shift_select(element.xid) + State.shift_select(element.xid) elif event.is_command_or_control_pressed(): - Indications.ctrl_select(element.xid) - elif not element.xid in Indications.selected_xids: - Indications.normal_select(element.xid) + State.ctrl_select(element.xid) + elif not element.xid in State.selected_xids: + State.normal_select(element.xid) elif event.is_released() and not event.shift_pressed and\ not event.is_command_or_control_pressed() and\ - Indications.selected_xids.size() > 1 and element.xid in Indications.selected_xids: - Indications.normal_select(element.xid) + State.selected_xids.size() > 1 and element.xid in State.selected_xids: + State.normal_select(element.xid) accept_event() elif event.button_index == MOUSE_BUTTON_RIGHT and event.is_pressed(): - if not element.xid in Indications.selected_xids: - Indications.normal_select(element.xid) + if not element.xid in State.selected_xids: + State.normal_select(element.xid) var viewport := get_viewport() var popup_pos := viewport.get_mouse_position() - HandlerGUI.popup_under_pos(Indications.get_selection_context( - HandlerGUI.popup_under_pos.bind(popup_pos, viewport), - Indications.Context.LIST), popup_pos, viewport) + HandlerGUI.popup_under_pos(State.get_selection_context( + HandlerGUI.popup_under_pos.bind(popup_pos, viewport), State.Context.LIST), + popup_pos, viewport) accept_event() func _on_mouse_entered() -> void: @@ -162,13 +162,13 @@ func _on_mouse_entered() -> void: func _on_mouse_exited() -> void: suppress_drag = false - Indications.remove_hovered(element.xid) + State.remove_hovered(element.xid) determine_selection_highlight() func determine_selection_highlight() -> void: - var is_selected := element.xid in Indications.selected_xids - var is_hovered := Indications.hovered_xid == element.xid + var is_selected := element.xid in State.selected_xids + var is_hovered := State.hovered_xid == element.xid if is_selected: if is_hovered: @@ -199,22 +199,22 @@ func _draw() -> void: RenderingServer.canvas_item_clear(surface) # There's only stuff to draw if there are drag-and-drop actions. - if Indications.proposed_drop_xid.is_empty(): + if State.proposed_drop_xid.is_empty(): return - for selected_xid in Indications.selected_xids: + for selected_xid in State.selected_xids: if XIDUtils.is_parent_or_self(selected_xid, element.xid): return var parent_xid := XIDUtils.get_parent_xid(element.xid) # Draw the indicator of drag and drop actions. var drop_sb := StyleBoxFlat.new() - var drop_xid := Indications.proposed_drop_xid + var drop_xid := State.proposed_drop_xid var root_element := element.root var drop_tag := root_element.get_xnode(XIDUtils.get_parent_xid(drop_xid)) var are_all_children_valid := true - for xid in Indications.selected_xids: + for xid in State.selected_xids: var xnode := root_element.get_xnode(xid) if xnode.is_element() and !DB.is_child_element_valid(drop_tag.name, xnode.name): are_all_children_valid = false diff --git a/src/ui_widgets/enum_field.gd b/src/ui_widgets/enum_field.gd index 613093ff..135aa843 100644 --- a/src/ui_widgets/enum_field.gd +++ b/src/ui_widgets/enum_field.gd @@ -10,7 +10,7 @@ func set_value(new_value: String, save := false) -> void: sync(new_value) element.set_attribute(attribute_name, new_value) if save: - SVG.queue_save() + State.queue_svg_save() func sync_to_attribute() -> void: set_value(element.get_attribute_value(attribute_name, true)) diff --git a/src/ui_widgets/id_field.gd b/src/ui_widgets/id_field.gd index be9058c0..ad01b2c2 100644 --- a/src/ui_widgets/id_field.gd +++ b/src/ui_widgets/id_field.gd @@ -8,7 +8,7 @@ func set_value(new_value: String, save := false) -> void: sync(new_value) element.set_attribute(attribute_name, new_value) if save: - SVG.queue_save() + State.queue_svg_save() func _ready() -> void: diff --git a/src/ui_widgets/number_field.gd b/src/ui_widgets/number_field.gd index 2a0b68cf..e31193ad 100644 --- a/src/ui_widgets/number_field.gd +++ b/src/ui_widgets/number_field.gd @@ -30,7 +30,7 @@ func set_value(new_value: String, save := false) -> void: sync(new_value) element.set_attribute(attribute_name, new_value) if save: - SVG.queue_save() + State.queue_svg_save() func setup_placeholder() -> void: placeholder_text = element.get_default(attribute_name) diff --git a/src/ui_widgets/number_field_with_slider.gd b/src/ui_widgets/number_field_with_slider.gd index ae105c73..1ab952e6 100644 --- a/src/ui_widgets/number_field_with_slider.gd +++ b/src/ui_widgets/number_field_with_slider.gd @@ -28,7 +28,7 @@ func set_value(new_value: String, save := false) -> void: sync(new_value) element.set_attribute(attribute_name, new_value) if save: - SVG.queue_save() + State.queue_svg_save() func set_num(new_number: float, save := false) -> void: set_value(element.get_attribute(attribute_name).num_to_text(new_number), save) diff --git a/src/ui_widgets/options_dialog.gd b/src/ui_widgets/options_dialog.gd new file mode 100644 index 00000000..b5918255 --- /dev/null +++ b/src/ui_widgets/options_dialog.gd @@ -0,0 +1,20 @@ +extends PanelContainer + +@onready var title_label: Label = $MainContainer/TextContainer/Title +@onready var label: RichTextLabel = $MainContainer/TextContainer/Label +@onready var options_container: HBoxContainer = $MainContainer/OptionsContainer + +func setup(title: String, message: String) -> void: + label.text = message + title_label.text = title + +func add_option(action_text: String, action: Callable, focused := false) -> void: + var button := Button.new() + button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND + button.text = action_text + button.size_flags_horizontal = Control.SIZE_EXPAND | Control.SIZE_SHRINK_CENTER + button.pressed.connect(action) + button.pressed.connect(queue_free) + options_container.add_child(button) + if focused: + button.grab_focus() diff --git a/src/ui_widgets/options_dialog.gd.uid b/src/ui_widgets/options_dialog.gd.uid new file mode 100644 index 00000000..2335c656 --- /dev/null +++ b/src/ui_widgets/options_dialog.gd.uid @@ -0,0 +1 @@ +uid://vjqyfycqgf8h diff --git a/src/ui_widgets/options_dialog.tscn b/src/ui_widgets/options_dialog.tscn new file mode 100644 index 00000000..b76eb4d3 --- /dev/null +++ b/src/ui_widgets/options_dialog.tscn @@ -0,0 +1,43 @@ +[gd_scene load_steps=3 format=3 uid="uid://rsf6f7pytv7u"] + +[ext_resource type="Script" uid="uid://vjqyfycqgf8h" path="res://src/ui_widgets/options_dialog.gd" id="1_shf74"] +[ext_resource type="FontFile" uid="uid://dc0w4sx0h0fui" path="res://assets/fonts/FontBold.ttf" id="2_it3qh"] + +[node name="OptionsDialog" type="PanelContainer"] +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -2.0 +offset_top = -2.0 +offset_right = 2.0 +offset_bottom = 2.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_type_variation = &"OverlayPanel" +script = ExtResource("1_shf74") + +[node name="MainContainer" type="VBoxContainer" parent="."] +layout_mode = 2 +theme_override_constants/separation = 12 + +[node name="TextContainer" type="VBoxContainer" parent="MainContainer"] +layout_mode = 2 +theme_override_constants/separation = 8 + +[node name="Title" type="Label" parent="MainContainer/TextContainer"] +layout_mode = 2 +theme_override_fonts/font = ExtResource("2_it3qh") +theme_override_font_sizes/font_size = 16 +horizontal_alignment = 1 + +[node name="Label" type="RichTextLabel" parent="MainContainer/TextContainer"] +custom_minimum_size = Vector2(300, 0) +layout_mode = 2 +theme_override_font_sizes/normal_font_size = 12 +fit_content = true + +[node name="OptionsContainer" type="HBoxContainer" parent="MainContainer"] +layout_mode = 2 +alignment = 1 diff --git a/src/ui_widgets/path_popup.gd b/src/ui_widgets/path_popup.gd index cf8c4d7f..b5fc4667 100644 --- a/src/ui_widgets/path_popup.gd +++ b/src/ui_widgets/path_popup.gd @@ -9,6 +9,7 @@ signal path_command_picked(new_command: String) @onready var top_margin: MarginContainer = $VBoxContainer/MarginContainer func _ready() -> void: + relative_toggle.text = Translator.translate("Relative") relative_toggle.toggled.connect(_on_relative_toggle_toggled) relative_toggle.button_pressed = Configs.savedata.path_command_relative for command_button in command_container.get_children(): diff --git a/src/ui_widgets/path_popup.tscn b/src/ui_widgets/path_popup.tscn index 2313fc07..2feb9fa2 100644 --- a/src/ui_widgets/path_popup.tscn +++ b/src/ui_widgets/path_popup.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=3 format=3 uid="uid://bvnheiqqay5ke"] -[ext_resource type="Script" path="res://src/ui_widgets/path_popup.gd" id="1_j10aq"] +[ext_resource type="Script" uid="uid://l4ongcnemxuq" path="res://src/ui_widgets/path_popup.gd" id="1_j10aq"] [ext_resource type="PackedScene" uid="uid://co2btefrqrm0e" path="res://src/ui_widgets/path_command_button.tscn" id="2_1jd8y"] [node name="PathCommandPopup" type="PanelContainer"] @@ -28,7 +28,6 @@ layout_mode = 2 size_flags_horizontal = 8 focus_mode = 0 mouse_default_cursor_shape = 2 -text = "Relative" flat = true alignment = 2 diff --git a/src/ui_widgets/pathdata_field.gd b/src/ui_widgets/pathdata_field.gd index 4f615379..6b0136c1 100644 --- a/src/ui_widgets/pathdata_field.gd +++ b/src/ui_widgets/pathdata_field.gd @@ -54,7 +54,7 @@ func set_value(new_value: String, save := false) -> void: element.set_attribute(attribute_name, new_value) sync(element.get_attribute_value(attribute_name, true)) if save: - SVG.queue_save() + State.queue_svg_save() func sync_to_attribute() -> void: set_value(element.get_attribute_value(attribute_name, true)) @@ -73,8 +73,8 @@ func setup() -> void: commands_container.draw.connect(_commands_draw) commands_container.gui_input.connect(_on_commands_gui_input) commands_container.mouse_exited.connect(_on_commands_mouse_exited) - Indications.hover_changed.connect(_on_selections_or_hover_changed) - Indications.selection_changed.connect(_on_selections_or_hover_changed) + State.hover_changed.connect(_on_selections_or_hover_changed) + State.selection_changed.connect(_on_selections_or_hover_changed) # So, the reason we need this is quite complicated. We need to know # the current_selections and current_hovered at the time this widget is created. # This is because the widget can sometimes be created before they are cleared @@ -127,26 +127,26 @@ func sync(new_value: String) -> void: func update_parameter(new_value: float, property: String, idx: int) -> void: element.get_attribute(attribute_name).set_command_property(idx, property, new_value) - SVG.queue_save() + State.queue_svg_save() func _on_relative_button_pressed() -> void: element.get_attribute(attribute_name).toggle_relative_command(hovered_idx) - SVG.queue_save() + State.queue_svg_save() func _on_add_move_button_pressed() -> void: element.get_attribute(attribute_name).insert_command(0, "M") - SVG.queue_save() + State.queue_svg_save() # Path commands editor orchestration. func _on_selections_or_hover_changed() -> void: var new_selections: Array[int] = [] - if Indications.semi_selected_xid == element.xid: - new_selections = Indications.inner_selections.duplicate() + if State.semi_selected_xid == element.xid: + new_selections = State.inner_selections.duplicate() var new_hovered := -1 - if Indications.semi_hovered_xid == element.xid: - new_hovered = Indications.inner_hovered + if State.semi_hovered_xid == element.xid: + new_hovered = State.inner_hovered # Only redraw if selections or hovered changed. if new_selections != current_selections: current_selections = new_selections @@ -156,10 +156,10 @@ func _on_selections_or_hover_changed() -> void: commands_container.queue_redraw() func _on_commands_mouse_exited() -> void: - var cmd_idx := Indications.inner_hovered - if Indications.semi_hovered_xid == element.xid: + var cmd_idx := State.inner_hovered + if State.semi_hovered_xid == element.xid: activate_hovered(-1) - Indications.remove_hovered(element.xid, cmd_idx) + State.remove_hovered(element.xid, cmd_idx) # Prevents buttons from selecting a whole subpath when double-clicked. @@ -183,9 +183,9 @@ func _on_commands_gui_input(event: InputEvent) -> void: if event is InputEventMouseMotion and event.button_mask == 0: if cmd_idx >= 0: - Indications.set_hovered(element.xid, cmd_idx) + State.set_hovered(element.xid, cmd_idx) else: - Indications.remove_hovered(element.xid, cmd_idx) + State.remove_hovered(element.xid, cmd_idx) activate_hovered(cmd_idx) elif event is InputEventMouseButton: if event.button_index == MOUSE_BUTTON_LEFT: @@ -193,29 +193,28 @@ func _on_commands_gui_input(event: InputEvent) -> void: if event.double_click: var subpath_range: Vector2i =\ element.get_attribute(attribute_name).get_subpath(cmd_idx) - Indications.normal_select(element.xid, subpath_range.x) - Indications.shift_select(element.xid, subpath_range.y) + State.normal_select(element.xid, subpath_range.x) + State.shift_select(element.xid, subpath_range.y) elif event.is_command_or_control_pressed(): - Indications.ctrl_select(element.xid, cmd_idx) + State.ctrl_select(element.xid, cmd_idx) elif event.shift_pressed: - Indications.shift_select(element.xid, cmd_idx) + State.shift_select(element.xid, cmd_idx) else: - Indications.normal_select(element.xid, cmd_idx) + State.normal_select(element.xid, cmd_idx) elif event.is_released() and not event.shift_pressed and\ not event.is_command_or_control_pressed() and not event.double_click and\ - Indications.inner_selections.size() > 1 and\ - cmd_idx in Indications.inner_selections: - Indications.normal_select(element.xid, cmd_idx) + State.inner_selections.size() > 1 and cmd_idx in State.inner_selections: + State.normal_select(element.xid, cmd_idx) elif event.button_index == MOUSE_BUTTON_RIGHT and event.is_pressed(): - if Indications.semi_selected_xid != element.xid or\ - not cmd_idx in Indications.inner_selections: - Indications.normal_select(element.xid, cmd_idx) + if State.semi_selected_xid != element.xid or\ + not cmd_idx in State.inner_selections: + State.normal_select(element.xid, cmd_idx) # Popup the actions. var viewport := get_viewport() var popup_pos := viewport.get_mouse_position() - HandlerGUI.popup_under_pos(Indications.get_selection_context( - HandlerGUI.popup_under_pos.bind(popup_pos, viewport), - Indications.Context.LIST), popup_pos, viewport) + HandlerGUI.popup_under_pos(State.get_selection_context( + HandlerGUI.popup_under_pos.bind(popup_pos, viewport), State.Context.LIST), + popup_pos, viewport) func _commands_draw() -> void: @@ -223,8 +222,8 @@ func _commands_draw() -> void: for i: int in element.get_attribute(attribute_name).get_command_count(): var v_offset := STRIP_HEIGHT * i # Draw the background hover or selection stylebox. - var hovered := Indications.is_hovered(element.xid, i) - var selected := Indications.is_selected(element.xid, i) + var hovered := State.is_hovered(element.xid, i) + var selected := State.is_selected(element.xid, i) if selected or hovered: var stylebox := StyleBoxFlat.new() stylebox.set_corner_radius_all(3) @@ -472,16 +471,16 @@ func setup_path_command_controls(idx: int) -> Control: func numfield(cmd_idx: int) -> BetterLineEdit: var new_field := MiniNumberField.instantiate() - new_field.focus_entered.connect(Indications.normal_select.bind(element.xid, cmd_idx)) + new_field.focus_entered.connect(State.normal_select.bind(element.xid, cmd_idx)) return new_field func _on_action_button_pressed(action_button_ref: Button) -> void: # Update the selection immediately, since if this path command is # in a multi-selection, only the mouse button release would change the selection. - Indications.normal_select(element.xid, hovered_idx) + State.normal_select(element.xid, hovered_idx) var viewport := get_viewport() var action_button_rect := action_button_ref.get_global_rect() - HandlerGUI.popup_under_rect_center(Indications.get_selection_context( + HandlerGUI.popup_under_rect_center(State.get_selection_context( HandlerGUI.popup_under_rect_center.bind(action_button_rect, viewport), - Indications.Context.LIST), action_button_rect, viewport) + State.Context.LIST), action_button_rect, viewport) diff --git a/src/ui_widgets/points_field.gd b/src/ui_widgets/points_field.gd index 4042867f..82e363df 100644 --- a/src/ui_widgets/points_field.gd +++ b/src/ui_widgets/points_field.gd @@ -46,7 +46,7 @@ func set_value(new_value: String, save := false) -> void: element.set_attribute(attribute_name, new_value) sync(element.get_attribute_value(attribute_name, true)) if save: - SVG.queue_save() + State.queue_svg_save() func sync_to_attribute() -> void: set_value(element.get_attribute_value(attribute_name)) @@ -64,8 +64,8 @@ func setup() -> void: points_container.draw.connect(points_draw) points_container.gui_input.connect(_on_points_gui_input) points_container.mouse_exited.connect(_on_points_mouse_exited) - Indications.hover_changed.connect(_on_selections_or_hover_changed) - Indications.selection_changed.connect(_on_selections_or_hover_changed) + State.hover_changed.connect(_on_selections_or_hover_changed) + State.selection_changed.connect(_on_selections_or_hover_changed) # So, the reason we need this is quite complicated. We need to know # the current_selections and current_hovered at the time this widget is created. # This is because the widget can sometimes be created before they are cleared @@ -120,28 +120,28 @@ func update_point_x_coordinate(new_value: float, idx: int) -> void: var list := element.get_attribute_list(attribute_name) list[idx * 2] = new_value element.get_attribute(attribute_name).set_list(list) - SVG.queue_save() + State.queue_svg_save() func update_point_y_coordinate(new_value: float, idx: int) -> void: var list := element.get_attribute_list(attribute_name) list[idx * 2 + 1] = new_value element.get_attribute(attribute_name).set_list(list) - SVG.queue_save() + State.queue_svg_save() func _on_add_move_button_pressed() -> void: element.get_attribute(attribute_name).set_list(PackedFloat64Array([0.0, 0.0])) - SVG.queue_save() + State.queue_svg_save() # Points editor orchestration. func _on_selections_or_hover_changed() -> void: var new_selections: Array[int] = [] - if Indications.semi_selected_xid == element.xid: - new_selections = Indications.inner_selections.duplicate() + if State.semi_selected_xid == element.xid: + new_selections = State.inner_selections.duplicate() var new_hovered := -1 - if Indications.semi_hovered_xid == element.xid: - new_hovered = Indications.inner_hovered + if State.semi_hovered_xid == element.xid: + new_hovered = State.inner_hovered # Only redraw if selections or hovered changed. if new_selections != current_selections: current_selections = new_selections @@ -151,10 +151,10 @@ func _on_selections_or_hover_changed() -> void: points_container.queue_redraw() func _on_points_mouse_exited() -> void: - var cmd_idx := Indications.inner_hovered - if Indications.semi_hovered_xid == element.xid: + var cmd_idx := State.inner_hovered + if State.semi_hovered_xid == element.xid: activate_hovered(-1) - Indications.remove_hovered(element.xid, cmd_idx) + State.remove_hovered(element.xid, cmd_idx) # Prevents buttons from selecting a whole subpath when double-clicked. @@ -178,38 +178,37 @@ func _on_points_gui_input(event: InputEvent) -> void: if event is InputEventMouseMotion and event.button_mask == 0: if cmd_idx >= 0: - Indications.set_hovered(element.xid, cmd_idx) + State.set_hovered(element.xid, cmd_idx) else: - Indications.remove_hovered(element.xid, cmd_idx) + State.remove_hovered(element.xid, cmd_idx) activate_hovered(cmd_idx) elif event is InputEventMouseButton: if event.button_index == MOUSE_BUTTON_LEFT: if event.is_pressed(): if event.double_click: - Indications.normal_select(element.xid, 0) - Indications.shift_select(element.xid, + State.normal_select(element.xid, 0) + State.shift_select(element.xid, element.get_attribute(attribute_name).get_list_size() / 2) elif event.is_command_or_control_pressed(): - Indications.ctrl_select(element.xid, cmd_idx) + State.ctrl_select(element.xid, cmd_idx) elif event.shift_pressed: - Indications.shift_select(element.xid, cmd_idx) + State.shift_select(element.xid, cmd_idx) else: - Indications.normal_select(element.xid, cmd_idx) + State.normal_select(element.xid, cmd_idx) elif event.is_released() and not event.shift_pressed and\ not event.is_command_or_control_pressed() and not event.double_click and\ - Indications.inner_selections.size() > 1 and\ - cmd_idx in Indications.inner_selections: - Indications.normal_select(element.xid, cmd_idx) + State.inner_selections.size() > 1 and cmd_idx in State.inner_selections: + State.normal_select(element.xid, cmd_idx) elif event.button_index == MOUSE_BUTTON_RIGHT and event.is_pressed(): - if Indications.semi_selected_xid != element.xid or\ - not cmd_idx in Indications.inner_selections: - Indications.normal_select(element.xid, cmd_idx) + if State.semi_selected_xid != element.xid or\ + not cmd_idx in State.inner_selections: + State.normal_select(element.xid, cmd_idx) # Popup the actions. var viewport := get_viewport() var popup_pos := viewport.get_mouse_position() - HandlerGUI.popup_under_pos(Indications.get_selection_context( - HandlerGUI.popup_under_pos.bind(popup_pos, viewport), - Indications.Context.LIST), popup_pos, viewport) + HandlerGUI.popup_under_pos(State.get_selection_context( + HandlerGUI.popup_under_pos.bind(popup_pos, viewport), State.Context.LIST), + popup_pos, viewport) func points_draw() -> void: @@ -217,8 +216,8 @@ func points_draw() -> void: for i: int in element.get_attribute(attribute_name).get_list_size() / 2: var v_offset := STRIP_HEIGHT * i # Draw the background hover or selection stylebox. - var hovered := Indications.is_hovered(element.xid, i) - var selected := Indications.is_selected(element.xid, i) + var hovered := State.is_hovered(element.xid, i) + var selected := State.is_selected(element.xid, i) if selected or hovered: var stylebox := StyleBoxFlat.new() stylebox.set_corner_radius_all(3) @@ -342,16 +341,16 @@ func setup_point_controls(idx: int) -> Control: func numfield(cmd_idx: int) -> BetterLineEdit: var new_field := MiniNumberField.instantiate() - new_field.focus_entered.connect(Indications.normal_select.bind(element.xid, cmd_idx)) + new_field.focus_entered.connect(State.normal_select.bind(element.xid, cmd_idx)) return new_field func _on_action_button_pressed(action_button_ref: Button) -> void: # Update the selection immediately, since if this point is # in a multi-selection, only the mouse button release would change the selection. - Indications.normal_select(element.xid, hovered_idx) + State.normal_select(element.xid, hovered_idx) var viewport := get_viewport() var action_button_rect := action_button_ref.get_global_rect() - HandlerGUI.popup_under_rect_center(Indications.get_selection_context( + HandlerGUI.popup_under_rect_center(State.get_selection_context( HandlerGUI.popup_under_rect_center.bind(action_button_rect, viewport), - Indications.Context.LIST), action_button_rect, viewport) + State.Context.LIST), action_button_rect, viewport) diff --git a/src/ui_widgets/preview_rect.gd b/src/ui_widgets/preview_rect.gd index 6b405382..4582eacf 100644 --- a/src/ui_widgets/preview_rect.gd +++ b/src/ui_widgets/preview_rect.gd @@ -5,6 +5,9 @@ const MAX_IMAGE_DIMENSION = 512 @onready var checkerboard: TextureRect = $Checkerboard @onready var texture_preview: TextureRect = $Checkerboard/TexturePreview +func setup_svg_without_dimensions(svg_text: String) -> void: + setup_svg(svg_text, SVGParser.text_to_root(svg_text, Formatter.new()).svg.get_size()) + func setup_svg(svg_text: String, dimensions: Vector2) -> void: var scaling_factor := size.x / maxf(dimensions.x, dimensions.y) var img := Image.new() @@ -22,7 +25,7 @@ func setup_image(config: ImageExportData, full_scale := false) -> void: final_image_config.format = config.format final_image_config.lossy = config.lossy final_image_config.quality = config.quality - var svg_size := SVG.root_element.get_size() + var svg_size := State.root_element.get_size() final_image_config.upscale_amount = minf(config.upscale_amount, MAX_IMAGE_DIMENSION / maxf(svg_size.x, svg_size.y)) diff --git a/src/ui_widgets/transform_field.gd b/src/ui_widgets/transform_field.gd index 5d0d14db..b1ed969f 100644 --- a/src/ui_widgets/transform_field.gd +++ b/src/ui_widgets/transform_field.gd @@ -10,7 +10,7 @@ func set_value(new_value: String, save := false) -> void: element.set_attribute(attribute_name, new_value) sync(element.get_attribute_value(attribute_name, true)) if save: - SVG.queue_save() + State.queue_svg_save() func _ready() -> void: diff --git a/src/ui_widgets/transform_popup.gd b/src/ui_widgets/transform_popup.gd index 9defe1b2..1f1cf35d 100644 --- a/src/ui_widgets/transform_popup.gd +++ b/src/ui_widgets/transform_popup.gd @@ -16,7 +16,7 @@ const _icons_dict: Dictionary[String, Texture2D] = { } var attribute_ref: AttributeTransformList -var UR := UndoRedo.new() +var undo_redo := UndoRedo.new() @onready var x1_edit: NumberEditType = %FinalMatrix/X1 @onready var x2_edit: NumberEditType = %FinalMatrix/X2 @@ -36,8 +36,8 @@ func _ready() -> void: update_translation() func _exit_tree() -> void: - SVG.queue_save() - UR.free() + State.queue_svg_save() + undo_redo.free() func update_translation() -> void: apply_matrix.tooltip_text = Translator.translate("Apply the matrix") @@ -105,40 +105,40 @@ func create_mini_number_field(idx: int, property: String) -> BetterLineEdit: func update_value(new_value: float, idx: int, property: String) -> void: - UR.create_action("") - UR.add_do_method(attribute_ref.set_transform_property.bind(idx, property, new_value)) - UR.add_do_method(rebuild) - UR.add_undo_method(attribute_ref.set_transform_list.bind(get_transform_list())) - UR.add_undo_method(rebuild) - UR.commit_action() + undo_redo.create_action("") + undo_redo.add_do_method(attribute_ref.set_transform_property.bind(idx, property, new_value)) + undo_redo.add_do_method(rebuild) + undo_redo.add_undo_method(attribute_ref.set_transform_list.bind(get_transform_list())) + undo_redo.add_undo_method(rebuild) + undo_redo.commit_action() func insert_transform(idx: int, transform_type: String) -> void: - UR.create_action("") - UR.add_do_method(attribute_ref.insert_transform.bind(idx, transform_type)) - UR.add_do_method(rebuild) - UR.add_undo_method(attribute_ref.set_transform_list.bind(get_transform_list())) - UR.add_undo_method(rebuild) - UR.commit_action() + undo_redo.create_action("") + undo_redo.add_do_method(attribute_ref.insert_transform.bind(idx, transform_type)) + undo_redo.add_do_method(rebuild) + undo_redo.add_undo_method(attribute_ref.set_transform_list.bind(get_transform_list())) + undo_redo.add_undo_method(rebuild) + undo_redo.commit_action() func delete_transform(idx: int) -> void: - UR.create_action("") - UR.add_do_method(attribute_ref.delete_transform.bind(idx)) - UR.add_do_method(rebuild) - UR.add_undo_method(attribute_ref.set_transform_list.bind(get_transform_list())) - UR.add_undo_method(rebuild) - UR.commit_action() + undo_redo.create_action("") + undo_redo.add_do_method(attribute_ref.delete_transform.bind(idx)) + undo_redo.add_do_method(rebuild) + undo_redo.add_undo_method(attribute_ref.set_transform_list.bind(get_transform_list())) + undo_redo.add_undo_method(rebuild) + undo_redo.commit_action() func _on_apply_matrix_pressed() -> void: var final_transform := attribute_ref.get_final_precise_transform() - UR.create_action("") - UR.add_do_method(attribute_ref.set_transform_list.bind([ + undo_redo.create_action("") + undo_redo.add_do_method(attribute_ref.set_transform_list.bind([ Transform.TransformMatrix.new(final_transform[0], final_transform[1], final_transform[2], final_transform[3], final_transform[4], final_transform[5])] as Array[Transform])) - UR.add_do_method(rebuild) - UR.add_undo_method(attribute_ref.set_transform_list.bind(get_transform_list())) - UR.add_undo_method(rebuild) - UR.commit_action() + undo_redo.add_do_method(rebuild) + undo_redo.add_undo_method(attribute_ref.set_transform_list.bind(get_transform_list())) + undo_redo.add_undo_method(rebuild) + undo_redo.commit_action() func update_final_transform() -> void: var final_transform := attribute_ref.get_final_precise_transform() @@ -183,11 +183,11 @@ func popup_new_transform_context(idx: int, control: Control) -> void: func _unhandled_input(event: InputEvent) -> void: if ShortcutUtils.is_action_pressed(event, "redo"): - if UR.has_redo(): - UR.redo() + if undo_redo.has_redo(): + undo_redo.redo() elif ShortcutUtils.is_action_pressed(event, "undo"): - if UR.has_undo(): - UR.undo() + if undo_redo.has_undo(): + undo_redo.undo() # So I have to rebuild this in its entirety to keep the references safe or something... diff --git a/src/ui_widgets/unrecognized_field.gd b/src/ui_widgets/unrecognized_field.gd index cb892e3f..f1240121 100644 --- a/src/ui_widgets/unrecognized_field.gd +++ b/src/ui_widgets/unrecognized_field.gd @@ -8,7 +8,7 @@ func set_value(new_value: String, save := false) -> void: sync(new_value) element.set_attribute(attribute_name, new_value) if save: - SVG.queue_save() + State.queue_svg_save() func sync_to_attribute() -> void: set_value(element.get_attribute_value(attribute_name, true)) diff --git a/src/utils/FileUtils.gd b/src/utils/FileUtils.gd index 4ffd2701..c944308b 100644 --- a/src/utils/FileUtils.gd +++ b/src/utils/FileUtils.gd @@ -5,19 +5,25 @@ enum FileState {SAME, DIFFERENT, DOES_NOT_EXIST} const GoodFileDialogType = preload("res://src/ui_parts/good_file_dialog.gd") -const AlertDialog = preload("res://src/ui_parts/alert_dialog.tscn") +const AlertDialog = preload("res://src/ui_widgets/alert_dialog.tscn") const ImportWarningMenu = preload("res://src/ui_parts/import_warning_menu.tscn") const GoodFileDialog = preload("res://src/ui_parts/good_file_dialog.tscn") +static func reset_svg() -> void: + var file_path := Configs.savedata.get_active_tab().svg_file_path + if FileAccess.file_exists(file_path): + State.apply_svg_text(FileAccess.get_file_as_string(file_path)) + static func apply_svg_from_path(path: String) -> void: _finish_file_import(path, _apply_svg, PackedStringArray(["svg"])) static func compare_svg_to_disk_contents() -> FileState: - var content := FileAccess.get_file_as_string(Configs.savedata.current_file_path) + var content := FileAccess.get_file_as_string( + Configs.savedata.get_active_tab().svg_file_path) if content.is_empty(): return FileState.DOES_NOT_EXIST # Check if importing the file's text into GodSVG would change the current SVG text. - if SVG.text == SVGParser.root_to_text(SVGParser.text_to_root(content, + if State.svg_text == SVGParser.root_to_text(SVGParser.text_to_root(content, Configs.savedata.editor_formatter).svg, Configs.savedata.editor_formatter): return FileState.SAME else: @@ -45,14 +51,14 @@ static func open_export_dialog(export_data: ImageExportData) -> void: DisplayServer.file_dialog_show( Translator.translate("Save the .\"{format}\" file").format( - {"format": export_data.format}), Configs.savedata.get_last_dir(), - Utils.get_file_name(Configs.savedata.current_file_path) + "." +\ - export_data.format, false, DisplayServer.FILE_DIALOG_MODE_SAVE_FILE, + {"format": export_data.format}), Configs.savedata.get_active_tab_dir(), + Utils.get_file_name(Configs.savedata.get_active_tab().svg_file_path) +\ + "." + export_data.format, false, DisplayServer.FILE_DIALOG_MODE_SAVE_FILE, PackedStringArray(["*." + export_data.format]), native_callback) else: var export_dialog := GoodFileDialog.instantiate() - export_dialog.setup(Configs.savedata.get_last_dir(), - Utils.get_file_name(Configs.savedata.current_file_path), + export_dialog.setup(Configs.savedata.get_active_tab_dir(), + Utils.get_file_name(Configs.savedata.get_active_tab().svg_file_path), GoodFileDialogType.FileMode.SAVE, PackedStringArray([export_data.format])) HandlerGUI.add_menu(export_dialog) export_dialog.file_selected.connect(func(path): _finish_export(path, export_data)) @@ -70,8 +76,8 @@ static func _finish_export(file_path: String, export_data: ImageExportData) -> v _: # When saving SVG, also modify the file path to associate it # with the graphic being edited. - Configs.savedata.current_file_path = file_path - FileAccess.open(file_path, FileAccess.WRITE).store_string(SVG.get_export_text()) + Configs.savedata.get_active_tab().svg_file_path = file_path + FileAccess.open(file_path, FileAccess.WRITE).store_string(State.get_export_text()) HandlerGUI.remove_all_menus() @@ -164,21 +170,46 @@ allowed_extensions: PackedStringArray) -> Error: return OK -static func _apply_svg(data: Variant, file_path: String) -> Error: - var warning_panel := ImportWarningMenu.instantiate() - warning_panel.imported.connect(_finish_svg_import.bind(data, file_path)) - warning_panel.set_svg(data) - HandlerGUI.add_menu(warning_panel) - return OK +static func _apply_svg(data: Variant, file_path: String) -> void: + var tab_exists := false + for tab in Configs.savedata.get_tabs(): + if tab.svg_file_path == file_path: + tab_exists = true + break + + if tab_exists: + Configs.savedata.add_tab_with_path(file_path) + var alert_message := Translator.translate( + "The imported file is already being edited inside GodSVG.") + if compare_svg_to_disk_contents() == FileState.DIFFERENT: + alert_message += "\n\n" + Translator.translate( + "If you want to apply the unsaved file state, use \"Reset SVG\" instead.") + var alert_dialog := AlertDialog.instantiate() + HandlerGUI.add_menu(alert_dialog) + alert_dialog.setup(alert_message) + else: + State.transient_tab_path = file_path + var warning_panel := ImportWarningMenu.instantiate() + warning_panel.canceled.connect(_on_import_panel_canceled) + warning_panel.imported.connect(_on_import_panel_accepted.bind(file_path, data)) + warning_panel.set_svg(data) + HandlerGUI.add_menu(warning_panel) -static func _finish_svg_import(svg_text: String, file_path: String) -> void: - Configs.savedata.current_file_path = file_path - SVG.apply_svg_text(svg_text) +static func _on_import_panel_canceled() -> void: + State.transient_tab_path = "" + +static func _on_import_panel_accepted(file_path: String, svg_text: String) -> void: + State.transient_tab_path = "" + Configs.savedata.add_tab_with_path(file_path) + Configs.savedata.get_active_tab().setup_svg_text(svg_text) + State.sync_elements() static func open_svg(file_path: String) -> void: - if file_path.get_extension() == "svg": - OS.shell_open(file_path) + OS.shell_open(file_path) + +static func open_svg_folder(file_path: String) -> void: + OS.shell_open(file_path.get_base_dir()) # Web stuff. @@ -202,7 +233,7 @@ completion_callback: Callable) -> void: _change_callback = JavaScriptBridge.create_callback(_web_on_file_selected) input.addEventListener("change", _change_callback) - _cancel_callback = JavaScriptBridge.create_callback(_web_on_file_dialog_cancelled) + _cancel_callback = JavaScriptBridge.create_callback(_web_on_file_dialog_canceled) input.addEventListener("cancel", _cancel_callback) input.click() # Open file dialog. @@ -236,7 +267,7 @@ static func _web_on_file_selected(args: Array) -> void: var file: JavaScriptObject = event.target.files[0] JavaScriptBridge.eval("window.godsvgFileName = '" + file.name + "';", true) - # Store the callback reference to prevent garbage collection. + # Store the callback to prevent garbage collection. var reader: JavaScriptObject = JavaScriptBridge.create_object("FileReader") _file_load_callback = JavaScriptBridge.create_callback(_web_on_file_loaded) reader.onloadend = _file_load_callback @@ -265,12 +296,12 @@ static func _web_on_file_loaded(args: Array) -> void: # For binary files, the ArrayBuffer gets handled. JavaScriptBridge.eval("window.godsvgFileData = new Uint8Array(event.target.result);", true) -static func _web_on_file_dialog_cancelled(_args: Array) -> void: +static func _web_on_file_dialog_canceled(_args: Array) -> void: JavaScriptBridge.eval("window.godsvgDialogClosed = true;", true) static func _web_save(buffer: PackedByteArray, format_name: String) -> void: - var file_name := Utils.get_file_name(Configs.savedata.current_file_path) + var file_name := Utils.get_file_name(Configs.savedata.get_active_tab().svg_file_path) if file_name.is_empty(): file_name = "export" JavaScriptBridge.download_buffer(buffer, file_name, format_name) diff --git a/src/utils/ImageExportData.gd b/src/utils/ImageExportData.gd index d2d5499a..f848dc5f 100644 --- a/src/utils/ImageExportData.gd +++ b/src/utils/ImageExportData.gd @@ -36,7 +36,7 @@ var lossy := false: func svg_to_buffer() -> PackedByteArray: - return SVG.get_export_text().to_utf8_buffer() + return State.get_export_text().to_utf8_buffer() func image_to_buffer(image: Image) -> PackedByteArray: match format: @@ -47,7 +47,7 @@ func image_to_buffer(image: Image) -> PackedByteArray: func generate_image() -> Image: - var export_svg := SVG.root_element.duplicate() + var export_svg := State.root_element.duplicate() if export_svg.get_attribute_list("viewBox").is_empty(): export_svg.set_attribute("viewBox", PackedFloat64Array([0.0, 0.0, export_svg.width, export_svg.height])) diff --git a/src/utils/ShortcutUtils.gd b/src/utils/ShortcutUtils.gd index 525e16ac..8143c7fe 100644 --- a/src/utils/ShortcutUtils.gd +++ b/src/utils/ShortcutUtils.gd @@ -6,10 +6,12 @@ const _shortcut_categories_dict: Dictionary[String, Dictionary] = { "import": true, "export": true, "save": true, + "close_tab": true, + "new_tab": true, + "select_next_tab": true, + "select_previous_tab": true, "optimize": true, "copy_svg_text": true, - "clear_svg": true, - "clear_file_path": true, "reset_svg": true, "open_svg": true, }, @@ -78,21 +80,25 @@ static func fn(shortcut: String) -> Callable: "save": return FileUtils.save_svg "export": return HandlerGUI.open_export "import": return FileUtils.open_svg_import_dialog - "copy_svg_text": return DisplayServer.clipboard_set.bind(SVG.text) - "clear_svg": return SVG.apply_svg_text.bind(SVG.DEFAULT) - "optimize": return SVG.optimize - "clear_file_path": return Configs.savedata.set.bind("current_file_path", "") - "reset_svg": return FileUtils.apply_svg_from_path.bind( - Configs.savedata.current_file_path) - "open_svg": return FileUtils.open_svg.bind(Configs.savedata.current_file_path) - "redo": return SVG.redo - "undo": return SVG.undo - "ui_cancel": return Indications.clear_all_selections - "delete": return Indications.delete_selected - "move_up": return Indications.move_up_selected - "move_down": return Indications.move_down_selected - "duplicate": return Indications.duplicate_selected - "select_all": return Indications.select_all + "close_tab": return Configs.savedata.remove_active_tab + "new_tab": return Configs.savedata.add_empty_tab + "select_next_tab": return func(): Configs.savedata.set_active_tab_index(posmod( + Configs.savedata.get_active_tab_index() + 1, Configs.savedata.get_tab_count())) + "select_previous_tab": return func(): 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_svg": return FileUtils.open_svg.bind( + 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/MewPurPur/GodSVG") @@ -107,8 +113,8 @@ static func get_shortcut_icon(shortcut: String) -> CompressedTexture2D: "import": return load("res://assets/icons/Import.svg") "export": return load("res://assets/icons/Export.svg") "save": 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") - "clear_svg", "clear_file_path": return load("res://assets/icons/Clear.svg") "optimize": return load("res://assets/icons/Compress.svg") "reset_svg", "zoom_reset": return load("res://assets/icons/Reload.svg") "open_svg": return load("res://assets/icons/OpenFile.svg") diff --git a/src/utils/TranslationUtils.gd b/src/utils/TranslationUtils.gd index 7dd4beb9..c8b8ffe3 100644 --- a/src/utils/TranslationUtils.gd +++ b/src/utils/TranslationUtils.gd @@ -5,19 +5,21 @@ static func get_shortcut_description(action_name: String) -> String: "export": return Translator.translate("Export") "import": return Translator.translate("Import") "save": return Translator.translate("Save") + "close_tab": return Translator.translate("Close tab") + "new_tab": return Translator.translate("Create 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") "reset_svg": return Translator.translate("Reset SVG") - "clear_svg": return Translator.translate("Clear SVG") "open_svg": return Translator.translate("Open SVG file") - "clear_file_path": return Translator.translate("Clear saving path") "undo": return Translator.translate("Undo") "redo": return Translator.translate("Redo") - "select_all": return Translator.translate("Select all elements") - "duplicate": return Translator.translate("Duplicate the selected elements") + "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 selected elements up") - "move_down": return Translator.translate("Move the selected elements down") + "move_up": return Translator.translate("Move the selection up") + "move_down": return 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") diff --git a/src/utils/XNodeChildrenBuilder.gd b/src/utils/XNodeChildrenBuilder.gd index 87dcc709..67fb2e4e 100644 --- a/src/utils/XNodeChildrenBuilder.gd +++ b/src/utils/XNodeChildrenBuilder.gd @@ -20,16 +20,16 @@ static func generate_drag_preview(xids: Array[PackedInt32Array], ) -> Control: var xnode_container := VBoxContainer.new() for data_idx in range(xids.size() - 1, -1, -1): var drag_xid := xids[data_idx] - var drag_xnode := SVG.root_element.get_xnode(drag_xid) + var drag_xnode := State.root_element.get_xnode(drag_xid) if drag_xnode is Element: var preview := ElementFrame.instantiate() - preview.element = SVG.root_element.get_xnode(drag_xid) + preview.element = State.root_element.get_xnode(drag_xid) preview.custom_minimum_size.x = 360.0 preview.z_index = 2 xnode_container.add_child(preview) elif drag_xnode is BasicXNode: var preview := BasicXNodeFrame.instantiate() - preview.xnode = SVG.root_element.get_xnode(drag_xid) + preview.xnode = State.root_element.get_xnode(drag_xid) preview.custom_minimum_size.x = 360.0 preview.z_index = 2 xnode_container.add_child(preview)