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)