diff --git a/assets/icons/Close.svg b/assets/icons/Close.svg
new file mode 100644
index 0000000..4879911
--- /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 0000000..ffddd08
--- /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 0000000..2110ccd
--- /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 0000000..1a75ed2
--- /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/assets/translations/GodSVG.pot b/assets/translations/GodSVG.pot
index 1d082ad..686128d 100644
--- a/assets/translations/GodSVG.pot
+++ b/assets/translations/GodSVG.pot
@@ -35,7 +35,7 @@ msgstr ""
msgid "This requires GodSVG to connect to the internet."
msgstr ""
-#: src/autoload/HandlerGUI.gd src/ui_parts/alert_dialog.gd
+#: src/autoload/HandlerGUI.gd src/ui_widgets/alert_dialog.gd
msgid "OK"
msgstr ""
@@ -44,7 +44,7 @@ msgid "Export SVG"
msgstr ""
#: src/autoload/HandlerGUI.gd src/ui_parts/export_menu.gd
-#: src/utils/TranslationUtils.gd
+#: src/ui_parts/global_actions.gd src/utils/TranslationUtils.gd
msgid "Export"
msgstr ""
@@ -54,35 +54,58 @@ msgid ""
"you want to proceed?"
msgstr ""
-#: src/autoload/Indications.gd
-msgid "View In List"
+#: src/autoload/State.gd src/ui_parts/good_file_dialog.gd
+#: src/ui_widgets/alert_dialog.gd
+msgid "Alert!"
+msgstr ""
+
+#: src/autoload/State.gd src/ui_parts/tab_bar.gd src/utils/TranslationUtils.gd
+msgid "Close tab"
+msgstr ""
+
+#: src/autoload/State.gd
+msgid "Restore"
msgstr ""
-#: src/autoload/Indications.gd
+#: src/autoload/State.gd
+msgid "View in List"
+msgstr ""
+
+#: src/autoload/State.gd
msgid "Duplicate"
msgstr ""
-#: src/autoload/Indications.gd
+#: src/autoload/State.gd
msgid "Convert To"
msgstr ""
-#: src/autoload/Indications.gd src/ui_widgets/palette_config.gd
+#: src/autoload/State.gd src/ui_widgets/palette_config.gd
msgid "Move Up"
msgstr ""
-#: src/autoload/Indications.gd src/ui_widgets/palette_config.gd
+#: src/autoload/State.gd src/ui_widgets/palette_config.gd
msgid "Move Down"
msgstr ""
-#: src/autoload/Indications.gd src/ui_widgets/palette_config.gd
+#: src/autoload/State.gd src/ui_widgets/palette_config.gd
#: src/ui_widgets/setting_shortcut.gd src/ui_widgets/transform_popup.gd
msgid "Delete"
msgstr ""
-#: src/autoload/Indications.gd src/ui_widgets/transform_popup.gd
+#: src/autoload/State.gd src/ui_widgets/transform_popup.gd
msgid "Insert After"
msgstr ""
+#: src/autoload/State.gd
+msgid "The last edited state of this tab could not be found."
+msgstr ""
+
+#: src/autoload/State.gd
+msgid ""
+"The tab is bound to the file path {file_path}. Do you want to restore from "
+"this path?"
+msgstr ""
+
#: src/config_classes/Formatter.gd
msgid "Compact"
msgstr ""
@@ -119,6 +142,10 @@ msgstr ""
msgid "6-digit hex"
msgstr ""
+#: src/config_classes/TabData.gd
+msgid "Unsaved"
+msgstr ""
+
#: src/data_classes/BasicXNode.gd
msgid "Comment"
msgstr ""
@@ -139,18 +166,15 @@ msgstr ""
msgid "This group has only one element."
msgstr ""
-#: src/data_classes/ElementLinearGradient.gd
-#: src/data_classes/ElementRadialGradient.gd
+#: src/data_classes/GradientUtils.gd
msgid "No \"id\" attribute defined."
msgstr ""
-#: src/data_classes/ElementLinearGradient.gd
-#: src/data_classes/ElementRadialGradient.gd
+#: src/data_classes/GradientUtils.gd
msgid "No elements under this gradient."
msgstr ""
-#: src/data_classes/ElementLinearGradient.gd
-#: src/data_classes/ElementRadialGradient.gd
+#: src/data_classes/GradientUtils.gd
msgid "This gradient is a solid color."
msgstr ""
@@ -162,6 +186,12 @@ msgstr ""
msgid "Improper nesting."
msgstr ""
+#: src/ui_parts/about_menu.gd src/ui_parts/good_file_dialog.gd
+#: src/ui_parts/settings_menu.gd src/ui_parts/shortcut_panel_config.gd
+#: src/ui_parts/update_menu.gd
+msgid "Close"
+msgstr ""
+
#: src/ui_parts/about_menu.gd
msgid "Project Founder and Manager"
msgstr ""
@@ -202,51 +232,6 @@ msgstr ""
msgid "Third-party licenses"
msgstr ""
-#: src/ui_parts/alert_dialog.gd src/ui_parts/good_file_dialog.gd
-msgid "Alert!"
-msgstr ""
-
-#: src/ui_parts/choose_name_dialog.gd src/ui_parts/confirm_dialog.gd
-#: src/ui_parts/export_menu.gd src/ui_parts/import_warning_menu.gd
-msgid "Cancel"
-msgstr ""
-
-#: src/ui_parts/choose_name_dialog.gd
-msgid "Create"
-msgstr ""
-
-#: src/ui_parts/code_editor.gd src/ui_parts/good_file_dialog.gd
-msgid "Save SVG"
-msgstr ""
-
-#: src/ui_parts/code_editor.gd
-msgid "Open file"
-msgstr ""
-
-#: src/ui_parts/code_editor.gd src/utils/TranslationUtils.gd
-msgid "Reset SVG"
-msgstr ""
-
-#: src/ui_parts/code_editor.gd src/utils/TranslationUtils.gd
-msgid "Clear saving path"
-msgstr ""
-
-#: src/ui_parts/code_editor.gd src/utils/TranslationUtils.gd
-msgid "Optimize"
-msgstr ""
-
-#: src/ui_parts/code_editor.gd src/utils/TranslationUtils.gd
-msgid "Copy all text"
-msgstr ""
-
-#: src/ui_parts/code_editor.gd src/utils/TranslationUtils.gd
-msgid "Clear SVG"
-msgstr ""
-
-#: src/ui_parts/display.gd
-msgid "Settings"
-msgstr ""
-
#: src/ui_parts/display.gd
msgid "Visuals"
msgstr ""
@@ -275,32 +260,14 @@ msgstr ""
msgid "Rasterized SVG"
msgstr ""
-#: src/ui_parts/display.gd
-msgid "About…"
-msgstr ""
-
#: src/ui_parts/display.gd
msgid "Snap size"
msgstr ""
-#: src/ui_parts/display.gd src/utils/TranslationUtils.gd
-msgid "Check for updates"
-msgstr ""
-
-#: src/ui_parts/display.gd
-msgid "Donate…"
-msgstr ""
-
-#: src/ui_parts/display.gd
-msgid "GodSVG repository"
-msgstr ""
-
-#: src/ui_parts/display.gd
-msgid "GodSVG website"
-msgstr ""
-
-#: src/ui_parts/display.gd
-msgid "View savedata"
+#: src/ui_parts/donate_menu.gd src/ui_parts/export_menu.gd
+#: src/ui_parts/import_warning_menu.gd src/ui_widgets/choose_name_dialog.gd
+#: src/ui_widgets/confirm_dialog.gd
+msgid "Cancel"
msgstr ""
#: src/ui_parts/element_container.gd
@@ -311,11 +278,6 @@ msgstr ""
msgid "Dimensions"
msgstr ""
-#: src/ui_parts/export_menu.gd src/ui_widgets/configure_color_popup.gd
-#: src/ui_widgets/palette_config.gd
-msgid "Unnamed"
-msgstr ""
-
#: src/ui_parts/export_menu.gd
msgid "Size"
msgstr ""
@@ -352,24 +314,57 @@ msgstr ""
msgid "Preview image size is limited to {dimensions}"
msgstr ""
-#: src/ui_parts/global_menu.gd src/ui_parts/settings_menu.gd
-msgid "File"
+#: src/ui_parts/global_actions.gd src/ui_parts/import_warning_menu.gd
+#: src/utils/TranslationUtils.gd
+msgid "Import"
msgstr ""
-#: src/ui_parts/global_menu.gd src/ui_parts/settings_menu.gd
-msgid "Edit"
+#: src/ui_parts/global_actions.gd
+msgid "Settings"
msgstr ""
-#: src/ui_parts/global_menu.gd src/ui_parts/settings_menu.gd
-msgid "Tool"
+#: src/ui_parts/global_actions.gd src/utils/TranslationUtils.gd
+msgid "Optimize"
msgstr ""
-#: src/ui_parts/global_menu.gd src/ui_parts/settings_menu.gd
-msgid "View"
+#: src/ui_parts/global_actions.gd src/ui_parts/good_file_dialog.gd
+msgid "Save SVG"
msgstr ""
-#: src/ui_parts/global_menu.gd
-msgid "Snap"
+#: src/ui_parts/global_actions.gd src/utils/TranslationUtils.gd
+msgid "Reset SVG"
+msgstr ""
+
+#: src/ui_parts/global_actions.gd src/ui_parts/tab_bar.gd
+msgid "Open externally"
+msgstr ""
+
+#: src/ui_parts/global_actions.gd src/ui_parts/tab_bar.gd
+msgid "Show in File Manager"
+msgstr ""
+
+#: src/ui_parts/global_actions.gd
+msgid "About…"
+msgstr ""
+
+#: src/ui_parts/global_actions.gd src/utils/TranslationUtils.gd
+msgid "Check for updates"
+msgstr ""
+
+#: src/ui_parts/global_actions.gd
+msgid "Donate…"
+msgstr ""
+
+#: src/ui_parts/global_actions.gd
+msgid "GodSVG repository"
+msgstr ""
+
+#: src/ui_parts/global_actions.gd
+msgid "GodSVG website"
+msgstr ""
+
+#: src/ui_parts/global_actions.gd
+msgid "View savedata"
msgstr ""
#: src/ui_parts/good_file_dialog.gd
@@ -400,11 +395,6 @@ msgstr ""
msgid "Select an image"
msgstr ""
-#: src/ui_parts/good_file_dialog.gd src/ui_parts/settings_menu.gd
-#: src/ui_parts/shortcut_panel_config.gd src/ui_parts/update_menu.gd
-msgid "Close"
-msgstr ""
-
#: src/ui_parts/good_file_dialog.gd
msgid "Select"
msgstr ""
@@ -459,10 +449,6 @@ msgstr ""
msgid "Import Problems"
msgstr ""
-#: src/ui_parts/import_warning_menu.gd src/utils/TranslationUtils.gd
-msgid "Import"
-msgstr ""
-
#: src/ui_parts/import_warning_menu.gd
msgid "Unrecognized element"
msgstr ""
@@ -479,6 +465,26 @@ msgstr ""
msgid "Add element"
msgstr ""
+#: src/ui_parts/mac_menu.gd src/ui_parts/settings_menu.gd
+msgid "File"
+msgstr ""
+
+#: src/ui_parts/mac_menu.gd src/ui_parts/settings_menu.gd
+msgid "Edit"
+msgstr ""
+
+#: src/ui_parts/mac_menu.gd src/ui_parts/settings_menu.gd
+msgid "Tool"
+msgstr ""
+
+#: src/ui_parts/mac_menu.gd src/ui_parts/settings_menu.gd
+msgid "View"
+msgstr ""
+
+#: src/ui_parts/mac_menu.gd
+msgid "Snap"
+msgstr ""
+
#: src/ui_parts/settings_menu.gd
msgid "Formatting"
msgstr ""
@@ -801,6 +807,30 @@ msgstr ""
msgid "Vertical strip"
msgstr ""
+#: src/ui_parts/tab_bar.gd
+msgid "Create tab"
+msgstr ""
+
+#: src/ui_parts/tab_bar.gd
+msgid "Close all other tabs"
+msgstr ""
+
+#: src/ui_parts/tab_bar.gd
+msgid "Close tabs to the left"
+msgstr ""
+
+#: src/ui_parts/tab_bar.gd
+msgid "Close tabs to the right"
+msgstr ""
+
+#: src/ui_parts/tab_bar.gd src/utils/TranslationUtils.gd
+msgid "Create a new tab"
+msgstr ""
+
+#: src/ui_parts/tab_bar.gd
+msgid "This SVG is not bound to a location on the computer yet."
+msgstr ""
+
#: src/ui_parts/update_menu.gd
msgid "Include prereleases"
msgstr ""
@@ -829,6 +859,18 @@ msgstr ""
msgid "New versions"
msgstr ""
+#: src/ui_parts/zoom_menu.gd src/utils/TranslationUtils.gd
+msgid "Zoom out"
+msgstr ""
+
+#: src/ui_parts/zoom_menu.gd src/utils/TranslationUtils.gd
+msgid "Zoom in"
+msgstr ""
+
+#: src/ui_parts/zoom_menu.gd src/utils/TranslationUtils.gd
+msgid "Zoom reset"
+msgstr ""
+
#: src/ui_widgets/BetterLineEdit.gd src/ui_widgets/BetterTextEdit.gd
#: src/utils/TranslationUtils.gd
msgid "Undo"
@@ -851,6 +893,14 @@ msgstr ""
msgid "Paste"
msgstr ""
+#: src/ui_widgets/choose_name_dialog.gd
+msgid "Create"
+msgstr ""
+
+#: src/ui_widgets/code_editor.gd src/utils/TranslationUtils.gd
+msgid "Copy all text"
+msgstr ""
+
#: src/ui_widgets/color_popup.gd
msgid "Search color"
msgstr ""
@@ -867,6 +917,10 @@ msgstr ""
msgid "Delete color"
msgstr ""
+#: src/ui_widgets/configure_color_popup.gd src/ui_widgets/palette_config.gd
+msgid "Unnamed"
+msgstr ""
+
#: src/ui_widgets/good_color_picker.gd
msgid "Color keywords"
msgstr ""
@@ -899,6 +953,11 @@ msgstr ""
msgid "Apply Preset"
msgstr ""
+#: src/ui_widgets/path_popup.gd src/ui_widgets/pathdata_field.gd
+#: src/utils/TranslationUtils.gd
+msgid "Relative"
+msgstr ""
+
#: src/ui_widgets/pathdata_field.gd
msgid "No path data"
msgstr ""
@@ -907,10 +966,6 @@ msgstr ""
msgid "Absolute"
msgstr ""
-#: src/ui_widgets/pathdata_field.gd src/utils/TranslationUtils.gd
-msgid "Relative"
-msgstr ""
-
#: src/ui_widgets/points_field.gd
msgid "No points"
msgstr ""
@@ -975,44 +1030,52 @@ msgstr ""
msgid "Check if the file still exists in the selected file path."
msgstr ""
+#: src/utils/FileUtils.gd
+msgid "The imported file is already being edited inside GodSVG."
+msgstr ""
+
+#: src/utils/FileUtils.gd
+msgid "If you want to apply the unsaved file state, use \"Reset SVG\" instead."
+msgstr ""
+
#: src/utils/TranslationUtils.gd
-msgid "Open SVG file"
+msgid "Select the next tab"
msgstr ""
#: src/utils/TranslationUtils.gd
-msgid "Select all elements"
+msgid "Select the previous tab"
msgstr ""
#: src/utils/TranslationUtils.gd
-msgid "Duplicate the selected elements"
+msgid "Open SVG externally"
msgstr ""
#: src/utils/TranslationUtils.gd
-msgid "Delete the selection"
+msgid "Show SVG in File Manager"
msgstr ""
#: src/utils/TranslationUtils.gd
-msgid "Move the selected elements up"
+msgid "Select all"
msgstr ""
#: src/utils/TranslationUtils.gd
-msgid "Move the selected elements down"
+msgid "Duplicate the selection"
msgstr ""
#: src/utils/TranslationUtils.gd
-msgid "Find"
+msgid "Delete the selection"
msgstr ""
#: src/utils/TranslationUtils.gd
-msgid "Zoom in"
+msgid "Move the selection up"
msgstr ""
#: src/utils/TranslationUtils.gd
-msgid "Zoom out"
+msgid "Move the selection down"
msgstr ""
#: src/utils/TranslationUtils.gd
-msgid "Zoom reset"
+msgid "Find"
msgstr ""
#: src/utils/TranslationUtils.gd
diff --git a/assets/translations/README.md b/assets/translations/README.md
index 3c61adb..32ce416 100644
--- a/assets/translations/README.md
+++ b/assets/translations/README.md
@@ -1,12 +1,14 @@
**The official GNU gettext manual can be found [here](https://www.gnu.org/software/gettext/manual/html_node/index.html)**
-# The po format
-You can familiarize yourself with the po format [here](https://www.gnu.org/software/gettext/manual/html_node/PO-Files.html).
+# Files to translate
+Most of the strings for a language are inside its respective po file. You can read more about this format [here](https://www.gnu.org/software/gettext/manual/html_node/PO-Files.html).
+
+Outside of this folder, there is the [assets/GodSVG.desktop](https://github.com/MewPurPur/GodSVG/blob/main/assets/GodSVG.desktop) file which has a few fields that can be internationalized.
# For programmers
-New translatable strings must be within GDScript files, inside a `TranslationServer.translate()` method or its plural version.
+New translatable strings must be within GDScript files, inside a `Translator.translate()` method. If they are part of a persistent UI, they must also respond to the `Configs.language_changed` signal.
-To include or update strings, open godot_only/update_translations.gd in the Godot editor and follow the comment on top.
+To include or update strings, open godot_only/scripts/update_translations.gd in the Godot editor and follow the comment on top.
>[!IMPORTANT]
>To run the above script, you must install [gettext tools](https://www.gnu.org/software/gettext/). It's preinstalled on most Linux distributions and Git Bash on Windows.
diff --git a/assets/translations/bg.po b/assets/translations/bg.po
index 0ba56c8..2b51819 100644
--- a/assets/translations/bg.po
+++ b/assets/translations/bg.po
@@ -35,7 +35,7 @@ msgstr "Провери за ъпдейти?"
msgid "This requires GodSVG to connect to the internet."
msgstr "Това изисква GodSVG да се свърже с интерната."
-#: src/autoload/HandlerGUI.gd src/ui_parts/alert_dialog.gd
+#: src/autoload/HandlerGUI.gd src/ui_widgets/alert_dialog.gd
msgid "OK"
msgstr "Добре"
@@ -44,7 +44,7 @@ msgid "Export SVG"
msgstr "Експортирай SVG-то"
#: src/autoload/HandlerGUI.gd src/ui_parts/export_menu.gd
-#: src/utils/TranslationUtils.gd
+#: src/ui_parts/global_actions.gd src/utils/TranslationUtils.gd
msgid "Export"
msgstr "Експортирай"
@@ -56,35 +56,60 @@ msgstr ""
"Графиката може да бъде експортирана само като SVG защото размерът не е "
"определен. Искаш ли да продължиш?"
-#: src/autoload/Indications.gd
-msgid "View In List"
+#: src/autoload/State.gd src/ui_parts/good_file_dialog.gd
+#: src/ui_widgets/alert_dialog.gd
+msgid "Alert!"
+msgstr "Предупреждение!"
+
+#: src/autoload/State.gd src/ui_parts/tab_bar.gd src/utils/TranslationUtils.gd
+msgid "Close tab"
+msgstr "Затвори раздела"
+
+#: src/autoload/State.gd
+msgid "Restore"
+msgstr "Възстанови"
+
+#: src/autoload/State.gd
+msgid "View in List"
msgstr "Виж в Списъка"
-#: src/autoload/Indications.gd
+#: src/autoload/State.gd
msgid "Duplicate"
msgstr "Дублирай"
-#: src/autoload/Indications.gd
+#: src/autoload/State.gd
msgid "Convert To"
msgstr "Превърни в"
-#: src/autoload/Indications.gd src/ui_widgets/palette_config.gd
+#: src/autoload/State.gd src/ui_widgets/palette_config.gd
msgid "Move Up"
msgstr "Премести нагоре"
-#: src/autoload/Indications.gd src/ui_widgets/palette_config.gd
+#: src/autoload/State.gd src/ui_widgets/palette_config.gd
msgid "Move Down"
msgstr "Премести надолу"
-#: src/autoload/Indications.gd src/ui_widgets/palette_config.gd
+#: src/autoload/State.gd src/ui_widgets/palette_config.gd
#: src/ui_widgets/setting_shortcut.gd src/ui_widgets/transform_popup.gd
msgid "Delete"
msgstr "Премахни"
-#: src/autoload/Indications.gd src/ui_widgets/transform_popup.gd
+#: src/autoload/State.gd src/ui_widgets/transform_popup.gd
msgid "Insert After"
msgstr "Вмъкни отпред"
+#: src/autoload/State.gd
+msgid "The last edited state of this tab could not be found."
+msgstr "Последното състояние на този раздел не беше намерено."
+
+#: src/autoload/State.gd
+msgid ""
+"The tab is bound to the file path {file_path}. Do you want to restore from "
+"this path?"
+msgstr ""
+"Този раздел е свързан с пътеката {file_path}. Искаш ли да го възстановиш от "
+"тази пътека?"
+
#: src/config_classes/Formatter.gd
msgid "Compact"
msgstr "Компактен"
@@ -121,6 +146,10 @@ msgstr "Хекс с 3 или 6 цифри"
msgid "6-digit hex"
msgstr "Хекс с 6 цифри"
+#: src/config_classes/TabData.gd
+msgid "Unsaved"
+msgstr "Незаписан"
+
#: src/data_classes/BasicXNode.gd
msgid "Comment"
msgstr "Коментар"
@@ -141,18 +170,15 @@ msgstr "Тази група няма елементи."
msgid "This group has only one element."
msgstr "Тази група има само един елемент."
-#: src/data_classes/ElementLinearGradient.gd
-#: src/data_classes/ElementRadialGradient.gd
+#: src/data_classes/GradientUtils.gd
msgid "No \"id\" attribute defined."
msgstr "Не е дефиниран \"id\" атрибут."
-#: src/data_classes/ElementLinearGradient.gd
-#: src/data_classes/ElementRadialGradient.gd
+#: src/data_classes/GradientUtils.gd
msgid "No elements under this gradient."
msgstr "Няма елемент под този градиент."
-#: src/data_classes/ElementLinearGradient.gd
-#: src/data_classes/ElementRadialGradient.gd
+#: src/data_classes/GradientUtils.gd
msgid "This gradient is a solid color."
msgstr "Този градиент е едноцветен."
@@ -164,6 +190,12 @@ msgstr "Текстът не описва SVG."
msgid "Improper nesting."
msgstr "Несъвместими тагове."
+#: src/ui_parts/about_menu.gd src/ui_parts/good_file_dialog.gd
+#: src/ui_parts/settings_menu.gd src/ui_parts/shortcut_panel_config.gd
+#: src/ui_parts/update_menu.gd
+msgid "Close"
+msgstr "Затвори"
+
#: src/ui_parts/about_menu.gd
msgid "Project Founder and Manager"
msgstr "Основател и мениджър на проекта"
@@ -204,51 +236,6 @@ msgstr "Компоненти в Godot от трети лица"
msgid "Third-party licenses"
msgstr "Лицензи от трети партии"
-#: src/ui_parts/alert_dialog.gd src/ui_parts/good_file_dialog.gd
-msgid "Alert!"
-msgstr "Предупреждение!"
-
-#: src/ui_parts/choose_name_dialog.gd src/ui_parts/confirm_dialog.gd
-#: src/ui_parts/export_menu.gd src/ui_parts/import_warning_menu.gd
-msgid "Cancel"
-msgstr "Отказ"
-
-#: src/ui_parts/choose_name_dialog.gd
-msgid "Create"
-msgstr "Създай"
-
-#: src/ui_parts/code_editor.gd src/ui_parts/good_file_dialog.gd
-msgid "Save SVG"
-msgstr "Запиши SVG-то"
-
-#: src/ui_parts/code_editor.gd
-msgid "Open file"
-msgstr "Отвори файла"
-
-#: src/ui_parts/code_editor.gd src/utils/TranslationUtils.gd
-msgid "Reset SVG"
-msgstr "Рестартирай SVG-то"
-
-#: src/ui_parts/code_editor.gd src/utils/TranslationUtils.gd
-msgid "Clear saving path"
-msgstr "Премахни запазената пътека"
-
-#: src/ui_parts/code_editor.gd src/utils/TranslationUtils.gd
-msgid "Optimize"
-msgstr "Оптимизирай"
-
-#: src/ui_parts/code_editor.gd src/utils/TranslationUtils.gd
-msgid "Copy all text"
-msgstr "Копирай всичкия текст"
-
-#: src/ui_parts/code_editor.gd src/utils/TranslationUtils.gd
-msgid "Clear SVG"
-msgstr "Изчисти SVG-то"
-
-#: src/ui_parts/display.gd
-msgid "Settings"
-msgstr "Настройки"
-
#: src/ui_parts/display.gd
msgid "Visuals"
msgstr "Графики"
@@ -277,33 +264,15 @@ msgstr "Покажи дръжките"
msgid "Rasterized SVG"
msgstr "Растеризирай SVG"
-#: src/ui_parts/display.gd
-msgid "About…"
-msgstr "Относно приложението…"
-
#: src/ui_parts/display.gd
msgid "Snap size"
msgstr "Размер на захващането"
-#: src/ui_parts/display.gd src/utils/TranslationUtils.gd
-msgid "Check for updates"
-msgstr "Провери за ъпдейти"
-
-#: src/ui_parts/display.gd
-msgid "Donate…"
-msgstr "Направи дарение…"
-
-#: src/ui_parts/display.gd
-msgid "GodSVG repository"
-msgstr "Репозиторията на GodSVG"
-
-#: src/ui_parts/display.gd
-msgid "GodSVG website"
-msgstr "Уебсайта на GodSVG"
-
-#: src/ui_parts/display.gd
-msgid "View savedata"
-msgstr "Виж запазените данни"
+#: src/ui_parts/donate_menu.gd src/ui_parts/export_menu.gd
+#: src/ui_parts/import_warning_menu.gd src/ui_widgets/choose_name_dialog.gd
+#: src/ui_widgets/confirm_dialog.gd
+msgid "Cancel"
+msgstr "Отказ"
#: src/ui_parts/element_container.gd
msgid "New element"
@@ -313,11 +282,6 @@ msgstr "Добави елемент"
msgid "Dimensions"
msgstr "Измерения"
-#: src/ui_parts/export_menu.gd src/ui_widgets/configure_color_popup.gd
-#: src/ui_widgets/palette_config.gd
-msgid "Unnamed"
-msgstr "Неименуван"
-
#: src/ui_parts/export_menu.gd
msgid "Size"
msgstr "Размер"
@@ -354,25 +318,58 @@ msgstr "Височина"
msgid "Preview image size is limited to {dimensions}"
msgstr "Размерът на визуализацията е ограничен до {dimensions}"
-#: src/ui_parts/global_menu.gd src/ui_parts/settings_menu.gd
-msgid "File"
-msgstr "Файл"
+#: src/ui_parts/global_actions.gd src/ui_parts/import_warning_menu.gd
+#: src/utils/TranslationUtils.gd
+msgid "Import"
+msgstr "Импортирай"
-#: src/ui_parts/global_menu.gd src/ui_parts/settings_menu.gd
-msgid "Edit"
-msgstr "Промени"
+#: src/ui_parts/global_actions.gd
+msgid "Settings"
+msgstr "Настройки"
-#: src/ui_parts/global_menu.gd src/ui_parts/settings_menu.gd
-msgid "Tool"
-msgstr "Инструмент"
+#: src/ui_parts/global_actions.gd src/utils/TranslationUtils.gd
+msgid "Optimize"
+msgstr "Оптимизирай"
-#: src/ui_parts/global_menu.gd src/ui_parts/settings_menu.gd
-msgid "View"
-msgstr "Гледка"
+#: src/ui_parts/global_actions.gd src/ui_parts/good_file_dialog.gd
+msgid "Save SVG"
+msgstr "Запиши SVG-то"
-#: src/ui_parts/global_menu.gd
-msgid "Snap"
-msgstr "Захващане"
+#: src/ui_parts/global_actions.gd src/utils/TranslationUtils.gd
+msgid "Reset SVG"
+msgstr "Рестартирай SVG-то"
+
+#: src/ui_parts/global_actions.gd src/ui_parts/tab_bar.gd
+msgid "Open externally"
+msgstr "Отвори с външно приложение"
+
+#: src/ui_parts/global_actions.gd src/ui_parts/tab_bar.gd
+msgid "Show in File Manager"
+msgstr "Покажи във Файловия мениджър"
+
+#: src/ui_parts/global_actions.gd
+msgid "About…"
+msgstr "Относно приложението…"
+
+#: src/ui_parts/global_actions.gd src/utils/TranslationUtils.gd
+msgid "Check for updates"
+msgstr "Провери за ъпдейти"
+
+#: src/ui_parts/global_actions.gd
+msgid "Donate…"
+msgstr "Направи дарение…"
+
+#: src/ui_parts/global_actions.gd
+msgid "GodSVG repository"
+msgstr "Репозиторията на GodSVG"
+
+#: src/ui_parts/global_actions.gd
+msgid "GodSVG website"
+msgstr "Уебсайта на GodSVG"
+
+#: src/ui_parts/global_actions.gd
+msgid "View savedata"
+msgstr "Виж запазените данни"
#: src/ui_parts/good_file_dialog.gd
msgid "Go to parent folder"
@@ -402,11 +399,6 @@ msgstr "Избери XML файл"
msgid "Select an image"
msgstr "Избери изображение"
-#: src/ui_parts/good_file_dialog.gd src/ui_parts/settings_menu.gd
-#: src/ui_parts/shortcut_panel_config.gd src/ui_parts/update_menu.gd
-msgid "Close"
-msgstr "Затвори"
-
#: src/ui_parts/good_file_dialog.gd
msgid "Select"
msgstr "Избери"
@@ -463,10 +455,6 @@ msgstr "Нова фигура"
msgid "Import Problems"
msgstr "Проблеми в импортирането"
-#: src/ui_parts/import_warning_menu.gd src/utils/TranslationUtils.gd
-msgid "Import"
-msgstr "Импортирай"
-
#: src/ui_parts/import_warning_menu.gd
msgid "Unrecognized element"
msgstr "Непознат елемент"
@@ -483,6 +471,26 @@ msgstr "Синтактична грешка"
msgid "Add element"
msgstr "Добави елемент"
+#: src/ui_parts/mac_menu.gd src/ui_parts/settings_menu.gd
+msgid "File"
+msgstr "Файл"
+
+#: src/ui_parts/mac_menu.gd src/ui_parts/settings_menu.gd
+msgid "Edit"
+msgstr "Промени"
+
+#: src/ui_parts/mac_menu.gd src/ui_parts/settings_menu.gd
+msgid "Tool"
+msgstr "Инструмент"
+
+#: src/ui_parts/mac_menu.gd src/ui_parts/settings_menu.gd
+msgid "View"
+msgstr "Гледка"
+
+#: src/ui_parts/mac_menu.gd
+msgid "Snap"
+msgstr "Захващане"
+
#: src/ui_parts/settings_menu.gd
msgid "Formatting"
msgstr "Форматиране"
@@ -812,6 +820,30 @@ msgstr "Хоризонтален с два реда"
msgid "Vertical strip"
msgstr "Вертикална лента"
+#: src/ui_parts/tab_bar.gd
+msgid "Create tab"
+msgstr "Създай раздел"
+
+#: src/ui_parts/tab_bar.gd
+msgid "Close all other tabs"
+msgstr "Затвори другите раздели"
+
+#: src/ui_parts/tab_bar.gd
+msgid "Close tabs to the left"
+msgstr "Затвори разделите отляво"
+
+#: src/ui_parts/tab_bar.gd
+msgid "Close tabs to the right"
+msgstr "Затвори разделите отдясно"
+
+#: src/ui_parts/tab_bar.gd src/utils/TranslationUtils.gd
+msgid "Create a new tab"
+msgstr "Създай нов раздел"
+
+#: src/ui_parts/tab_bar.gd
+msgid "This SVG is not bound to a location on the computer yet."
+msgstr "Това SVG не е свързано с местоположение на компютъра."
+
#: src/ui_parts/update_menu.gd
msgid "Include prereleases"
msgstr "Включи предварителните издания"
@@ -840,6 +872,18 @@ msgstr "GodSVG е актуален."
msgid "New versions"
msgstr "Нови версии"
+#: src/ui_parts/zoom_menu.gd src/utils/TranslationUtils.gd
+msgid "Zoom out"
+msgstr "Намали"
+
+#: src/ui_parts/zoom_menu.gd src/utils/TranslationUtils.gd
+msgid "Zoom in"
+msgstr "Увеличи"
+
+#: src/ui_parts/zoom_menu.gd src/utils/TranslationUtils.gd
+msgid "Zoom reset"
+msgstr "Рестартирай мащаба"
+
#: src/ui_widgets/BetterLineEdit.gd src/ui_widgets/BetterTextEdit.gd
#: src/utils/TranslationUtils.gd
msgid "Undo"
@@ -862,6 +906,14 @@ msgstr "Копирай"
msgid "Paste"
msgstr "Постави"
+#: src/ui_widgets/choose_name_dialog.gd
+msgid "Create"
+msgstr "Създай"
+
+#: src/ui_widgets/code_editor.gd src/utils/TranslationUtils.gd
+msgid "Copy all text"
+msgstr "Копирай всичкия текст"
+
#: src/ui_widgets/color_popup.gd
msgid "Search color"
msgstr "Търсене на цвят"
@@ -878,6 +930,10 @@ msgstr "Промени името на цвета"
msgid "Delete color"
msgstr "Изтрий цвета"
+#: src/ui_widgets/configure_color_popup.gd src/ui_widgets/palette_config.gd
+msgid "Unnamed"
+msgstr "Неименуван"
+
#: src/ui_widgets/good_color_picker.gd
msgid "Color keywords"
msgstr "Ключови цветове"
@@ -910,6 +966,11 @@ msgstr "Копирай като XML"
msgid "Apply Preset"
msgstr "Приложи шаблон"
+#: src/ui_widgets/path_popup.gd src/ui_widgets/pathdata_field.gd
+#: src/utils/TranslationUtils.gd
+msgid "Relative"
+msgstr "Относително"
+
#: src/ui_widgets/pathdata_field.gd
msgid "No path data"
msgstr "Няма пътека"
@@ -918,10 +979,6 @@ msgstr "Няма пътека"
msgid "Absolute"
msgstr "Абсолютно"
-#: src/ui_widgets/pathdata_field.gd src/utils/TranslationUtils.gd
-msgid "Relative"
-msgstr "Относително"
-
#: src/ui_widgets/points_field.gd
msgid "No points"
msgstr "Няма точки"
@@ -986,45 +1043,55 @@ msgstr "Файлът не можа да бъде отворен."
msgid "Check if the file still exists in the selected file path."
msgstr "Проверете дали файлът все още съществува на избраното място."
+#: src/utils/FileUtils.gd
+msgid "The imported file is already being edited inside GodSVG."
+msgstr "Импортираният файл вече се редактира в GodSVG."
+
+#: src/utils/FileUtils.gd
+msgid "If you want to apply the unsaved file state, use \"Reset SVG\" instead."
+msgstr ""
+"Ако искаш да приложиш незаписаното състояние на файла, използвай \"Reset "
+"SVG\"."
+
#: src/utils/TranslationUtils.gd
-msgid "Open SVG file"
-msgstr "Отвори SVG файла"
+msgid "Select the next tab"
+msgstr "Избери раздела отпред"
#: src/utils/TranslationUtils.gd
-msgid "Select all elements"
-msgstr "Избери всички елементи"
+msgid "Select the previous tab"
+msgstr "Избери раздела отзад"
#: src/utils/TranslationUtils.gd
-msgid "Duplicate the selected elements"
-msgstr "Дублирай избраните елементи"
+msgid "Open SVG externally"
+msgstr "Отвори SVG с външно приложение"
#: src/utils/TranslationUtils.gd
-msgid "Delete the selection"
-msgstr "Изтрий селекцията"
+msgid "Show SVG in File Manager"
+msgstr "Покажи SVG-то във Файловия мениджър"
#: src/utils/TranslationUtils.gd
-msgid "Move the selected elements up"
-msgstr "Премести избраните елементи нагоре"
+msgid "Select all"
+msgstr "Избери всички"
#: src/utils/TranslationUtils.gd
-msgid "Move the selected elements down"
-msgstr "Премести избраните елементи надолу"
+msgid "Duplicate the selection"
+msgstr "Изтрий селекцията"
#: src/utils/TranslationUtils.gd
-msgid "Find"
-msgstr "Намери"
+msgid "Delete the selection"
+msgstr "Изтрий селекцията"
#: src/utils/TranslationUtils.gd
-msgid "Zoom in"
-msgstr "Увеличи"
+msgid "Move the selection up"
+msgstr "Премести селекцията нагоре"
#: src/utils/TranslationUtils.gd
-msgid "Zoom out"
-msgstr "Намали"
+msgid "Move the selection down"
+msgstr "Премести селекцията надолу"
#: src/utils/TranslationUtils.gd
-msgid "Zoom reset"
-msgstr "Рестартирай мащаба"
+msgid "Find"
+msgstr "Намери"
#: src/utils/TranslationUtils.gd
msgid "Show rasterized SVG"
@@ -1121,9 +1188,3 @@ msgstr ""
#: src/utils/TranslationUtils.gd
msgid "The file extension is empty. Only {extension_list} files are supported."
msgstr "Форматът е празен. Единствено {extension_list} файловете са подържани."
-
-#~ msgid "Enable the color"
-#~ msgstr "Включи цвета"
-
-#~ msgid "Disable the color"
-#~ msgstr "Изключи цвета"
diff --git a/assets/translations/de.po b/assets/translations/de.po
index 30ff28b..c92c074 100644
--- a/assets/translations/de.po
+++ b/assets/translations/de.po
@@ -35,7 +35,7 @@ msgstr "Nach Aktualisierungen suchen?"
msgid "This requires GodSVG to connect to the internet."
msgstr "Hierfür muss sich GodSVG mit dem Internet verbinden."
-#: src/autoload/HandlerGUI.gd src/ui_parts/alert_dialog.gd
+#: src/autoload/HandlerGUI.gd src/ui_widgets/alert_dialog.gd
msgid "OK"
msgstr "OK"
@@ -44,7 +44,7 @@ msgid "Export SVG"
msgstr "SVG exportieren"
#: src/autoload/HandlerGUI.gd src/ui_parts/export_menu.gd
-#: src/utils/TranslationUtils.gd
+#: src/ui_parts/global_actions.gd src/utils/TranslationUtils.gd
msgid "Export"
msgstr "Exportieren"
@@ -54,35 +54,59 @@ msgid ""
"you want to proceed?"
msgstr ""
-#: src/autoload/Indications.gd
-msgid "View In List"
+#: src/autoload/State.gd src/ui_parts/good_file_dialog.gd
+#: src/ui_widgets/alert_dialog.gd
+msgid "Alert!"
+msgstr "Achtung!"
+
+#: src/autoload/State.gd src/ui_parts/tab_bar.gd src/utils/TranslationUtils.gd
+#, fuzzy
+msgid "Close tab"
+msgstr "Pfad schließen"
+
+#: src/autoload/State.gd
+msgid "Restore"
+msgstr ""
+
+#: src/autoload/State.gd
+msgid "View in List"
msgstr "Als Liste ansehen"
-#: src/autoload/Indications.gd
+#: src/autoload/State.gd
msgid "Duplicate"
msgstr "Duplizieren"
-#: src/autoload/Indications.gd
+#: src/autoload/State.gd
msgid "Convert To"
msgstr "Konvertieren zu"
-#: src/autoload/Indications.gd src/ui_widgets/palette_config.gd
+#: src/autoload/State.gd src/ui_widgets/palette_config.gd
msgid "Move Up"
msgstr "Nach oben"
-#: src/autoload/Indications.gd src/ui_widgets/palette_config.gd
+#: src/autoload/State.gd src/ui_widgets/palette_config.gd
msgid "Move Down"
msgstr "Nach unten"
-#: src/autoload/Indications.gd src/ui_widgets/palette_config.gd
+#: src/autoload/State.gd src/ui_widgets/palette_config.gd
#: src/ui_widgets/setting_shortcut.gd src/ui_widgets/transform_popup.gd
msgid "Delete"
msgstr "Löschen"
-#: src/autoload/Indications.gd src/ui_widgets/transform_popup.gd
+#: src/autoload/State.gd src/ui_widgets/transform_popup.gd
msgid "Insert After"
msgstr "Danach einsetzen"
+#: src/autoload/State.gd
+msgid "The last edited state of this tab could not be found."
+msgstr ""
+
+#: src/autoload/State.gd
+msgid ""
+"The tab is bound to the file path {file_path}. Do you want to restore from "
+"this path?"
+msgstr ""
+
#: src/config_classes/Formatter.gd
msgid "Compact"
msgstr "Kompakt"
@@ -119,6 +143,10 @@ msgstr "3-stelligen oder 6-stelligen Hex"
msgid "6-digit hex"
msgstr "6-stelligen Hex"
+#: src/config_classes/TabData.gd
+msgid "Unsaved"
+msgstr ""
+
#: src/data_classes/BasicXNode.gd
msgid "Comment"
msgstr "Kommentar"
@@ -140,18 +168,15 @@ msgstr "Diese Gruppe hat keine Elemente."
msgid "This group has only one element."
msgstr "Diese Gruppe hat nur ein Element."
-#: src/data_classes/ElementLinearGradient.gd
-#: src/data_classes/ElementRadialGradient.gd
+#: src/data_classes/GradientUtils.gd
msgid "No \"id\" attribute defined."
msgstr "Kein \"id\" Attribut definiert."
-#: src/data_classes/ElementLinearGradient.gd
-#: src/data_classes/ElementRadialGradient.gd
+#: src/data_classes/GradientUtils.gd
msgid "No elements under this gradient."
msgstr "Keine Elemente unter diesem Farbverlauf."
-#: src/data_classes/ElementLinearGradient.gd
-#: src/data_classes/ElementRadialGradient.gd
+#: src/data_classes/GradientUtils.gd
msgid "This gradient is a solid color."
msgstr "Dieser Farbverlauf ist einfarbig."
@@ -163,6 +188,12 @@ msgstr "Beschreibt kein SVG."
msgid "Improper nesting."
msgstr "Ungültige Formatierung."
+#: src/ui_parts/about_menu.gd src/ui_parts/good_file_dialog.gd
+#: src/ui_parts/settings_menu.gd src/ui_parts/shortcut_panel_config.gd
+#: src/ui_parts/update_menu.gd
+msgid "Close"
+msgstr "Schließen"
+
#: src/ui_parts/about_menu.gd
msgid "Project Founder and Manager"
msgstr "Projektgründer und Manager"
@@ -203,51 +234,6 @@ msgstr "Godot Drittanbieter-Komponente"
msgid "Third-party licenses"
msgstr "Drittanbieter-Lizenzen"
-#: src/ui_parts/alert_dialog.gd src/ui_parts/good_file_dialog.gd
-msgid "Alert!"
-msgstr "Achtung!"
-
-#: src/ui_parts/choose_name_dialog.gd src/ui_parts/confirm_dialog.gd
-#: src/ui_parts/export_menu.gd src/ui_parts/import_warning_menu.gd
-msgid "Cancel"
-msgstr "Abbrechen"
-
-#: src/ui_parts/choose_name_dialog.gd
-msgid "Create"
-msgstr "Erstellen"
-
-#: src/ui_parts/code_editor.gd src/ui_parts/good_file_dialog.gd
-msgid "Save SVG"
-msgstr "SVG speichern"
-
-#: src/ui_parts/code_editor.gd
-msgid "Open file"
-msgstr "Datei öffnen"
-
-#: src/ui_parts/code_editor.gd src/utils/TranslationUtils.gd
-msgid "Reset SVG"
-msgstr "SVG zurücksetzen"
-
-#: src/ui_parts/code_editor.gd src/utils/TranslationUtils.gd
-msgid "Clear saving path"
-msgstr "Speicherpfad löschen"
-
-#: src/ui_parts/code_editor.gd src/utils/TranslationUtils.gd
-msgid "Optimize"
-msgstr "Optimieren"
-
-#: src/ui_parts/code_editor.gd src/utils/TranslationUtils.gd
-msgid "Copy all text"
-msgstr "Text kopieren"
-
-#: src/ui_parts/code_editor.gd src/utils/TranslationUtils.gd
-msgid "Clear SVG"
-msgstr "SVG zurücksetzen"
-
-#: src/ui_parts/display.gd
-msgid "Settings"
-msgstr "Einstellungen"
-
#: src/ui_parts/display.gd
msgid "Visuals"
msgstr "Visuelles"
@@ -276,33 +262,15 @@ msgstr "Griffe anzeigen"
msgid "Rasterized SVG"
msgstr "SVG rasterisieren"
-#: src/ui_parts/display.gd
-msgid "About…"
-msgstr "Über…"
-
#: src/ui_parts/display.gd
msgid "Snap size"
msgstr "Rastergröße"
-#: src/ui_parts/display.gd src/utils/TranslationUtils.gd
-msgid "Check for updates"
-msgstr "Nach Aktualisierungen suchen"
-
-#: src/ui_parts/display.gd
-msgid "Donate…"
-msgstr "Spenden…"
-
-#: src/ui_parts/display.gd
-msgid "GodSVG repository"
-msgstr "GodSVG Repository"
-
-#: src/ui_parts/display.gd
-msgid "GodSVG website"
-msgstr "GodSVG Webseite"
-
-#: src/ui_parts/display.gd
-msgid "View savedata"
-msgstr "Speicherdaten anzeigen"
+#: src/ui_parts/donate_menu.gd src/ui_parts/export_menu.gd
+#: src/ui_parts/import_warning_menu.gd src/ui_widgets/choose_name_dialog.gd
+#: src/ui_widgets/confirm_dialog.gd
+msgid "Cancel"
+msgstr "Abbrechen"
#: src/ui_parts/element_container.gd
msgid "New element"
@@ -312,11 +280,6 @@ msgstr "Neues Element"
msgid "Dimensions"
msgstr ""
-#: src/ui_parts/export_menu.gd src/ui_widgets/configure_color_popup.gd
-#: src/ui_widgets/palette_config.gd
-msgid "Unnamed"
-msgstr "Unbenannt"
-
#: src/ui_parts/export_menu.gd
msgid "Size"
msgstr "Größe"
@@ -353,25 +316,58 @@ msgstr ""
msgid "Preview image size is limited to {dimensions}"
msgstr ""
-#: src/ui_parts/global_menu.gd src/ui_parts/settings_menu.gd
-msgid "File"
-msgstr "Datei"
+#: src/ui_parts/global_actions.gd src/ui_parts/import_warning_menu.gd
+#: src/utils/TranslationUtils.gd
+msgid "Import"
+msgstr "Importieren"
-#: src/ui_parts/global_menu.gd src/ui_parts/settings_menu.gd
-msgid "Edit"
-msgstr "Bearbeiten"
+#: src/ui_parts/global_actions.gd
+msgid "Settings"
+msgstr "Einstellungen"
-#: src/ui_parts/global_menu.gd src/ui_parts/settings_menu.gd
-msgid "Tool"
-msgstr "Werkzeuge"
+#: src/ui_parts/global_actions.gd src/utils/TranslationUtils.gd
+msgid "Optimize"
+msgstr "Optimieren"
-#: src/ui_parts/global_menu.gd src/ui_parts/settings_menu.gd
-msgid "View"
-msgstr "Ansicht"
+#: src/ui_parts/global_actions.gd src/ui_parts/good_file_dialog.gd
+msgid "Save SVG"
+msgstr "SVG speichern"
-#: src/ui_parts/global_menu.gd
-msgid "Snap"
-msgstr "Einrasten"
+#: src/ui_parts/global_actions.gd src/utils/TranslationUtils.gd
+msgid "Reset SVG"
+msgstr "SVG zurücksetzen"
+
+#: src/ui_parts/global_actions.gd src/ui_parts/tab_bar.gd
+msgid "Open externally"
+msgstr ""
+
+#: src/ui_parts/global_actions.gd src/ui_parts/tab_bar.gd
+msgid "Show in File Manager"
+msgstr ""
+
+#: src/ui_parts/global_actions.gd
+msgid "About…"
+msgstr "Über…"
+
+#: src/ui_parts/global_actions.gd src/utils/TranslationUtils.gd
+msgid "Check for updates"
+msgstr "Nach Aktualisierungen suchen"
+
+#: src/ui_parts/global_actions.gd
+msgid "Donate…"
+msgstr "Spenden…"
+
+#: src/ui_parts/global_actions.gd
+msgid "GodSVG repository"
+msgstr "GodSVG Repository"
+
+#: src/ui_parts/global_actions.gd
+msgid "GodSVG website"
+msgstr "GodSVG Webseite"
+
+#: src/ui_parts/global_actions.gd
+msgid "View savedata"
+msgstr "Speicherdaten anzeigen"
#: src/ui_parts/good_file_dialog.gd
msgid "Go to parent folder"
@@ -401,11 +397,6 @@ msgstr "XML Datei auswählen"
msgid "Select an image"
msgstr "Bild auswählen"
-#: src/ui_parts/good_file_dialog.gd src/ui_parts/settings_menu.gd
-#: src/ui_parts/shortcut_panel_config.gd src/ui_parts/update_menu.gd
-msgid "Close"
-msgstr "Schließen"
-
#: src/ui_parts/good_file_dialog.gd
msgid "Select"
msgstr "Auswählen"
@@ -462,10 +453,6 @@ msgstr "Neue Form"
msgid "Import Problems"
msgstr "Probleme beim Importieren"
-#: src/ui_parts/import_warning_menu.gd src/utils/TranslationUtils.gd
-msgid "Import"
-msgstr "Importieren"
-
#: src/ui_parts/import_warning_menu.gd
msgid "Unrecognized element"
msgstr "Unerkanntes Element"
@@ -482,6 +469,26 @@ msgstr "Syntaxfehler"
msgid "Add element"
msgstr "Element hinzufügen"
+#: src/ui_parts/mac_menu.gd src/ui_parts/settings_menu.gd
+msgid "File"
+msgstr "Datei"
+
+#: src/ui_parts/mac_menu.gd src/ui_parts/settings_menu.gd
+msgid "Edit"
+msgstr "Bearbeiten"
+
+#: src/ui_parts/mac_menu.gd src/ui_parts/settings_menu.gd
+msgid "Tool"
+msgstr "Werkzeuge"
+
+#: src/ui_parts/mac_menu.gd src/ui_parts/settings_menu.gd
+msgid "View"
+msgstr "Ansicht"
+
+#: src/ui_parts/mac_menu.gd
+msgid "Snap"
+msgstr "Einrasten"
+
#: src/ui_parts/settings_menu.gd
msgid "Formatting"
msgstr "Formatierung"
@@ -817,6 +824,33 @@ msgstr "Horizontale Linie zu"
msgid "Vertical strip"
msgstr "Vertikale Linue zu"
+#: src/ui_parts/tab_bar.gd
+#, fuzzy
+msgid "Create tab"
+msgstr "Erstellen"
+
+#: src/ui_parts/tab_bar.gd
+#, fuzzy
+msgid "Close all other tabs"
+msgstr "Text kopieren"
+
+#: src/ui_parts/tab_bar.gd
+msgid "Close tabs to the left"
+msgstr ""
+
+#: src/ui_parts/tab_bar.gd
+msgid "Close tabs to the right"
+msgstr ""
+
+#: src/ui_parts/tab_bar.gd src/utils/TranslationUtils.gd
+#, fuzzy
+msgid "Create a new tab"
+msgstr "Neuen Ordner erstellen"
+
+#: src/ui_parts/tab_bar.gd
+msgid "This SVG is not bound to a location on the computer yet."
+msgstr ""
+
#: src/ui_parts/update_menu.gd
msgid "Include prereleases"
msgstr "Vorabversionen miteinbeziehen"
@@ -845,6 +879,18 @@ msgstr "GodSVG ist auf dem neuesten Stand."
msgid "New versions"
msgstr "Neue Versionen"
+#: src/ui_parts/zoom_menu.gd src/utils/TranslationUtils.gd
+msgid "Zoom out"
+msgstr "Herauszoomen"
+
+#: src/ui_parts/zoom_menu.gd src/utils/TranslationUtils.gd
+msgid "Zoom in"
+msgstr "Hineinzoomen"
+
+#: src/ui_parts/zoom_menu.gd src/utils/TranslationUtils.gd
+msgid "Zoom reset"
+msgstr "Zoom zurücksetzen"
+
#: src/ui_widgets/BetterLineEdit.gd src/ui_widgets/BetterTextEdit.gd
#: src/utils/TranslationUtils.gd
msgid "Undo"
@@ -867,6 +913,14 @@ msgstr "Kopieren"
msgid "Paste"
msgstr "Einfügen"
+#: src/ui_widgets/choose_name_dialog.gd
+msgid "Create"
+msgstr "Erstellen"
+
+#: src/ui_widgets/code_editor.gd src/utils/TranslationUtils.gd
+msgid "Copy all text"
+msgstr "Text kopieren"
+
#: src/ui_widgets/color_popup.gd
msgid "Search color"
msgstr "Farbe suchen"
@@ -883,6 +937,10 @@ msgstr "Farbnamen ändern"
msgid "Delete color"
msgstr "Farbe löschen"
+#: src/ui_widgets/configure_color_popup.gd src/ui_widgets/palette_config.gd
+msgid "Unnamed"
+msgstr "Unbenannt"
+
#: src/ui_widgets/good_color_picker.gd
#, fuzzy
msgid "Color keywords"
@@ -916,6 +974,11 @@ msgstr "Kopiere als XML"
msgid "Apply Preset"
msgstr "Voreinstellung anwenden"
+#: src/ui_widgets/path_popup.gd src/ui_widgets/pathdata_field.gd
+#: src/utils/TranslationUtils.gd
+msgid "Relative"
+msgstr "Relativ"
+
#: src/ui_widgets/pathdata_field.gd
msgid "No path data"
msgstr "Keine Pfaddaten"
@@ -924,10 +987,6 @@ msgstr "Keine Pfaddaten"
msgid "Absolute"
msgstr "Absolut"
-#: src/ui_widgets/pathdata_field.gd src/utils/TranslationUtils.gd
-msgid "Relative"
-msgstr "Relativ"
-
#: src/ui_widgets/points_field.gd
msgid "No points"
msgstr "Keine Punkte"
@@ -992,47 +1051,59 @@ msgstr "Die Datei konnte nicht geöffnet werden."
msgid "Check if the file still exists in the selected file path."
msgstr "Prüfen, ob die Datei im ausgewählten Dateipfad noch existiert."
+#: src/utils/FileUtils.gd
+msgid "The imported file is already being edited inside GodSVG."
+msgstr ""
+
+#: src/utils/FileUtils.gd
+msgid "If you want to apply the unsaved file state, use \"Reset SVG\" instead."
+msgstr ""
+
+#: src/utils/TranslationUtils.gd
+msgid "Select the next tab"
+msgstr ""
+
+#: src/utils/TranslationUtils.gd
+msgid "Select the previous tab"
+msgstr ""
+
#: src/utils/TranslationUtils.gd
#, fuzzy
-msgid "Open SVG file"
-msgstr "Datei öffnen"
+msgid "Open SVG externally"
+msgstr "GodSVG-Repository öffnen"
#: src/utils/TranslationUtils.gd
-msgid "Select all elements"
-msgstr "Alle Elemente auswählen"
+msgid "Show SVG in File Manager"
+msgstr ""
#: src/utils/TranslationUtils.gd
-msgid "Duplicate the selected elements"
-msgstr "Ausgewählte Elemente duplizieren"
+#, fuzzy
+msgid "Select all"
+msgstr "Auswählen"
+
+#: src/utils/TranslationUtils.gd
+#, fuzzy
+msgid "Duplicate the selection"
+msgstr "Auswahl löschen"
#: src/utils/TranslationUtils.gd
msgid "Delete the selection"
msgstr "Auswahl löschen"
#: src/utils/TranslationUtils.gd
-msgid "Move the selected elements up"
+#, fuzzy
+msgid "Move the selection up"
msgstr "Ausgewählte Tags nach oben verschieben"
#: src/utils/TranslationUtils.gd
-msgid "Move the selected elements down"
+#, fuzzy
+msgid "Move the selection down"
msgstr "Ausgewählte Tags nach unten verschieben"
#: src/utils/TranslationUtils.gd
msgid "Find"
msgstr "Finden"
-#: src/utils/TranslationUtils.gd
-msgid "Zoom in"
-msgstr "Hineinzoomen"
-
-#: src/utils/TranslationUtils.gd
-msgid "Zoom out"
-msgstr "Herauszoomen"
-
-#: src/utils/TranslationUtils.gd
-msgid "Zoom reset"
-msgstr "Zoom zurücksetzen"
-
#: src/utils/TranslationUtils.gd
msgid "Show rasterized SVG"
msgstr "Rasterisiertes SVG anzeigen"
@@ -1131,6 +1202,29 @@ msgstr ""
"Die Dateierweiterung ist leer. Nur {extension_list} Dateien werden "
"unterstützt."
+#, fuzzy
+#~ msgid "Add new tab"
+#~ msgstr "Neues Element hinzufügen"
+
+#~ msgid "Open file"
+#~ msgstr "Datei öffnen"
+
+#~ msgid "Clear saving path"
+#~ msgstr "Speicherpfad löschen"
+
+#~ msgid "Clear SVG"
+#~ msgstr "SVG zurücksetzen"
+
+#, fuzzy
+#~ msgid "Open SVG file"
+#~ msgstr "Datei öffnen"
+
+#~ msgid "Select all elements"
+#~ msgstr "Alle Elemente auswählen"
+
+#~ msgid "Duplicate the selected elements"
+#~ msgstr "Ausgewählte Elemente duplizieren"
+
#~ msgid "Enable the color"
#~ msgstr "Farbe aktivieren"
@@ -1192,9 +1286,6 @@ msgstr ""
#~ msgid "New tag"
#~ msgstr "Neues Element hinzufügen"
-#~ msgid "Add new tag"
-#~ msgstr "Neues Element hinzufügen"
-
#~ msgid "Paths"
#~ msgstr "Pfade"
diff --git a/assets/translations/en.po b/assets/translations/en.po
index 84187b7..649147f 100644
--- a/assets/translations/en.po
+++ b/assets/translations/en.po
@@ -35,7 +35,7 @@ msgstr ""
msgid "This requires GodSVG to connect to the internet."
msgstr ""
-#: src/autoload/HandlerGUI.gd src/ui_parts/alert_dialog.gd
+#: src/autoload/HandlerGUI.gd src/ui_widgets/alert_dialog.gd
msgid "OK"
msgstr ""
@@ -44,7 +44,7 @@ msgid "Export SVG"
msgstr ""
#: src/autoload/HandlerGUI.gd src/ui_parts/export_menu.gd
-#: src/utils/TranslationUtils.gd
+#: src/ui_parts/global_actions.gd src/utils/TranslationUtils.gd
msgid "Export"
msgstr ""
@@ -54,35 +54,58 @@ msgid ""
"you want to proceed?"
msgstr ""
-#: src/autoload/Indications.gd
-msgid "View In List"
+#: src/autoload/State.gd src/ui_parts/good_file_dialog.gd
+#: src/ui_widgets/alert_dialog.gd
+msgid "Alert!"
+msgstr ""
+
+#: src/autoload/State.gd src/ui_parts/tab_bar.gd src/utils/TranslationUtils.gd
+msgid "Close tab"
+msgstr ""
+
+#: src/autoload/State.gd
+msgid "Restore"
msgstr ""
-#: src/autoload/Indications.gd
+#: src/autoload/State.gd
+msgid "View in List"
+msgstr ""
+
+#: src/autoload/State.gd
msgid "Duplicate"
msgstr ""
-#: src/autoload/Indications.gd
+#: src/autoload/State.gd
msgid "Convert To"
msgstr ""
-#: src/autoload/Indications.gd src/ui_widgets/palette_config.gd
+#: src/autoload/State.gd src/ui_widgets/palette_config.gd
msgid "Move Up"
msgstr ""
-#: src/autoload/Indications.gd src/ui_widgets/palette_config.gd
+#: src/autoload/State.gd src/ui_widgets/palette_config.gd
msgid "Move Down"
msgstr ""
-#: src/autoload/Indications.gd src/ui_widgets/palette_config.gd
+#: src/autoload/State.gd src/ui_widgets/palette_config.gd
#: src/ui_widgets/setting_shortcut.gd src/ui_widgets/transform_popup.gd
msgid "Delete"
msgstr ""
-#: src/autoload/Indications.gd src/ui_widgets/transform_popup.gd
+#: src/autoload/State.gd src/ui_widgets/transform_popup.gd
msgid "Insert After"
msgstr ""
+#: src/autoload/State.gd
+msgid "The last edited state of this tab could not be found."
+msgstr ""
+
+#: src/autoload/State.gd
+msgid ""
+"The tab is bound to the file path {file_path}. Do you want to restore from "
+"this path?"
+msgstr ""
+
#: src/config_classes/Formatter.gd
msgid "Compact"
msgstr ""
@@ -119,6 +142,10 @@ msgstr ""
msgid "6-digit hex"
msgstr ""
+#: src/config_classes/TabData.gd
+msgid "Unsaved"
+msgstr ""
+
#: src/data_classes/BasicXNode.gd
msgid "Comment"
msgstr ""
@@ -139,18 +166,15 @@ msgstr ""
msgid "This group has only one element."
msgstr ""
-#: src/data_classes/ElementLinearGradient.gd
-#: src/data_classes/ElementRadialGradient.gd
+#: src/data_classes/GradientUtils.gd
msgid "No \"id\" attribute defined."
msgstr ""
-#: src/data_classes/ElementLinearGradient.gd
-#: src/data_classes/ElementRadialGradient.gd
+#: src/data_classes/GradientUtils.gd
msgid "No elements under this gradient."
msgstr ""
-#: src/data_classes/ElementLinearGradient.gd
-#: src/data_classes/ElementRadialGradient.gd
+#: src/data_classes/GradientUtils.gd
msgid "This gradient is a solid color."
msgstr ""
@@ -162,6 +186,12 @@ msgstr ""
msgid "Improper nesting."
msgstr ""
+#: src/ui_parts/about_menu.gd src/ui_parts/good_file_dialog.gd
+#: src/ui_parts/settings_menu.gd src/ui_parts/shortcut_panel_config.gd
+#: src/ui_parts/update_menu.gd
+msgid "Close"
+msgstr ""
+
#: src/ui_parts/about_menu.gd
msgid "Project Founder and Manager"
msgstr ""
@@ -202,51 +232,6 @@ msgstr ""
msgid "Third-party licenses"
msgstr ""
-#: src/ui_parts/alert_dialog.gd src/ui_parts/good_file_dialog.gd
-msgid "Alert!"
-msgstr ""
-
-#: src/ui_parts/choose_name_dialog.gd src/ui_parts/confirm_dialog.gd
-#: src/ui_parts/export_menu.gd src/ui_parts/import_warning_menu.gd
-msgid "Cancel"
-msgstr ""
-
-#: src/ui_parts/choose_name_dialog.gd
-msgid "Create"
-msgstr ""
-
-#: src/ui_parts/code_editor.gd src/ui_parts/good_file_dialog.gd
-msgid "Save SVG"
-msgstr ""
-
-#: src/ui_parts/code_editor.gd
-msgid "Open file"
-msgstr ""
-
-#: src/ui_parts/code_editor.gd src/utils/TranslationUtils.gd
-msgid "Reset SVG"
-msgstr ""
-
-#: src/ui_parts/code_editor.gd src/utils/TranslationUtils.gd
-msgid "Clear saving path"
-msgstr ""
-
-#: src/ui_parts/code_editor.gd src/utils/TranslationUtils.gd
-msgid "Optimize"
-msgstr ""
-
-#: src/ui_parts/code_editor.gd src/utils/TranslationUtils.gd
-msgid "Copy all text"
-msgstr ""
-
-#: src/ui_parts/code_editor.gd src/utils/TranslationUtils.gd
-msgid "Clear SVG"
-msgstr ""
-
-#: src/ui_parts/display.gd
-msgid "Settings"
-msgstr ""
-
#: src/ui_parts/display.gd
msgid "Visuals"
msgstr ""
@@ -275,32 +260,14 @@ msgstr ""
msgid "Rasterized SVG"
msgstr ""
-#: src/ui_parts/display.gd
-msgid "About…"
-msgstr ""
-
#: src/ui_parts/display.gd
msgid "Snap size"
msgstr ""
-#: src/ui_parts/display.gd src/utils/TranslationUtils.gd
-msgid "Check for updates"
-msgstr ""
-
-#: src/ui_parts/display.gd
-msgid "Donate…"
-msgstr ""
-
-#: src/ui_parts/display.gd
-msgid "GodSVG repository"
-msgstr ""
-
-#: src/ui_parts/display.gd
-msgid "GodSVG website"
-msgstr ""
-
-#: src/ui_parts/display.gd
-msgid "View savedata"
+#: src/ui_parts/donate_menu.gd src/ui_parts/export_menu.gd
+#: src/ui_parts/import_warning_menu.gd src/ui_widgets/choose_name_dialog.gd
+#: src/ui_widgets/confirm_dialog.gd
+msgid "Cancel"
msgstr ""
#: src/ui_parts/element_container.gd
@@ -311,11 +278,6 @@ msgstr ""
msgid "Dimensions"
msgstr ""
-#: src/ui_parts/export_menu.gd src/ui_widgets/configure_color_popup.gd
-#: src/ui_widgets/palette_config.gd
-msgid "Unnamed"
-msgstr ""
-
#: src/ui_parts/export_menu.gd
msgid "Size"
msgstr ""
@@ -352,24 +314,57 @@ msgstr ""
msgid "Preview image size is limited to {dimensions}"
msgstr ""
-#: src/ui_parts/global_menu.gd src/ui_parts/settings_menu.gd
-msgid "File"
+#: src/ui_parts/global_actions.gd src/ui_parts/import_warning_menu.gd
+#: src/utils/TranslationUtils.gd
+msgid "Import"
msgstr ""
-#: src/ui_parts/global_menu.gd src/ui_parts/settings_menu.gd
-msgid "Edit"
+#: src/ui_parts/global_actions.gd
+msgid "Settings"
msgstr ""
-#: src/ui_parts/global_menu.gd src/ui_parts/settings_menu.gd
-msgid "Tool"
+#: src/ui_parts/global_actions.gd src/utils/TranslationUtils.gd
+msgid "Optimize"
msgstr ""
-#: src/ui_parts/global_menu.gd src/ui_parts/settings_menu.gd
-msgid "View"
+#: src/ui_parts/global_actions.gd src/ui_parts/good_file_dialog.gd
+msgid "Save SVG"
msgstr ""
-#: src/ui_parts/global_menu.gd
-msgid "Snap"
+#: src/ui_parts/global_actions.gd src/utils/TranslationUtils.gd
+msgid "Reset SVG"
+msgstr ""
+
+#: src/ui_parts/global_actions.gd src/ui_parts/tab_bar.gd
+msgid "Open externally"
+msgstr ""
+
+#: src/ui_parts/global_actions.gd src/ui_parts/tab_bar.gd
+msgid "Show in File Manager"
+msgstr ""
+
+#: src/ui_parts/global_actions.gd
+msgid "About…"
+msgstr ""
+
+#: src/ui_parts/global_actions.gd src/utils/TranslationUtils.gd
+msgid "Check for updates"
+msgstr ""
+
+#: src/ui_parts/global_actions.gd
+msgid "Donate…"
+msgstr ""
+
+#: src/ui_parts/global_actions.gd
+msgid "GodSVG repository"
+msgstr ""
+
+#: src/ui_parts/global_actions.gd
+msgid "GodSVG website"
+msgstr ""
+
+#: src/ui_parts/global_actions.gd
+msgid "View savedata"
msgstr ""
#: src/ui_parts/good_file_dialog.gd
@@ -400,11 +395,6 @@ msgstr ""
msgid "Select an image"
msgstr ""
-#: src/ui_parts/good_file_dialog.gd src/ui_parts/settings_menu.gd
-#: src/ui_parts/shortcut_panel_config.gd src/ui_parts/update_menu.gd
-msgid "Close"
-msgstr ""
-
#: src/ui_parts/good_file_dialog.gd
msgid "Select"
msgstr ""
@@ -459,10 +449,6 @@ msgstr ""
msgid "Import Problems"
msgstr ""
-#: src/ui_parts/import_warning_menu.gd src/utils/TranslationUtils.gd
-msgid "Import"
-msgstr ""
-
#: src/ui_parts/import_warning_menu.gd
msgid "Unrecognized element"
msgstr ""
@@ -479,6 +465,26 @@ msgstr ""
msgid "Add element"
msgstr ""
+#: src/ui_parts/mac_menu.gd src/ui_parts/settings_menu.gd
+msgid "File"
+msgstr ""
+
+#: src/ui_parts/mac_menu.gd src/ui_parts/settings_menu.gd
+msgid "Edit"
+msgstr ""
+
+#: src/ui_parts/mac_menu.gd src/ui_parts/settings_menu.gd
+msgid "Tool"
+msgstr ""
+
+#: src/ui_parts/mac_menu.gd src/ui_parts/settings_menu.gd
+msgid "View"
+msgstr ""
+
+#: src/ui_parts/mac_menu.gd
+msgid "Snap"
+msgstr ""
+
#: src/ui_parts/settings_menu.gd
msgid "Formatting"
msgstr ""
@@ -801,6 +807,30 @@ msgstr ""
msgid "Vertical strip"
msgstr ""
+#: src/ui_parts/tab_bar.gd
+msgid "Create tab"
+msgstr ""
+
+#: src/ui_parts/tab_bar.gd
+msgid "Close all other tabs"
+msgstr ""
+
+#: src/ui_parts/tab_bar.gd
+msgid "Close tabs to the left"
+msgstr ""
+
+#: src/ui_parts/tab_bar.gd
+msgid "Close tabs to the right"
+msgstr ""
+
+#: src/ui_parts/tab_bar.gd src/utils/TranslationUtils.gd
+msgid "Create a new tab"
+msgstr ""
+
+#: src/ui_parts/tab_bar.gd
+msgid "This SVG is not bound to a location on the computer yet."
+msgstr ""
+
#: src/ui_parts/update_menu.gd
msgid "Include prereleases"
msgstr ""
@@ -829,6 +859,18 @@ msgstr ""
msgid "New versions"
msgstr ""
+#: src/ui_parts/zoom_menu.gd src/utils/TranslationUtils.gd
+msgid "Zoom out"
+msgstr ""
+
+#: src/ui_parts/zoom_menu.gd src/utils/TranslationUtils.gd
+msgid "Zoom in"
+msgstr ""
+
+#: src/ui_parts/zoom_menu.gd src/utils/TranslationUtils.gd
+msgid "Zoom reset"
+msgstr ""
+
#: src/ui_widgets/BetterLineEdit.gd src/ui_widgets/BetterTextEdit.gd
#: src/utils/TranslationUtils.gd
msgid "Undo"
@@ -851,6 +893,14 @@ msgstr ""
msgid "Paste"
msgstr ""
+#: src/ui_widgets/choose_name_dialog.gd
+msgid "Create"
+msgstr ""
+
+#: src/ui_widgets/code_editor.gd src/utils/TranslationUtils.gd
+msgid "Copy all text"
+msgstr ""
+
#: src/ui_widgets/color_popup.gd
msgid "Search color"
msgstr ""
@@ -867,6 +917,10 @@ msgstr ""
msgid "Delete color"
msgstr ""
+#: src/ui_widgets/configure_color_popup.gd src/ui_widgets/palette_config.gd
+msgid "Unnamed"
+msgstr ""
+
#: src/ui_widgets/good_color_picker.gd
msgid "Color keywords"
msgstr ""
@@ -899,6 +953,11 @@ msgstr ""
msgid "Apply Preset"
msgstr ""
+#: src/ui_widgets/path_popup.gd src/ui_widgets/pathdata_field.gd
+#: src/utils/TranslationUtils.gd
+msgid "Relative"
+msgstr ""
+
#: src/ui_widgets/pathdata_field.gd
msgid "No path data"
msgstr ""
@@ -907,10 +966,6 @@ msgstr ""
msgid "Absolute"
msgstr ""
-#: src/ui_widgets/pathdata_field.gd src/utils/TranslationUtils.gd
-msgid "Relative"
-msgstr ""
-
#: src/ui_widgets/points_field.gd
msgid "No points"
msgstr ""
@@ -975,44 +1030,52 @@ msgstr ""
msgid "Check if the file still exists in the selected file path."
msgstr ""
+#: src/utils/FileUtils.gd
+msgid "The imported file is already being edited inside GodSVG."
+msgstr ""
+
+#: src/utils/FileUtils.gd
+msgid "If you want to apply the unsaved file state, use \"Reset SVG\" instead."
+msgstr ""
+
#: src/utils/TranslationUtils.gd
-msgid "Open SVG file"
+msgid "Select the next tab"
msgstr ""
#: src/utils/TranslationUtils.gd
-msgid "Select all elements"
+msgid "Select the previous tab"
msgstr ""
#: src/utils/TranslationUtils.gd
-msgid "Duplicate the selected elements"
+msgid "Open SVG externally"
msgstr ""
#: src/utils/TranslationUtils.gd
-msgid "Delete the selection"
+msgid "Show SVG in File Manager"
msgstr ""
#: src/utils/TranslationUtils.gd
-msgid "Move the selected elements up"
+msgid "Select all"
msgstr ""
#: src/utils/TranslationUtils.gd
-msgid "Move the selected elements down"
+msgid "Duplicate the selection"
msgstr ""
#: src/utils/TranslationUtils.gd
-msgid "Find"
+msgid "Delete the selection"
msgstr ""
#: src/utils/TranslationUtils.gd
-msgid "Zoom in"
+msgid "Move the selection up"
msgstr ""
#: src/utils/TranslationUtils.gd
-msgid "Zoom out"
+msgid "Move the selection down"
msgstr ""
#: src/utils/TranslationUtils.gd
-msgid "Zoom reset"
+msgid "Find"
msgstr ""
#: src/utils/TranslationUtils.gd
diff --git a/assets/translations/fr.po b/assets/translations/fr.po
index cb6a1e0..d0fa126 100644
--- a/assets/translations/fr.po
+++ b/assets/translations/fr.po
@@ -35,7 +35,7 @@ msgstr "Vérifier les mises à jour ?"
msgid "This requires GodSVG to connect to the internet."
msgstr "Ceci nécessite que GodSVG se connecte à Internet."
-#: src/autoload/HandlerGUI.gd src/ui_parts/alert_dialog.gd
+#: src/autoload/HandlerGUI.gd src/ui_widgets/alert_dialog.gd
msgid "OK"
msgstr "OK"
@@ -44,7 +44,7 @@ msgid "Export SVG"
msgstr "Exporter en SVG"
#: src/autoload/HandlerGUI.gd src/ui_parts/export_menu.gd
-#: src/utils/TranslationUtils.gd
+#: src/ui_parts/global_actions.gd src/utils/TranslationUtils.gd
msgid "Export"
msgstr "Exporter"
@@ -56,35 +56,59 @@ msgstr ""
"Le graphique ne peut être exporté qu'en SVG car sa taille n'est pas définie. "
"Voulez-vous continuer?"
-#: src/autoload/Indications.gd
-msgid "View In List"
+#: src/autoload/State.gd src/ui_parts/good_file_dialog.gd
+#: src/ui_widgets/alert_dialog.gd
+msgid "Alert!"
+msgstr "Alerte !"
+
+#: src/autoload/State.gd src/ui_parts/tab_bar.gd src/utils/TranslationUtils.gd
+#, fuzzy
+msgid "Close tab"
+msgstr "Fermer le chemin"
+
+#: src/autoload/State.gd
+msgid "Restore"
+msgstr ""
+
+#: src/autoload/State.gd
+msgid "View in List"
msgstr "Voir en liste"
-#: src/autoload/Indications.gd
+#: src/autoload/State.gd
msgid "Duplicate"
msgstr "Dupliquer"
-#: src/autoload/Indications.gd
+#: src/autoload/State.gd
msgid "Convert To"
msgstr "Convertir en"
-#: src/autoload/Indications.gd src/ui_widgets/palette_config.gd
+#: src/autoload/State.gd src/ui_widgets/palette_config.gd
msgid "Move Up"
msgstr "Remonter"
-#: src/autoload/Indications.gd src/ui_widgets/palette_config.gd
+#: src/autoload/State.gd src/ui_widgets/palette_config.gd
msgid "Move Down"
msgstr "Descendre"
-#: src/autoload/Indications.gd src/ui_widgets/palette_config.gd
+#: src/autoload/State.gd src/ui_widgets/palette_config.gd
#: src/ui_widgets/setting_shortcut.gd src/ui_widgets/transform_popup.gd
msgid "Delete"
msgstr "Supprimer"
-#: src/autoload/Indications.gd src/ui_widgets/transform_popup.gd
+#: src/autoload/State.gd src/ui_widgets/transform_popup.gd
msgid "Insert After"
msgstr "Insérer après"
+#: src/autoload/State.gd
+msgid "The last edited state of this tab could not be found."
+msgstr ""
+
+#: src/autoload/State.gd
+msgid ""
+"The tab is bound to the file path {file_path}. Do you want to restore from "
+"this path?"
+msgstr ""
+
#: src/config_classes/Formatter.gd
msgid "Compact"
msgstr "Compact"
@@ -121,6 +145,10 @@ msgstr "Héxadécimal de 3 ou 6 chiffres"
msgid "6-digit hex"
msgstr "Héxadécimal de 6 chiffres"
+#: src/config_classes/TabData.gd
+msgid "Unsaved"
+msgstr ""
+
#: src/data_classes/BasicXNode.gd
msgid "Comment"
msgstr "Commentaire"
@@ -141,18 +169,15 @@ msgstr "Ce groupe n'a aucun élément."
msgid "This group has only one element."
msgstr "Ce groupe n'a qu'un seul élément."
-#: src/data_classes/ElementLinearGradient.gd
-#: src/data_classes/ElementRadialGradient.gd
+#: src/data_classes/GradientUtils.gd
msgid "No \"id\" attribute defined."
msgstr "Aucun attribut « id » défini."
-#: src/data_classes/ElementLinearGradient.gd
-#: src/data_classes/ElementRadialGradient.gd
+#: src/data_classes/GradientUtils.gd
msgid "No elements under this gradient."
msgstr "Aucun élément en dessous de ce dégradé."
-#: src/data_classes/ElementLinearGradient.gd
-#: src/data_classes/ElementRadialGradient.gd
+#: src/data_classes/GradientUtils.gd
msgid "This gradient is a solid color."
msgstr "Ce dégradé est une couleur solide."
@@ -164,6 +189,12 @@ msgstr "Ne décrit pas un SVG."
msgid "Improper nesting."
msgstr "Formatage incorrect."
+#: src/ui_parts/about_menu.gd src/ui_parts/good_file_dialog.gd
+#: src/ui_parts/settings_menu.gd src/ui_parts/shortcut_panel_config.gd
+#: src/ui_parts/update_menu.gd
+msgid "Close"
+msgstr "Fermer"
+
#: src/ui_parts/about_menu.gd
msgid "Project Founder and Manager"
msgstr "Fondateur et gestionnaire du projet"
@@ -204,51 +235,6 @@ msgstr "Composants Godot tiers"
msgid "Third-party licenses"
msgstr "Licences tierces"
-#: src/ui_parts/alert_dialog.gd src/ui_parts/good_file_dialog.gd
-msgid "Alert!"
-msgstr "Alerte !"
-
-#: src/ui_parts/choose_name_dialog.gd src/ui_parts/confirm_dialog.gd
-#: src/ui_parts/export_menu.gd src/ui_parts/import_warning_menu.gd
-msgid "Cancel"
-msgstr "Annuler"
-
-#: src/ui_parts/choose_name_dialog.gd
-msgid "Create"
-msgstr "Créer"
-
-#: src/ui_parts/code_editor.gd src/ui_parts/good_file_dialog.gd
-msgid "Save SVG"
-msgstr "Sauvegarder le SVG"
-
-#: src/ui_parts/code_editor.gd
-msgid "Open file"
-msgstr "Ouvrir le fichier"
-
-#: src/ui_parts/code_editor.gd src/utils/TranslationUtils.gd
-msgid "Reset SVG"
-msgstr "Rénitialiser le SVG"
-
-#: src/ui_parts/code_editor.gd src/utils/TranslationUtils.gd
-msgid "Clear saving path"
-msgstr "Vider le chemin d'enregistrement"
-
-#: src/ui_parts/code_editor.gd src/utils/TranslationUtils.gd
-msgid "Optimize"
-msgstr "Optimiser"
-
-#: src/ui_parts/code_editor.gd src/utils/TranslationUtils.gd
-msgid "Copy all text"
-msgstr "Copier tout le texte"
-
-#: src/ui_parts/code_editor.gd src/utils/TranslationUtils.gd
-msgid "Clear SVG"
-msgstr "Vider le SVG"
-
-#: src/ui_parts/display.gd
-msgid "Settings"
-msgstr "Paramètres"
-
#: src/ui_parts/display.gd
msgid "Visuals"
msgstr "Visuels"
@@ -277,33 +263,15 @@ msgstr "Montrer les poignées"
msgid "Rasterized SVG"
msgstr "Rastériser le SVG"
-#: src/ui_parts/display.gd
-msgid "About…"
-msgstr "À propos…"
-
#: src/ui_parts/display.gd
msgid "Snap size"
msgstr "Taille d'aimantation"
-#: src/ui_parts/display.gd src/utils/TranslationUtils.gd
-msgid "Check for updates"
-msgstr "Vérifier les mises à jour"
-
-#: src/ui_parts/display.gd
-msgid "Donate…"
-msgstr "Faire un don…"
-
-#: src/ui_parts/display.gd
-msgid "GodSVG repository"
-msgstr "Dépôt de GodSVG"
-
-#: src/ui_parts/display.gd
-msgid "GodSVG website"
-msgstr "Site web de GodSVG"
-
-#: src/ui_parts/display.gd
-msgid "View savedata"
-msgstr "Voir les données de sauvegarde"
+#: src/ui_parts/donate_menu.gd src/ui_parts/export_menu.gd
+#: src/ui_parts/import_warning_menu.gd src/ui_widgets/choose_name_dialog.gd
+#: src/ui_widgets/confirm_dialog.gd
+msgid "Cancel"
+msgstr "Annuler"
#: src/ui_parts/element_container.gd
msgid "New element"
@@ -313,11 +281,6 @@ msgstr "Nouvel élément"
msgid "Dimensions"
msgstr "Dimensions"
-#: src/ui_parts/export_menu.gd src/ui_widgets/configure_color_popup.gd
-#: src/ui_widgets/palette_config.gd
-msgid "Unnamed"
-msgstr "Sans nom"
-
#: src/ui_parts/export_menu.gd
msgid "Size"
msgstr "Taille"
@@ -354,25 +317,58 @@ msgstr "Hauteur"
msgid "Preview image size is limited to {dimensions}"
msgstr "La taille de l'image de prévisualisation est limitée à {dimensions}"
-#: src/ui_parts/global_menu.gd src/ui_parts/settings_menu.gd
-msgid "File"
-msgstr "Fichier"
+#: src/ui_parts/global_actions.gd src/ui_parts/import_warning_menu.gd
+#: src/utils/TranslationUtils.gd
+msgid "Import"
+msgstr "Importer"
-#: src/ui_parts/global_menu.gd src/ui_parts/settings_menu.gd
-msgid "Edit"
-msgstr "Édition"
+#: src/ui_parts/global_actions.gd
+msgid "Settings"
+msgstr "Paramètres"
-#: src/ui_parts/global_menu.gd src/ui_parts/settings_menu.gd
-msgid "Tool"
-msgstr "Outil"
+#: src/ui_parts/global_actions.gd src/utils/TranslationUtils.gd
+msgid "Optimize"
+msgstr "Optimiser"
-#: src/ui_parts/global_menu.gd src/ui_parts/settings_menu.gd
-msgid "View"
-msgstr "Affichage"
+#: src/ui_parts/global_actions.gd src/ui_parts/good_file_dialog.gd
+msgid "Save SVG"
+msgstr "Sauvegarder le SVG"
-#: src/ui_parts/global_menu.gd
-msgid "Snap"
-msgstr "Aimanter"
+#: src/ui_parts/global_actions.gd src/utils/TranslationUtils.gd
+msgid "Reset SVG"
+msgstr "Rénitialiser le SVG"
+
+#: src/ui_parts/global_actions.gd src/ui_parts/tab_bar.gd
+msgid "Open externally"
+msgstr ""
+
+#: src/ui_parts/global_actions.gd src/ui_parts/tab_bar.gd
+msgid "Show in File Manager"
+msgstr ""
+
+#: src/ui_parts/global_actions.gd
+msgid "About…"
+msgstr "À propos…"
+
+#: src/ui_parts/global_actions.gd src/utils/TranslationUtils.gd
+msgid "Check for updates"
+msgstr "Vérifier les mises à jour"
+
+#: src/ui_parts/global_actions.gd
+msgid "Donate…"
+msgstr "Faire un don…"
+
+#: src/ui_parts/global_actions.gd
+msgid "GodSVG repository"
+msgstr "Dépôt de GodSVG"
+
+#: src/ui_parts/global_actions.gd
+msgid "GodSVG website"
+msgstr "Site web de GodSVG"
+
+#: src/ui_parts/global_actions.gd
+msgid "View savedata"
+msgstr "Voir les données de sauvegarde"
#: src/ui_parts/good_file_dialog.gd
msgid "Go to parent folder"
@@ -402,11 +398,6 @@ msgstr "Sélectionner un fichier XML"
msgid "Select an image"
msgstr "Sélectionner une image"
-#: src/ui_parts/good_file_dialog.gd src/ui_parts/settings_menu.gd
-#: src/ui_parts/shortcut_panel_config.gd src/ui_parts/update_menu.gd
-msgid "Close"
-msgstr "Fermer"
-
#: src/ui_parts/good_file_dialog.gd
msgid "Select"
msgstr "Sélectionner"
@@ -463,10 +454,6 @@ msgstr "Nouvelle forme"
msgid "Import Problems"
msgstr "Importer des problèmes"
-#: src/ui_parts/import_warning_menu.gd src/utils/TranslationUtils.gd
-msgid "Import"
-msgstr "Importer"
-
#: src/ui_parts/import_warning_menu.gd
msgid "Unrecognized element"
msgstr "Élément non reconnu"
@@ -483,6 +470,26 @@ msgstr "Erreur de syntaxe"
msgid "Add element"
msgstr "Ajouter un élément"
+#: src/ui_parts/mac_menu.gd src/ui_parts/settings_menu.gd
+msgid "File"
+msgstr "Fichier"
+
+#: src/ui_parts/mac_menu.gd src/ui_parts/settings_menu.gd
+msgid "Edit"
+msgstr "Édition"
+
+#: src/ui_parts/mac_menu.gd src/ui_parts/settings_menu.gd
+msgid "Tool"
+msgstr "Outil"
+
+#: src/ui_parts/mac_menu.gd src/ui_parts/settings_menu.gd
+msgid "View"
+msgstr "Affichage"
+
+#: src/ui_parts/mac_menu.gd
+msgid "Snap"
+msgstr "Aimanter"
+
#: src/ui_parts/settings_menu.gd
msgid "Formatting"
msgstr "Formatage"
@@ -818,6 +825,33 @@ msgstr "Ligne horizontale vers"
msgid "Vertical strip"
msgstr "Ligne verticale vers"
+#: src/ui_parts/tab_bar.gd
+#, fuzzy
+msgid "Create tab"
+msgstr "Créer"
+
+#: src/ui_parts/tab_bar.gd
+#, fuzzy
+msgid "Close all other tabs"
+msgstr "Copier tout le texte"
+
+#: src/ui_parts/tab_bar.gd
+msgid "Close tabs to the left"
+msgstr ""
+
+#: src/ui_parts/tab_bar.gd
+msgid "Close tabs to the right"
+msgstr ""
+
+#: src/ui_parts/tab_bar.gd src/utils/TranslationUtils.gd
+#, fuzzy
+msgid "Create a new tab"
+msgstr "Créer un nouveau dossier"
+
+#: src/ui_parts/tab_bar.gd
+msgid "This SVG is not bound to a location on the computer yet."
+msgstr ""
+
#: src/ui_parts/update_menu.gd
msgid "Include prereleases"
msgstr "Inclure les versions préliminaires"
@@ -846,6 +880,18 @@ msgstr "GodSVG est à jour."
msgid "New versions"
msgstr "Nouvelles versions"
+#: src/ui_parts/zoom_menu.gd src/utils/TranslationUtils.gd
+msgid "Zoom out"
+msgstr "Zoom arrière"
+
+#: src/ui_parts/zoom_menu.gd src/utils/TranslationUtils.gd
+msgid "Zoom in"
+msgstr "Zoom avant"
+
+#: src/ui_parts/zoom_menu.gd src/utils/TranslationUtils.gd
+msgid "Zoom reset"
+msgstr "Rénitialisation de zoom"
+
#: src/ui_widgets/BetterLineEdit.gd src/ui_widgets/BetterTextEdit.gd
#: src/utils/TranslationUtils.gd
msgid "Undo"
@@ -868,6 +914,14 @@ msgstr "Copier"
msgid "Paste"
msgstr "Coller"
+#: src/ui_widgets/choose_name_dialog.gd
+msgid "Create"
+msgstr "Créer"
+
+#: src/ui_widgets/code_editor.gd src/utils/TranslationUtils.gd
+msgid "Copy all text"
+msgstr "Copier tout le texte"
+
#: src/ui_widgets/color_popup.gd
msgid "Search color"
msgstr "Rechercher la couleur"
@@ -884,6 +938,10 @@ msgstr "Éditer le nom de la couleur"
msgid "Delete color"
msgstr "Supprimer la couleur"
+#: src/ui_widgets/configure_color_popup.gd src/ui_widgets/palette_config.gd
+msgid "Unnamed"
+msgstr "Sans nom"
+
#: src/ui_widgets/good_color_picker.gd
#, fuzzy
msgid "Color keywords"
@@ -917,6 +975,11 @@ msgstr "Copier en tant que XML"
msgid "Apply Preset"
msgstr "Appliquer le préréglage"
+#: src/ui_widgets/path_popup.gd src/ui_widgets/pathdata_field.gd
+#: src/utils/TranslationUtils.gd
+msgid "Relative"
+msgstr "Relatif"
+
#: src/ui_widgets/pathdata_field.gd
msgid "No path data"
msgstr "Aucune donnée de chemin"
@@ -925,10 +988,6 @@ msgstr "Aucune donnée de chemin"
msgid "Absolute"
msgstr "Absolu"
-#: src/ui_widgets/pathdata_field.gd src/utils/TranslationUtils.gd
-msgid "Relative"
-msgstr "Relatif"
-
#: src/ui_widgets/points_field.gd
msgid "No points"
msgstr "Aucun point"
@@ -993,47 +1052,59 @@ msgstr "Le fichier n'a pas pu être ouvert."
msgid "Check if the file still exists in the selected file path."
msgstr "Vérifiez si le fichier existe encore au chemin selectionné."
+#: src/utils/FileUtils.gd
+msgid "The imported file is already being edited inside GodSVG."
+msgstr ""
+
+#: src/utils/FileUtils.gd
+msgid "If you want to apply the unsaved file state, use \"Reset SVG\" instead."
+msgstr ""
+
+#: src/utils/TranslationUtils.gd
+msgid "Select the next tab"
+msgstr ""
+
+#: src/utils/TranslationUtils.gd
+msgid "Select the previous tab"
+msgstr ""
+
#: src/utils/TranslationUtils.gd
#, fuzzy
-msgid "Open SVG file"
-msgstr "Ouvrir le fichier"
+msgid "Open SVG externally"
+msgstr "Ouvrir le dépôt de GodSVG"
#: src/utils/TranslationUtils.gd
-msgid "Select all elements"
-msgstr "Sélectionner tous les éléments"
+msgid "Show SVG in File Manager"
+msgstr ""
#: src/utils/TranslationUtils.gd
-msgid "Duplicate the selected elements"
-msgstr "Dupliquer les éléments sélectionnés"
+#, fuzzy
+msgid "Select all"
+msgstr "Sélectionner"
+
+#: src/utils/TranslationUtils.gd
+#, fuzzy
+msgid "Duplicate the selection"
+msgstr "Supprimer les éléments sélectionnés"
#: src/utils/TranslationUtils.gd
msgid "Delete the selection"
msgstr "Supprimer les éléments sélectionnés"
#: src/utils/TranslationUtils.gd
-msgid "Move the selected elements up"
+#, fuzzy
+msgid "Move the selection up"
msgstr "Remonter les éléments sélectionnés"
#: src/utils/TranslationUtils.gd
-msgid "Move the selected elements down"
+#, fuzzy
+msgid "Move the selection down"
msgstr "Descendre les éléments sélectionnés"
#: src/utils/TranslationUtils.gd
msgid "Find"
msgstr "Rechercher"
-#: src/utils/TranslationUtils.gd
-msgid "Zoom in"
-msgstr "Zoom avant"
-
-#: src/utils/TranslationUtils.gd
-msgid "Zoom out"
-msgstr "Zoom arrière"
-
-#: src/utils/TranslationUtils.gd
-msgid "Zoom reset"
-msgstr "Rénitialisation de zoom"
-
#: src/utils/TranslationUtils.gd
msgid "Show rasterized SVG"
msgstr "Montrer le SVG rastérisé"
@@ -1132,6 +1203,25 @@ msgstr ""
"L'extension de fichier est vide. Seuls les fichiers {extension_list} sont "
"supportés."
+#~ msgid "Open file"
+#~ msgstr "Ouvrir le fichier"
+
+#~ msgid "Clear saving path"
+#~ msgstr "Vider le chemin d'enregistrement"
+
+#~ msgid "Clear SVG"
+#~ msgstr "Vider le SVG"
+
+#, fuzzy
+#~ msgid "Open SVG file"
+#~ msgstr "Ouvrir le fichier"
+
+#~ msgid "Select all elements"
+#~ msgstr "Sélectionner tous les éléments"
+
+#~ msgid "Duplicate the selected elements"
+#~ msgstr "Dupliquer les éléments sélectionnés"
+
#~ msgid "Enable the color"
#~ msgstr "Activer la couleur"
diff --git a/assets/translations/nl.po b/assets/translations/nl.po
index 937cab7..f83b507 100644
--- a/assets/translations/nl.po
+++ b/assets/translations/nl.po
@@ -35,7 +35,7 @@ msgstr "Voor updates controleren?"
msgid "This requires GodSVG to connect to the internet."
msgstr "Hiervoor moet GodSVG verbinding maken met het internet."
-#: src/autoload/HandlerGUI.gd src/ui_parts/alert_dialog.gd
+#: src/autoload/HandlerGUI.gd src/ui_widgets/alert_dialog.gd
msgid "OK"
msgstr "OK"
@@ -44,7 +44,7 @@ msgid "Export SVG"
msgstr "Exporteer SVG"
#: src/autoload/HandlerGUI.gd src/ui_parts/export_menu.gd
-#: src/utils/TranslationUtils.gd
+#: src/ui_parts/global_actions.gd src/utils/TranslationUtils.gd
msgid "Export"
msgstr "Exporteren"
@@ -56,35 +56,59 @@ msgstr ""
"Het beeld kan alleen maar als SVG geëxporteerd worden want zijn maat is niet "
"gedefiniëerd. Wil je verder gaan?"
-#: src/autoload/Indications.gd
-msgid "View In List"
+#: src/autoload/State.gd src/ui_parts/good_file_dialog.gd
+#: src/ui_widgets/alert_dialog.gd
+msgid "Alert!"
+msgstr "Aandacht!"
+
+#: src/autoload/State.gd src/ui_parts/tab_bar.gd src/utils/TranslationUtils.gd
+#, fuzzy
+msgid "Close tab"
+msgstr "Pad sluiten"
+
+#: src/autoload/State.gd
+msgid "Restore"
+msgstr ""
+
+#: src/autoload/State.gd
+msgid "View in List"
msgstr "Bekijken In Lijst"
-#: src/autoload/Indications.gd
+#: src/autoload/State.gd
msgid "Duplicate"
msgstr "Dupliceren"
-#: src/autoload/Indications.gd
+#: src/autoload/State.gd
msgid "Convert To"
msgstr "Omzetten Naar"
-#: src/autoload/Indications.gd src/ui_widgets/palette_config.gd
+#: src/autoload/State.gd src/ui_widgets/palette_config.gd
msgid "Move Up"
msgstr "Naar Boven"
-#: src/autoload/Indications.gd src/ui_widgets/palette_config.gd
+#: src/autoload/State.gd src/ui_widgets/palette_config.gd
msgid "Move Down"
msgstr "Naar Onder"
-#: src/autoload/Indications.gd src/ui_widgets/palette_config.gd
+#: src/autoload/State.gd src/ui_widgets/palette_config.gd
#: src/ui_widgets/setting_shortcut.gd src/ui_widgets/transform_popup.gd
msgid "Delete"
msgstr "Verwijderen"
-#: src/autoload/Indications.gd src/ui_widgets/transform_popup.gd
+#: src/autoload/State.gd src/ui_widgets/transform_popup.gd
msgid "Insert After"
msgstr "Erna Invoegen"
+#: src/autoload/State.gd
+msgid "The last edited state of this tab could not be found."
+msgstr ""
+
+#: src/autoload/State.gd
+msgid ""
+"The tab is bound to the file path {file_path}. Do you want to restore from "
+"this path?"
+msgstr ""
+
#: src/config_classes/Formatter.gd
msgid "Compact"
msgstr "Compact"
@@ -121,6 +145,10 @@ msgstr "3-cijferig of 6-cijferige hex"
msgid "6-digit hex"
msgstr "6-cijferige hex"
+#: src/config_classes/TabData.gd
+msgid "Unsaved"
+msgstr ""
+
#: src/data_classes/BasicXNode.gd
msgid "Comment"
msgstr "Commentaar"
@@ -141,18 +169,15 @@ msgstr "Deze groep bevat geen elementen."
msgid "This group has only one element."
msgstr "Deze groep bevat maar één element."
-#: src/data_classes/ElementLinearGradient.gd
-#: src/data_classes/ElementRadialGradient.gd
+#: src/data_classes/GradientUtils.gd
msgid "No \"id\" attribute defined."
msgstr "Geen \"id\" attribuut gedefineerd."
-#: src/data_classes/ElementLinearGradient.gd
-#: src/data_classes/ElementRadialGradient.gd
+#: src/data_classes/GradientUtils.gd
msgid "No elements under this gradient."
msgstr "Geen elementen onder deze kleurovergang."
-#: src/data_classes/ElementLinearGradient.gd
-#: src/data_classes/ElementRadialGradient.gd
+#: src/data_classes/GradientUtils.gd
msgid "This gradient is a solid color."
msgstr "Deze kleurovergang is een vaste kleur."
@@ -164,6 +189,12 @@ msgstr "Beschrijft geen SVG."
msgid "Improper nesting."
msgstr "Onjuiste nesting."
+#: src/ui_parts/about_menu.gd src/ui_parts/good_file_dialog.gd
+#: src/ui_parts/settings_menu.gd src/ui_parts/shortcut_panel_config.gd
+#: src/ui_parts/update_menu.gd
+msgid "Close"
+msgstr "Afluiten"
+
#: src/ui_parts/about_menu.gd
msgid "Project Founder and Manager"
msgstr "Projectoprichter en Manager"
@@ -204,51 +235,6 @@ msgstr "Godot derde partij componenten"
msgid "Third-party licenses"
msgstr "Derde partij Licenties"
-#: src/ui_parts/alert_dialog.gd src/ui_parts/good_file_dialog.gd
-msgid "Alert!"
-msgstr "Aandacht!"
-
-#: src/ui_parts/choose_name_dialog.gd src/ui_parts/confirm_dialog.gd
-#: src/ui_parts/export_menu.gd src/ui_parts/import_warning_menu.gd
-msgid "Cancel"
-msgstr "Annuleren"
-
-#: src/ui_parts/choose_name_dialog.gd
-msgid "Create"
-msgstr "Creëren"
-
-#: src/ui_parts/code_editor.gd src/ui_parts/good_file_dialog.gd
-msgid "Save SVG"
-msgstr "SVG opslaan"
-
-#: src/ui_parts/code_editor.gd
-msgid "Open file"
-msgstr "Open bestand"
-
-#: src/ui_parts/code_editor.gd src/utils/TranslationUtils.gd
-msgid "Reset SVG"
-msgstr "SVG Resetten"
-
-#: src/ui_parts/code_editor.gd src/utils/TranslationUtils.gd
-msgid "Clear saving path"
-msgstr "Ontruim opslagpad"
-
-#: src/ui_parts/code_editor.gd src/utils/TranslationUtils.gd
-msgid "Optimize"
-msgstr "Optimaliseren"
-
-#: src/ui_parts/code_editor.gd src/utils/TranslationUtils.gd
-msgid "Copy all text"
-msgstr "Kopiëer alle tekst"
-
-#: src/ui_parts/code_editor.gd src/utils/TranslationUtils.gd
-msgid "Clear SVG"
-msgstr "SVG ontruimen"
-
-#: src/ui_parts/display.gd
-msgid "Settings"
-msgstr "Instellingen"
-
#: src/ui_parts/display.gd
msgid "Visuals"
msgstr "Visuelen"
@@ -277,33 +263,15 @@ msgstr "Handvaten weergeven"
msgid "Rasterized SVG"
msgstr "Gerasterd SVG"
-#: src/ui_parts/display.gd
-msgid "About…"
-msgstr "Betreffende…"
-
#: src/ui_parts/display.gd
msgid "Snap size"
msgstr "Maat inknappen"
-#: src/ui_parts/display.gd src/utils/TranslationUtils.gd
-msgid "Check for updates"
-msgstr "Nakijken voor updates"
-
-#: src/ui_parts/display.gd
-msgid "Donate…"
-msgstr "Doneren…"
-
-#: src/ui_parts/display.gd
-msgid "GodSVG repository"
-msgstr "GodSVG opslagplaats"
-
-#: src/ui_parts/display.gd
-msgid "GodSVG website"
-msgstr "GodSVG website"
-
-#: src/ui_parts/display.gd
-msgid "View savedata"
-msgstr "Bekijk opslaggegevens"
+#: src/ui_parts/donate_menu.gd src/ui_parts/export_menu.gd
+#: src/ui_parts/import_warning_menu.gd src/ui_widgets/choose_name_dialog.gd
+#: src/ui_widgets/confirm_dialog.gd
+msgid "Cancel"
+msgstr "Annuleren"
#: src/ui_parts/element_container.gd
msgid "New element"
@@ -313,11 +281,6 @@ msgstr "Nieuw element"
msgid "Dimensions"
msgstr "Afmetingen"
-#: src/ui_parts/export_menu.gd src/ui_widgets/configure_color_popup.gd
-#: src/ui_widgets/palette_config.gd
-msgid "Unnamed"
-msgstr "Onbenoemd"
-
#: src/ui_parts/export_menu.gd
msgid "Size"
msgstr "Maat"
@@ -354,25 +317,58 @@ msgstr "Hoogte"
msgid "Preview image size is limited to {dimensions}"
msgstr "Voorbeeldafbeelding maat is beperkt tot {dimensions}"
-#: src/ui_parts/global_menu.gd src/ui_parts/settings_menu.gd
-msgid "File"
-msgstr "Bestand"
+#: src/ui_parts/global_actions.gd src/ui_parts/import_warning_menu.gd
+#: src/utils/TranslationUtils.gd
+msgid "Import"
+msgstr "Importeren"
-#: src/ui_parts/global_menu.gd src/ui_parts/settings_menu.gd
-msgid "Edit"
-msgstr "Bewerken"
+#: src/ui_parts/global_actions.gd
+msgid "Settings"
+msgstr "Instellingen"
-#: src/ui_parts/global_menu.gd src/ui_parts/settings_menu.gd
-msgid "Tool"
-msgstr "Gereedschap"
+#: src/ui_parts/global_actions.gd src/utils/TranslationUtils.gd
+msgid "Optimize"
+msgstr "Optimaliseren"
-#: src/ui_parts/global_menu.gd src/ui_parts/settings_menu.gd
-msgid "View"
-msgstr "Zicht"
+#: src/ui_parts/global_actions.gd src/ui_parts/good_file_dialog.gd
+msgid "Save SVG"
+msgstr "SVG opslaan"
-#: src/ui_parts/global_menu.gd
-msgid "Snap"
-msgstr "Knappen"
+#: src/ui_parts/global_actions.gd src/utils/TranslationUtils.gd
+msgid "Reset SVG"
+msgstr "SVG Resetten"
+
+#: src/ui_parts/global_actions.gd src/ui_parts/tab_bar.gd
+msgid "Open externally"
+msgstr ""
+
+#: src/ui_parts/global_actions.gd src/ui_parts/tab_bar.gd
+msgid "Show in File Manager"
+msgstr ""
+
+#: src/ui_parts/global_actions.gd
+msgid "About…"
+msgstr "Betreffende…"
+
+#: src/ui_parts/global_actions.gd src/utils/TranslationUtils.gd
+msgid "Check for updates"
+msgstr "Nakijken voor updates"
+
+#: src/ui_parts/global_actions.gd
+msgid "Donate…"
+msgstr "Doneren…"
+
+#: src/ui_parts/global_actions.gd
+msgid "GodSVG repository"
+msgstr "GodSVG opslagplaats"
+
+#: src/ui_parts/global_actions.gd
+msgid "GodSVG website"
+msgstr "GodSVG website"
+
+#: src/ui_parts/global_actions.gd
+msgid "View savedata"
+msgstr "Bekijk opslaggegevens"
#: src/ui_parts/good_file_dialog.gd
msgid "Go to parent folder"
@@ -402,11 +398,6 @@ msgstr "Kies een XML bestand uit"
msgid "Select an image"
msgstr "Kies een afbeedling"
-#: src/ui_parts/good_file_dialog.gd src/ui_parts/settings_menu.gd
-#: src/ui_parts/shortcut_panel_config.gd src/ui_parts/update_menu.gd
-msgid "Close"
-msgstr "Afluiten"
-
#: src/ui_parts/good_file_dialog.gd
msgid "Select"
msgstr "Selecteren"
@@ -463,10 +454,6 @@ msgstr "Nieuwe vorm"
msgid "Import Problems"
msgstr "Importproblemen"
-#: src/ui_parts/import_warning_menu.gd src/utils/TranslationUtils.gd
-msgid "Import"
-msgstr "Importeren"
-
#: src/ui_parts/import_warning_menu.gd
msgid "Unrecognized element"
msgstr "Onherkende element"
@@ -483,6 +470,26 @@ msgstr "Syntaxfout"
msgid "Add element"
msgstr "Element toevoegen"
+#: src/ui_parts/mac_menu.gd src/ui_parts/settings_menu.gd
+msgid "File"
+msgstr "Bestand"
+
+#: src/ui_parts/mac_menu.gd src/ui_parts/settings_menu.gd
+msgid "Edit"
+msgstr "Bewerken"
+
+#: src/ui_parts/mac_menu.gd src/ui_parts/settings_menu.gd
+msgid "Tool"
+msgstr "Gereedschap"
+
+#: src/ui_parts/mac_menu.gd src/ui_parts/settings_menu.gd
+msgid "View"
+msgstr "Zicht"
+
+#: src/ui_parts/mac_menu.gd
+msgid "Snap"
+msgstr "Knappen"
+
#: src/ui_parts/settings_menu.gd
msgid "Formatting"
msgstr "Formattering"
@@ -813,6 +820,33 @@ msgstr "Horizontaal met twee rijen"
msgid "Vertical strip"
msgstr "Vertikale lijn"
+#: src/ui_parts/tab_bar.gd
+#, fuzzy
+msgid "Create tab"
+msgstr "Creëren"
+
+#: src/ui_parts/tab_bar.gd
+#, fuzzy
+msgid "Close all other tabs"
+msgstr "Kopiëer alle tekst"
+
+#: src/ui_parts/tab_bar.gd
+msgid "Close tabs to the left"
+msgstr ""
+
+#: src/ui_parts/tab_bar.gd
+msgid "Close tabs to the right"
+msgstr ""
+
+#: src/ui_parts/tab_bar.gd src/utils/TranslationUtils.gd
+#, fuzzy
+msgid "Create a new tab"
+msgstr "Nieuwe map creëren"
+
+#: src/ui_parts/tab_bar.gd
+msgid "This SVG is not bound to a location on the computer yet."
+msgstr ""
+
#: src/ui_parts/update_menu.gd
msgid "Include prereleases"
msgstr "Voorpublicaties includen"
@@ -841,6 +875,18 @@ msgstr "GodSVG is up-to-date."
msgid "New versions"
msgstr "Nieuwe versies"
+#: src/ui_parts/zoom_menu.gd src/utils/TranslationUtils.gd
+msgid "Zoom out"
+msgstr "Uitzoomen"
+
+#: src/ui_parts/zoom_menu.gd src/utils/TranslationUtils.gd
+msgid "Zoom in"
+msgstr "Inzoomen"
+
+#: src/ui_parts/zoom_menu.gd src/utils/TranslationUtils.gd
+msgid "Zoom reset"
+msgstr "Zoom resetten"
+
#: src/ui_widgets/BetterLineEdit.gd src/ui_widgets/BetterTextEdit.gd
#: src/utils/TranslationUtils.gd
msgid "Undo"
@@ -863,6 +909,14 @@ msgstr "Kopiëren"
msgid "Paste"
msgstr "Plakken"
+#: src/ui_widgets/choose_name_dialog.gd
+msgid "Create"
+msgstr "Creëren"
+
+#: src/ui_widgets/code_editor.gd src/utils/TranslationUtils.gd
+msgid "Copy all text"
+msgstr "Kopiëer alle tekst"
+
#: src/ui_widgets/color_popup.gd
msgid "Search color"
msgstr "Zoekkleur"
@@ -879,6 +933,10 @@ msgstr "Kleurnaam bewerken"
msgid "Delete color"
msgstr "Kleur verwijderen"
+#: src/ui_widgets/configure_color_popup.gd src/ui_widgets/palette_config.gd
+msgid "Unnamed"
+msgstr "Onbenoemd"
+
#: src/ui_widgets/good_color_picker.gd
#, fuzzy
msgid "Color keywords"
@@ -912,6 +970,11 @@ msgstr "Als XML kopiëren"
msgid "Apply Preset"
msgstr "Voorinstelling toepassen"
+#: src/ui_widgets/path_popup.gd src/ui_widgets/pathdata_field.gd
+#: src/utils/TranslationUtils.gd
+msgid "Relative"
+msgstr "Relatief"
+
#: src/ui_widgets/pathdata_field.gd
msgid "No path data"
msgstr "Geen padgegevens"
@@ -920,10 +983,6 @@ msgstr "Geen padgegevens"
msgid "Absolute"
msgstr "Absoluut"
-#: src/ui_widgets/pathdata_field.gd src/utils/TranslationUtils.gd
-msgid "Relative"
-msgstr "Relatief"
-
#: src/ui_widgets/points_field.gd
msgid "No points"
msgstr "Geen punten"
@@ -988,45 +1047,58 @@ msgstr "Het bestand kon niet worden geopend."
msgid "Check if the file still exists in the selected file path."
msgstr "Kijk na of het bestand nog steeds bestaat in het gekozen bestandspad"
+#: src/utils/FileUtils.gd
+msgid "The imported file is already being edited inside GodSVG."
+msgstr ""
+
+#: src/utils/FileUtils.gd
+msgid "If you want to apply the unsaved file state, use \"Reset SVG\" instead."
+msgstr ""
+
#: src/utils/TranslationUtils.gd
-msgid "Open SVG file"
-msgstr "Open SVG bestand"
+msgid "Select the next tab"
+msgstr ""
#: src/utils/TranslationUtils.gd
-msgid "Select all elements"
-msgstr "Selecteer alle elementen"
+msgid "Select the previous tab"
+msgstr ""
#: src/utils/TranslationUtils.gd
-msgid "Duplicate the selected elements"
-msgstr "Dupliceer de gekozen elementen"
+#, fuzzy
+msgid "Open SVG externally"
+msgstr "Open GodSVG opslagplaats"
#: src/utils/TranslationUtils.gd
-msgid "Delete the selection"
-msgstr "Verwijder de selectie"
+msgid "Show SVG in File Manager"
+msgstr ""
#: src/utils/TranslationUtils.gd
-msgid "Move the selected elements up"
-msgstr "Beweeg the gekozen elementen omhoog"
+#, fuzzy
+msgid "Select all"
+msgstr "Selecteren"
#: src/utils/TranslationUtils.gd
-msgid "Move the selected elements down"
-msgstr "Beweeg the gekozen elementen omlaag"
+#, fuzzy
+msgid "Duplicate the selection"
+msgstr "Verwijder de selectie"
#: src/utils/TranslationUtils.gd
-msgid "Find"
-msgstr "Vind"
+msgid "Delete the selection"
+msgstr "Verwijder de selectie"
#: src/utils/TranslationUtils.gd
-msgid "Zoom in"
-msgstr "Inzoomen"
+#, fuzzy
+msgid "Move the selection up"
+msgstr "Beweeg the gekozen elementen omhoog"
#: src/utils/TranslationUtils.gd
-msgid "Zoom out"
-msgstr "Uitzoomen"
+#, fuzzy
+msgid "Move the selection down"
+msgstr "Beweeg the gekozen elementen omlaag"
#: src/utils/TranslationUtils.gd
-msgid "Zoom reset"
-msgstr "Zoom resetten"
+msgid "Find"
+msgstr "Vind"
#: src/utils/TranslationUtils.gd
msgid "Show rasterized SVG"
@@ -1126,6 +1198,24 @@ msgstr ""
"Deze bestandsextensie is leeg. Alleen {extension_list} bestanden zijn "
"ondersteund."
+#~ msgid "Open file"
+#~ msgstr "Open bestand"
+
+#~ msgid "Clear saving path"
+#~ msgstr "Ontruim opslagpad"
+
+#~ msgid "Clear SVG"
+#~ msgstr "SVG ontruimen"
+
+#~ msgid "Open SVG file"
+#~ msgstr "Open SVG bestand"
+
+#~ msgid "Select all elements"
+#~ msgstr "Selecteer alle elementen"
+
+#~ msgid "Duplicate the selected elements"
+#~ msgstr "Dupliceer de gekozen elementen"
+
#~ msgid "Enable the color"
#~ msgstr "De kleur inschakelen"
diff --git a/assets/translations/ru.po b/assets/translations/ru.po
index 9b42f36..8dc1064 100644
--- a/assets/translations/ru.po
+++ b/assets/translations/ru.po
@@ -36,7 +36,7 @@ msgstr "Проверить обновления?"
msgid "This requires GodSVG to connect to the internet."
msgstr "Для этого необходимо подключение к Интернету."
-#: src/autoload/HandlerGUI.gd src/ui_parts/alert_dialog.gd
+#: src/autoload/HandlerGUI.gd src/ui_widgets/alert_dialog.gd
msgid "OK"
msgstr "Хорошо"
@@ -45,7 +45,7 @@ msgid "Export SVG"
msgstr "Экспортировать SVG"
#: src/autoload/HandlerGUI.gd src/ui_parts/export_menu.gd
-#: src/utils/TranslationUtils.gd
+#: src/ui_parts/global_actions.gd src/utils/TranslationUtils.gd
msgid "Export"
msgstr "Экспортовать"
@@ -57,35 +57,59 @@ msgstr ""
"Изображение можно экспортировать только как SVG, поскольку его размеры не "
"заданы. Желаете продолжить?"
-#: src/autoload/Indications.gd
-msgid "View In List"
+#: src/autoload/State.gd src/ui_parts/good_file_dialog.gd
+#: src/ui_widgets/alert_dialog.gd
+msgid "Alert!"
+msgstr "Внимание!"
+
+#: src/autoload/State.gd src/ui_parts/tab_bar.gd src/utils/TranslationUtils.gd
+#, fuzzy
+msgid "Close tab"
+msgstr "Закрыть путь"
+
+#: src/autoload/State.gd
+msgid "Restore"
+msgstr ""
+
+#: src/autoload/State.gd
+msgid "View in List"
msgstr "Просмотреть в списке"
-#: src/autoload/Indications.gd
+#: src/autoload/State.gd
msgid "Duplicate"
msgstr "Дублировать"
-#: src/autoload/Indications.gd
+#: src/autoload/State.gd
msgid "Convert To"
msgstr "Конвертировать в"
-#: src/autoload/Indications.gd src/ui_widgets/palette_config.gd
+#: src/autoload/State.gd src/ui_widgets/palette_config.gd
msgid "Move Up"
msgstr "Подвинуть вверх"
-#: src/autoload/Indications.gd src/ui_widgets/palette_config.gd
+#: src/autoload/State.gd src/ui_widgets/palette_config.gd
msgid "Move Down"
msgstr "Подвинуть вниз"
-#: src/autoload/Indications.gd src/ui_widgets/palette_config.gd
+#: src/autoload/State.gd src/ui_widgets/palette_config.gd
#: src/ui_widgets/setting_shortcut.gd src/ui_widgets/transform_popup.gd
msgid "Delete"
msgstr "Удалить"
-#: src/autoload/Indications.gd src/ui_widgets/transform_popup.gd
+#: src/autoload/State.gd src/ui_widgets/transform_popup.gd
msgid "Insert After"
msgstr "Вставить после"
+#: src/autoload/State.gd
+msgid "The last edited state of this tab could not be found."
+msgstr ""
+
+#: src/autoload/State.gd
+msgid ""
+"The tab is bound to the file path {file_path}. Do you want to restore from "
+"this path?"
+msgstr ""
+
#: src/config_classes/Formatter.gd
msgid "Compact"
msgstr "Компактно"
@@ -122,6 +146,10 @@ msgstr "3 или 6 цифровое шестандацитиричное зна
msgid "6-digit hex"
msgstr "6 цифровое шестандцатяричное значение"
+#: src/config_classes/TabData.gd
+msgid "Unsaved"
+msgstr ""
+
#: src/data_classes/BasicXNode.gd
msgid "Comment"
msgstr "Комментарий"
@@ -142,18 +170,15 @@ msgstr "В этой групе нет элементов."
msgid "This group has only one element."
msgstr "В этой группе только один элемент."
-#: src/data_classes/ElementLinearGradient.gd
-#: src/data_classes/ElementRadialGradient.gd
+#: src/data_classes/GradientUtils.gd
msgid "No \"id\" attribute defined."
msgstr "Атрибут \"id\" не определен."
-#: src/data_classes/ElementLinearGradient.gd
-#: src/data_classes/ElementRadialGradient.gd
+#: src/data_classes/GradientUtils.gd
msgid "No elements under this gradient."
msgstr "Нет елементов под этим градиентом."
-#: src/data_classes/ElementLinearGradient.gd
-#: src/data_classes/ElementRadialGradient.gd
+#: src/data_classes/GradientUtils.gd
msgid "This gradient is a solid color."
msgstr "Этот градиент это обычный цвет."
@@ -165,6 +190,12 @@ msgstr "Не является описанием SVG."
msgid "Improper nesting."
msgstr "Неправильное вложение."
+#: src/ui_parts/about_menu.gd src/ui_parts/good_file_dialog.gd
+#: src/ui_parts/settings_menu.gd src/ui_parts/shortcut_panel_config.gd
+#: src/ui_parts/update_menu.gd
+msgid "Close"
+msgstr "Закрыть"
+
#: src/ui_parts/about_menu.gd
msgid "Project Founder and Manager"
msgstr "Основатель и руководитель проекта"
@@ -205,51 +236,6 @@ msgstr "Компоненты Godot от третьих лиц"
msgid "Third-party licenses"
msgstr "Лицензии третьих лиц"
-#: src/ui_parts/alert_dialog.gd src/ui_parts/good_file_dialog.gd
-msgid "Alert!"
-msgstr "Внимание!"
-
-#: src/ui_parts/choose_name_dialog.gd src/ui_parts/confirm_dialog.gd
-#: src/ui_parts/export_menu.gd src/ui_parts/import_warning_menu.gd
-msgid "Cancel"
-msgstr "Отменить"
-
-#: src/ui_parts/choose_name_dialog.gd
-msgid "Create"
-msgstr "Создать"
-
-#: src/ui_parts/code_editor.gd src/ui_parts/good_file_dialog.gd
-msgid "Save SVG"
-msgstr "Сохранить SVG"
-
-#: src/ui_parts/code_editor.gd
-msgid "Open file"
-msgstr "Открыть файл"
-
-#: src/ui_parts/code_editor.gd src/utils/TranslationUtils.gd
-msgid "Reset SVG"
-msgstr "Сбросить SVG"
-
-#: src/ui_parts/code_editor.gd src/utils/TranslationUtils.gd
-msgid "Clear saving path"
-msgstr "Очистить путь сохранения"
-
-#: src/ui_parts/code_editor.gd src/utils/TranslationUtils.gd
-msgid "Optimize"
-msgstr "Оптимизировать"
-
-#: src/ui_parts/code_editor.gd src/utils/TranslationUtils.gd
-msgid "Copy all text"
-msgstr "Скопировать весь текст"
-
-#: src/ui_parts/code_editor.gd src/utils/TranslationUtils.gd
-msgid "Clear SVG"
-msgstr "Очистить SVG"
-
-#: src/ui_parts/display.gd
-msgid "Settings"
-msgstr "Настройки"
-
#: src/ui_parts/display.gd
msgid "Visuals"
msgstr "Внешний вид"
@@ -278,33 +264,15 @@ msgstr "Показать ручки редактирования"
msgid "Rasterized SVG"
msgstr "Растеризовать SVG"
-#: src/ui_parts/display.gd
-msgid "About…"
-msgstr "Про программу…"
-
#: src/ui_parts/display.gd
msgid "Snap size"
msgstr "Размер привязки"
-#: src/ui_parts/display.gd src/utils/TranslationUtils.gd
-msgid "Check for updates"
-msgstr "Проверить обновления"
-
-#: src/ui_parts/display.gd
-msgid "Donate…"
-msgstr "Сделать пожертвование…"
-
-#: src/ui_parts/display.gd
-msgid "GodSVG repository"
-msgstr "Репозиторий GodSVG"
-
-#: src/ui_parts/display.gd
-msgid "GodSVG website"
-msgstr "Веб-сайт GodSVG"
-
-#: src/ui_parts/display.gd
-msgid "View savedata"
-msgstr "Просмотреть даные сохранения"
+#: src/ui_parts/donate_menu.gd src/ui_parts/export_menu.gd
+#: src/ui_parts/import_warning_menu.gd src/ui_widgets/choose_name_dialog.gd
+#: src/ui_widgets/confirm_dialog.gd
+msgid "Cancel"
+msgstr "Отменить"
#: src/ui_parts/element_container.gd
msgid "New element"
@@ -314,11 +282,6 @@ msgstr "Новый элемент"
msgid "Dimensions"
msgstr "Размеры"
-#: src/ui_parts/export_menu.gd src/ui_widgets/configure_color_popup.gd
-#: src/ui_widgets/palette_config.gd
-msgid "Unnamed"
-msgstr "Без названия"
-
#: src/ui_parts/export_menu.gd
msgid "Size"
msgstr "Размер"
@@ -356,25 +319,58 @@ msgid "Preview image size is limited to {dimensions}"
msgstr ""
"Размер предварительного просмотра изображения ограничен до {dimensions}"
-#: src/ui_parts/global_menu.gd src/ui_parts/settings_menu.gd
-msgid "File"
-msgstr "Файл"
+#: src/ui_parts/global_actions.gd src/ui_parts/import_warning_menu.gd
+#: src/utils/TranslationUtils.gd
+msgid "Import"
+msgstr "Импортировать"
-#: src/ui_parts/global_menu.gd src/ui_parts/settings_menu.gd
-msgid "Edit"
-msgstr "Редактировать"
+#: src/ui_parts/global_actions.gd
+msgid "Settings"
+msgstr "Настройки"
-#: src/ui_parts/global_menu.gd src/ui_parts/settings_menu.gd
-msgid "Tool"
-msgstr "Инструмент"
+#: src/ui_parts/global_actions.gd src/utils/TranslationUtils.gd
+msgid "Optimize"
+msgstr "Оптимизировать"
-#: src/ui_parts/global_menu.gd src/ui_parts/settings_menu.gd
-msgid "View"
-msgstr "Просмотр"
+#: src/ui_parts/global_actions.gd src/ui_parts/good_file_dialog.gd
+msgid "Save SVG"
+msgstr "Сохранить SVG"
-#: src/ui_parts/global_menu.gd
-msgid "Snap"
-msgstr "Привязка"
+#: src/ui_parts/global_actions.gd src/utils/TranslationUtils.gd
+msgid "Reset SVG"
+msgstr "Сбросить SVG"
+
+#: src/ui_parts/global_actions.gd src/ui_parts/tab_bar.gd
+msgid "Open externally"
+msgstr ""
+
+#: src/ui_parts/global_actions.gd src/ui_parts/tab_bar.gd
+msgid "Show in File Manager"
+msgstr ""
+
+#: src/ui_parts/global_actions.gd
+msgid "About…"
+msgstr "Про программу…"
+
+#: src/ui_parts/global_actions.gd src/utils/TranslationUtils.gd
+msgid "Check for updates"
+msgstr "Проверить обновления"
+
+#: src/ui_parts/global_actions.gd
+msgid "Donate…"
+msgstr "Сделать пожертвование…"
+
+#: src/ui_parts/global_actions.gd
+msgid "GodSVG repository"
+msgstr "Репозиторий GodSVG"
+
+#: src/ui_parts/global_actions.gd
+msgid "GodSVG website"
+msgstr "Веб-сайт GodSVG"
+
+#: src/ui_parts/global_actions.gd
+msgid "View savedata"
+msgstr "Просмотреть даные сохранения"
#: src/ui_parts/good_file_dialog.gd
msgid "Go to parent folder"
@@ -404,11 +400,6 @@ msgstr "Выбрать XML файл"
msgid "Select an image"
msgstr "Выбрать изображение"
-#: src/ui_parts/good_file_dialog.gd src/ui_parts/settings_menu.gd
-#: src/ui_parts/shortcut_panel_config.gd src/ui_parts/update_menu.gd
-msgid "Close"
-msgstr "Закрыть"
-
#: src/ui_parts/good_file_dialog.gd
msgid "Select"
msgstr "Выбрать"
@@ -465,10 +456,6 @@ msgstr "Новая привязка"
msgid "Import Problems"
msgstr "Проблемы с импортированием"
-#: src/ui_parts/import_warning_menu.gd src/utils/TranslationUtils.gd
-msgid "Import"
-msgstr "Импортировать"
-
#: src/ui_parts/import_warning_menu.gd
msgid "Unrecognized element"
msgstr "Неизвестный элемент"
@@ -485,6 +472,26 @@ msgstr "Синтаксическая ошибка"
msgid "Add element"
msgstr "Добавить элемент"
+#: src/ui_parts/mac_menu.gd src/ui_parts/settings_menu.gd
+msgid "File"
+msgstr "Файл"
+
+#: src/ui_parts/mac_menu.gd src/ui_parts/settings_menu.gd
+msgid "Edit"
+msgstr "Редактировать"
+
+#: src/ui_parts/mac_menu.gd src/ui_parts/settings_menu.gd
+msgid "Tool"
+msgstr "Инструмент"
+
+#: src/ui_parts/mac_menu.gd src/ui_parts/settings_menu.gd
+msgid "View"
+msgstr "Просмотр"
+
+#: src/ui_parts/mac_menu.gd
+msgid "Snap"
+msgstr "Привязка"
+
#: src/ui_parts/settings_menu.gd
msgid "Formatting"
msgstr "Форматирование"
@@ -819,6 +826,33 @@ msgstr "Горизонтальная линия до"
msgid "Vertical strip"
msgstr "Вертикальная линия до"
+#: src/ui_parts/tab_bar.gd
+#, fuzzy
+msgid "Create tab"
+msgstr "Создать"
+
+#: src/ui_parts/tab_bar.gd
+#, fuzzy
+msgid "Close all other tabs"
+msgstr "Скопировать весь текст"
+
+#: src/ui_parts/tab_bar.gd
+msgid "Close tabs to the left"
+msgstr ""
+
+#: src/ui_parts/tab_bar.gd
+msgid "Close tabs to the right"
+msgstr ""
+
+#: src/ui_parts/tab_bar.gd src/utils/TranslationUtils.gd
+#, fuzzy
+msgid "Create a new tab"
+msgstr "Создать новую папку"
+
+#: src/ui_parts/tab_bar.gd
+msgid "This SVG is not bound to a location on the computer yet."
+msgstr ""
+
#: src/ui_parts/update_menu.gd
msgid "Include prereleases"
msgstr "Включить пре-релизы"
@@ -847,6 +881,18 @@ msgstr "Установлена последняя версия GodSVG."
msgid "New versions"
msgstr "Новые версии"
+#: src/ui_parts/zoom_menu.gd src/utils/TranslationUtils.gd
+msgid "Zoom out"
+msgstr "Отдалить масштаб"
+
+#: src/ui_parts/zoom_menu.gd src/utils/TranslationUtils.gd
+msgid "Zoom in"
+msgstr "Приблизить масштаб"
+
+#: src/ui_parts/zoom_menu.gd src/utils/TranslationUtils.gd
+msgid "Zoom reset"
+msgstr "Сбросить масштаб"
+
#: src/ui_widgets/BetterLineEdit.gd src/ui_widgets/BetterTextEdit.gd
#: src/utils/TranslationUtils.gd
msgid "Undo"
@@ -869,6 +915,14 @@ msgstr "Скопировать"
msgid "Paste"
msgstr "Вставить"
+#: src/ui_widgets/choose_name_dialog.gd
+msgid "Create"
+msgstr "Создать"
+
+#: src/ui_widgets/code_editor.gd src/utils/TranslationUtils.gd
+msgid "Copy all text"
+msgstr "Скопировать весь текст"
+
#: src/ui_widgets/color_popup.gd
msgid "Search color"
msgstr "Искать цвет"
@@ -885,6 +939,10 @@ msgstr "Редактировать название цвета"
msgid "Delete color"
msgstr "Удалить цвет"
+#: src/ui_widgets/configure_color_popup.gd src/ui_widgets/palette_config.gd
+msgid "Unnamed"
+msgstr "Без названия"
+
#: src/ui_widgets/good_color_picker.gd
#, fuzzy
msgid "Color keywords"
@@ -918,6 +976,11 @@ msgstr "Скопировать как XML"
msgid "Apply Preset"
msgstr "Принять пресет"
+#: src/ui_widgets/path_popup.gd src/ui_widgets/pathdata_field.gd
+#: src/utils/TranslationUtils.gd
+msgid "Relative"
+msgstr "Относительно"
+
#: src/ui_widgets/pathdata_field.gd
msgid "No path data"
msgstr "Нет данных пути"
@@ -926,10 +989,6 @@ msgstr "Нет данных пути"
msgid "Absolute"
msgstr "Абсолютно"
-#: src/ui_widgets/pathdata_field.gd src/utils/TranslationUtils.gd
-msgid "Relative"
-msgstr "Относительно"
-
#: src/ui_widgets/points_field.gd
msgid "No points"
msgstr "Нет точек"
@@ -994,47 +1053,59 @@ msgstr "Файл невозможно открыть."
msgid "Check if the file still exists in the selected file path."
msgstr "Проверить, если файл все еще существует по выбранному пути."
+#: src/utils/FileUtils.gd
+msgid "The imported file is already being edited inside GodSVG."
+msgstr ""
+
+#: src/utils/FileUtils.gd
+msgid "If you want to apply the unsaved file state, use \"Reset SVG\" instead."
+msgstr ""
+
+#: src/utils/TranslationUtils.gd
+msgid "Select the next tab"
+msgstr ""
+
+#: src/utils/TranslationUtils.gd
+msgid "Select the previous tab"
+msgstr ""
+
#: src/utils/TranslationUtils.gd
#, fuzzy
-msgid "Open SVG file"
-msgstr "Открыть файл"
+msgid "Open SVG externally"
+msgstr "Открыть репозиторий GodSVG"
#: src/utils/TranslationUtils.gd
-msgid "Select all elements"
-msgstr "Выбрать все элементы"
+msgid "Show SVG in File Manager"
+msgstr ""
#: src/utils/TranslationUtils.gd
-msgid "Duplicate the selected elements"
-msgstr "Дублировать выбранные элементы"
+#, fuzzy
+msgid "Select all"
+msgstr "Выбрать"
+
+#: src/utils/TranslationUtils.gd
+#, fuzzy
+msgid "Duplicate the selection"
+msgstr "Удалить все выбранные теги"
#: src/utils/TranslationUtils.gd
msgid "Delete the selection"
msgstr "Удалить все выбранные теги"
#: src/utils/TranslationUtils.gd
-msgid "Move the selected elements up"
+#, fuzzy
+msgid "Move the selection up"
msgstr "Подвинуть вверх все выбранные теги"
#: src/utils/TranslationUtils.gd
-msgid "Move the selected elements down"
+#, fuzzy
+msgid "Move the selection down"
msgstr "Подвинуть вниз все выбранные элементы"
#: src/utils/TranslationUtils.gd
msgid "Find"
msgstr "Найти"
-#: src/utils/TranslationUtils.gd
-msgid "Zoom in"
-msgstr "Приблизить масштаб"
-
-#: src/utils/TranslationUtils.gd
-msgid "Zoom out"
-msgstr "Отдалить масштаб"
-
-#: src/utils/TranslationUtils.gd
-msgid "Zoom reset"
-msgstr "Сбросить масштаб"
-
#: src/utils/TranslationUtils.gd
msgid "Show rasterized SVG"
msgstr "Показать растеризованый SVG"
@@ -1131,6 +1202,29 @@ msgstr ""
msgid "The file extension is empty. Only {extension_list} files are supported."
msgstr "Расширение файла пустое. Поддерживаются только {extension_list} файлы."
+#, fuzzy
+#~ msgid "Add new tab"
+#~ msgstr "Добавить новый тег"
+
+#~ msgid "Open file"
+#~ msgstr "Открыть файл"
+
+#~ msgid "Clear saving path"
+#~ msgstr "Очистить путь сохранения"
+
+#~ msgid "Clear SVG"
+#~ msgstr "Очистить SVG"
+
+#, fuzzy
+#~ msgid "Open SVG file"
+#~ msgstr "Открыть файл"
+
+#~ msgid "Select all elements"
+#~ msgstr "Выбрать все элементы"
+
+#~ msgid "Duplicate the selected elements"
+#~ msgstr "Дублировать выбранные элементы"
+
#~ msgid "Enable the color"
#~ msgstr "Активировать цвет"
@@ -1194,9 +1288,6 @@ msgstr "Расширение файла пустое. Поддерживаютс
#~ msgid "New tag"
#~ msgstr "Новый тег"
-#~ msgid "Add new tag"
-#~ msgstr "Добавить новый тег"
-
#~ msgid "Paths"
#~ msgstr "Пути"
diff --git a/assets/translations/uk.po b/assets/translations/uk.po
index 84d72d3..13cbbec 100644
--- a/assets/translations/uk.po
+++ b/assets/translations/uk.po
@@ -35,7 +35,7 @@ msgstr "Перевірити оновлення?"
msgid "This requires GodSVG to connect to the internet."
msgstr "Ця опція потребує інтернет-з'єднання."
-#: src/autoload/HandlerGUI.gd src/ui_parts/alert_dialog.gd
+#: src/autoload/HandlerGUI.gd src/ui_widgets/alert_dialog.gd
msgid "OK"
msgstr "Добре"
@@ -44,7 +44,7 @@ msgid "Export SVG"
msgstr "Експортувати SVG"
#: src/autoload/HandlerGUI.gd src/ui_parts/export_menu.gd
-#: src/utils/TranslationUtils.gd
+#: src/ui_parts/global_actions.gd src/utils/TranslationUtils.gd
msgid "Export"
msgstr "Експортувати"
@@ -56,35 +56,59 @@ msgstr ""
"Зображення можна експортувати тільки як SVG, оскільки його розміри не "
"задані. Бажаєте продовжити?"
-#: src/autoload/Indications.gd
-msgid "View In List"
+#: src/autoload/State.gd src/ui_parts/good_file_dialog.gd
+#: src/ui_widgets/alert_dialog.gd
+msgid "Alert!"
+msgstr "Увага!"
+
+#: src/autoload/State.gd src/ui_parts/tab_bar.gd src/utils/TranslationUtils.gd
+#, fuzzy
+msgid "Close tab"
+msgstr "Закрити шлях"
+
+#: src/autoload/State.gd
+msgid "Restore"
+msgstr ""
+
+#: src/autoload/State.gd
+msgid "View in List"
msgstr "Переглянути списком"
-#: src/autoload/Indications.gd
+#: src/autoload/State.gd
msgid "Duplicate"
msgstr "Дублювати"
-#: src/autoload/Indications.gd
+#: src/autoload/State.gd
msgid "Convert To"
msgstr "Конвертувати в"
-#: src/autoload/Indications.gd src/ui_widgets/palette_config.gd
+#: src/autoload/State.gd src/ui_widgets/palette_config.gd
msgid "Move Up"
msgstr "Пересунути вгору"
-#: src/autoload/Indications.gd src/ui_widgets/palette_config.gd
+#: src/autoload/State.gd src/ui_widgets/palette_config.gd
msgid "Move Down"
msgstr "Пересунути вниз"
-#: src/autoload/Indications.gd src/ui_widgets/palette_config.gd
+#: src/autoload/State.gd src/ui_widgets/palette_config.gd
#: src/ui_widgets/setting_shortcut.gd src/ui_widgets/transform_popup.gd
msgid "Delete"
msgstr "Видалити"
-#: src/autoload/Indications.gd src/ui_widgets/transform_popup.gd
+#: src/autoload/State.gd src/ui_widgets/transform_popup.gd
msgid "Insert After"
msgstr "Вставити після"
+#: src/autoload/State.gd
+msgid "The last edited state of this tab could not be found."
+msgstr ""
+
+#: src/autoload/State.gd
+msgid ""
+"The tab is bound to the file path {file_path}. Do you want to restore from "
+"this path?"
+msgstr ""
+
#: src/config_classes/Formatter.gd
msgid "Compact"
msgstr "Компактно"
@@ -121,6 +145,10 @@ msgstr "3 або 6 значне шістнадцятирічне число"
msgid "6-digit hex"
msgstr "6 значне шістнадцятирічне число"
+#: src/config_classes/TabData.gd
+msgid "Unsaved"
+msgstr ""
+
#: src/data_classes/BasicXNode.gd
msgid "Comment"
msgstr "Коментар"
@@ -141,18 +169,15 @@ msgstr "Ця група не має елементів."
msgid "This group has only one element."
msgstr "Ця група має тільки один елемент."
-#: src/data_classes/ElementLinearGradient.gd
-#: src/data_classes/ElementRadialGradient.gd
+#: src/data_classes/GradientUtils.gd
msgid "No \"id\" attribute defined."
msgstr "Не вказано \"id\" атрибуту."
-#: src/data_classes/ElementLinearGradient.gd
-#: src/data_classes/ElementRadialGradient.gd
+#: src/data_classes/GradientUtils.gd
msgid "No elements under this gradient."
msgstr "Немає елементів під цим градієнтом."
-#: src/data_classes/ElementLinearGradient.gd
-#: src/data_classes/ElementRadialGradient.gd
+#: src/data_classes/GradientUtils.gd
msgid "This gradient is a solid color."
msgstr "Цей градієнт це суцільний колір."
@@ -164,6 +189,12 @@ msgstr "Не є описом SVG."
msgid "Improper nesting."
msgstr "Неправильне вкладення."
+#: src/ui_parts/about_menu.gd src/ui_parts/good_file_dialog.gd
+#: src/ui_parts/settings_menu.gd src/ui_parts/shortcut_panel_config.gd
+#: src/ui_parts/update_menu.gd
+msgid "Close"
+msgstr "Закрити"
+
#: src/ui_parts/about_menu.gd
msgid "Project Founder and Manager"
msgstr "Засновник і керівник проекту"
@@ -204,51 +235,6 @@ msgstr "Компоненти Godot від третіх сторін"
msgid "Third-party licenses"
msgstr "Ліцензії третіх осіб"
-#: src/ui_parts/alert_dialog.gd src/ui_parts/good_file_dialog.gd
-msgid "Alert!"
-msgstr "Увага!"
-
-#: src/ui_parts/choose_name_dialog.gd src/ui_parts/confirm_dialog.gd
-#: src/ui_parts/export_menu.gd src/ui_parts/import_warning_menu.gd
-msgid "Cancel"
-msgstr "Скасувати"
-
-#: src/ui_parts/choose_name_dialog.gd
-msgid "Create"
-msgstr "Створити"
-
-#: src/ui_parts/code_editor.gd src/ui_parts/good_file_dialog.gd
-msgid "Save SVG"
-msgstr "Зберегти SVG"
-
-#: src/ui_parts/code_editor.gd
-msgid "Open file"
-msgstr "Відкрити файл"
-
-#: src/ui_parts/code_editor.gd src/utils/TranslationUtils.gd
-msgid "Reset SVG"
-msgstr "Скинути SVG"
-
-#: src/ui_parts/code_editor.gd src/utils/TranslationUtils.gd
-msgid "Clear saving path"
-msgstr "Очистити шлях збереження"
-
-#: src/ui_parts/code_editor.gd src/utils/TranslationUtils.gd
-msgid "Optimize"
-msgstr "Оптимізувати"
-
-#: src/ui_parts/code_editor.gd src/utils/TranslationUtils.gd
-msgid "Copy all text"
-msgstr "Скопіювати весь текст"
-
-#: src/ui_parts/code_editor.gd src/utils/TranslationUtils.gd
-msgid "Clear SVG"
-msgstr "Очистити SVG"
-
-#: src/ui_parts/display.gd
-msgid "Settings"
-msgstr "Налаштування"
-
#: src/ui_parts/display.gd
msgid "Visuals"
msgstr "Зовнішній вигляд"
@@ -278,33 +264,15 @@ msgstr "Показати ручки редагування"
msgid "Rasterized SVG"
msgstr "Растеризувати SVG"
-#: src/ui_parts/display.gd
-msgid "About…"
-msgstr "Про додаток…"
-
#: src/ui_parts/display.gd
msgid "Snap size"
msgstr "Розмір прилипання"
-#: src/ui_parts/display.gd src/utils/TranslationUtils.gd
-msgid "Check for updates"
-msgstr "Перевірити оновлення"
-
-#: src/ui_parts/display.gd
-msgid "Donate…"
-msgstr "Зробити пожертвування…"
-
-#: src/ui_parts/display.gd
-msgid "GodSVG repository"
-msgstr "Репозиторій GodSVG"
-
-#: src/ui_parts/display.gd
-msgid "GodSVG website"
-msgstr "Веб-сайт GodSVG"
-
-#: src/ui_parts/display.gd
-msgid "View savedata"
-msgstr "Переглянути дані збереження"
+#: src/ui_parts/donate_menu.gd src/ui_parts/export_menu.gd
+#: src/ui_parts/import_warning_menu.gd src/ui_widgets/choose_name_dialog.gd
+#: src/ui_widgets/confirm_dialog.gd
+msgid "Cancel"
+msgstr "Скасувати"
#: src/ui_parts/element_container.gd
msgid "New element"
@@ -314,11 +282,6 @@ msgstr "Новий елемент"
msgid "Dimensions"
msgstr "Розміри"
-#: src/ui_parts/export_menu.gd src/ui_widgets/configure_color_popup.gd
-#: src/ui_widgets/palette_config.gd
-msgid "Unnamed"
-msgstr "Без назви"
-
#: src/ui_parts/export_menu.gd
msgid "Size"
msgstr "Розмір"
@@ -355,25 +318,58 @@ msgstr "Висота"
msgid "Preview image size is limited to {dimensions}"
msgstr "Розмір попереднього перегляду зображення обмежено до {dimensions}"
-#: src/ui_parts/global_menu.gd src/ui_parts/settings_menu.gd
-msgid "File"
-msgstr "Файл"
+#: src/ui_parts/global_actions.gd src/ui_parts/import_warning_menu.gd
+#: src/utils/TranslationUtils.gd
+msgid "Import"
+msgstr "Імпортувати"
-#: src/ui_parts/global_menu.gd src/ui_parts/settings_menu.gd
-msgid "Edit"
-msgstr "Редагування"
+#: src/ui_parts/global_actions.gd
+msgid "Settings"
+msgstr "Налаштування"
-#: src/ui_parts/global_menu.gd src/ui_parts/settings_menu.gd
-msgid "Tool"
-msgstr "Інструмент"
+#: src/ui_parts/global_actions.gd src/utils/TranslationUtils.gd
+msgid "Optimize"
+msgstr "Оптимізувати"
-#: src/ui_parts/global_menu.gd src/ui_parts/settings_menu.gd
-msgid "View"
-msgstr "Перегляд"
+#: src/ui_parts/global_actions.gd src/ui_parts/good_file_dialog.gd
+msgid "Save SVG"
+msgstr "Зберегти SVG"
-#: src/ui_parts/global_menu.gd
-msgid "Snap"
-msgstr "Прилипання"
+#: src/ui_parts/global_actions.gd src/utils/TranslationUtils.gd
+msgid "Reset SVG"
+msgstr "Скинути SVG"
+
+#: src/ui_parts/global_actions.gd src/ui_parts/tab_bar.gd
+msgid "Open externally"
+msgstr ""
+
+#: src/ui_parts/global_actions.gd src/ui_parts/tab_bar.gd
+msgid "Show in File Manager"
+msgstr ""
+
+#: src/ui_parts/global_actions.gd
+msgid "About…"
+msgstr "Про додаток…"
+
+#: src/ui_parts/global_actions.gd src/utils/TranslationUtils.gd
+msgid "Check for updates"
+msgstr "Перевірити оновлення"
+
+#: src/ui_parts/global_actions.gd
+msgid "Donate…"
+msgstr "Зробити пожертвування…"
+
+#: src/ui_parts/global_actions.gd
+msgid "GodSVG repository"
+msgstr "Репозиторій GodSVG"
+
+#: src/ui_parts/global_actions.gd
+msgid "GodSVG website"
+msgstr "Веб-сайт GodSVG"
+
+#: src/ui_parts/global_actions.gd
+msgid "View savedata"
+msgstr "Переглянути дані збереження"
#: src/ui_parts/good_file_dialog.gd
msgid "Go to parent folder"
@@ -403,11 +399,6 @@ msgstr "Обрати XML файл"
msgid "Select an image"
msgstr "Обрати зображення"
-#: src/ui_parts/good_file_dialog.gd src/ui_parts/settings_menu.gd
-#: src/ui_parts/shortcut_panel_config.gd src/ui_parts/update_menu.gd
-msgid "Close"
-msgstr "Закрити"
-
#: src/ui_parts/good_file_dialog.gd
msgid "Select"
msgstr "Обрати"
@@ -464,10 +455,6 @@ msgstr "Нова форма"
msgid "Import Problems"
msgstr "Проблема з імпортуванням"
-#: src/ui_parts/import_warning_menu.gd src/utils/TranslationUtils.gd
-msgid "Import"
-msgstr "Імпортувати"
-
#: src/ui_parts/import_warning_menu.gd
msgid "Unrecognized element"
msgstr "Невідомий елемент"
@@ -484,6 +471,26 @@ msgstr "Синтаксична помилка"
msgid "Add element"
msgstr "Додати елемент"
+#: src/ui_parts/mac_menu.gd src/ui_parts/settings_menu.gd
+msgid "File"
+msgstr "Файл"
+
+#: src/ui_parts/mac_menu.gd src/ui_parts/settings_menu.gd
+msgid "Edit"
+msgstr "Редагування"
+
+#: src/ui_parts/mac_menu.gd src/ui_parts/settings_menu.gd
+msgid "Tool"
+msgstr "Інструмент"
+
+#: src/ui_parts/mac_menu.gd src/ui_parts/settings_menu.gd
+msgid "View"
+msgstr "Перегляд"
+
+#: src/ui_parts/mac_menu.gd
+msgid "Snap"
+msgstr "Прилипання"
+
#: src/ui_parts/settings_menu.gd
msgid "Formatting"
msgstr "Форматування"
@@ -823,6 +830,33 @@ msgstr "Горизонтальна лінія до"
msgid "Vertical strip"
msgstr "Вертикальна лінія до"
+#: src/ui_parts/tab_bar.gd
+#, fuzzy
+msgid "Create tab"
+msgstr "Створити"
+
+#: src/ui_parts/tab_bar.gd
+#, fuzzy
+msgid "Close all other tabs"
+msgstr "Скопіювати весь текст"
+
+#: src/ui_parts/tab_bar.gd
+msgid "Close tabs to the left"
+msgstr ""
+
+#: src/ui_parts/tab_bar.gd
+msgid "Close tabs to the right"
+msgstr ""
+
+#: src/ui_parts/tab_bar.gd src/utils/TranslationUtils.gd
+#, fuzzy
+msgid "Create a new tab"
+msgstr "Створити нову теку"
+
+#: src/ui_parts/tab_bar.gd
+msgid "This SVG is not bound to a location on the computer yet."
+msgstr ""
+
#: src/ui_parts/update_menu.gd
msgid "Include prereleases"
msgstr "Включати тестові версії"
@@ -851,6 +885,18 @@ msgstr "GodSVG оновлено до останньої версії."
msgid "New versions"
msgstr "Нові версії"
+#: src/ui_parts/zoom_menu.gd src/utils/TranslationUtils.gd
+msgid "Zoom out"
+msgstr "Зменшити масштаб"
+
+#: src/ui_parts/zoom_menu.gd src/utils/TranslationUtils.gd
+msgid "Zoom in"
+msgstr "Збільшити масштаб"
+
+#: src/ui_parts/zoom_menu.gd src/utils/TranslationUtils.gd
+msgid "Zoom reset"
+msgstr "Скинути масштаб"
+
#: src/ui_widgets/BetterLineEdit.gd src/ui_widgets/BetterTextEdit.gd
#: src/utils/TranslationUtils.gd
msgid "Undo"
@@ -873,6 +919,14 @@ msgstr "Скопіювати"
msgid "Paste"
msgstr "Вставити"
+#: src/ui_widgets/choose_name_dialog.gd
+msgid "Create"
+msgstr "Створити"
+
+#: src/ui_widgets/code_editor.gd src/utils/TranslationUtils.gd
+msgid "Copy all text"
+msgstr "Скопіювати весь текст"
+
#: src/ui_widgets/color_popup.gd
msgid "Search color"
msgstr "Шукати колір"
@@ -889,6 +943,10 @@ msgstr "Редагувати назву кольору"
msgid "Delete color"
msgstr "Видалити колір"
+#: src/ui_widgets/configure_color_popup.gd src/ui_widgets/palette_config.gd
+msgid "Unnamed"
+msgstr "Без назви"
+
#: src/ui_widgets/good_color_picker.gd
#, fuzzy
msgid "Color keywords"
@@ -922,6 +980,11 @@ msgstr "Скопіювати як XML"
msgid "Apply Preset"
msgstr "Застосувати пресет"
+#: src/ui_widgets/path_popup.gd src/ui_widgets/pathdata_field.gd
+#: src/utils/TranslationUtils.gd
+msgid "Relative"
+msgstr "Відносно"
+
#: src/ui_widgets/pathdata_field.gd
msgid "No path data"
msgstr "Немає даних шляху"
@@ -930,10 +993,6 @@ msgstr "Немає даних шляху"
msgid "Absolute"
msgstr "Абсолютно"
-#: src/ui_widgets/pathdata_field.gd src/utils/TranslationUtils.gd
-msgid "Relative"
-msgstr "Відносно"
-
#: src/ui_widgets/points_field.gd
msgid "No points"
msgstr "Немає точок"
@@ -998,47 +1057,59 @@ msgstr "Цей файл неможливо відкрити."
msgid "Check if the file still exists in the selected file path."
msgstr "Перевірити, чи файл все щє існує по обраному шляху."
+#: src/utils/FileUtils.gd
+msgid "The imported file is already being edited inside GodSVG."
+msgstr ""
+
+#: src/utils/FileUtils.gd
+msgid "If you want to apply the unsaved file state, use \"Reset SVG\" instead."
+msgstr ""
+
+#: src/utils/TranslationUtils.gd
+msgid "Select the next tab"
+msgstr ""
+
+#: src/utils/TranslationUtils.gd
+msgid "Select the previous tab"
+msgstr ""
+
#: src/utils/TranslationUtils.gd
#, fuzzy
-msgid "Open SVG file"
-msgstr "Відкрити файл"
+msgid "Open SVG externally"
+msgstr "Відкрити репозиторій GodSVG"
#: src/utils/TranslationUtils.gd
-msgid "Select all elements"
-msgstr "Обрати всі елементи"
+msgid "Show SVG in File Manager"
+msgstr ""
#: src/utils/TranslationUtils.gd
-msgid "Duplicate the selected elements"
-msgstr "Дублювати обрані елементи"
+#, fuzzy
+msgid "Select all"
+msgstr "Обрати"
+
+#: src/utils/TranslationUtils.gd
+#, fuzzy
+msgid "Duplicate the selection"
+msgstr "Видалити усі обрані теги"
#: src/utils/TranslationUtils.gd
msgid "Delete the selection"
msgstr "Видалити усі обрані теги"
#: src/utils/TranslationUtils.gd
-msgid "Move the selected elements up"
+#, fuzzy
+msgid "Move the selection up"
msgstr "Пересунути всі обрані елементи вгору"
#: src/utils/TranslationUtils.gd
-msgid "Move the selected elements down"
+#, fuzzy
+msgid "Move the selection down"
msgstr "Пересунути всі обрані елементи вниз"
#: src/utils/TranslationUtils.gd
msgid "Find"
msgstr "Знайти"
-#: src/utils/TranslationUtils.gd
-msgid "Zoom in"
-msgstr "Збільшити масштаб"
-
-#: src/utils/TranslationUtils.gd
-msgid "Zoom out"
-msgstr "Зменшити масштаб"
-
-#: src/utils/TranslationUtils.gd
-msgid "Zoom reset"
-msgstr "Скинути масштаб"
-
#: src/utils/TranslationUtils.gd
msgid "Show rasterized SVG"
msgstr "Показати растеризуваний SVG"
@@ -1136,6 +1207,29 @@ msgstr ""
msgid "The file extension is empty. Only {extension_list} files are supported."
msgstr "Розширення файлу порожнє. Тільки {extension_list} файли підтримуються."
+#, fuzzy
+#~ msgid "Add new tab"
+#~ msgstr "Додати новий тег"
+
+#~ msgid "Open file"
+#~ msgstr "Відкрити файл"
+
+#~ msgid "Clear saving path"
+#~ msgstr "Очистити шлях збереження"
+
+#~ msgid "Clear SVG"
+#~ msgstr "Очистити SVG"
+
+#, fuzzy
+#~ msgid "Open SVG file"
+#~ msgstr "Відкрити файл"
+
+#~ msgid "Select all elements"
+#~ msgstr "Обрати всі елементи"
+
+#~ msgid "Duplicate the selected elements"
+#~ msgstr "Дублювати обрані елементи"
+
#~ msgid "Enable the color"
#~ msgstr "Активувати колір"
@@ -1199,9 +1293,6 @@ msgstr "Розширення файлу порожнє. Тільки {extension_
#~ msgid "New tag"
#~ msgstr "Новий тег"
-#~ msgid "Add new tag"
-#~ msgstr "Додати новий тег"
-
#~ msgid "Paths"
#~ msgstr "Шляхи"
diff --git a/assets/translations/zh.po b/assets/translations/zh.po
index ffd4ca3..e2741f6 100644
--- a/assets/translations/zh.po
+++ b/assets/translations/zh.po
@@ -35,7 +35,7 @@ msgstr "检查更新?"
msgid "This requires GodSVG to connect to the internet."
msgstr "这需要 GodSVG 连接互联网。"
-#: src/autoload/HandlerGUI.gd src/ui_parts/alert_dialog.gd
+#: src/autoload/HandlerGUI.gd src/ui_widgets/alert_dialog.gd
msgid "OK"
msgstr "确定"
@@ -44,7 +44,7 @@ msgid "Export SVG"
msgstr "导出 SVG"
#: src/autoload/HandlerGUI.gd src/ui_parts/export_menu.gd
-#: src/utils/TranslationUtils.gd
+#: src/ui_parts/global_actions.gd src/utils/TranslationUtils.gd
msgid "Export"
msgstr "导出"
@@ -54,35 +54,59 @@ msgid ""
"you want to proceed?"
msgstr "此图像只能以 SVG 格式导出,因为其大小未定义。您确定要继续吗?"
-#: src/autoload/Indications.gd
-msgid "View In List"
+#: src/autoload/State.gd src/ui_parts/good_file_dialog.gd
+#: src/ui_widgets/alert_dialog.gd
+msgid "Alert!"
+msgstr "警告!"
+
+#: src/autoload/State.gd src/ui_parts/tab_bar.gd src/utils/TranslationUtils.gd
+#, fuzzy
+msgid "Close tab"
+msgstr "闭合路径"
+
+#: src/autoload/State.gd
+msgid "Restore"
+msgstr ""
+
+#: src/autoload/State.gd
+msgid "View in List"
msgstr "在列表中查看"
-#: src/autoload/Indications.gd
+#: src/autoload/State.gd
msgid "Duplicate"
msgstr "克隆"
-#: src/autoload/Indications.gd
+#: src/autoload/State.gd
msgid "Convert To"
msgstr "转换为"
-#: src/autoload/Indications.gd src/ui_widgets/palette_config.gd
+#: src/autoload/State.gd src/ui_widgets/palette_config.gd
msgid "Move Up"
msgstr "上移"
-#: src/autoload/Indications.gd src/ui_widgets/palette_config.gd
+#: src/autoload/State.gd src/ui_widgets/palette_config.gd
msgid "Move Down"
msgstr "下移"
-#: src/autoload/Indications.gd src/ui_widgets/palette_config.gd
+#: src/autoload/State.gd src/ui_widgets/palette_config.gd
#: src/ui_widgets/setting_shortcut.gd src/ui_widgets/transform_popup.gd
msgid "Delete"
msgstr "删除"
-#: src/autoload/Indications.gd src/ui_widgets/transform_popup.gd
+#: src/autoload/State.gd src/ui_widgets/transform_popup.gd
msgid "Insert After"
msgstr "在后方插入"
+#: src/autoload/State.gd
+msgid "The last edited state of this tab could not be found."
+msgstr ""
+
+#: src/autoload/State.gd
+msgid ""
+"The tab is bound to the file path {file_path}. Do you want to restore from "
+"this path?"
+msgstr ""
+
#: src/config_classes/Formatter.gd
msgid "Compact"
msgstr "压缩"
@@ -119,6 +143,10 @@ msgstr "3 位或 6 位十六进制"
msgid "6-digit hex"
msgstr "6 位十六进制"
+#: src/config_classes/TabData.gd
+msgid "Unsaved"
+msgstr ""
+
#: src/data_classes/BasicXNode.gd
msgid "Comment"
msgstr "注释"
@@ -139,18 +167,15 @@ msgstr "此组内没有元素。"
msgid "This group has only one element."
msgstr "此组内只有一个元素。"
-#: src/data_classes/ElementLinearGradient.gd
-#: src/data_classes/ElementRadialGradient.gd
+#: src/data_classes/GradientUtils.gd
msgid "No \"id\" attribute defined."
msgstr "\"id\" 属性未定义。"
-#: src/data_classes/ElementLinearGradient.gd
-#: src/data_classes/ElementRadialGradient.gd
+#: src/data_classes/GradientUtils.gd
msgid "No elements under this gradient."
msgstr "此渐变下没有 元素。"
-#: src/data_classes/ElementLinearGradient.gd
-#: src/data_classes/ElementRadialGradient.gd
+#: src/data_classes/GradientUtils.gd
msgid "This gradient is a solid color."
msgstr "此渐变为单一颜色。"
@@ -162,6 +187,12 @@ msgstr "文本不是 SVG 格式。"
msgid "Improper nesting."
msgstr "错误的嵌套。"
+#: src/ui_parts/about_menu.gd src/ui_parts/good_file_dialog.gd
+#: src/ui_parts/settings_menu.gd src/ui_parts/shortcut_panel_config.gd
+#: src/ui_parts/update_menu.gd
+msgid "Close"
+msgstr "关闭"
+
#: src/ui_parts/about_menu.gd
msgid "Project Founder and Manager"
msgstr "项目创立者与管理者"
@@ -202,51 +233,6 @@ msgstr "Godot 第三方组件"
msgid "Third-party licenses"
msgstr "第三方协议"
-#: src/ui_parts/alert_dialog.gd src/ui_parts/good_file_dialog.gd
-msgid "Alert!"
-msgstr "警告!"
-
-#: src/ui_parts/choose_name_dialog.gd src/ui_parts/confirm_dialog.gd
-#: src/ui_parts/export_menu.gd src/ui_parts/import_warning_menu.gd
-msgid "Cancel"
-msgstr "取消"
-
-#: src/ui_parts/choose_name_dialog.gd
-msgid "Create"
-msgstr "创建"
-
-#: src/ui_parts/code_editor.gd src/ui_parts/good_file_dialog.gd
-msgid "Save SVG"
-msgstr "保存 SVG"
-
-#: src/ui_parts/code_editor.gd
-msgid "Open file"
-msgstr "打开文件"
-
-#: src/ui_parts/code_editor.gd src/utils/TranslationUtils.gd
-msgid "Reset SVG"
-msgstr "重置 SVG"
-
-#: src/ui_parts/code_editor.gd src/utils/TranslationUtils.gd
-msgid "Clear saving path"
-msgstr "清除保存路径"
-
-#: src/ui_parts/code_editor.gd src/utils/TranslationUtils.gd
-msgid "Optimize"
-msgstr "优化"
-
-#: src/ui_parts/code_editor.gd src/utils/TranslationUtils.gd
-msgid "Copy all text"
-msgstr "复制所有文本"
-
-#: src/ui_parts/code_editor.gd src/utils/TranslationUtils.gd
-msgid "Clear SVG"
-msgstr "清除 SVG"
-
-#: src/ui_parts/display.gd
-msgid "Settings"
-msgstr "设置"
-
#: src/ui_parts/display.gd
msgid "Visuals"
msgstr "显示"
@@ -275,33 +261,15 @@ msgstr "显示拖拽框"
msgid "Rasterized SVG"
msgstr "光栅化 SVG"
-#: src/ui_parts/display.gd
-msgid "About…"
-msgstr "关于…"
-
#: src/ui_parts/display.gd
msgid "Snap size"
msgstr "吸附大小"
-#: src/ui_parts/display.gd src/utils/TranslationUtils.gd
-msgid "Check for updates"
-msgstr "检查更新"
-
-#: src/ui_parts/display.gd
-msgid "Donate…"
-msgstr "捐赠…"
-
-#: src/ui_parts/display.gd
-msgid "GodSVG repository"
-msgstr "GodSVG 代码仓库"
-
-#: src/ui_parts/display.gd
-msgid "GodSVG website"
-msgstr "GodSVG 网站"
-
-#: src/ui_parts/display.gd
-msgid "View savedata"
-msgstr "查看保存的数据"
+#: src/ui_parts/donate_menu.gd src/ui_parts/export_menu.gd
+#: src/ui_parts/import_warning_menu.gd src/ui_widgets/choose_name_dialog.gd
+#: src/ui_widgets/confirm_dialog.gd
+msgid "Cancel"
+msgstr "取消"
#: src/ui_parts/element_container.gd
msgid "New element"
@@ -311,11 +279,6 @@ msgstr "新元素"
msgid "Dimensions"
msgstr "大小"
-#: src/ui_parts/export_menu.gd src/ui_widgets/configure_color_popup.gd
-#: src/ui_widgets/palette_config.gd
-msgid "Unnamed"
-msgstr "未命名"
-
#: src/ui_parts/export_menu.gd
msgid "Size"
msgstr "大小"
@@ -352,25 +315,58 @@ msgstr "高度"
msgid "Preview image size is limited to {dimensions}"
msgstr "预览图像大小被限制到 {dimensions}"
-#: src/ui_parts/global_menu.gd src/ui_parts/settings_menu.gd
-msgid "File"
-msgstr "文件"
+#: src/ui_parts/global_actions.gd src/ui_parts/import_warning_menu.gd
+#: src/utils/TranslationUtils.gd
+msgid "Import"
+msgstr "导入"
-#: src/ui_parts/global_menu.gd src/ui_parts/settings_menu.gd
-msgid "Edit"
-msgstr "编辑"
+#: src/ui_parts/global_actions.gd
+msgid "Settings"
+msgstr "设置"
-#: src/ui_parts/global_menu.gd src/ui_parts/settings_menu.gd
-msgid "Tool"
-msgstr "工具"
+#: src/ui_parts/global_actions.gd src/utils/TranslationUtils.gd
+msgid "Optimize"
+msgstr "优化"
-#: src/ui_parts/global_menu.gd src/ui_parts/settings_menu.gd
-msgid "View"
-msgstr "视图"
+#: src/ui_parts/global_actions.gd src/ui_parts/good_file_dialog.gd
+msgid "Save SVG"
+msgstr "保存 SVG"
-#: src/ui_parts/global_menu.gd
-msgid "Snap"
-msgstr "吸附"
+#: src/ui_parts/global_actions.gd src/utils/TranslationUtils.gd
+msgid "Reset SVG"
+msgstr "重置 SVG"
+
+#: src/ui_parts/global_actions.gd src/ui_parts/tab_bar.gd
+msgid "Open externally"
+msgstr ""
+
+#: src/ui_parts/global_actions.gd src/ui_parts/tab_bar.gd
+msgid "Show in File Manager"
+msgstr ""
+
+#: src/ui_parts/global_actions.gd
+msgid "About…"
+msgstr "关于…"
+
+#: src/ui_parts/global_actions.gd src/utils/TranslationUtils.gd
+msgid "Check for updates"
+msgstr "检查更新"
+
+#: src/ui_parts/global_actions.gd
+msgid "Donate…"
+msgstr "捐赠…"
+
+#: src/ui_parts/global_actions.gd
+msgid "GodSVG repository"
+msgstr "GodSVG 代码仓库"
+
+#: src/ui_parts/global_actions.gd
+msgid "GodSVG website"
+msgstr "GodSVG 网站"
+
+#: src/ui_parts/global_actions.gd
+msgid "View savedata"
+msgstr "查看保存的数据"
#: src/ui_parts/good_file_dialog.gd
msgid "Go to parent folder"
@@ -400,11 +396,6 @@ msgstr "选择 XML 文件"
msgid "Select an image"
msgstr "选择图像"
-#: src/ui_parts/good_file_dialog.gd src/ui_parts/settings_menu.gd
-#: src/ui_parts/shortcut_panel_config.gd src/ui_parts/update_menu.gd
-msgid "Close"
-msgstr "关闭"
-
#: src/ui_parts/good_file_dialog.gd
msgid "Select"
msgstr "选择"
@@ -459,10 +450,6 @@ msgstr "新形状"
msgid "Import Problems"
msgstr "导入出错"
-#: src/ui_parts/import_warning_menu.gd src/utils/TranslationUtils.gd
-msgid "Import"
-msgstr "导入"
-
#: src/ui_parts/import_warning_menu.gd
msgid "Unrecognized element"
msgstr "未知元素"
@@ -479,6 +466,26 @@ msgstr "语法错误"
msgid "Add element"
msgstr "添加元素"
+#: src/ui_parts/mac_menu.gd src/ui_parts/settings_menu.gd
+msgid "File"
+msgstr "文件"
+
+#: src/ui_parts/mac_menu.gd src/ui_parts/settings_menu.gd
+msgid "Edit"
+msgstr "编辑"
+
+#: src/ui_parts/mac_menu.gd src/ui_parts/settings_menu.gd
+msgid "Tool"
+msgstr "工具"
+
+#: src/ui_parts/mac_menu.gd src/ui_parts/settings_menu.gd
+msgid "View"
+msgstr "视图"
+
+#: src/ui_parts/mac_menu.gd
+msgid "Snap"
+msgstr "吸附"
+
#: src/ui_parts/settings_menu.gd
msgid "Formatting"
msgstr "格式化"
@@ -809,6 +816,33 @@ msgstr "横线"
msgid "Vertical strip"
msgstr "竖线"
+#: src/ui_parts/tab_bar.gd
+#, fuzzy
+msgid "Create tab"
+msgstr "创建"
+
+#: src/ui_parts/tab_bar.gd
+#, fuzzy
+msgid "Close all other tabs"
+msgstr "复制所有文本"
+
+#: src/ui_parts/tab_bar.gd
+msgid "Close tabs to the left"
+msgstr ""
+
+#: src/ui_parts/tab_bar.gd
+msgid "Close tabs to the right"
+msgstr ""
+
+#: src/ui_parts/tab_bar.gd src/utils/TranslationUtils.gd
+#, fuzzy
+msgid "Create a new tab"
+msgstr "新建文件夹"
+
+#: src/ui_parts/tab_bar.gd
+msgid "This SVG is not bound to a location on the computer yet."
+msgstr ""
+
#: src/ui_parts/update_menu.gd
msgid "Include prereleases"
msgstr "包括预发布版本"
@@ -837,6 +871,18 @@ msgstr "GodSVG 是最新版本。"
msgid "New versions"
msgstr "新版本"
+#: src/ui_parts/zoom_menu.gd src/utils/TranslationUtils.gd
+msgid "Zoom out"
+msgstr "缩小"
+
+#: src/ui_parts/zoom_menu.gd src/utils/TranslationUtils.gd
+msgid "Zoom in"
+msgstr "放大"
+
+#: src/ui_parts/zoom_menu.gd src/utils/TranslationUtils.gd
+msgid "Zoom reset"
+msgstr "重置缩放"
+
#: src/ui_widgets/BetterLineEdit.gd src/ui_widgets/BetterTextEdit.gd
#: src/utils/TranslationUtils.gd
msgid "Undo"
@@ -859,6 +905,14 @@ msgstr "复制"
msgid "Paste"
msgstr "粘贴"
+#: src/ui_widgets/choose_name_dialog.gd
+msgid "Create"
+msgstr "创建"
+
+#: src/ui_widgets/code_editor.gd src/utils/TranslationUtils.gd
+msgid "Copy all text"
+msgstr "复制所有文本"
+
#: src/ui_widgets/color_popup.gd
msgid "Search color"
msgstr "搜索颜色"
@@ -875,6 +929,10 @@ msgstr "编辑颜色名称"
msgid "Delete color"
msgstr "删除颜色"
+#: src/ui_widgets/configure_color_popup.gd src/ui_widgets/palette_config.gd
+msgid "Unnamed"
+msgstr "未命名"
+
#: src/ui_widgets/good_color_picker.gd
#, fuzzy
msgid "Color keywords"
@@ -908,6 +966,11 @@ msgstr "复制为 XML"
msgid "Apply Preset"
msgstr "应用预设"
+#: src/ui_widgets/path_popup.gd src/ui_widgets/pathdata_field.gd
+#: src/utils/TranslationUtils.gd
+msgid "Relative"
+msgstr "相对"
+
#: src/ui_widgets/pathdata_field.gd
msgid "No path data"
msgstr "没有路径数据"
@@ -916,10 +979,6 @@ msgstr "没有路径数据"
msgid "Absolute"
msgstr "绝对"
-#: src/ui_widgets/pathdata_field.gd src/utils/TranslationUtils.gd
-msgid "Relative"
-msgstr "相对"
-
#: src/ui_widgets/points_field.gd
msgid "No points"
msgstr "没有点"
@@ -984,47 +1043,59 @@ msgstr "无法打开文件。"
msgid "Check if the file still exists in the selected file path."
msgstr "检查文件是否还存在于选中的路径。"
+#: src/utils/FileUtils.gd
+msgid "The imported file is already being edited inside GodSVG."
+msgstr ""
+
+#: src/utils/FileUtils.gd
+msgid "If you want to apply the unsaved file state, use \"Reset SVG\" instead."
+msgstr ""
+
+#: src/utils/TranslationUtils.gd
+msgid "Select the next tab"
+msgstr ""
+
+#: src/utils/TranslationUtils.gd
+msgid "Select the previous tab"
+msgstr ""
+
#: src/utils/TranslationUtils.gd
#, fuzzy
-msgid "Open SVG file"
-msgstr "打开文件"
+msgid "Open SVG externally"
+msgstr "打开 GodSVG 代码仓库"
#: src/utils/TranslationUtils.gd
-msgid "Select all elements"
-msgstr "选择所有元素"
+msgid "Show SVG in File Manager"
+msgstr ""
#: src/utils/TranslationUtils.gd
-msgid "Duplicate the selected elements"
-msgstr "克隆选中的元素"
+#, fuzzy
+msgid "Select all"
+msgstr "选择"
+
+#: src/utils/TranslationUtils.gd
+#, fuzzy
+msgid "Duplicate the selection"
+msgstr "删除所选项"
#: src/utils/TranslationUtils.gd
msgid "Delete the selection"
msgstr "删除所选项"
#: src/utils/TranslationUtils.gd
-msgid "Move the selected elements up"
+#, fuzzy
+msgid "Move the selection up"
msgstr "将选中的元素上移"
#: src/utils/TranslationUtils.gd
-msgid "Move the selected elements down"
+#, fuzzy
+msgid "Move the selection down"
msgstr "将选中的元素下移"
#: src/utils/TranslationUtils.gd
msgid "Find"
msgstr "查找"
-#: src/utils/TranslationUtils.gd
-msgid "Zoom in"
-msgstr "放大"
-
-#: src/utils/TranslationUtils.gd
-msgid "Zoom out"
-msgstr "缩小"
-
-#: src/utils/TranslationUtils.gd
-msgid "Zoom reset"
-msgstr "重置缩放"
-
#: src/utils/TranslationUtils.gd
msgid "Show rasterized SVG"
msgstr "显示光栅化的 SVG"
@@ -1120,6 +1191,29 @@ msgstr ""
msgid "The file extension is empty. Only {extension_list} files are supported."
msgstr "文件扩展名为空。只有 {extension_list} 内的文件受支持。"
+#, fuzzy
+#~ msgid "Add new tab"
+#~ msgstr "添加新元素"
+
+#~ msgid "Open file"
+#~ msgstr "打开文件"
+
+#~ msgid "Clear saving path"
+#~ msgstr "清除保存路径"
+
+#~ msgid "Clear SVG"
+#~ msgstr "清除 SVG"
+
+#, fuzzy
+#~ msgid "Open SVG file"
+#~ msgstr "打开文件"
+
+#~ msgid "Select all elements"
+#~ msgstr "选择所有元素"
+
+#~ msgid "Duplicate the selected elements"
+#~ msgstr "克隆选中的元素"
+
#~ msgid "Enable the color"
#~ msgstr "启用该颜色"
@@ -1181,9 +1275,6 @@ msgstr "文件扩展名为空。只有 {extension_list} 内的文件受支持。
#~ msgid "Unknown tag"
#~ msgstr "未知的元素"
-#~ msgid "Add new tag"
-#~ msgstr "添加新元素"
-
#~ msgid "Paths"
#~ msgstr "文件路径"
diff --git a/godot_only/scripts/update_translations.gd b/godot_only/scripts/update_translations.gd
index 5bacd22..2c21ae2 100644
--- a/godot_only/scripts/update_translations.gd
+++ b/godot_only/scripts/update_translations.gd
@@ -2,6 +2,8 @@
@tool
extends EditorScript
+const TRANSLATIONS_DIR = "assets/translations"
+
const HEADER = """#, fuzzy
msgid \"\"
msgstr \"\"
@@ -82,28 +84,28 @@ func search_directory(dir: String) -> void:
func update_translations() -> void:
- var location := ProjectSettings.globalize_path("translations/GodSVG.pot")
+ var location := ProjectSettings.globalize_path(TRANSLATIONS_DIR + "/GodSVG.pot")
var fa := FileAccess.open(location, FileAccess.WRITE)
fa.store_string(HEADER)
for msg in messages:
fa.store_string(msg.to_string())
fa = null
- print("Created translations/GodSVG.pot with %d strings" % (messages.size() + 1))
+ print("Created " + TRANSLATIONS_DIR + "/GodSVG.pot with %d strings" % (messages.size() + 1))
- var files := DirAccess.get_files_at(ProjectSettings.globalize_path("translations"))
+ var files := DirAccess.get_files_at(ProjectSettings.globalize_path(TRANSLATIONS_DIR))
for file in files:
if not (file.get_extension() == "po" or file == "GodSVG.pot"):
continue
var args := PackedStringArray(["--update", "--quiet", "--verbose", "--backup=off",
- ProjectSettings.globalize_path("translations").path_join(file), location])
+ ProjectSettings.globalize_path(TRANSLATIONS_DIR).path_join(file), location])
var output: Array = []
var result := OS.execute("msgmerge", args, output, true)
if not result == -1:
if file == "GodSVG.pot":
continue
elif not output.is_empty():
- print("Updated translations/%s: %s" % [file, output[0].rstrip("\n")])
+ print("Updated " + TRANSLATIONS_DIR + "/%s: %s" % [file, output[0].rstrip("\n")])
else:
- print("Updated translations%s" % file)
+ print("Updated " + TRANSLATIONS_DIR + "%s" % file)
diff --git a/project.godot b/project.godot
index 480be61..83033b7 100644
--- a/project.godot
+++ b/project.godot
@@ -10,10 +10,10 @@ config_version=5
[application]
-config/name="GodSVG Mobile"
+config/name="GodSVG"
config/version="1.0-alpha7"
config/tags=PackedStringArray("project")
-run/main_scene="res://src/portrait_ui/editor_scene.tscn"
+run/main_scene="res://src/ui_parts/editor_scene.tscn"
config/use_custom_user_dir=true
config/features=PackedStringArray("4.4")
run/low_processor_mode=true
@@ -31,16 +31,15 @@ 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=720
-window/size/viewport_height=1280
+window/size/viewport_width=1040
+window/size/viewport_height=650
+window/size/mode=2
window/energy_saving/keep_screen_on=false
-window/handheld/orientation=1
mouse_cursor/tooltip_position_offset=Vector2(0, 10)
[filesystem]
@@ -51,67 +50,87 @@ import/fbx/enabled=false
[gui]
timers/tooltip_delay_sec=0.4
-theme/custom="res://temp_theme.tres"
[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)
]
}
+save_as={
+"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":83,"physical_keycode":0,"key_label":0,"unicode":83,"location":0,"echo":false,"script":null)
+]
+}
copy_svg_text={
-"deadzone": 0.5,
+"deadzone": 0.2,
"events": []
}
-clear_svg={
-"deadzone": 0.5,
+reset_svg={
+"deadzone": 0.2,
"events": []
}
-clear_file_path={
-"deadzone": 0.5,
+open_externally={
+"deadzone": 0.2,
"events": []
}
-reset_svg={
-"deadzone": 0.5,
+open_in_folder={
+"deadzone": 0.2,
"events": []
}
-open_svg={
-"deadzone": 0.5,
-"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)
]
}
@@ -121,200 +140,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)
]
}
@@ -326,6 +345,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 f6b3b93..e7eeb1c 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 a8c6516..b610fa5 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()
@@ -204,8 +208,9 @@ func _input(event: InputEvent) -> void:
event.double_click = true
last_mouse_click_double = false
- # Stuff that should replace the existing overlays.
- for action in ["about_info", "about_donate", "check_updates", "open_settings"]:
+ # Stuff that should replace the existing overlays, or that opens separate windows.
+ for action in ["about_info", "about_donate", "check_updates", "open_settings",
+ "open_externally", "open_in_folder"]:
if ShortcutUtils.is_action_pressed(event, action):
remove_all_menus()
get_viewport().set_input_as_handled()
@@ -224,8 +229,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", "save_as", "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 +257,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:
@@ -288,8 +293,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:
@@ -321,6 +326,15 @@ func _calculate_auto_scale() -> float:
return 1.0 # Default fallback scale
+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 74b8960..0000000
--- a/src/autoload/SVG.gd
+++ /dev/null
@@ -1,155 +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
-
-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:
- UR.version_changed.connect(_on_undo_redo)
- 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()
-
-
-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
- set_text(SVGParser.root_to_text(root_element, Configs.savedata.editor_formatter))
-
-func _save() -> void:
- if not _save_pending:
- return
- _save_pending = false
- 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 svg_parse_result := SVGParser.text_to_root(text, Configs.savedata.editor_formatter)
- parsing_finished.emit(svg_parse_result.error)
- if svg_parse_result.error == SVGParser.ParseError.OK:
- 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 set_text(new_text: String) -> void:
- text = new_text
- changed.emit()
-
-
-func undo() -> void:
- if UR.has_undo():
- UR.undo()
- sync_elements()
-
-func redo() -> void:
- if UR.has_redo():
- UR.redo()
- sync_elements()
-
-func _on_undo_redo() -> void:
- Configs.svg_text = text
-
-func apply_svg_text(svg_text: String) -> void:
- set_text(svg_text)
- queue_save()
- sync_elements()
-
-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 3cda313..0000000
--- 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 72%
rename from src/autoload/Indications.gd
rename to src/autoload/State.gd
index 452ad33..3d22c94 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,11 +19,181 @@ 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.new()
+
+# 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:
+ 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_editor_text(root_element)
+ 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)
+ 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_export_text(root_element)
+
+
signal hover_changed
signal selection_changed
signal proposed_drop_changed
-signal requested_scroll_to_element_editor(xid: PackedInt32Array)
+signal requested_scroll_to_element_editor(xid: PackedInt32Array, inner_idx: int)
# The viewport listens for this signal to put you in handle-placing mode.
signal handle_added
@@ -64,14 +236,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 +360,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 +449,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 +568,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 +589,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 +609,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,27 +625,27 @@ 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:
+func view_in_list(xid: PackedInt32Array, inner_index := -1) -> void:
if xid.is_empty():
return
- requested_scroll_to_element_editor.emit(xid)
+ requested_scroll_to_element_editor.emit(xid, inner_index)
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,21 +654,18 @@ 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 {
- VIEWPORT,
- LIST,
-}
+enum Context {VIEWPORT, LIST}
func get_selection_context(popup_method: Callable, context: Context) -> ContextPopup:
var btn_arr: Array[Button] = []
@@ -517,15 +685,14 @@ 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
if not can_move_down and base_xid[-1] < parent_child_count - filtered_count:
can_move_down = true
if context == Context.VIEWPORT:
- btn_arr.append(ContextPopup.create_button(
- Translator.translate("View In List"),
+ btn_arr.append(ContextPopup.create_button(Translator.translate("View in List"),
view_in_list.bind(selected_xids[0]), false,
load("res://assets/icons/ViewInList.svg")))
@@ -533,7 +700,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,12 +723,15 @@ 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(
- Translator.translate("View In List"),
- view_in_list.bind(semi_selected_xid), false,
+ var inner_idx := inner_selections[0]
+ for idx in inner_selections:
+ if idx < inner_idx:
+ inner_idx = idx
+ btn_arr.append(ContextPopup.create_button(Translator.translate("View in List"),
+ view_in_list.bind(semi_selected_xid, inner_idx), false,
load("res://assets/icons/ViewInList.svg")))
match element_ref.name:
"path":
@@ -594,7 +764,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 +783,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 +807,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
@@ -656,7 +826,7 @@ func popup_insert_command_after_context(popup_method: Callable) -> void:
"C", "S": warned_commands = PackedStringArray(["T"])
"Q", "T": warned_commands = PackedStringArray(["S"])
- if disable_z or (path_attrib.get_command_count() > selection_idx and\
+ if disable_z or (path_attrib.get_command_count() > selection_idx + 1 and\
path_attrib.get_command(selection_idx + 1).command_char.to_upper() == "Z"):
disabled_commands = PackedStringArray(["Z"])
@@ -664,17 +834,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 64bbb0a..05e2a4e 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 c580942..51adcb2 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 0000000..f5b25f1
--- /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 0000000..dece06a
--- /dev/null
+++ b/src/config_classes/TabData.gd.uid
@@ -0,0 +1 @@
+uid://doyq1jmdgfuk8
diff --git a/src/data_classes/Attribute.gd b/src/data_classes/Attribute.gd
index fc5b42c..f6b3cf6 100644
--- a/src/data_classes/Attribute.gd
+++ b/src/data_classes/Attribute.gd
@@ -5,10 +5,12 @@ class_name Attribute extends RefCounted
signal value_changed
var name: String
-var formatter: Formatter
var _value: String
func set_value(new_value: String) -> void:
+ # Formatting can be expensive, so do this cheap check first.
+ if new_value == _value:
+ return
var proposed_new_value := format(new_value)
if proposed_new_value != _value:
_value = proposed_new_value
@@ -22,9 +24,14 @@ func _sync() -> void:
pass
func format(text: String) -> String:
+ return _format(text, Configs.savedata.editor_formatter)
+
+func get_export_value() -> String:
+ return _format(_value, Configs.savedata.export_formatter)
+
+func _format(text: String, _formatter: Formatter) -> String:
return text
-func _init(new_name: String, new_formatter: Formatter, init_value := "") -> void:
+func _init(new_name: String, init_value := "") -> void:
name = new_name
- formatter = new_formatter
set_value(init_value)
diff --git a/src/data_classes/AttributeColor.gd b/src/data_classes/AttributeColor.gd
index 598b1e5..28fa6c9 100644
--- a/src/data_classes/AttributeColor.gd
+++ b/src/data_classes/AttributeColor.gd
@@ -8,7 +8,7 @@ func set_value(new_value: String) -> void:
name in DB.attribute_color_url_allowed, name in DB.attribute_color_none_allowed,
name in DB.attribute_color_current_color_allowed) else "")
-func format(text: String) -> String:
+func _format(text: String, formatter: Formatter) -> String:
text = text.strip_edges()
if text.is_empty() or text in ["none", "currentColor"]:
diff --git a/src/data_classes/AttributeList.gd b/src/data_classes/AttributeList.gd
index e0b9cf3..eb370f8 100644
--- a/src/data_classes/AttributeList.gd
+++ b/src/data_classes/AttributeList.gd
@@ -6,8 +6,8 @@ var _list: PackedFloat64Array
func _sync() -> void:
_list = text_to_list(get_value())
-func format(text: String) -> String:
- return list_to_text(text_to_list(text))
+func _format(text: String, formatter: Formatter) -> String:
+ return list_to_text(text_to_list(text), formatter)
func set_list(new_list: PackedFloat64Array) -> void:
@@ -81,7 +81,8 @@ static func text_to_list(string: String) -> PackedFloat64Array:
return nums_parsed
-func list_to_text(list: PackedFloat64Array) -> String:
+func list_to_text(list: PackedFloat64Array,
+formatter := Configs.savedata.editor_formatter) -> String:
var params := PackedStringArray()
for element in list:
# It's fine to use this parser, AttributeList is just a list of numbers.
diff --git a/src/data_classes/AttributeNumeric.gd b/src/data_classes/AttributeNumeric.gd
index 3ac8f75..ebe7bab 100644
--- a/src/data_classes/AttributeNumeric.gd
+++ b/src/data_classes/AttributeNumeric.gd
@@ -20,14 +20,14 @@ func is_percentage() -> bool:
return _percentage
-func format(text: String) -> String:
+func _format(text: String, formatter: Formatter) -> String:
var num := text_to_num(text)
if text_check_percentage(text):
- return num_to_text(num * 100.0) + "%"
+ return num_to_text(num * 100.0, formatter) + "%"
else:
- return num_to_text(num)
+ return num_to_text(num, formatter)
-func num_to_text(number: float) -> String:
+func num_to_text(number: float, formatter := Configs.savedata.editor_formatter) -> String:
return NumberParser.num_to_text(number, formatter)
static func text_to_num(text: String) -> float:
diff --git a/src/data_classes/AttributePathdata.gd b/src/data_classes/AttributePathdata.gd
index 2f5d523..5533ec1 100644
--- a/src/data_classes/AttributePathdata.gd
+++ b/src/data_classes/AttributePathdata.gd
@@ -7,8 +7,8 @@ func _sync() -> void:
_commands = parse_pathdata(get_value())
locate_start_points()
-func format(text: String) -> String:
- return path_commands_to_text(parse_pathdata(text))
+func _format(text: String, formatter: Formatter) -> String:
+ return path_commands_to_text(parse_pathdata(text), formatter)
func get_commands() -> Array[PathCommand]:
@@ -156,10 +156,10 @@ func insert_command(idx: int, cmd_char: String, vec := Vector2.ZERO) -> void:
sync_after_commands_change()
-func _convert_command(idx: int, cmd_char: String) -> void:
+func _convert_command(idx: int, cmd_char: String) -> bool:
var old_cmd := get_command(idx)
if old_cmd.command_char == cmd_char:
- return
+ return false
var cmd_absolute_char := cmd_char.to_upper()
var new_cmd: PathCommand = PathCommand.translation_dict[cmd_absolute_char].new()
@@ -202,16 +202,22 @@ func _convert_command(idx: int, cmd_char: String) -> void:
_commands.insert(idx, new_cmd)
if relative:
_commands[idx].toggle_relative()
+ return true
func convert_command(idx: int, cmd_char: String) -> void:
- _convert_command(idx, cmd_char)
- sync_after_commands_change()
+ var conversion_made := _convert_command(idx, cmd_char)
+ if conversion_made:
+ sync_after_commands_change()
func convert_commands_optimized(indices: PackedInt32Array,
cmd_chars: PackedStringArray) -> void:
+ var conversions_made := false
for i in indices.size():
- _convert_command(indices[i], cmd_chars[i])
- sync_after_commands_change()
+ var conversion_made := _convert_command(indices[i], cmd_chars[i])
+ if conversion_made:
+ conversions_made = true
+ if conversions_made:
+ sync_after_commands_change()
func delete_commands(indices: Array[int]) -> void:
@@ -399,7 +405,8 @@ static func path_commands_from_parsed_data(data: Array[Array]) -> Array[PathComm
return cmds
-func path_commands_to_text(commands_arr: Array[PathCommand]) -> String:
+func path_commands_to_text(commands_arr: Array[PathCommand],
+formatter := Configs.savedata.editor_formatter) -> String:
var output := ""
var num_parser := NumstringParser.new()
num_parser.compress_numbers = formatter.pathdata_compress_numbers
diff --git a/src/data_classes/AttributeTransformList.gd b/src/data_classes/AttributeTransformList.gd
index c7eff9c..629e124 100644
--- a/src/data_classes/AttributeTransformList.gd
+++ b/src/data_classes/AttributeTransformList.gd
@@ -11,8 +11,8 @@ func _sync() -> void:
func sync_after_transforms_change() -> void:
set_value(transform_list_to_text(_transform_list))
-func format(text: String) -> String:
- return transform_list_to_text(text_to_transform_list(text))
+func _format(text: String, formatter: Formatter) -> String:
+ return transform_list_to_text(text_to_transform_list(text), formatter)
func set_transform_list(new_transform_list: Array[Transform]) -> void:
_transform_list = new_transform_list
@@ -61,7 +61,8 @@ func insert_transform(idx: int, type: String) -> void:
sync_after_transforms_change()
-func transform_list_to_text(transform_list: Array[Transform]) -> String:
+func transform_list_to_text(transform_list: Array[Transform],
+formatter := Configs.savedata.editor_formatter) -> String:
var output := ""
var num_parser := NumstringParser.new()
num_parser.compress_numbers = formatter.transform_list_compress_numbers
diff --git a/src/data_classes/DB.gd b/src/data_classes/DB.gd
index 440f52e..aff2028 100644
--- a/src/data_classes/DB.gd
+++ b/src/data_classes/DB.gd
@@ -199,12 +199,9 @@ attribute_name: String) -> PercentageHandling:
_: return PercentageHandling.FRACTION
-static func element_with_setup(name: String, user_setup_value = null) -> Element:
+static func element_with_setup(name: String, user_setup_values: Array) -> Element:
var new_element := element(name)
- if user_setup_value != null:
- new_element.user_setup(user_setup_value)
- else:
- new_element.user_setup()
+ new_element.user_setup.callv(user_setup_values)
return new_element
static func element(name: String) -> Element:
@@ -223,16 +220,16 @@ static func element(name: String) -> Element:
"stop": return ElementStop.new()
_: return ElementUnrecognized.new(name)
-static func attribute(name: String, formatter: Formatter, value: String) -> Attribute:
+static func attribute(name: String, value: String) -> Attribute:
match DB.get_attribute_type(name):
- DB.AttributeType.NUMERIC: return AttributeNumeric.new(name, formatter, value)
- DB.AttributeType.COLOR: return AttributeColor.new(name, formatter, value)
- DB.AttributeType.LIST: return AttributeList.new(name, formatter, value)
- DB.AttributeType.PATHDATA: return AttributePathdata.new(name, formatter, value)
- DB.AttributeType.ENUM: return AttributeEnum.new(name, formatter, value)
- DB.AttributeType.TRANSFORM_LIST: return AttributeTransformList.new(name, formatter, value)
- DB.AttributeType.ID: return AttributeID.new(name, formatter, value)
- _: return Attribute.new(name, formatter, value)
+ DB.AttributeType.NUMERIC: return AttributeNumeric.new(name, value)
+ DB.AttributeType.COLOR: return AttributeColor.new(name, value)
+ DB.AttributeType.LIST: return AttributeList.new(name, value)
+ DB.AttributeType.PATHDATA: return AttributePathdata.new(name, value)
+ DB.AttributeType.ENUM: return AttributeEnum.new(name, value)
+ DB.AttributeType.TRANSFORM_LIST: return AttributeTransformList.new(name, value)
+ DB.AttributeType.ID: return AttributeID.new(name, value)
+ _: return Attribute.new(name, value)
static func is_element_gradient(checked_element: Element) -> bool:
diff --git a/src/data_classes/Element.gd b/src/data_classes/Element.gd
index bfa40a4..d9f2971 100644
--- a/src/data_classes/Element.gd
+++ b/src/data_classes/Element.gd
@@ -149,14 +149,23 @@ func get_attribute_value(attribute_name: String, real := false) -> String:
return ""
return get_default(attribute_name)
+
+func get_attribute_true_color(attribute_name: String) -> String:
+ if DB.get_attribute_type(attribute_name) != DB.AttributeType.COLOR:
+ push_error("Attribute not the correct type.")
+ var attrib_value := get_attribute_value(attribute_name)
+ if attrib_value == "currentColor":
+ return get_default("color")
+ return attrib_value
+
func get_attribute_num(attribute_name: String) -> float:
if DB.get_attribute_type(attribute_name) != DB.AttributeType.NUMERIC:
push_error("Attribute not the correct type.")
- var attrib: AttributeNumeric = _attributes[attribute_name] if\
- has_attribute(attribute_name) else new_default_attribute(attribute_name)
+ var num: float = _attributes[attribute_name].get_num() if\
+ has_attribute(attribute_name) else\
+ AttributeNumeric.text_to_num(get_default(attribute_name))
# Possibly adjust for percentage.
- var num := attrib.get_num()
- if attrib.is_percentage():
+ if is_attribute_percentage(attribute_name):
var percentage_handling := get_percentage_handling(attribute_name)
if percentage_handling == DB.PercentageHandling.FRACTION:
return num
@@ -173,50 +182,17 @@ func get_attribute_num(attribute_name: String) -> float:
DB.PercentageHandling.NORMALIZED: return svg.normalized_diagonal * num
return num
-func get_attribute_true_color(attribute_name: String) -> String:
- if DB.get_attribute_type(attribute_name) != DB.AttributeType.COLOR:
- push_error("Attribute not the correct type.")
- var attrib: AttributeColor = _attributes[attribute_name] if\
- has_attribute(attribute_name) else new_default_attribute(attribute_name)
- var attrib_value := attrib.get_value()
- if attrib_value == "currentColor":
- return get_default("color")
- return attrib_value
-
func is_attribute_percentage(attribute_name: String) -> bool:
if DB.get_attribute_type(attribute_name) != DB.AttributeType.NUMERIC:
push_error("Attribute not the correct type.")
- var attrib: AttributeNumeric = _attributes[attribute_name] if\
- has_attribute(attribute_name) else new_default_attribute(attribute_name)
- return attrib.is_percentage()
-
-func get_attribute_rect(attribute_name: String) -> float:
- if DB.get_attribute_type(attribute_name) != DB.AttributeType.LIST:
- push_error("Attribute not the correct type.")
- var attrib: AttributeList = _attributes[attribute_name] if\
- has_attribute(attribute_name) else new_default_attribute(attribute_name)
- return attrib.get_rect()
+ return _attributes[attribute_name].is_percentage() if has_attribute(attribute_name) else\
+ AttributeNumeric.text_check_percentage(get_default(attribute_name))
func get_attribute_list(attribute_name: String) -> PackedFloat64Array:
if DB.get_attribute_type(attribute_name) != DB.AttributeType.LIST:
push_error("Attribute not the correct type.")
- var attrib: AttributeList = _attributes[attribute_name] if\
- has_attribute(attribute_name) else new_default_attribute(attribute_name)
- return attrib.get_list()
-
-func get_attribute_commands(attribute_name: String) -> Array[PathCommand]:
- if DB.get_attribute_type(attribute_name) != DB.AttributeType.PATHDATA:
- push_error("Attribute not the correct type.")
- var attrib: AttributePathdata = _attributes[attribute_name] if\
- has_attribute(attribute_name) else new_default_attribute(attribute_name)
- return attrib.get_commands()
-
-func get_attribute_transforms(attribute_name: String) -> Array[Transform]:
- if DB.get_attribute_type(attribute_name) != DB.AttributeType.TRANSFORM_LIST:
- push_error("Attribute not the correct type.")
- var attrib: AttributeTransformList = _attributes[attribute_name] if\
- has_attribute(attribute_name) else new_default_attribute(attribute_name)
- return attrib.get_transform_list()
+ return _attributes[attribute_name].get_list() if has_attribute(attribute_name) else\
+ AttributeList.text_to_list(get_default(attribute_name))
func get_attribute_final_precise_transform(attribute_name: String) -> PackedFloat64Array:
if DB.get_attribute_type(attribute_name) != DB.AttributeType.TRANSFORM_LIST:
@@ -226,19 +202,19 @@ func get_attribute_final_precise_transform(attribute_name: String) -> PackedFloa
return attrib.get_final_precise_transform()
-func set_attribute(attribute_name: String, value: Variant) -> void:
- var attrib: Attribute
- if has_attribute(attribute_name):
- attrib = _attributes[attribute_name]
- else:
- attrib = new_attribute(attribute_name)
-
+func set_attribute(attrib_name: String, value: Variant) -> void:
var value_type := typeof(value)
+ var has_attrib := has_attribute(attrib_name)
+ if not has_attrib and value_type == TYPE_STRING and value.is_empty():
+ return
+
+ var attrib := _attributes[attrib_name] if has_attrib else new_attribute(attrib_name)
+
if value_type == TYPE_STRING:
attrib.set_value(value)
else:
- match DB.get_attribute_type(attribute_name):
+ match DB.get_attribute_type(attrib_name):
DB.AttributeType.NUMERIC:
if value_type in [TYPE_FLOAT, TYPE_INT]: attrib.set_num(value)
else: push_error("Invalid value set to attribute.")
@@ -271,14 +247,9 @@ func duplicate(include_children := true) -> Element:
var new_element: Element
if type == ElementUnrecognized:
new_element = ElementUnrecognized.new(self.name)
- elif type == ElementRoot:
- new_element = ElementRoot.new(self.formatter)
else:
new_element = type.new()
- if type == ElementRoot:
- new_element.formatter = self.formatter
-
for attribute in _attributes:
new_element.set_attribute(attribute, get_attribute_value(attribute))
@@ -296,11 +267,11 @@ func apply_to(element: Element, dropped_attributes: PackedStringArray) -> void:
# Converts a percentage numeric attribute to absolute.
# TODO this is no longer used, but might become useful again in the future.
-func make_attribute_absolute(attribute_name: String) -> void:
- if is_attribute_percentage(attribute_name):
- var new_attrib := new_attribute(attribute_name)
- new_attrib.set_num(get_attribute_num(attribute_name))
- _attributes[attribute_name] = new_attrib
+func make_attribute_absolute(attrib_name: String) -> void:
+ if is_attribute_percentage(attrib_name):
+ var new_attrib := new_attribute(attrib_name)
+ new_attrib.set_num(get_attribute_num(attrib_name))
+ _attributes[attrib_name] = new_attrib
# To be overridden in extending classes.
@@ -354,9 +325,4 @@ func new_default_attribute(name: String) -> Attribute:
return _create_attribute(name, get_default(name))
func _create_attribute(name: String, value := "") -> Attribute:
- if root != null:
- return DB.attribute(name, root.formatter, value)
- elif root == self:
- return DB.attribute(name, self.formatter, value)
- else:
- return DB.attribute(name, Formatter.new(), value)
+ return DB.attribute(name, value)
diff --git a/src/data_classes/ElementLine.gd b/src/data_classes/ElementLine.gd
index a574f83..b60dbe6 100644
--- a/src/data_classes/ElementLine.gd
+++ b/src/data_classes/ElementLine.gd
@@ -24,10 +24,10 @@ func get_replacement(new_element: String) -> Element:
match new_element:
"polyline":
dropped_attributes = PackedStringArray(["x1", "y1", "x2", "y2", "points"])
- var points := PackedVector2Array([
- Vector2(get_attribute_num("x1"), get_attribute_num("y1")),
- Vector2(get_attribute_num("x2"), get_attribute_num("y2"))])
- element.get_attribute("points").set_points(points)
+ var points := PackedFloat64Array([get_attribute_num("x1"),
+ get_attribute_num("y1"), get_attribute_num("x2"), get_attribute_num("y2")])
+ element.get_attribute("points").set_list(points)
+ element.set_attribute("fill", "none")
"path":
element = ElementPath.new()
dropped_attributes = PackedStringArray(["x1", "y1", "x2", "y2", "d"])
diff --git a/src/data_classes/ElementLinearGradient.gd b/src/data_classes/ElementLinearGradient.gd
index 1b20051..f7b7a91 100644
--- a/src/data_classes/ElementLinearGradient.gd
+++ b/src/data_classes/ElementLinearGradient.gd
@@ -21,40 +21,12 @@ func get_percentage_handling(attribute_name: String) -> DB.PercentageHandling:
func get_config_warnings() -> PackedStringArray:
var warnings := super()
- if not has_attribute("id"):
- warnings.append(Translator.translate("No \"id\" attribute defined."))
-
- var first_stop_color := ""
- var first_stop_opacity := -1.0
- var is_solid_color := true
- for child in get_children():
- if not child is ElementStop:
- continue
-
- var stop_opacity := maxf(child.get_attribute_num("stop-opacity"), 0.0)
- var stop_color: String = child.get_attribute_value("stop-color")
- if stop_color.strip_edges() == "currentColor":
- stop_color = child.get_attribute_value("color")
-
- if first_stop_color.is_empty():
- first_stop_opacity = stop_opacity
- first_stop_color = stop_color
- elif is_solid_color and not (ColorParser.are_colors_same(first_stop_color,
- stop_color) and first_stop_opacity == stop_opacity) and\
- not (first_stop_opacity == 0 and stop_opacity <= 0):
- is_solid_color = false
- break
-
- if first_stop_color.is_empty():
- warnings.append(Translator.translate("No elements under this gradient."))
- elif is_solid_color:
- warnings.append(Translator.translate("This gradient is a solid color."))
-
+ warnings += GradientUtils.get_gradient_warnings(self)
return warnings
func generate_texture() -> GradientTexture2D:
var texture := GradientTexture2D.new()
- texture.gradient = Utils.generate_gradient(self)
+ texture.gradient = GradientUtils.generate_gradient(self)
texture.fill_from = Vector2(get_attribute_num("x1"), get_attribute_num("y1"))
texture.fill_to = Vector2(get_attribute_num("x2"), get_attribute_num("y2"))
diff --git a/src/data_classes/ElementPolygon.gd b/src/data_classes/ElementPolygon.gd
index ce019e5..92193a8 100644
--- a/src/data_classes/ElementPolygon.gd
+++ b/src/data_classes/ElementPolygon.gd
@@ -85,18 +85,22 @@ func get_replacement(new_element: String) -> Element:
func simplify() -> void:
- var list_points := ListParser.list_to_points(get_attribute_list("points"))
- var new_list_points := PackedVector2Array()
+ var list := get_attribute_list("points")
+ var new_list_points := PackedFloat64Array()
- for idx in list_points.size() - 1:
- var prev_point := list_points[idx - 1]
- var current_point := list_points[idx]
- if not is_equal_approx(prev_point.angle_to_point(current_point),
- prev_point.angle_to_point(list_points[idx + 1])):
- new_list_points.append(current_point)
+ @warning_ignore("integer_division")
+ for idx in list.size() / 2 - 1:
+ var prev_point := Vector2(list[idx * 2 - 2], list[idx * 2 - 2])
+ if not is_equal_approx(prev_point.angle_to_point(
+ Vector2(list[idx * 2], list[idx * 2 + 1])), prev_point.angle_to_point(
+ Vector2(list[idx * 2 + 2], list[idx * 2 + 3]))):
+ new_list_points.append(list[idx * 2])
+ new_list_points.append(list[idx * 2 + 1])
- if not is_equal_approx(list_points[-2].angle_to_point(list_points[-1]),
- list_points[-2].angle_to_point(list_points[0])):
- new_list_points.append(list_points[-1])
+ var second_to_last_point := Vector2(list[-4], list[-3])
+ if not is_equal_approx(second_to_last_point.angle_to_point(Vector2(list[-2], list[-1])),
+ second_to_last_point.angle_to_point(Vector2(list[0], list[1]))):
+ new_list_points.append(list[-2])
+ new_list_points.append(list[-1])
- get_attribute("points").set_points(new_list_points)
+ get_attribute("points").set_list(new_list_points)
diff --git a/src/data_classes/ElementPolyline.gd b/src/data_classes/ElementPolyline.gd
index 7e6945c..a75f0f6 100644
--- a/src/data_classes/ElementPolyline.gd
+++ b/src/data_classes/ElementPolyline.gd
@@ -54,7 +54,7 @@ func get_replacement(new_element: String) -> Element:
match new_element:
"line":
dropped_attributes = PackedStringArray(["points", "rx", "ry", "cx", "cy",
- "width", "height"])
+ "width", "height", "fill", "fill-opacity", "stroke-linejoin"])
simplify()
var list := get_attribute_list("points")
element.set_attribute("x1", list[0])
@@ -75,14 +75,19 @@ func get_replacement(new_element: String) -> Element:
func simplify() -> void:
- var list_points := ListParser.list_to_points(get_attribute_list("points"))
- var new_list_points := PackedVector2Array()
- new_list_points.append(list_points[0])
- for idx in range(1, list_points.size() - 1):
- var prev_point := list_points[idx - 1]
- var current_point := list_points[idx]
- if not is_equal_approx(prev_point.angle_to_point(current_point),
- prev_point.angle_to_point(list_points[idx + 1])):
- new_list_points.append(current_point)
- new_list_points.append(list_points[-1])
- get_attribute("points").set_points(new_list_points)
+ var list := get_attribute_list("points")
+ var new_list_points := PackedFloat64Array()
+
+ new_list_points.append(list[0])
+ new_list_points.append(list[1])
+ @warning_ignore("integer_division")
+ for idx in range(1, list.size() / 2 - 1):
+ var prev_point := Vector2(list[idx * 2 - 2], list[idx * 2 - 1])
+ if not is_equal_approx(prev_point.angle_to_point(
+ Vector2(list[idx * 2], list[idx * 2 + 1])), prev_point.angle_to_point(
+ Vector2(list[idx * 2 + 2], list[idx * 2 + 3]))):
+ new_list_points.append(list[idx * 2])
+ new_list_points.append(list[idx * 2 + 1])
+ new_list_points.append(list[-2])
+ new_list_points.append(list[-1])
+ get_attribute("points").set_list(new_list_points)
diff --git a/src/data_classes/ElementRadialGradient.gd b/src/data_classes/ElementRadialGradient.gd
index d3a2220..0ca4012 100644
--- a/src/data_classes/ElementRadialGradient.gd
+++ b/src/data_classes/ElementRadialGradient.gd
@@ -20,40 +20,12 @@ func get_percentage_handling(attribute_name: String) -> DB.PercentageHandling:
func get_config_warnings() -> PackedStringArray:
var warnings := super()
- if not has_attribute("id"):
- warnings.append(Translator.translate("No \"id\" attribute defined."))
-
- var first_stop_color := ""
- var first_stop_opacity := -1.0
- var is_solid_color := true
- for child in get_children():
- if not child is ElementStop:
- continue
-
- var stop_opacity := maxf(child.get_attribute_num("stop-opacity"), 0.0)
- var stop_color: String = child.get_attribute_value("stop-color")
- if stop_color.strip_edges() == "currentColor":
- stop_color = child.get_attribute_value("color")
-
- if first_stop_color.is_empty():
- first_stop_opacity = stop_opacity
- first_stop_color = stop_color
- elif is_solid_color and not (ColorParser.are_colors_same(first_stop_color,
- stop_color) and first_stop_opacity == stop_opacity) and\
- not (first_stop_opacity == 0 and stop_opacity <= 0):
- is_solid_color = false
- break
-
- if first_stop_color.is_empty():
- warnings.append(Translator.translate("No elements under this gradient."))
- elif is_solid_color:
- warnings.append(Translator.translate("This gradient is a solid color."))
-
+ warnings += GradientUtils.get_gradient_warnings(self)
return warnings
func generate_texture() -> GradientTexture2D:
var texture := GradientTexture2D.new()
- texture.gradient = Utils.generate_gradient(self)
+ texture.gradient = GradientUtils.generate_gradient(self)
texture.fill = GradientTexture2D.FILL_RADIAL
texture.fill_from = Vector2(get_attribute_num("cx"), get_attribute_num("cy"))
texture.fill_to = Vector2(get_attribute_num("cx") + get_attribute_num("r"),
diff --git a/src/data_classes/ElementRect.gd b/src/data_classes/ElementRect.gd
index 2014191..3caa359 100644
--- a/src/data_classes/ElementRect.gd
+++ b/src/data_classes/ElementRect.gd
@@ -55,11 +55,9 @@ func get_replacement(new_element: String) -> Element:
"polygon":
dropped_attributes = PackedStringArray(["x", "y", "width", "height", "rx", "ry",
"points"])
- var points := PackedVector2Array([Vector2(x_num, y_num),
- Vector2(x_num + width_num, y_num),
- Vector2(x_num + width_num, y_num + height_num),
- Vector2(x_num, y_num + height_num)])
- element.get_attribute("points").set_points(points)
+ var points := PackedFloat64Array([x_num, y_num, x_num + width_num, y_num,
+ x_num + width_num, y_num + height_num, x_num, y_num + height_num])
+ element.get_attribute("points").set_list(points)
"path":
dropped_attributes = PackedStringArray(["x", "y", "width", "height", "rx", "ry",
"d"])
diff --git a/src/data_classes/ElementRoot.gd b/src/data_classes/ElementRoot.gd
index 54727f7..61e9504 100644
--- a/src/data_classes/ElementRoot.gd
+++ b/src/data_classes/ElementRoot.gd
@@ -7,20 +7,17 @@ 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 xnode_layout_changed # Emitted together with any of the above four.
@warning_ignore("unused_signal")
signal basic_xnode_text_changed(xid: PackedInt32Array)
@warning_ignore("unused_signal")
signal basic_xnode_rendered_text_changed(xid: PackedInt32Array)
-var formatter: Formatter
-
-func _init(new_formatter: Formatter) -> void:
+func _init() -> void:
super()
xid = PackedInt32Array()
root = self
- formatter = new_formatter
func get_xnode(loc: PackedInt32Array) -> XNode:
var current_element: XNode = self
@@ -237,24 +234,21 @@ func optimize(not_applied := false) -> bool:
return true
replace_xnode(element.xid, element.get_replacement("path"))
"path":
- var pathdata: AttributePathdata = element.get_attribute("d")
- # Simplify A rotation to 0 degrees for circular arcs.
- for cmd_idx in pathdata.get_command_count():
- var command := pathdata.get_command(cmd_idx)
- var cmd_char := command.command_char
- if cmd_char in "Aa" and command.rx == command.ry and command.rot != 0:
- if not_applied:
- return true
- pathdata.set_command_property(cmd_idx, "rot", 0)
-
# Replace L with H or V when possible.
var conversion_indices := PackedInt32Array()
var conversion_cmd_chars := PackedStringArray()
+ var pathdata: AttributePathdata = element.get_attribute("d")
for cmd_idx in pathdata.get_command_count():
var command := pathdata.get_command(cmd_idx)
var cmd_char := command.command_char
+ # Simplify A rotation to 0 degrees for circular arcs.
+ if cmd_char in "Aa" and command.rx == command.ry and command.rot != 0.0:
+ if not_applied:
+ return true
+ pathdata.set_command_property(cmd_idx, "rot", 0.0)
+
if cmd_char == "l":
if command.x == 0:
if not_applied:
diff --git a/src/data_classes/GradientUtils.gd b/src/data_classes/GradientUtils.gd
new file mode 100644
index 0000000..f040527
--- /dev/null
+++ b/src/data_classes/GradientUtils.gd
@@ -0,0 +1,66 @@
+class_name GradientUtils extends RefCounted
+
+static func generate_gradient(element: Element) -> Gradient:
+ if not (element is ElementLinearGradient or element is ElementRadialGradient):
+ return null
+
+ var gradient := Gradient.new()
+ gradient.remove_point(0)
+
+ var current_offset := 0.0
+ var is_gradient_empty := true
+
+ for child in element.get_children():
+ if not child is ElementStop:
+ continue
+
+ current_offset = clamp(child.get_attribute_num("offset"), current_offset, 1.0)
+ gradient.add_point(current_offset,
+ Color(ColorParser.text_to_color(child.get_attribute_value("stop-color")),
+ child.get_attribute_num("stop-opacity")))
+ if is_gradient_empty:
+ is_gradient_empty = false
+ gradient.remove_point(0)
+
+ if is_gradient_empty:
+ gradient.set_color(0, Color.TRANSPARENT)
+
+ return gradient
+
+
+static func get_gradient_warnings(element: Element) -> PackedStringArray:
+ if not (element is ElementLinearGradient or element is ElementRadialGradient):
+ return PackedStringArray()
+
+ var warnings := PackedStringArray()
+
+ if not element.has_attribute("id"):
+ warnings.append(Translator.translate("No \"id\" attribute defined."))
+
+ var first_stop_color := ""
+ var first_stop_opacity := -1.0
+ var is_solid_color := true
+ for child in element.get_children():
+ if not child is ElementStop:
+ continue
+
+ var stop_opacity := maxf(child.get_attribute_num("stop-opacity"), 0.0)
+ var stop_color: String = child.get_attribute_value("stop-color")
+ if stop_color.strip_edges() == "currentColor":
+ stop_color = child.get_attribute_value("color")
+
+ if first_stop_color.is_empty():
+ first_stop_opacity = stop_opacity
+ first_stop_color = stop_color
+ elif is_solid_color and not (ColorParser.are_colors_same(first_stop_color,
+ stop_color) and first_stop_opacity == stop_opacity) and\
+ not (first_stop_opacity == 0 and stop_opacity <= 0):
+ is_solid_color = false
+ break
+
+ if first_stop_color.is_empty():
+ warnings.append(Translator.translate("No elements under this gradient."))
+ elif is_solid_color:
+ warnings.append(Translator.translate("This gradient is a solid color."))
+
+ return warnings
diff --git a/src/data_classes/GradientUtils.gd.uid b/src/data_classes/GradientUtils.gd.uid
new file mode 100644
index 0000000..a0160a4
--- /dev/null
+++ b/src/data_classes/GradientUtils.gd.uid
@@ -0,0 +1 @@
+uid://bcprbwp8itu4
diff --git a/src/data_classes/SVGParser.gd b/src/data_classes/SVGParser.gd
index b58c80a..5494561 100644
--- a/src/data_classes/SVGParser.gd
+++ b/src/data_classes/SVGParser.gd
@@ -3,28 +3,31 @@ class_name SVGParser extends RefCounted
# For rendering only a section of the SVG.
static func root_cutout_to_text(root_element: ElementRoot, custom_width: float,
custom_height: float, custom_viewbox: Rect2) -> String:
- var blank_formatter := Formatter.new()
- blank_formatter.xml_shorthand_tags = Formatter.ShorthandTags.ALL_EXCEPT_CONTAINERS
var new_root_element: ElementRoot = root_element.duplicate(false)
-
new_root_element.set_attribute("viewBox", ListParser.rect_to_list(custom_viewbox))
new_root_element.set_attribute("width", custom_width)
new_root_element.set_attribute("height", custom_height)
- var text := _xnode_to_text(new_root_element, blank_formatter)
+ var text := _xnode_to_editor_text(new_root_element)
text = text.strip_edges(false, true).left(-6) # Remove the at the end.)
for child_idx in root_element.get_child_count():
- text += _xnode_to_text(root_element.get_xnode(PackedInt32Array([child_idx])),
- blank_formatter, true)
+ text += _xnode_to_editor_text(
+ root_element.get_xnode(PackedInt32Array([child_idx])), true)
return text + ""
-static func root_to_text(root_element: ElementRoot, formatter: Formatter) -> String:
- var text := _xnode_to_text(root_element, formatter).trim_suffix('\n')
- if formatter.xml_add_trailing_newline:
+static func root_to_editor_text(root_element: ElementRoot) -> String:
+ var text := _xnode_to_editor_text(root_element).trim_suffix('\n')
+ if Configs.savedata.editor_formatter.xml_add_trailing_newline:
+ text += "\n"
+ return text
+
+static func root_to_export_text(root_element: ElementRoot) -> String:
+ var text := _xnode_to_export_text(root_element).trim_suffix('\n')
+ if Configs.savedata.export_formatter.xml_add_trailing_newline:
text += "\n"
return text
-static func _xnode_to_text(xnode: XNode, formatter: Formatter,
-make_attributes_absolute := false) -> String:
+static func _xnode_to_editor_text(xnode: XNode, make_attributes_absolute := false) -> String:
+ var formatter := Configs.savedata.editor_formatter
var text := ""
if formatter.xml_pretty_formatting:
if formatter.xml_indentation_use_spaces:
@@ -46,7 +49,7 @@ make_attributes_absolute := false) -> String:
if make_attributes_absolute:
# Add known default value attributes if they are percentage-based.
for attrib_name in DB.get_recognized_attributes(element.name):
- if DB.get_attribute_type(attrib_name) != DB.AttributeType.NUMERIC:
+ if element.get_percentage_handling(attrib_name) == DB.PercentageHandling.FRACTION:
continue
var already_exists := false
@@ -88,7 +91,55 @@ make_attributes_absolute := false) -> String:
if formatter.xml_pretty_formatting:
text += '\n'
for child in element.get_children():
- text += _xnode_to_text(child, formatter, make_attributes_absolute)
+ text += _xnode_to_editor_text(child, make_attributes_absolute)
+ if formatter.xml_pretty_formatting:
+ text += '\t'.repeat(element.xid.size())
+ text += '%s>' % element.name
+ if formatter.xml_pretty_formatting:
+ text += '\n'
+ return text
+
+static func _xnode_to_export_text(xnode: XNode) -> String:
+ var formatter := Configs.savedata.export_formatter
+ var text := ""
+ if formatter.xml_pretty_formatting:
+ if formatter.xml_indentation_use_spaces:
+ text = ' '.repeat(xnode.xid.size() * formatter.xml_indentation_spaces)
+ else:
+ text = '\t'.repeat(xnode.xid.size())
+
+ if not xnode.is_element():
+ match xnode.get_type():
+ BasicXNode.NodeType.COMMENT: text += "" % xnode.get_text()
+ BasicXNode.NodeType.CDATA: text += "" % xnode.get_text()
+ _: text += xnode.get_text()
+ if formatter.xml_pretty_formatting:
+ text += "\n"
+ return text
+
+ var element := xnode as Element
+ text += '<' + element.name
+ for attribute: Attribute in element.get_all_attributes():
+ var value := attribute.get_export_value()
+
+ if not '"' in value:
+ text += ' %s="%s"' % [attribute.name, value]
+ else:
+ text += " %s='%s'" % [attribute.name, value]
+
+ if not element.has_children() and (formatter.xml_shorthand_tags ==\
+ Formatter.ShorthandTags.ALWAYS or (formatter.xml_shorthand_tags ==\
+ Formatter.ShorthandTags.ALL_EXCEPT_CONTAINERS and\
+ not element.name in Formatter.container_elements)):
+ text += ' />' if formatter.xml_shorthand_tags_space_out_slash else '/>'
+ if formatter.xml_pretty_formatting:
+ text += '\n'
+ else:
+ text += '>'
+ if formatter.xml_pretty_formatting:
+ text += '\n'
+ for child in element.get_children():
+ text += _xnode_to_export_text(child)
if formatter.xml_pretty_formatting:
text += '\t'.repeat(element.xid.size())
text += '%s>' % element.name
@@ -115,11 +166,12 @@ static func get_error_string(parse_error: ParseError) -> String:
return Translator.translate("Improper nesting.")
_: return ""
-static func text_to_root(text: String, formatter: Formatter) -> ParseResult:
+# The root always uses the editor formatter.
+static func text_to_root(text: String) -> ParseResult:
if text.is_empty():
return ParseResult.new(ParseError.ERR_NOT_SVG)
- var root_element := ElementRoot.new(formatter)
+ var root_element := ElementRoot.new()
var parser := XMLParser.new()
parser.open_buffer(text.to_utf8_buffer())
var unclosed_element_stack: Array[Element] = []
@@ -178,7 +230,7 @@ static func text_to_root(text: String, formatter: Formatter) -> ParseResult:
break
XMLParser.NODE_COMMENT:
- if formatter.xml_keep_comments:
+ if Configs.savedata.editor_formatter.xml_keep_comments:
unclosed_element_stack.back().insert_child(-1,
BasicXNode.new(BasicXNode.NodeType.COMMENT, parser.get_node_name()))
XMLParser.NODE_TEXT:
@@ -190,7 +242,7 @@ static func text_to_root(text: String, formatter: Formatter) -> ParseResult:
unclosed_element_stack.back().insert_child(-1,
BasicXNode.new(BasicXNode.NodeType.CDATA, parser.get_node_name()))
_:
- if formatter.xml_keep_unrecognized:
+ if Configs.savedata.editor_formatter.xml_keep_unrecognized:
unclosed_element_stack.back().insert_child(-1,
BasicXNode.new(BasicXNode.NodeType.UNKNOWN, parser.get_node_name()))
diff --git a/src/ui_parts/PathHandle.gd b/src/ui_parts/PathHandle.gd
index 7041619..83e52f0 100644
--- a/src/ui_parts/PathHandle.gd
+++ b/src/ui_parts/PathHandle.gd
@@ -12,7 +12,7 @@ func _init(new_element: Element, command_idx: int, x_name: String, y_name: Strin
command_index = command_idx
x_param = x_name
y_param = y_name
- element.attribute_changed.connect(sync.unbind(1))
+ element.attribute_changed.connect(_on_attribute_changed)
element.ancestor_attribute_changed.connect(sync.unbind(1))
sync()
@@ -27,7 +27,6 @@ func set_pos(new_pos: PackedFloat64Array) -> void:
path_attribute.set_command_property(command_index, y_param, new_pos[1])
sync()
-
func sync() -> void:
if command_index >= element.get_attribute(pathdata_name).get_command_count():
# Handle might have been removed.
@@ -46,3 +45,8 @@ func sync() -> void:
else:
precise_pos[1] = command.start_y
super()
+
+
+func _on_attribute_changed(name: String) -> void:
+ if name in [pathdata_name, "transform"]:
+ sync()
diff --git a/src/ui_parts/PolyHandle.gd b/src/ui_parts/PolyHandle.gd
index 27562c5..b55ebce 100644
--- a/src/ui_parts/PolyHandle.gd
+++ b/src/ui_parts/PolyHandle.gd
@@ -7,7 +7,7 @@ var point_index: int
func _init(new_element: Element, point_idx: int) -> void:
element = new_element
point_index = point_idx
- element.attribute_changed.connect(sync.unbind(1))
+ element.attribute_changed.connect(_on_attribute_changed)
element.ancestor_attribute_changed.connect(sync.unbind(1))
sync()
@@ -20,10 +20,16 @@ func set_pos(new_pos: PackedFloat64Array) -> void:
func sync() -> void:
var list := element.get_attribute_list(points_name)
- if point_index >= list.size():
+ @warning_ignore("integer_division")
+ if point_index >= list.size() / 2:
# Handle might have been removed.
return
precise_pos[0] = list[point_index * 2]
precise_pos[1] = list[point_index * 2 + 1]
super()
+
+
+func _on_attribute_changed(name: String) -> void:
+ if name in [points_name, "transform"]:
+ sync()
diff --git a/src/ui_parts/about_menu.gd b/src/ui_parts/about_menu.gd
index d2edeec..c425b99 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 d1c3284..db5ee43 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 a2d3fb4..47cd64a 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,14 +15,13 @@ 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
@onready var input_debug_label: Label = %DebugContainer/InputDebugLabel
+@onready var toolbar: PanelContainer = $ViewportPanel/VBoxContainer/Toolbar
var reference_overlay := false
@@ -30,6 +29,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 +62,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 +69,11 @@ 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 toolbar_stylebox := StyleBoxFlat.new()
+ toolbar_stylebox.bg_color = ThemeUtils.overlay_panel_inner_color.lerp(Color.WHITE, 0.01)
+ toolbar_stylebox.set_content_margin_all(4)
+ toolbar.add_theme_stylebox_override("panel", toolbar_stylebox)
+
var frame := StyleBoxFlat.new()
frame.draw_center = false
frame.border_width_left = 2
@@ -91,12 +91,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 +126,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 +159,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 +186,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 839bb09..f405f03 100644
--- a/src/ui_parts/display.tscn
+++ b/src/ui_parts/display.tscn
@@ -1,30 +1,29 @@
-[gd_scene load_steps=18 format=3 uid="uid://bvrncl7e6yn5b"]
+[gd_scene load_steps=17 format=3 uid="uid://bvrncl7e6yn5b"]
-[ext_resource type="Script" uid="uid://dkdc1i64be3ph" 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" uid="uid://cgv206odqfy2k" 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" uid="uid://b6pmlbnl76wmm" path="res://src/ui_parts/viewport.gd" id="9_4xrk7"]
-[ext_resource type="Shader" uid="uid://ki2mjb6y33jl" path="res://src/shaders/zoom_shader.gdshader" id="10_x7ybk"]
+[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" 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://bghbnvoqacogo" path="res://src/ui_parts/camera.gd" id="15_hevpa"]
-
-[sub_resource type="ShaderMaterial" id="ShaderMaterial_kqplg"]
-shader = ExtResource("10_x7ybk")
-shader_parameter/uv_scale = 1.0
+[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)
corner_radius_top_left = 5
corner_radius_bottom_left = 5
+[sub_resource type="ShaderMaterial" id="ShaderMaterial_kqplg"]
+shader = ExtResource("10_x7ybk")
+shader_parameter/uv_scale = 1.0
+
[node name="Display" type="VBoxContainer"]
anchors_preset = 15
anchor_right = 1.0
@@ -34,34 +33,103 @@ grow_vertical = 2
theme_override_constants/separation = 0
script = ExtResource("1_oib5g")
+[node name="TabBar" type="Control" parent="."]
+clip_contents = true
+custom_minimum_size = Vector2(0, 24)
+layout_mode = 2
+size_flags_horizontal = 3
+script = ExtResource("9_rll1m")
+
[node name="ViewportPanel" type="PanelContainer" parent="."]
layout_mode = 2
size_flags_vertical = 3
-[node name="ViewportContainer" type="SubViewportContainer" parent="ViewportPanel"]
-custom_minimum_size = Vector2(300, 0)
+[node name="VBoxContainer" type="VBoxContainer" parent="ViewportPanel"]
+layout_mode = 2
+theme_override_constants/separation = 0
+
+[node name="Toolbar" type="PanelContainer" parent="ViewportPanel/VBoxContainer"]
+layout_mode = 2
+
+[node name="ViewportOptions" type="HBoxContainer" parent="ViewportPanel/VBoxContainer/Toolbar"]
+layout_mode = 2
+alignment = 2
+
+[node name="LeftMenu" type="HBoxContainer" parent="ViewportPanel/VBoxContainer/Toolbar/ViewportOptions"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 2
+theme_override_constants/separation = 5
+
+[node name="Visuals" type="Button" parent="ViewportPanel/VBoxContainer/Toolbar/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_alignment = 1
+
+[node name="Reference" type="Button" parent="ViewportPanel/VBoxContainer/Toolbar/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_alignment = 1
+
+[node name="Snapping" type="HBoxContainer" parent="ViewportPanel/VBoxContainer/Toolbar/ViewportOptions/LeftMenu"]
+layout_mode = 2
+theme_override_constants/separation = 0
+
+[node name="SnapButton" type="Button" parent="ViewportPanel/VBoxContainer/Toolbar/ViewportOptions/LeftMenu/Snapping"]
+layout_mode = 2
+focus_mode = 0
+mouse_default_cursor_shape = 2
+theme_type_variation = &"RightConnectedButton"
+toggle_mode = true
+icon = ExtResource("5_1k2cq")
+script = ExtResource("6_3v3ve")
+hover_pressed_stylebox = SubResource("StyleBoxFlat_eujxa")
+
+[node name="SnapNumberEdit" parent="ViewportPanel/VBoxContainer/Toolbar/ViewportOptions/LeftMenu/Snapping" instance=ExtResource("7_wrrfr")]
+custom_minimum_size = Vector2(48, 22)
+layout_mode = 2
+theme_type_variation = &"LeftConnectedLineEdit"
+max_length = 20
+editable = false
+min_value = 0.001
+allow_lower = false
+
+[node name="ZoomMenu" parent="ViewportPanel/VBoxContainer/Toolbar/ViewportOptions" instance=ExtResource("8_xtdmn")]
+unique_name_in_owner = true
+layout_mode = 2
+
+[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(716, 1248)
+size = Vector2i(450, 2)
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
@@ -69,7 +137,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
@@ -80,14 +148,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
@@ -99,13 +166,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
@@ -125,89 +193,10 @@ theme_override_constants/outline_size = 4
theme_override_font_sizes/font_size = 14
horizontal_alignment = 2
-[node name="PanelContainer" type="PanelContainer" parent="."]
-layout_mode = 2
-size_flags_vertical = 0
-
-[node name="HBoxContainer" type="HBoxContainer" parent="PanelContainer"]
-layout_mode = 2
-alignment = 2
-
-[node name="LeftMenu" type="HBoxContainer" parent="PanelContainer/HBoxContainer"]
-unique_name_in_owner = true
-layout_mode = 2
-size_flags_horizontal = 2
-theme_override_constants/separation = 5
-
-[node name="MoreOptions" type="Button" parent="PanelContainer/HBoxContainer/LeftMenu"]
-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
-
-[node name="Settings" type="Button" parent="PanelContainer/HBoxContainer/LeftMenu"]
-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
-
-[node name="Reference" type="Button" parent="PanelContainer/HBoxContainer/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_alignment = 1
-
-[node name="Visuals" type="Button" parent="PanelContainer/HBoxContainer/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_alignment = 1
-
-[node name="Snapping" type="HBoxContainer" parent="PanelContainer/HBoxContainer/LeftMenu"]
-layout_mode = 2
-theme_override_constants/separation = 0
-
-[node name="SnapButton" type="Button" parent="PanelContainer/HBoxContainer/LeftMenu/Snapping"]
-layout_mode = 2
-focus_mode = 0
-mouse_default_cursor_shape = 2
-theme_type_variation = &"RightConnectedButton"
-toggle_mode = true
-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")]
-custom_minimum_size = Vector2(48, 22)
-layout_mode = 2
-theme_type_variation = &"LeftConnectedLineEdit"
-max_length = 20
-editable = false
-min_value = 0.001
-allow_lower = false
-
-[node name="ZoomMenu" parent="PanelContainer/HBoxContainer" instance=ExtResource("8_xtdmn")]
-unique_name_in_owner = true
-layout_mode = 2
-
-[connection signal="size_changed" from="ViewportPanel/ViewportContainer/Viewport" to="ViewportPanel/ViewportContainer/Viewport" method="_on_size_changed"]
-[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="pressed" from="ViewportPanel/VBoxContainer/Toolbar/ViewportOptions/LeftMenu/Visuals" to="." method="_on_visuals_button_pressed"]
+[connection signal="pressed" from="ViewportPanel/VBoxContainer/Toolbar/ViewportOptions/LeftMenu/Reference" to="." method="_on_reference_pressed"]
+[connection signal="toggled" from="ViewportPanel/VBoxContainer/Toolbar/ViewportOptions/LeftMenu/Snapping/SnapButton" to="." method="_on_snap_button_toggled"]
+[connection signal="value_changed" from="ViewportPanel/VBoxContainer/Toolbar/ViewportOptions/LeftMenu/Snapping/SnapNumberEdit" to="." method="_on_snap_number_edit_value_changed"]
+[connection signal="zoom_changed" from="ViewportPanel/VBoxContainer/Toolbar/ViewportOptions/ZoomMenu" to="ViewportPanel/VBoxContainer/ViewportContainer/Viewport" method="_on_zoom_changed"]
+[connection signal="zoom_reset_pressed" from="ViewportPanel/VBoxContainer/Toolbar/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 0fba42f..f13b953 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 2082aaf..94c020b 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 a932dad..76a0f1f 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 2da627b..5796e54 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 1cc3f54..62f5432 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,30 @@ 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
+theme_override_constants/separation = 6
+
+[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/VBoxContainer/MainContainer" instance=ExtResource("4_podmt")]
layout_mode = 2
-[node name="Inspector" parent="PanelContainer/HSplitContainer/MarginContainer/MainContainer" instance=ExtResource("4_podmt")]
+[node name="MarginContainer2" type="MarginContainer" parent="PanelContainer/HSplitContainer"]
layout_mode = 2
+theme_override_constants/margin_top = 6
-[node name="Display" parent="PanelContainer/HSplitContainer" instance=ExtResource("5_4vrq4")]
+[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 b56af60..5373ed0 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():
@@ -135,16 +135,23 @@ func get_xnode_editor(xid: PackedInt32Array) -> Control:
xnode_editor = xnode_editor.child_xnodes_container.get_child(xid[i])
return xnode_editor
-func get_xnode_editor_rect(xid: PackedInt32Array) -> Rect2:
+func get_xnode_editor_rect(xid: PackedInt32Array, inner_index := -1) -> Rect2:
var xnode_editor := get_xnode_editor(xid)
if not is_instance_valid(xnode_editor):
return Rect2()
# Position relative to the element container.
- return Rect2(xnode_editor.global_position - scroll_container.global_position +\
- Vector2(0, scroll_container.scroll_vertical), xnode_editor.size)
+ var xid_position := Vector2(xnode_editor.global_position -\
+ scroll_container.global_position) + Vector2(0, scroll_container.scroll_vertical)
+
+ if inner_index == -1:
+ return Rect2(xid_position, xnode_editor.size)
+ else:
+ var inner_rect: Rect2 = xnode_editor.get_inner_rect(inner_index) if\
+ State.root_element.get_xnode(xid).is_element() else Rect2()
+ return Rect2(xid_position + inner_rect.position, inner_rect.size)
# This function assumes there exists a element editor for the corresponding XID.
-func scroll_to_view_element_editor(xid: PackedInt32Array) -> void:
- scroll_container.get_v_scroll_bar().value = get_xnode_editor_rect(xid).position.y -\
- scroll_container.size.y / 5
+func scroll_to_view_element_editor(xid: PackedInt32Array, inner_idx := -1) -> void:
+ scroll_container.get_v_scroll_bar().value =\
+ get_xnode_editor_rect(xid, inner_idx).position.y - scroll_container.size.y / 5
diff --git a/src/ui_parts/export_menu.gd b/src/ui_parts/export_menu.gd
index 945f251..7e7712a 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/eyedropper_popup.gd b/src/ui_parts/eyedropper_popup.gd
index 6363dbb..24ee42b 100644
--- a/src/ui_parts/eyedropper_popup.gd
+++ b/src/ui_parts/eyedropper_popup.gd
@@ -80,9 +80,9 @@ func _draw() -> void:
draw_circle(pos, FRAME_RADIUS + FRAME_WIDTH / 2.0, theme_color, false, FRAME_WIDTH,
true)
- draw_rect(Rect2(pos - Vector2(1, 1) * (PIXEL_SIZE / 2.0 - 0.5),
- Vector2(1, 1) * PIXEL_SIZE), Color.WHITE, false, 1.0)
draw_rect(Rect2(pos - Vector2(1, 1) * (PIXEL_SIZE / 2.0 + 0.5),
+ Vector2(1, 1) * PIXEL_SIZE), Color.WHITE, false, 1.0)
+ draw_rect(Rect2(pos - Vector2(1, 1) * (PIXEL_SIZE / 2.0 + 1.5),
Vector2(1, 1) * (PIXEL_SIZE + 2)), Color.BLACK, false, 1.0)
var stylebox_width := FRAME_RADIUS * 2
diff --git a/src/ui_parts/global_actions.gd b/src/ui_parts/global_actions.gd
new file mode 100644
index 0000000..a7a27a3
--- /dev/null
+++ b/src/ui_parts/global_actions.gd
@@ -0,0 +1,153 @@
+extends HBoxContainer
+
+@onready var import_button: Button = $RightSide/ImportButton
+@onready var export_button: Button = $RightSide/ExportButton
+@onready var more_options: Button = $LeftSide/MoreOptions
+@onready var settings_button: Button = $LeftSide/SettingsButton
+@onready var size_button: Button = $RightSide/SizeButton
+@onready var file_button: Button = $RightSide/FileButton
+
+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")
+ update_file_button()
+
+func _ready() -> void:
+ Configs.language_changed.connect(update_translations)
+ update_translations()
+
+ State.svg_changed.connect(update_size_button)
+ 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)
+ update_file_button()
+
+ # Fix the size button sizing.
+ size_button.begin_bulk_theme_override()
+ for theming in ["normal", "hover", "pressed", "disabled"]:
+ var stylebox := size_button.get_theme_stylebox(theming).duplicate()
+ stylebox.content_margin_bottom = 0
+ stylebox.content_margin_top = 0
+ size_button.add_theme_stylebox_override(theming, stylebox)
+ size_button.end_bulk_theme_override()
+
+ import_button.pressed.connect(ShortcutUtils.fn("import"))
+ export_button.pressed.connect(ShortcutUtils.fn("export"))
+ more_options.pressed.connect(_on_more_options_pressed)
+ size_button.pressed.connect(_on_size_button_pressed)
+ file_button.pressed.connect(_on_file_button_pressed)
+ settings_button.pressed.connect(ShortcutUtils.fn_call.bind("open_settings"))
+
+
+func _on_size_button_pressed() -> void:
+ var btn_array: Array[Button] = [
+ ContextPopup.create_button(Translator.translate("Optimize"),
+ ShortcutUtils.fn("optimize"), false, load("res://assets/icons/Compress.svg"),
+ "optimize")]
+ var context_popup := ContextPopup.new()
+ context_popup.setup(btn_array, true)
+ HandlerGUI.popup_under_rect_center(context_popup, size_button.get_global_rect(),
+ get_viewport())
+
+func _on_file_button_pressed() -> void:
+ var btn_array: Array[Button] = []
+ btn_array.append(ContextPopup.create_button(Translator.translate("Save SVG"),
+ FileUtils.save_svg, false, load("res://assets/icons/Save.svg"), "save"))
+ btn_array.append(ContextPopup.create_button(Translator.translate("Save SVG as…"),
+ FileUtils.save_svg_as, false, load("res://assets/icons/Save.svg"), "save_as"))
+ btn_array.append(ContextPopup.create_button(Translator.translate("Reset SVG"),
+ ShortcutUtils.fn("reset_svg"),
+ FileUtils.compare_svg_to_disk_contents() != FileUtils.FileState.DIFFERENT,
+ load("res://assets/icons/Reload.svg"), "reset_svg"))
+ btn_array.append(ContextPopup.create_button(Translator.translate("Open externally"),
+ ShortcutUtils.fn("open_externally"),
+ not FileAccess.file_exists(Configs.savedata.get_active_tab().svg_file_path),
+ load("res://assets/icons/OpenFile.svg"), "open_externally"))
+ btn_array.append(ContextPopup.create_button(Translator.translate("Show in File Manager"),
+ ShortcutUtils.fn("open_in_folder"),
+ not FileAccess.file_exists(Configs.savedata.get_active_tab().svg_file_path),
+ load("res://assets/icons/OpenFolder.svg"), "open_in_folder"))
+ var context_popup := ContextPopup.new()
+ context_popup.setup(btn_array, true, file_button.size.x, -1, PackedInt32Array([2]))
+ HandlerGUI.popup_under_rect_center(context_popup, file_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 antialias_fraction := 0.25
+ var final_size := 16
+ var first_resizing_size := final_size / antialias_fraction
+ var svg_buffer := FileAccess.get_file_as_bytes("res://assets/logos/icon.svg")
+ var about_image := Image.new()
+ about_image.load_svg_from_buffer(svg_buffer)
+ var factor := minf(first_resizing_size / about_image.get_width(),
+ first_resizing_size / about_image.get_height())
+ about_image.load_svg_from_buffer(svg_buffer, factor)
+ about_image.resize(final_size, final_size, Image.INTERPOLATE_LANCZOS)
+
+ var about_btn := ContextPopup.create_button(Translator.translate("About…"),
+ ShortcutUtils.fn("about_info"), false,
+ ImageTexture.create_from_image(about_image), "about_info")
+ 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://"))
+
+
+func update_size_button() -> void:
+ 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 State.root_element.optimize(true):
+ size_button.disabled = false
+ size_button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
+ update_size_button_colors()
+ else:
+ size_button.disabled = true
+ size_button.mouse_default_cursor_shape = Control.CURSOR_ARROW
+ size_button.remove_theme_color_override("font_color")
+
+func update_size_button_colors() -> void:
+ size_button.begin_bulk_theme_override()
+ for theming in ["font_color", "font_hover_color", "font_pressed_color"]:
+ size_button.add_theme_color_override(theming,
+ Configs.savedata.basic_color_warning.lerp(Color.WHITE, 0.5))
+ size_button.end_bulk_theme_override()
+
+func update_file_button() -> void:
+ 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)
diff --git a/src/ui_parts/global_actions.gd.uid b/src/ui_parts/global_actions.gd.uid
new file mode 100644
index 0000000..bf60184
--- /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 0000000..2e581cc
--- /dev/null
+++ b/src/ui_parts/global_actions.tscn
@@ -0,0 +1,67 @@
+[gd_scene load_steps=7 format=3 uid="uid://cxmrx6t4jkhyj"]
+
+[ext_resource type="Script" uid="uid://cgbgw4ok5jxk5" path="res://src/ui_parts/global_actions.gd" id="1_x4rqo"]
+[ext_resource type="Texture2D" uid="uid://ccbta5q43jobk" path="res://assets/icons/More.svg" id="2_71075"]
+[ext_resource type="Texture2D" uid="uid://6ymbl3jqersp" path="res://assets/icons/Import.svg" id="2_giwu1"]
+[ext_resource type="Texture2D" uid="uid://d0uvwj0t44n6v" path="res://assets/icons/Export.svg" id="3_4ckhj"]
+[ext_resource type="Texture2D" uid="uid://ckkkgof1hcbld" path="res://assets/icons/Gear.svg" id="3_xl5uh"]
+[ext_resource type="FontFile" uid="uid://dc0w4sx0h0fui" path="res://assets/fonts/FontBold.ttf" id="4_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="SizeButton" type="Button" parent="RightSide"]
+layout_mode = 2
+focus_mode = 0
+mouse_default_cursor_shape = 2
+theme_type_variation = &"TranslucentButton"
+theme_override_fonts/font = ExtResource("4_xl5uh")
+
+[node name="FileButton" type="Button" parent="RightSide"]
+layout_mode = 2
+size_flags_horizontal = 2
+focus_mode = 0
+mouse_default_cursor_shape = 2
+theme_type_variation = &"FlatButton"
+text_overrun_behavior = 3
+
+[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 a2a3484..0000000
--- 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 94d0743..e203e28 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 b48ff7c..d93da89 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
@@ -143,6 +143,8 @@ func sync_handles(xid: PackedInt32Array) -> void:
handles = new_handles
handles += generate_path_handles(element)
handles += generate_polyhandles(element)
+ # Pretend the mouse was moved to update the hovering.
+ HandlerGUI.throw_mouse_motion_event()
queue_redraw()
func generate_path_handles(element: Element) -> Array[Handle]:
@@ -183,10 +185,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 +336,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 +357,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 +392,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 +582,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 +597,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 +622,19 @@ 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)
+ State.root_element.canvas_transform * xnode.get_transform())
+ var grow_amount := Vector2(4, 4) / State.zoom
+ grow_amount /= State.root_element.canvas_transform.get_scale()
+ grow_amount /= xnode.get_transform().get_scale()
RenderingServer.canvas_item_add_rect(selections_surface,
- bounding_box.grow(4.0 / Indications.zoom), Color.WHITE)
+ bounding_box.grow_individual(grow_amount.x, grow_amount.y,
+ grow_amount.x, grow_amount.y), Color.WHITE)
func draw_objects_of_type(color: Color, polylines: Array[PackedVector2Array],
multiline: PackedVector2Array, handles_array: Array[Handle],
@@ -638,12 +644,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 +657,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 +669,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 +677,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 +702,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,39 +725,39 @@ 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()
- var vec2_pos := Vector2(event_pos[0], event_pos[1])
- HandlerGUI.popup_under_pos(create_element_context(vec2_pos), popup_pos, vp)
+ State.clear_all_selections()
+ HandlerGUI.popup_under_pos(create_element_context(
+ State.root_element.world_to_canvas_64_bit(event_pos)), popup_pos, vp)
else:
var hovered_xid := hovered_handle.element.xid
var inner_idx := -1
@@ -762,22 +766,23 @@ 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 not (State.semi_selected_xid == hovered_xid and\
+ inner_idx in State.inner_selections) and\
+ not (inner_idx == -1 and 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 +790,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,47 +810,46 @@ 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
# Creates a popup for adding a shape at a position.
-func create_element_context(pos: Vector2) -> ContextPopup:
+func create_element_context(precise_pos: PackedFloat64Array) -> ContextPopup:
var btn_array: Array[Button] = []
for shape in ["path", "circle", "ellipse", "rect", "line", "polygon", "polyline"]:
var btn := ContextPopup.create_button(shape,
- add_shape_at_pos.bind(shape, pos), false, DB.get_element_icon(shape))
+ add_shape_at_pos.bind(shape, precise_pos), false, DB.get_element_icon(shape))
btn.add_theme_font_override("font", ThemeUtils.mono_font)
btn_array.append(btn)
var element_context := ContextPopup.new()
- var separation_indices: Array[int] = [1, 4]
element_context.setup_with_title(btn_array, Translator.translate("New shape"),
- true, -1, -1, separation_indices)
+ true, -1, -1, PackedInt32Array([1, 4]))
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()
+func add_shape_at_pos(element_name: String, precise_pos: PackedFloat64Array) -> void:
+ State.root_element.add_xnode(DB.element_with_setup(element_name, [precise_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 3e3fe98..3c9f121 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,18 +14,18 @@ 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,
- Configs.savedata.editor_formatter)
- var preview_text := SVGParser.root_to_text(imported_text_parse_result.svg,
- Configs.savedata.editor_formatter)
- var preview_parse_result := SVGParser.text_to_root(preview_text,
- Configs.savedata.editor_formatter)
- var preview := preview_parse_result.svg
- if is_instance_valid(preview):
- texture_preview.setup_svg(SVGParser.root_to_text(preview,
- Configs.savedata.editor_formatter), preview.get_size())
+ var imported_text_parse_result := SVGParser.text_to_root(imported_text)
+ if is_instance_valid(imported_text_parse_result.svg):
+ var preview_text := SVGParser.root_to_editor_text(imported_text_parse_result.svg)
+ var preview_parse_result := SVGParser.text_to_root(preview_text)
+ var preview := preview_parse_result.svg
+ if is_instance_valid(preview):
+ texture_preview.setup_svg(SVGParser.root_to_editor_text(preview),
+ preview.get_size())
if imported_text_parse_result.error != SVGParser.ParseError.OK:
texture_preview.hide()
@@ -44,7 +45,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 e4e6cee..4b17a50 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 f3bb98a..1e20365 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,18 +29,18 @@ 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:
- var new_element := DB.element_with_setup(element_name)
+ var new_element := DB.element_with_setup(element_name, [])
var loc: PackedInt32Array
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 c9762bb..e658bb9 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,7 +42,7 @@ func _enter_tree() -> void:
# Custom menus.
_generate_main_menus()
_setup_menu_items()
- SVG.changed.connect(_on_svg_changed)
+ State.svg_changed.connect(_on_svg_changed)
func _reset_menus() -> void:
@@ -108,12 +106,11 @@ func _setup_menu_items() -> void:
_add_action(file_rid, "import")
_add_action(file_rid, "export")
_add_action(file_rid, "save")
+ _add_action(file_rid, "save_as")
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()
# Edit and Tool menus.
@@ -180,18 +177,14 @@ 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)
- var is_path_empty := Configs.savedata.current_file_path.is_empty()
- NativeMenu.set_item_disabled(file_rid, file_clear_association_idx, is_path_empty)
- NativeMenu.set_item_disabled(file_rid, file_reset_svg_idx, is_path_empty)
-
+ NativeMenu.set_item_disabled(file_rid, file_reset_svg_idx,
+ FileUtils.compare_svg_to_disk_contents() == FileUtils.FileState.DIFFERENT)
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)
NativeMenu.set_item_checked(view_rid, view_rasterized_svg_idx, rasterized_svg)
-
func _on_display_snap_settings_updated(snap_enabled: bool, snap_amount: float) -> void:
NativeMenu.set_item_checked(snap_rid, snap_enable_idx, snap_enabled)
NativeMenu.set_item_checked(snap_rid, snap_0125_idx, is_equal_approx(snap_amount, 0.125))
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 0000000..2f252f1
--- /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 38416e5..538abe1 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 b00faf9..5c41b03 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 e0bc2cb..4e846ec 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()
@@ -534,8 +536,8 @@ func show_formatter(category: String) -> void:
current_setup_setting = "xml_indentation_spaces"
add_number_dropdown(Translator.translate("Number of indentation spaces"),
[2, 3, 4, 6, 8], true, false, Formatter.INDENTS_MIN, Formatter.INDENTS_MAX,
- not current_setup_resource.xml_pretty_formatting and\
- current_setup_resource.xml_indentation_use_spaces)
+ not (current_setup_resource.xml_pretty_formatting and\
+ current_setup_resource.xml_indentation_use_spaces))
add_section(Translator.translate("Numbers"))
current_setup_setting = "number_remove_leading_zero"
diff --git a/src/ui_parts/tab_bar.gd b/src/ui_parts/tab_bar.gd
new file mode 100644
index 0000000..3612360
--- /dev/null
+++ b/src/ui_parts/tab_bar.gd
@@ -0,0 +1,285 @@
+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"), "new_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))
+ btn_arr.append(ContextPopup.create_button(Translator.translate("Open externally"),
+ ShortcutUtils.fn("open_externally"),
+ not FileAccess.file_exists(Configs.savedata.get_active_tab().svg_file_path),
+ load("res://assets/icons/OpenFile.svg"), "open_externally"))
+ btn_arr.append(ContextPopup.create_button(Translator.translate("Show in File Manager"),
+ ShortcutUtils.fn("open_in_folder"),
+ not FileAccess.file_exists(Configs.savedata.get_active_tab().svg_file_path),
+ load("res://assets/icons/OpenFolder.svg"), "open_in_folder"))
+ var tab_popup := ContextPopup.new()
+ tab_popup.setup(btn_arr, true, -1, -1, PackedInt32Array([4]))
+
+ 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("Create a 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:
+ var tab_index_at_position := get_tab_index_at(at_position)
+ if tab_index_at_position == -1:
+ return
+ # 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(tab_index_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 0000000..3f20792
--- /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 219b234..6ec8006 100644
--- a/src/ui_parts/viewport.gd
+++ b/src/ui_parts/viewport.gd
@@ -18,22 +18,24 @@ 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
- view.position = new_position.clamp(Vector2(view.limit_left, view.limit_top),
+ var scaled_size := size / State.zoom
+ view.unsnapped_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_left := maxf(view.unsnapped_position.x, 0.0)
+ var stripped_top := maxf(view.unsnapped_position.y, 0.0)
+ var stripped_right := minf(view.unsnapped_position.x + scaled_size.x,
+ State.root_element.width)
+ var stripped_bottom := minf(view.unsnapped_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 +43,26 @@ 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()
+ var root_element_size := State.root_element.get_size()
+ if root_element_size.is_finite():
+ display.size = root_element_size
+ reference_texture.size = root_element_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\
@@ -67,27 +70,27 @@ func _unhandled_input(event: InputEvent) -> void:
# Zooming with Ctrl + MMB.
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_to = get_mouse_position() / Vector2(size)
+ 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)
# Panning with LMB or MMB. This gives a reliable way to adjust the view
# 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)
+ set_view(view.unsnapped_position - (wrap_mouse(event.relative) if\
+ 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.unsnapped_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 +133,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.unsnapped_position + move_vec * factor / State.zoom * 32)
else:
if not event.is_echo():
@@ -138,33 +141,34 @@ 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)))
+
+ set_view(Vector2(lerpf(view.unsnapped_position.x, view.unsnapped_position.x +\
+ old_size.x - size.x / State.zoom, offset.x), lerpf(view.unsnapped_position.y,
+ view.unsnapped_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 4bb8afe..c5ce356 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 a075005..d53187a 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 9777ae5..55ad314 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 927399e..5f89e32 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 bc2ecce..fb97127 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 92%
rename from src/ui_parts/camera.gd
rename to src/ui_widgets/camera.gd
index 1619129..aaf02a9 100644
--- a/src/ui_parts/camera.gd
+++ b/src/ui_widgets/camera.gd
@@ -14,22 +14,24 @@ var zoom: float
var ci := get_canvas_item()
var surface := RenderingServer.canvas_item_create() # Used for drawing the numbers.
+var unsnapped_position: Vector2
+
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:
- position = position.snapped(Vector2(1, 1) / zoom)
+ position = unsnapped_position.snapped(Vector2(1, 1) / zoom)
get_viewport().canvas_transform = Transform2D(0.0, Vector2(zoom, zoom),
0.0, -position * zoom)
queue_redraw()
@@ -37,7 +39,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) / 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 61840d2..6b6dce2 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 52%
rename from src/ui_parts/code_editor.gd
rename to src/ui_widgets/code_editor.gd
index 50d4399..4097d3b 100644
--- a/src/ui_parts/code_editor.gd
+++ b/src/ui_widgets/code_editor.gd
@@ -4,43 +4,23 @@ extends VBoxContainer
@onready var code_edit: TextEdit = $ScriptEditor/SVGCodeEdit
@onready var error_bar: PanelContainer = $ScriptEditor/ErrorBar
@onready var error_label: RichTextLabel = $ScriptEditor/ErrorBar/Label
-@onready var size_button: Button = %SizeButton
-@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)
- auto_update_text()
- update_size_button()
- 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)
- 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"]:
- var stylebox := size_button.get_theme_stylebox(theming).duplicate()
- stylebox.content_margin_bottom = 0
- stylebox.content_margin_top = 0
- size_button.add_theme_stylebox_override(theming, stylebox)
- size_button.end_bulk_theme_override()
+ State.svg_changed.connect(auto_update_text)
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()
func update_error(err_id: SVGParser.ParseError) -> void:
if err_id == SVGParser.ParseError.OK:
@@ -114,77 +94,16 @@ func setup_theme() -> void:
error_bar.add_theme_stylebox_override("panel", bottom_stylebox)
-func update_size_button() -> void:
- var svg_text_size := 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):
- size_button.disabled = false
- size_button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
- update_size_button_colors()
-
- else:
- size_button.disabled = true
- size_button.mouse_default_cursor_shape = Control.CURSOR_ARROW
- size_button.remove_theme_color_override("font_color")
-
-func update_size_button_colors() -> void:
- size_button.begin_bulk_theme_override()
- for theming in ["font_color", "font_hover_color", "font_pressed_color"]:
- size_button.add_theme_color_override(theming,
- Configs.savedata.basic_color_warning.lerp(Color.WHITE, 0.5))
- 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()
- Utils.set_max_text_width(file_button, 140.0, 12.0)
-
-
func _on_svg_code_edit_text_changed() -> void:
- SVG.set_text(code_edit.text)
- SVG.sync_elements()
+ 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()
-
-
-func _on_file_button_pressed() -> void:
- var btn_array: Array[Button] = []
- btn_array.append(ContextPopup.create_button(Translator.translate("Save SVG"),
- FileUtils.save_svg, false, load("res://assets/icons/Save.svg"), "save"))
- btn_array.append(ContextPopup.create_button(Translator.translate("Open file"),
- ShortcutUtils.fn("open_svg"),
- not FileAccess.file_exists(Configs.savedata.current_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(),
- get_viewport())
-
-func _on_size_button_pressed() -> void:
- var btn_array: Array[Button] = [
- ContextPopup.create_button(Translator.translate("Optimize"),
- ShortcutUtils.fn("optimize"), false, load("res://assets/icons/Compress.svg"),
- "optimize")]
- var context_popup := ContextPopup.new()
- context_popup.setup(btn_array, true)
- HandlerGUI.popup_under_rect_center(context_popup, size_button.get_global_rect(),
- get_viewport())
+ State.clear_all_selections()
func _on_options_button_pressed() -> void:
@@ -192,9 +111,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 62%
rename from src/ui_parts/code_editor.tscn
rename to src/ui_widgets/code_editor.tscn
index 4a46ba6..03f417d 100644
--- a/src/ui_parts/code_editor.tscn
+++ b/src/ui_widgets/code_editor.tscn
@@ -1,12 +1,9 @@
-[gd_scene load_steps=10 format=3 uid="uid://cr1fdlmbknnko"]
+[gd_scene load_steps=7 format=3 uid="uid://cr1fdlmbknnko"]
-[ext_resource type="Script" uid="uid://c3q5dvxm6ro1m" path="res://src/ui_parts/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="Script" uid="uid://c3q5dvxm6ro1m" path="res://src/ui_widgets/code_editor.gd" id="1_nffk0"]
[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" uid="uid://d1mpyxtnqqxh0" 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
@@ -34,7 +31,7 @@ corner_radius_bottom_right = 5
corner_radius_bottom_left = 5
[node name="CodeEditor" type="VBoxContainer"]
-custom_minimum_size = Vector2(0, 90)
+size_flags_vertical = 3
theme_override_constants/separation = 0
script = ExtResource("1_nffk0")
@@ -46,23 +43,6 @@ theme_override_styles/panel = SubResource("StyleBoxFlat_q56qh")
layout_mode = 2
theme_override_constants/separation = 8
-[node name="SizeButton" type="Button" parent="PanelContainer/CodeButtons"]
-unique_name_in_owner = true
-layout_mode = 2
-focus_mode = 0
-mouse_default_cursor_shape = 2
-theme_type_variation = &"TranslucentButton"
-theme_override_fonts/font = ExtResource("2_hl52o")
-
-[node name="FileButton" type="Button" parent="PanelContainer/CodeButtons"]
-unique_name_in_owner = true
-layout_mode = 2
-size_flags_horizontal = 2
-focus_mode = 0
-mouse_default_cursor_shape = 2
-theme_type_variation = &"FlatButton"
-text_overrun_behavior = 3
-
[node name="MetaActions" type="HBoxContainer" parent="PanelContainer/CodeButtons"]
unique_name_in_owner = true
layout_mode = 2
@@ -70,29 +50,12 @@ size_flags_horizontal = 10
[node name="OptionsButton" type="Button" parent="PanelContainer/CodeButtons/MetaActions"]
layout_mode = 2
-tooltip_text = "More"
focus_mode = 0
mouse_default_cursor_shape = 2
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
@@ -100,7 +63,7 @@ size_flags_vertical = 3
theme_override_constants/separation = -2
[node name="SVGCodeEdit" type="TextEdit" parent="ScriptEditor"]
-custom_minimum_size = Vector2(0, 80)
+custom_minimum_size = Vector2(0, 96)
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
@@ -118,8 +81,6 @@ theme_override_fonts/normal_font = ExtResource("2_p4nol")
theme_override_font_sizes/normal_font_size = 14
fit_content = true
-[connection signal="pressed" from="PanelContainer/CodeButtons/SizeButton" to="." method="_on_size_button_pressed"]
-[connection signal="pressed" from="PanelContainer/CodeButtons/FileButton" to="." method="_on_file_button_pressed"]
[connection signal="pressed" from="PanelContainer/CodeButtons/MetaActions/OptionsButton" to="." method="_on_options_button_pressed"]
[connection signal="focus_entered" from="ScriptEditor/SVGCodeEdit" to="." method="_on_svg_code_edit_focus_entered"]
[connection signal="focus_exited" from="ScriptEditor/SVGCodeEdit" to="." method="_on_svg_code_edit_focus_exited"]
diff --git a/src/ui_widgets/color_field.gd b/src/ui_widgets/color_field.gd
index 8e98b5e..9eddebe 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 b13f566..343c5ae 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 1480d23..9eb7283 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 f19c6ee..d53dda9 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 d3f0a49..5d8981e 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 a337ef4..e2623b5 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 a337ef4..e2623b5 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 b7c0a25..2fc6919 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 0eb460c..0bc2eeb 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 e4933f0..b88fc57 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 7f5ebab..7a20459 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 619934f..60596a2 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,28 @@ 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 get_inner_rect(idx: int) -> Rect2:
+ if element is ElementPath:
+ var inner_rect: Rect2 = main_container.get_child(0).path_field.get_inner_rect(idx)
+ inner_rect.position += main_container.position
+ inner_rect.position += main_container.get_child(0).position
+ inner_rect.position += main_container.get_child(0).path_field.position
+ return inner_rect
+ elif element is ElementPolygon or element is ElementPolyline:
+ var inner_rect: Rect2 = main_container.get_child(0).points_field.get_inner_rect(idx)
+ inner_rect.position += main_container.position
+ inner_rect.position += main_container.get_child(0).position
+ inner_rect.position += main_container.get_child(0).points_field.position
+ return inner_rect
+ return Rect2()
+
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 +214,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 613093f..135aa84 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 d1d4070..ad01b2c 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:
@@ -34,7 +34,7 @@ func sync_to_attribute() -> void:
func _on_text_submitted(new_text: String) -> void:
if new_text.is_empty() or\
AttributeID.get_validity(new_text) != AttributeID.ValidityLevel.INVALID:
- set_value(new_text)
+ set_value(new_text, true)
else:
sync_to_attribute()
diff --git a/src/ui_widgets/number_field.gd b/src/ui_widgets/number_field.gd
index 1594bf6..e31193a 100644
--- a/src/ui_widgets/number_field.gd
+++ b/src/ui_widgets/number_field.gd
@@ -18,7 +18,7 @@ var cached_max_value: float
func set_value(new_value: String, save := false) -> void:
if not new_value.is_empty():
new_value = new_value.strip_edges()
- if not new_value.ends_with("%"):
+ if not AttributeNumeric.text_check_percentage(new_value):
var numeric_value := NumstringParser.evaluate(new_value)
# Validate the value.
if !is_finite(numeric_value):
@@ -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.tscn b/src/ui_widgets/number_field.tscn
index ea674fb..835f631 100644
--- a/src/ui_widgets/number_field.tscn
+++ b/src/ui_widgets/number_field.tscn
@@ -1,6 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://c6vgjud6wrdu4"]
-[ext_resource type="Script" path="res://src/ui_widgets/number_field.gd" id="1_saak1"]
+[ext_resource type="Script" uid="uid://313bndd6viir" path="res://src/ui_widgets/number_field.gd" id="1_saak1"]
[node name="NumberField" type="LineEdit"]
custom_minimum_size = Vector2(54, 22)
diff --git a/src/ui_widgets/number_field_with_slider.gd b/src/ui_widgets/number_field_with_slider.gd
index ae105c7..1ab952e 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/number_field_with_slider.tscn b/src/ui_widgets/number_field_with_slider.tscn
index 6d4ed5e..fd3d433 100644
--- a/src/ui_widgets/number_field_with_slider.tscn
+++ b/src/ui_widgets/number_field_with_slider.tscn
@@ -1,6 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://bp2vpf7g8w8aj"]
-[ext_resource type="Script" path="res://src/ui_widgets/number_field_with_slider.gd" id="1_ymm02"]
+[ext_resource type="Script" uid="uid://cmj05v3n1gfpl" path="res://src/ui_widgets/number_field_with_slider.gd" id="1_ymm02"]
[node name="NumberFieldWithSlider" type="Control"]
clip_contents = true
diff --git a/src/ui_widgets/options_dialog.gd b/src/ui_widgets/options_dialog.gd
new file mode 100644
index 0000000..b591825
--- /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 0000000..2335c65
--- /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 0000000..b76eb4d
--- /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/palette_config.gd b/src/ui_widgets/palette_config.gd
index 39cf794..988c8af 100644
--- a/src/ui_widgets/palette_config.gd
+++ b/src/ui_widgets/palette_config.gd
@@ -176,12 +176,12 @@ func move_down() -> void:
Configs.savedata.move_palette_down(find_palette_index())
layout_changed.emit()
-func paste_palette() -> void:
- var pasted_palettes := Palette.text_to_palettes(Utils.get_clipboard_web_safe())
- if pasted_palettes.is_empty():
- return
- Configs.savedata.replace_palette(find_palette_index(), pasted_palettes[0])
- layout_changed.emit() # Emit it in any case, since the palette is a new object.
+func copy_palette(palette_idx: int) -> void:
+ DisplayServer.clipboard_set(Configs.savedata.get_palette(palette_idx).to_text())
+
+func save_palette(palette_idx: int) -> void:
+ var saved_palette := Configs.savedata.get_palette(palette_idx)
+ FileUtils.open_xml_export_dialog(saved_palette.to_text(), saved_palette.title)
func open_palette_options() -> void:
var btn_arr: Array[Button] = []
@@ -215,28 +215,29 @@ func find_palette_index() -> int:
func _on_palette_button_pressed() -> void:
var palette_idx := find_palette_index()
+ var separator_idx := 3
var btn_arr: Array[Button] = []
btn_arr.append(ContextPopup.create_button(Translator.translate("Rename"),
popup_edit_name, false, load("res://assets/icons/Rename.svg")))
if palette_idx >= 1:
+ separator_idx += 1
btn_arr.append(ContextPopup.create_button(Translator.translate("Move Up"),
move_up, false, load("res://assets/icons/MoveUp.svg")))
if palette_idx < Configs.savedata.get_palette_count() - 1:
+ separator_idx += 1
btn_arr.append(ContextPopup.create_button(Translator.translate("Move Down"),
move_down, false, load("res://assets/icons/MoveDown.svg")))
- btn_arr.append(ContextPopup.create_button(Translator.translate("Copy as XML"),
- DisplayServer.clipboard_set.bind(Configs.savedata.get_palette(palette_idx).\
- to_text()), false, load("res://assets/icons/Copy.svg")))
- btn_arr.append(ContextPopup.create_button(Translator.translate("Paste XML"),
- paste_palette, !Palette.is_valid_palette(Utils.get_clipboard_web_safe()),
- load("res://assets/icons/Paste.svg")))
btn_arr.append(ContextPopup.create_button(Translator.translate("Apply Preset"),
open_palette_options, false, load("res://assets/icons/Import.svg")))
btn_arr.append(ContextPopup.create_button(Translator.translate("Delete"),
delete, false, load("res://assets/icons/Delete.svg")))
+ btn_arr.append(ContextPopup.create_button(Translator.translate("Copy as XML"),
+ copy_palette.bind(palette_idx), false, load("res://assets/icons/Copy.svg")))
+ btn_arr.append(ContextPopup.create_button(Translator.translate("Save as XML"),
+ save_palette.bind(palette_idx), false, load("res://assets/icons/Paste.svg")))
var context_popup := ContextPopup.new()
- context_popup.setup(btn_arr, true)
+ context_popup.setup(btn_arr, true, -1, -1, PackedInt32Array([separator_idx]))
HandlerGUI.popup_under_rect_center(context_popup, palette_button.get_global_rect(),
get_viewport())
diff --git a/src/ui_widgets/path_popup.gd b/src/ui_widgets/path_popup.gd
index cf8c4d7..b5fc466 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 2313fc0..2feb9fa 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 cb19112..c902cfa 100644
--- a/src/ui_widgets/pathdata_field.gd
+++ b/src/ui_widgets/pathdata_field.gd
@@ -52,9 +52,9 @@ var add_move_button: Control
func set_value(new_value: String, save := false) -> void:
element.set_attribute(attribute_name, new_value)
- sync(element.get_attribute(attribute_name).get_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
@@ -83,6 +83,11 @@ func setup() -> void:
update_translation()
+func get_inner_rect(index: int) -> Rect2:
+ return Rect2(commands_container.position + Vector2(0, STRIP_HEIGHT * index),
+ Vector2(commands_container.size.x, STRIP_HEIGHT))
+
+
func _on_element_attribute_changed(attribute_changed: String) -> void:
if attribute_name == attribute_changed:
sync_to_attribute()
@@ -118,7 +123,8 @@ func sync(new_value: String) -> void:
add_move_button.queue_free()
# Rebuild the path commands.
commands_container.custom_minimum_size.y = cmd_count * STRIP_HEIGHT
- HandlerGUI.throw_mouse_motion_event()
+ if get_rect().has_point(get_local_mouse_position()):
+ HandlerGUI.throw_mouse_motion_event()
if hovered_idx >= cmd_count:
activate_hovered(-1)
reactivate_hovered()
@@ -127,26 +133,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 +162,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 +189,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 +199,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 +228,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)
@@ -374,7 +379,7 @@ func setup_path_command_controls(idx: int) -> Control:
relative_button.add_theme_font_override("font", ThemeUtils.mono_font)
relative_button.add_theme_font_size_override("font_size", 13)
relative_button.add_theme_color_override("font_color", Color(1, 1, 1))
- # Disabled styleboxes aren unused, but must be set for the correct content margins.
+ # Disabled styleboxes are unused, but must be set for the correct content margins.
if is_absolute:
relative_button.add_theme_stylebox_override("disabled", absolute_button_normal)
relative_button.add_theme_stylebox_override("normal", absolute_button_normal)
@@ -472,16 +477,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 4c2f8ce..a90a338 100644
--- a/src/ui_widgets/points_field.gd
+++ b/src/ui_widgets/points_field.gd
@@ -44,9 +44,9 @@ var add_move_button: Control
func set_value(new_value: String, save := false) -> void:
element.set_attribute(attribute_name, new_value)
- sync(element.get_attribute(attribute_name).get_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
@@ -74,6 +74,11 @@ func setup() -> void:
update_translation()
+func get_inner_rect(index: int) -> Rect2:
+ return Rect2(points_container.position + Vector2(0, STRIP_HEIGHT * index),
+ Vector2(points_container.size.x, STRIP_HEIGHT))
+
+
func _on_element_attribute_changed(attribute_changed: String) -> void:
if attribute_name == attribute_changed:
sync_to_attribute()
@@ -109,7 +114,8 @@ func sync(new_value: String) -> void:
add_move_button.queue_free()
# Rebuild the points.
points_container.custom_minimum_size.y = points_count * STRIP_HEIGHT
- HandlerGUI.throw_mouse_motion_event()
+ if get_rect().has_point(get_local_mouse_position()):
+ HandlerGUI.throw_mouse_motion_event()
if hovered_idx >= points_count:
activate_hovered(-1)
reactivate_hovered()
@@ -120,28 +126,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 +157,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 +184,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 +222,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 +347,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 6b40538..0ad5de1 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).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 565c28e..b1ed969 100644
--- a/src/ui_widgets/transform_field.gd
+++ b/src/ui_widgets/transform_field.gd
@@ -8,9 +8,9 @@ const TransformPopup = preload("res://src/ui_widgets/transform_popup.tscn")
func set_value(new_value: String, save := false) -> void:
element.set_attribute(attribute_name, new_value)
- sync(element.get_attribute(attribute_name).get_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 9defe1b..1f1cf35 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 cb892e3..f124012 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 4ffd270..755a655 100644
--- a/src/utils/FileUtils.gd
+++ b/src/utils/FileUtils.gd
@@ -5,26 +5,38 @@ 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,
- Configs.savedata.editor_formatter).svg, Configs.savedata.editor_formatter):
+ if State.svg_text == SVGParser.root_to_editor_text(SVGParser.text_to_root(content).svg):
return FileState.SAME
else:
return FileState.DIFFERENT
static func save_svg() -> void:
+ var file_path := Configs.savedata.get_active_tab().svg_file_path
+ if not file_path.is_empty() and FileAccess.file_exists(file_path):
+ FileAccess.open(file_path, FileAccess.WRITE).store_string(State.get_export_text())
+ else:
+ save_svg_as()
+
+static func save_svg_as() -> void:
open_export_dialog(ImageExportData.new())
static func open_export_dialog(export_data: ImageExportData) -> void:
@@ -32,7 +44,7 @@ static func open_export_dialog(export_data: ImageExportData) -> void:
if OS.has_feature("web"):
var web_format_name := ImageExportData.web_formats[export_data.format]
if export_data.format == "svg":
- _web_save(export_data.svg_to_buffer(), web_format_name)
+ _web_save(ImageExportData.svg_to_buffer(), web_format_name)
else:
var img := export_data.generate_image()
_web_save(export_data.image_to_buffer(img), web_format_name)
@@ -45,18 +57,41 @@ 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))
+static func open_xml_export_dialog(xml: String, file_name: String) -> void:
+ OS.request_permissions()
+ if OS.has_feature("web"):
+ _web_save(xml.to_utf8_buffer(), "application/xml")
+ else:
+ if _is_native_preferred():
+ var native_callback :=\
+ func(has_selected: bool, files: PackedStringArray, _filter_idx: int):
+ if has_selected:
+ _finish_xml_export(files[0], xml)
+
+ DisplayServer.file_dialog_show(
+ Translator.translate("Save the .\"{format}\" file").format(
+ {"format": "xml"}), Configs.savedata.get_last_dir(),
+ file_name + ".xml", false, DisplayServer.FILE_DIALOG_MODE_SAVE_FILE,
+ PackedStringArray(["*.xml"]), native_callback)
+ else:
+ var export_dialog := GoodFileDialog.instantiate()
+ export_dialog.setup(Configs.savedata.get_last_dir(),
+ file_name, GoodFileDialogType.FileMode.SAVE, PackedStringArray(["xml"]))
+ HandlerGUI.add_menu(export_dialog)
+ export_dialog.file_selected.connect(func(path): _finish_xml_export(path, xml))
+
static func _finish_export(file_path: String, export_data: ImageExportData) -> void:
if file_path.get_extension().is_empty():
file_path += "." + export_data.format
@@ -70,8 +105,16 @@ 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()
+
+static func _finish_xml_export(file_path: String, xml: String) -> void:
+ if file_path.get_extension().is_empty():
+ file_path += ".xml"
+
+ Configs.savedata.add_recent_dir(file_path.get_base_dir())
+ FileAccess.open(file_path, FileAccess.WRITE).store_string(xml)
HandlerGUI.remove_all_menus()
@@ -164,21 +207,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 _on_import_panel_canceled() -> void:
+ State.transient_tab_path = ""
-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_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_show_in_file_manager(file_path)
# Web stuff.
@@ -202,7 +270,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 +304,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 +333,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 d2d5499..1f8343f 100644
--- a/src/utils/ImageExportData.gd
+++ b/src/utils/ImageExportData.gd
@@ -35,8 +35,8 @@ var lossy := false:
changed.emit()
-func svg_to_buffer() -> PackedByteArray:
- return SVG.get_export_text().to_utf8_buffer()
+static func svg_to_buffer() -> PackedByteArray:
+ 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]))
@@ -58,6 +58,6 @@ func generate_image() -> Image:
export_svg.set_attribute("width", export_svg.width * upscale_amount)
export_svg.set_attribute("height", export_svg.height * upscale_amount)
var img := Image.new()
- img.load_svg_from_string(SVGParser.root_to_text(export_svg, Formatter.new()))
+ img.load_svg_from_string(SVGParser.root_to_export_text(export_svg))
img.fix_alpha_edges() # See godot issue 82579.
return img
diff --git a/src/utils/ShortcutUtils.gd b/src/utils/ShortcutUtils.gd
index 525e16a..bc0183b 100644
--- a/src/utils/ShortcutUtils.gd
+++ b/src/utils/ShortcutUtils.gd
@@ -6,12 +6,16 @@ const _shortcut_categories_dict: Dictionary[String, Dictionary] = {
"import": true,
"export": true,
"save": true,
+ "save_as": 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,
+ "open_externally": true,
+ "open_in_folder": true,
},
"edit": {
"undo": true,
@@ -76,23 +80,30 @@ static func fn_call(shortcut: String) -> void:
static func fn(shortcut: String) -> Callable:
match shortcut:
"save": return FileUtils.save_svg
+ "save_as": return FileUtils.save_svg_as
"export": return HandlerGUI.open_export
"import": return FileUtils.open_svg_import_dialog
- "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_externally": return FileUtils.open_svg.bind(
+ Configs.savedata.get_active_tab().svg_file_path)
+ "open_in_folder": return FileUtils.open_svg_folder.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,11 +118,13 @@ 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")
+ "save_as": return load("res://assets/icons/Save.svg")
+ "new_tab": return load("res://assets/icons/CreateTab.svg")
"copy_svg_text": return load("res://assets/icons/Copy.svg")
- "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")
+ "open_externally": return load("res://assets/icons/OpenFile.svg")
+ "open_in_folder": return load("res://assets/icons/OpenFolder.svg")
"undo": return load("res://assets/icons/Undo.svg")
"redo": return load("res://assets/icons/Redo.svg")
"duplicate": return load("res://assets/icons/Duplicate.svg")
diff --git a/src/utils/TranslationUtils.gd b/src/utils/TranslationUtils.gd
index 7dd4beb..f8bcfa6 100644
--- a/src/utils/TranslationUtils.gd
+++ b/src/utils/TranslationUtils.gd
@@ -5,19 +5,23 @@ static func get_shortcut_description(action_name: String) -> String:
"export": return Translator.translate("Export")
"import": return Translator.translate("Import")
"save": return Translator.translate("Save")
+ "save_as": return Translator.translate("Save as")
+ "close_tab": return Translator.translate("Close tab")
+ "new_tab": return Translator.translate("Create a new tab")
+ "select_next_tab": return Translator.translate("Select the next tab")
+ "select_previous_tab": return Translator.translate("Select the previous tab")
"optimize": return Translator.translate("Optimize")
"copy_svg_text": return Translator.translate("Copy all text")
"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")
+ "open_externally": return Translator.translate("Open SVG externally")
+ "open_in_folder": return Translator.translate("Show SVG in File Manager")
"undo": return Translator.translate("Undo")
"redo": return Translator.translate("Redo")
- "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/Utils.gd b/src/utils/Utils.gd
index 7f08c88..92d0869 100644
--- a/src/utils/Utils.gd
+++ b/src/utils/Utils.gd
@@ -87,34 +87,6 @@ static func mouse_filter_pass_non_drag_events(event: InputEvent) -> Control.Mous
event.button_mask == MOUSE_BUTTON_MASK_LEFT else Control.MOUSE_FILTER_PASS
-static func generate_gradient(element: Element) -> Gradient:
- if not (element is ElementLinearGradient or element is ElementRadialGradient):
- return null
-
- var gradient := Gradient.new()
- gradient.remove_point(0)
-
- var current_offset := 0.0
- var is_gradient_empty := true
-
- for child in element.get_children():
- if not child is ElementStop:
- continue
-
- current_offset = clamp(child.get_attribute_num("offset"), current_offset, 1.0)
- gradient.add_point(current_offset,
- Color(ColorParser.text_to_color(child.get_attribute_value("stop-color")),
- child.get_attribute_num("stop-opacity")))
- if is_gradient_empty:
- is_gradient_empty = false
- gradient.remove_point(0)
-
- if is_gradient_empty:
- gradient.set_color(0, Color.TRANSPARENT)
-
- return gradient
-
-
static func has_clipboard_web_safe() -> bool:
if OS.has_feature("web"):
return false
diff --git a/src/utils/XNodeChildrenBuilder.gd b/src/utils/XNodeChildrenBuilder.gd
index 87dcc70..67fb2e4 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)