diff --git a/.github/disabled_classes.build b/.github/disabled_classes.build
new file mode 100644
index 0000000..bf04932
--- /dev/null
+++ b/.github/disabled_classes.build
@@ -0,0 +1,154 @@
+{
+ "disabled_build_options": {
+
+ },
+ "disabled_classes": [
+ "AcceptDialog",
+ "AnimatedTexture",
+ "Animation",
+ "AnimationLibrary",
+ "AnimationMixer",
+ "AnimationNode",
+ "AnimationNodeStateMachinePlayback",
+ "AnimationNodeStateMachineTransition",
+ "AspectRatioContainer",
+ "AtlasTexture",
+ "AudioBusLayout",
+ "AudioEffect",
+ "AudioStream",
+ "AudioStreamPlayer",
+ "BaseMaterial3D",
+ "BitMap",
+ "BoneMap",
+ "CameraAttributes",
+ "CameraTexture",
+ "CanvasItemMaterial",
+ "CanvasLayer",
+ "CanvasTexture",
+ "CodeEdit",
+ "CodeHighlighter",
+ "ColorPalette",
+ "ColorPicker",
+ "ColorPickerButton",
+ "Compositor",
+ "CompositorEffect",
+ "CryptoKey",
+ "Curve3D",
+ "CurveTexture",
+ "CurveXYZTexture",
+ "Environment",
+ "ExternalTexture",
+ "FogMaterial",
+ "GDExtension",
+ "GLTFAccessor",
+ "GLTFAnimation",
+ "GLTFBufferView",
+ "GLTFCamera",
+ "GLTFDocument",
+ "GLTFDocumentExtension",
+ "GLTFLight",
+ "GLTFMesh",
+ "GLTFNode",
+ "GLTFPhysicsBody",
+ "GLTFPhysicsShape",
+ "GLTFSkeleton",
+ "GLTFSkin",
+ "GLTFSpecGloss",
+ "GLTFState",
+ "GLTFTexture",
+ "GLTFTextureSampler",
+ "GraphEdit",
+ "GraphElement",
+ "ImporterMesh",
+ "InputEventMIDI",
+ "LabelSettings",
+ "LightmapGIData",
+ "LinkButton",
+ "MenuBar",
+ "MenuButton",
+ "Mesh",
+ "MeshLibrary",
+ "MeshTexture",
+ "MissingNode",
+ "MissingResource",
+ "MultiMesh",
+ "MultiplayerSpawner",
+ "MultiplayerSynchronizer",
+ "NavigationAgent2D",
+ "NavigationAgent3D",
+ "NavigationMesh",
+ "NavigationMeshSourceGeometryData2D",
+ "NavigationMeshSourceGeometryData3D",
+ "NavigationPolygon",
+ "NinePatchRect",
+ "Node2D",
+ "Node3D",
+ "Noise",
+ "NoiseTexture2D",
+ "Occluder3D",
+ "OccluderPolygon2D",
+ "OggPacketSequence",
+ "OpenXRAction",
+ "OpenXRActionMap",
+ "OpenXRActionSet",
+ "OpenXRBindingModifier",
+ "OpenXRBindingModifierEditor",
+ "OpenXRHapticBase",
+ "OpenXRIPBinding",
+ "OpenXRInteractionProfile",
+ "OpenXRInteractionProfileEditorBase",
+ "OptionButton",
+ "PackedDataContainer",
+ "PanoramaSkyMaterial",
+ "ParticleProcessMaterial",
+ "PhysicalSkyMaterial",
+ "PhysicsMaterial",
+ "PlaceholderMaterial",
+ "PlaceholderMesh",
+ "PlaceholderTexture2D",
+ "PolygonPathFinder",
+ "PopupMenu",
+ "PopupPanel",
+ "PortableCompressedTexture2D",
+ "PrimitiveMesh",
+ "ProceduralSkyMaterial",
+ "RDShaderFile",
+ "RDShaderSPIRV",
+ "ReferenceRect",
+ "RichTextEffect",
+ "SceneReplicationConfig",
+ "ScriptExtension",
+ "ShaderInclude",
+ "Shape2D",
+ "Shape3D",
+ "SkeletonModification2D",
+ "SkeletonModificationStack2D",
+ "SkeletonProfile",
+ "Skin",
+ "Sky",
+ "Slider",
+ "SpinBox",
+ "SpriteFrames",
+ "StyleBoxTexture",
+ "TabBar",
+ "Texture2DRD",
+ "Texture3D",
+ "TextureButton",
+ "TextureLayered",
+ "TextureProgressBar",
+ "TileMapPattern",
+ "TileSet",
+ "TileSetSource",
+ "Tree",
+ "VideoStream",
+ "VideoStreamPlayback",
+ "VideoStreamPlayer",
+ "VisualShader",
+ "VisualShaderNode",
+ "VoxelGIData",
+ "World3D",
+ "WorldEnvironment",
+ "X509Certificate"
+ ],
+ "type": "build_profile"
+}
\ No newline at end of file
diff --git a/.github/file_format.sh b/.github/file_format.sh
index 2f90284..a3a0b6f 100644
--- a/.github/file_format.sh
+++ b/.github/file_format.sh
@@ -11,6 +11,8 @@ while IFS= read -rd '' f; do
# Exclude some types of files.
if [[ "$f" == *"svg" ]]; then
continue
+ elif [[ "$f" == *"build" ]]; then
+ continue
fi
# Ensure that files are UTF-8 formatted.
recode UTF-8 "$f" 2> /dev/null
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 6188b82..11e3939 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -9,16 +9,16 @@ on:
env:
# Which godot version to use for exporting.
- GODOT_VERSION: 4.4
+ GODOT_VERSION: 4.4.1
# Which godot release to use for exporting. (stable/rc/beta/alpha)
- GODOT_RELEASE: beta1
+ GODOT_RELEASE: rc1
# Used in the editor config file name. Do not change this for patch releases.
GODOT_FEATURE_VERSION: 4.4
# Commit hash
- GODOT_COMMIT_HASH: d33da79d3
+ GODOT_COMMIT_HASH: daa4b058ee9272dd4ee9033bb093afb21ad558b7
PROJECT_NAME: GodSVG Mobile
+ BUILD_OPTIONS: target=template_release lto=full production=yes deprecated=no minizip=no brotli=no vulkan=no openxr=no use_volk=no disable_3d=yes modules_enabled_by_default=no module_freetype_enabled=yes module_gdscript_enabled=yes module_svg_enabled=yes module_jpg_enabled=yes module_text_server_adv_enabled=yes graphite=no module_webp_enabled=yes swappy=no build_profile=../godsvg/.github/disabled_classes.build
GODOT_REPO: https://github.com/godotengine/godot.git
- BUILD_OPTIONS: target=template_release lto=full production=yes deprecated=no minizip=no brotli=no vulkan=no openxr=no use_volk=no disable_3d=yes modules_enabled_by_default=no module_freetype_enabled=yes module_gdscript_enabled=yes module_svg_enabled=yes module_jpg_enabled=yes module_text_server_adv_enabled=yes graphite=no module_webp_enabled=yes swappy=no
jobs:
format:
@@ -88,6 +88,11 @@ jobs:
git fetch
git checkout $GODOT_COMMIT_HASH
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ path: godsvg
+
- if: ${{ steps.cache-template.outputs.cache-hit != 'true' }}
name: Build Godot template for Android
run: |
@@ -106,11 +111,6 @@ jobs:
mv ./bin/android_debug.apk ~/.local/share/godot/export_templates/${GODOT_VERSION}.${GODOT_RELEASE}/android_release.apk
mv ./bin/android_source.zip ~/.local/share/godot/export_templates/${GODOT_VERSION}.${GODOT_RELEASE}/android_source.zip
- - name: Checkout repository
- uses: actions/checkout@v4
- with:
- path: godsvg
-
- name: Export debug project
env:
GODOT_ANDROID_KEYSTORE_DEBUG_PATH: "./godot_only/debug.keystore"
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index bdf2687..1163015 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -2,7 +2,7 @@
Your contribution is always appreciated!
-Contributions don't need to be perfect, but they must move GodSVG in the right direction. If you are planning to implement a feature or overhaul a system, it's important to write a proposal and discuss your ideas. I will try to quickly accept or decline them. Please do understand that PRs with a large maintenance cost may be under high scrutiny because of their long-term responsibility, even in the absence of the original contributor.
+Contributions don't need to be perfect, but they must move GodSVG in the right direction. If you are planning to implement a feature or overhaul a system, it's important to write a proposal and discuss your ideas first. I will try to be quick with accepting or declining them. Please do understand that PRs with a large maintenance cost may be under high scrutiny because of their long-term responsibility, even in the absence of the original contributor.
## Setup
@@ -19,8 +19,8 @@ Look through the list of issues to see if your contribution would resolve any of
1. Fork the repository.
2. Create a new branch: `git checkout -b implement-gradients`
3. Make your modifications, add them with `git add .`
-4. Commit your changes: `git commit -m "Implement linear gradients"`
-5. Push to the branch: `git push origin implement-gradients`
+4. Commit your changes: `git commit -m "Implement the mask element"`
+5. Push to the branch: `git push origin implement-masks`
6. Create a new pull request with a clear and informative title and describe your changes.
This is the preferred workflow, but tidiness is not as important as work being done, so feel free to do something different you may be comfortable with.
@@ -37,16 +37,16 @@ To document some quirks of our code that we've decided on:
- StringNames are avoided when possible. We do this because it makes the codebase simpler, although if something is actually shown to be performance-critical, it can be reconsidered.
- Nodes may only be exported if their runtime structure isn't known.
-- Translating is done via `TranslationServer.translate()` rather than `tr()`. We've decided to stick to this everywhere because `tr()` doesn't work in static contexts.
+- Strings are always translated with `Translator.translate()`, not `tr()`.
## Code style
For scripts, only GDScript code is allowed. Follow the [GDScript style guide](https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/gdscript_styleguide.html). Most of its rules are enforced here. Additionally:
-- Static typing is predominantly used.
-- Comments are normally written like sentences with punctuation.
-- Two spaces are used to separate code and inline comments.
+- Static typing is used as much as possible.
+- Comments are typically written like sentences with punctuation.
+- Two spaces are used to separate inline comments and code.
- For empty lines in the middle of indented blocks, the scope's indentation is kept.
- Class names use `class_name X extends Y` syntax.
-Don't make pull requests for code style changes without discussing them first (unless it's for corrections to abide by the ones described here). Pull requests may also get production tweaks to fix their style before being merged.
+Don't make pull requests for code style changes without discussing them first (unless it's for corrections to abide by the ones described here). The same generally applies to making style changes unrelated to a PR's main goal. Pull requests may also get production tweaks to tweak their style before being merged.
diff --git a/assets/fonts/Font.ttf.import b/assets/fonts/Font.ttf.import
index 3f9a876..fd23a39 100644
--- a/assets/fonts/Font.ttf.import
+++ b/assets/fonts/Font.ttf.import
@@ -23,6 +23,7 @@ allow_system_fallback=true
force_autohinter=false
hinting=1
subpixel_positioning=1
+keep_rounding_remainders=true
oversampling=0.0
Fallbacks=null
fallbacks=[]
diff --git a/assets/fonts/FontBold.ttf.import b/assets/fonts/FontBold.ttf.import
index 737848c..fe22fe8 100644
--- a/assets/fonts/FontBold.ttf.import
+++ b/assets/fonts/FontBold.ttf.import
@@ -23,6 +23,7 @@ allow_system_fallback=true
force_autohinter=false
hinting=1
subpixel_positioning=1
+keep_rounding_remainders=true
oversampling=0.0
Fallbacks=null
fallbacks=[]
diff --git a/assets/fonts/FontMono.ttf.import b/assets/fonts/FontMono.ttf.import
index f61482f..6787b91 100644
--- a/assets/fonts/FontMono.ttf.import
+++ b/assets/fonts/FontMono.ttf.import
@@ -23,6 +23,7 @@ allow_system_fallback=true
force_autohinter=false
hinting=1
subpixel_positioning=1
+keep_rounding_remainders=true
oversampling=0.0
Fallbacks=null
fallbacks=[]
diff --git a/assets/icons/BWHandle.svg b/assets/icons/BWHandle.svg
index 3d7352e..bf8269c 100644
--- a/assets/icons/BWHandle.svg
+++ b/assets/icons/BWHandle.svg
@@ -1 +1 @@
-
+
\ No newline at end of file
diff --git a/assets/icons/CodeOptions.svg b/assets/icons/CodeOptions.svg
index 0d6a338..040ad83 100644
--- a/assets/icons/CodeOptions.svg
+++ b/assets/icons/CodeOptions.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/icons/Config.svg b/assets/icons/Config.svg
index 12f4344..913e7e6 100644
--- a/assets/icons/Config.svg
+++ b/assets/icons/Config.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/icons/CreateTab.svg b/assets/icons/CreateTab.svg
index 2110ccd..aa7635d 100644
--- a/assets/icons/CreateTab.svg
+++ b/assets/icons/CreateTab.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/icons/Cube.svg b/assets/icons/Cube.svg
index ed09fdf..f735d8b 100644
--- a/assets/icons/Cube.svg
+++ b/assets/icons/Cube.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/icons/Cut.svg b/assets/icons/Cut.svg
index d4d7117..cb3389b 100644
--- a/assets/icons/Cut.svg
+++ b/assets/icons/Cut.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/icons/Debug.svg b/assets/icons/Debug.svg
index 6c2e558..a22942b 100644
--- a/assets/icons/Debug.svg
+++ b/assets/icons/Debug.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/icons/Delete.svg b/assets/icons/Delete.svg
index fbe253e..293ccd3 100644
--- a/assets/icons/Delete.svg
+++ b/assets/icons/Delete.svg
@@ -1 +1 @@
-
+
\ No newline at end of file
diff --git a/assets/icons/DotPatternSegment.svg b/assets/icons/DotPatternSegment.svg
index 374610a..bfff879 100644
--- a/assets/icons/DotPatternSegment.svg
+++ b/assets/icons/DotPatternSegment.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/icons/Export.svg b/assets/icons/Export.svg
index ee331a5..bacb145 100644
--- a/assets/icons/Export.svg
+++ b/assets/icons/Export.svg
@@ -1 +1 @@
-
+
\ No newline at end of file
diff --git a/assets/icons/Folder.svg b/assets/icons/Folder.svg
index 53891da..df31259 100644
--- a/assets/icons/Folder.svg
+++ b/assets/icons/Folder.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/icons/FolderUp.svg b/assets/icons/FolderUp.svg
index d798edf..151dc5c 100644
--- a/assets/icons/FolderUp.svg
+++ b/assets/icons/FolderUp.svg
@@ -1 +1 @@
-
+
\ No newline at end of file
diff --git a/assets/icons/Gear.svg b/assets/icons/Gear.svg
index 3cb6af1..b2ce606 100644
--- a/assets/icons/Gear.svg
+++ b/assets/icons/Gear.svg
@@ -1 +1 @@
-
+
\ No newline at end of file
diff --git a/assets/icons/Import.svg b/assets/icons/Import.svg
index 53081c6..7921525 100644
--- a/assets/icons/Import.svg
+++ b/assets/icons/Import.svg
@@ -1 +1 @@
-
+
\ No newline at end of file
diff --git a/assets/icons/Languages.svg b/assets/icons/Languages.svg
index fd40dd9..b6a51bb 100644
--- a/assets/icons/Languages.svg
+++ b/assets/icons/Languages.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/icons/Minus.svg b/assets/icons/Minus.svg
index 405dd95..77fd2c8 100644
--- a/assets/icons/Minus.svg
+++ b/assets/icons/Minus.svg
@@ -1 +1 @@
-
+
\ No newline at end of file
diff --git a/assets/icons/More.svg b/assets/icons/More.svg
index 198b727..67f8928 100644
--- a/assets/icons/More.svg
+++ b/assets/icons/More.svg
@@ -1 +1 @@
-
+
\ No newline at end of file
diff --git a/assets/icons/MoveDown.svg b/assets/icons/MoveDown.svg
index a496086..e0b71db 100644
--- a/assets/icons/MoveDown.svg
+++ b/assets/icons/MoveDown.svg
@@ -1 +1 @@
-
+
\ No newline at end of file
diff --git a/assets/icons/MoveUp.svg b/assets/icons/MoveUp.svg
index e140099..4066bf1 100644
--- a/assets/icons/MoveUp.svg
+++ b/assets/icons/MoveUp.svg
@@ -1 +1 @@
-
+
\ No newline at end of file
diff --git a/assets/icons/OpenFile.svg b/assets/icons/OpenFile.svg
index c883ca6..93d6a20 100644
--- a/assets/icons/OpenFile.svg
+++ b/assets/icons/OpenFile.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/icons/OpenFolder.svg b/assets/icons/OpenFolder.svg
index d46380d..b5a8769 100644
--- a/assets/icons/OpenFolder.svg
+++ b/assets/icons/OpenFolder.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/icons/Plus.svg b/assets/icons/Plus.svg
index 39f9368..92db187 100644
--- a/assets/icons/Plus.svg
+++ b/assets/icons/Plus.svg
@@ -1 +1 @@
-
+
\ No newline at end of file
diff --git a/assets/icons/PopupArrow.svg b/assets/icons/PopupArrow.svg
new file mode 100644
index 0000000..9cd4d9f
--- /dev/null
+++ b/assets/icons/PopupArrow.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/icons/PopupArrow.svg.import b/assets/icons/PopupArrow.svg.import
new file mode 100644
index 0000000..f88a16c
--- /dev/null
+++ b/assets/icons/PopupArrow.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://crdbyje1tji4h"
+path="res://.godot/imported/PopupArrow.svg-4e1f1544d59b20621caa7c87d9004cd1.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/icons/PopupArrow.svg"
+dest_files=["res://.godot/imported/PopupArrow.svg-4e1f1544d59b20621caa7c87d9004cd1.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/Redo.svg b/assets/icons/Redo.svg
index f2835dc..164f6a8 100644
--- a/assets/icons/Redo.svg
+++ b/assets/icons/Redo.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/icons/Rename.svg b/assets/icons/Rename.svg
index 1596b91..f618fd6 100644
--- a/assets/icons/Rename.svg
+++ b/assets/icons/Rename.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/icons/ScrollBackwards.svg b/assets/icons/ScrollBackwards.svg
new file mode 100644
index 0000000..2562102
--- /dev/null
+++ b/assets/icons/ScrollBackwards.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/icons/ScrollBackwards.svg.import b/assets/icons/ScrollBackwards.svg.import
new file mode 100644
index 0000000..e0362b0
--- /dev/null
+++ b/assets/icons/ScrollBackwards.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://b6wvqmor1sq08"
+path="res://.godot/imported/ScrollBackwards.svg-1ff0ea66adb12029ac78f704d80e6248.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/icons/ScrollBackwards.svg"
+dest_files=["res://.godot/imported/ScrollBackwards.svg-1ff0ea66adb12029ac78f704d80e6248.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/ScrollForwards.svg b/assets/icons/ScrollForwards.svg
new file mode 100644
index 0000000..b8e8c7e
--- /dev/null
+++ b/assets/icons/ScrollForwards.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/icons/ScrollForwards.svg.import b/assets/icons/ScrollForwards.svg.import
new file mode 100644
index 0000000..838e820
--- /dev/null
+++ b/assets/icons/ScrollForwards.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://raxijmbc48q5"
+path="res://.godot/imported/ScrollForwards.svg-6f6937f4ac0b631bdce59141e49cba7f.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/icons/ScrollForwards.svg"
+dest_files=["res://.godot/imported/ScrollForwards.svg-6f6937f4ac0b631bdce59141e49cba7f.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/SmallMore.svg b/assets/icons/SmallMore.svg
index 115d5d4..9821e3d 100644
--- a/assets/icons/SmallMore.svg
+++ b/assets/icons/SmallMore.svg
@@ -1 +1 @@
-
+
\ No newline at end of file
diff --git a/assets/icons/SmallQuestionMark.svg b/assets/icons/SmallQuestionMark.svg
index f7440bc..72e8e0c 100644
--- a/assets/icons/SmallQuestionMark.svg
+++ b/assets/icons/SmallQuestionMark.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/icons/Snap.svg b/assets/icons/Snap.svg
index 55cad27..3e13d62 100644
--- a/assets/icons/Snap.svg
+++ b/assets/icons/Snap.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/icons/TextFile.svg b/assets/icons/TextFile.svg
index 2f77285..b450da7 100644
--- a/assets/icons/TextFile.svg
+++ b/assets/icons/TextFile.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/icons/Undo.svg b/assets/icons/Undo.svg
index a2e880f..9c136a1 100644
--- a/assets/icons/Undo.svg
+++ b/assets/icons/Undo.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/icons/ViewInList.svg b/assets/icons/ViewInList.svg
index ca9c344..befba79 100644
--- a/assets/icons/ViewInList.svg
+++ b/assets/icons/ViewInList.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/icons/Visuals.svg b/assets/icons/Visuals.svg
index bde3885..39d5f46 100644
--- a/assets/icons/Visuals.svg
+++ b/assets/icons/Visuals.svg
@@ -1 +1 @@
-
+
\ No newline at end of file
diff --git a/assets/icons/theme/GuiBoxCheckedDisabled.svg b/assets/icons/theme/GuiBoxCheckedDisabled.svg
index f1cf2e1..8e07b5f 100644
--- a/assets/icons/theme/GuiBoxCheckedDisabled.svg
+++ b/assets/icons/theme/GuiBoxCheckedDisabled.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/godot_only/icons/PanelGrid.svg b/godot_only/icons/PanelGrid.svg
new file mode 100644
index 0000000..6c2cf4c
--- /dev/null
+++ b/godot_only/icons/PanelGrid.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/godot_only/icons/PanelGrid.svg.import b/godot_only/icons/PanelGrid.svg.import
new file mode 100644
index 0000000..9c3141f
--- /dev/null
+++ b/godot_only/icons/PanelGrid.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://oh62m8j2btxk"
+path="res://.godot/imported/PanelGrid.svg-a9ed42ada73bdd9e53d178e4c19a2855.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://godot_only/icons/PanelGrid.svg"
+dest_files=["res://.godot/imported/PanelGrid.svg-a9ed42ada73bdd9e53d178e4c19a2855.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/godot_only/scripts/tests.gd b/godot_only/scripts/tests.gd
index 0b69580..d372a40 100644
--- a/godot_only/scripts/tests.gd
+++ b/godot_only/scripts/tests.gd
@@ -11,7 +11,7 @@ func _run() -> void:
func pathdata_tests(print_success := false) -> bool:
- var tests: Dictionary[String, Array] = {
+ const tests: Dictionary[String, Array] = {
"Jerky": [],
"M 3s 6 h 6 v 3 z": [],
"M 3 s6 h 6 v 3 z": [],
diff --git a/godot_only/scripts/update_translations.gd b/godot_only/scripts/update_translations.gd
index 2c21ae2..06030ed 100644
--- a/godot_only/scripts/update_translations.gd
+++ b/godot_only/scripts/update_translations.gd
@@ -2,7 +2,7 @@
@tool
extends EditorScript
-const TRANSLATIONS_DIR = "assets/translations"
+const TRANSLATIONS_DIR = "translations"
const HEADER = """#, fuzzy
msgid \"\"
@@ -14,8 +14,7 @@ msgstr \"\"
\"Language-Team: \\n\"
\"MIME-Version: 1.0\\n\"
\"Content-Type: text/plain; charset=UTF-8\\n\"
-\"Content-Transfer-Encoding: 8bit\\n\"
-\"X-Generator: Poedit 3.4.2\\n\"\n"""
+\"Content-Transfer-Encoding: 8bit\\n\"\n"""
# Don't have a better solution than handling all these different whitespace variations...
var delimiters: Dictionary[String, String] = {}
@@ -33,17 +32,14 @@ class Message:
# The files this message is in are added as comments. The lines aren't
# kept track of, as they don't provide useful context and change frequently.
var files := PackedStringArray()
- var msgid := String()
+ var msgid := ""
func _init(p_msgid: String, p_files: PackedStringArray):
msgid = p_msgid
files = p_files.duplicate()
func _to_string() -> String:
- var ret := "\n"
- for file in files:
- ret += "#: %s\n" % file
- return ret + 'msgid "%s"\nmsgstr ""\n' % msgid
+ return "\n#: " + "\n#: ".join(files) + '\nmsgid "%s"\nmsgstr ""\n' % msgid
func _run() -> void:
diff --git a/project.godot b/project.godot
index 5005a68..6a367ec 100644
--- a/project.godot
+++ b/project.godot
@@ -18,8 +18,8 @@ config/use_custom_user_dir=true
config/features=PackedStringArray("4.4")
run/low_processor_mode=true
boot_splash/bg_color=Color(0.1065, 0.1181, 0.15, 1)
-boot_splash/image="res://assets/logos/splash.png"
boot_splash/fullsize=false
+boot_splash/image="res://assets/logos/splash.png"
config/icon="res://assets/logos/icon.png"
[audio]
@@ -102,6 +102,18 @@ close_tab={
"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)
]
}
+close_tabs_to_left={
+"deadzone": 0.2,
+"events": []
+}
+close_tabs_to_right={
+"deadzone": 0.2,
+"events": []
+}
+close_all_other_tabs={
+"deadzone": 0.2,
+"events": []
+}
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)
@@ -133,7 +145,7 @@ zoom_out={
]
}
zoom_reset={
-"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":48,"physical_keycode":0,"key_label":0,"unicode":48,"location":0,"echo":false,"script":null)
]
}
@@ -344,7 +356,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")
+locale/translations=PackedStringArray("res://translations/bg.po", "res://translations/de.po", "res://translations/et.po", "res://translations/en.po", "res://translations/fr.po", "res://translations/nl.po", "res://translations/pt_BR.po", "res://translations/ru.po", "res://translations/uk.po", "res://translations/zh_CN.po")
[physics]
diff --git a/src/autoload/Configs.gd b/src/autoload/Configs.gd
index e7eeb1c..53c8ff3 100644
--- a/src/autoload/Configs.gd
+++ b/src/autoload/Configs.gd
@@ -20,7 +20,7 @@ signal handle_visuals_changed
@warning_ignore("unused_signal")
signal shortcut_panel_changed
@warning_ignore("unused_signal")
-signal active_tab_file_path_changed
+signal active_tab_status_changed
@warning_ignore("unused_signal")
signal active_tab_changed
@warning_ignore("unused_signal")
@@ -71,7 +71,6 @@ 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()
diff --git a/src/autoload/HandlerGUI.gd b/src/autoload/HandlerGUI.gd
index 50d32e6..2f9b175 100644
--- a/src/autoload/HandlerGUI.gd
+++ b/src/autoload/HandlerGUI.gd
@@ -1,13 +1,12 @@
extends Node
-# Not a good idea to preload scenes inside a singleton.
-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")
-const UpdateMenu = preload("res://src/ui_parts/update_menu.tscn")
-const ExportMenu = preload("res://src/ui_parts/export_menu.tscn")
+const AlertDialogScene = preload("res://src/ui_widgets/alert_dialog.tscn")
+const ConfirmDialogScene = preload("res://src/ui_widgets/confirm_dialog.tscn")
+const SettingsMenuScene = preload("res://src/ui_parts/settings_menu.tscn")
+const AboutMenuScene = preload("res://src/ui_parts/about_menu.tscn")
+const DonateMenuScene = preload("res://src/ui_parts/donate_menu.tscn")
+const UpdateMenuScene = preload("res://src/ui_parts/update_menu.tscn")
+const ExportMenuScene = preload("res://src/ui_parts/export_menu.tscn")
const ShortcutPanelScene = preload("res://src/ui_parts/shortcut_panel.tscn")
# Menus should be added with add_menu() and removed by being freed.
@@ -25,7 +24,7 @@ func _enter_tree() -> void:
func _ready() -> void:
Configs.active_tab_changed.connect(update_window_title)
- Configs.active_tab_file_path_changed.connect(update_window_title)
+ Configs.active_tab_status_changed.connect(update_window_title)
update_window_title()
Configs.ui_scale_changed.connect(update_ui_scale)
@@ -62,13 +61,14 @@ func _add_control(new_control: Control) -> void:
get_tree().root.propagate_notification(NOTIFICATION_DRAG_END)
remove_all_popups()
- var overlay_ref = ColorRect.new()
+ var overlay_ref := ColorRect.new()
overlay_ref.color = Color(0, 0, 0, 0.4)
overlay_ref.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
menu_stack.append(overlay_ref)
get_tree().root.add_child(overlay_ref)
overlay_ref.add_child(new_control)
new_control.tree_exiting.connect(_remove_control.bind(overlay_ref))
+ throw_mouse_motion_event()
func _remove_control(overlay_ref: ColorRect = null) -> void:
# If an overlay_ref is passed but doesn't match, do nothing.
@@ -92,6 +92,9 @@ func _remove_control(overlay_ref: ColorRect = null) -> void:
throw_mouse_motion_event()
func remove_all_menus() -> void:
+ if menu_stack.is_empty():
+ return
+
while not menu_stack.is_empty():
menu_stack.pop_back().queue_free()
throw_mouse_motion_event()
@@ -112,6 +115,7 @@ func add_popup(new_popup: Control) -> void:
new_popup.reset_size()
new_popup.tree_exiting.connect(remove_popup.bind(overlay_ref))
+ throw_mouse_motion_event()
func remove_popup(overlay_ref: Control = null) -> void:
if popup_stack.is_empty():
@@ -126,6 +130,9 @@ func remove_popup(overlay_ref: Control = null) -> void:
throw_mouse_motion_event()
func remove_all_popups() -> void:
+ if popup_stack.is_empty():
+ return
+
while not popup_stack.is_empty():
popup_stack.pop_back().queue_free()
throw_mouse_motion_event()
@@ -191,7 +198,7 @@ var last_mouse_click_double := false
func _input(event: InputEvent) -> void:
if ShortcutUtils.is_action_pressed(event, "quit"):
remove_all_menus()
- var confirm_dialog = ConfirmDialog.instantiate()
+ var confirm_dialog := ConfirmDialogScene.instantiate()
add_menu(confirm_dialog)
confirm_dialog.setup(Translator.translate("Quit GodSVG"),
Translator.translate("Do you want to quit GodSVG?"),
@@ -208,8 +215,9 @@ func _input(event: InputEvent) -> void:
last_mouse_click_double = false
# 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"]:
+ const CONST_ARR_1: PackedStringArray = ["about_info", "about_donate", "check_updates",
+ "open_settings", "open_externally", "open_in_folder"]
+ for action in CONST_ARR_1:
if ShortcutUtils.is_action_pressed(event, action):
remove_all_menus()
get_viewport().set_input_as_handled()
@@ -217,7 +225,8 @@ func _input(event: InputEvent) -> void:
return
# Stuff that links externally.
- for action in ["about_repo", "about_website"]:
+ const CONST_ARR_2: PackedStringArray = ["about_repo", "about_website"]
+ for action in CONST_ARR_2:
if ShortcutUtils.is_action_pressed(event, action):
get_viewport().set_input_as_handled()
ShortcutUtils.fn_call(action)
@@ -228,8 +237,11 @@ func _input(event: InputEvent) -> void:
return
# Global actions that should happen regardless of the context.
- for action in ["import", "export", "save", "save_as", "close_tab", "new_tab",
- "select_next_tab", "select_previous_tab", "copy_svg_text", "optimize", "reset_svg"]:
+ const CONST_ARR_3: PackedStringArray = ["import", "export", "save", "save_as",
+ "close_tab", "close_tabs_to_left", "close_tabs_to_right", "close_all_other_tabs",
+ "new_tab", "select_next_tab", "select_previous_tab", "copy_svg_text", "optimize",
+ "reset_svg"]
+ for action in CONST_ARR_3:
if ShortcutUtils.is_action_pressed(event, action):
get_viewport().set_input_as_handled()
ShortcutUtils.fn_call(action)
@@ -249,8 +261,9 @@ func _unhandled_input(event: InputEvent) -> void:
get_viewport().gui_is_dragging():
return
- for action in ["redo", "undo", "ui_cancel", "delete", "move_up", "move_down",
- "duplicate", "select_all"]:
+ const CONST_ARR: PackedStringArray = ["redo", "undo", "ui_cancel", "delete", "move_up",
+ "move_down", "duplicate", "select_all"]
+ for action in CONST_ARR:
if ShortcutUtils.is_action_pressed(event, action):
get_viewport().set_input_as_handled()
ShortcutUtils.fn_call(action)
@@ -259,11 +272,50 @@ func _unhandled_input(event: InputEvent) -> void:
State.respond_to_key_input(event)
+func get_window_default_size() -> Vector2i:
+ return Vector2i(ProjectSettings.get_setting("display/window/size/viewport_width"),
+ ProjectSettings.get_setting("display/window/size/viewport_height"))
+
+func get_usable_rect() -> Vector2i:
+ var window := get_window()
+ return Vector2i(DisplayServer.screen_get_usable_rect(
+ DisplayServer.window_get_current_screen()).size -\
+ window.get_size_with_decorations() + window.size)
+
+func get_max_ui_scale() -> float:
+ var usable_screen_size := get_usable_rect()
+ var window_default_size := get_window_default_size()
+ # How much can the default size be increased before it takes all usable screen space.
+ var max_expansion := Vector2(usable_screen_size) / Vector2(window_default_size)
+ return clampf(snappedf(minf(max_expansion.x, max_expansion.y) - 0.025, 0.05), 0.75, 4.0)
+
+func get_min_ui_scale() -> float:
+ return maxf(snappedf(get_max_ui_scale() / 2.0 - 0.125, 0.25), 0.75)
+
+func get_auto_ui_scale() -> float:
+ # Usable rect might not be reliable on web, so attempt to use devicePixelRatio.
+ if OS.get_name() == "Web":
+ var pixel_ratio: float = JavaScriptBridge.eval("window.devicePixelRatio || 1", true)
+ if is_finite(pixel_ratio):
+ return snappedf(pixel_ratio, 0.25)
+
+ var screen_size := get_usable_rect()
+ if screen_size.x == 0 or screen_size.y == 0:
+ return 1.0
+
+ # The wider the screen, the bigger the automatically chosen UI scale.
+ var aspect_ratio := screen_size.aspect()
+ var auto_scale := get_max_ui_scale() * clampf(aspect_ratio * 0.375, 0.6, 0.8)
+ if OS.get_name() == "Android":
+ auto_scale *= 1.1 # Default to giving mobile a bit more space.
+ return clampf(snappedf(auto_scale, 0.25), get_min_ui_scale(), get_max_ui_scale())
+
+
func update_ui_scale() -> void:
var window := get_window()
if not window.is_node_ready():
await window.ready
- if Configs.savedata.auto_ui_scale:
+ if Configs.savedata.ui_scale == SaveData.ScalingApproach.AUTO:
window.content_scale_factor = _calculate_auto_scale()
else:
var final_scale := minf(Configs.savedata.ui_scale, 4.0)
@@ -271,45 +323,61 @@ func update_ui_scale() -> void:
func open_update_checker() -> void:
- var confirmation_dialog = ConfirmDialog.instantiate()
+ var confirmation_dialog := ConfirmDialogScene.instantiate()
add_menu(confirmation_dialog)
confirmation_dialog.setup(Translator.translate("Check for updates?"),
- Translator.translate("This requires GodSVG to connect to the internet."),
+ Translator.translate("This will connect to github.com to compare version numbers. No other data is collected or transmitted."),
Translator.translate("OK"), _list_updates)
func _list_updates() -> void:
remove_all_menus()
- var update_menu_instance = UpdateMenu.instantiate()
+ var update_menu_instance := UpdateMenuScene.instantiate()
add_menu(update_menu_instance)
func open_settings() -> void:
- add_menu(SettingsMenu.instantiate())
+ add_menu(SettingsMenuScene.instantiate())
func open_about() -> void:
- add_menu(AboutMenu.instantiate())
+ add_menu(AboutMenuScene.instantiate())
func open_donate() -> void:
- add_menu(DonateMenu.instantiate())
+ add_menu(DonateMenuScene.instantiate())
func open_export() -> void:
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())
+
+ var dimensions_valid := (is_finite(width) and is_finite(height) and\
+ width > 0.0 and height > 0.0)
+ var dimensions_too_different := false
+
+ if dimensions_valid:
+ dimensions_too_different = (1 / minf(width, height) > 16384 / maxf(width, height))
+ if not dimensions_too_different:
+ add_menu(ExportMenuScene.instantiate())
+ return
+
+ var message: String
+ if dimensions_too_different:
+ message = Translator.translate(
+ "The graphic can be exported only as SVG because its proportions are too extreme.")
else:
- var confirm_dialog = ConfirmDialog.instantiate()
- add_menu(confirm_dialog)
- var svg_export_data := ImageExportData.new()
- confirm_dialog.setup(Translator.translate("Export SVG"), Translator.translate(
- "The graphic can only be exported as SVG because its size is not defined. Do you want to proceed?"),
- Translator.translate("Export"), FileUtils.open_export_dialog.bind(svg_export_data))
+ message = Translator.translate(
+ "The graphic can be exported only as SVG because its size is not defined.")
+ message += "\n\n" + Translator.translate("Do you want to proceed?")
+
+ var confirm_dialog := ConfirmDialogScene.instantiate()
+ add_menu(confirm_dialog)
+ var svg_export_data := ImageExportData.new()
+ confirm_dialog.setup(Translator.translate("Export SVG"), message,
+ Translator.translate("Export"), FileUtils.open_export_dialog.bind(svg_export_data))
func _calculate_auto_scale() -> float:
var dpi := DisplayServer.screen_get_dpi(DisplayServer.window_get_current_screen())
- if dpi <= 120:
- return 0.75 # ldpi
- elif dpi <= 160:
+ #if dpi <= 120:
+ #return 0.75 # ldpi
+ if dpi <= 160:
return 1.0 # mdpi (baseline)
elif dpi <= 240:
return 1.5 # hdpi
@@ -326,8 +394,7 @@ func _calculate_auto_scale() -> float:
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"
+ get_window().title = Configs.savedata.get_active_tab().presented_name + " - GodSVG"
else:
get_window().title = "GodSVG"
diff --git a/src/autoload/State.gd b/src/autoload/State.gd
index 3d22c94..df73122 100644
--- a/src/autoload/State.gd
+++ b/src/autoload/State.gd
@@ -1,10 +1,8 @@
# This singleton handles information that's session-wide, but not saved.
extends Node
-const OptionsDialog = preload("res://src/ui_widgets/options_dialog.tscn")
-const PathCommandPopup = preload("res://src/ui_widgets/path_popup.tscn")
-
-const DEFAULT_SVG = ''
+const OptionsDialogScene = preload("res://src/ui_widgets/options_dialog.tscn")
+const PathCommandPopupScene = preload("res://src/ui_widgets/path_popup.tscn")
const path_actions_dict: Dictionary[String, String] = {
"move_absolute": "M", "move_relative": "m",
@@ -43,8 +41,11 @@ 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,
+# These both differ from the TabData 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.
+# "last_saved_svg_text" is a variable set temporarily when a save is requested, so that
+# any changes made between the request and the deferred sync don't go in the undo stack.
+var last_saved_svg_text := ""
var unstable_svg_text := ""
var svg_text := ""
var root_element := ElementRoot.new()
@@ -55,7 +56,7 @@ var transient_tab_path := "":
if transient_tab_path != new_value:
transient_tab_path = new_value
Configs.tabs_changed.emit()
- Configs.active_tab_file_path_changed.emit()
+ Configs.active_tab_status_changed.emit()
setup_from_tab()
func _enter_tree() -> void:
@@ -88,19 +89,20 @@ func setup_from_tab() -> void:
var new_text := active_tab.get_svg_text()
if not transient_tab_path.is_empty():
- apply_svg_text(DEFAULT_SVG, false)
+ apply_svg_text(TabData.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()):
+ if active_tab.fully_loaded and not active_tab.empty_unsaved and\
+ 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()
+ var options_dialog := OptionsDialogScene.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)
@@ -109,16 +111,16 @@ func setup_from_tab() -> void:
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?").\
+ "The tab is bound to the file path {file_path}. Do you want to restore the SVG 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)
+ apply_svg_text(TabData.DEFAULT_SVG, false)
return
- active_tab.setup_svg_text(DEFAULT_SVG)
+ active_tab.setup_svg_text(TabData.DEFAULT_SVG, active_tab.svg_file_path.is_empty())
sync_elements()
@@ -134,13 +136,17 @@ func _update() -> void:
svg_text = SVGParser.root_to_editor_text(root_element)
svg_changed.emit()
+
# Ensure the save happens after the update.
func queue_svg_save() -> void:
+ _update()
+ last_saved_svg_text = svg_text
_svg_save.call_deferred()
func _svg_save() -> void:
unstable_svg_text = ""
- Configs.savedata.get_active_tab().set_svg_text(svg_text)
+ Configs.savedata.get_active_tab().set_svg_text(last_saved_svg_text)
+ last_saved_svg_text = ""
func sync_elements() -> void:
@@ -191,7 +197,6 @@ func get_export_text() -> String:
signal hover_changed
signal selection_changed
-signal proposed_drop_changed
signal requested_scroll_to_element_editor(xid: PackedInt32Array, inner_idx: int)
@@ -216,10 +221,31 @@ var inner_selections: Array[int] = []
var inner_selection_pivot := -1
# When dragging elements in the inspector.
+var is_xnode_selection_dragged := false
var proposed_drop_xid := PackedInt32Array()
+signal xnode_dragging_state_changed
+signal proposed_drop_changed
+
+func set_selection_dragged(new_value: bool) -> void:
+ if is_xnode_selection_dragged != new_value:
+ is_xnode_selection_dragged = new_value
+ xnode_dragging_state_changed.emit()
+
+func set_proposed_drop_xid(xid: PackedInt32Array) -> void:
+ if proposed_drop_xid != xid:
+ proposed_drop_xid = xid.duplicate()
+ proposed_drop_changed.emit()
+
+func clear_proposed_drop_xid() -> void:
+ if not proposed_drop_xid.is_empty():
+ proposed_drop_xid.clear()
+ proposed_drop_changed.emit()
+
signal zoom_changed
+@warning_ignore("unused_signal")
+signal view_changed
signal viewport_size_changed
var zoom := 0.0
@@ -236,6 +262,30 @@ func set_viewport_size(new_value: Vector2i) -> void:
viewport_size_changed.emit()
+var view_rasterized := false
+var show_grid := true
+var show_handles := true
+
+signal view_rasterized_changed
+signal show_grid_changed
+signal show_handles_changed
+
+func set_view_rasterized(new_value: bool) -> void:
+ if view_rasterized != new_value:
+ view_rasterized = new_value
+ view_rasterized_changed.emit()
+
+func set_show_grid(new_value: bool) -> void:
+ if show_grid != new_value:
+ show_grid = new_value
+ show_grid_changed.emit()
+
+func set_show_handles(new_value: bool) -> void:
+ if show_handles != new_value:
+ show_handles = new_value
+ show_handles_changed.emit()
+
+
# 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:
@@ -255,6 +305,7 @@ func normal_select(xid: PackedInt32Array, inner_idx := -1) -> void:
else:
var old_inner_selections := inner_selections.duplicate()
var old_semi_selected_xid := semi_selected_xid.duplicate()
+ xid = xid.duplicate()
_clear_selection_no_signal()
if semi_selected_xid == xid and\
@@ -361,7 +412,8 @@ func shift_select(xid: PackedInt32Array, inner_idx := -1) -> void:
func select_all() -> void:
_clear_inner_selection_no_signal()
var xnode_list: Array[XNode] = root_element.get_all_xnode_descendants()
- var xid_list: Array = xnode_list.map(func(xnode): return xnode.xid)
+ var xid_list: Array = xnode_list.map(
+ func(xnode: XNode) -> PackedInt32Array: return xnode.xid)
# The order might not be the same, so ensure like this.
if XIDUtils.are_xid_lists_same(xid_list, selected_xids):
return
@@ -489,16 +541,20 @@ func is_selected(xid: PackedInt32Array, inner_idx := -1, propagate := false) ->
else:
return semi_selected_xid == xid and inner_idx in inner_selections
+# Returns whether the selection matches a subpath.
+func is_selection_subpath() -> bool:
+ if semi_selected_xid.is_empty() or inner_selections.is_empty():
+ return false
-func set_proposed_drop_xid(xid: PackedInt32Array) -> void:
- if proposed_drop_xid != xid:
- proposed_drop_xid = xid.duplicate()
- proposed_drop_changed.emit()
+ var element_ref := root_element.get_xnode(semi_selected_xid)
+ if not element_ref is ElementPath:
+ return false
-func clear_proposed_drop_xid() -> void:
- if not proposed_drop_xid.is_empty():
- proposed_drop_xid.clear()
- proposed_drop_changed.emit()
+ var subpath: Vector2i = element_ref.get_attribute("d").get_subpath(inner_selections[0])
+ for i in range(subpath.x, subpath.y):
+ if not i in inner_selections:
+ return false
+ return true
func _on_xnodes_added(xids: Array[PackedInt32Array]) -> void:
@@ -585,7 +641,6 @@ func respond_to_key_input(event: InputEventKey) -> void:
normal_select(selected_xids[0], path_cmd_count)
handle_added.emit()
break
-
return
# If path commands are selected, insert after the last one.
for action_name in path_actions_dict.keys():
@@ -596,8 +651,10 @@ func respond_to_key_input(event: InputEventKey) -> void:
var path_cmd_char := path_actions_dict[action_name]
var last_selection: int = inner_selections.max()
# Z after a Z is syntactically invalid.
- if path_attrib.get_command(last_selection) is PathCommand.CloseCommand and\
- path_cmd_char in "Zz":
+ if path_cmd_char in "Zz" and (path_attrib.get_command(last_selection) is\
+ PathCommand.CloseCommand or (path_attrib.get_command_count() >\
+ last_selection + 1 and path_attrib.get_command(last_selection + 1) is\
+ PathCommand.CloseCommand)):
return
path_attrib.insert_command(last_selection + 1, path_cmd_char, Vector2.ZERO)
normal_select(semi_selected_xid, last_selection + 1)
@@ -628,11 +685,20 @@ func delete_selected() -> void:
queue_svg_save()
func move_up_selected() -> void:
- root_element.move_xnodes_in_parent(selected_xids, false)
- queue_svg_save()
+ _move_selected(false)
func move_down_selected() -> void:
- root_element.move_xnodes_in_parent(selected_xids, true)
+ _move_selected(true)
+
+func _move_selected(down: bool) -> void:
+ if not selected_xids.is_empty():
+ root_element.move_xnodes_in_parent(selected_xids, down)
+ elif not semi_selected_xid.is_empty():
+ var xnode := root_element.get_xnode(semi_selected_xid)
+ if not xnode is ElementPath:
+ return
+ # TODO
+ #xnode.get_attribute("d").move_subpath(inner_selections[0], down)
queue_svg_save()
func view_in_list(xid: PackedInt32Array, inner_index := -1) -> void:
@@ -701,8 +767,9 @@ func get_selection_context(popup_method: Callable, context: Context) -> ContextP
"duplicate"))
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()):
+ if selected_xids.size() == 1 and ((not xnode.is_element() and\
+ xnode.get_type() != BasicXNode.NodeType.UNKNOWN) or (xnode.is_element() and\
+ not xnode.possible_conversions.is_empty())):
btn_arr.append(ContextPopup.create_button(
Translator.translate("Convert To"),
popup_convert_to_context.bind(popup_method), false,
@@ -746,6 +813,20 @@ func get_selection_context(popup_method: Callable, context: Context) -> ContextP
Translator.translate("Convert To"),
popup_convert_to_context.bind(popup_method), false,
load("res://assets/icons/Reload.svg")))
+ if is_selection_subpath():
+ # TODO
+ var can_move_up := false
+ var can_move_down := false
+ if can_move_up:
+ btn_arr.append(ContextPopup.create_button(
+ Translator.translate("Move Up"), # Change to "Move Subpath Up"
+ move_up_selected, false,
+ load("res://visual/icons/MoveUp.svg"), "move_up"))
+ if can_move_down:
+ btn_arr.append(ContextPopup.create_button(
+ Translator.translate("Move Down"), # Change to "Move Subpath Down"
+ move_down_selected, false,
+ load("res://visual/icons/MoveDown.svg"), "move_down"))
"polygon", "polyline":
if inner_selections.size() == 1:
btn_arr.append(ContextPopup.create_button(
@@ -788,12 +869,12 @@ func popup_convert_to_context(popup_method: Callable) -> void:
var selection_idx: int = inner_selections.max()
var cmd_char := path_attrib.get_command(selection_idx).command_char
- var command_picker = PathCommandPopup.instantiate()
+ var command_picker := PathCommandPopupScene.instantiate()
popup_method.call(command_picker)
command_picker.force_relativity(Utils.is_string_lower(cmd_char))
var cmd_char_upper := cmd_char.to_upper()
- var disabled_commands := PackedStringArray()
+ var disabled_commands: PackedStringArray
if selection_idx == 0:
disabled_commands = PackedStringArray(["L", "H", "V", "A", "Z", "Q", "T", "C", "S"])
else:
@@ -812,21 +893,19 @@ func popup_insert_command_after_context(popup_method: Callable) -> void:
var selection_idx: int = inner_selections.max()
var cmd_char := path_attrib.get_command(selection_idx).command_char
- var command_picker = PathCommandPopup.instantiate()
+ var command_picker := PathCommandPopupScene.instantiate()
popup_method.call(command_picker)
command_picker.path_command_picked.connect(insert_path_command_after_selection)
# Disable invalid commands. Z is syntactically invalid, so disallow it even harder.
- var warned_commands := PackedStringArray()
- var disabled_commands := PackedStringArray()
- var disable_z := false
+ var warned_commands: PackedStringArray
+ var disabled_commands: PackedStringArray
match cmd_char.to_upper():
"M": warned_commands = PackedStringArray(["M", "Z", "T"])
- "Z": disable_z = true
"L", "H", "V", "A": warned_commands = PackedStringArray(["S", "T"])
"C", "S": warned_commands = PackedStringArray(["T"])
"Q", "T": warned_commands = PackedStringArray(["S"])
- if disable_z or (path_attrib.get_command_count() > selection_idx + 1 and\
+ if (cmd_char in "Zz") 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"])
diff --git a/src/config_classes/Formatter.gd b/src/config_classes/Formatter.gd
index 05e2a4e..8a0d0b3 100644
--- a/src/config_classes/Formatter.gd
+++ b/src/config_classes/Formatter.gd
@@ -9,54 +9,46 @@ enum PrimaryColorSyntax {THREE_OR_SIX_DIGIT_HEX, SIX_DIGIT_HEX, RGB}
# Elements that don't make sense without child elements.
const container_elements: Array[String] = ["svg", "g", "linearGradient", "radialGradient"]
-
-static func get_preset_enum_text(enum_value: Preset) -> String:
- match enum_value:
- Preset.COMPACT: return Translator.translate("Compact")
- Preset.PRETTY: return Translator.translate("Pretty")
- return ""
-
-static func get_shorthand_tags_enum_text(enum_value: ShorthandTags) -> String:
- match enum_value:
- ShorthandTags.ALWAYS: return Translator.translate("Always")
- ShorthandTags.ALL_EXCEPT_CONTAINERS:
- return Translator.translate("All except containers")
- ShorthandTags.NEVER: return Translator.translate("Never")
- return ""
-
-static func get_named_color_use_enum_text(enum_value: NamedColorUse) -> String:
- match enum_value:
- NamedColorUse.ALWAYS: return Translator.translate("Always")
- NamedColorUse.WHEN_SHORTER_OR_EQUAL:
- return Translator.translate("When shorter or equal")
- NamedColorUse.WHEN_SHORTER: return Translator.translate("When shorter")
- NamedColorUse.NEVER: return Translator.translate("Never")
- return ""
-
-static func get_primary_color_syntax_enum_text(enum_value: PrimaryColorSyntax) -> String:
- match enum_value:
- PrimaryColorSyntax.THREE_OR_SIX_DIGIT_HEX:
- return Translator.translate("3-digit or 6-digit hex")
- PrimaryColorSyntax.SIX_DIGIT_HEX: return Translator.translate("6-digit hex")
- PrimaryColorSyntax.RGB: return "RGB"
- return ""
-
-static func get_enum_texts(property: String) -> PackedStringArray:
- var values := PackedStringArray()
+# TODO Typed Dictionary wonkiness Dictionary[Preset, String]
+static func get_preset_value_text_map() -> Dictionary:
+ return {
+ Preset.COMPACT: Translator.translate("Compact"),
+ Preset.PRETTY: Translator.translate("Pretty"),
+ }
+
+# TODO Typed Dictionary wonkiness Dictionary[ShorthandTags, String]
+static func get_shorthand_tags_value_text_map() -> Dictionary:
+ return {
+ ShorthandTags.ALWAYS: Translator.translate("Always"),
+ ShorthandTags.ALL_EXCEPT_CONTAINERS: Translator.translate("All except containers"),
+ ShorthandTags.NEVER: Translator.translate("Never"),
+ }
+
+# TODO Typed Dictionary wonkiness Dictionary[NamedColorUse, String]
+static func get_named_color_use_value_text_map() -> Dictionary:
+ return {
+ NamedColorUse.ALWAYS: Translator.translate("Always"),
+ NamedColorUse.WHEN_SHORTER_OR_EQUAL: Translator.translate("When shorter or equal"),
+ NamedColorUse.WHEN_SHORTER: Translator.translate("When shorter"),
+ NamedColorUse.NEVER: Translator.translate("Never"),
+ }
+
+# TODO Typed Dictionary wonkiness Dictionary[PrimaryColorSyntax, String]
+static func get_primary_color_syntax_value_text_map() -> Dictionary:
+ return {
+ PrimaryColorSyntax.THREE_OR_SIX_DIGIT_HEX: Translator.translate("3-digit or 6-digit hex"),
+ PrimaryColorSyntax.SIX_DIGIT_HEX: Translator.translate("6-digit hex"),
+ PrimaryColorSyntax.RGB: "RGB",
+ }
+
+# TODO Typed Dictionary wonkiness Dictionary[Variant, String]
+static func get_enum_value_text_map(property: String) -> Dictionary:
match property:
- "preset":
- for key in Preset.keys():
- values.append(get_preset_enum_text(Preset[key]))
- "xml_shorthand_tags":
- for key in ShorthandTags.keys():
- values.append(get_shorthand_tags_enum_text(ShorthandTags[key]))
- "color_use_named_colors":
- for key in NamedColorUse.keys():
- values.append(get_named_color_use_enum_text(NamedColorUse[key]))
- "color_primary_syntax":
- for key in PrimaryColorSyntax.keys():
- values.append(get_primary_color_syntax_enum_text(PrimaryColorSyntax[key]))
- return values
+ "preset": return get_preset_value_text_map()
+ "xml_shorthand_tags": return get_shorthand_tags_value_text_map()
+ "color_use_named_colors": return get_named_color_use_value_text_map()
+ "color_primary_syntax": return get_primary_color_syntax_value_text_map()
+ return {}
func get_setting_default(setting: String) -> Variant:
diff --git a/src/config_classes/SaveData.gd b/src/config_classes/SaveData.gd
index 51adcb2..9f93421 100644
--- a/src/config_classes/SaveData.gd
+++ b/src/config_classes/SaveData.gd
@@ -28,13 +28,13 @@ func get_setting_default(setting: String) -> Variant:
"basic_color_warning": return Color("ee5")
"invert_zoom": return false
- "wrap_mouse": return false
+ "wraparound_panning": return false
"use_ctrl_for_zoom": return true
"use_native_file_dialog": return true
"use_filename_for_window_title": return true
"handle_size": return 1.0 if OS.get_name() != "Android" else 2.0
- "ui_scale": return 1.0
- "auto_ui_scale": return true
+ "ui_scale": return ScalingApproach.AUTO
+ "custom_ui_scale": return true
return null
func reset_to_default() -> void:
@@ -54,6 +54,8 @@ func validate() -> void:
editor_formatter = Formatter.new(Formatter.Preset.PRETTY)
if not is_instance_valid(export_formatter):
export_formatter = Formatter.new(Formatter.Preset.COMPACT)
+ if _active_tab_index >= _tabs.size() or _active_tab_index < 0:
+ _active_tab_index = _active_tab_index # Run the setter.
const CURRENT_VERSION = 1
@@ -199,10 +201,10 @@ const CURRENT_VERSION = 1
invert_zoom = new_value
emit_changed()
-@export var wrap_mouse := false:
+@export var wraparound_panning := false:
set(new_value):
- if wrap_mouse != new_value:
- wrap_mouse = new_value
+ if wraparound_panning != new_value:
+ wraparound_panning = new_value
emit_changed()
@export var use_ctrl_for_zoom := true:
@@ -238,27 +240,19 @@ const HANDLE_SIZE_MAX = 4.0
emit_changed()
Configs.handle_visuals_changed.emit()
-const UI_SCALE_MIN = 0.5
-const UI_SCALE_MAX = 5.0
-@export var ui_scale := 1.0:
+enum ScalingApproach {AUTO, CONSTANT_075, CONSTANT_100, CONSTANT_125, CONSTANT_150,
+ CONSTANT_175, CONSTANT_200, CONSTANT_300, CONSTANT_400, MAX}
+@export var ui_scale := ScalingApproach.AUTO:
set(new_value):
# Validation
- new_value = clampf(new_value, UI_SCALE_MIN, UI_SCALE_MAX)
- if is_nan(new_value):
- new_value = get_setting_default("ui_scale")
+ if not (new_value >= 0 and new_value < ScalingApproach.size()):
+ new_value = ScalingApproach.AUTO
# Main part
if ui_scale != new_value:
ui_scale = new_value
emit_changed()
Configs.ui_scale_changed.emit()
-@export var auto_ui_scale := true:
- set(new_value):
- if auto_ui_scale != new_value:
- auto_ui_scale = new_value
- emit_changed()
- Configs.ui_scale_changed.emit()
-
# Session
const MAX_SNAP = 16384
@@ -277,7 +271,7 @@ const MAX_SNAP = 16384
@export var color_picker_slider_mode := GoodColorPicker.SliderMode.RGB:
set(new_value):
# Validation
- if new_value < 0 || new_value >= GoodColorPicker.SliderMode.size():
+ if not (new_value >= 0 and new_value < GoodColorPicker.SliderMode.size()):
new_value = GoodColorPicker.SliderMode.RGB
# Main part
if color_picker_slider_mode != new_value:
@@ -296,17 +290,6 @@ const MAX_SNAP = 16384
file_dialog_show_hidden = new_value
emit_changed()
-@export var shortcut_panel_layout := ShortcutPanel.Layout.HORIZONTAL_STRIP:
- set(new_value):
- # Validation
- if new_value < 0 || new_value >= ShortcutPanel.Layout.size():
- new_value = ShortcutPanel.Layout.HORIZONTAL_STRIP
- # Main part
- if shortcut_panel_layout != new_value:
- shortcut_panel_layout = new_value
- emit_changed()
- Configs.shortcut_panel_changed.emit()
-
const MAX_RECENT_DIRS = 5
@export var _recent_dirs := PackedStringArray():
@@ -369,10 +352,20 @@ func action_get_shortcuts(action: String) -> Array[InputEvent]:
return Configs.default_shortcuts[action]
func action_modify_shortcuts(action: String, new_events: Array[InputEvent]) -> void:
- if new_events != Configs.default_shortcuts[action]:
- _shortcuts[action] = new_events
+ var are_new_events_default := true
+ if new_events.size() != Configs.default_shortcuts[action].size():
+ are_new_events_default = false
else:
+ for i in new_events.size():
+ if not new_events[i].is_match(Configs.default_shortcuts[action][i]):
+ are_new_events_default = false
+ break
+
+ if are_new_events_default:
_shortcuts.erase(action)
+ else:
+ _shortcuts[action] = new_events
+
_action_sync_inputmap(action)
update_shortcut_validities()
emit_changed()
@@ -391,14 +384,14 @@ func update_shortcut_validities() -> void:
# If the key already exists, set validity to false, otherwise set to true.
_shortcut_validities[shortcut_id] = not shortcut_id in _shortcut_validities
-func is_shortcut_valid(shortcut: InputEvent) -> bool:
- var shortcut_id = shortcut.get_keycode_with_modifiers()
+func is_shortcut_valid(shortcut: InputEventKey) -> bool:
+ var shortcut_id := shortcut.get_keycode_with_modifiers()
if not shortcut_id in _shortcut_validities:
return true
return _shortcut_validities[shortcut_id]
-func get_actions_with_shortcut(shortcut: InputEvent) -> PackedStringArray:
- var shortcut_id = shortcut.get_keycode_with_modifiers()
+func get_actions_with_shortcut(shortcut: InputEventKey) -> PackedStringArray:
+ var shortcut_id := shortcut.get_keycode_with_modifiers()
if not shortcut_id in _shortcut_validities:
return PackedStringArray()
elif _shortcut_validities[shortcut_id]:
@@ -510,6 +503,17 @@ func set_palettes(new_palettes: Array[Palette]) -> void:
export_formatter.changed.connect(emit_changed)
+@export var shortcut_panel_layout := ShortcutPanel.Layout.HORIZONTAL_STRIP:
+ set(new_value):
+ # Validation
+ if not (new_value >= 0 and new_value < ShortcutPanel.Layout.size()):
+ new_value = ShortcutPanel.Layout.HORIZONTAL_STRIP
+ # Main part
+ if shortcut_panel_layout != new_value:
+ shortcut_panel_layout = new_value
+ emit_changed()
+ Configs.shortcut_panel_changed.emit()
+
const SHORTCUT_PANEL_MAX_SLOTS = 6
@export var _shortcut_panel_slots: Dictionary[int, String] = {}:
set(new_value):
@@ -549,13 +553,13 @@ func erase_shortcut_panel_slot(slot: int) -> void:
Configs.shortcut_panel_changed.emit()
-const MAX_TABS = 5
+const MAX_TABS = 50
@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]
+ var tab := new_value[idx]
if not is_instance_valid(tab) or tab.id in used_ids:
new_value.remove_at(idx)
else:
@@ -571,7 +575,7 @@ const MAX_TABS = 5
for tab in _tabs:
tab.changed.connect(emit_changed)
- tab.file_path_changed.connect(_on_tab_file_path_changed.bind(tab.id))
+ tab.status_changed.connect(_on_tab_status_changed.bind(tab.id))
emit_changed()
if _tabs.is_empty():
_add_new_tab()
@@ -590,9 +594,10 @@ const MAX_TABS = 5
_active_tab_index = new_value
emit_changed()
-func _on_tab_file_path_changed(id: int):
+func _on_tab_status_changed(id: int) -> void:
if id == _tabs[_active_tab_index].id:
- Configs.active_tab_file_path_changed.emit()
+ Configs.active_tab_status_changed.emit()
+ Configs.tabs_changed.emit()
func has_tabs() -> bool:
return not _tabs.is_empty()
@@ -638,13 +643,20 @@ func _add_new_tab() -> void:
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
+ break
new_id += 1
+
+ var new_tab := TabData.new(new_id)
+ new_tab.fully_loaded = false
+ new_tab.changed.connect(emit_changed)
+ new_tab.status_changed.connect(_on_tab_status_changed.bind(new_id))
+
+ # Clear file path for the new tab.
+ var new_tab_path := new_tab.get_edited_file_path()
+ if FileAccess.file_exists(new_tab_path):
+ DirAccess.remove_absolute(new_tab_path)
+
+ _tabs.append(new_tab)
func add_empty_tab() -> void:
_add_new_tab()
@@ -664,47 +676,40 @@ func add_tab_with_path(new_file_path: String) -> void:
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():
+func remove_tab(idx: int) -> void:
+ if idx < 0 or idx >= _tabs.size():
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
+ # If there are no tabs in the end, add one.
+ _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 DirAccess.dir_exists_absolute(TabData.EDITED_FILES_DIR):
+ 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)
+ var has_tab_changed := (_active_tab_index == idx)
_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]))
+ remove_tab(_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\
diff --git a/src/config_classes/TabData.gd b/src/config_classes/TabData.gd
index f5b25f1..eb280ac 100644
--- a/src/config_classes/TabData.gd
+++ b/src/config_classes/TabData.gd
@@ -1,12 +1,29 @@
# A resource that keeps track of the tabs.
class_name TabData extends ConfigResource
+var _sync_pending := false
+
+const DEFAULT_SVG = ''
const EDITED_FILES_DIR = "user://edited"
-signal file_path_changed
+signal status_changed
+var presented_name: String:
+ set(new_value):
+ if presented_name != new_value:
+ presented_name = new_value
+ status_changed.emit()
+
+var marked_unsaved := false:
+ set(new_value):
+ if marked_unsaved != new_value:
+ marked_unsaved = new_value
+ status_changed.emit()
+
+var active := false
-var is_new := false
-var undo_redo: UndoRedo
+var fully_loaded := true
+var empty_unsaved := false
+var undo_redo: UndoRedoRef
var reference_image: Texture2D
# This variable represents the saved state of the SVG. Intermediate operations such as
@@ -18,7 +35,7 @@ func set_svg_text(new_text: String) -> void:
return
if not is_instance_valid(undo_redo):
- undo_redo = UndoRedo.new()
+ undo_redo = UndoRedoRef.new()
var old_value := _svg_text
undo_redo.create_action("")
undo_redo.add_do_property(self, "_svg_text", new_text)
@@ -30,15 +47,33 @@ func set_svg_text(new_text: String) -> void:
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)
+ var edited_file_path := get_edited_file_path()
+ if not FileAccess.file_exists(edited_file_path):
+ DirAccess.make_dir_recursive_absolute(edited_file_path.get_base_dir())
+
+ if active:
+ FileAccess.open(edited_file_path, FileAccess.WRITE).store_string(_svg_text)
+ else:
+ var edited_text_parse_result := SVGParser.text_to_root(
+ FileAccess.get_file_as_string(get_edited_file_path()))
+
+ if is_instance_valid(edited_text_parse_result.svg):
+ FileAccess.open(edited_file_path, FileAccess.WRITE).store_string(
+ SVGParser.root_to_export_text(edited_text_parse_result.svg))
+ queue_sync()
+
+func save_to_bound_path() -> void:
+ if Configs.savedata.get_active_tab() != self:
+ return
+ FileAccess.open(svg_file_path, FileAccess.WRITE).store_string(State.get_export_text())
+ queue_sync()
-func setup_svg_text(new_text: String) -> void:
+func setup_svg_text(new_text: String, fully_load := true) -> void:
_svg_text = new_text
State.svg_text = new_text
_save_svg_text()
- is_new = false
+ if fully_load:
+ fully_loaded = true
func get_svg_text() -> String:
return _svg_text
@@ -49,26 +84,43 @@ func get_svg_text() -> String:
if svg_file_path != new_value:
svg_file_path = new_value
emit_changed()
- file_path_changed.emit()
+ queue_sync()
@export var id := -1:
set(new_value):
if id != new_value:
id = new_value
emit_changed()
+ queue_sync()
func _init(new_id := -1) -> void:
id = new_id
+ Configs.language_changed.connect(_on_language_changed)
+ _connect_to_export_formatter_change.call_deferred()
super()
-func get_edited_file_path() -> String:
- return "%s/save%d.svg" % [EDITED_FILES_DIR, id]
+func _connect_to_export_formatter_change() -> void:
+ Configs.savedata.export_formatter.changed.connect(_save_svg_text)
+func get_edited_file_path() -> String:
+ return get_edited_file_path_for_id(id)
+
+static func get_edited_file_path_for_id(checked_id: int) -> String:
+ return "%s/save%d.svg" % [EDITED_FILES_DIR, checked_id]
+
+# Method for showing the file path without stuff like "/home/mewpurpur/".
+# This information is pretty much always unnecessary clutter.
+func get_presented_svg_file_path() -> String:
+ var home_dir: String
+ if OS.get_name() == "Windows":
+ home_dir = OS.get_environment("USERPROFILE")
+ else:
+ home_dir = OS.get_environment("HOME")
+
+ if svg_file_path.begins_with(home_dir):
+ return svg_file_path.trim_prefix(home_dir).trim_prefix("/").trim_prefix("\\")
+ return svg_file_path
-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():
@@ -80,13 +132,56 @@ func redo() -> void:
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 _on_language_changed() -> void:
+ if svg_file_path.is_empty():
+ queue_sync()
+
+func queue_sync() -> void:
+ _sync.call_deferred()
+ _sync_pending = true
+
+func _sync() -> void:
+ if not _sync_pending:
+ return
+ _sync_pending = false
+
+ if not svg_file_path.is_empty():
+ # The extension is included in the presented name too.
+ # It's always in the end anyway so it can't hide useless info.
+ # And also, it prevents ".svg" from being presented as an empty string.
+ presented_name = svg_file_path.get_file()
+ empty_unsaved = false
+
+ if not fully_loaded:
+ marked_unsaved = false
+ else:
+ var edited_text_parse_result := SVGParser.text_to_root(
+ FileAccess.get_file_as_string(get_edited_file_path()))
+
+ if is_instance_valid(edited_text_parse_result.svg):
+ marked_unsaved = FileAccess.get_file_as_string(svg_file_path) !=\
+ SVGParser.root_to_export_text(edited_text_parse_result.svg)
+ else:
+ marked_unsaved = true
+
+ elif SVGParser.text_check_is_root_empty(get_true_svg_text()):
+ empty_unsaved = true
+ marked_unsaved = false
+ presented_name = "[ %s ]" % Translator.translate("Empty")
+ else:
+ empty_unsaved = false
+ marked_unsaved = false
+ presented_name = "[ %s ]" % Translator.translate("Unsaved")
func activate() -> void:
+ active = true
_svg_text = FileAccess.get_file_as_string(get_edited_file_path())
func deactivate() -> void:
+ active = false
_svg_text = ""
+
+func get_true_svg_text() -> String:
+ return _svg_text if active else FileAccess.get_file_as_string(get_edited_file_path())
diff --git a/src/data_classes/AttributeColor.gd b/src/data_classes/AttributeColor.gd
index 28fa6c9..cbae7e3 100644
--- a/src/data_classes/AttributeColor.gd
+++ b/src/data_classes/AttributeColor.gd
@@ -32,12 +32,9 @@ func _format(text: String, formatter: Formatter) -> String:
text[1] == text[2] and text[3] == text[4] and text[5] == text[6]:
text = "#" + text[1] + text[3] + text[5]
Formatter.PrimaryColorSyntax.RGB:
- var new_text := "rgb("
- for i in [1, 3, 5]:
- new_text += String.num_uint64(text.substr(i, 2).hex_to_int())
- if i != 5:
- new_text += ", "
- text = new_text + ")"
+ text = "rgb(" + String.num_uint64(text.substr(1, 2).hex_to_int()) +\
+ ", " + String.num_uint64(text.substr(3, 2).hex_to_int()) +\
+ ", " + String.num_uint64(text.substr(5, 2).hex_to_int()) + ")"
if named_colors_usage != Formatter.NamedColorUse.NEVER:
var hex := text.to_lower()
diff --git a/src/data_classes/AttributeNumeric.gd b/src/data_classes/AttributeNumeric.gd
index ebe7bab..1cc0066 100644
--- a/src/data_classes/AttributeNumeric.gd
+++ b/src/data_classes/AttributeNumeric.gd
@@ -31,7 +31,7 @@ func num_to_text(number: float, formatter := Configs.savedata.editor_formatter)
return NumberParser.num_to_text(number, formatter)
static func text_to_num(text: String) -> float:
- text = text.strip_edges()
+ text = text.strip_edges(false, true)
if text.is_empty():
return NAN
if text.ends_with("%"):
@@ -39,5 +39,4 @@ static func text_to_num(text: String) -> float:
return text.to_float()
static func text_check_percentage(text: String) -> bool:
- # Only the right side needs to be stripped to check for %.
return text.strip_edges(false, true).ends_with("%")
diff --git a/src/data_classes/AttributePathdata.gd b/src/data_classes/AttributePathdata.gd
index 5533ec1..e67cf3c 100644
--- a/src/data_classes/AttributePathdata.gd
+++ b/src/data_classes/AttributePathdata.gd
@@ -2,10 +2,11 @@
class_name AttributePathdata extends Attribute
var _commands: Array[PathCommand]
+var subpath_start_indices: PackedInt32Array
func _sync() -> void:
_commands = parse_pathdata(get_value())
- locate_start_points()
+ parse_properties()
func _format(text: String, formatter: Formatter) -> String:
return path_commands_to_text(parse_pathdata(text), formatter)
@@ -22,21 +23,25 @@ func sync_after_commands_change() -> void:
set_value(path_commands_to_text(_commands))
-func locate_start_points() -> void:
+func parse_properties() -> void:
# Start points are absolute. Individual floats, since 64-bit precision is needed here.
var last_end_point_x := 0.0
var last_end_point_y := 0.0
var curr_subpath_start_x := 0.0
var curr_subpath_start_y := 0.0
- for command: PathCommand in _commands:
+ for idx in _commands.size():
+ var command := _commands[idx]
command.start_x = last_end_point_x
command.start_y = last_end_point_y
if command is PathCommand.MoveCommand:
+ subpath_start_indices.append(idx)
curr_subpath_start_x = command.start_x + command.x if\
command.relative else command.x
curr_subpath_start_y = command.start_y + command.y if\
command.relative else command.y
+ elif idx > 0 and _commands[idx - 1] is PathCommand.CloseCommand:
+ subpath_start_indices.append(idx)
elif command is PathCommand.CloseCommand:
last_end_point_x = curr_subpath_start_x
last_end_point_y = curr_subpath_start_y
@@ -64,19 +69,17 @@ func get_command(idx: int) -> PathCommand:
# Return the start and end indices of the subpath.
func get_subpath(idx: int) -> Vector2i:
var output := Vector2i(idx, idx)
- # Subpaths start from the last M command, or the commmand after the last Z command.
- while output.x > 0:
- if get_command(output.x) is PathCommand.MoveCommand or\
- get_command(output.x - 1) is PathCommand.CloseCommand:
- break
- output.x -= 1
- while output.y < get_command_count() - 1:
- if get_command(output.y + 1) is PathCommand.MoveCommand or\
- get_command(output.y) is PathCommand.CloseCommand:
+ for i in range(subpath_start_indices.size() - 1, -1, -1):
+ if subpath_start_indices[i] <= idx:
+ output.x = subpath_start_indices[i]
+ if i < subpath_start_indices.size() - 1:
+ output.y = subpath_start_indices[i + 1] - 1
+ else:
+ output.y = get_command_count() - 1
break
- output.y += 1
return output
+
func get_implied_S_control(cmd_idx: int) -> Vector2:
var cmd := get_command(cmd_idx)
var prev_cmd := get_command(cmd_idx - 1)
@@ -136,7 +139,7 @@ func insert_command(idx: int, cmd_char: String, vec := Vector2.ZERO) -> void:
if relative:
new_cmd.toggle_relative()
_commands.insert(idx, new_cmd)
- locate_start_points()
+ parse_properties()
if not cmd_char in "Zz":
if not cmd_char in "Vv":
new_cmd.x = vec.x
@@ -163,7 +166,9 @@ func _convert_command(idx: int, cmd_char: String) -> bool:
var cmd_absolute_char := cmd_char.to_upper()
var new_cmd: PathCommand = PathCommand.translation_dict[cmd_absolute_char].new()
- for property in ["x", "y", "x1", "y1", "x2", "y2"]:
+
+ const CONST_ARR: PackedStringArray = ["x", "y", "x1", "y1", "x2", "y2"]
+ for property in CONST_ARR:
if property in old_cmd and property in new_cmd:
new_cmd[property] = old_cmd[property]
@@ -254,7 +259,7 @@ static func pathdata_to_arrays(text: String) -> Array[Array]:
@warning_ignore("shadowed_global_identifier")
var char := text[idx]
# Stop parsing if we've hit a character that's not allowed.
- if not char in "MmLlHhVvAaQqTtCcSsZz0123456789-+e., \n\t\r":
+ if not char in "MmLlHhVvAaQqTtCcSsZz0123456789-+Ee., \n\t\r":
return new_commands
# Logic for finding out what the next command is going to be.
if args_left == 0:
diff --git a/src/data_classes/AttributeTransformList.gd b/src/data_classes/AttributeTransformList.gd
index 629e124..d00e0c6 100644
--- a/src/data_classes/AttributeTransformList.gd
+++ b/src/data_classes/AttributeTransformList.gd
@@ -162,7 +162,8 @@ static func text_to_transform_list(text: String) -> Array[Transform]:
start_idx += 1
end_idx += 1
continue
- if not transform_params.substr(start_idx, idx - start_idx).is_valid_float():
+ if not transform_params.substr(
+ start_idx, idx - start_idx).is_valid_float():
return []
number_proceed = false
",":
@@ -179,8 +180,8 @@ static func text_to_transform_list(text: String) -> Array[Transform]:
idx += 1
exponent_just_passed = true
_:
- if not transform_params.substr(start_idx,
- end_idx - start_idx).is_valid_float():
+ if not transform_params.substr(
+ start_idx, end_idx - start_idx).is_valid_float():
return []
else:
idx -= 1
diff --git a/src/data_classes/ColorParser.gd b/src/data_classes/ColorParser.gd
index fef0efb..a39e9b5 100644
--- a/src/data_classes/ColorParser.gd
+++ b/src/data_classes/ColorParser.gd
@@ -9,11 +9,11 @@ static func add_hash_if_hex(color: String) -> String:
static func is_valid(color: String, allow_alpha := false, allow_url := false,
allow_none := false, allow_current_color := false) -> bool:
- color = color.strip_edges()
return is_valid_hex(color, allow_alpha) or is_valid_rgb(color, allow_alpha) or\
is_valid_hsl(color, allow_alpha) or is_valid_named(color, allow_alpha) or\
- (allow_url and is_valid_url(color)) or (allow_none and color == "none") or\
- (allow_current_color and color == "currentColor")
+ (allow_url and is_valid_url(color)) or (allow_none and\
+ color.strip_edges() == "none") or (allow_current_color and\
+ color.strip_edges() == "currentColor")
static func is_valid_hex(color: String, allow_alpha := false) -> bool:
color = color.strip_edges()
@@ -116,19 +116,19 @@ allow_alpha := false) -> Color:
if not (args.size() == 3 or (args.size() == 4 and allow_alpha)):
return backup
- var a0 := args[0].strip_edges()
- var a1 := args[1].strip_edges()
- var a2 := args[2].strip_edges()
+ var a0 := args[0].strip_edges(false, true)
+ var a1 := args[1].strip_edges(false, true)
+ var a2 := args[2].strip_edges(false, true)
var r := a0.to_int() if _is_valid_number(a0) else int(a0.left(-1).to_float() * 2.55)
var g := a1.to_int() if _is_valid_number(a1) else int(a1.left(-1).to_float() * 2.55)
var b := a2.to_int() if _is_valid_number(a2) else int(a2.left(-1).to_float() * 2.55)
if args.size() == 3:
- return Color8(clampi(r, 0, 255), clampi(g, 0, 255), clampi(b, 0, 255))
+ return Color.from_rgba8(clampi(r, 0, 255), clampi(g, 0, 255), clampi(b, 0, 255))
else:
- var a3 := args[3].strip_edges()
+ var a3 := args[3].strip_edges(false, true)
var a := int(a3.to_float() * 255) if _is_valid_number(a3) else\
int(a3.left(-1).to_float() * 2.55)
- return Color8(clampi(r, 0, 255), clampi(g, 0, 255), clampi(b, 0, 255),
+ return Color.from_rgba8(clampi(r, 0, 255), clampi(g, 0, 255), clampi(b, 0, 255),
clampi(a, 0, 255))
elif is_valid_hsl(color, allow_alpha):
var inside_brackets := color.substr(4, color.length() - 5)
@@ -136,16 +136,16 @@ allow_alpha := false) -> Color:
if not (args.size() == 3 or (args.size() == 4 and allow_alpha)):
return backup
- var h := posmod(args[0].strip_edges().to_int(), 360)
- var s := clampf(int(args[1].strip_edges().left(-1).to_float()) * 0.01, 0.0, 1.0)
- var l := clampf(int(args[2].strip_edges().left(-1).to_float()) * 0.01, 0.0, 1.0)
+ var h := posmod(args[0].to_int(), 360)
+ var s := clampf(int(args[1].strip_edges(false, true).left(-1).to_float()) * 0.01, 0.0, 1.0)
+ var l := clampf(int(args[2].strip_edges(false, true).left(-1).to_float()) * 0.01, 0.0, 1.0)
if args.size() == 3:
- return Color8(hsl_get_r(h, s, l), hsl_get_g(h, s, l), hsl_get_b(h, s, l))
+ return Color.from_rgba8(hsl_get_r(h, s, l), hsl_get_g(h, s, l), hsl_get_b(h, s, l))
else:
- var a3 := args[3].strip_edges()
+ var a3 := args[3].strip_edges(false, true)
var a := int(a3.to_float() * 255) if _is_valid_number(a3) else\
int(a3.left(-1).to_float() * 2.55)
- return Color8(hsl_get_r(h, s, l), hsl_get_g(h, s, l), hsl_get_b(h, s, l),
+ return Color.from_rgba8(hsl_get_r(h, s, l), hsl_get_g(h, s, l), hsl_get_b(h, s, l),
clampi(a, 0, 255))
elif is_valid_hex(color, allow_alpha):
return Color.from_string(color, backup)
diff --git a/src/data_classes/Element.gd b/src/data_classes/Element.gd
index d9f2971..9525162 100644
--- a/src/data_classes/Element.gd
+++ b/src/data_classes/Element.gd
@@ -141,19 +141,21 @@ func get_attribute(attribute_name: String) -> Attribute:
return new_attribute(attribute_name)
-# "real" determines if we want the true value or fallback on defaults.
-func get_attribute_value(attribute_name: String, real := false) -> String:
+func get_attribute_value(attribute_name: String) -> String:
+ if has_attribute(attribute_name):
+ return _attributes[attribute_name].get_value()
+ return ""
+
+func get_implied_attribute_value(attribute_name: String) -> String:
if has_attribute(attribute_name):
return _attributes[attribute_name].get_value()
- if real:
- 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)
+ var attrib_value := get_implied_attribute_value(attribute_name)
if attrib_value == "currentColor":
return get_default("color")
return attrib_value
@@ -233,9 +235,9 @@ func set_attribute(attrib_name: String, value: Variant) -> void:
func get_default(attribute_name: String) -> String:
if attribute_name in DB.propagated_attributes:
if is_parent_g():
- return parent.get_attribute_value(attribute_name)
+ return parent.get_implied_attribute_value(attribute_name)
elif svg != null:
- return svg.get_attribute_value(attribute_name)
+ return svg.get_implied_attribute_value(attribute_name)
return _get_own_default(attribute_name)
func get_all_attributes() -> Array:
@@ -250,8 +252,8 @@ func duplicate(include_children := true) -> Element:
else:
new_element = type.new()
- for attribute in _attributes:
- new_element.set_attribute(attribute, get_attribute_value(attribute))
+ for attribute_name in _attributes:
+ new_element.set_attribute(attribute_name, _attributes[attribute_name].get_value())
if include_children:
for i in get_child_count():
@@ -263,7 +265,7 @@ func apply_to(element: Element, dropped_attributes: PackedStringArray) -> void:
element._child_elements = _child_elements
for attribute_name in _attributes:
if not attribute_name in dropped_attributes:
- element.set_attribute(attribute_name, get_attribute_value(attribute_name))
+ element.set_attribute(attribute_name, _attributes[attribute_name].get_value())
# Converts a percentage numeric attribute to absolute.
# TODO this is no longer used, but might become useful again in the future.
diff --git a/src/data_classes/ElementLinearGradient.gd b/src/data_classes/ElementLinearGradient.gd
index f7b7a91..a1f5b6a 100644
--- a/src/data_classes/ElementLinearGradient.gd
+++ b/src/data_classes/ElementLinearGradient.gd
@@ -13,7 +13,7 @@ func _get_own_default(attribute_name: String) -> String:
_: return ""
func get_percentage_handling(attribute_name: String) -> DB.PercentageHandling:
- if get_attribute_value("gradientUnits") == "objectBoundingBox" and\
+ if get_attribute_value("gradientUnits") in ["objectBoundingBox", ""] and\
attribute_name in ["x1", "y1", "x2", "y2"]:
return DB.PercentageHandling.FRACTION
else:
diff --git a/src/data_classes/ElementPath.gd b/src/data_classes/ElementPath.gd
index fe9a5ea..89766be 100644
--- a/src/data_classes/ElementPath.gd
+++ b/src/data_classes/ElementPath.gd
@@ -75,16 +75,16 @@ func get_bounding_box() -> Rect2:
var b := 6 * j - 6 * i
var c := 3 * i
- var sol_x = solve_quadratic(a.x, b.x, c.x)
+ var sol_x := _solve_quadratic(a.x, b.x, c.x)
for sol in sol_x:
- if sol != null and sol > 0 and sol < 1:
+ if sol > 0 and sol < 1:
var pt := Utils.cubic_bezier_point(cp1.x, cp2.x, cp3.x, cp4.x, sol)
min_x = minf(pt, min_x)
max_x = maxf(pt, max_x)
- var sol_y = solve_quadratic(a.y, b.y, c.y)
+ var sol_y := _solve_quadratic(a.y, b.y, c.y)
for sol in sol_y:
- if sol != null and sol > 0 and sol < 1:
+ if sol > 0 and sol < 1:
var pt := Utils.cubic_bezier_point(cp1.y, cp2.y, cp3.y, cp4.y, sol)
min_y = minf(pt, min_y)
max_y = maxf(pt, max_y)
@@ -166,7 +166,7 @@ func get_bounding_box() -> Rect2:
var rot_h := tan(rot)
var extreme1 := atan2(-r.y * rot_h, r.x)
var extreme2 := atan2(r.y, r.x * rot_h)
- for angle in [extreme1, extreme2, PI + extreme1, PI + extreme2]:
+ for angle: float in [extreme1, extreme2, PI + extreme1, PI + extreme2]:
if (angle < theta1 or angle > theta1 + delta_theta) and\
(angle + TAU < theta1 or angle + TAU > theta1 + delta_theta):
continue
@@ -181,12 +181,12 @@ func get_bounding_box() -> Rect2:
return Rect2(min_x, min_y, max_x - min_x, max_y - min_y)
-static func solve_quadratic(a: float, b: float, c: float) -> Array:
+func _solve_quadratic(a: float, b: float, c: float) -> Array[float]:
if a == 0:
return [-c/b]
- var D = sqrt(b * b - 4 * a * c)
+ var D := sqrt(b * b - 4 * a * c)
if is_nan(D):
- return [null, null]
+ return []
else:
return [(-b + D) / (2 * a), (-b - D) / (2 * a)]
diff --git a/src/data_classes/ElementRadialGradient.gd b/src/data_classes/ElementRadialGradient.gd
index 0ca4012..c90f199 100644
--- a/src/data_classes/ElementRadialGradient.gd
+++ b/src/data_classes/ElementRadialGradient.gd
@@ -12,7 +12,7 @@ func _get_own_default(attribute_name: String) -> String:
_: return ""
func get_percentage_handling(attribute_name: String) -> DB.PercentageHandling:
- if get_attribute_value("gradientUnits") == "objectBoundingBox" and\
+ if get_attribute_value("gradientUnits") in ["objectBoundingBox", ""] and\
attribute_name in ["cx", "cy", "r"]:
return DB.PercentageHandling.FRACTION
else:
diff --git a/src/data_classes/ElementRect.gd b/src/data_classes/ElementRect.gd
index 3caa359..95cd554 100644
--- a/src/data_classes/ElementRect.gd
+++ b/src/data_classes/ElementRect.gd
@@ -62,32 +62,56 @@ func get_replacement(new_element: String) -> Element:
dropped_attributes = PackedStringArray(["x", "y", "width", "height", "rx", "ry",
"d"])
var commands: Array[PathCommand] = []
- if get_rx() == 0 and get_ry() == 0:
+ var rx := get_rx()
+ var ry := get_ry()
+
+ if rx == 0 and ry == 0:
commands.append(PathCommand.MoveCommand.new(x_num, y_num, true))
commands.append(PathCommand.HorizontalLineCommand.new(width_num, true))
commands.append(PathCommand.VerticalLineCommand.new(height_num, true))
commands.append(PathCommand.HorizontalLineCommand.new(-width_num, true))
commands.append(PathCommand.CloseCommand.new(true))
else:
- var w := width_num - get_rx() * 2
- var h := height_num - get_ry() * 2
+ var w := width_num - rx * 2
+ var h := height_num - ry * 2
- commands.append(PathCommand.MoveCommand.new(x_num, y_num + get_ry(), true))
- commands.append(PathCommand.EllipticalArcCommand.new(
- get_rx(), get_ry(), 0, 0, 1, get_rx(), -get_ry(), true))
- if w > 0.0:
+ if w > 0.0 and h > 0.0:
+ commands.append(PathCommand.MoveCommand.new(x_num, y_num + ry, true))
+ commands.append(PathCommand.EllipticalArcCommand.new(
+ rx, ry, 0, 0, 1, rx, -ry, true))
commands.append(PathCommand.HorizontalLineCommand.new(w, true))
- commands.append(PathCommand.EllipticalArcCommand.new(
- get_rx(), get_ry(), 0, 0, 1, get_rx(), get_ry(), true))
- if h > 0.0:
+ commands.append(PathCommand.EllipticalArcCommand.new(
+ rx, ry, 0, 0, 1, rx, ry, true))
commands.append(PathCommand.VerticalLineCommand.new(h, true))
- commands.append(PathCommand.EllipticalArcCommand.new(
- get_rx(), get_ry(), 0, 0, 1, -get_rx(), get_ry(), true))
- if w > 0.0:
+ commands.append(PathCommand.EllipticalArcCommand.new(
+ rx, ry, 0, 0, 1, -rx, ry, true))
commands.append(PathCommand.HorizontalLineCommand.new(-w, true))
- commands.append(PathCommand.EllipticalArcCommand.new(
- get_rx(), get_ry(), 0, 0, 1, -get_rx(), -get_ry(), true))
- commands.append(PathCommand.CloseCommand.new(true))
+ commands.append(PathCommand.EllipticalArcCommand.new(
+ rx, ry, 0, 0, 1, -rx, -ry, true))
+ commands.append(PathCommand.CloseCommand.new(true))
+ elif w > 0.0:
+ commands.append(PathCommand.MoveCommand.new(x_num + rx + w, y_num, true))
+ commands.append(PathCommand.EllipticalArcCommand.new(
+ rx, ry, 0, 0, 1, 0, height_num, true))
+ commands.append(PathCommand.HorizontalLineCommand.new(-w, true))
+ commands.append(PathCommand.EllipticalArcCommand.new(
+ rx, ry, 0, 0, 1, 0, -height_num, true))
+ commands.append(PathCommand.CloseCommand.new(true))
+ elif h > 0.0:
+ commands.append(PathCommand.MoveCommand.new(x_num, y_num + ry, true))
+ commands.append(PathCommand.EllipticalArcCommand.new(
+ rx, ry, 0, 0, 1, width_num, 0, true))
+ commands.append(PathCommand.VerticalLineCommand.new(h, true))
+ commands.append(PathCommand.EllipticalArcCommand.new(
+ rx, ry, 0, 0, 1, -width_num, 0, true))
+ commands.append(PathCommand.CloseCommand.new(true))
+ else:
+ commands.append(PathCommand.MoveCommand.new(x_num + rx, y_num, true))
+ commands.append(PathCommand.EllipticalArcCommand.new(
+ rx, ry, 0, 0, 1, 0, height_num, true))
+ commands.append(PathCommand.EllipticalArcCommand.new(
+ rx, ry, 0, 0, 1, 0, -height_num, true))
+ commands.append(PathCommand.CloseCommand.new(true))
element.set_attribute("d", commands)
apply_to(element, dropped_attributes)
return element
diff --git a/src/data_classes/GradientUtils.gd b/src/data_classes/GradientUtils.gd
index f040527..f340f1f 100644
--- a/src/data_classes/GradientUtils.gd
+++ b/src/data_classes/GradientUtils.gd
@@ -16,8 +16,9 @@ static func generate_gradient(element: Element) -> Gradient:
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")),
+ Color(ColorParser.text_to_color(child.get_attribute_true_color("stop-color")),
child.get_attribute_num("stop-opacity")))
+
if is_gradient_empty:
is_gradient_empty = false
gradient.remove_point(0)
@@ -37,30 +38,42 @@ static func get_gradient_warnings(element: Element) -> 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
+ var prev_offset := -1.0
+ var initial_color := ""
+ var initial_opacity := -1.0
+ var has_effective_transition := false
+
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")
+ var stop_offset := clampf(child.get_attribute_num("offset"), 0.0, 1.0)
+ var stop_opacity := clampf(child.get_attribute_num("stop-opacity"), 0.0, 1.0)
+ var stop_color: String = child.get_attribute_true_color("stop-color")
+
+ if initial_color.is_empty():
+ prev_offset = stop_offset
+ initial_color = stop_color
+ initial_opacity = stop_opacity
+ continue
+
+ # Different color from the initial one (which, even at offset 0, still always
+ # has effect on the stroke). Mark it for having the potential to begin an
+ # effective transition if the next stop offset is greater.
+ has_effective_transition = not (ColorParser.are_colors_same(
+ initial_color, stop_color) and initial_opacity == stop_opacity) and\
+ (initial_opacity != 0 or stop_opacity > 0)
- 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
+ if has_effective_transition and stop_offset > prev_offset:
break
+
+ prev_offset = stop_offset
- if first_stop_color.is_empty():
+ if initial_color.is_empty():
warnings.append(Translator.translate("No elements under this gradient."))
- elif is_solid_color:
+ elif not has_effective_transition:
+ # A potential effective transition being last would still mean it's a real
+ # transition. Even at offset 1, it would still have an effect on the stroke.
warnings.append(Translator.translate("This gradient is a solid color."))
return warnings
diff --git a/src/data_classes/NumstringParser.gd b/src/data_classes/NumstringParser.gd
index 7d25bea..90f6bab 100644
--- a/src/data_classes/NumstringParser.gd
+++ b/src/data_classes/NumstringParser.gd
@@ -40,26 +40,6 @@ static func evaluate(text: String) -> float:
text = text.strip_edges()
text = text.trim_prefix("+") # Expression can't handle unary plus.
- # Expression can't handle exponents with capital E. This contrived logic replaces
- # valid capital E exponents with a lowercase E.
- var exponent := ""
- while text.right(1) in ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]:
- exponent = text.right(1) + exponent
- text = text.left(-1)
- if text.is_empty():
- text = exponent
- else:
- if text.right(1) in ["-", "+"]:
- exponent = text.right(1) + exponent
- text = text.left(-1)
- if text.is_empty():
- text = exponent
- else:
- if text.right(1) == "E":
- text = text.left(-1) + "e" + exponent
- else:
- text = text + exponent
-
var expr := Expression.new()
var err := expr.parse(text.replace(",", "."))
if err == OK:
diff --git a/src/data_classes/PathCommand.gd b/src/data_classes/PathCommand.gd
index cadc7fd..f15b0c6 100644
--- a/src/data_classes/PathCommand.gd
+++ b/src/data_classes/PathCommand.gd
@@ -27,21 +27,33 @@ func toggle_relative() -> void:
if relative:
relative = false
command_char = command_char.to_upper()
- for property in ["x", "x1", "x2"]:
- if property in self:
- set(property, start_x + get(property))
- for property in ["y", "y1", "y2"]:
- if property in self:
- set(property, start_y + get(property))
+ if "x" in self:
+ self.x += start_x
+ if "x1" in self:
+ self.x1 += start_x
+ if "x2" in self:
+ self.x2 += start_x
+ if "y" in self:
+ self.y += start_y
+ if "y1" in self:
+ self.y1 += start_y
+ if "y2" in self:
+ self.y2 += start_y
else:
relative = true
command_char = command_char.to_lower()
- for property in ["x", "x1", "x2"]:
- if property in self:
- set(property, get(property) - start_x)
- for property in ["y", "y1", "y2"]:
- if property in self:
- set(property, get(property) - start_y)
+ if "x" in self:
+ self.x -= start_x
+ if "x1" in self:
+ self.x1 -= start_x
+ if "x2" in self:
+ self.x2 -= start_x
+ if "y" in self:
+ self.y -= start_y
+ if "y1" in self:
+ self.y1 -= start_y
+ if "y2" in self:
+ self.y2 -= start_y
class MoveCommand extends PathCommand:
diff --git a/src/data_classes/SVGParser.gd b/src/data_classes/SVGParser.gd
index 5494561..1614caf 100644
--- a/src/data_classes/SVGParser.gd
+++ b/src/data_classes/SVGParser.gd
@@ -1,5 +1,38 @@
class_name SVGParser extends RefCounted
+# For checking if an SVG is empty. If the text errors out, it's as if the SVG is empty.
+static func text_check_is_root_empty(text: String) -> bool:
+ if text.is_empty():
+ return false
+
+ var parser := XMLParser.new()
+ parser.open_buffer(text.to_utf8_buffer())
+
+ # Ignore everything before the first svg tag.
+ var describes_svg := false
+
+ # Parse the first svg tag that's encountered.
+ while parser.read() == OK:
+ if parser.get_node_type() != XMLParser.NODE_ELEMENT or\
+ parser.get_node_name() != "svg":
+ continue
+
+ describes_svg = true
+
+ var node_offset := parser.get_node_offset()
+ var closure_pos := text.find("/>", node_offset)
+ if closure_pos != -1 and closure_pos <= text.find(">", node_offset):
+ return true
+ break
+
+ if not describes_svg:
+ return false
+
+ if parser.read() == OK:
+ if parser.get_node_type() == XMLParser.NODE_ELEMENT_END:
+ return parser.get_node_name() == "svg"
+ return false
+
# 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:
@@ -8,7 +41,7 @@ custom_height: float, custom_viewbox: Rect2) -> String:
new_root_element.set_attribute("width", custom_width)
new_root_element.set_attribute("height", custom_height)
var text := _xnode_to_editor_text(new_root_element)
- text = text.strip_edges(false, true).left(-6) # Remove the at the end.)
+ text = text.left(maxi(text.find("/>"), text.find(""))) + ">"
for child_idx in root_element.get_child_count():
text += _xnode_to_editor_text(
root_element.get_xnode(PackedInt32Array([child_idx])), true)
@@ -176,7 +209,7 @@ static func text_to_root(text: String) -> ParseResult:
parser.open_buffer(text.to_utf8_buffer())
var unclosed_element_stack: Array[Element] = []
- # Remove everything before the first SVG tag.
+ # Ignore everything before the first SVG tag.
var describes_svg := false
# Parse the first svg tag that's encountered.
diff --git a/src/ui_parts/about_menu.gd b/src/ui_parts/about_menu.gd
index ead84b4..5d6c679 100644
--- a/src/ui_parts/about_menu.gd
+++ b/src/ui_parts/about_menu.gd
@@ -2,112 +2,213 @@ extends PanelContainer
const app_info_json = preload("res://app_info.json")
-@onready var version_label: Label = %VersionLabel
@onready var close_button: Button = $VBoxContainer/CloseButton
-@onready var translations_list: VBoxContainer = %Translations/List
+@onready var translators_vbox: VBoxContainer = %TranslatorsVBox
+@onready var developers_list: PanelGrid = %DevelopersList
-@onready var project_founder_list: PanelGrid = %ProjectFounder/List
-@onready var authors_list: PanelGrid = %Developers/List
-
-@onready var donors_vbox: VBoxContainer = %Donors
-@onready var golden_donors_vbox: VBoxContainer = %GoldenDonors
-@onready var diamond_donors_vbox: VBoxContainer = %DiamondDonors
@onready var donors_list: PanelGrid = %Donors/List
@onready var golden_donors_list: PanelGrid = %GoldenDonors/List
@onready var diamond_donors_list: PanelGrid = %DiamondDonors/List
@onready var past_donors_list: PanelGrid = %PastDonors/List
@onready var past_golden_donors_list: PanelGrid = %PastGoldenDonors/List
@onready var past_diamond_donors_list: PanelGrid = %PastDiamondDonors/List
+@onready var tab_container: TabContainer = $VBoxContainer/TabContainer
func _ready() -> void:
- var app_info: Dictionary = app_info_json.data
- version_label.text = "GodSVG-Mobile v" + ProjectSettings.get_setting("application/config/version")
- project_founder_list.items = app_info.project_founder_and_manager
- project_founder_list.setup()
- authors_list.items = app_info.authors
- authors_list.setup()
- # Once the past donors lists start filling up, they will never unfill, so no need to
- # bother with logic, we can just unhide it manually.
- if app_info.donors.is_empty() and app_info.anonymous_donors == 0:
- donors_vbox.hide()
- else:
- donors_list.items = app_info.donors
- if app_info.anonymous_donors != 0:
- donors_list.dim_last_item = true
- donors_list.items.append("%d anonymous" % app_info.anonymous_donors)
- donors_list.setup()
-
- if app_info.golden_donors.is_empty() and app_info.anonymous_golden_donors == 0:
- golden_donors_vbox.hide()
- else:
- golden_donors_list.items = app_info.golden_donors
- if app_info.anonymous_golden_donors != 0:
- golden_donors_list.dim_last_item = true
- golden_donors_list.items.append("%d anonymous" % app_info.anonymous_golden_donors)
- golden_donors_list.setup()
-
- if app_info.diamond_donors.is_empty() and app_info.anonymous_diamond_donors == 0:
- diamond_donors_vbox.hide()
- else:
- diamond_donors_list.items = app_info.diamond_donors
- if app_info.anonymous_diamond_donors != 0:
- diamond_donors_list.dim_last_item = true
- diamond_donors_list.items.append("%d anonymous" % app_info.anonymous_diamond_donors)
- diamond_donors_list.setup()
-
- past_donors_list.items = app_info.past_donors
- if app_info.past_anonymous_donors != 0:
- past_donors_list.dim_last_item = true
- past_donors_list.items.append("%d anonymous" % app_info.past_anonymous_donors)
- past_donors_list.setup()
-
- past_golden_donors_list.items = app_info.past_golden_donors
- if app_info.past_anonymous_golden_donors != 0:
- past_golden_donors_list.dim_last_item = true
- past_golden_donors_list.items.append("%d anonymous" % app_info.past_anonymous_golden_donors)
- past_golden_donors_list.setup()
+ var stylebox := get_theme_stylebox("panel").duplicate()
+ stylebox.content_margin_top += 2.0
+ add_theme_stylebox_override("panel", stylebox)
- past_donors_list.items = app_info.past_diamond_donors
- if app_info.past_anonymous_diamond_donors != 0:
- past_diamond_donors_list.dim_last_item = true
- past_diamond_donors_list.items.append("%d anonymous" % app_info.past_anonymous_diamond_donors)
- past_diamond_donors_list.setup()
- # There can be multiple translators for a single locale.
- for lang in TranslationServer.get_loaded_locales():
- var credits := TranslationServer.get_translation_object(lang).get_message(
- "translation-credits").split(",", false)
- if credits.is_empty():
- continue
-
- for i in credits.size():
- credits[i] = credits[i].strip_edges()
-
- var label := Label.new()
- label.text = TranslationServer.get_locale_name(lang) + " (%s):" % lang.to_upper()
- translations_list.add_child(label)
- var list := PanelGrid.new()
- list.stylebox = authors_list.stylebox
- list.add_theme_constant_override("h_separation", -1)
- list.add_theme_constant_override("v_separation", -1)
- list.items = credits
- list.setup()
- translations_list.add_child(list)
+ %VersionLabel.text = "GodSVG v" + ProjectSettings.get_setting("application/config/version")
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")
- %Translations/Label.text = Translator.translate("Translators")
- %Donors/Label.text = Translator.translate("Donors")
- %GoldenDonors/Label.text = Translator.translate("Golden donors")
- %DiamondDonors/Label.text = Translator.translate("Diamond donors")
- $VBoxContainer/TabContainer.set_tab_title(0, Translator.translate("Authors"))
- $VBoxContainer/TabContainer.set_tab_title(1, Translator.translate("Donors"))
- $VBoxContainer/TabContainer.set_tab_title(2, Translator.translate("License"))
- $VBoxContainer/TabContainer.set_tab_title(3, Translator.translate(
- "Third-party licenses"))
- %Components.text = Translator.translate("Godot third-party components")
+ tab_container.set_tab_title(0, Translator.translate("Authors"))
+ tab_container.set_tab_title(1, Translator.translate("Donors"))
+ tab_container.set_tab_title(2, Translator.translate("License"))
+ tab_container.set_tab_title(3, Translator.translate("Third-party licenses"))
+ tab_container.tab_changed.connect(_on_tab_changed)
+ _on_tab_changed(0)
-func _on_components_pressed() -> void:
- OS.shell_open("https://github.com/godotengine/godot/blob/master/COPYRIGHT.txt")
+func _on_tab_changed(idx: int) -> void:
+ match idx:
+ 0:
+ var app_info: Dictionary = app_info_json.data
+
+ %ProjectFounderLabel.text = Translator.translate("Project Founder and Manager") +\
+ ": " + app_info.project_founder_and_manager
+ %DevelopersLabel.text = Translator.translate("Developers")
+ %TranslatorsLabel.text = Translator.translate("Translators")
+
+ developers_list.items = app_info.authors
+
+ # There can be multiple translators for a single locale.
+ for locale in TranslationServer.get_loaded_locales():
+ var credits := TranslationServer.get_translation_object(locale).get_message(
+ "translation-credits").split(",", false)
+ if credits.is_empty():
+ continue
+
+ for i in credits.size():
+ credits[i] = credits[i].strip_edges()
+
+ var label := Label.new()
+ label.text = " " + TranslationUtils.get_locale_display(locale)
+ translators_vbox.add_child(label)
+ var list := PanelGrid.new()
+ list.columns = 1
+ list.items = credits
+ translators_vbox.add_child(list)
+ 1:
+ %Donors/Label.text = Translator.translate("Donors")
+ %GoldenDonors/Label.text = Translator.translate("Golden donors")
+ %DiamondDonors/Label.text = Translator.translate("Diamond donors")
+
+ var app_info: Dictionary = app_info_json.data
+ # Once the past donors lists start filling up, they will never unfill,
+ # so no need to bother with logic, we can just unhide it manually.
+ if app_info.donors.is_empty() and app_info.anonymous_donors == 0:
+ %Donors.hide()
+ else:
+ donors_list.items = app_info.donors
+ if app_info.anonymous_donors != 0:
+ donors_list.dim_last_item = true
+ donors_list.items.append("%d anonymous" % app_info.anonymous_donors)
+
+ if app_info.golden_donors.is_empty() and app_info.anonymous_golden_donors == 0:
+ %GoldenDonors.hide()
+ else:
+ golden_donors_list.items = app_info.golden_donors
+ if app_info.anonymous_golden_donors != 0:
+ golden_donors_list.dim_last_item = true
+ golden_donors_list.items.append("%d anonymous" % app_info.anonymous_golden_donors)
+
+ if app_info.diamond_donors.is_empty() and app_info.anonymous_diamond_donors == 0:
+ %DiamondDonors.hide()
+ else:
+ diamond_donors_list.items = app_info.diamond_donors
+ if app_info.anonymous_diamond_donors != 0:
+ diamond_donors_list.dim_last_item = true
+ diamond_donors_list.items.append("%d anonymous" % app_info.anonymous_diamond_donors)
+
+ past_donors_list.items = app_info.past_donors
+ if app_info.past_anonymous_donors != 0:
+ past_donors_list.dim_last_item = true
+ past_donors_list.items.append("%d anonymous" % app_info.past_anonymous_donors)
+
+ past_golden_donors_list.items = app_info.past_golden_donors
+ if app_info.past_anonymous_golden_donors != 0:
+ past_golden_donors_list.dim_last_item = true
+ past_golden_donors_list.items.append("%d anonymous" % app_info.past_anonymous_golden_donors)
+
+ past_donors_list.items = app_info.past_diamond_donors
+ if app_info.past_anonymous_diamond_donors != 0:
+ past_diamond_donors_list.dim_last_item = true
+ past_diamond_donors_list.items.append("%d anonymous" % app_info.past_anonymous_diamond_donors)
+ 2:
+ # This part doesn't need to be translated.
+ var licenses_dict := Engine.get_license_info()
+
+ %LicenseLabel.text = "MIT License\n\nCopyright (c) 2023 MewPurPur\n" +\
+ "Copyright (c) 2023-present GodSVG contributors\n\n" + licenses_dict["Expat"]
+ 3:
+ for child in %GodSVGParts.get_children():
+ child.queue_free()
+ for child in %GodotParts.get_children():
+ child.queue_free()
+ for child in %LicenseTexts.get_children():
+ child.queue_free()
+
+ var godsvg_parts_label := Label.new()
+ godsvg_parts_label.text = "GodSVG parts"
+ var godot_parts_label := Label.new()
+ godot_parts_label.text = "Godot parts"
+ var license_texts_label := Label.new()
+ license_texts_label.text = "License texts"
+ for label: Label in [godsvg_parts_label, godot_parts_label, license_texts_label]:
+ label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
+ label.add_theme_font_size_override("font_size", 16)
+ %GodSVGParts.add_child(godsvg_parts_label)
+ %GodotParts.add_child(godot_parts_label)
+ %LicenseTexts.add_child(license_texts_label)
+
+ # This part doesn't need to be translated.
+ var licenses_dict := Engine.get_license_info()
+ var godot_copyright_info := Engine.get_copyright_info()
+ var godot_engine_copyright: Dictionary
+ for dict in godot_copyright_info:
+ if dict.name == "Godot Engine":
+ dict.parts[0].erase("files")
+ godot_engine_copyright = dict
+ break
+
+ var godsvg_copyright_info: Array[Dictionary] = [
+ godot_engine_copyright,
+ {
+ "name": "Noto Sans font",
+ "parts": [
+ {
+ "copyright": ["2012, Google Inc."],
+ "license": "OFL-1.1",
+ "files": ["res://visual/fonts/Font.ttf", "res://visual/fonts/FontBold.ttf"]
+ }
+ ]
+ },
+ {
+ "name": "JetBrains Mono font",
+ "parts": [
+ {
+ "copyright": ["2020, JetBrains s.r.o."],
+ "license": "OFL-1.1",
+ "files": ["res://visual/fonts/FontMono.ttf"]
+ }
+ ]
+ }
+ ]
+
+ for copyright_info in godsvg_copyright_info:
+ var vbox := VBoxContainer.new()
+ var name_label := Label.new()
+ name_label.add_theme_font_size_override("font_size", 14)
+ name_label.text = copyright_info["name"]
+ vbox.add_child(name_label)
+
+ var label := Label.new()
+ label.add_theme_font_size_override("font_size", 11)
+ for part in copyright_info["parts"]:
+ if part.has("files"):
+ label.text += "Files:\n- %s\n" % "\n- ".join(part["files"])
+ label.text += "© %s\nLicense: %s" % ["\n© ".join(
+ part["copyright"]), part["license"]]
+ vbox.add_child(label)
+ %GodSVGParts.add_child(vbox)
+
+ for copyright_info in godot_copyright_info:
+ var vbox := VBoxContainer.new()
+ var name_label := Label.new()
+ name_label.add_theme_font_size_override("font_size", 14)
+ name_label.text = copyright_info["name"]
+ vbox.add_child(name_label)
+
+ var label := Label.new()
+ label.add_theme_font_size_override("font_size", 11)
+ for part in copyright_info["parts"]:
+ if part.has("files"):
+ label.text += "Files:\n- %s\n" % "\n- ".join(part["files"])
+ label.text += "© %s\nLicense: %s" % ["\n© ".join(
+ part["copyright"]), part["license"]]
+ vbox.add_child(label)
+ %GodotParts.add_child(vbox)
+
+ for license_name in licenses_dict:
+ var license_vbox := VBoxContainer.new()
+ var license_title := Label.new()
+ license_title.text = license_name
+ license_vbox.add_child(license_title)
+ var license_text := Label.new()
+ license_text.add_theme_font_override("font", ThemeUtils.mono_font)
+ license_text.add_theme_font_size_override("font_size", 11)
+ license_text.text = licenses_dict[license_name]
+ license_vbox.add_child(license_text)
+ %LicenseTexts.add_child(license_vbox)
diff --git a/src/ui_parts/about_menu.tscn b/src/ui_parts/about_menu.tscn
index 3cef0e4..f88c0c3 100644
--- a/src/ui_parts/about_menu.tscn
+++ b/src/ui_parts/about_menu.tscn
@@ -1,23 +1,9 @@
-[gd_scene load_steps=8 format=3 uid="uid://mhfp37lr7q4f"]
+[gd_scene load_steps=5 format=3 uid="uid://mhfp37lr7q4f"]
[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" uid="uid://d1wnuyd66tafu" path="res://src/ui_widgets/GridDrawingControl.gd" id="7_nvctb"]
-
-[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_jtvwe"]
-content_margin_left = 6.0
-content_margin_top = 1.0
-content_margin_right = 6.0
-content_margin_bottom = 1.0
-bg_color = Color(0.196078, 0.196078, 0.301961, 1)
-border_width_left = 1
-border_width_top = 1
-border_width_right = 1
-border_width_bottom = 1
-border_color = Color(0.301961, 0.301961, 0.4, 1)
+[ext_resource type="Script" uid="uid://ci44864moadn" path="res://src/ui_widgets/PanelGrid.gd" id="7_nvctb"]
[node name="AboutMenu" type="PanelContainer"]
anchors_preset = 15
@@ -68,175 +54,163 @@ current_tab = 0
layout_mode = 2
metadata/_tab_index = 0
-[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/TabContainer/Authors"]
+[node name="MarginContainer" type="MarginContainer" parent="VBoxContainer/TabContainer/Authors"]
layout_mode = 2
size_flags_horizontal = 3
-theme_override_constants/separation = 8
+theme_override_constants/margin_top = 4
+theme_override_constants/margin_right = 6
+theme_override_constants/margin_bottom = 6
-[node name="ProjectFounder" type="VBoxContainer" parent="VBoxContainer/TabContainer/Authors/VBoxContainer"]
-unique_name_in_owner = true
+[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/TabContainer/Authors/MarginContainer"]
layout_mode = 2
size_flags_horizontal = 3
-theme_override_constants/separation = 6
+theme_override_constants/separation = 8
-[node name="Label" type="Label" parent="VBoxContainer/TabContainer/Authors/VBoxContainer/ProjectFounder"]
+[node name="ProjectFounderLabel" type="Label" parent="VBoxContainer/TabContainer/Authors/MarginContainer/VBoxContainer"]
+unique_name_in_owner = true
layout_mode = 2
+theme_override_font_sizes/font_size = 16
horizontal_alignment = 1
-[node name="List" type="GridContainer" parent="VBoxContainer/TabContainer/Authors/VBoxContainer/ProjectFounder"]
-layout_mode = 2
-theme_override_constants/h_separation = -1
-theme_override_constants/v_separation = -1
-script = ExtResource("7_nvctb")
-stylebox = SubResource("StyleBoxFlat_jtvwe")
-
-[node name="Developers" type="VBoxContainer" parent="VBoxContainer/TabContainer/Authors/VBoxContainer"]
+[node name="DevelopersLabel" type="Label" parent="VBoxContainer/TabContainer/Authors/MarginContainer/VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
-size_flags_horizontal = 3
-theme_override_constants/separation = 6
-
-[node name="Label" type="Label" parent="VBoxContainer/TabContainer/Authors/VBoxContainer/Developers"]
-layout_mode = 2
+theme_override_font_sizes/font_size = 16
horizontal_alignment = 1
-[node name="List" type="GridContainer" parent="VBoxContainer/TabContainer/Authors/VBoxContainer/Developers"]
+[node name="DevelopersList" type="Control" parent="VBoxContainer/TabContainer/Authors/MarginContainer/VBoxContainer"]
+unique_name_in_owner = true
layout_mode = 2
-theme_override_constants/h_separation = -1
-theme_override_constants/v_separation = -1
script = ExtResource("7_nvctb")
-stylebox = SubResource("StyleBoxFlat_jtvwe")
+columns = 2
-[node name="Translations" type="VBoxContainer" parent="VBoxContainer/TabContainer/Authors/VBoxContainer"]
+[node name="TranslatorsLabel" type="Label" parent="VBoxContainer/TabContainer/Authors/MarginContainer/VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
-size_flags_horizontal = 3
-theme_override_constants/separation = 6
-
-[node name="Label" type="Label" parent="VBoxContainer/TabContainer/Authors/VBoxContainer/Translations"]
-layout_mode = 2
+theme_override_font_sizes/font_size = 16
horizontal_alignment = 1
-[node name="List" type="VBoxContainer" parent="VBoxContainer/TabContainer/Authors/VBoxContainer/Translations"]
+[node name="TranslatorsVBox" type="VBoxContainer" parent="VBoxContainer/TabContainer/Authors/MarginContainer/VBoxContainer"]
+unique_name_in_owner = true
layout_mode = 2
+theme_override_constants/separation = 6
[node name="Donors" type="ScrollContainer" parent="VBoxContainer/TabContainer"]
visible = false
layout_mode = 2
metadata/_tab_index = 1
-[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/TabContainer/Donors"]
+[node name="MarginContainer" type="MarginContainer" parent="VBoxContainer/TabContainer/Donors"]
+layout_mode = 2
+size_flags_horizontal = 3
+theme_override_constants/margin_top = 4
+theme_override_constants/margin_right = 6
+theme_override_constants/margin_bottom = 6
+
+[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/TabContainer/Donors/MarginContainer"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_constants/separation = 8
-[node name="DiamondDonors" type="VBoxContainer" parent="VBoxContainer/TabContainer/Donors/VBoxContainer"]
+[node name="DiamondDonors" type="VBoxContainer" parent="VBoxContainer/TabContainer/Donors/MarginContainer/VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
theme_override_constants/separation = 6
-[node name="Label" type="Label" parent="VBoxContainer/TabContainer/Donors/VBoxContainer/DiamondDonors"]
+[node name="Label" type="Label" parent="VBoxContainer/TabContainer/Donors/MarginContainer/VBoxContainer/DiamondDonors"]
layout_mode = 2
+theme_override_font_sizes/font_size = 16
horizontal_alignment = 1
-[node name="List" type="GridContainer" parent="VBoxContainer/TabContainer/Donors/VBoxContainer/DiamondDonors"]
+[node name="List" type="Control" parent="VBoxContainer/TabContainer/Donors/MarginContainer/VBoxContainer/DiamondDonors"]
unique_name_in_owner = true
layout_mode = 2
-theme_override_constants/h_separation = -1
-theme_override_constants/v_separation = -1
script = ExtResource("7_nvctb")
-stylebox = SubResource("StyleBoxFlat_jtvwe")
+columns = 2
-[node name="GoldenDonors" type="VBoxContainer" parent="VBoxContainer/TabContainer/Donors/VBoxContainer"]
+[node name="GoldenDonors" type="VBoxContainer" parent="VBoxContainer/TabContainer/Donors/MarginContainer/VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
theme_override_constants/separation = 6
-[node name="Label" type="Label" parent="VBoxContainer/TabContainer/Donors/VBoxContainer/GoldenDonors"]
+[node name="Label" type="Label" parent="VBoxContainer/TabContainer/Donors/MarginContainer/VBoxContainer/GoldenDonors"]
layout_mode = 2
+theme_override_font_sizes/font_size = 16
horizontal_alignment = 1
-[node name="List" type="GridContainer" parent="VBoxContainer/TabContainer/Donors/VBoxContainer/GoldenDonors"]
+[node name="List" type="Control" parent="VBoxContainer/TabContainer/Donors/MarginContainer/VBoxContainer/GoldenDonors"]
layout_mode = 2
-theme_override_constants/h_separation = -1
-theme_override_constants/v_separation = -1
-columns = 2
script = ExtResource("7_nvctb")
-stylebox = SubResource("StyleBoxFlat_jtvwe")
+columns = 2
-[node name="Donors" type="VBoxContainer" parent="VBoxContainer/TabContainer/Donors/VBoxContainer"]
+[node name="Donors" type="VBoxContainer" parent="VBoxContainer/TabContainer/Donors/MarginContainer/VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
theme_override_constants/separation = 6
-[node name="Label" type="Label" parent="VBoxContainer/TabContainer/Donors/VBoxContainer/Donors"]
+[node name="Label" type="Label" parent="VBoxContainer/TabContainer/Donors/MarginContainer/VBoxContainer/Donors"]
layout_mode = 2
+theme_override_font_sizes/font_size = 16
horizontal_alignment = 1
-[node name="List" type="GridContainer" parent="VBoxContainer/TabContainer/Donors/VBoxContainer/Donors"]
+[node name="List" type="Control" parent="VBoxContainer/TabContainer/Donors/MarginContainer/VBoxContainer/Donors"]
layout_mode = 2
-theme_override_constants/h_separation = -1
-theme_override_constants/v_separation = -1
script = ExtResource("7_nvctb")
-stylebox = SubResource("StyleBoxFlat_jtvwe")
+columns = 2
-[node name="PastDiamondDonors" type="VBoxContainer" parent="VBoxContainer/TabContainer/Donors/VBoxContainer"]
+[node name="PastDiamondDonors" type="VBoxContainer" parent="VBoxContainer/TabContainer/Donors/MarginContainer/VBoxContainer"]
unique_name_in_owner = true
visible = false
layout_mode = 2
size_flags_horizontal = 3
theme_override_constants/separation = 6
-[node name="Label" type="Label" parent="VBoxContainer/TabContainer/Donors/VBoxContainer/PastDiamondDonors"]
+[node name="Label" type="Label" parent="VBoxContainer/TabContainer/Donors/MarginContainer/VBoxContainer/PastDiamondDonors"]
layout_mode = 2
+theme_override_font_sizes/font_size = 16
horizontal_alignment = 1
-[node name="List" type="GridContainer" parent="VBoxContainer/TabContainer/Donors/VBoxContainer/PastDiamondDonors"]
+[node name="List" type="Control" parent="VBoxContainer/TabContainer/Donors/MarginContainer/VBoxContainer/PastDiamondDonors"]
layout_mode = 2
-theme_override_constants/h_separation = -1
-theme_override_constants/v_separation = -1
script = ExtResource("7_nvctb")
-stylebox = SubResource("StyleBoxFlat_jtvwe")
+columns = 2
-[node name="PastGoldenDonors" type="VBoxContainer" parent="VBoxContainer/TabContainer/Donors/VBoxContainer"]
+[node name="PastGoldenDonors" type="VBoxContainer" parent="VBoxContainer/TabContainer/Donors/MarginContainer/VBoxContainer"]
unique_name_in_owner = true
visible = false
layout_mode = 2
size_flags_horizontal = 3
theme_override_constants/separation = 6
-[node name="Label" type="Label" parent="VBoxContainer/TabContainer/Donors/VBoxContainer/PastGoldenDonors"]
+[node name="Label" type="Label" parent="VBoxContainer/TabContainer/Donors/MarginContainer/VBoxContainer/PastGoldenDonors"]
layout_mode = 2
+theme_override_font_sizes/font_size = 16
horizontal_alignment = 1
-[node name="List" type="GridContainer" parent="VBoxContainer/TabContainer/Donors/VBoxContainer/PastGoldenDonors"]
+[node name="List" type="Control" parent="VBoxContainer/TabContainer/Donors/MarginContainer/VBoxContainer/PastGoldenDonors"]
layout_mode = 2
-theme_override_constants/h_separation = -1
-theme_override_constants/v_separation = -1
-columns = 2
script = ExtResource("7_nvctb")
-stylebox = SubResource("StyleBoxFlat_jtvwe")
+columns = 2
-[node name="PastDonors" type="VBoxContainer" parent="VBoxContainer/TabContainer/Donors/VBoxContainer"]
+[node name="PastDonors" type="VBoxContainer" parent="VBoxContainer/TabContainer/Donors/MarginContainer/VBoxContainer"]
unique_name_in_owner = true
visible = false
layout_mode = 2
size_flags_horizontal = 3
theme_override_constants/separation = 6
-[node name="Label" type="Label" parent="VBoxContainer/TabContainer/Donors/VBoxContainer/PastDonors"]
+[node name="Label" type="Label" parent="VBoxContainer/TabContainer/Donors/MarginContainer/VBoxContainer/PastDonors"]
layout_mode = 2
+theme_override_font_sizes/font_size = 16
horizontal_alignment = 1
-[node name="List" type="GridContainer" parent="VBoxContainer/TabContainer/Donors/VBoxContainer/PastDonors"]
+[node name="List" type="Control" parent="VBoxContainer/TabContainer/Donors/MarginContainer/VBoxContainer/PastDonors"]
layout_mode = 2
-theme_override_constants/h_separation = -1
-theme_override_constants/v_separation = -1
script = ExtResource("7_nvctb")
-stylebox = SubResource("StyleBoxFlat_jtvwe")
+columns = 2
[node name="License" type="ScrollContainer" parent="VBoxContainer/TabContainer"]
visible = false
@@ -244,222 +218,50 @@ layout_mode = 2
size_flags_vertical = 3
metadata/_tab_index = 2
-[node name="LicenseLabel" type="Label" parent="VBoxContainer/TabContainer/License"]
+[node name="MarginContainer" type="MarginContainer" parent="VBoxContainer/TabContainer/License"]
+layout_mode = 2
+size_flags_horizontal = 3
+theme_override_constants/margin_left = 2
+theme_override_constants/margin_top = 6
+theme_override_constants/margin_right = 2
+
+[node name="LicenseLabel" type="Label" parent="VBoxContainer/TabContainer/License/MarginContainer"]
+unique_name_in_owner = true
layout_mode = 2
theme_override_fonts/font = ExtResource("3_e8i1t")
-theme_override_font_sizes/font_size = 10
-text = "MIT License
-
-Copyright (c) 2025 Anish Mishra
-Copyright (c) 2023 MewPurPur
-Copyright (c) 2023-present GodSVG contributors
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the \"Software\"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE."
+theme_override_font_sizes/font_size = 11
[node name="Third-party Licenses" type="ScrollContainer" parent="VBoxContainer/TabContainer"]
visible = false
layout_mode = 2
metadata/_tab_index = 3
-[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/TabContainer/Third-party Licenses"]
+[node name="MarginContainer" type="MarginContainer" parent="VBoxContainer/TabContainer/Third-party Licenses"]
layout_mode = 2
size_flags_horizontal = 3
-theme_override_constants/separation = 20
-alignment = 1
-
-[node name="ThirdParties" type="VBoxContainer" parent="VBoxContainer/TabContainer/Third-party Licenses/VBoxContainer"]
-layout_mode = 2
-theme_override_constants/separation = 12
+theme_override_constants/margin_top = 4
+theme_override_constants/margin_right = 6
+theme_override_constants/margin_bottom = 6
-[node name="Godot" type="VBoxContainer" parent="VBoxContainer/TabContainer/Third-party Licenses/VBoxContainer/ThirdParties"]
+[node name="ThirdParties" type="VBoxContainer" parent="VBoxContainer/TabContainer/Third-party Licenses/MarginContainer"]
layout_mode = 2
+theme_override_constants/separation = 18
-[node name="Label" type="Label" parent="VBoxContainer/TabContainer/Third-party Licenses/VBoxContainer/ThirdParties/Godot"]
-layout_mode = 2
-theme_override_fonts/font = ExtResource("4_n6gp0")
-text = "Godot Engine"
-horizontal_alignment = 1
-
-[node name="License" type="Label" parent="VBoxContainer/TabContainer/Third-party Licenses/VBoxContainer/ThirdParties/Godot"]
-layout_mode = 2
-theme_override_fonts/font = ExtResource("3_e8i1t")
-theme_override_font_sizes/font_size = 12
-text = "License: Expat"
-horizontal_alignment = 1
-
-[node name="Components" type="Button" parent="VBoxContainer/TabContainer/Third-party Licenses/VBoxContainer/ThirdParties/Godot"]
+[node name="GodSVGParts" type="VBoxContainer" parent="VBoxContainer/TabContainer/Third-party Licenses/MarginContainer/ThirdParties"]
unique_name_in_owner = true
layout_mode = 2
-size_flags_horizontal = 4
-focus_mode = 0
-mouse_default_cursor_shape = 2
-theme_override_font_sizes/font_size = 12
-icon = ExtResource("6_hbk78")
-
-[node name="NotoSans" type="VBoxContainer" parent="VBoxContainer/TabContainer/Third-party Licenses/VBoxContainer/ThirdParties"]
-layout_mode = 2
-theme_override_constants/separation = 3
-
-[node name="Label" type="Label" parent="VBoxContainer/TabContainer/Third-party Licenses/VBoxContainer/ThirdParties/NotoSans"]
-layout_mode = 2
-theme_override_fonts/font = ExtResource("4_n6gp0")
-text = "Noto Sans font"
-horizontal_alignment = 1
-
-[node name="Copy" type="Label" parent="VBoxContainer/TabContainer/Third-party Licenses/VBoxContainer/ThirdParties/NotoSans"]
-layout_mode = 2
-theme_override_font_sizes/font_size = 12
-text = "© 2012, Google Inc."
-horizontal_alignment = 1
-
-[node name="Files" type="Label" parent="VBoxContainer/TabContainer/Third-party Licenses/VBoxContainer/ThirdParties/NotoSans"]
-layout_mode = 2
-theme_override_fonts/font = ExtResource("3_e8i1t")
-theme_override_font_sizes/font_size = 12
-text = "res://visual/fonts/Font.ttf
-res://visual/fonts/FontBold.ttf"
-horizontal_alignment = 1
-
-[node name="License" type="Label" parent="VBoxContainer/TabContainer/Third-party Licenses/VBoxContainer/ThirdParties/NotoSans"]
-layout_mode = 2
-theme_override_fonts/font = ExtResource("3_e8i1t")
-theme_override_font_sizes/font_size = 12
-text = "License: OFL-1.1"
-horizontal_alignment = 1
-
-[node name="JetbrainsMono" type="VBoxContainer" parent="VBoxContainer/TabContainer/Third-party Licenses/VBoxContainer/ThirdParties"]
-layout_mode = 2
-theme_override_constants/separation = 3
-
-[node name="Label" type="Label" parent="VBoxContainer/TabContainer/Third-party Licenses/VBoxContainer/ThirdParties/JetbrainsMono"]
-layout_mode = 2
-theme_override_fonts/font = ExtResource("4_n6gp0")
-text = "JetBrains Mono font"
-horizontal_alignment = 1
-
-[node name="Copy" type="Label" parent="VBoxContainer/TabContainer/Third-party Licenses/VBoxContainer/ThirdParties/JetbrainsMono"]
-layout_mode = 2
-theme_override_font_sizes/font_size = 12
-text = "© 2020, JetBrains s.r.o"
-horizontal_alignment = 1
-
-[node name="File" type="Label" parent="VBoxContainer/TabContainer/Third-party Licenses/VBoxContainer/ThirdParties/JetbrainsMono"]
-layout_mode = 2
-theme_override_fonts/font = ExtResource("3_e8i1t")
-theme_override_font_sizes/font_size = 12
-text = "res://visual/fonts/FontMono.ttf"
-horizontal_alignment = 1
+theme_override_constants/separation = 8
-[node name="License" type="Label" parent="VBoxContainer/TabContainer/Third-party Licenses/VBoxContainer/ThirdParties/JetbrainsMono"]
+[node name="GodotParts" type="VBoxContainer" parent="VBoxContainer/TabContainer/Third-party Licenses/MarginContainer/ThirdParties"]
+unique_name_in_owner = true
layout_mode = 2
-theme_override_fonts/font = ExtResource("3_e8i1t")
-theme_override_font_sizes/font_size = 12
-text = "License: OFL-1.1"
-horizontal_alignment = 1
+theme_override_constants/separation = 8
-[node name="LicenseTexts" type="VBoxContainer" parent="VBoxContainer/TabContainer/Third-party Licenses/VBoxContainer"]
+[node name="LicenseTexts" type="VBoxContainer" parent="VBoxContainer/TabContainer/Third-party Licenses/MarginContainer/ThirdParties"]
+unique_name_in_owner = true
layout_mode = 2
theme_override_constants/separation = 12
-[node name="Expat" type="VBoxContainer" parent="VBoxContainer/TabContainer/Third-party Licenses/VBoxContainer/LicenseTexts"]
-layout_mode = 2
-theme_override_constants/separation = 5
-
-[node name="Title" type="Label" parent="VBoxContainer/TabContainer/Third-party Licenses/VBoxContainer/LicenseTexts/Expat"]
-layout_mode = 2
-theme_override_fonts/font = ExtResource("4_n6gp0")
-text = "Expat license text"
-horizontal_alignment = 1
-
-[node name="Text" type="Label" parent="VBoxContainer/TabContainer/Third-party Licenses/VBoxContainer/LicenseTexts/Expat"]
-layout_mode = 2
-theme_override_fonts/font = ExtResource("3_e8i1t")
-theme_override_font_sizes/font_size = 10
-text = "Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the \"Software\"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE."
-
-[node name="OFL" type="VBoxContainer" parent="VBoxContainer/TabContainer/Third-party Licenses/VBoxContainer/LicenseTexts"]
-layout_mode = 2
-theme_override_constants/separation = 5
-
-[node name="Title" type="Label" parent="VBoxContainer/TabContainer/Third-party Licenses/VBoxContainer/LicenseTexts/OFL"]
-layout_mode = 2
-theme_override_fonts/font = ExtResource("4_n6gp0")
-text = "OFL-1.1 license text"
-horizontal_alignment = 1
-
-[node name="Text" type="Label" parent="VBoxContainer/TabContainer/Third-party Licenses/VBoxContainer/LicenseTexts/OFL"]
-layout_mode = 2
-theme_override_fonts/font = ExtResource("3_e8i1t")
-theme_override_font_sizes/font_size = 10
-text = "PREAMBLE
-
-The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others.
-
-The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives.
-
-DEFINITIONS
-
-\"Font Software\" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation.
-
-\"Reserved Font Name\" refers to any names specified as such after the copyright statement(s).
-
-\"Original Version\" refers to the collection of Font Software components as distributed by the Copyright Holder(s).
-
-\"Modified Version\" refers to any derivative made by adding to, deleting, or substituting — in part or in whole — any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment.
-
-\"Author\" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software.
-
-PERMISSION & CONDITIONS
-
-Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions:
-
- 1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself.
- 2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user.
- 3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users.
- 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission.
- 5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software.
-
-TERMINATION
-
-This license becomes null and void if any of the above conditions are not met.
-
-DISCLAIMER
-
-THE FONT SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE."
-autowrap_mode = 3
-
[node name="CloseButton" type="Button" parent="VBoxContainer"]
layout_mode = 2
size_flags_horizontal = 4
@@ -467,5 +269,3 @@ size_flags_vertical = 8
focus_mode = 0
mouse_default_cursor_shape = 2
text = "Close"
-
-[connection signal="pressed" from="VBoxContainer/TabContainer/Third-party Licenses/VBoxContainer/ThirdParties/Godot/Components" to="." method="_on_components_pressed"]
diff --git a/src/ui_widgets/code_editor.gd b/src/ui_parts/code_editor.gd
similarity index 85%
rename from src/ui_widgets/code_editor.gd
rename to src/ui_parts/code_editor.gd
index 4097d3b..8ec4d3d 100644
--- a/src/ui_widgets/code_editor.gd
+++ b/src/ui_parts/code_editor.gd
@@ -9,9 +9,9 @@ extends VBoxContainer
func _ready() -> void:
Configs.theme_changed.connect(setup_theme)
+ setup_theme()
State.parsing_finished.connect(update_error)
Configs.highlighting_colors_changed.connect(update_syntax_highlighter)
- setup_theme()
update_syntax_highlighter()
code_edit.clear_undo_history()
State.svg_changed.connect(auto_update_text)
@@ -26,25 +26,20 @@ func update_error(err_id: SVGParser.ParseError) -> void:
if err_id == SVGParser.ParseError.OK:
if error_bar.visible:
error_bar.hide()
- var error_bar_real_height := error_bar.size.y - 2
- code_edit.custom_minimum_size.y += error_bar_real_height
- code_edit.size.y += error_bar_real_height
setup_theme()
else:
# When the error is shown, the code editor's theme is changed to match up.
if not error_bar.visible:
error_bar.show()
error_label.text = SVGParser.get_error_string(err_id)
- var error_bar_real_height := error_bar.size.y - 2
- code_edit.custom_minimum_size.y -= error_bar_real_height
- code_edit.size.y -= error_bar_real_height
setup_theme()
func setup_theme() -> void:
# Set up the code edit.
code_edit.begin_bulk_theme_override()
- for theming in ["normal", "focus", "hover"]:
- var stylebox := get_theme_stylebox(theming, "TextEdit").duplicate()
+ const CONST_ARR_1: PackedStringArray = ["normal", "focus", "hover"]
+ for theme_type in CONST_ARR_1:
+ var stylebox := get_theme_stylebox(theme_type, "TextEdit").duplicate()
stylebox.corner_radius_top_right = 0
stylebox.corner_radius_top_left = 0
stylebox.border_width_top = 2
@@ -52,15 +47,16 @@ func setup_theme() -> void:
stylebox.corner_radius_bottom_right = 0
stylebox.corner_radius_bottom_left = 0
stylebox.border_width_bottom = 1
- code_edit.add_theme_stylebox_override(theming, stylebox)
+ code_edit.add_theme_stylebox_override(theme_type, stylebox)
code_edit.end_bulk_theme_override()
# Make it so the scrollbar doesn't overlap with the code editor's border.
var scrollbar := code_edit.get_v_scroll_bar()
scrollbar.begin_bulk_theme_override()
- for theming in ["grabber", "grabber_highlight", "grabber_pressed"]:
- var stylebox := get_theme_stylebox(theming, "VScrollBar").duplicate()
+ const CONST_ARR_2: PackedStringArray = ["normal", "focus", "hover"]
+ for theme_type in CONST_ARR_2:
+ var stylebox := get_theme_stylebox(theme_type, "VScrollBar").duplicate()
stylebox.expand_margin_right = -2.0
- scrollbar.add_theme_stylebox_override(theming, stylebox)
+ scrollbar.add_theme_stylebox_override(theme_type, stylebox)
var bg_stylebox := get_theme_stylebox("scroll", "VScrollBar").duplicate()
bg_stylebox.expand_margin_right = -2.0
bg_stylebox.content_margin_left += 1.0
diff --git a/src/ui_widgets/code_editor.gd.uid b/src/ui_parts/code_editor.gd.uid
similarity index 100%
rename from src/ui_widgets/code_editor.gd.uid
rename to src/ui_parts/code_editor.gd.uid
diff --git a/src/ui_widgets/code_editor.tscn b/src/ui_parts/code_editor.tscn
similarity index 68%
rename from src/ui_widgets/code_editor.tscn
rename to src/ui_parts/code_editor.tscn
index 4d735b1..a49c4e5 100644
--- a/src/ui_widgets/code_editor.tscn
+++ b/src/ui_parts/code_editor.tscn
@@ -1,43 +1,18 @@
-[gd_scene load_steps=7 format=3 uid="uid://cr1fdlmbknnko"]
+[gd_scene load_steps=5 format=3 uid="uid://cr1fdlmbknnko"]
-[ext_resource type="Script" uid="uid://c3q5dvxm6ro1m" path="res://src/ui_widgets/code_editor.gd" id="1_nffk0"]
+[ext_resource type="Script" uid="uid://c3q5dvxm6ro1m" path="res://src/ui_parts/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://dthdjf4v2vlvg" path="res://assets/icons/CodeOptions.svg" id="4_sos04"]
-[ext_resource type="Script" uid="uid://d1mpyxtnqqxh0" path="res://src/ui_widgets/BetterTextEdit.gd" id="8_ser4i"]
-
-[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_q56qh"]
-content_margin_left = 8.0
-content_margin_top = 3.0
-content_margin_right = 6.0
-content_margin_bottom = 1.0
-bg_color = Color(0.0975, 0.0975, 0.15, 1)
-border_width_left = 2
-border_width_top = 2
-border_width_right = 2
-border_color = Color(0.152941, 0.152941, 0.2, 1)
-corner_radius_top_left = 5
-corner_radius_top_right = 5
-
-[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_k0een"]
-content_margin_left = 10.0
-content_margin_right = 8.0
-bg_color = Color(0.0975, 0.0975, 0.15, 1)
-border_width_left = 2
-border_width_top = 1
-border_width_right = 2
-border_width_bottom = 2
-border_color = Color(0.1875, 0.1875, 0.25, 1)
-corner_radius_bottom_right = 5
-corner_radius_bottom_left = 5
+[ext_resource type="Script" uid="uid://dh5mir6i27u4u" path="res://src/ui_widgets/BetterTextEdit.gd" id="8_ser4i"]
[node name="CodeEditor" type="VBoxContainer"]
+custom_minimum_size = Vector2(0, 120)
size_flags_vertical = 3
theme_override_constants/separation = 0
script = ExtResource("1_nffk0")
[node name="PanelContainer" type="PanelContainer" parent="."]
layout_mode = 2
-theme_override_styles/panel = SubResource("StyleBoxFlat_q56qh")
[node name="CodeButtons" type="HBoxContainer" parent="PanelContainer"]
layout_mode = 2
@@ -71,7 +46,6 @@ script = ExtResource("8_ser4i")
[node name="ErrorBar" type="PanelContainer" parent="ScriptEditor"]
visible = false
layout_mode = 2
-theme_override_styles/panel = SubResource("StyleBoxFlat_k0een")
[node name="Label" type="RichTextLabel" parent="ScriptEditor/ErrorBar"]
custom_minimum_size = Vector2(0, 20)
diff --git a/src/ui_parts/current_file_button.gd b/src/ui_parts/current_file_button.gd
new file mode 100644
index 0000000..afc817b
--- /dev/null
+++ b/src/ui_parts/current_file_button.gd
@@ -0,0 +1,49 @@
+extends Button
+
+func _ready() -> void:
+ Configs.active_tab_status_changed.connect(update_file_button)
+ Configs.active_tab_changed.connect(update_file_button)
+ pressed.connect(_on_file_button_pressed)
+ update_file_button()
+
+func _make_custom_tooltip(_for_text: String) -> Object:
+ var file_path := Configs.savedata.get_active_tab().get_presented_svg_file_path()
+ if file_path.is_empty():
+ return null
+
+ var label := Label.new()
+ label.add_theme_font_override("font", ThemeUtils.mono_font)
+ label.add_theme_font_size_override("font_size", 12)
+ label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
+ label.text = file_path
+ Utils.set_max_text_width(label, 192.0, 4.0)
+ return label
+
+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, size.x, -1, PackedInt32Array([2]))
+ HandlerGUI.popup_under_rect_center(context_popup, get_global_rect(), get_viewport())
+
+func update_file_button() -> void:
+ var transient_tab_path := State.transient_tab_path
+ text = transient_tab_path.get_file() if not transient_tab_path.is_empty() else\
+ Configs.savedata.get_active_tab().presented_name
+ Utils.set_max_text_width(self, 140.0, 12.0)
diff --git a/src/ui_parts/current_file_button.gd.uid b/src/ui_parts/current_file_button.gd.uid
new file mode 100644
index 0000000..527b5b2
--- /dev/null
+++ b/src/ui_parts/current_file_button.gd.uid
@@ -0,0 +1 @@
+uid://41g64ussxcbn
diff --git a/src/ui_parts/display.gd b/src/ui_parts/display.gd
index 47cd64a..f257a71 100644
--- a/src/ui_parts/display.gd
+++ b/src/ui_parts/display.gd
@@ -1,22 +1,13 @@
extends VBoxContainer
-signal view_settings_updated(show_grid: bool, show_handles: bool, rasterized_svg: bool)
-signal snap_settings_updated(snap_enabled: bool, snap_amount: float)
-
-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_widgets/confirm_dialog.tscn")
+const NumberEdit = preload("res://src/ui_widgets/number_edit.gd")
@onready var viewport: SubViewport = %Viewport
-@onready var controls: Control = %Viewport/Controls
-@onready var grid_visuals: Control = %Viewport/Camera
-@onready var reference_texture = %Viewport/ReferenceTexture
-@onready var reference_button = %LeftMenu/Reference
+@onready var reference_texture: TextureRect = %Viewport/ReferenceTexture
+@onready var reference_button: Button = %LeftMenu/Reference
@onready var visuals_button: Button = %LeftMenu/Visuals
-@onready var snapper: NumberEditType = %LeftMenu/Snapping/SnapNumberEdit
-@onready var snap_button: BetterToggleButtonType = %LeftMenu/Snapping/SnapButton
+@onready var snapper: NumberEdit = %LeftMenu/Snapping/SnapNumberEdit
+@onready var snap_button: BetterToggleButton = %LeftMenu/Snapping/SnapButton
@onready var viewport_panel: PanelContainer = $ViewportPanel
@onready var debug_container: MarginContainer = $ViewportPanel/DebugMargins
@onready var debug_label: Label = %DebugContainer/DebugLabel
@@ -29,13 +20,11 @@ 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)
+ Configs.active_tab_changed.connect(sync_reference_image)
update_translations()
update_theme()
update_snap_config()
get_window().window_input.connect(_update_input_debug)
- view_settings_updated.emit(grid_visuals.visible, controls.visible,
- viewport.display_texture.rasterized)
func _unhandled_input(event: InputEvent) -> void:
if Input.is_action_pressed("debug"):
@@ -89,10 +78,6 @@ func update_snap_config() -> void:
snap_button.button_pressed = snap_enabled
snapper.editable = snap_enabled
snapper.set_value(absf(snap_config))
- snap_settings_updated.emit(snap_enabled, absf(snap_config))
-
-func update_reference_image() -> void:
- apply_reference(Configs.savedata.get_active_tab().reference_image)
func _on_reference_pressed() -> void:
@@ -114,12 +99,11 @@ func _on_reference_pressed() -> void:
func _on_visuals_button_pressed() -> void:
var btn_arr: Array[Button] = [
ContextPopup.create_checkbox(Translator.translate("Show grid"),
- toggle_grid_visuals, grid_visuals.visible, "view_show_grid"),
+ toggle_grid_visuals, State.show_grid, "view_show_grid"),
ContextPopup.create_checkbox(Translator.translate("Show handles"),
- toggle_handles_visuals, controls.visible, "view_show_handles"),
+ toggle_handles_visuals, State.show_handles, "view_show_handles"),
ContextPopup.create_checkbox(Translator.translate("Rasterized SVG"),
- toggle_rasterization, viewport.display_texture.rasterized,
- "view_rasterized_svg")]
+ toggle_rasterization, State.view_rasterized, "view_rasterized_svg")]
var visuals_popup := ContextPopup.new()
visuals_popup.setup(btn_arr, true)
@@ -128,19 +112,13 @@ func _on_visuals_button_pressed() -> void:
func toggle_grid_visuals() -> void:
- grid_visuals.visible = not grid_visuals.visible
- view_settings_updated.emit(grid_visuals.visible, controls.visible,
- viewport.display_texture.rasterized)
+ State.set_show_grid(not State.show_grid)
func toggle_handles_visuals() -> void:
- controls.visible = not controls.visible
- view_settings_updated.emit(grid_visuals.visible, controls.visible,
- viewport.display_texture.rasterized)
+ State.set_show_handles(not State.show_handles)
func toggle_rasterization() -> void:
- viewport.display_texture.rasterized = not viewport.display_texture.rasterized
- view_settings_updated.emit(grid_visuals.visible, controls.visible,
- viewport.display_texture.rasterized)
+ State.set_view_rasterized(not State.view_rasterized)
func toggle_reference_image() -> void:
reference_texture.visible = not reference_texture.visible
@@ -161,13 +139,15 @@ func finish_reference_import(data: Variant, file_path: String) -> void:
"webp": img.load_webp_from_buffer(data)
var image_texture := ImageTexture.create_from_image(img)
Configs.savedata.get_active_tab().reference_image = image_texture
- apply_reference(image_texture)
+ sync_reference_image()
-func apply_reference(reference: Texture2D) -> void:
+func sync_reference_image() -> void:
+ var reference := Configs.savedata.get_active_tab().reference_image
if is_instance_valid(reference):
reference_texture.texture = reference
reference_texture.show()
else:
+ reference_texture.texture = null
reference_texture.hide()
func toggle_snap() -> void:
diff --git a/src/ui_parts/display.tscn b/src/ui_parts/display.tscn
index c6e7c36..6f062e6 100644
--- a/src/ui_parts/display.tscn
+++ b/src/ui_parts/display.tscn
@@ -4,12 +4,12 @@
[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="Script" uid="uid://rqrxhe8wa6fn" path="res://src/ui_parts/tab_bar.gd" id="9_rll1m"]
-[ext_resource type="Shader" uid="uid://ki2mjb6y33jl" path="res://src/shaders/zoom_shader.gdshader" id="10_x7ybk"]
+[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"]
@@ -151,7 +151,6 @@ 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
diff --git a/src/ui_parts/display_texture.gd b/src/ui_parts/display_texture.gd
index f13b953..cd84a63 100644
--- a/src/ui_parts/display_texture.gd
+++ b/src/ui_parts/display_texture.gd
@@ -6,20 +6,18 @@ var view_rect := Rect2():
view_rect = new_value
queue_update()
-var rasterized := false:
- set(new_value):
- if new_value != rasterized:
- rasterized = new_value
- if State.zoom != 1.0:
- queue_update()
-
var _update_pending := false
func _ready() -> void:
State.svg_changed.connect(queue_update)
State.zoom_changed.connect(queue_update)
+ State.view_rasterized_changed.connect(_on_view_rasterized_changed)
queue_update()
+func _on_view_rasterized_changed() -> void:
+ if State.zoom != 1.0:
+ queue_update()
+
func queue_update() -> void:
_update.call_deferred()
@@ -31,7 +29,7 @@ func _update() -> void:
_update_pending = false
- var image_zoom := 1.0 if rasterized and State.zoom > 1.0 else State.zoom
+ var image_zoom := 1.0 if State.view_rasterized and State.zoom > 1.0 else State.zoom
var pixel_size := 1 / image_zoom
# Translate to canvas coords.
diff --git a/src/ui_parts/donate_menu.gd b/src/ui_parts/donate_menu.gd
index 94c020b..2873bf3 100644
--- a/src/ui_parts/donate_menu.gd
+++ b/src/ui_parts/donate_menu.gd
@@ -1,42 +1,81 @@
extends PanelContainer
+# This overengineered menu mostly has logic around not hiding the text as you move your
+# mouse between the buttons. The text stays unless the mouse moves too far away from
+# the specific button that showed the current list of pros/cons.
+
+enum Link {NONE, GITHUB, KO_FI, PATREON}
+var current_link := Link.NONE
+
@onready var clarifications: RichTextLabel = %Clarifications
@onready var close_button: Button = $VBoxContainer/CloseButton
+@onready var margin_container: MarginContainer = %MarginContainer
+@onready var github_link: Button = %MarginContainer/HBoxContainer/GithubLink
+@onready var ko_fi_link: Button = %MarginContainer/HBoxContainer/KoFiLink
+@onready var patreon_link: Button = %MarginContainer/HBoxContainer/PatreonLink
+@onready var hbox: HBoxContainer = %MarginContainer/HBoxContainer
func _ready() -> void:
close_button.text = Translator.translate("Cancel")
close_button.pressed.connect(queue_free)
- reset_clarifications()
-
-func _on_github_link_pressed() -> void:
- OS.shell_open("https://github.com/sponsors/MewPurPur")
-
-func _on_ko_fi_link_pressed() -> void:
- OS.shell_open("https://ko-fi.com/mewpurpur")
-
-func _on_patreon_link_pressed() -> void:
- OS.shell_open("https://www.patreon.com/GodSVG")
-
+ set_link(Link.NONE)
+
+ github_link.pressed.connect(OS.shell_open.bind("https://github.com/sponsors/MewPurPur"))
+ ko_fi_link.pressed.connect(OS.shell_open.bind("https://ko-fi.com/mewpurpur"))
+ patreon_link.pressed.connect(OS.shell_open.bind("https://www.patreon.com/GodSVG"))
+ github_link.mouse_entered.connect(set_link.bind(Link.GITHUB))
+ ko_fi_link.mouse_entered.connect(set_link.bind(Link.KO_FI))
+ patreon_link.mouse_entered.connect(set_link.bind(Link.PATREON))
+ margin_container.gui_input.connect(_on_margin_container_gui_input)
+ margin_container.mouse_exited.connect(set_link.bind(Link.NONE))
-# Shouldn't be localized.
-func _on_github_link_mouse_entered() -> void:
- show_description("Guthub Sponsors", PackedStringArray(["Low extra fees",
- "Can donate an arbitrary amount", "Includes all perks"]))
+func _on_margin_container_gui_input(event: InputEvent) -> void:
+ if not event is InputEventMouseMotion:
+ return
+
+ if current_link == Link.NONE:
+ return
+
+ var hbox_pos := hbox.position
+
+ if (current_link == Link.GITHUB and not Rect2(hbox_pos + github_link.position - Vector2(13, 13),
+ github_link.size + Vector2(26, 26)).has_point(event.position)) or\
+ (current_link == Link.KO_FI and not Rect2(hbox_pos + ko_fi_link.position - Vector2(13, 13),
+ ko_fi_link.size + Vector2(26, 26)).has_point(event.position)) or\
+ (current_link == Link.PATREON and not Rect2(hbox_pos + patreon_link.position - Vector2(13, 13),
+ patreon_link.size + Vector2(26, 26)).has_point(event.position)):
+ set_link(Link.NONE)
-func _on_ko_fi_link_mouse_entered() -> void:
- show_description("Ko-Fi", PackedStringArray(["Low extra fees",
- "Can donate an arbitrary amount"]), PackedStringArray(["No perks"]))
-func _on_patreon_link_mouse_entered() -> void:
- show_description("Patreon", PackedStringArray(["Includes all perks"]),
- PackedStringArray(["Higher extra fees", "Can only donate fixed amounts"]))
-
-func _on_link_mouse_exited() -> void:
- reset_clarifications()
-
-
-func show_description(title: String, pros: PackedStringArray, cons := PackedStringArray()) -> void:
+func set_link(new_link: Link) -> void:
+ current_link = new_link
+ if new_link == Link.NONE:
+ clarifications.clear()
+ clarifications.push_color(ThemeUtils.common_subtle_text_color)
+ clarifications.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
+ clarifications.add_text("\nHover a platform for details.")
+ return
+
+ var title: String
+ var pros: PackedStringArray
+ var cons: PackedStringArray
+ match new_link:
+ Link.GITHUB:
+ title = "Guthub Sponsors"
+ pros = PackedStringArray(["Low extra fees", "Can donate an arbitrary amount",
+ "Includes all perks"])
+ cons = PackedStringArray()
+ Link.KO_FI:
+ title = "Ko-Fi"
+ pros = PackedStringArray(["Low extra fees", "Can donate an arbitrary amount"])
+ cons = PackedStringArray(["No perks"])
+ Link.PATREON:
+ title = "Patreon"
+ pros = PackedStringArray(["Includes all perks"])
+ cons = PackedStringArray(["Higher extra fees", "Can only donate fixed amounts"])
+
+ clarifications.horizontal_alignment = HORIZONTAL_ALIGNMENT_LEFT
clarifications.clear()
clarifications.push_bold()
clarifications.add_text(title + ":")
@@ -47,8 +86,3 @@ func show_description(title: String, pros: PackedStringArray, cons := PackedStri
clarifications.push_color(Configs.savedata.basic_color_error)
for con in cons:
clarifications.add_text("\n– " + con)
-
-func reset_clarifications() -> void:
- clarifications.clear()
- clarifications.push_color(ThemeUtils.common_subtle_text_color)
- clarifications.add_text("\nHover a platform for details.")
diff --git a/src/ui_parts/donate_menu.tscn b/src/ui_parts/donate_menu.tscn
index 76a0f1f..aa69a7c 100644
--- a/src/ui_parts/donate_menu.tscn
+++ b/src/ui_parts/donate_menu.tscn
@@ -23,19 +23,16 @@ script = ExtResource("1_yjfkr")
[node name="VBoxContainer" type="VBoxContainer" parent="."]
layout_mode = 2
-theme_override_constants/separation = 8
+theme_override_constants/separation = 4
[node name="TitleLabel" type="Label" parent="VBoxContainer"]
layout_mode = 2
+theme_override_font_sizes/font_size = 15
text = "Links to donation platforms"
horizontal_alignment = 1
-[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer"]
-layout_mode = 2
-size_flags_vertical = 3
-theme_override_constants/separation = 8
-
-[node name="MarginContainer" type="MarginContainer" parent="VBoxContainer/VBoxContainer"]
+[node name="MarginContainer" type="MarginContainer" parent="VBoxContainer"]
+unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 4
size_flags_vertical = 0
@@ -44,39 +41,46 @@ theme_override_constants/margin_top = 8
theme_override_constants/margin_right = 8
theme_override_constants/margin_bottom = 8
-[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/VBoxContainer/MarginContainer"]
+[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/MarginContainer"]
layout_mode = 2
theme_override_constants/separation = 12
alignment = 1
-[node name="GithubLink" type="Button" parent="VBoxContainer/VBoxContainer/MarginContainer/HBoxContainer"]
+[node name="GithubLink" type="Button" parent="VBoxContainer/MarginContainer/HBoxContainer"]
custom_minimum_size = Vector2(64, 64)
layout_mode = 2
focus_mode = 0
+mouse_filter = 1
mouse_default_cursor_shape = 2
theme_type_variation = &"TranslucentButton"
icon = ExtResource("2_3gj3j")
expand_icon = true
-[node name="KoFiLink" type="Button" parent="VBoxContainer/VBoxContainer/MarginContainer/HBoxContainer"]
+[node name="KoFiLink" type="Button" parent="VBoxContainer/MarginContainer/HBoxContainer"]
custom_minimum_size = Vector2(64, 64)
layout_mode = 2
focus_mode = 0
+mouse_filter = 1
mouse_default_cursor_shape = 2
theme_type_variation = &"TranslucentButton"
icon = ExtResource("3_5q1ti")
expand_icon = true
-[node name="PatreonLink" type="Button" parent="VBoxContainer/VBoxContainer/MarginContainer/HBoxContainer"]
+[node name="PatreonLink" type="Button" parent="VBoxContainer/MarginContainer/HBoxContainer"]
custom_minimum_size = Vector2(64, 64)
layout_mode = 2
focus_mode = 0
+mouse_filter = 1
mouse_default_cursor_shape = 2
theme_type_variation = &"TranslucentButton"
icon = ExtResource("4_0irlu")
expand_icon = true
-[node name="Clarifications" type="RichTextLabel" parent="VBoxContainer/VBoxContainer"]
+[node name="MarginContainer2" type="MarginContainer" parent="VBoxContainer"]
+layout_mode = 2
+theme_override_constants/margin_left = 4
+
+[node name="Clarifications" type="RichTextLabel" parent="VBoxContainer/MarginContainer2"]
unique_name_in_owner = true
layout_mode = 2
bbcode_enabled = true
@@ -85,14 +89,6 @@ fit_content = true
[node name="CloseButton" type="Button" parent="VBoxContainer"]
layout_mode = 2
size_flags_horizontal = 4
-size_flags_vertical = 8
+size_flags_vertical = 10
focus_mode = 0
mouse_default_cursor_shape = 2
-
-[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"]
-[connection signal="pressed" from="VBoxContainer/VBoxContainer/MarginContainer/HBoxContainer/GithubLink" to="." method="_on_github_link_pressed"]
-[connection signal="mouse_entered" from="VBoxContainer/VBoxContainer/MarginContainer/HBoxContainer/KoFiLink" to="." method="_on_ko_fi_link_mouse_entered"]
-[connection signal="pressed" from="VBoxContainer/VBoxContainer/MarginContainer/HBoxContainer/KoFiLink" to="." method="_on_ko_fi_link_pressed"]
-[connection signal="mouse_entered" from="VBoxContainer/VBoxContainer/MarginContainer/HBoxContainer/PatreonLink" to="." method="_on_patreon_link_mouse_entered"]
-[connection signal="pressed" from="VBoxContainer/VBoxContainer/MarginContainer/HBoxContainer/PatreonLink" to="." method="_on_patreon_link_pressed"]
diff --git a/src/ui_parts/editor_scene.tscn b/src/ui_parts/editor_scene.tscn
index 6129a71..3a9acab 100644
--- a/src/ui_parts/editor_scene.tscn
+++ b/src/ui_parts/editor_scene.tscn
@@ -2,7 +2,7 @@
[ext_resource type="Script" uid="uid://dunoppeuubgd1" path="res://src/ui_parts/editor_scene.gd" id="1_o7lif"]
[ext_resource type="PackedScene" uid="uid://cxmrx6t4jkhyj" path="res://src/ui_parts/global_actions.tscn" id="2_p8urg"]
-[ext_resource type="PackedScene" uid="uid://cr1fdlmbknnko" path="res://src/ui_widgets/code_editor.tscn" id="3_4uluy"]
+[ext_resource type="PackedScene" uid="uid://cr1fdlmbknnko" path="res://src/ui_parts/code_editor.tscn" id="3_4uluy"]
[ext_resource type="PackedScene" uid="uid://ccynisiuyn5qn" path="res://src/ui_parts/inspector.tscn" id="4_jik7v"]
[ext_resource type="PackedScene" uid="uid://bvrncl7e6yn5b" path="res://src/ui_parts/display.tscn" id="5_gb5yr"]
@@ -41,6 +41,7 @@ current_tab = 0
tabs_position = 1
[node name="Inspector" parent="HSplitContainer/TabContainer" instance=ExtResource("4_jik7v")]
+custom_minimum_size = Vector2(0, 120)
layout_mode = 2
metadata/_tab_index = 0
diff --git a/src/ui_parts/element_container.gd b/src/ui_parts/element_container.gd
index 5373ed0..fbd4b49 100644
--- a/src/ui_parts/element_container.gd
+++ b/src/ui_parts/element_container.gd
@@ -8,8 +8,13 @@ const autoscroll_speed = 1500.0
@onready var xnodes: VBoxContainer = %RootChildren
@onready var covering_rect: Control = $MoveToOverlay
-func _ready():
+func _ready() -> void:
State.requested_scroll_to_element_editor.connect(scroll_to_view_element_editor)
+ set_dragging(false)
+
+func set_dragging(new_state: bool) -> void:
+ covering_rect.visible = new_state
+ set_process(new_state)
func _process(delta: float) -> void:
if State.proposed_drop_xid.is_empty():
@@ -73,22 +78,14 @@ func update_proposed_xid() -> void:
State.set_proposed_drop_xid(prev_xid + PackedInt32Array([0]))
-var dragged_xnode_editors: Array[Control] = []
-
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 State.selected_xids:
- var xnode_editor := get_xnode_editor(selected_xid)
- dragged_xnode_editors.append(xnode_editor)
- xnode_editor.modulate.a = 0.55
+ set_dragging(true)
update_proposed_xid()
elif what == NOTIFICATION_DRAG_END:
- covering_rect.hide()
- for xnode_editor in dragged_xnode_editors:
- xnode_editor.modulate.a = 1.0
- dragged_xnode_editors.clear()
+ set_dragging(false)
+ State.set_selection_dragged(false)
State.clear_proposed_drop_xid()
func _gui_input(event: InputEvent) -> void:
@@ -105,8 +102,10 @@ func _gui_input(event: InputEvent) -> void:
location += 1
# Create the context popup.
var btn_array: Array[Button] = []
- for element_name in ["path", "circle", "ellipse", "rect", "line", "polygon",
- "polyline", "g", "linearGradient", "radialGradient", "stop"]:
+ const CONST_ARR: PackedStringArray = ["path", "circle", "ellipse", "rect",
+ "line", "polygon", "polyline", "g", "linearGradient", "radialGradient",
+ "stop"]
+ for element_name in CONST_ARR:
var btn := ContextPopup.create_button(element_name,
add_element.bind(element_name, location), false,
DB.get_element_icon(element_name))
@@ -126,17 +125,15 @@ func add_element(element_name: String, element_idx: int) -> void:
PackedInt32Array([element_idx]))
State.queue_svg_save()
-func get_xnode_editor(xid: PackedInt32Array) -> Control:
+
+func get_xnode_editor_rect(xid: PackedInt32Array, inner_index := -1) -> Rect2:
if xid.is_empty():
- return null
+ return Rect2()
var xnode_editor: Control = xnodes.get_child(xid[0])
for i in range(1, xid.size()):
xnode_editor = xnode_editor.child_xnodes_container.get_child(xid[i])
- return xnode_editor
-
-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()
diff --git a/src/ui_parts/export_menu.gd b/src/ui_parts/export_menu.gd
index 3e81f33..90c30e7 100644
--- a/src/ui_parts/export_menu.gd
+++ b/src/ui_parts/export_menu.gd
@@ -1,23 +1,24 @@
extends PanelContainer
-const NumberEditType = preload("res://src/ui_widgets/number_edit.gd")
-const DropdownType = preload("res://src/ui_widgets/dropdown.gd")
+const NumberEdit = preload("res://src/ui_widgets/number_edit.gd")
+const Dropdown = preload("res://src/ui_widgets/dropdown.gd")
+const PreviewRect = preload("res://src/ui_widgets/preview_rect.gd")
-var UR := UndoRedo.new()
+var undo_redo := UndoRedoRef.new()
var export_data := ImageExportData.new()
var dimensions := Vector2.ZERO
@onready var dimensions_label: Label = %DimensionsLabel
-@onready var texture_preview: CenterContainer = %TexturePreview
+@onready var texture_preview: PreviewRect = %TexturePreview
@onready var format_hbox: HBoxContainer = %FormatHBox
-@onready var format_dropdown: DropdownType = %FormatHBox/Dropdown
+@onready var format_dropdown: Dropdown = %FormatHBox/Dropdown
@onready var final_size_label: Label = %FinalSizeLabel
-@onready var scale_edit: NumberEditType = %Scale
-@onready var width_edit: NumberEditType = %Width
-@onready var height_edit: NumberEditType = %Height
+@onready var scale_edit: NumberEdit = %Scale
+@onready var width_edit: NumberEdit = %Width
+@onready var height_edit: NumberEdit = %Height
@onready var size_container: CenterContainer = %SizeContainer
@onready var lossless_checkbox: CheckBox = %LosslessCheckBox
-@onready var quality_edit: NumberEditType = %Quality
+@onready var quality_edit: NumberEdit = %Quality
@onready var quality_hbox: HBoxContainer = %QualityHBox
@onready var cancel_button: Button = %ButtonContainer/CancelButton
@onready var export_button: Button = %ButtonContainer/ExportButton
@@ -26,8 +27,6 @@ var dimensions := Vector2.ZERO
@onready var quality_related_container: HBoxContainer = %QualityRelatedContainer
@onready var titled_panel: VBoxContainer = %TitledPanel
-func _exit_tree() -> void:
- UR.free()
func _ready() -> void:
cancel_button.pressed.connect(queue_free)
@@ -41,6 +40,7 @@ func _ready() -> void:
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
@@ -55,11 +55,13 @@ func _ready() -> void:
var scaling_factor: float = texture_preview.MAX_IMAGE_DIMENSION / bigger_dimension
info_tooltip.tooltip_text = Translator.translate(
"Preview image size is limited to {dimensions}").format(
- {"dimensions": get_dimensions_text(dimensions * scaling_factor, true)})
+ {"dimensions": get_dimensions_text(Vector2(
+ maxf(dimensions.x * scaling_factor, 1.0),
+ maxf(dimensions.y * scaling_factor, 1.0)), true)})
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 = Configs.savedata.get_active_tab().get_presented_name()
+ file_title.text = Configs.savedata.get_active_tab().presented_name
final_size_label.text = Translator.translate("Size") + ": " +\
String.humanize_size(State.get_export_text().length())
@@ -79,51 +81,51 @@ func _on_export_button_pressed() -> void:
func _on_dropdown_value_changed(new_value: String) -> void:
var current_format := export_data.format
- UR.create_action("")
- UR.add_do_property(export_data, "format", new_value)
- UR.add_undo_property(export_data, "format", current_format)
- UR.commit_action()
+ undo_redo.create_action("")
+ undo_redo.add_do_property(export_data, "format", new_value)
+ undo_redo.add_undo_property(export_data, "format", current_format)
+ undo_redo.commit_action()
func _on_lossless_check_box_toggled(toggled_on: bool) -> void:
var current_lossy := export_data.lossy
- UR.create_action("")
- UR.add_do_property(export_data, "lossy", not toggled_on)
- UR.add_undo_property(export_data, "lossy", current_lossy)
- UR.commit_action()
+ undo_redo.create_action("")
+ undo_redo.add_do_property(export_data, "lossy", not toggled_on)
+ undo_redo.add_undo_property(export_data, "lossy", current_lossy)
+ undo_redo.commit_action()
func _on_quality_value_changed(new_value: float) -> void:
var current_quality := export_data.quality
- UR.create_action("")
- UR.add_do_property(export_data, "quality", new_value / 100)
- UR.add_undo_property(export_data, "quality", current_quality)
- UR.commit_action()
+ undo_redo.create_action("")
+ undo_redo.add_do_property(export_data, "quality", new_value / 100)
+ undo_redo.add_undo_property(export_data, "quality", current_quality)
+ undo_redo.commit_action()
func _on_scale_edit_value_changed(new_value: float) -> void:
if new_value == export_data.upscale_amount:
return
var current_upscale_amount := export_data.upscale_amount
- UR.create_action("")
- UR.add_do_property(export_data, "upscale_amount", new_value)
- UR.add_undo_property(export_data, "upscale_amount", current_upscale_amount)
- UR.commit_action()
+ undo_redo.create_action("")
+ undo_redo.add_do_property(export_data, "upscale_amount", new_value)
+ undo_redo.add_undo_property(export_data, "upscale_amount", current_upscale_amount)
+ undo_redo.commit_action()
func _on_width_edit_value_changed(new_value: float) -> void:
if roundi(dimensions.x * export_data.upscale_amount) == roundi(new_value):
return
var current_upscale_amount := export_data.upscale_amount
- UR.create_action("")
- UR.add_do_property(export_data, "upscale_amount", new_value / dimensions.x)
- UR.add_undo_property(export_data, "upscale_amount", current_upscale_amount)
- UR.commit_action()
+ undo_redo.create_action("")
+ undo_redo.add_do_property(export_data, "upscale_amount", new_value / dimensions.x)
+ undo_redo.add_undo_property(export_data, "upscale_amount", current_upscale_amount)
+ undo_redo.commit_action()
func _on_height_edit_value_changed(new_value: float) -> void:
if roundi(dimensions.y * export_data.upscale_amount) == roundi(new_value):
return
var current_upscale_amount := export_data.upscale_amount
- UR.create_action("")
- UR.add_do_property(export_data, "upscale_amount", new_value / dimensions.y)
- UR.add_undo_property(export_data, "upscale_amount", current_upscale_amount)
- UR.commit_action()
+ undo_redo.create_action("")
+ undo_redo.add_do_property(export_data, "upscale_amount", new_value / dimensions.y)
+ undo_redo.add_undo_property(export_data, "upscale_amount", current_upscale_amount)
+ undo_redo.commit_action()
# Everything gets updated at once when export config changes for simplicity.
func update() -> void:
@@ -170,10 +172,10 @@ func _input(event: InputEvent) -> void:
return
if ShortcutUtils.is_action_pressed(event, "redo"):
- if UR.has_redo():
- UR.redo()
+ if undo_redo.has_redo():
+ undo_redo.redo()
accept_event()
elif ShortcutUtils.is_action_pressed(event, "undo"):
- if UR.has_undo():
- UR.undo()
+ if undo_redo.has_undo():
+ undo_redo.undo()
accept_event()
diff --git a/src/ui_parts/export_menu.tscn b/src/ui_parts/export_menu.tscn
index ecc87e4..7a50466 100644
--- a/src/ui_parts/export_menu.tscn
+++ b/src/ui_parts/export_menu.tscn
@@ -1,6 +1,7 @@
[gd_scene load_steps=7 format=3 uid="uid://c13dadqbljqlu"]
[ext_resource type="Script" uid="uid://bouo2y3v0gy4a" path="res://src/ui_parts/export_menu.gd" id="1_objnb"]
+[ext_resource type="Script" uid="uid://chk5g6lh7jc14" path="res://src/ui_widgets/HTitledPanel.gd" id="2_07m68"]
[ext_resource type="PackedScene" uid="uid://xh26qa68xed4" path="res://src/ui_widgets/preview_rect.tscn" id="2_ewk0a"]
[ext_resource type="Texture2D" uid="uid://v0lqyuvo50yq" path="res://assets/icons/Info.svg" id="2_rk3rd"]
[ext_resource type="FontFile" uid="uid://clpf84p1lfwlp" path="res://assets/fonts/Font.ttf" id="5_s3e6m"]
@@ -34,6 +35,7 @@ theme_override_constants/separation = 8
[node name="TitleLabel" type="Label" parent="MarginContainer/VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
+theme_override_font_sizes/font_size = 15
horizontal_alignment = 1
[node name="TitledPanel" type="VBoxContainer" parent="MarginContainer/VBoxContainer"]
@@ -61,7 +63,6 @@ custom_minimum_size = Vector2(132, 20)
layout_mode = 2
size_flags_horizontal = 3
theme_override_colors/font_color = Color(0.866667, 0.866667, 0.866667, 1)
-theme_override_font_sizes/font_size = 13
horizontal_alignment = 1
vertical_alignment = 1
text_overrun_behavior = 3
@@ -113,7 +114,17 @@ theme_override_font_sizes/font_size = 12
[node name="Dropdown" parent="MarginContainer/VBoxContainer/TitledPanel/VBoxContainer/CenterContainer/VBoxContainer/FormatHBox" instance=ExtResource("5_y6ex0")]
layout_mode = 2
-values = PackedStringArray("svg", "png", "jpg", "webp")
+values = ["svg", "png", "jpeg", "webp"]
+aliases = {
+"JPEG": "jpeg",
+"JPG": "jpg",
+"PNG": "png",
+"SVG": "svg",
+"WEBP": "webp",
+"WebP": "webp",
+"jpg": "jpeg"
+}
+editing_enabled = true
[node name="FinalSizeLabel" type="Label" parent="MarginContainer/VBoxContainer/TitledPanel/VBoxContainer/CenterContainer/VBoxContainer"]
unique_name_in_owner = true
@@ -183,7 +194,6 @@ unique_name_in_owner = true
custom_minimum_size = Vector2(47, 22)
layout_mode = 2
size_flags_vertical = 4
-initial_value = 1.0
allow_lower = false
allow_higher = false
@@ -208,7 +218,6 @@ unique_name_in_owner = true
custom_minimum_size = Vector2(47, 22)
layout_mode = 2
max_value = 16384.0
-initial_value = 1.0
allow_lower = false
allow_higher = false
is_float = false
@@ -227,7 +236,6 @@ unique_name_in_owner = true
custom_minimum_size = Vector2(47, 22)
layout_mode = 2
max_value = 16384.0
-initial_value = 1.0
allow_lower = false
allow_higher = false
is_float = false
diff --git a/src/ui_parts/eyedropper_popup.gd b/src/ui_parts/eyedropper_popup.gd
index 24ee42b..2ec8178 100644
--- a/src/ui_parts/eyedropper_popup.gd
+++ b/src/ui_parts/eyedropper_popup.gd
@@ -57,10 +57,10 @@ func _draw() -> void:
if is_nan(max_horizontal) and is_nan(max_vertical):
continue
- var left = clampf(pos.x + l, pos.x - max_horizontal, pos.x + max_horizontal)
- var right = clampf(pos.x + r, pos.x - max_horizontal, pos.x + max_horizontal)
- var top = clampf(pos.y + t, pos.y - max_vertical, pos.y + max_vertical)
- var bottom = clampf(pos.y + b, pos.y - max_vertical, pos.y + max_vertical)
+ var left := clampf(pos.x + l, pos.x - max_horizontal, pos.x + max_horizontal)
+ var right := clampf(pos.x + r, pos.x - max_horizontal, pos.x + max_horizontal)
+ var top := clampf(pos.y + t, pos.y - max_vertical, pos.y + max_vertical)
+ var bottom := clampf(pos.y + b, pos.y - max_vertical, pos.y + max_vertical)
if left < right and top < bottom:
draw_rect(Rect2(Vector2(left, top), Vector2(right - left, bottom - top)),
diff --git a/src/ui_parts/eyedropper_popup.tscn b/src/ui_parts/eyedropper_popup.tscn
index 1d22566..1bd4ed7 100644
--- a/src/ui_parts/eyedropper_popup.tscn
+++ b/src/ui_parts/eyedropper_popup.tscn
@@ -1,6 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://cnhb2yvcb4fbe"]
-[ext_resource type="Script" path="res://src/ui_parts/eyedropper_popup.gd" id="1_qjmw3"]
+[ext_resource type="Script" uid="uid://3owchx7ygm21" path="res://src/ui_parts/eyedropper_popup.gd" id="1_qjmw3"]
[node name="EyedropperPopup" type="TextureRect"]
texture_filter = 1
diff --git a/src/ui_parts/global_actions.gd b/src/ui_parts/global_actions.gd
index a27694c..e2cbd5d 100644
--- a/src/ui_parts/global_actions.gd
+++ b/src/ui_parts/global_actions.gd
@@ -5,38 +5,32 @@ extends HBoxContainer
@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()
+ const CONST_ARR: PackedStringArray = ["normal", "focus", "hover", "disabled"]
+ for theme_type in CONST_ARR:
+ var stylebox := size_button.get_theme_stylebox(theme_type).duplicate()
stylebox.content_margin_bottom = 0
stylebox.content_margin_top = 0
- size_button.add_theme_stylebox_override(theming, stylebox)
+ size_button.add_theme_stylebox_override(theme_type, stylebox)
size_button.end_bulk_theme_override()
import_button.pressed.connect(ShortcutUtils.fn("import"))
export_button.pressed.connect(ShortcutUtils.fn("export"))
more_options.pressed.connect(_on_more_options_pressed)
size_button.pressed.connect(_on_size_button_pressed)
- file_button.pressed.connect(_on_file_button_pressed)
settings_button.pressed.connect(ShortcutUtils.fn_call.bind("open_settings"))
@@ -50,29 +44,6 @@ func _on_size_button_pressed() -> void:
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)
@@ -81,26 +52,16 @@ func _on_more_options_pressed() -> void:
"Check for updates"), ShortcutUtils.fn("check_updates"), false,
load("res://assets/icons/Reload.svg"), "check_updates"))
- 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)
+ 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,
- ImageTexture.create_from_image(about_image), "about_info")
+ load("res://assets/logos/icon.svg"), "about_info")
+ about_btn.expand_icon = true
buttons_arr.append(about_btn)
- # It requires to hover over it to see details, which is not possible on Android.
- # Would re-enable it in future with a better UX.
- #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_godsvg_repo"), false,
load("res://assets/icons/Link.svg"), ""))
@@ -136,15 +97,9 @@ func update_size_button() -> void:
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,
+ const CONST_ARR: PackedStringArray = ["font_color", "font_hover_color",
+ "font_pressed_color"]
+ for theme_type in CONST_ARR:
+ size_button.add_theme_color_override(theme_type,
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.tscn b/src/ui_parts/global_actions.tscn
index 2e581cc..f45b3ae 100644
--- a/src/ui_parts/global_actions.tscn
+++ b/src/ui_parts/global_actions.tscn
@@ -1,4 +1,4 @@
-[gd_scene load_steps=7 format=3 uid="uid://cxmrx6t4jkhyj"]
+[gd_scene load_steps=8 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"]
@@ -6,6 +6,7 @@
[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"]
+[ext_resource type="Script" uid="uid://41g64ussxcbn" path="res://src/ui_parts/current_file_button.gd" id="5_14xct"]
[node name="GlobalActions" type="HBoxContainer"]
offset_right = 52.0
@@ -44,13 +45,14 @@ mouse_default_cursor_shape = 2
theme_type_variation = &"TranslucentButton"
theme_override_fonts/font = ExtResource("4_xl5uh")
-[node name="FileButton" type="Button" parent="RightSide"]
+[node name="CurrentFileButton" 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
+script = ExtResource("5_14xct")
[node name="ImportButton" type="Button" parent="RightSide"]
layout_mode = 2
diff --git a/src/ui_parts/good_file_dialog.gd b/src/ui_parts/good_file_dialog.gd
index e203e28..790b8ac 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_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")
+const ChooseNameDialogScene = preload("res://src/ui_widgets/choose_name_dialog.tscn")
+const ConfirmDialogScene = preload("res://src/ui_widgets/confirm_dialog.tscn")
+const AlertDialogScene = preload("res://src/ui_widgets/alert_dialog.tscn")
signal file_selected(path: String)
@@ -20,13 +20,13 @@ var mode: FileMode
var current_dir := ""
var current_file := ""
-var default_file := ""
+var default_file := "" # The file you opened this dialog with.
var extensions := PackedStringArray()
var item_height := 16
var search_text := ""
-var DA: DirAccess
+var dir_cursor: DirAccess
@onready var title_label: Label = $VBoxContainer/TitleLabel
@onready var search_field: BetterLineEdit = %SearchField
@@ -146,16 +146,17 @@ func file_sort(file1: String, file2: String) -> bool:
# This function requires a safe input.
func set_dir(dir: String) -> void:
- DA = DirAccess.open(dir)
- if !is_instance_valid(DA):
+ dir_cursor = DirAccess.open(dir)
+ if !is_instance_valid(dir_cursor):
return
file_list.clear()
+ file_list.get_v_scroll_bar().value = 0
# Basic setup.
unfocus_file()
current_dir = dir
path_field.text = current_dir
- DA.include_hidden = Configs.savedata.file_dialog_show_hidden
+ dir_cursor.include_hidden = Configs.savedata.file_dialog_show_hidden
# Rebuild the system dirs, as we may now need to highlight the current one.
drives_list.clear()
for drive in system_dirs_to_show:
@@ -182,9 +183,9 @@ func set_dir(dir: String) -> void:
# Gather the files and directories. Must be sorted, so can't use PackedStringArray.
var directories: Array[String] = []
var files: Array[String] = []
- for directory in DA.get_directories():
+ for directory in dir_cursor.get_directories():
directories.append(directory)
- for file in DA.get_files():
+ for file in dir_cursor.get_files():
files.append(file)
directories.sort_custom(file_sort)
files.sort_custom(file_sort)
@@ -223,7 +224,8 @@ func set_file(file: String) -> void:
file += "." + extensions[0]
file_list.ensure_current_is_visible()
current_file = file
- file_field.text = current_file
+ if not file.is_empty():
+ file_field.text = file
# For optimization, only generate the visible files' images.
func _setup_file_images() -> void:
@@ -259,7 +261,7 @@ func _setup_file_images() -> void:
func select_file() -> void:
if mode == FileMode.SAVE and FileAccess.file_exists(current_dir.path_join(current_file)):
- var confirm_dialog := ConfirmDialog.instantiate()
+ var confirm_dialog := ConfirmDialogScene.instantiate()
HandlerGUI.add_dialog(confirm_dialog)
confirm_dialog.setup(Translator.translate("Alert!"), Translator.translate(
"A file named \"{file_name}\" already exists. Replacing will overwrite its contents!").format(
@@ -274,11 +276,11 @@ func focus_file(path: String) -> void:
func unfocus_file() -> void:
set_file(default_file)
-func copy_path() -> void:
+func copy_file_path() -> void:
DisplayServer.clipboard_set(current_dir.path_join(current_file))
func create_folder() -> void:
- var create_folder_dialog := ChooseNameDialog.instantiate()
+ var create_folder_dialog := ChooseNameDialogScene.instantiate()
HandlerGUI.add_dialog(create_folder_dialog)
create_folder_dialog.setup(Translator.translate("Create new folder"),
_on_create_folder_finished, _create_folder_error)
@@ -293,15 +295,15 @@ func _create_folder_error(text: String) -> String:
return ""
func _on_create_folder_finished(text: String) -> void:
- DA = DirAccess.open(current_dir)
- if !is_instance_valid(DA):
+ dir_cursor = DirAccess.open(current_dir)
+ if !is_instance_valid(dir_cursor):
return
- var err := DA.make_dir(text)
+ var err := dir_cursor.make_dir(text)
if err == OK:
refresh_dir()
else:
- var alert_dialog := AlertDialog.instantiate()
+ var alert_dialog := AlertDialogScene.instantiate()
HandlerGUI.add_dialog(alert_dialog)
alert_dialog.setup(Translator.translate("Failed to create a folder."))
@@ -313,7 +315,8 @@ func open_dir_context(dir: String) -> void:
enter_dir.bind(dir), false, load("res://assets/icons/OpenFolder.svg"),
"ui_accept"),
ContextPopup.create_button(Translator.translate("Copy path"),
- copy_path, false, load("res://assets/icons/Copy.svg"))]
+ DisplayServer.clipboard_set.bind(dir), false,
+ load("res://assets/icons/Copy.svg"))]
context_popup.setup(btn_arr, true)
var vp := get_viewport()
HandlerGUI.popup_under_pos(context_popup, vp.get_mouse_position(), vp)
@@ -324,7 +327,7 @@ func open_file_context(file: String) -> void:
ContextPopup.create_button(special_button.text,
select_file, false, load("res://assets/icons/OpenFile.svg"), "ui_accept"),
ContextPopup.create_button(Translator.translate("Copy path"),
- copy_path, false, load("res://assets/icons/Copy.svg"))]
+ copy_file_path, false, load("res://assets/icons/Copy.svg"))]
var context_popup := ContextPopup.new()
context_popup.setup(btn_arr, true)
var vp := get_viewport()
@@ -383,8 +386,8 @@ func _on_file_field_text_submitted(new_text: String) -> void:
file_field.text = current_file
func _on_path_field_text_submitted(new_text: String) -> void:
- DA = DirAccess.open(new_text)
- if is_instance_valid(DA):
+ dir_cursor = DirAccess.open(new_text)
+ if is_instance_valid(dir_cursor):
set_dir(new_text)
else:
path_field.text = current_dir
@@ -419,9 +422,10 @@ func _on_replace_button_pressed() -> void:
# Helpers
func _init() -> void:
- for enum_value in [OS.SYSTEM_DIR_DCIM, OS.SYSTEM_DIR_DESKTOP, OS.SYSTEM_DIR_DOCUMENTS,
- OS.SYSTEM_DIR_DOWNLOADS, OS.SYSTEM_DIR_MOVIES, OS.SYSTEM_DIR_MUSIC,
- OS.SYSTEM_DIR_PICTURES, OS.SYSTEM_DIR_RINGTONES]:
+ const arr: Array[OS.SystemDir] = [OS.SYSTEM_DIR_DCIM, OS.SYSTEM_DIR_DESKTOP,
+ OS.SYSTEM_DIR_DOCUMENTS, OS.SYSTEM_DIR_DOWNLOADS, OS.SYSTEM_DIR_MOVIES,
+ OS.SYSTEM_DIR_MUSIC, OS.SYSTEM_DIR_PICTURES, OS.SYSTEM_DIR_RINGTONES]
+ for enum_value in arr:
system_dir_paths[enum_value] = OS.get_system_dir(enum_value)
var system_dir_paths: Dictionary[OS.SystemDir, String] = {}
diff --git a/src/ui_parts/good_file_dialog.tscn b/src/ui_parts/good_file_dialog.tscn
index cc72361..4c16503 100644
--- a/src/ui_parts/good_file_dialog.tscn
+++ b/src/ui_parts/good_file_dialog.tscn
@@ -1,7 +1,7 @@
[gd_scene load_steps=8 format=3 uid="uid://mp1nxm8i8tv8"]
-[ext_resource type="Script" path="res://src/ui_parts/good_file_dialog.gd" id="1_awdto"]
-[ext_resource type="Script" path="res://src/ui_widgets/BetterLineEdit.gd" id="2_52puw"]
+[ext_resource type="Script" uid="uid://cdlrqylqsmc22" path="res://src/ui_parts/good_file_dialog.gd" id="1_awdto"]
+[ext_resource type="Script" uid="uid://1hox6gd5pxku" path="res://src/ui_widgets/BetterLineEdit.gd" id="2_52puw"]
[ext_resource type="Texture2D" uid="uid://rrhdja8l17cn" path="res://assets/icons/FolderUp.svg" id="2_i2mtk"]
[ext_resource type="Texture2D" uid="uid://cvh3kwbucf2n1" path="res://assets/icons/Reload.svg" id="4_udwbh"]
[ext_resource type="Texture2D" uid="uid://kkxyv1gyrjgj" path="res://assets/icons/Visuals.svg" id="5_2ggtv"]
@@ -20,6 +20,7 @@ theme_override_constants/separation = 8
[node name="TitleLabel" type="Label" parent="VBoxContainer"]
layout_mode = 2
+theme_override_font_sizes/font_size = 15
horizontal_alignment = 1
[node name="TopBar" type="HBoxContainer" parent="VBoxContainer"]
diff --git a/src/ui_parts/handles_manager.gd b/src/ui_parts/handles_manager.gd
index d93da89..d8c9493 100644
--- a/src/ui_parts/handles_manager.gd
+++ b/src/ui_parts/handles_manager.gd
@@ -53,7 +53,8 @@ func render_handle_textures() -> void:
s * 4, s * 4, s * 2.4, "%s", "%s", s * 1.2],
}
- for handle_type in [Handle.Display.BIG, Handle.Display.SMALL]:
+ const CONST_ARR: Array[Handle.Display] = [Handle.Display.BIG, Handle.Display.SMALL]
+ for handle_type in CONST_ARR:
var handle_type_svg := handles_dict[handle_type]
img.load_svg_from_string(handle_type_svg % [inside_str, normal_str])
img.fix_alpha_edges()
@@ -93,9 +94,15 @@ func _ready() -> void:
State.hover_changed.connect(queue_redraw)
State.zoom_changed.connect(queue_redraw)
State.handle_added.connect(_on_handle_added)
+ State.show_handles_changed.connect(toggle_visibility)
+ State.view_changed.connect(HandlerGUI.throw_mouse_motion_event)
queue_update_handles()
+func toggle_visibility() -> void:
+ visible = not visible
+ HandlerGUI.throw_mouse_motion_event()
+
func queue_update_handles() -> void:
update_handles.call_deferred()
_handles_update_pending = true
@@ -168,7 +175,7 @@ func generate_path_handles(element: Element) -> Array[Handle]:
func generate_polyhandles(element: Element) -> Array[Handle]:
var polyhandles: Array[Handle] = []
- for idx in element.get_attribute("points").get_list_size() / 2:
+ for idx: int in element.get_attribute("points").get_list_size() / 2:
polyhandles.append(PolyHandle.new(element, idx))
return polyhandles
@@ -668,15 +675,18 @@ var should_deselect_all := false
func _unhandled_input(event: InputEvent) -> void:
if not visible:
+ dragged_handle = null
hovered_handle = null
- State.clear_all_hovered()
- return
+ # Mouse events on the viewport clear hovered, but other events don't.
+ if ((event is InputEventMouseMotion and event.button_mask == 0) or\
+ (event is InputEventMouseButton and (event.button_index in [MOUSE_BUTTON_LEFT,
+ MOUSE_BUTTON_RIGHT]))):
+ State.clear_all_hovered()
# Set the nearest handle as hovered, if any handles are within range.
- if (event is InputEventMouseMotion and !is_instance_valid(dragged_handle) and\
+ if visible and ((event is InputEventMouseMotion and !is_instance_valid(dragged_handle) and\
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])):
+ (event.button_index in [MOUSE_BUTTON_LEFT, MOUSE_BUTTON_RIGHT]))):
var nearest_handle := find_nearest_handle(event.position / State.zoom +\
get_parent().view.position)
if is_instance_valid(nearest_handle):
@@ -697,7 +707,7 @@ func _unhandled_input(event: InputEvent) -> void:
return
should_deselect_all = false
- if is_instance_valid(dragged_handle):
+ if visible and is_instance_valid(dragged_handle):
# Move the handle that's being dragged.
var event_pos := get_event_pos(event)
var new_pos := Utils64Bit.transform_vector_mult(
@@ -711,7 +721,7 @@ func _unhandled_input(event: InputEvent) -> void:
if event.button_index == MOUSE_BUTTON_LEFT:
# React to LMB actions.
- if is_instance_valid(hovered_handle) and event.is_pressed():
+ if visible and is_instance_valid(hovered_handle) and event.is_pressed():
dragged_handle = hovered_handle
var inner_idx := -1
var dragged_xid := dragged_handle.element.xid
@@ -730,14 +740,14 @@ func _unhandled_input(event: InputEvent) -> void:
elif dragged_handle is PolyHandle:
State.normal_select(dragged_xid, 0)
State.shift_select(dragged_xid,
- dragged_handle.element.get_attribute("points").get_list_size() / 2)
+ dragged_handle.element.get_attribute("points").get_list_size() / 2 - 1)
elif event.is_command_or_control_pressed():
State.ctrl_select(dragged_xid, inner_idx)
elif event.shift_pressed:
State.shift_select(dragged_xid, inner_idx)
else:
State.normal_select(dragged_xid, inner_idx)
- elif is_instance_valid(dragged_handle) and event.is_released():
+ elif visible and 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),
@@ -758,7 +768,7 @@ func _unhandled_input(event: InputEvent) -> void:
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:
+ elif visible:
var hovered_xid := hovered_handle.element.xid
var inner_idx := -1
if hovered_handle is PathHandle:
@@ -817,14 +827,16 @@ func _on_handle_added() -> void:
return
update_handles()
+ var first_inner_selection := State.inner_selections[0]
if State.root_element.get_xnode(State.semi_selected_xid).get_attribute("d").\
- get_commands()[State.inner_selections[0]].command_char in "Zz":
+ get_commands()[first_inner_selection].command_char in "Zz":
+ dragged_handle = null
State.queue_svg_save()
return
for handle in handles:
if handle is PathHandle and handle.element.xid == State.semi_selected_xid and\
- handle.command_index == State.inner_selections[0]:
+ handle.command_index == first_inner_selection:
State.set_hovered(handle.element.xid, handle.command_index)
dragged_handle = handle
# Move the handle that's being dragged.
@@ -839,7 +851,9 @@ func _on_handle_added() -> void:
# Creates a popup for adding a shape at a position.
func create_element_context(precise_pos: PackedFloat64Array) -> ContextPopup:
var btn_array: Array[Button] = []
- for shape in ["path", "circle", "ellipse", "rect", "line", "polygon", "polyline"]:
+ const CONST_ARR: PackedStringArray = ["path", "circle", "ellipse", "rect", "line",
+ "polygon", "polyline"]
+ for shape in CONST_ARR:
var btn := ContextPopup.create_button(shape,
add_shape_at_pos.bind(shape, precise_pos), false, DB.get_element_icon(shape))
btn.add_theme_font_override("font", ThemeUtils.mono_font)
diff --git a/src/ui_parts/import_warning_menu.gd b/src/ui_parts/import_warning_menu.gd
index 3c9f121..78dd129 100644
--- a/src/ui_parts/import_warning_menu.gd
+++ b/src/ui_parts/import_warning_menu.gd
@@ -1,5 +1,7 @@
extends PanelContainer
+var import_success := false
+
signal imported
signal canceled
@@ -11,11 +13,19 @@ signal canceled
var imported_text := ""
+func _exit_tree() -> void:
+ if import_success:
+ imported.emit()
+ else:
+ canceled.emit()
+
+func finish_import() -> void:
+ import_success = true
+ queue_free()
+
func _ready() -> void:
- imported.connect(queue_free)
- ok_button.pressed.connect(imported.emit)
- canceled.connect(queue_free)
- cancel_button.pressed.connect(canceled.emit)
+ ok_button.pressed.connect(finish_import)
+ cancel_button.pressed.connect(queue_free)
# Convert forward and backward to show how GodSVG would display the given SVG.
var imported_text_parse_result := SVGParser.text_to_root(imported_text)
@@ -38,14 +48,14 @@ func _ready() -> void:
else:
var svg_warnings := get_svg_warnings(imported_text_parse_result.svg)
if svg_warnings.is_empty():
- imported.emit()
+ finish_import()
else:
warnings_label.add_theme_color_override("default_color",
Configs.savedata.basic_color_warning)
for warning in svg_warnings:
warnings_label.text += warning + "\n"
ok_button.grab_focus()
- $VBoxContainer/Title.text = Translator.translate("Import Problems")
+ $VBoxContainer/TitleLabel.text = Translator.translate("Import Problems")
ok_button.text = Translator.translate("Import")
cancel_button.text = Translator.translate("Cancel")
@@ -68,8 +78,7 @@ func get_svg_warnings(root_element: ElementRoot) -> PackedStringArray:
unrecognized_attributes.append(attribute.name)
var warnings := PackedStringArray()
for element in unrecognized_elements:
- warnings.append("%s: %s" % [Translator.translate("Unrecognized element"),
- element])
+ warnings.append("%s: %s" % [Translator.translate("Unrecognized element"), element])
for attribute in unrecognized_attributes:
warnings.append("%s: %s" % [Translator.translate("Unrecognized attribute"),
attribute])
diff --git a/src/ui_parts/import_warning_menu.tscn b/src/ui_parts/import_warning_menu.tscn
index 4b17a50..2a6594d 100644
--- a/src/ui_parts/import_warning_menu.tscn
+++ b/src/ui_parts/import_warning_menu.tscn
@@ -23,8 +23,9 @@ script = ExtResource("1_1rv5w")
layout_mode = 2
theme_override_constants/separation = 12
-[node name="Title" type="Label" parent="VBoxContainer"]
+[node name="TitleLabel" type="Label" parent="VBoxContainer"]
layout_mode = 2
+theme_override_font_sizes/font_size = 15
horizontal_alignment = 1
[node name="TextureContainer" type="HBoxContainer" parent="VBoxContainer"]
diff --git a/src/ui_parts/inspector.gd b/src/ui_parts/inspector.gd
index 1e20365..c58475f 100644
--- a/src/ui_parts/inspector.gd
+++ b/src/ui_parts/inspector.gd
@@ -1,8 +1,5 @@
extends VTitledPanel
-const ElementFrame = preload("res://src/ui_widgets/element_frame.tscn")
-const BasicXNodeFrame = preload("res://src/ui_widgets/basic_xnode_frame.tscn")
-
@onready var xnodes_container: VBoxContainer = %RootChildren
@onready var add_button: Button = $ActionContainer/AddButton
diff --git a/src/ui_parts/mac_menu.gd b/src/ui_parts/mac_menu.gd
index e658bb9..711d924 100644
--- a/src/ui_parts/mac_menu.gd
+++ b/src/ui_parts/mac_menu.gd
@@ -33,16 +33,26 @@ var snap_4_idx: int
func _enter_tree() -> void:
- Configs.language_changed.connect(_reset_menus)
- Configs.shortcuts_changed.connect(_reset_menu_items)
- # Included menus.
+ # Included menus
global_rid = NativeMenu.get_system_menu(NativeMenu.MAIN_MENU_ID)
appl_rid = NativeMenu.get_system_menu(NativeMenu.APPLICATION_MENU_ID)
help_rid = NativeMenu.get_system_menu(NativeMenu.HELP_MENU_ID)
- # Custom menus.
+ # Custom menus
_generate_main_menus()
_setup_menu_items()
+ # Updates
+ Configs.language_changed.connect(_reset_menus)
+ Configs.shortcuts_changed.connect(_setup_menu_items)
+ Configs.snap_changed.connect(_on_snap_changed)
+ _on_snap_changed()
+ State.view_rasterized_changed.connect(_on_view_rasterized_changed)
+ _on_view_rasterized_changed()
+ State.show_grid_changed.connect(_on_show_grid_changed)
+ _on_show_grid_changed()
+ State.show_handles_changed.connect(_on_show_handles_changed)
+ _on_show_handles_changed()
State.svg_changed.connect(_on_svg_changed)
+ _on_svg_changed()
func _reset_menus() -> void:
@@ -79,10 +89,6 @@ func _generate_main_menus() -> void:
Translator.translate("Snap"), snap_rid)
-func _reset_menu_items() -> void:
- _setup_menu_items()
-
-
func _clear_menu_items() -> void:
NativeMenu.clear(appl_rid)
NativeMenu.clear(help_rid)
@@ -112,7 +118,6 @@ func _setup_menu_items() -> void:
file_optimize_idx = _add_action(file_rid, "optimize")
NativeMenu.add_separator(file_rid)
file_reset_svg_idx = _add_action(file_rid, "reset_svg")
- _on_svg_changed()
# Edit and Tool menus.
_add_many_actions(edit_rid, ShortcutUtils.get_shortcuts("edit"))
_add_many_actions(tool_rid, ShortcutUtils.get_shortcuts("tool"))
@@ -120,7 +125,6 @@ func _setup_menu_items() -> void:
view_show_grid_idx = _add_check_item(view_rid, "view_show_grid")
view_show_handles_idx = _add_check_item(view_rid, "view_show_handles")
view_rasterized_svg_idx = _add_check_item(view_rid, "view_rasterized_svg")
- _on_display_view_settings_updated(true, true, false)
NativeMenu.add_separator(view_rid)
_add_action(view_rid, "zoom_in")
_add_action(view_rid, "zoom_out")
@@ -136,29 +140,29 @@ func _setup_menu_items() -> void:
snap_4_idx = NativeMenu.add_radio_check_item(snap_rid, "4", _set_snap, _set_snap, 4)
-func _add_many_actions(menu_rid: RID, actions: Array) -> void:
+func _add_many_actions(menu_rid: RID, actions: PackedStringArray) -> void:
for action in actions:
_add_action(menu_rid, action)
func _add_action(menu_rid: RID, action_name: StringName) -> int:
- var display_name = _get_action_display_name(action_name)
- var key = _get_keycode_for_events(InputMap.action_get_events(action_name))
+ var display_name := _get_action_display_name(action_name)
+ var key := _get_keycode_for_events(InputMap.action_get_events(action_name))
return NativeMenu.add_item(menu_rid, display_name, _action_call, _action_call, action_name, key)
func _add_check_item(menu_rid: RID, action_name: StringName) -> int:
- var display_name = _get_action_display_name(action_name)
+ var display_name := _get_action_display_name(action_name)
return NativeMenu.add_check_item(menu_rid, display_name, _action_call, _action_call, action_name)
func _add_icon_item(menu_rid: RID, action_name: StringName, icon: Texture2D) -> int:
- var display_name = _get_action_display_name(action_name)
+ var display_name := _get_action_display_name(action_name)
return NativeMenu.add_icon_item(menu_rid, icon, display_name, _action_call, _action_call, action_name)
func _get_action_display_name(action_name: StringName) -> String:
- var display_name = TranslationUtils.get_shortcut_description(action_name)
+ var display_name := TranslationUtils.get_shortcut_description(action_name)
if display_name.is_empty():
display_name = action_name.capitalize().replace("Svg", "SVG")
return display_name
@@ -167,7 +171,7 @@ func _get_action_display_name(action_name: StringName) -> String:
func _get_keycode_for_events(input_events: Array[InputEvent]) -> Key:
for input_event in input_events:
if input_event is InputEventKey:
- var key = input_event.get_keycode_with_modifiers()
+ var key: Key = input_event.get_keycode_with_modifiers()
if key != KEY_NONE:
return key
key = input_event.get_physical_keycode_with_modifiers()
@@ -180,13 +184,18 @@ func _on_svg_changed() -> void:
NativeMenu.set_item_disabled(file_rid, file_reset_svg_idx,
FileUtils.compare_svg_to_disk_contents() == FileUtils.FileState.DIFFERENT)
-func _on_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_view_rasterized_changed() -> void:
+ NativeMenu.set_item_checked(view_rid, view_rasterized_svg_idx, State.view_rasterized)
+
+func _on_show_grid_changed() -> void:
+ NativeMenu.set_item_checked(view_rid, view_show_grid_idx, State.show_grid)
-func _on_display_snap_settings_updated(snap_enabled: bool, snap_amount: float) -> void:
- NativeMenu.set_item_checked(snap_rid, snap_enable_idx, snap_enabled)
+func _on_show_handles_changed() -> void:
+ NativeMenu.set_item_checked(view_rid, view_show_handles_idx, State.show_handles)
+
+func _on_snap_changed() -> void:
+ var snap_amount := absf(Configs.savedata.snap)
+ NativeMenu.set_item_checked(snap_rid, snap_enable_idx, Configs.savedata.snap > 0)
NativeMenu.set_item_checked(snap_rid, snap_0125_idx, is_equal_approx(snap_amount, 0.125))
NativeMenu.set_item_checked(snap_rid, snap_025_idx, is_equal_approx(snap_amount, 0.25))
NativeMenu.set_item_checked(snap_rid, snap_05_idx, is_equal_approx(snap_amount, 0.5))
@@ -194,13 +203,12 @@ func _on_display_snap_settings_updated(snap_enabled: bool, snap_amount: float) -
NativeMenu.set_item_checked(snap_rid, snap_2_idx, is_equal_approx(snap_amount, 2))
NativeMenu.set_item_checked(snap_rid, snap_4_idx, is_equal_approx(snap_amount, 4))
-
-func _set_snap(tag: float) -> void:
- %Display.set_snap_amount(tag)
+func _set_snap(amount: float) -> void:
+ Configs.savedata.snap = amount
func _action_call(tag: StringName) -> void:
- var a = InputEventAction.new()
+ var a := InputEventAction.new()
a.action = tag
a.pressed = true
Input.parse_input_event(a)
diff --git a/src/ui_parts/root_element_editor.gd b/src/ui_parts/root_element_editor.gd
index 5c41b03..dc69f50 100644
--- a/src/ui_parts/root_element_editor.gd
+++ b/src/ui_parts/root_element_editor.gd
@@ -1,28 +1,20 @@
extends VBoxContainer
+const NumberEdit = preload("res://src/ui_widgets/number_edit.gd")
+
# So, about this editor. Width and height don't have default values, so they use NAN and
# use NumberEdit, rather than NumberField. Viewbox is a list and it also doesn't have a
# default value, so it uses 4 NumberEdits.
-const UnrecognizedField = preload("res://src/ui_widgets/unrecognized_field.tscn")
-const ColorField = preload("res://src/ui_widgets/color_field.tscn")
-const NumberField = preload("res://src/ui_widgets/number_field.tscn")
-const NumberSlider = preload("res://src/ui_widgets/number_field_with_slider.tscn")
-const IDField = preload("res://src/ui_widgets/id_field.tscn")
-const EnumField = preload("res://src/ui_widgets/enum_field.tscn")
-const TransformField = preload("res://src/ui_widgets/transform_field.tscn")
-
-const NumberEditType = preload("res://src/ui_widgets/number_edit.gd")
-
@onready var width_button: Button = %Size/Width/WidthButton
@onready var height_button: Button = %Size/Height/HeightButton
@onready var viewbox_button: Button = %Viewbox/ViewboxButton
-@onready var width_edit: NumberEditType = %Size/Width/WidthEdit
-@onready var height_edit: NumberEditType = %Size/Height/HeightEdit
-@onready var viewbox_edit_x: NumberEditType = %Viewbox/Rect/ViewboxEditX
-@onready var viewbox_edit_y: NumberEditType = %Viewbox/Rect/ViewboxEditY
-@onready var viewbox_edit_w: NumberEditType = %Viewbox/Rect/ViewboxEditW
-@onready var viewbox_edit_h: NumberEditType = %Viewbox/Rect/ViewboxEditH
+@onready var width_edit: NumberEdit = %Size/Width/WidthEdit
+@onready var height_edit: NumberEdit = %Size/Height/HeightEdit
+@onready var viewbox_edit_x: NumberEdit = %Viewbox/Rect/ViewboxEditX
+@onready var viewbox_edit_y: NumberEdit = %Viewbox/Rect/ViewboxEditY
+@onready var viewbox_edit_w: NumberEdit = %Viewbox/Rect/ViewboxEditW
+@onready var viewbox_edit_h: NumberEdit = %Viewbox/Rect/ViewboxEditH
@onready var unknown_container: MarginContainer
func _ready() -> void:
@@ -38,6 +30,13 @@ func _ready() -> void:
width_button.toggled.connect(_on_width_button_toggled)
height_button.toggled.connect(_on_height_button_toggled)
viewbox_button.toggled.connect(_on_viewbox_button_toggled)
+
+ for control: Control in [width_edit, height_edit,
+ viewbox_edit_x, viewbox_edit_y, viewbox_edit_w, viewbox_edit_h]:
+ control.focus_entered.connect(State.clear_all_selections)
+
+ for button: Button in [width_button, height_button, viewbox_button]:
+ button.pressed.connect(State.clear_all_selections)
func _on_any_attribute_changed(xid: PackedInt32Array) -> void:
if xid.is_empty():
@@ -58,14 +57,17 @@ func update_attributes() -> void:
if is_instance_valid(unknown_container):
unknown_container.queue_free()
unknown_container = MarginContainer.new()
+ unknown_container.begin_bulk_theme_override()
unknown_container.add_theme_constant_override("margin_left", 4)
unknown_container.add_theme_constant_override("margin_right", 4)
+ unknown_container.end_bulk_theme_override()
var unknown_container_child := HFlowContainer.new()
unknown_container.add_child(unknown_container_child)
add_child(unknown_container)
move_child(unknown_container, 0)
var input_field := AttributeFieldBuilder.create(attribute.name, State.root_element)
+ input_field.focus_entered.connect(State.clear_all_selections)
unknown_container.get_child(0).add_child(input_field)
if not has_unrecognized_attributes and is_instance_valid(unknown_container):
unknown_container.queue_free()
diff --git a/src/ui_parts/root_element_editor.tscn b/src/ui_parts/root_element_editor.tscn
index 381c1fd..ce2d828 100644
--- a/src/ui_parts/root_element_editor.tscn
+++ b/src/ui_parts/root_element_editor.tscn
@@ -1,9 +1,9 @@
[gd_scene load_steps=5 format=3 uid="uid://bktmk76u7dsu0"]
-[ext_resource type="Script" path="res://src/ui_parts/root_element_editor.gd" id="1_xgyg0"]
+[ext_resource type="Script" uid="uid://beukt3a23d5ug" path="res://src/ui_parts/root_element_editor.gd" id="1_xgyg0"]
[ext_resource type="FontFile" uid="uid://depydd16jq777" path="res://assets/fonts/FontMono.ttf" id="2_fm5sa"]
[ext_resource type="PackedScene" uid="uid://dad7fkhmsooc6" path="res://src/ui_widgets/number_edit.tscn" id="3_1gu7n"]
-[ext_resource type="Script" path="res://src/ui_widgets/BetterToggleButton.gd" id="4_7r848"]
+[ext_resource type="Script" uid="uid://ynx3s1jc6bwq" path="res://src/ui_widgets/BetterToggleButton.gd" id="4_7r848"]
[node name="RootElementEditor" type="VBoxContainer"]
offset_right = 452.0
@@ -101,6 +101,3 @@ allow_lower = false
custom_minimum_size = Vector2(48, 22)
layout_mode = 2
allow_lower = false
-
-[connection signal="value_changed" from="CenterContainer/Edits/Viewbox/Rect/ViewboxEditW" to="CenterContainer" method="_on_viewbox_edit_w_value_changed"]
-[connection signal="value_changed" from="CenterContainer/Edits/Viewbox/Rect/ViewboxEditH" to="CenterContainer" method="_on_viewbox_edit_h_value_changed"]
diff --git a/src/ui_parts/settings_menu.gd b/src/ui_parts/settings_menu.gd
index 7134f5c..b13e715 100644
--- a/src/ui_parts/settings_menu.gd
+++ b/src/ui_parts/settings_menu.gd
@@ -1,17 +1,18 @@
extends PanelContainer
-const PaletteConfigWidget = preload("res://src/ui_widgets/palette_config.tscn")
-const ShortcutConfigWidget = preload("res://src/ui_widgets/setting_shortcut.tscn")
-#const ShortcutShowcaseWidget = preload("res://src/ui_widgets/presented_shortcut.tscn")
-const SettingFrame = preload("res://src/ui_widgets/setting_frame.tscn")
-const ProfileFrame = preload("res://src/ui_widgets/profile_frame.tscn")
+const PaletteConfigWidgetScene = preload("res://src/ui_widgets/palette_config.tscn")
+const ShortcutConfigWidgetScene = preload("res://src/ui_widgets/setting_shortcut.tscn")
+const ShortcutShowcaseWidgetScene = preload("res://src/ui_widgets/presented_shortcut.tscn")
+const SettingFrameScene = preload("res://src/ui_widgets/setting_frame.tscn")
+const ProfileFrameScene = preload("res://src/ui_widgets/profile_frame.tscn")
const plus_icon = preload("res://assets/icons/Plus.svg")
const import_icon = preload("res://assets/icons/Import.svg")
const reset_icon = preload("res://assets/icons/Reload.svg")
@onready var lang_button: Button = $VBoxContainer/Language
-@onready var content_container: ScrollContainer = %ContentContainer
+@onready var scroll_container: ScrollContainer = %ScrollContainer
+@onready var content_container: MarginContainer = %ScrollContainer/ContentContainer
@onready var tabs: VBoxContainer = %Tabs
@onready var close_button: Button = $VBoxContainer/CloseButton
@onready var advice_panel: PanelContainer = $VBoxContainer/AdvicePanel
@@ -26,6 +27,10 @@ var advice: Dictionary[String, String] = {}
func _ready() -> void:
close_button.pressed.connect(queue_free)
Configs.language_changed.connect(setup_everything)
+
+ scroll_container.get_v_scroll_bar().visibility_changed.connect(adjust_right_margin)
+ adjust_right_margin()
+
update_language_button()
update_close_button()
setup_tabs()
@@ -40,6 +45,11 @@ func setup_theming() -> void:
stylebox.content_margin_top += 4.0
add_theme_stylebox_override("panel", stylebox)
+func adjust_right_margin() -> void:
+ var scrollbar := scroll_container.get_v_scroll_bar()
+ content_container.add_theme_constant_override("margin_right",
+ 2 if scrollbar.visible else int(2 + scrollbar.size.x))
+
func setup_tabs() -> void:
for tab in tabs.get_children():
tab.queue_free()
@@ -80,7 +90,7 @@ func _on_tab_toggled(toggled_on: bool, tab_name: String) -> void:
setup_content()
func setup_content() -> void:
- content_container.scroll_vertical = 0
+ scroll_container.scroll_vertical = 0
for child in content_container.get_children():
child.queue_free()
@@ -92,6 +102,7 @@ func setup_content() -> void:
vbox.add_theme_constant_override("separation", 6)
content_container.add_child(vbox)
var categories := HFlowContainer.new()
+ categories.alignment = FlowContainer.ALIGNMENT_CENTER
var button_group := ButtonGroup.new()
for tab_idx in formatter_tab_names:
var btn := Button.new()
@@ -195,11 +206,11 @@ func setup_content() -> void:
current_setup_setting = "invert_zoom"
add_checkbox(Translator.translate("Invert zoom direction"))
add_advice(Translator.translate(
- "Swaps zoom in and zoom out with the mouse wheel."))
- current_setup_setting = "wrap_mouse"
- var wrap_mouse := add_checkbox(Translator.translate("Wrap mouse"))
+ "Swaps the scroll directions for zooming in and zooming out."))
+ current_setup_setting = "wraparound_panning"
+ var wraparound_panning := add_checkbox(Translator.translate("Wrap-around panning"))
add_advice(Translator.translate(
- "Wraps the mouse cursor around when panning the viewport."))
+ "Warps the cursor to the opposite side whenever it reaches a viewport boundary while panning."))
current_setup_setting = "use_ctrl_for_zoom"
add_checkbox(Translator.translate("Use CTRL for zooming"))
add_advice(Translator.translate(
@@ -236,7 +247,7 @@ func setup_content() -> void:
# Disable mouse wrap if not available.
if not DisplayServer.has_feature(DisplayServer.FEATURE_MOUSE_WARP):
- wrap_mouse.permanent_disable_checkbox(false)
+ wraparound_panning.permanent_disable_checkbox(false)
# Disable fallback file dialog on web, and native file dialog if not available.
if OS.has_feature("web"):
use_native_file_dialog.permanent_disable_checkbox(true)
@@ -248,6 +259,7 @@ func add_section(section_name: String) -> void:
var vbox := VBoxContainer.new()
vbox.add_theme_constant_override("separation", 0)
var label := Label.new()
+ label.add_theme_font_size_override("font_size", 15)
label.text = section_name
vbox.add_child(label)
var spacer := Control.new()
@@ -256,7 +268,7 @@ func add_section(section_name: String) -> void:
setting_container.add_child(vbox)
func add_checkbox(text: String, dim_text := false) -> Control:
- var frame := SettingFrame.instantiate()
+ var frame := SettingFrameScene.instantiate()
frame.dim_text = dim_text
frame.text = text
setup_frame(frame)
@@ -264,17 +276,19 @@ func add_checkbox(text: String, dim_text := false) -> Control:
add_frame(frame)
return frame
-func add_dropdown(text: String) -> Control:
- var frame := SettingFrame.instantiate()
+# TODO Typed Dictionary wonkiness
+func add_dropdown(text: String, values: Array[Variant],
+value_text_map: Dictionary) -> Control: # Dictionary[Variant, String]
+ var frame := SettingFrameScene.instantiate()
frame.text = text
setup_frame(frame)
- frame.setup_dropdown(current_setup_resource.get_enum_texts(current_setup_setting))
+ frame.setup_dropdown(values, value_text_map)
add_frame(frame)
return frame
func add_number_dropdown(text: String, values: Array[float], is_integer := false,
restricted := true, min_value := -INF, max_value := INF, dim_text := false) -> Control:
- var frame := SettingFrame.instantiate()
+ var frame := SettingFrameScene.instantiate()
frame.dim_text = dim_text
frame.text = text
setup_frame(frame)
@@ -283,7 +297,7 @@ restricted := true, min_value := -INF, max_value := INF, dim_text := false) -> C
return frame
func add_color_edit(text: String, enable_alpha := true) -> Control:
- var frame := SettingFrame.instantiate()
+ var frame := SettingFrameScene.instantiate()
frame.text = text
setup_frame(frame)
frame.setup_color(enable_alpha)
@@ -293,7 +307,7 @@ func add_color_edit(text: String, enable_alpha := true) -> Control:
func setup_frame(frame: Control) -> void:
var bind := current_setup_setting
frame.getter = current_setup_resource.get.bind(bind)
- frame.setter = func(p): current_setup_resource.set(bind, p)
+ frame.setter = func(p: Variant) -> void: current_setup_resource.set(bind, p)
frame.default = current_setup_resource.get_setting_default(current_setup_setting)
frame.mouse_entered.connect(show_advice.bind(current_setup_setting))
frame.mouse_exited.connect(hide_advice.bind(current_setup_setting))
@@ -308,6 +322,11 @@ func add_advice(text: String) -> void:
func show_advice(setting: String) -> void:
if advice.has(setting):
advice_label.text = advice[setting]
+ advice_label.remove_theme_font_size_override("font_size")
+ var advice_font_size := get_theme_font_size("font_size", "Label")
+ while advice_label.get_line_count() > 2:
+ advice_font_size -= 1
+ advice_label.add_theme_font_size_override("font_size", advice_font_size)
func hide_advice(setting: String) -> void:
if advice.has(setting) and advice_label.text == advice[setting]:
@@ -318,7 +337,9 @@ func _on_language_pressed() -> void:
var strings_count := TranslationServer.get_translation_object("en").get_message_count()
var btn_arr: Array[Button] = []
- for lang in TranslationServer.get_loaded_locales():
+ for locale in TranslationServer.get_loaded_locales():
+ var is_current_locale := (locale == TranslationServer.get_locale())
+
# Translation percentages.
# TODO Godot drove me insane here. So Translation.get_translated_message() gets
# all the translations, even the fuzzied ones that aren't used... whuh?
@@ -326,20 +347,15 @@ func _on_language_pressed() -> void:
# for all the translations... except the fuzzied ones for some reason? WHAT?!
# We solve this by finding the number of fuzzied strings by subtracting the
# message count of English messages by the message count of the locale.
- if lang != "en":
- var translation_obj := TranslationServer.get_translation_object(lang)
- var fuzzied_count := strings_count - translation_obj.get_message_count()
- var translated_count := -fuzzied_count
- for msg in translation_obj.get_message_list():
- if not msg.is_empty():
- translated_count += 1
+ if locale != "en":
+ var translation_obj := TranslationServer.get_translation_object(locale)
+ var translated_count := 2 * translation_obj.get_message_count() -\
+ strings_count - translation_obj.get_translated_message_list().count("")
var percentage :=\
Utils.num_simple(translated_count * 100.0 / strings_count, 1) + "%"
- var is_current_locale := (lang == TranslationServer.get_locale())
var new_btn := ContextPopup.create_button(
- TranslationServer.get_locale_name(lang) + " (" + lang.to_upper() + ")",
- Callable(), is_current_locale)
+ TranslationUtils.get_locale_display(locale), Callable(), is_current_locale)
var ret_button := Button.new()
ret_button.theme_type_variation = "ContextButton"
@@ -349,12 +365,18 @@ func _on_language_pressed() -> void:
ret_button.disabled = true
else:
ret_button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
- for theme_item in ["normal", "hover", "pressed", "disabled"]:
- new_btn.add_theme_stylebox_override(theme_item,
+
+ new_btn.begin_bulk_theme_override()
+
+ const CONST_ARR: PackedStringArray = ["normal", "hover", "pressed", "disabled"]
+ for theme_type in CONST_ARR:
+ new_btn.add_theme_stylebox_override(theme_type,
new_btn.get_theme_stylebox("normal", "ContextButton"))
+ new_btn.end_bulk_theme_override()
+
var internal_hbox := HBoxContainer.new()
new_btn.mouse_filter = Control.MOUSE_FILTER_IGNORE # Unpressable.
- internal_hbox.add_theme_constant_override("separation", 6)
+ internal_hbox.add_theme_constant_override("separation", 12)
new_btn.add_theme_color_override("icon_normal_color",
ret_button.get_theme_color("icon_normal_color", "ContextButton"))
var label_margin := MarginContainer.new()
@@ -378,26 +400,27 @@ func _on_language_pressed() -> void:
label_margin.add_child(label)
internal_hbox.add_child(label_margin)
ret_button.add_child(internal_hbox)
- ret_button.pressed.connect(_on_language_chosen.bind(lang))
+ ret_button.pressed.connect(_on_language_chosen.bind(locale))
ret_button.pressed.connect(HandlerGUI.remove_popup)
btn_arr.append(ret_button)
else:
var new_btn := ContextPopup.create_button(
- TranslationServer.get_locale_name(lang) + " (" + lang.to_upper() + ")",
- _on_language_chosen.bind(lang), lang == TranslationServer.get_locale())
+ TranslationUtils.get_locale_display(locale),
+ _on_language_chosen.bind(locale), is_current_locale)
btn_arr.append(new_btn)
var lang_popup := ContextPopup.new()
lang_popup.setup(btn_arr, true)
- HandlerGUI.popup_under_rect_center(lang_popup, lang_button.get_global_rect(), get_viewport())
+ HandlerGUI.popup_under_rect_center(lang_popup, lang_button.get_global_rect(),
+ get_viewport())
func _on_language_chosen(locale: String) -> void:
Configs.savedata.language = locale
func update_language_button() -> void:
lang_button.text = Translator.translate("Language") + ": " +\
- TranslationServer.get_locale().to_upper()
+ TranslationUtils.get_locale_string(TranslationServer.get_locale())
# Palette tab helpers.
@@ -442,7 +465,7 @@ func rebuild_palettes() -> void:
for palette_config in palette_container.get_children():
palette_config.queue_free()
for palette in Configs.savedata.get_palettes():
- var palette_config := PaletteConfigWidget.instantiate()
+ var palette_config := PaletteConfigWidgetScene.instantiate()
palette_container.add_child(palette_config)
palette_config.assign_palette(palette)
palette_config.layout_changed.connect(rebuild_palettes)
@@ -512,12 +535,12 @@ func show_formatter(category: String) -> void:
button.pressed.connect(current_setup_resource.reset_to_default)
# The preset field shouldn't have a reset button or a section, so set it up manually.
- var frame := ProfileFrame.instantiate()
- frame.setup_dropdown(true)
+ var frame := ProfileFrameScene.instantiate()
+ frame.setup_dropdown(range(Formatter.Preset.size()),
+ Formatter.get_preset_value_text_map())
frame.getter = current_setup_resource.get.bind("preset")
- frame.setter = func(p): current_setup_resource.set("preset", p)
+ frame.setter = func(p: Variant) -> void: current_setup_resource.set("preset", p)
frame.text = Translator.translate("Preset")
- frame.dropdown.values = Formatter.get_enum_texts("preset")
setting_container.add_child(frame)
add_section("XML")
@@ -528,7 +551,9 @@ func show_formatter(category: String) -> void:
current_setup_setting = "xml_add_trailing_newline"
add_checkbox(Translator.translate("Add trailing newline"))
current_setup_setting = "xml_shorthand_tags"
- add_dropdown(Translator.translate("Use shorthand tag syntax"))
+ add_dropdown(Translator.translate("Use shorthand tag syntax"),
+ range(Formatter.ShorthandTags.size()),
+ Formatter.get_shorthand_tags_value_text_map())
current_setup_setting = "xml_shorthand_tags_space_out_slash"
add_checkbox(Translator.translate("Space out the slash of shorthand tags"))
current_setup_setting = "xml_pretty_formatting"
@@ -550,9 +575,13 @@ func show_formatter(category: String) -> void:
add_section(Translator.translate("Colors"))
current_setup_setting = "color_use_named_colors"
- add_dropdown(Translator.translate("Use named colors"))
+ add_dropdown(Translator.translate("Use named colors"),
+ range(Formatter.NamedColorUse.size()),
+ Formatter.get_named_color_use_value_text_map())
current_setup_setting = "color_primary_syntax"
- add_dropdown(Translator.translate("Primary syntax"))
+ add_dropdown(Translator.translate("Primary syntax"),
+ range(Formatter.PrimaryColorSyntax.size()),
+ Formatter.get_primary_color_syntax_value_text_map())
current_setup_setting = "color_capital_hex"
add_checkbox(Translator.translate("Capitalize hexadecimal letters"),
current_setup_resource.color_primary_syntax == Formatter.PrimaryColorSyntax.RGB)
@@ -576,19 +605,19 @@ func show_formatter(category: String) -> void:
add_checkbox(Translator.translate("Remove unnecessary parameters"))
-#func show_shortcuts(category: String) -> void:
- #var shortcuts_container := content_container.get_child(-1).get_child(-1)
- #for child in shortcuts_container.get_children():
- #child.queue_free()
- #
- #for action in ShortcutUtils.get_shortcuts(category):
- #var shortcut_config := ShortcutConfigWidget.instantiate() if\
- #ShortcutUtils.is_shortcut_modifiable(action) else\
- #ShortcutShowcaseWidget.instantiate()
- #
- #shortcuts_container.add_child(shortcut_config)
- #shortcut_config.label.text = TranslationUtils.get_shortcut_description(action)
- #shortcut_config.setup(action)
+func show_shortcuts(category: String) -> void:
+ var shortcuts_container := content_container.get_child(-1).get_child(-1)
+ for child in shortcuts_container.get_children():
+ child.queue_free()
+
+ for action in ShortcutUtils.get_shortcuts(category):
+ var shortcut_config := ShortcutConfigWidgetScene.instantiate() if\
+ ShortcutUtils.is_shortcut_modifiable(action) else\
+ ShortcutShowcaseWidgetScene.instantiate()
+
+ shortcuts_container.add_child(shortcut_config)
+ shortcut_config.label.text = TranslationUtils.get_shortcut_description(action)
+ shortcut_config.setup(action)
func create_setting_container() -> void:
setting_container = VBoxContainer.new()
diff --git a/src/ui_parts/settings_menu.tscn b/src/ui_parts/settings_menu.tscn
index 94d5ec7..a42c3ad 100644
--- a/src/ui_parts/settings_menu.tscn
+++ b/src/ui_parts/settings_menu.tscn
@@ -45,12 +45,20 @@ size_flags_vertical = 3
mouse_filter = 1
theme_type_variation = &"SideBarContent"
-[node name="ContentContainer" type="ScrollContainer" parent="VBoxContainer/VBoxContainer/PanelContainer"]
+[node name="ScrollContainer" type="ScrollContainer" parent="VBoxContainer/VBoxContainer/PanelContainer"]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 120)
layout_mode = 2
horizontal_scroll_mode = 0
+[node name="ContentContainer" type="MarginContainer" parent="VBoxContainer/VBoxContainer/PanelContainer/ScrollContainer"]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+theme_override_constants/margin_top = 10
+theme_override_constants/margin_right = 2
+theme_override_constants/margin_bottom = 4
+
[node name="AdvicePanel" type="PanelContainer" parent="VBoxContainer"]
custom_minimum_size = Vector2(0, 45)
layout_mode = 2
@@ -60,7 +68,7 @@ theme_type_variation = &"TextBox"
layout_mode = 2
size_flags_vertical = 1
theme_override_colors/font_color = Color(0.878431, 0.878431, 0.878431, 1)
-theme_override_font_sizes/font_size = 13
+theme_override_constants/line_spacing = 2
autowrap_mode = 3
[node name="CloseButton" type="Button" parent="VBoxContainer"]
diff --git a/src/ui_parts/shortcut_panel.gd b/src/ui_parts/shortcut_panel.gd
index aba0b49..b058d5d 100644
--- a/src/ui_parts/shortcut_panel.gd
+++ b/src/ui_parts/shortcut_panel.gd
@@ -2,7 +2,14 @@ extends PanelContainer
enum Layout {HORIZONTAL_STRIP, HORIZONTAL_TWO_ROWS, VERTICAL_STRIP}
-const ShortcutPanelConfig = preload("res://src/ui_parts/shortcut_panel_config.tscn")
+static func get_preset_value_text_map() -> Dictionary:
+ return {
+ Layout.HORIZONTAL_STRIP: Translator.translate("Horizontal strip"),
+ Layout.VERTICAL_STRIP: Translator.translate("Vertical strip"),
+ Layout.HORIZONTAL_TWO_ROWS: Translator.translate("Horizontal with two rows"),
+ }
+
+const ShortcutPanelConfigScene = preload("res://src/ui_parts/shortcut_panel_config.tscn")
const dot_pattern = preload("res://assets/icons/DotPatternSegment.svg")
const config_icon = preload("res://assets/icons/Config.svg")
@@ -94,8 +101,10 @@ func update_layout() -> void:
var margin_container := MarginContainer.new()
margin_container.begin_bulk_theme_override()
- for margin in ["margin_left", "margin_right", "margin_top", "margin_bottom"]:
- margin_container.add_theme_constant_override(margin, 4)
+ const CONST_ARR: PackedStringArray = ["margin_left", "margin_right", "margin_top",
+ "margin_bottom"]
+ for theme_type in CONST_ARR:
+ margin_container.add_theme_constant_override(theme_type, 4)
margin_container.begin_bulk_theme_override()
main_container.add_child(margin_container)
@@ -143,7 +152,7 @@ func simulate_key_press(action_name: String) -> void:
return
func _on_config_button_pressed() -> void:
- HandlerGUI.add_menu(ShortcutPanelConfig.instantiate())
+ HandlerGUI.add_menu(ShortcutPanelConfigScene.instantiate())
func sync_relative_position() -> void:
set_position_relative(position_window_relative)
diff --git a/src/ui_parts/shortcut_panel.tscn b/src/ui_parts/shortcut_panel.tscn
index 9b66ab1..730e0c9 100644
--- a/src/ui_parts/shortcut_panel.tscn
+++ b/src/ui_parts/shortcut_panel.tscn
@@ -1,6 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://jf1wq6xy617n"]
-[ext_resource type="Script" path="res://src/ui_parts/shortcut_panel.gd" id="1_bdakw"]
+[ext_resource type="Script" uid="uid://cqqaic7b2s5xu" path="res://src/ui_parts/shortcut_panel.gd" id="1_bdakw"]
[node name="ShortcutPanel" type="PanelContainer"]
script = ExtResource("1_bdakw")
diff --git a/src/ui_parts/shortcut_panel_config.gd b/src/ui_parts/shortcut_panel_config.gd
index 025defd..87ff138 100644
--- a/src/ui_parts/shortcut_panel_config.gd
+++ b/src/ui_parts/shortcut_panel_config.gd
@@ -1,23 +1,23 @@
extends PanelContainer
+const Dropdown = preload("res://src/ui_widgets/dropdown.gd")
+const ShortcutPanel = preload("res://src/ui_parts/shortcut_panel.gd")
+
const clear_icon = preload("res://assets/icons/Clear.svg")
-const Dropdown = preload("res://src/ui_widgets/dropdown.tscn")
-const DropdownType = preload("res://src/ui_widgets/dropdown.gd")
-const ShortcutPanel = preload("res://src/ui_parts/shortcut_panel.gd")
+const DropdownScene = preload("res://src/ui_widgets/dropdown.tscn")
@onready var close_button: Button = $VBoxContainer/CloseButton
@onready var slot_container: VBoxContainer = %SlotContainer
-@onready var layout_dropdown: HBoxContainer = %LayoutDropdown
+@onready var layout_dropdown: Dropdown = %LayoutDropdown
func _ready() -> void:
close_button.pressed.connect(queue_free)
close_button.text = Translator.translate("Close")
%Title.text = Translator.translate("Configure Shortcut Panel")
%LayoutLabel.text = Translator.translate("Layout")
- layout_dropdown.values = [Translator.translate("Horizontal strip"),
- Translator.translate("Horizontal with two rows"),
- Translator.translate("Vertical strip")]
+ layout_dropdown.values = range(ShortcutPanel.Layout.size())
+ layout_dropdown.value_text_map = ShortcutPanel.get_preset_value_text_map()
layout_dropdown.set_value(Configs.savedata.shortcut_panel_layout)
layout_dropdown.value_changed.connect(_on_layout_dropdown_value_changed)
update_shortcut_slots()
@@ -45,7 +45,7 @@ func update_shortcut_slots() -> void:
icon_presentation.add_child(icon)
hbox.add_child(icon_presentation)
- var dropdown := Dropdown.instantiate()
+ var dropdown := DropdownScene.instantiate()
dropdown.custom_minimum_size = Vector2(100, 28)
dropdown.size_flags_horizontal = Control.SIZE_EXPAND_FILL
dropdown.align_left = true
diff --git a/src/ui_parts/shortcut_panel_config.tscn b/src/ui_parts/shortcut_panel_config.tscn
index a5c1486..bf793e3 100644
--- a/src/ui_parts/shortcut_panel_config.tscn
+++ b/src/ui_parts/shortcut_panel_config.tscn
@@ -1,7 +1,7 @@
[gd_scene load_steps=3 format=3 uid="uid://cnay1l0u6a5tw"]
[ext_resource type="Script" uid="uid://cnmsf3h1qq3q6" path="res://src/ui_parts/shortcut_panel_config.gd" id="1_eg1f5"]
-[ext_resource type="PackedScene" uid="uid://bp1iblavfxjvu" path="res://src/ui_widgets/enum_dropdown.tscn" id="2_pru61"]
+[ext_resource type="PackedScene" uid="uid://dbu1lvajypafb" path="res://src/ui_widgets/dropdown.tscn" id="2_4egw5"]
[node name="ShortcutPanelConfig" type="PanelContainer"]
custom_minimum_size = Vector2(200, 300)
@@ -55,15 +55,12 @@ size_flags_vertical = 4
[node name="LayoutLabel" type="Label" parent="VBoxContainer/PanelContainer/HBoxContainer/ConfigContainer/LayoutBox"]
unique_name_in_owner = true
layout_mode = 2
-theme_override_font_sizes/font_size = 13
-horizontal_alignment = 1
-[node name="LayoutDropdown" parent="VBoxContainer/PanelContainer/HBoxContainer/ConfigContainer/LayoutBox" instance=ExtResource("2_pru61")]
+[node name="LayoutDropdown" parent="VBoxContainer/PanelContainer/HBoxContainer/ConfigContainer/LayoutBox" instance=ExtResource("2_4egw5")]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 1
-values = PackedStringArray()
[node name="CloseButton" type="Button" parent="VBoxContainer"]
unique_name_in_owner = true
diff --git a/src/ui_parts/tab_bar.gd b/src/ui_parts/tab_bar.gd
index 3612360..c2c2e5f 100644
--- a/src/ui_parts/tab_bar.gd
+++ b/src/ui_parts/tab_bar.gd
@@ -1,26 +1,43 @@
extends Control
+const PreviewRectScene = preload("res://src/ui_widgets/preview_rect.tscn")
+
const plus_icon = preload("res://assets/icons/Plus.svg")
const close_icon = preload("res://assets/icons/Close.svg")
+const scroll_forwards_icon = preload("res://assets/icons/ScrollForwards.svg")
+const scroll_backwards_icon = preload("res://assets/icons/ScrollBackwards.svg")
-const TAB_WIDTH = 120.0
+const DEFAULT_TAB_WIDTH = 140.0
+const MIN_TAB_WIDTH = 70.0
const CLOSE_BUTTON_MARGIN = 2
+var ci := get_canvas_item()
+
+var current_scroll := 0.0
+var scrolling_backwards := false
+var scrolling_forwards := false
var active_controls: Array[Control] = []
+# Processing is enabled only when dragging.
var proposed_drop_idx := -1:
set(new_value):
if proposed_drop_idx != new_value:
proposed_drop_idx = new_value
queue_redraw()
+func _exit_tree() -> void:
+ RenderingServer.free_rid(ci)
+
func _ready() -> void:
- Configs.active_tab_file_path_changed.connect(queue_redraw)
Configs.active_tab_changed.connect(activate)
Configs.tabs_changed.connect(activate)
+ Configs.active_tab_changed.connect(scroll_to_active)
+ Configs.tabs_changed.connect(scroll_to_active)
+ resized.connect(scroll_to_active)
Configs.language_changed.connect(queue_redraw)
mouse_entered.connect(_on_mouse_entered)
mouse_exited.connect(_on_mouse_exited)
+ set_process(false)
func _draw() -> void:
var background_stylebox: StyleBoxFlat =\
@@ -30,45 +47,110 @@ func _draw() -> void:
background_stylebox.bg_color = Color(ThemeUtils.common_panel_inner_color, 0.4)
draw_style_box(background_stylebox, get_rect())
+ var has_transient_tab := not State.transient_tab_path.is_empty()
+ var mouse_pos := get_local_mouse_position()
+
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()
+ 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 not rect.has_area():
+ continue
+
+ var current_tab_name := ""
+ if drawing_transient_tab:
+ current_tab_name = State.transient_tab_path.get_file()
+ else:
+ var current_tab := Configs.savedata.get_tab(tab_index)
+ current_tab_name = current_tab.presented_name
+ if current_tab.marked_unsaved:
+ current_tab_name = "* " + current_tab_name
+
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)
+ var text_line_width := rect.size.x - size.y
+ if text_line_width > 0:
+ 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)
+ text_line.width = text_line_width
+ text_line.draw(ci, rect.position + Vector2(4, 3),
+ get_theme_color("font_selected_color", "TabContainer"))
+ var close_rect := get_close_button_rect()
+ if close_rect.has_area():
+ 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 is_hovered := rect.has_point(mouse_pos)
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 text_line_width := rect.size.x - 8
+ if text_line_width > 0:
+ var text_color := get_theme_color("font_hovered_color" if is_hovered else\
+ "font_unselected_color", "TabContainer")
+
+ 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)
+ text_line.width = text_line_width
+ text_line.draw(ci, rect.position + Vector2(4, 3), text_color)
+
+ var add_button_rect := get_add_button_rect()
+ if add_button_rect.has_area():
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)
+ draw_texture_rect(plus_icon, Rect2(add_button_rect.position +\
+ (add_button_rect.size - plus_icon_size) / 2.0, plus_icon_size), false)
+
+ var scroll_backwards_rect := get_scroll_backwards_area_rect()
+ if scroll_backwards_rect.has_area():
+ var scroll_backwards_icon_size := scroll_backwards_icon.get_size()
+ var icon_modulate := Color.WHITE
+ if is_scroll_backwards_disabled():
+ icon_modulate = get_theme_color("icon_disabled_color", "Button")
+ else:
+ var line_x := scroll_backwards_rect.end.x + 1
+ draw_line(Vector2(line_x, 0), Vector2(line_x, size.y),
+ ThemeUtils.common_panel_border_color)
+ if scroll_backwards_rect.has_point(mouse_pos):
+ var stylebox_theme := "pressed" if\
+ Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT) else "hover"
+ get_theme_stylebox(stylebox_theme, "FlatButton").draw(ci,
+ scroll_backwards_rect)
+ draw_texture_rect(scroll_backwards_icon, Rect2(scroll_backwards_rect.position +\
+ (scroll_backwards_rect.size - scroll_backwards_icon_size) / 2.0,
+ scroll_backwards_icon_size), false, icon_modulate)
+
+ var scroll_forwards_rect := get_scroll_forwards_area_rect()
+ if scroll_forwards_rect.has_area():
+ var scroll_forwards_icon_size := scroll_forwards_icon.get_size()
+ var icon_modulate := Color.WHITE
+ if is_scroll_forwards_disabled():
+ icon_modulate = get_theme_color("icon_disabled_color", "Button")
+ else:
+ var line_x := scroll_forwards_rect.position.x
+ draw_line(Vector2(line_x, 0), Vector2(line_x, size.y),
+ ThemeUtils.common_panel_border_color)
+ if scroll_forwards_rect.has_point(mouse_pos):
+ var stylebox_theme := "pressed" if\
+ Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT) else "hover"
+ get_theme_stylebox(stylebox_theme, "FlatButton").draw(ci, scroll_forwards_rect)
+ draw_texture_rect(scroll_forwards_icon, Rect2(scroll_forwards_rect.position +\
+ (scroll_forwards_rect.size - scroll_forwards_icon_size) / 2.0,
+ scroll_forwards_icon_size), false, icon_modulate)
if proposed_drop_idx != -1:
- draw_line(Vector2(TAB_WIDTH * proposed_drop_idx, 0),
- Vector2(TAB_WIDTH * proposed_drop_idx, size.y),
+ var prev_tab_rect := get_tab_rect(proposed_drop_idx - 1)
+ var x_pos: float
+ if prev_tab_rect.has_area():
+ x_pos = prev_tab_rect.end.x
+ else:
+ x_pos = get_tab_rect(proposed_drop_idx).position.x
+ draw_line(Vector2(x_pos, 0), Vector2(x_pos, size.y),
Configs.savedata.basic_color_valid, 4)
@@ -77,68 +159,107 @@ func _gui_input(event: InputEvent) -> void:
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())))
+ if event is InputEventMouseButton:
+ if event.is_pressed():
+ if event.button_index in [MOUSE_BUTTON_WHEEL_UP, MOUSE_BUTTON_WHEEL_LEFT]:
+ scroll_backwards()
+ if event.button_index in [MOUSE_BUTTON_WHEEL_DOWN, MOUSE_BUTTON_WHEEL_RIGHT]:
+ scroll_forwards()
+ elif event.button_index in [MOUSE_BUTTON_LEFT, MOUSE_BUTTON_RIGHT]:
+ var hovered_idx := get_hovered_index()
+ if hovered_idx != -1:
+ if hovered_idx == Configs.savedata.get_active_tab_index():
+ scroll_to_active()
+ else:
+ # Give time for deferred callbacks that might change the active SVG.
+ Configs.savedata.set_active_tab_index.call_deferred(hovered_idx)
+ if event.button_index == MOUSE_BUTTON_LEFT:
+ var scroll_backwards_area_rect := get_scroll_backwards_area_rect()
+ if scroll_backwards_area_rect.has_area() and\
+ scroll_backwards_area_rect.has_point(event.position) and\
+ not is_scroll_backwards_disabled():
+ scrolling_backwards = true
+ set_process(true)
+ return
+
+ var scroll_forwards_area_rect := get_scroll_forwards_area_rect()
+ if scroll_forwards_area_rect.has_area() and\
+ scroll_forwards_area_rect.has_point(event.position) and\
+ not is_scroll_forwards_disabled():
+ scrolling_forwards = true
+ set_process(true)
+ 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:
+ var new_active_tab := Configs.savedata.get_tab(hovered_idx)
+
+ btn_arr.append(ContextPopup.create_button(
+ Translator.translate("Close tab"),
+ FileUtils.close_tabs.bind(hovered_idx), false, null, "close_tab"))
+ # TODO Unify into "Close multiple tabs"
+ btn_arr.append(ContextPopup.create_button(
+ TranslationUtils.get_shortcut_description("close_all_other_tabs"),
+ FileUtils.close_tabs.bind(hovered_idx,
+ FileUtils.TabCloseMode.ALL_OTHERS),
+ Configs.savedata.get_tab_count() < 2, null, "close_all_other_tabs"))
+ btn_arr.append(ContextPopup.create_button(
+ TranslationUtils.get_shortcut_description("close_tabs_to_left"),
+ FileUtils.close_tabs.bind(hovered_idx,
+ FileUtils.TabCloseMode.TO_LEFT),
+ hovered_idx == 0, null, "close_tabs_to_left"))
+ btn_arr.append(ContextPopup.create_button(
+ TranslationUtils.get_shortcut_description("close_tabs_to_right"),
+ FileUtils.close_tabs.bind(hovered_idx,
+ FileUtils.TabCloseMode.TO_RIGHT),
+ hovered_idx == Configs.savedata.get_tab_count() - 1,
+ null, "close_tabs_to_right"))
+ btn_arr.append(ContextPopup.create_button(
+ Translator.translate("Open externally"),
+ ShortcutUtils.fn("open_externally"),
+ not FileAccess.file_exists(new_active_tab.svg_file_path),
+ load("res://assets/icons/OpenFile.svg"), "open_externally"))
+ btn_arr.append(ContextPopup.create_button(
+ Translator.translate("Show in File Manager"),
+ ShortcutUtils.fn("open_in_folder"),
+ not FileAccess.file_exists(new_active_tab.svg_file_path),
+ load("res://assets/icons/OpenFolder.svg"), "open_in_folder"))
+ 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())
+ elif event.button_index == MOUSE_BUTTON_LEFT and event.is_released():
+ scrolling_backwards = false
+ scrolling_forwards = false
+ set_process(false)
+
+# Autoscroll when the dragged tab is hovered beyond the tabs area.
+func _process(_delta: float) -> void:
+ var mouse_pos := get_local_mouse_position()
+ var scroll_forwards_area_rect := get_scroll_forwards_area_rect()
+ if ((scrolling_forwards and scroll_forwards_area_rect.has_point(mouse_pos)) or\
+ (mouse_pos.x > size.x - get_add_button_rect().size.x -\
+ scroll_forwards_area_rect.size.x)) and scroll_forwards_area_rect.has_area():
+ scroll_forwards()
+ return
+
+ var scroll_backwards_area_rect := get_scroll_backwards_area_rect()
+ if ((scrolling_backwards and scroll_backwards_area_rect.has_point(mouse_pos)) or\
+ (mouse_pos.x < scroll_backwards_area_rect.size.x)) and\
+ scroll_backwards_area_rect.has_area():
+ scroll_backwards()
+ return
func _on_mouse_entered() -> void:
@@ -154,21 +275,98 @@ func cleanup() -> void:
queue_redraw()
+func scroll_backwards() -> void:
+ set_scroll(current_scroll - 5.0)
+
+func scroll_forwards() -> void:
+ set_scroll(current_scroll + 5.0)
+
+func scroll_to_active() -> void:
+ var idx := Configs.savedata.get_active_tab_index() if\
+ State.transient_tab_path.is_empty() else Configs.savedata.get_tab_count()
+ set_scroll(clampf(current_scroll, MIN_TAB_WIDTH * (idx + 1) -\
+ size.x + get_add_button_rect().size.x + get_scroll_forwards_area_rect().size.x +\
+ get_scroll_backwards_area_rect().size.x, MIN_TAB_WIDTH * idx))
+
+func set_scroll(new_value: float) -> void:
+ if get_scroll_limit() < 0:
+ new_value = 0.0
+ else:
+ new_value = clampf(new_value, 0, get_scroll_limit())
+ if current_scroll != new_value:
+ current_scroll = new_value
+ queue_redraw()
+ activate()
+
+
+func get_proper_tab_count() -> int:
+ if State.transient_tab_path.is_empty():
+ return Configs.savedata.get_tab_count()
+ return Configs.savedata.get_tab_count() + 1
+
func get_tab_rect(idx: int) -> Rect2:
- return Rect2(TAB_WIDTH * idx, 0, TAB_WIDTH, size.y)
+ # Things that can take space.
+ var add_button_width := get_add_button_rect().size.x
+ var scroll_backwards_button_width := get_scroll_backwards_area_rect().size.x
+ var scroll_forwards_button_width := get_scroll_forwards_area_rect().size.x
+
+ var left_limit := scroll_backwards_button_width
+ var right_limit := size.x - add_button_width - scroll_forwards_button_width
+
+ var tab_width := clampf((size.x - add_button_width - scroll_backwards_button_width -\
+ scroll_forwards_button_width) / get_proper_tab_count(),
+ MIN_TAB_WIDTH, DEFAULT_TAB_WIDTH)
+ var unclamped_tab_start := tab_width * idx - current_scroll + left_limit
+ var tab_start := clampf(unclamped_tab_start, left_limit, right_limit)
+ var tab_end := clampf(unclamped_tab_start + tab_width, left_limit, right_limit)
+
+ if tab_end <= tab_start:
+ return Rect2()
+ return Rect2(tab_start, 0, tab_end - tab_start, 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 tab_rect := get_tab_rect(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)
+ var left_coords := tab_rect.position.x + tab_rect.size.x - CLOSE_BUTTON_MARGIN - side
+ if left_coords < get_scroll_backwards_area_rect().size.x or\
+ tab_rect.size.x < size.y - CLOSE_BUTTON_MARGIN:
+ return Rect2()
+ return Rect2(left_coords, 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)
+ var tab_count := get_proper_tab_count()
+ if tab_count >= SaveData.MAX_TABS:
+ return Rect2()
+ return Rect2(minf(DEFAULT_TAB_WIDTH * tab_count, size.x - size.y), 0, size.y, size.y)
+
+func get_scroll_forwards_area_rect() -> Rect2:
+ var add_button_width := get_add_button_rect().size.x
+ if size.x - add_button_width > get_proper_tab_count() * MIN_TAB_WIDTH:
+ return Rect2()
+ var width := size.y / 1.5
+ return Rect2(size.x - add_button_width - width, 0, width, size.y)
+
+func is_scroll_forwards_disabled() -> bool:
+ return current_scroll >= get_scroll_limit()
+
+func get_scroll_backwards_area_rect() -> Rect2:
+ if size.x - get_add_button_rect().size.x > get_proper_tab_count() * MIN_TAB_WIDTH:
+ return Rect2()
+ return Rect2(0, 0, size.y / 1.5, size.y)
+
+func is_scroll_backwards_disabled() -> bool:
+ return current_scroll <= 0.0
+
+func get_scroll_limit() -> float:
+ var add_button_width := get_add_button_rect().size.x
+ var scroll_backwards_button_width := get_scroll_backwards_area_rect().size.x
+ var scroll_forwards_button_width := get_scroll_forwards_area_rect().size.x
+
+ var available_area := size.x - add_button_width - scroll_backwards_button_width -\
+ scroll_forwards_button_width
+ return clampf(available_area / get_proper_tab_count(),
+ MIN_TAB_WIDTH, DEFAULT_TAB_WIDTH) * get_proper_tab_count() - available_area
func get_hovered_index() -> int:
var mouse_pos := get_local_mouse_position()
@@ -185,48 +383,103 @@ 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
+ if close_rect.has_area():
+ 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(func() -> void:
+ FileUtils.close_tabs(Configs.savedata.get_active_tab_index())
+ )
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)
+ if add_rect.has_area():
+ 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:
+ var backwards_area_rect := get_scroll_backwards_area_rect()
+ if backwards_area_rect.has_area() and backwards_area_rect.has_point(at_position):
+ return Translator.translate("Scroll backwards")
+
+ var forwards_area_rect := get_scroll_forwards_area_rect()
+ if forwards_area_rect.has_area() and forwards_area_rect.has_point(at_position):
+ return Translator.translate("Scroll forwards")
+
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
+ return Translator.translate("This SVG is not bound to a file location yet.")
+ # We have to pass some metadata to the tooltip.
+ # Since "*" isn't valid in filepaths, we use it as a delimiter.
+ elif hovered_tab_idx == Configs.savedata.get_active_tab_index():
+ return "%s*hovered" % current_tab.get_presented_svg_file_path()
+
+ return "%s*%d" % [current_tab.get_presented_svg_file_path(), current_tab.id]
+
+func _make_custom_tooltip(for_text: String) -> Object:
+ var asterisk_pos := for_text.find("*")
+ if asterisk_pos == -1:
+ return null
+
+ var path := for_text.left(asterisk_pos)
+ var label := Label.new()
+ label.add_theme_font_override("font", ThemeUtils.mono_font)
+ label.add_theme_font_size_override("font_size", 12)
+ label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
+ label.text = path
+ Utils.set_max_text_width(label, 192.0, 4.0)
+
+ var metadata := for_text.right(-asterisk_pos - 1)
+ if metadata == "hovered":
+ return label
+
+ var id := metadata.to_int()
+ var margin_container := MarginContainer.new()
+ var tooltip_panel_stylebox := get_theme_stylebox("panel", "TooltipPanel")
+ margin_container.begin_bulk_theme_override()
+ margin_container.add_theme_constant_override("margin_top",
+ int(8 - tooltip_panel_stylebox.content_margin_top))
+ margin_container.add_theme_constant_override("margin_bottom",
+ int(8 - tooltip_panel_stylebox.content_margin_bottom))
+ margin_container.add_theme_constant_override("margin_left",
+ int(8 - tooltip_panel_stylebox.content_margin_left))
+ margin_container.end_bulk_theme_override()
+ var hbox := HBoxContainer.new()
+ hbox.add_theme_constant_override("separation", 8)
+ var preview_rect := PreviewRectScene.instantiate()
+ hbox.add_child(preview_rect)
+ preview_rect.custom_minimum_size = Vector2(96, 96)
+ preview_rect.size = Vector2.ZERO
+ preview_rect.setup_svg_without_dimensions(FileAccess.get_file_as_string(
+ TabData.get_edited_file_path_for_id(id)))
+ preview_rect.shrink_to_fit(16, 16)
+ hbox.add_child(label)
+ margin_container.add_child(hbox)
+ return margin_container
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):
+ var tab_rect := get_tab_rect(tab_index)
+ if tab_rect.has_area() and tab_rect.has_point(pos):
return tab_index
return -1
@@ -237,8 +490,28 @@ class TabDropData extends RefCounted:
index = new_index
func get_drop_index_at(pos: Vector2) -> int:
+ var add_button_width := get_add_button_rect().size.x
+ var scroll_backwards_button_width := get_scroll_backwards_area_rect().size.x
+ var scroll_forwards_button_width := get_scroll_forwards_area_rect().size.x
+
+ if pos.x < scroll_backwards_button_width or\
+ pos.x > size.x - scroll_forwards_button_width - add_button_width:
+ return -1
+
+ var first_tab_with_area := 0
for idx in Configs.savedata.get_tab_count():
- if get_tab_rect(idx).get_center().x > pos.x:
+ if get_tab_rect(idx).has_area():
+ first_tab_with_area = idx
+ break
+
+ var tab_width := clampf((size.x - add_button_width - scroll_backwards_button_width -\
+ scroll_forwards_button_width) / get_proper_tab_count(),
+ MIN_TAB_WIDTH, DEFAULT_TAB_WIDTH)
+
+ for idx in range(first_tab_with_area, Configs.savedata.get_tab_count()):
+ var tab_rect := get_tab_rect(idx)
+ if not tab_rect.has_area() or tab_width * (idx + 0.5) - current_scroll +\
+ scroll_backwards_button_width > pos.x:
return idx
return Configs.savedata.get_tab_count()
@@ -246,28 +519,30 @@ 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
+
+ var tab_width := get_tab_rect(tab_index_at_position).size.x
# 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.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()
+ label.text = Configs.savedata.get_active_tab().presented_name
preview.add_child(label)
label.position = Vector2(4, 3)
- label.size.x = TAB_WIDTH - 8
+ label.size.x = tab_width - 8
set_drag_preview(preview)
+ set_process(true)
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)
+ 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
@@ -278,8 +553,10 @@ func _can_drop_data(at_position: Vector2, data: Variant) -> bool:
func _drop_data(at_position: Vector2, data: Variant) -> void:
if not data is TabDropData:
return
+ set_process(false)
Configs.savedata.move_tab(data.index, get_drop_index_at(at_position))
func _notification(what: int) -> void:
if what == NOTIFICATION_DRAG_END:
+ set_process(false)
proposed_drop_idx = -1
diff --git a/src/ui_parts/update_menu.gd b/src/ui_parts/update_menu.gd
index 40c633c..c179300 100644
--- a/src/ui_parts/update_menu.gd
+++ b/src/ui_parts/update_menu.gd
@@ -3,7 +3,7 @@ extends PanelContainer
@onready var http: HTTPRequest = $HTTPRequest
@onready var status_label: RichTextLabel = $VBoxContainer/Status
@onready var current_version_label: Label = $VBoxContainer/CurrentVersionLabel
-@onready var prereleases_checkbox: CheckBox = $VBoxContainer/IncludePrereleases
+@onready var prereleases_checkbox: CheckButton = $VBoxContainer/IncludePrereleases
@onready var retry_button: Button = $VBoxContainer/RetryButton
@onready var close_button: Button = $VBoxContainer/CloseButton
@@ -18,7 +18,7 @@ func _ready() -> void:
prereleases_checkbox.toggled.connect(display_results.unbind(1))
close_button.text = Translator.translate("Close")
- prereleases_checkbox.text = Translator.translate("Include prereleases")
+ prereleases_checkbox.text = Translator.translate("Show prereleases")
retry_button.text = Translator.translate("Retry")
current_version_label.text = Translator.translate("Current Version") + ": " +\
current_version
@@ -41,7 +41,7 @@ _headers: PackedStringArray, body: PackedByteArray) -> void:
display_error_message("Response code %d" % response_code)
return
- var json = JSON.parse_string(body.get_string_from_utf8())
+ var json: Variant = JSON.parse_string(body.get_string_from_utf8())
if json == null:
display_error_message("Failed to decode JSON")
return
diff --git a/src/ui_parts/update_menu.tscn b/src/ui_parts/update_menu.tscn
index 6900a63..4e71962 100644
--- a/src/ui_parts/update_menu.tscn
+++ b/src/ui_parts/update_menu.tscn
@@ -29,10 +29,12 @@ theme_override_constants/separation = 8
[node name="CurrentVersionLabel" type="Label" parent="VBoxContainer"]
layout_mode = 2
+theme_override_font_sizes/font_size = 15
horizontal_alignment = 1
-[node name="IncludePrereleases" type="CheckBox" parent="VBoxContainer"]
+[node name="IncludePrereleases" type="CheckButton" parent="VBoxContainer"]
layout_mode = 2
+size_flags_horizontal = 8
focus_mode = 0
mouse_default_cursor_shape = 2
disabled = true
diff --git a/src/ui_parts/viewport.gd b/src/ui_parts/viewport.gd
index 6ec8006..ed2c6df 100644
--- a/src/ui_parts/viewport.gd
+++ b/src/ui_parts/viewport.gd
@@ -1,6 +1,9 @@
extends SubViewport
-const ZoomMenuType = preload("res://src/ui_parts/zoom_menu.gd")
+const ZoomMenu = preload("res://src/ui_parts/zoom_menu.gd")
+const Camera = preload("res://src/ui_widgets/camera.gd")
+const HandlesManager = preload("res://src/ui_parts/handles_manager.gd")
+const DisplayTexture = preload("res://src/ui_parts/display_texture.gd")
const BUFFER_VIEW_SPACE = 0.8
const ZOOM_RESET_BUFFER = 0.875
@@ -9,11 +12,11 @@ const ZOOM_RESET_BUFFER = 0.875
var _zoom_to: Vector2
@onready var display: TextureRect = $Checkerboard
-@onready var view: Control = $Camera
-@onready var controls: Control = $Controls
-@onready var display_texture: TextureRect = $Checkerboard/DisplayTexture
-@onready var reference_texture = $ReferenceTexture
-@onready var zoom_menu: ZoomMenuType = %ZoomMenu
+@onready var view: Camera = $Camera
+@onready var controls: HandlesManager = $Controls
+@onready var display_texture: DisplayTexture = $Checkerboard/DisplayTexture
+@onready var reference_texture: TextureRect = $ReferenceTexture
+@onready var zoom_menu: ZoomMenu = %ZoomMenu
func _ready() -> void:
@@ -73,13 +76,13 @@ func _unhandled_input(event: InputEvent) -> void:
_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\
+ (wrap_mouse(event.relative).y if Configs.savedata.wraparound_panning 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.unsnapped_position - (wrap_mouse(event.relative) if\
- Configs.savedata.wrap_mouse else event.relative) / State.zoom)
+ Configs.savedata.wraparound_panning else event.relative) / State.zoom)
elif event is InputEventPanGesture and not DisplayServer.get_name() == "Android":
# Zooming with Ctrl + touch?
diff --git a/src/ui_parts/zoom_menu.gd b/src/ui_parts/zoom_menu.gd
index c5ce356..17dca06 100644
--- a/src/ui_parts/zoom_menu.gd
+++ b/src/ui_parts/zoom_menu.gd
@@ -19,6 +19,9 @@ func update_translation() -> void:
zoom_reset_button.tooltip_text = Translator.translate("Zoom reset")
func _ready() -> void:
+ zoom_out_button.pressed.connect(zoom_out)
+ zoom_in_button.pressed.connect(zoom_in)
+ zoom_reset_button.pressed.connect(zoom_reset)
Configs.language_changed.connect(update_translation)
update_translation()
diff --git a/src/ui_parts/zoom_menu.tscn b/src/ui_parts/zoom_menu.tscn
index d53187a..0c60559 100644
--- a/src/ui_parts/zoom_menu.tscn
+++ b/src/ui_parts/zoom_menu.tscn
@@ -43,7 +43,6 @@ custom_minimum_size = Vector2(58, 0)
layout_mode = 2
focus_mode = 0
mouse_default_cursor_shape = 2
-theme_override_font_sizes/font_size = 13
shortcut = SubResource("Shortcut_4v7wx")
shortcut_in_tooltip = false
text = "100%"
@@ -57,7 +56,3 @@ shortcut = SubResource("Shortcut_y6ouu")
shortcut_in_tooltip = false
icon = ExtResource("2_284x5")
icon_alignment = 1
-
-[connection signal="pressed" from="ZoomOut" to="." method="zoom_out"]
-[connection signal="pressed" from="ZoomReset" to="." method="zoom_reset"]
-[connection signal="pressed" from="ZoomIn" to="." method="zoom_in"]
diff --git a/src/ui_widgets/BetterLineEdit.gd b/src/ui_widgets/BetterLineEdit.gd
index 55ad314..48967cf 100644
--- a/src/ui_widgets/BetterLineEdit.gd
+++ b/src/ui_widgets/BetterLineEdit.gd
@@ -71,10 +71,7 @@ func _draw() -> void:
func _make_custom_tooltip(for_text: String) -> Object:
if mono_font_tooltip:
var label := Label.new()
- label.begin_bulk_theme_override()
label.add_theme_font_override("font", ThemeUtils.mono_font)
- label.add_theme_font_size_override("font_size", 13)
- label.end_bulk_theme_override()
label.text = for_text
return label
else:
@@ -85,7 +82,8 @@ func _input(event: InputEvent) -> void:
if not has_focus():
return
- if event is InputEventMouseButton:
+ if event is InputEventMouseButton and (event.button_index in [MOUSE_BUTTON_LEFT,
+ MOUSE_BUTTON_RIGHT, MOUSE_BUTTON_MIDDLE]):
if event.is_pressed() and not get_global_rect().has_point(event.position) and\
popup_level == HandlerGUI.popup_stack.size():
release_focus()
@@ -109,10 +107,10 @@ func _gui_input(event: InputEvent) -> void:
var separator_arr: Array[int] = []
if editable:
btn_arr.append(ContextPopup.create_button(Translator.translate("Undo"),
- menu_option.bind(LineEdit.MENU_UNDO), false,
+ menu_option.bind(LineEdit.MENU_UNDO), not has_undo(),
load("res://assets/icons/Undo.svg"), "ui_undo"))
btn_arr.append(ContextPopup.create_button(Translator.translate("Redo"),
- menu_option.bind(LineEdit.MENU_REDO), false,
+ menu_option.bind(LineEdit.MENU_REDO), not has_redo(),
load("res://assets/icons/Redo.svg"), "ui_redo"))
if DisplayServer.has_feature(DisplayServer.FEATURE_CLIPBOARD):
separator_arr = [2]
diff --git a/src/ui_widgets/BetterTextEdit.gd b/src/ui_widgets/BetterTextEdit.gd
index fbd4854..61854ad 100644
--- a/src/ui_widgets/BetterTextEdit.gd
+++ b/src/ui_widgets/BetterTextEdit.gd
@@ -111,8 +111,10 @@ func _draw() -> void:
func _input(event: InputEvent) -> void:
- if (has_focus() and event is InputEventMouseButton and event.is_pressed() and\
- not get_global_rect().has_point(event.position) and HandlerGUI.popup_stack.is_empty()):
+ if (has_focus() and event is InputEventMouseButton and (event.button_index in\
+ [MOUSE_BUTTON_LEFT, MOUSE_BUTTON_RIGHT, MOUSE_BUTTON_MIDDLE]) and\
+ event.is_pressed() and not get_global_rect().has_point(event.position) and\
+ HandlerGUI.popup_stack.is_empty()):
release_focus()
func _gui_input(event: InputEvent) -> void:
@@ -133,10 +135,10 @@ func _gui_input(event: InputEvent) -> void:
if editable:
btn_arr.append(ContextPopup.create_button(
Translator.translate("Undo"), undo,
- !has_undo(), load("res://assets/icons/Undo.svg"), "ui_undo"))
+ not has_undo(), load("res://assets/icons/Undo.svg"), "ui_undo"))
btn_arr.append(ContextPopup.create_button(
Translator.translate("Redo"), redo,
- !has_redo(), load("res://assets/icons/Redo.svg"), "ui_redo"))
+ not has_redo(), load("res://assets/icons/Redo.svg"), "ui_redo"))
if DisplayServer.has_feature(DisplayServer.FEATURE_CLIPBOARD):
separator_arr = PackedInt32Array([2])
btn_arr.append(ContextPopup.create_button(
diff --git a/src/ui_widgets/ContextPopup.gd b/src/ui_widgets/ContextPopup.gd
index c524253..2238074 100644
--- a/src/ui_widgets/ContextPopup.gd
+++ b/src/ui_widgets/ContextPopup.gd
@@ -1,6 +1,8 @@
class_name ContextPopup extends PanelContainer
## Standard popup for actions with methods for easy setup.
+const arrow = preload("res://assets/icons/PopupArrow.svg")
+
func _init() -> void:
mouse_filter = Control.MOUSE_FILTER_STOP
@@ -16,9 +18,14 @@ icon: Texture2D = null, shortcut := "") -> Button:
if not shortcut.is_empty():
if not InputMap.has_action(shortcut):
push_error("Non-existent shortcut was passed to ContextPopup.create_button().")
- elif InputMap.has_action(shortcut):
+ else:
var events := InputMap.action_get_events(shortcut)
- if not events.is_empty():
+ var showcased_event: InputEventKey
+ for event in events:
+ if Configs.savedata.is_shortcut_valid(event):
+ showcased_event = event
+
+ if is_instance_valid(showcased_event):
# Add button with a shortcut.
var ret_button := Button.new()
ret_button.theme_type_variation = "ContextButton"
@@ -29,19 +36,24 @@ icon: Texture2D = null, shortcut := "") -> Button:
ret_button.disabled = true
else:
ret_button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
- for theme_item in ["normal", "hover", "pressed", "disabled"]:
- main_button.add_theme_stylebox_override(theme_item,
+
+ const CONST_ARR: PackedStringArray = ["normal", "hover", "pressed", "disabled"]
+ main_button.begin_bulk_theme_override()
+ for theme_type in CONST_ARR:
+ main_button.add_theme_stylebox_override(theme_type,
main_button.get_theme_stylebox("normal", "ContextButton"))
+ main_button.end_bulk_theme_override()
+
var internal_hbox := HBoxContainer.new()
main_button.mouse_filter = Control.MOUSE_FILTER_IGNORE # Unpressable.
- internal_hbox.add_theme_constant_override("separation", 6)
+ internal_hbox.add_theme_constant_override("separation", 12)
main_button.add_theme_color_override("icon_normal_color",
ret_button.get_theme_color("icon_normal_color", "ContextButton"))
var label_margin := MarginContainer.new()
label_margin.add_theme_constant_override("margin_right",
int(ret_button.get_theme_stylebox("normal").content_margin_right))
var label := Label.new()
- label.text = events[0].as_text_keycode()
+ label.text = showcased_event.as_text_keycode()
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
var shortcut_text_color := ThemeUtils.common_subtle_text_color
if disabled:
@@ -91,28 +103,38 @@ start_pressed: bool, shortcut := "") -> CheckBox:
if not shortcut.is_empty():
if not InputMap.has_action(shortcut):
push_error("Non-existent shortcut was passed to ContextPopup.create_checkbox().")
- elif InputMap.has_action(shortcut):
+ else:
var events := InputMap.action_get_events(shortcut)
- if not events.is_empty():
+ var showcased_event: InputEventKey
+ for event in events:
+ if Configs.savedata.is_shortcut_valid(event):
+ showcased_event = event
+
+ if is_instance_valid(showcased_event):
# Add button with a shortcut.
var ret_button := Button.new()
ret_button.theme_type_variation = "ContextButton"
ret_button.focus_mode = Control.FOCUS_NONE
ret_button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
ret_button.shortcut_in_tooltip = false
- for theme_stylebox in ["normal", "pressed"]:
- checkbox.add_theme_stylebox_override(theme_stylebox,
+
+ checkbox.begin_bulk_theme_override()
+ const CONST_ARR: PackedStringArray = ["normal", "pressed"]
+ for theme_type in CONST_ARR:
+ checkbox.add_theme_stylebox_override(theme_type,
checkbox.get_theme_stylebox("normal", "ContextButton"))
+ checkbox.end_bulk_theme_override()
+
var internal_hbox := HBoxContainer.new()
checkbox.mouse_filter = Control.MOUSE_FILTER_IGNORE # Unpressable.
- internal_hbox.add_theme_constant_override("separation", 6)
+ internal_hbox.add_theme_constant_override("separation", 12)
checkbox.add_theme_color_override("icon_normal_color",
ret_button.get_theme_color("icon_normal_color", "ContextButton"))
var label_margin := MarginContainer.new()
label_margin.add_theme_constant_override("margin_right",
int(ret_button.get_theme_stylebox("normal").content_margin_right))
var label := Label.new()
- label.text = events[0].as_text_keycode()
+ label.text = showcased_event.as_text_keycode()
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
var shortcut_text_color := ThemeUtils.common_subtle_text_color
#if disabled:
@@ -130,7 +152,7 @@ start_pressed: bool, shortcut := "") -> CheckBox:
internal_hbox.add_child(label_margin)
ret_button.add_child(internal_hbox)
ret_button.pressed.connect(
- func(): checkbox.button_pressed = not checkbox.button_pressed)
+ func() -> void: checkbox.button_pressed = not checkbox.button_pressed)
var shortcut_obj := Shortcut.new()
var action_obj := InputEventAction.new()
@@ -235,14 +257,6 @@ func _common_initial_setup() -> VBoxContainer:
scroll_container.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
scroll_container.vertical_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
- # Increase the scrollbar area on Android.
- if OS.get_name() == "Android":
- var scrollbar := scroll_container.get_v_scroll_bar()
- var stylebox := scrollbar.get_theme_stylebox("scroll").duplicate()
- stylebox.content_margin_left = 10
- stylebox.content_margin_right = 10
- scrollbar.add_theme_stylebox_override("scroll", stylebox)
-
var main_container := VBoxContainer.new()
main_container.size_flags_horizontal = Control.SIZE_EXPAND_FILL
main_container.add_theme_constant_override("separation", 0)
diff --git a/src/ui_parts/DeltaHandle.gd b/src/ui_widgets/DeltaHandle.gd
similarity index 100%
rename from src/ui_parts/DeltaHandle.gd
rename to src/ui_widgets/DeltaHandle.gd
diff --git a/src/ui_parts/DeltaHandle.gd.uid b/src/ui_widgets/DeltaHandle.gd.uid
similarity index 100%
rename from src/ui_parts/DeltaHandle.gd.uid
rename to src/ui_widgets/DeltaHandle.gd.uid
diff --git a/src/ui_widgets/GridDrawingControl.gd b/src/ui_widgets/GridDrawingControl.gd
deleted file mode 100644
index cd9993f..0000000
--- a/src/ui_widgets/GridDrawingControl.gd
+++ /dev/null
@@ -1,19 +0,0 @@
-class_name PanelGrid extends GridContainer
-
-@export var items: PackedStringArray
-@export var stylebox: StyleBox
-@export var dim_last_item := false
-
-func setup() -> void:
- for item_idx in items.size():
- var item := items[item_idx]
- var panel_container := PanelContainer.new()
- panel_container.mouse_filter = Control.MOUSE_FILTER_PASS
- panel_container.size_flags_horizontal = Control.SIZE_EXPAND_FILL
- panel_container.add_theme_stylebox_override("panel", stylebox)
- var label := Label.new()
- label.text = item
- if dim_last_item and item_idx == items.size() - 1:
- label.add_theme_color_override("font_color", Color.GRAY)
- panel_container.add_child(label)
- add_child(panel_container)
diff --git a/src/ui_parts/Handle.gd b/src/ui_widgets/Handle.gd
similarity index 100%
rename from src/ui_parts/Handle.gd
rename to src/ui_widgets/Handle.gd
diff --git a/src/ui_parts/Handle.gd.uid b/src/ui_widgets/Handle.gd.uid
similarity index 100%
rename from src/ui_parts/Handle.gd.uid
rename to src/ui_widgets/Handle.gd.uid
diff --git a/src/ui_widgets/PanelGrid.gd b/src/ui_widgets/PanelGrid.gd
new file mode 100644
index 0000000..03dc0c2
--- /dev/null
+++ b/src/ui_widgets/PanelGrid.gd
@@ -0,0 +1,57 @@
+@icon("res://godot_only/icons/PanelGrid.svg")
+class_name PanelGrid extends Control
+
+# Can be made into vars if necessary.
+const inner_color = Color("#32324d")
+const border_color = Color("#4d4d66")
+const border_width = 1
+const side_spacing = 6
+const top_spacing = 2
+const bottom_spacing = 2
+
+@export var columns: int
+@export var items: PackedStringArray
+@export var dim_last_item := false
+
+func _draw() -> void:
+ var item_count := items.size()
+ if item_count == 0:
+ return
+
+ var effective_columns := clampi(columns, 1, item_count)
+ var text_color := get_theme_color("font_color", "Label")
+ var text_font := get_theme_font("font", "Label")
+ var text_font_size := get_theme_font_size("font_size", "Label")
+ var text_height := text_font.get_height(text_font_size)
+ var box_height := text_height + top_spacing + bottom_spacing
+ var box_width := size.x / effective_columns
+ var ci := get_canvas_item()
+
+ custom_minimum_size.y = box_height * ceili(item_count / float(effective_columns))
+
+ @warning_ignore("integer_division")
+ RenderingServer.canvas_item_add_rect(ci, Rect2(0, 0, size.x,
+ box_height * (item_count / effective_columns)), inner_color)
+
+ for item_idx in item_count:
+ var pos_x := (size.x / effective_columns) * (item_idx % effective_columns)
+ @warning_ignore("integer_division")
+ var pos_y := box_height * (item_idx / effective_columns)
+
+ if item_idx == item_count - 1:
+ if item_count % effective_columns != 0:
+ RenderingServer.canvas_item_add_rect(ci,
+ Rect2(pos_x, pos_y, box_width, box_height), inner_color)
+ if dim_last_item:
+ text_color = ThemeUtils.common_dim_text_color
+
+ # Sigh...
+ if is_zero_approx(pos_x):
+ draw_rect(Rect2(1, pos_y, box_width - 1, box_height), border_color, false, 1.0)
+ else:
+ draw_rect(Rect2(pos_x, pos_y, box_width, box_height), border_color, false, 1.0)
+
+
+ text_font.draw_string(ci, Vector2(pos_x + side_spacing, pos_y + top_spacing +\
+ text_font_size), items[item_idx], HORIZONTAL_ALIGNMENT_LEFT, -1,
+ text_font_size, text_color)
diff --git a/src/ui_widgets/GridDrawingControl.gd.uid b/src/ui_widgets/PanelGrid.gd.uid
similarity index 100%
rename from src/ui_widgets/GridDrawingControl.gd.uid
rename to src/ui_widgets/PanelGrid.gd.uid
diff --git a/src/ui_parts/PathHandle.gd b/src/ui_widgets/PathHandle.gd
similarity index 100%
rename from src/ui_parts/PathHandle.gd
rename to src/ui_widgets/PathHandle.gd
diff --git a/src/ui_parts/PathHandle.gd.uid b/src/ui_widgets/PathHandle.gd.uid
similarity index 100%
rename from src/ui_parts/PathHandle.gd.uid
rename to src/ui_widgets/PathHandle.gd.uid
diff --git a/src/ui_parts/PolyHandle.gd b/src/ui_widgets/PolyHandle.gd
similarity index 100%
rename from src/ui_parts/PolyHandle.gd
rename to src/ui_widgets/PolyHandle.gd
diff --git a/src/ui_parts/PolyHandle.gd.uid b/src/ui_widgets/PolyHandle.gd.uid
similarity index 100%
rename from src/ui_parts/PolyHandle.gd.uid
rename to src/ui_widgets/PolyHandle.gd.uid
diff --git a/src/ui_widgets/UndoRedoRef.gd b/src/ui_widgets/UndoRedoRef.gd
new file mode 100644
index 0000000..9ef765d
--- /dev/null
+++ b/src/ui_widgets/UndoRedoRef.gd
@@ -0,0 +1,53 @@
+# This class only exists because it's stupid how Godot's UndoRedo extends Object.
+# I mean, every class you're expected to construct is a RefCounted. Objects are used
+# for sitting ducks like singletons and read-only stuff. Except UndoRedo, which is easy
+# memory leaks if you forget to free it.
+class_name UndoRedoRef extends RefCounted
+
+signal version_changed
+
+var _undo_redo := UndoRedo.new()
+
+func _init() -> void:
+ _undo_redo.version_changed.connect(version_changed.emit)
+
+func create_action(name: String) -> void:
+ _undo_redo.create_action(name)
+
+func add_do_method(callable: Callable) -> void:
+ _undo_redo.add_do_method(callable)
+
+func add_undo_method(callable: Callable) -> void:
+ _undo_redo.add_undo_method(callable)
+
+func add_do_property(object: Object, property: StringName, value: Variant) -> void:
+ _undo_redo.add_do_property(object, property, value)
+
+func add_undo_property(object: Object, property: StringName, value: Variant) -> void:
+ _undo_redo.add_undo_property(object, property, value)
+
+func add_do_reference(object: Object) -> void:
+ _undo_redo.add_do_reference(object)
+
+func add_undo_reference(object: Object) -> void:
+ _undo_redo.add_undo_reference(object)
+
+func commit_action() -> void:
+ _undo_redo.commit_action()
+
+func undo() -> bool:
+ return _undo_redo.undo()
+
+func redo() -> bool:
+ return _undo_redo.redo()
+
+func has_undo() -> bool:
+ return _undo_redo.has_undo()
+
+func has_redo() -> bool:
+ return _undo_redo.has_redo()
+
+func _notification(what: int) -> void:
+ if what == NOTIFICATION_PREDELETE:
+ if _undo_redo:
+ _undo_redo.free()
diff --git a/src/ui_widgets/UndoRedoRef.gd.uid b/src/ui_widgets/UndoRedoRef.gd.uid
new file mode 100644
index 0000000..a5d0539
--- /dev/null
+++ b/src/ui_widgets/UndoRedoRef.gd.uid
@@ -0,0 +1 @@
+uid://bu0xyd6v74hny
diff --git a/src/ui_parts/XYHandle.gd b/src/ui_widgets/XYHandle.gd
similarity index 100%
rename from src/ui_parts/XYHandle.gd
rename to src/ui_widgets/XYHandle.gd
diff --git a/src/ui_parts/XYHandle.gd.uid b/src/ui_widgets/XYHandle.gd.uid
similarity index 100%
rename from src/ui_parts/XYHandle.gd.uid
rename to src/ui_widgets/XYHandle.gd.uid
diff --git a/src/ui_widgets/basic_xnode_frame.gd b/src/ui_widgets/basic_xnode_frame.gd
index fb97127..735b7ca 100644
--- a/src/ui_widgets/basic_xnode_frame.gd
+++ b/src/ui_widgets/basic_xnode_frame.gd
@@ -14,6 +14,7 @@ func _ready() -> void:
State.selection_changed.connect(determine_selection_highlight)
State.hover_changed.connect(determine_selection_highlight)
State.proposed_drop_changed.connect(queue_redraw)
+ State.xnode_dragging_state_changed.connect(_on_xnodes_dragging_state_changed)
title_bar.draw.connect(_on_title_bar_draw)
mouse_exited.connect(_on_mouse_exited)
determine_selection_highlight()
@@ -35,9 +36,9 @@ func _get_drag_data(_at_position: Vector2) -> Variant:
set_drag_preview(XNodeChildrenBuilder.generate_drag_preview(data))
return data
-func _notification(what: int) -> void:
- if what == NOTIFICATION_DRAG_END:
- modulate = Color(1, 1, 1)
+func _on_xnodes_dragging_state_changed() -> void:
+ modulate.a = 0.55 if (State.is_xnode_selection_dragged and\
+ xnode.xid in State.selected_xids) else 1.0
func _on_title_button_pressed() -> void:
diff --git a/src/ui_widgets/basic_xnode_frame.tscn b/src/ui_widgets/basic_xnode_frame.tscn
index 7d48565..391f092 100644
--- a/src/ui_widgets/basic_xnode_frame.tscn
+++ b/src/ui_widgets/basic_xnode_frame.tscn
@@ -1,7 +1,7 @@
[gd_scene load_steps=3 format=3 uid="uid://cwc7ke3w7bf6t"]
-[ext_resource type="Script" path="res://src/ui_widgets/basic_xnode_frame.gd" id="1_c72yn"]
-[ext_resource type="Script" path="res://src/ui_widgets/BetterTextEdit.gd" id="2_0i3q7"]
+[ext_resource type="Script" uid="uid://cftadbnbgghps" path="res://src/ui_widgets/basic_xnode_frame.gd" id="1_c72yn"]
+[ext_resource type="Script" uid="uid://dh5mir6i27u4u" path="res://src/ui_widgets/BetterTextEdit.gd" id="2_0i3q7"]
[node name="BasicXNodeFrame" type="Container"]
offset_left = 2.0
diff --git a/src/ui_widgets/camera.gd b/src/ui_widgets/camera.gd
index aaf02a9..36eb134 100644
--- a/src/ui_widgets/camera.gd
+++ b/src/ui_widgets/camera.gd
@@ -22,6 +22,7 @@ func _ready() -> void:
State.svg_resized.connect(queue_redraw)
State.zoom_changed.connect(change_zoom)
State.zoom_changed.connect(queue_redraw)
+ State.show_grid_changed.connect(toggle_visibility)
func exit_tree() -> void:
RenderingServer.free_rid(surface)
@@ -29,9 +30,16 @@ func exit_tree() -> void:
func change_zoom() -> void:
zoom = State.zoom
+func toggle_visibility() -> void:
+ visible = not visible
+
func update() -> void:
- position = unsnapped_position.snapped(Vector2(1, 1) / zoom)
+ var new_position := unsnapped_position.snapped(Vector2(1, 1) / zoom)
+ if position != new_position:
+ position = unsnapped_position.snapped(Vector2(1, 1) / zoom)
+ State.view_changed.emit()
+
get_viewport().canvas_transform = Transform2D(0.0, Vector2(zoom, zoom),
0.0, -position * zoom)
queue_redraw()
diff --git a/src/ui_widgets/choose_name_dialog.gd b/src/ui_widgets/choose_name_dialog.gd
index deeaf5f..0e6965c 100644
--- a/src/ui_widgets/choose_name_dialog.gd
+++ b/src/ui_widgets/choose_name_dialog.gd
@@ -16,14 +16,18 @@ func _ready() -> void:
action_button.pressed.connect(queue_free)
name_edit.text_changed.connect(adapt_to_text)
name_edit.text_change_canceled.connect(queue_free)
- name_edit.text_submitted.connect(action_button.grab_focus.unbind(1))
+ name_edit.text_submitted.connect(_on_name_edit_text_submitted)
name_edit.add_theme_font_override("font", ThemeUtils.regular_font)
+func _on_name_edit_text_submitted() -> void:
+ if not action_button.disabled:
+ action_button.grab_focus()
+
# The error/warning callables should take the stripped text and return a string.
func setup(title: String, action: Callable, error_callable := Callable(),
warning_callable := Callable()) -> void:
title_label.text = title
- action_button.pressed.connect(func(): action.call(name_edit.text))
+ action_button.pressed.connect(func() -> void: action.call(name_edit.text))
name_edit.grab_focus()
name_edit.custom_minimum_size.x = 300.0
warning_callback = warning_callable
diff --git a/src/ui_widgets/color_edit.gd b/src/ui_widgets/color_edit.gd
index e0ab11e..68bc22a 100644
--- a/src/ui_widgets/color_edit.gd
+++ b/src/ui_widgets/color_edit.gd
@@ -1,8 +1,8 @@
# A color editor, not tied to any attribute.
extends LineEditButton
-const ColorPopup = preload("res://src/ui_widgets/color_popup.tscn")
-const ColorPickerPopup = preload("res://src/ui_widgets/color_picker_popup.tscn")
+const ColorPopupScene = preload("res://src/ui_widgets/color_popup.tscn")
+const ColorPickerPopupScene = preload("res://src/ui_widgets/color_picker_popup.tscn")
const checkerboard = preload("res://assets/icons/backgrounds/ColorButtonBG.svg")
@onready var color_picker: Control
@@ -18,28 +18,28 @@ var value: String:
if new_value != value:
value = new_value
value_changed.emit(value)
- sync(value)
+ sync()
func _ready() -> void:
- text_submitted.connect(func(x): value = x)
+ text_submitted.connect(func(x: String) -> void: value = x)
pressed.connect(_on_pressed)
text_changed.connect(_on_text_changed)
- text_change_canceled.connect(func(): sync(value))
+ text_change_canceled.connect(sync)
button_gui_input.connect(queue_redraw.unbind(1))
if enable_alpha:
custom_minimum_size.x += 14.0
- sync(value)
+ sync()
-func sync(new_value: String) -> void:
- text = new_value.trim_prefix("#")
+func sync() -> void:
+ text = value.trim_prefix("#")
reset_font_color()
queue_redraw()
func _on_pressed() -> void:
- color_picker = ColorPopup.instantiate() if enable_palettes\
- else ColorPickerPopup.instantiate()
+ color_picker = ColorPopupScene.instantiate() if enable_palettes\
+ else ColorPickerPopupScene.instantiate()
if enable_alpha:
color_picker.enable_alpha = true
color_picker.current_value = ColorParser.add_hash_if_hex(value)
@@ -48,7 +48,7 @@ func _on_pressed() -> void:
func _on_text_changed(new_text: String) -> void:
font_color = Configs.savedata.get_validity_color(
- ColorParser.is_valid(ColorParser.add_hash_if_hex(new_text), enable_alpha))
+ not ColorParser.is_valid(ColorParser.add_hash_if_hex(new_text), enable_alpha))
func _draw() -> void:
super()
diff --git a/src/ui_widgets/color_edit.tscn b/src/ui_widgets/color_edit.tscn
index b0b8c7b..7122204 100644
--- a/src/ui_widgets/color_edit.tscn
+++ b/src/ui_widgets/color_edit.tscn
@@ -1,6 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://5f8uxavn1or1"]
-[ext_resource type="Script" path="res://src/ui_widgets/color_edit.gd" id="1_1uexr"]
+[ext_resource type="Script" uid="uid://bu3mp6qwgvgh0" path="res://src/ui_widgets/color_edit.gd" id="1_1uexr"]
[node name="ColorEdit" type="Control"]
custom_minimum_size = Vector2(68, 0)
diff --git a/src/ui_widgets/color_field.gd b/src/ui_widgets/color_field.gd
index 9eddebe..132f09b 100644
--- a/src/ui_widgets/color_field.gd
+++ b/src/ui_widgets/color_field.gd
@@ -1,6 +1,8 @@
# An editor to be tied to a color attribute.
extends LineEditButton
+const ColorPopup = preload("res://src/ui_widgets/color_popup.gd")
+
var element: Element
var attribute_name: String: # May propagate.
set(new_value):
@@ -13,10 +15,10 @@ var cached_allow_url: bool
var cached_allow_none: bool
var cached_allow_current_color: bool
-const ColorPopup = preload("res://src/ui_widgets/color_popup.tscn")
+const ColorPopupScene = preload("res://src/ui_widgets/color_popup.tscn")
const checkerboard = preload("res://assets/icons/backgrounds/ColorButtonBG.svg")
-@onready var color_popup: Control
+@onready var color_popup: ColorPopup
var gradient_texture: GradientTexture2D
@@ -24,11 +26,11 @@ func set_value(new_value: String, save := false) -> void:
if not new_value.is_empty():
# Validate the value.
if not is_valid(new_value):
- sync_to_attribute()
+ sync()
return
new_value = ColorParser.add_hash_if_hex(new_value)
- sync(element.get_attribute(attribute_name).format(new_value))
element.set_attribute(attribute_name, new_value)
+ sync()
if save:
State.queue_svg_save()
@@ -37,15 +39,15 @@ func setup_placeholder() -> void:
func _ready() -> void:
- Configs.basic_colors_changed.connect(resync)
- sync_to_attribute()
+ Configs.basic_colors_changed.connect(sync)
+ sync()
element.attribute_changed.connect(_on_element_attribute_changed)
if attribute_name in DB.propagated_attributes:
element.ancestor_attribute_changed.connect(_on_element_ancestor_attribute_changed)
text_submitted.connect(set_value.bind(true))
focus_entered.connect(reset_font_color)
text_changed.connect(_on_text_changed)
- text_change_canceled.connect(sync_to_attribute)
+ text_change_canceled.connect(sync)
pressed.connect(_on_pressed)
button_gui_input.connect(_on_button_gui_input)
# URLs and currentColor require to always listen for changes to the SVG.
@@ -57,28 +59,25 @@ func _ready() -> void:
func _on_element_attribute_changed(attribute_changed: String) -> void:
if attribute_name == attribute_changed:
- sync_to_attribute()
+ sync()
func _on_element_ancestor_attribute_changed(attribute_changed: String) -> void:
if attribute_name == attribute_changed:
setup_placeholder()
- resync()
-
-func sync_to_attribute() -> void:
- set_value(element.get_attribute_value(attribute_name, true))
+ sync()
# Redraw in case the gradient might have changed.
func _on_svg_changed() -> void:
if cached_allow_url and\
- ColorParser.is_valid_url(element.get_attribute_value(attribute_name, false)):
+ ColorParser.is_valid_url(element.get_implied_attribute_value(attribute_name)):
update_gradient_texture()
queue_redraw()
- elif element.get_attribute_value(attribute_name, true) == "currentColor":
+ elif element.get_attribute_value(attribute_name) == "currentColor":
queue_redraw()
func _on_pressed() -> void:
- color_popup = ColorPopup.instantiate()
- color_popup.current_value = element.get_attribute_value(attribute_name, true)
+ color_popup = ColorPopupScene.instantiate()
+ color_popup.current_value = element.get_attribute_value(attribute_name)
color_popup.effective_color = ColorParser.text_to_color(
element.get_attribute_true_color(attribute_name))
color_popup.show_url = cached_allow_url
@@ -111,7 +110,7 @@ func _draw() -> void:
checkerboard.draw(ci, Vector2(h_offset, 1))
# Draw the color or gradient.
var drawn := false
- var color_value := element.get_attribute_value(attribute_name, false)
+ var color_value := element.get_implied_attribute_value(attribute_name)
if cached_allow_url and ColorParser.is_valid_url(color_value):
var id := color_value.substr(5, color_value.length() - 6)
var gradient_element := State.root_element.get_element_by_id(id)
@@ -167,10 +166,8 @@ func is_valid(color_text: String) -> bool:
func _on_text_changed(new_text: String) -> void:
font_color = Configs.savedata.get_validity_color(!is_valid(new_text))
-func resync() -> void:
- sync(text)
-
-func sync(new_value: String) -> void:
+func sync() -> void:
+ var new_value := element.get_attribute_value(attribute_name)
reset_font_color()
if ColorParser.add_hash_if_hex(new_value) == element.get_default(attribute_name):
font_color = Configs.savedata.basic_color_warning
@@ -181,7 +178,7 @@ func sync(new_value: String) -> void:
# TODO remove this method when #94584 is fixed.
func update_gradient_texture() -> void:
- var color_value := element.get_attribute_value(attribute_name, false)
+ var color_value := element.get_implied_attribute_value(attribute_name)
if ColorParser.is_valid_url(color_value):
var id := color_value.substr(5, color_value.length() - 6)
var gradient_element := State.root_element.get_element_by_id(id)
diff --git a/src/ui_widgets/color_field.tscn b/src/ui_widgets/color_field.tscn
index b589ea1..7929472 100644
--- a/src/ui_widgets/color_field.tscn
+++ b/src/ui_widgets/color_field.tscn
@@ -1,6 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://carf2o1y7wvmc"]
-[ext_resource type="Script" path="res://src/ui_widgets/color_field.gd" id="1_2pe1j"]
+[ext_resource type="Script" uid="uid://ditw2rswb3tjd" path="res://src/ui_widgets/color_field.gd" id="1_2pe1j"]
[node name="ColorField" type="Control"]
custom_minimum_size = Vector2(68, 0)
diff --git a/src/ui_widgets/color_picker_popup.gd b/src/ui_widgets/color_picker_popup.gd
index 8101952..e97d213 100644
--- a/src/ui_widgets/color_picker_popup.gd
+++ b/src/ui_widgets/color_picker_popup.gd
@@ -1,6 +1,6 @@
extends PanelContainer
-const GoodColorPicker = preload("res://src/ui_widgets/good_color_picker.tscn")
+const GoodColorPickerScene = preload("res://src/ui_widgets/good_color_picker.tscn")
@onready var margin_container: MarginContainer = $MarginContainer
@@ -12,7 +12,7 @@ var current_value: String
var effective_color: Color
func _ready() -> void:
- var color_picker := GoodColorPicker.instantiate()
+ var color_picker := GoodColorPickerScene.instantiate()
color_picker.alpha_enabled = enable_alpha
color_picker.is_none_keyword_available = is_none_keyword_available
color_picker.is_current_color_keyword_available = is_current_color_keyword_available
diff --git a/src/ui_widgets/color_picker_popup.tscn b/src/ui_widgets/color_picker_popup.tscn
index d6fcb47..3107636 100644
--- a/src/ui_widgets/color_picker_popup.tscn
+++ b/src/ui_widgets/color_picker_popup.tscn
@@ -1,6 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://bs68u5annwepo"]
-[ext_resource type="Script" path="res://src/ui_widgets/color_picker_popup.gd" id="1_wmbff"]
+[ext_resource type="Script" uid="uid://6epc6l4yo18d" path="res://src/ui_widgets/color_picker_popup.gd" id="1_wmbff"]
[node name="ColorPickerPopup" type="PanelContainer"]
offset_right = 4.0
diff --git a/src/ui_widgets/color_popup.gd b/src/ui_widgets/color_popup.gd
index 343c5ae..ab6674f 100644
--- a/src/ui_widgets/color_popup.gd
+++ b/src/ui_widgets/color_popup.gd
@@ -1,6 +1,9 @@
# A popup for picking a color.
extends PanelContainer
+const GoodColorPicker = preload("res://src/ui_widgets/good_color_picker.gd")
+const ColorSwatch = preload("res://src/ui_widgets/color_swatch.gd")
+
# Useful here, because it avoids the Palette validation.
class MockPalette extends RefCounted:
var title: String
@@ -8,7 +11,7 @@ class MockPalette extends RefCounted:
var color_names: PackedStringArray
func _init(new_title: String, new_colors: PackedStringArray,
- new_color_names: PackedStringArray):
+ new_color_names: PackedStringArray) -> void:
title = new_title
colors = new_colors
color_names = new_color_names
@@ -16,10 +19,7 @@ class MockPalette extends RefCounted:
# If the currentColor keyword is available, but uninteresting, don't show it.
enum CurrentColorAvailability {UNAVAILABLE, UNINTERESTING, INTERESTING}
-const GoodColorPickerType = preload("res://src/ui_widgets/good_color_picker.gd")
-const ColorSwatchType = preload("res://src/ui_widgets/color_swatch.gd")
-
-const ColorSwatch = preload("res://src/ui_widgets/color_swatch.tscn")
+const ColorSwatchScene = preload("res://src/ui_widgets/color_swatch.tscn")
signal color_picked(new_color: String, final: bool)
var is_none_keyword_available := false
@@ -38,10 +38,10 @@ var _palettes_pending_update := false # Palettes will update when visible.
@onready var palettes_content_container: VBoxContainer = %PalettesContent
@onready var search_field: BetterLineEdit = %SearchBox/SearchField
@onready var color_picker_content: VBoxContainer = %Content/ColorPicker
-@onready var color_picker: GoodColorPickerType = %Content/ColorPicker
+@onready var color_picker: GoodColorPicker = %Content/ColorPicker
@onready var switch_mode_button: Button = $MainContainer/SwitchMode
-var swatches_list: Array[ColorSwatchType] = [] # Updated manually.
+var swatches_list: Array[ColorSwatch] = [] # Updated manually.
func _ready() -> void:
color_picker.is_none_keyword_available = is_none_keyword_available
@@ -53,7 +53,8 @@ func _ready() -> void:
_palettes_pending_update = true
setup_content()
- for theme_type in ["normal", "hover", "pressed"]:
+ const CONST_ARR: PackedStringArray = ["normal", "hover", "pressed"]
+ for theme_type in CONST_ARR:
var sb: StyleBoxFlat = switch_mode_button.get_theme_stylebox(theme_type,
"TranslucentButton").duplicate()
sb.corner_radius_top_left = 0
@@ -119,7 +120,7 @@ func update_palettes(search_text := "") -> void:
var swatch_container := HFlowContainer.new()
swatch_container.add_theme_constant_override("h_separation", 3)
for i in indices_to_show:
- var swatch := ColorSwatch.instantiate()
+ var swatch := ColorSwatchScene.instantiate()
var color_to_show := palette.colors[i]
swatch.color = color_to_show
swatch.color_name = palette.color_names[i]
diff --git a/src/ui_widgets/color_popup.tscn b/src/ui_widgets/color_popup.tscn
index 48b086f..4f48372 100644
--- a/src/ui_widgets/color_popup.tscn
+++ b/src/ui_widgets/color_popup.tscn
@@ -60,6 +60,5 @@ layout_mode = 2
size_flags_horizontal = 3
focus_mode = 0
mouse_default_cursor_shape = 2
-theme_override_font_sizes/font_size = 13
[connection signal="color_changed" from="MainContainer/Content/ColorPicker" to="." method="pick_color"]
diff --git a/src/ui_widgets/color_swatch_config.gd b/src/ui_widgets/color_swatch_config.gd
index 0d8832a..8ea574c 100644
--- a/src/ui_widgets/color_swatch_config.gd
+++ b/src/ui_widgets/color_swatch_config.gd
@@ -3,7 +3,7 @@ extends Button
const checkerboard = preload("res://assets/icons/backgrounds/Checkerboard.svg")
const gear_icon = preload("res://assets/icons/GearOutlined.svg")
-const ColorSwatch = preload("res://src/ui_widgets/color_swatch.tscn")
+const ColorSwatchScene = preload("res://src/ui_widgets/color_swatch.tscn")
var palette: Palette
var idx := -1 # Index inside the palette.
@@ -81,7 +81,7 @@ class DropData extends RefCounted:
func _get_drag_data(_at_position: Vector2) -> Variant:
var data := DropData.new(palette, idx)
# Set up a preview.
- var preview := ColorSwatch.instantiate()
+ var preview := ColorSwatchScene.instantiate()
preview.color = palette.get_color(idx)
preview.color_name = palette.get_color_name(idx)
preview.modulate = Color(1, 1, 1, 0.85)
diff --git a/src/ui_widgets/configure_color_popup.tscn b/src/ui_widgets/configure_color_popup.tscn
index fe8df82..4d56ffa 100644
--- a/src/ui_widgets/configure_color_popup.tscn
+++ b/src/ui_widgets/configure_color_popup.tscn
@@ -1,7 +1,7 @@
[gd_scene load_steps=7 format=3 uid="uid://b7wobq0ndm35"]
-[ext_resource type="Script" path="res://src/ui_widgets/BetterLineEdit.gd" id="1_i4wi2"]
-[ext_resource type="Script" path="res://src/ui_widgets/configure_color_popup.gd" id="1_x6cll"]
+[ext_resource type="Script" uid="uid://1hox6gd5pxku" path="res://src/ui_widgets/BetterLineEdit.gd" id="1_i4wi2"]
+[ext_resource type="Script" uid="uid://btl68wj2dmivk" path="res://src/ui_widgets/configure_color_popup.gd" id="1_x6cll"]
[ext_resource type="Texture2D" uid="uid://dr2erka82g6j4" path="res://assets/icons/Edit.svg" id="2_0dind"]
[ext_resource type="FontFile" uid="uid://clpf84p1lfwlp" path="res://assets/fonts/Font.ttf" id="2_j4qfn"]
[ext_resource type="PackedScene" uid="uid://5f8uxavn1or1" path="res://src/ui_widgets/color_edit.tscn" id="3_dq5ly"]
@@ -31,7 +31,6 @@ layout_mode = 2
[node name="ColorLabel" type="Label" parent="ConfigureContainer/TopContainer/LabelContainer"]
layout_mode = 2
size_flags_horizontal = 3
-theme_override_font_sizes/font_size = 13
horizontal_alignment = 1
[node name="EditButton" type="Button" parent="ConfigureContainer/TopContainer/LabelContainer"]
diff --git a/src/ui_widgets/dropdown.gd b/src/ui_widgets/dropdown.gd
index 9ea040d..cd0fae0 100644
--- a/src/ui_widgets/dropdown.gd
+++ b/src/ui_widgets/dropdown.gd
@@ -1,18 +1,21 @@
# A dropdown with multiple options, not tied to any attribute.
extends HBoxContainer
-@export var values: PackedStringArray
-@export var disabled_values: PackedStringArray
+@export var values: Array[Variant]
+@export var disabled_values: Array[Variant] # References values.
+@export var aliases: Dictionary = {} # References values.
@export var restricted := true
+@export var editing_enabled := false
@export var align_left := false # The alignment of the popup options' text.
-@export var value_text_map: Dictionary[String, String] = {}
+# TODO Typed Dictionary wonkiness
+@export var value_text_map: Dictionary = {} # Dictionary[Variant, String]
@onready var line_edit: BetterLineEdit = $LineEdit
-signal value_changed(new_value: String)
-var _value := ""
+signal value_changed(new_value: Variant)
+var _value: Variant
-func set_value(new_value: String, emit_changed := true) -> void:
+func set_value(new_value: Variant, emit_changed := true) -> void:
if _value != new_value:
_value = new_value
if emit_changed:
@@ -21,13 +24,15 @@ func set_value(new_value: String, emit_changed := true) -> void:
_sync_line_edit()
func _ready() -> void:
+ if not editing_enabled:
+ line_edit.editable = false
line_edit.text_changed.connect(_on_text_changed)
line_edit.text_submitted.connect(_on_text_submitted)
_sync_line_edit()
var max_width := 0
for val in values:
- max_width = maxi(int(line_edit.get_theme_font("font").get_string_size(val,
+ max_width = maxi(int(line_edit.get_theme_font("font").get_string_size(str(val),
HORIZONTAL_ALIGNMENT_LEFT, -1, line_edit.get_theme_font_size("font_size")).x),
max_width)
line_edit.size.x = max_width + 4
@@ -35,19 +40,18 @@ func _ready() -> void:
func _on_button_pressed() -> void:
var btn_arr: Array[Button] = []
for val in values:
- var disabled := disabled_values.has(val) or (val == _value)
- btn_arr.append(ContextPopup.create_button(value_text_map.get(val, val),
- _on_value_chosen.bind(val), disabled))
+ btn_arr.append(ContextPopup.create_button(value_text_map.get(val, str(val)),
+ set_value.bind(val), disabled_values.has(val) or val == _value))
var value_picker := ContextPopup.new()
value_picker.setup(btn_arr, align_left, size.x, get_window().size.y / 2.0)
HandlerGUI.popup_under_rect(value_picker, line_edit.get_global_rect(), get_viewport())
-func _on_value_chosen(new_value: String) -> void:
- set_value(new_value)
-
func _on_text_submitted(new_text: String) -> void:
+ if new_text in aliases:
+ new_text = aliases[new_text]
+
if (restricted and new_text in values) or not restricted:
set_value(new_text)
else:
@@ -56,8 +60,10 @@ func _on_text_submitted(new_text: String) -> void:
func _on_text_changed(new_text: String) -> void:
if restricted:
+ if new_text in aliases:
+ new_text = aliases[new_text]
line_edit.add_theme_color_override("font_color",
Configs.savedata.get_validity_color(not new_text in values))
func _sync_line_edit() -> void:
- line_edit.text = value_text_map.get(_value, _value)
+ line_edit.text = value_text_map.get(_value, str(_value))
diff --git a/src/ui_widgets/dropdown.tscn b/src/ui_widgets/dropdown.tscn
index fe3663c..a65bb90 100644
--- a/src/ui_widgets/dropdown.tscn
+++ b/src/ui_widgets/dropdown.tscn
@@ -1,7 +1,7 @@
[gd_scene load_steps=5 format=3 uid="uid://dbu1lvajypafb"]
-[ext_resource type="Script" path="res://src/ui_widgets/BetterLineEdit.gd" id="1_0ifbb"]
-[ext_resource type="Script" path="res://src/ui_widgets/dropdown.gd" id="1_133xu"]
+[ext_resource type="Script" uid="uid://1hox6gd5pxku" path="res://src/ui_widgets/BetterLineEdit.gd" id="1_0ifbb"]
+[ext_resource type="Script" uid="uid://bp4ji11u8dr0m" path="res://src/ui_widgets/dropdown.gd" id="1_133xu"]
[ext_resource type="Texture2D" uid="uid://coda6chhcatal" path="res://assets/icons/Arrow.svg" id="2_4oygd"]
[ext_resource type="FontFile" uid="uid://clpf84p1lfwlp" path="res://assets/fonts/Font.ttf" id="2_s4ro5"]
diff --git a/src/ui_widgets/element_content_basic_shape.tscn b/src/ui_widgets/element_content_basic_shape.tscn
index 4e15453..b0be9cc 100644
--- a/src/ui_widgets/element_content_basic_shape.tscn
+++ b/src/ui_widgets/element_content_basic_shape.tscn
@@ -1,6 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://b0srm51hsvpel"]
-[ext_resource type="Script" path="res://src/ui_widgets/element_content_basic_shape.gd" id="1_tg77v"]
+[ext_resource type="Script" uid="uid://cv6p3un40sgne" path="res://src/ui_widgets/element_content_basic_shape.gd" id="1_tg77v"]
[node name="ElementContentBasicShape" type="VBoxContainer"]
offset_right = 40.0
diff --git a/src/ui_widgets/element_content_g.tscn b/src/ui_widgets/element_content_g.tscn
index 4dc7eed..b2bec88 100644
--- a/src/ui_widgets/element_content_g.tscn
+++ b/src/ui_widgets/element_content_g.tscn
@@ -1,6 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://bmbrrcuxvyfv3"]
-[ext_resource type="Script" path="res://src/ui_widgets/element_content_g.gd" id="1_wpyt2"]
+[ext_resource type="Script" uid="uid://dcvadp80yr47v" path="res://src/ui_widgets/element_content_g.gd" id="1_wpyt2"]
[node name="ElementContentG" type="VBoxContainer"]
offset_right = 40.0
diff --git a/src/ui_widgets/element_content_linear_gradient.tscn b/src/ui_widgets/element_content_linear_gradient.tscn
index 5c5345b..7d9e1d8 100644
--- a/src/ui_widgets/element_content_linear_gradient.tscn
+++ b/src/ui_widgets/element_content_linear_gradient.tscn
@@ -1,6 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://bt0kb3188plwn"]
-[ext_resource type="Script" path="res://src/ui_widgets/element_content_linear_gradient.gd" id="1_q43hc"]
+[ext_resource type="Script" uid="uid://4xraagkofgdj" path="res://src/ui_widgets/element_content_linear_gradient.gd" id="1_q43hc"]
[node name="ElementContentLinearGradient" type="VBoxContainer"]
offset_right = 40.0
diff --git a/src/ui_widgets/element_content_path.gd b/src/ui_widgets/element_content_path.gd
index 0bc2eeb..658b4bf 100644
--- a/src/ui_widgets/element_content_path.gd
+++ b/src/ui_widgets/element_content_path.gd
@@ -14,6 +14,5 @@ func _ready() -> void:
if attribute == "d":
continue
var input_field := AttributeFieldBuilder.create(attribute, element)
- # Focused signal for pathdata attribute.
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.tscn b/src/ui_widgets/element_content_path.tscn
index 392a958..a456532 100644
--- a/src/ui_widgets/element_content_path.tscn
+++ b/src/ui_widgets/element_content_path.tscn
@@ -1,10 +1,10 @@
[gd_scene load_steps=3 format=3 uid="uid://ntom8irbw0d5"]
-[ext_resource type="Script" path="res://src/ui_widgets/element_content_path.gd" id="1_t5x4b"]
+[ext_resource type="Script" uid="uid://db6oyweh2t54d" path="res://src/ui_widgets/element_content_path.gd" id="1_t5x4b"]
[ext_resource type="PackedScene" uid="uid://dqy5lv33sy5r7" path="res://src/ui_widgets/pathdata_field.tscn" id="1_vf17i"]
[node name="ElementContentPath" type="VBoxContainer"]
-offset_right = 40.0
+offset_right = 250.0
offset_bottom = 40.0
script = ExtResource("1_t5x4b")
diff --git a/src/ui_widgets/element_content_polyshape.tscn b/src/ui_widgets/element_content_polyshape.tscn
index 77423aa..8858432 100644
--- a/src/ui_widgets/element_content_polyshape.tscn
+++ b/src/ui_widgets/element_content_polyshape.tscn
@@ -1,6 +1,6 @@
[gd_scene load_steps=3 format=3 uid="uid://s0cuf24p8bbd"]
-[ext_resource type="Script" path="res://src/ui_widgets/element_content_polyshape.gd" id="1_10fyu"]
+[ext_resource type="Script" uid="uid://d3tlbx5skjsjj" path="res://src/ui_widgets/element_content_polyshape.gd" id="1_10fyu"]
[ext_resource type="PackedScene" uid="uid://b2gchy70px8jt" path="res://src/ui_widgets/points_field.tscn" id="2_8l4pm"]
[node name="ElementContentPoly" type="VBoxContainer"]
diff --git a/src/ui_widgets/element_content_radial_gradient.tscn b/src/ui_widgets/element_content_radial_gradient.tscn
index d5e710e..f001f9e 100644
--- a/src/ui_widgets/element_content_radial_gradient.tscn
+++ b/src/ui_widgets/element_content_radial_gradient.tscn
@@ -1,6 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://bethkcrbf0tf7"]
-[ext_resource type="Script" path="res://src/ui_widgets/element_content_radial_gradient.gd" id="1_le714"]
+[ext_resource type="Script" uid="uid://1mmtphi577im" path="res://src/ui_widgets/element_content_radial_gradient.gd" id="1_le714"]
[node name="ElementContentRadialGradient" type="VBoxContainer"]
offset_right = 40.0
diff --git a/src/ui_widgets/element_content_stop.gd b/src/ui_widgets/element_content_stop.gd
index 03adf33..0737e3b 100644
--- a/src/ui_widgets/element_content_stop.gd
+++ b/src/ui_widgets/element_content_stop.gd
@@ -5,11 +5,7 @@ extends VBoxContainer
var element: Element
func _ready() -> void:
- var offset_input_field := AttributeFieldBuilder.create("offset", element)
- attribute_container.add_child(offset_input_field)
-
- var color_input_field := AttributeFieldBuilder.create("stop-color", element)
- attribute_container.add_child(color_input_field)
-
- var opacity_input_field := AttributeFieldBuilder.create("stop-opacity", element)
- attribute_container.add_child(opacity_input_field)
+ for attrib_name in ["offset", "stop-color", "stop-opacity"]:
+ var input_field := AttributeFieldBuilder.create(attrib_name, element)
+ 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_stop.tscn b/src/ui_widgets/element_content_stop.tscn
index 28da9c2..16e2129 100644
--- a/src/ui_widgets/element_content_stop.tscn
+++ b/src/ui_widgets/element_content_stop.tscn
@@ -1,6 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://5l4oyxbb7rek"]
-[ext_resource type="Script" path="res://src/ui_widgets/element_content_stop.gd" id="1_ymc3o"]
+[ext_resource type="Script" uid="uid://b21t3dseqwcdt" path="res://src/ui_widgets/element_content_stop.gd" id="1_ymc3o"]
[node name="ElementContentStop" type="VBoxContainer"]
offset_right = 40.0
diff --git a/src/ui_widgets/element_content_unrecognized.tscn b/src/ui_widgets/element_content_unrecognized.tscn
index 3d02bb5..a0575ed 100644
--- a/src/ui_widgets/element_content_unrecognized.tscn
+++ b/src/ui_widgets/element_content_unrecognized.tscn
@@ -1,6 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://b70dhqt8ldcmc"]
-[ext_resource type="Script" path="res://src/ui_widgets/element_content_unrecognized.gd" id="1_vy7fr"]
+[ext_resource type="Script" uid="uid://cpgpynf4om34b" path="res://src/ui_widgets/element_content_unrecognized.gd" id="1_vy7fr"]
[node name="ElementContentUnrecognized" type="VBoxContainer"]
size_flags_horizontal = 3
diff --git a/src/ui_widgets/element_frame.gd b/src/ui_widgets/element_frame.gd
index 60596a2..3d35a8e 100644
--- a/src/ui_widgets/element_frame.gd
+++ b/src/ui_widgets/element_frame.gd
@@ -16,7 +16,8 @@ const element_content_types: Dictionary[String, PackedScene] = {
"linearGradient": preload("res://src/ui_widgets/element_content_linear_gradient.tscn"),
"radialGradient": preload("res://src/ui_widgets/element_content_radial_gradient.tscn"),
}
-const ElementContentUnrecognized = preload("res://src/ui_widgets/element_content_unrecognized.tscn")
+const ElementContentUnrecognizedScene =\
+ preload("res://src/ui_widgets/element_content_unrecognized.tscn")
@onready var main_container: VBoxContainer = $MainContainer
@onready var title_bar: Control = $TitleBar
@@ -35,6 +36,7 @@ func _ready() -> void:
State.selection_changed.connect(determine_selection_highlight)
State.hover_changed.connect(determine_selection_highlight)
State.proposed_drop_changed.connect(queue_redraw)
+ State.xnode_dragging_state_changed.connect(_on_xnodes_dragging_state_changed)
title_bar.draw.connect(_on_title_bar_draw)
mouse_entered.connect(_on_mouse_entered)
mouse_exited.connect(_on_mouse_exited)
@@ -56,13 +58,15 @@ func _ready() -> void:
unknown_container = HFlowContainer.new()
main_container.add_child(unknown_container)
main_container.move_child(unknown_container, 0)
- unknown_container.add_child(AttributeFieldBuilder.create(attribute.name, element))
+ var unknown_field := AttributeFieldBuilder.create(attribute.name, element)
+ unknown_field.focus_entered.connect(State.normal_select.bind(element.xid))
+ unknown_container.add_child(unknown_field)
var element_content: Control
if element.name in element_content_types:
element_content = element_content_types[element.name].instantiate()
else:
- element_content = ElementContentUnrecognized.instantiate()
+ element_content = ElementContentUnrecognizedScene.instantiate()
element_content.element = element
main_container.add_child(element_content)
@@ -81,14 +85,15 @@ func _get_drag_data(_at_position: Vector2) -> Variant:
if suppress_drag or State.selected_xids.is_empty():
return null
+ State.set_selection_dragged(true)
var data: Array[PackedInt32Array] = XIDUtils.filter_descendants(
State.selected_xids.duplicate(true))
set_drag_preview(XNodeChildrenBuilder.generate_drag_preview(data))
return data
-func _notification(what: int) -> void:
- if what == NOTIFICATION_DRAG_END:
- modulate = Color(1, 1, 1)
+func _on_xnodes_dragging_state_changed() -> void:
+ modulate.a = 0.55 if (State.is_xnode_selection_dragged and\
+ element.xid in State.selected_xids) else 1.0
func _on_title_button_pressed() -> void:
@@ -167,18 +172,25 @@ func _on_mouse_exited() -> void:
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
+ if element is ElementPath or element is ElementPolygon or element is ElementPolyline:
+ var attributes_container := main_container.get_child(0)
+ for attribute in element.get_all_attributes():
+ if not DB.is_attribute_recognized(element.name, attribute.name):
+ attributes_container = main_container.get_child(1)
+ break
+
+ if element is ElementPath:
+ var inner_rect: Rect2 = attributes_container.path_field.get_inner_rect(idx)
+ inner_rect.position += main_container.position
+ inner_rect.position += attributes_container.position
+ inner_rect.position += attributes_container.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:
diff --git a/src/ui_widgets/element_frame.tscn b/src/ui_widgets/element_frame.tscn
index 118299b..ec1c43c 100644
--- a/src/ui_widgets/element_frame.tscn
+++ b/src/ui_widgets/element_frame.tscn
@@ -1,6 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://dfvfsput41mnw"]
-[ext_resource type="Script" path="res://src/ui_widgets/element_frame.gd" id="1_5mc4m"]
+[ext_resource type="Script" uid="uid://cgtbtlg2nfwvt" path="res://src/ui_widgets/element_frame.gd" id="1_5mc4m"]
[node name="ElementFrame" type="Container"]
offset_left = 2.0
diff --git a/src/ui_widgets/enum_dropdown.gd b/src/ui_widgets/enum_dropdown.gd
deleted file mode 100644
index 22917c6..0000000
--- a/src/ui_widgets/enum_dropdown.gd
+++ /dev/null
@@ -1,25 +0,0 @@
-# A dropdown with multiple options, not tied to any attribute.
-extends HBoxContainer
-
-signal value_changed(new_value: int)
-
-@onready var line_edit: BetterLineEdit = $LineEdit
-
-@export var values := PackedStringArray()
-
-var _value := -1
-
-func set_value(new_value: int) -> void:
- if new_value != _value:
- _value = new_value
- value_changed.emit(_value)
- line_edit.text = values[_value]
-
-func _on_button_pressed() -> void:
- var btn_arr: Array[Button] = []
- for idx in values.size():
- btn_arr.append(ContextPopup.create_button(values[idx], set_value.bind(idx),
- _value == idx))
- var value_picker := ContextPopup.new()
- value_picker.setup(btn_arr, false, size.x)
- HandlerGUI.popup_under_rect(value_picker, line_edit.get_global_rect(), get_viewport())
diff --git a/src/ui_widgets/enum_dropdown.gd.uid b/src/ui_widgets/enum_dropdown.gd.uid
deleted file mode 100644
index 5ab8c47..0000000
--- a/src/ui_widgets/enum_dropdown.gd.uid
+++ /dev/null
@@ -1 +0,0 @@
-uid://cmywc1vb0rw8y
diff --git a/src/ui_widgets/enum_dropdown.tscn b/src/ui_widgets/enum_dropdown.tscn
deleted file mode 100644
index 27a7a11..0000000
--- a/src/ui_widgets/enum_dropdown.tscn
+++ /dev/null
@@ -1,34 +0,0 @@
-[gd_scene load_steps=5 format=3 uid="uid://bp1iblavfxjvu"]
-
-[ext_resource type="Script" path="res://src/ui_widgets/enum_dropdown.gd" id="1_fmykh"]
-[ext_resource type="FontFile" uid="uid://clpf84p1lfwlp" path="res://assets/fonts/Font.ttf" id="2_qeova"]
-[ext_resource type="Script" path="res://src/ui_widgets/BetterLineEdit.gd" id="3_w88iq"]
-[ext_resource type="Texture2D" uid="uid://coda6chhcatal" path="res://assets/icons/Arrow.svg" id="4_tk7o3"]
-
-[node name="EnumDropdown" type="HBoxContainer"]
-custom_minimum_size = Vector2(0, 22)
-offset_right = 49.0
-offset_bottom = 22.0
-size_flags_horizontal = 0
-size_flags_vertical = 0
-theme_override_constants/separation = 0
-script = ExtResource("1_fmykh")
-
-[node name="LineEdit" type="LineEdit" parent="."]
-layout_mode = 2
-size_flags_horizontal = 3
-theme_type_variation = &"RightConnectedLineEdit"
-theme_override_fonts/font = ExtResource("2_qeova")
-editable = false
-script = ExtResource("3_w88iq")
-
-[node name="Button" type="Button" parent="."]
-custom_minimum_size = Vector2(15, 0)
-layout_mode = 2
-focus_mode = 0
-mouse_default_cursor_shape = 2
-theme_type_variation = &"LeftConnectedButton"
-icon = ExtResource("4_tk7o3")
-expand_icon = true
-
-[connection signal="pressed" from="Button" to="." method="_on_button_pressed"]
diff --git a/src/ui_widgets/enum_field.gd b/src/ui_widgets/enum_field.gd
index 135aa84..9584e01 100644
--- a/src/ui_widgets/enum_field.gd
+++ b/src/ui_widgets/enum_field.gd
@@ -7,28 +7,25 @@ var attribute_name: String # May propagate.
const reload_icon = preload("res://assets/icons/Reload.svg")
func set_value(new_value: String, save := false) -> void:
- sync(new_value)
element.set_attribute(attribute_name, new_value)
+ sync()
if save:
State.queue_svg_save()
-func sync_to_attribute() -> void:
- set_value(element.get_attribute_value(attribute_name, true))
-
func setup_placeholder() -> void:
placeholder_text = element.get_default(attribute_name)
func _ready() -> void:
- Configs.basic_colors_changed.connect(resync)
- sync_to_attribute()
+ Configs.basic_colors_changed.connect(sync)
+ sync()
element.attribute_changed.connect(_on_element_attribute_changed)
if attribute_name in DB.propagated_attributes:
element.ancestor_attribute_changed.connect(_on_element_ancestor_attribute_changed)
text_submitted.connect(_on_text_submitted)
focus_entered.connect(reset_font_color)
text_changed.connect(_on_text_changed)
- text_change_canceled.connect(sync_to_attribute)
+ text_change_canceled.connect(sync)
pressed.connect(_on_pressed)
button_gui_input.connect(_on_button_gui_input)
tooltip_text = attribute_name
@@ -37,26 +34,26 @@ func _ready() -> void:
func _on_element_attribute_changed(attribute_changed: String) -> void:
if attribute_name == attribute_changed:
- sync_to_attribute()
+ sync()
func _on_element_ancestor_attribute_changed(attribute_changed: String) -> void:
if attribute_name == attribute_changed:
setup_placeholder()
- resync()
+ sync()
func _on_pressed() -> void:
var btn_arr: Array[Button] = []
# Add a default.
var reset_btn := ContextPopup.create_button("", set_value.bind("", true),
- element.get_attribute_value(attribute_name, true).is_empty(), reload_icon)
+ element.get_attribute_value(attribute_name).is_empty(), reload_icon)
reset_btn.icon_alignment = HORIZONTAL_ALIGNMENT_CENTER
btn_arr.append(reset_btn)
# Add a button for each enum value.
for enum_constant in DB.attribute_enum_values[attribute_name]:
var btn := ContextPopup.create_button(enum_constant,
set_value.bind(enum_constant, true),
- enum_constant == element.get_attribute_value(attribute_name, true))
+ enum_constant == element.get_attribute_value(attribute_name))
if enum_constant == element.get_default(attribute_name):
btn.add_theme_font_override("font", ThemeUtils.bold_font)
btn_arr.append(btn)
@@ -69,17 +66,16 @@ func _on_text_submitted(new_text: String) -> void:
if new_text.is_empty() or new_text in DB.attribute_enum_values[attribute_name]:
set_value(new_text, true)
else:
- sync_to_attribute()
+ sync()
func _on_text_changed(new_text: String) -> void:
font_color = Configs.savedata.get_validity_color(
not new_text in DB.attribute_enum_values[attribute_name])
-func resync() -> void:
- sync(text)
-func sync(new_value: String) -> void:
+func sync() -> void:
+ var new_value := element.get_attribute_value(attribute_name)
text = new_value
reset_font_color()
if new_value == element.get_default(attribute_name):
diff --git a/src/ui_widgets/enum_field.tscn b/src/ui_widgets/enum_field.tscn
index 6813451..7f30830 100644
--- a/src/ui_widgets/enum_field.tscn
+++ b/src/ui_widgets/enum_field.tscn
@@ -1,6 +1,6 @@
[gd_scene load_steps=3 format=3 uid="uid://d2da0thyq5rq8"]
-[ext_resource type="Script" path="res://src/ui_widgets/enum_field.gd" id="1_1jqoy"]
+[ext_resource type="Script" uid="uid://bplpuld6fqv7q" path="res://src/ui_widgets/enum_field.gd" id="1_1jqoy"]
[ext_resource type="Texture2D" uid="uid://coda6chhcatal" path="res://assets/icons/Arrow.svg" id="3_vhd8v"]
[node name="EnumField" type="Control"]
diff --git a/src/ui_widgets/flag_field.gd b/src/ui_widgets/flag_field.gd
index 28e06de..106c059 100644
--- a/src/ui_widgets/flag_field.gd
+++ b/src/ui_widgets/flag_field.gd
@@ -50,9 +50,6 @@ func _draw() -> void:
func _make_custom_tooltip(for_text: String) -> Object:
var label := Label.new()
- label.begin_bulk_theme_override()
label.add_theme_font_override("font", ThemeUtils.mono_font)
- label.add_theme_font_size_override("font_size", 13)
- label.end_bulk_theme_override()
label.text = for_text
return label
diff --git a/src/ui_widgets/flag_field.tscn b/src/ui_widgets/flag_field.tscn
index 217ecd6..abbaa08 100644
--- a/src/ui_widgets/flag_field.tscn
+++ b/src/ui_widgets/flag_field.tscn
@@ -1,7 +1,7 @@
[gd_scene load_steps=7 format=3 uid="uid://br8g7w38jguh4"]
[ext_resource type="FontFile" uid="uid://depydd16jq777" path="res://assets/fonts/FontMono.ttf" id="1_p8s8y"]
-[ext_resource type="Script" path="res://src/ui_widgets/flag_field.gd" id="2_0bhg4"]
+[ext_resource type="Script" uid="uid://ndfvlxytvcm8" path="res://src/ui_widgets/flag_field.gd" id="2_0bhg4"]
[sub_resource type="FontVariation" id="FontVariation_46ud6"]
base_font = ExtResource("1_p8s8y")
diff --git a/src/ui_widgets/good_color_picker.gd b/src/ui_widgets/good_color_picker.gd
index d62b17b..1e26014 100644
--- a/src/ui_widgets/good_color_picker.gd
+++ b/src/ui_widgets/good_color_picker.gd
@@ -1,6 +1,6 @@
extends VBoxContainer
-const EyedropperPopup = preload("res://src/ui_parts/eyedropper_popup.tscn")
+const EyedropperPopupScene = preload("res://src/ui_parts/eyedropper_popup.tscn")
const handle_texture = preload("res://assets/icons/BWHandle.svg")
const slider_arrow = preload("res://assets/icons/SliderArrow.svg")
@@ -11,14 +11,15 @@ var alpha_enabled := false
var is_none_keyword_available := false
var is_current_color_keyword_available := false
-var UR := UndoRedo.new()
+var undo_redo := UndoRedoRef.new()
enum SliderMode {RGB, HSV}
var slider_mode: SliderMode:
set(new_mode):
slider_mode = new_mode
var disabled_button := hsv_button if new_mode == SliderMode.HSV else rgb_button
- for btn in [hsv_button, rgb_button]:
+ var arr: Array[Button] = [hsv_button, rgb_button]
+ for btn in arr:
btn.disabled = (btn == disabled_button)
btn.mouse_default_cursor_shape = Control.CURSOR_ARROW if\
btn == disabled_button else Control.CURSOR_POINTING_HAND
@@ -111,8 +112,9 @@ func update_keyword_button() -> void:
func _ready() -> void:
# Set up signals.
widgets_arr[0].gui_input.connect(parse_slider_input.bind(0, true))
- for i in [1, 2, 3]:
- widgets_arr[i].gui_input.connect(parse_slider_input.bind(i))
+ widgets_arr[1].gui_input.connect(parse_slider_input.bind(1))
+ widgets_arr[2].gui_input.connect(parse_slider_input.bind(2))
+ widgets_arr[3].gui_input.connect(parse_slider_input.bind(3))
if alpha_enabled:
alpha_slider.visible = alpha_enabled
widgets_arr[4].gui_input.connect(parse_slider_input.bind(4))
@@ -125,7 +127,6 @@ func _ready() -> void:
func _exit_tree() -> void:
RenderingServer.free_rid(color_wheel_surface)
- UR.free()
func register_visual_change(new_color: Color, use_backup := true) -> void:
if use_backup and new_color == backup_display_color:
@@ -133,13 +134,13 @@ func register_visual_change(new_color: Color, use_backup := true) -> void:
elif not use_backup and new_color == display_color:
return
- UR.create_action("")
- UR.add_do_method(set_color.bind(hex(new_color), new_color))
+ undo_redo.create_action("")
+ undo_redo.add_do_method(set_color.bind(hex(new_color), new_color))
if use_backup:
- UR.add_undo_method(set_color.bind(backup_color, backup_display_color))
+ undo_redo.add_undo_method(set_color.bind(backup_color, backup_display_color))
else:
- UR.add_undo_method(set_color.bind(color, display_color))
- UR.commit_action()
+ undo_redo.add_undo_method(set_color.bind(color, display_color))
+ undo_redo.commit_action()
func set_color(new_color: String, new_display_color: Color) -> void:
@@ -162,8 +163,9 @@ func update() -> void:
tracks_arr[0].material.set_shader_parameter("v", display_color.v)
tracks_arr[0].material.set_shader_parameter("base_color",
Color.from_hsv(display_color.h, display_color.s, 1.0))
- for i in [1, 2, 3]:
- tracks_arr[i].material.set_shader_parameter("base_color", display_color)
+ tracks_arr[1].material.set_shader_parameter("base_color", display_color)
+ tracks_arr[2].material.set_shader_parameter("base_color", display_color)
+ tracks_arr[3].material.set_shader_parameter("base_color", display_color)
if alpha_enabled:
tracks_arr[4].material.set_shader_parameter("base_color", display_color)
# Redraw widgets, color indicators, color wheel.
@@ -339,22 +341,22 @@ func _on_keyword_button_pressed() -> void:
get_viewport())
func set_to_keyword(keyword: String) -> void:
- UR.create_action("")
+ undo_redo.create_action("")
if color.strip_edges() == keyword:
- UR.add_do_method(set_color.bind(backup_color, backup_display_color))
- UR.add_undo_method(set_color.bind(color, display_color))
+ undo_redo.add_do_method(set_color.bind(backup_color, backup_display_color))
+ undo_redo.add_undo_method(set_color.bind(color, display_color))
else:
backup()
- UR.add_do_method(set_color.bind(keyword, display_color))
- UR.add_undo_method(set_color.bind(color, display_color))
- UR.commit_action()
+ undo_redo.add_do_method(set_color.bind(keyword, display_color))
+ undo_redo.add_undo_method(set_color.bind(color, display_color))
+ undo_redo.commit_action()
func _on_reset_color_button_pressed() -> void:
reset_color_button.disabled = true
- UR.create_action("")
- UR.add_do_method(set_color.bind(starting_color, starting_display_color))
- UR.add_undo_method(set_color.bind(color, display_color))
- UR.commit_action()
+ undo_redo.create_action("")
+ undo_redo.add_do_method(set_color.bind(starting_color, starting_display_color))
+ undo_redo.add_undo_method(set_color.bind(color, display_color))
+ undo_redo.commit_action()
func _on_rgb_pressed() -> void:
@@ -417,8 +419,10 @@ func _on_track_resized() -> void:
queue_redraw_widgets()
func queue_redraw_widgets() -> void:
- for i in [0, 1, 2, 3]:
- widgets_arr[i].queue_redraw()
+ widgets_arr[0].queue_redraw()
+ widgets_arr[1].queue_redraw()
+ widgets_arr[2].queue_redraw()
+ widgets_arr[3].queue_redraw()
if alpha_enabled:
widgets_arr[4].queue_redraw()
@@ -468,16 +472,16 @@ func _input(event: InputEvent) -> void:
return
if ShortcutUtils.is_action_pressed(event, "redo"):
- if UR.has_redo():
- UR.redo()
+ if undo_redo.has_redo():
+ undo_redo.redo()
accept_event()
elif ShortcutUtils.is_action_pressed(event, "undo"):
- if UR.has_undo():
- UR.undo()
+ if undo_redo.has_undo():
+ undo_redo.undo()
accept_event()
func _on_eyedropper_pressed() -> void:
- var eyedropper_popup := EyedropperPopup.instantiate()
+ var eyedropper_popup := EyedropperPopupScene.instantiate()
eyedropper_popup.color_picked.connect(register_visual_change.bind(false))
HandlerGUI.add_popup(eyedropper_popup)
diff --git a/src/ui_widgets/good_color_picker.tscn b/src/ui_widgets/good_color_picker.tscn
index 2b9eee8..77885dd 100644
--- a/src/ui_widgets/good_color_picker.tscn
+++ b/src/ui_widgets/good_color_picker.tscn
@@ -1,11 +1,11 @@
[gd_scene load_steps=19 format=3 uid="uid://b1eig44cov474"]
-[ext_resource type="Script" path="res://src/ui_widgets/good_color_picker.gd" id="1_0pc78"]
-[ext_resource type="Shader" path="res://src/shaders/color_wheel.gdshader" id="2_nf1uk"]
+[ext_resource type="Script" uid="uid://bof1kkpsf7cmx" path="res://src/ui_widgets/good_color_picker.gd" id="1_0pc78"]
+[ext_resource type="Shader" uid="uid://dwop5cmg7i04p" path="res://src/shaders/color_wheel.gdshader" id="2_nf1uk"]
[ext_resource type="Texture2D" uid="uid://7vnn8bloi26s" path="res://assets/icons/Cube.svg" id="4_ewifk"]
-[ext_resource type="Shader" path="res://src/shaders/slider_visuals.gdshader" id="5_acxpg"]
+[ext_resource type="Shader" uid="uid://b5e666h18rkbo" path="res://src/shaders/slider_visuals.gdshader" id="5_acxpg"]
[ext_resource type="Texture2D" uid="uid://cvh3kwbucf2n1" path="res://assets/icons/Reload.svg" id="5_rh0xc"]
-[ext_resource type="Script" path="res://src/ui_widgets/BetterLineEdit.gd" id="6_aqyoh"]
+[ext_resource type="Script" uid="uid://1hox6gd5pxku" path="res://src/ui_widgets/BetterLineEdit.gd" id="6_aqyoh"]
[ext_resource type="Texture2D" uid="uid://stpallv5q0rb" path="res://assets/icons/backgrounds/CheckerboardMini.svg" id="6_qq1j3"]
[ext_resource type="Texture2D" uid="uid://brff7fx0puj6" path="res://assets/icons/Eyedropper.svg" id="6_titg4"]
@@ -142,8 +142,8 @@ grow_horizontal = 2
grow_vertical = 2
focus_mode = 0
theme_type_variation = &"TextButton"
-theme_override_colors/icon_normal_color = Color(0, 0, 0, 0)
theme_override_colors/icon_disabled_color = Color(0, 0, 0, 0)
+theme_override_colors/icon_normal_color = Color(0, 0, 0, 0)
icon = ExtResource("5_rh0xc")
icon_alignment = 1
diff --git a/src/ui_widgets/id_field.gd b/src/ui_widgets/id_field.gd
index ad01b2c..ee81fd5 100644
--- a/src/ui_widgets/id_field.gd
+++ b/src/ui_widgets/id_field.gd
@@ -5,38 +5,35 @@ var element: Element
const attribute_name = "id" # Never propagates.
func set_value(new_value: String, save := false) -> void:
- sync(new_value)
element.set_attribute(attribute_name, new_value)
+ sync()
if save:
State.queue_svg_save()
func _ready() -> void:
- Configs.basic_colors_changed.connect(resync)
- sync_to_attribute()
+ Configs.basic_colors_changed.connect(sync)
+ sync()
element.attribute_changed.connect(_on_element_attribute_changed)
text_changed.connect(_on_text_changed)
text_submitted.connect(_on_text_submitted)
- text_change_canceled.connect(sync_to_attribute)
+ text_change_canceled.connect(sync)
focus_entered.connect(_on_focus_entered)
tooltip_text = attribute_name
func _on_element_attribute_changed(attribute_changed: String) -> void:
if attribute_name == attribute_changed:
- sync_to_attribute()
+ sync()
func _on_focus_entered() -> void:
remove_theme_color_override("font_color")
-func sync_to_attribute() -> void:
- set_value(element.get_attribute_value(attribute_name, true))
-
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, true)
else:
- sync_to_attribute()
+ sync()
func _on_text_changed(new_text: String) -> void:
var validity_level := AttributeID.get_validity(new_text)
@@ -45,9 +42,6 @@ func _on_text_changed(new_text: String) -> void:
validity_level == AttributeID.ValidityLevel.INVALID_XML_NAMETOKEN)
add_theme_color_override("font_color", font_color)
-func resync() -> void:
- sync(text)
-
-func sync(new_value: String) -> void:
- text = new_value
+func sync() -> void:
+ text = element.get_attribute_value(attribute_name)
remove_theme_color_override("font_color")
diff --git a/src/ui_widgets/id_field.tscn b/src/ui_widgets/id_field.tscn
index 2703006..05ad6a5 100644
--- a/src/ui_widgets/id_field.tscn
+++ b/src/ui_widgets/id_field.tscn
@@ -1,6 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://6tbfb2t0tyfh"]
-[ext_resource type="Script" path="res://src/ui_widgets/id_field.gd" id="1_dqgkn"]
+[ext_resource type="Script" uid="uid://dla17p5hsn7e1" path="res://src/ui_widgets/id_field.gd" id="1_dqgkn"]
[node name="IdField" type="LineEdit"]
custom_minimum_size = Vector2(54, 22)
diff --git a/src/ui_widgets/mini_number_field.gd b/src/ui_widgets/mini_number_field.gd
index 2914070..0d39646 100644
--- a/src/ui_widgets/mini_number_field.gd
+++ b/src/ui_widgets/mini_number_field.gd
@@ -9,6 +9,9 @@ var _value := NAN # Must not be updated directly.
var default := NAN # Setting to an empty value would bring you back to the default.
+func _ready() -> void:
+ text_submitted.connect(_on_text_submitted)
+
func set_value(new_value: float, no_signal := false) -> void:
if not is_finite(new_value):
text = NumstringParser.basic_num_to_text(_value)
diff --git a/src/ui_widgets/mini_number_field.tscn b/src/ui_widgets/mini_number_field.tscn
index dd0cec4..a87a6e0 100644
--- a/src/ui_widgets/mini_number_field.tscn
+++ b/src/ui_widgets/mini_number_field.tscn
@@ -1,6 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://cm3uphorskcix"]
-[ext_resource type="Script" path="res://src/ui_widgets/mini_number_field.gd" id="1_ggi5v"]
+[ext_resource type="Script" uid="uid://dcnkg4irpqdl" path="res://src/ui_widgets/mini_number_field.gd" id="1_ggi5v"]
[node name="MiniNumberField" type="LineEdit"]
custom_minimum_size = Vector2(0, 18)
@@ -10,5 +10,3 @@ mouse_filter = 1
theme_type_variation = &"MiniLineEdit"
script = ExtResource("1_ggi5v")
mono_font_tooltip = true
-
-[connection signal="text_submitted" from="." to="." method="_on_text_submitted"]
diff --git a/src/ui_widgets/number_dropdown.tscn b/src/ui_widgets/number_dropdown.tscn
index 710cb14..d415eca 100644
--- a/src/ui_widgets/number_dropdown.tscn
+++ b/src/ui_widgets/number_dropdown.tscn
@@ -1,8 +1,8 @@
[gd_scene load_steps=5 format=3 uid="uid://b4qgdo1od11hx"]
-[ext_resource type="Script" path="res://src/ui_widgets/number_dropdown.gd" id="1_axymu"]
+[ext_resource type="Script" uid="uid://trxmy0mb706i" path="res://src/ui_widgets/number_dropdown.gd" id="1_axymu"]
[ext_resource type="FontFile" uid="uid://clpf84p1lfwlp" path="res://assets/fonts/Font.ttf" id="2_v4n2i"]
-[ext_resource type="Script" path="res://src/ui_widgets/BetterLineEdit.gd" id="3_y7lt6"]
+[ext_resource type="Script" uid="uid://1hox6gd5pxku" path="res://src/ui_widgets/BetterLineEdit.gd" id="3_y7lt6"]
[ext_resource type="Texture2D" uid="uid://coda6chhcatal" path="res://assets/icons/Arrow.svg" id="4_vet1k"]
[node name="NumberDropdown" type="HBoxContainer"]
diff --git a/src/ui_widgets/number_edit.gd b/src/ui_widgets/number_edit.gd
index 169f544..3f89d4c 100644
--- a/src/ui_widgets/number_edit.gd
+++ b/src/ui_widgets/number_edit.gd
@@ -1,9 +1,21 @@
# A number editor, not tied to any attribute.
extends BetterLineEdit
-@export var min_value := 0.0
-@export var max_value := 1.0
-@export var initial_value := 0.5
+@export var min_value := 0.0:
+ set(new_value):
+ if min_value != new_value:
+ min_value = new_value
+ if _value < min_value:
+ set_value(min_value)
+
+@export var max_value := 1.0:
+ set(new_value):
+ if max_value != new_value:
+ max_value = new_value
+ if _value > max_value:
+ set_value(max_value)
+
+@export var initial_value := 1.0
@export var allow_lower := true
@export var allow_higher := true
@export var is_float := true
diff --git a/src/ui_widgets/number_edit.tscn b/src/ui_widgets/number_edit.tscn
index 2cc9ef5..a9aae34 100644
--- a/src/ui_widgets/number_edit.tscn
+++ b/src/ui_widgets/number_edit.tscn
@@ -1,6 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://dad7fkhmsooc6"]
-[ext_resource type="Script" path="res://src/ui_widgets/number_edit.gd" id="1_dywrg"]
+[ext_resource type="Script" uid="uid://csvfi4sy8ky68" path="res://src/ui_widgets/number_edit.gd" id="1_dywrg"]
[node name="NumberEdit" type="LineEdit"]
custom_minimum_size = Vector2(0, 22)
diff --git a/src/ui_widgets/number_field.gd b/src/ui_widgets/number_field.gd
index e31193a..68251e0 100644
--- a/src/ui_widgets/number_field.gd
+++ b/src/ui_widgets/number_field.gd
@@ -17,18 +17,17 @@ 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 AttributeNumeric.text_check_percentage(new_value):
var numeric_value := NumstringParser.evaluate(new_value)
# Validate the value.
if !is_finite(numeric_value):
- sync_to_attribute()
+ sync()
return
numeric_value = clampf(numeric_value, cached_min_value, cached_max_value)
new_value = element.get_attribute(attribute_name).num_to_text(numeric_value)
- sync(new_value)
element.set_attribute(attribute_name, new_value)
+ sync()
if save:
State.queue_svg_save()
@@ -37,37 +36,32 @@ func setup_placeholder() -> void:
func _ready() -> void:
- Configs.basic_colors_changed.connect(resync)
- sync_to_attribute()
+ Configs.basic_colors_changed.connect(sync)
+ sync()
element.attribute_changed.connect(_on_element_attribute_changed)
if attribute_name in DB.propagated_attributes:
element.ancestor_attribute_changed.connect(_on_element_ancestor_attribute_changed)
tooltip_text = attribute_name
text_submitted.connect(set_value.bind(true))
- text_change_canceled.connect(sync_to_attribute)
+ text_change_canceled.connect(sync)
focus_entered.connect(_on_focus_entered)
setup_placeholder()
func _on_element_attribute_changed(attribute_changed: String) -> void:
if attribute_name == attribute_changed:
- set_value(element.get_attribute_value(attribute_name, true))
+ set_value(element.get_attribute_value(attribute_name))
func _on_element_ancestor_attribute_changed(attribute_changed: String) -> void:
if attribute_name == attribute_changed:
setup_placeholder()
- resync()
+ sync()
func _on_focus_entered() -> void:
remove_theme_color_override("font_color")
-func sync_to_attribute() -> void:
- sync(element.get_attribute_value(attribute_name, true))
-
-func resync() -> void:
- sync(text)
-
-func sync(new_value: String) -> void:
+func sync() -> void:
+ var new_value := element.get_attribute_value(attribute_name)
text = new_value
remove_theme_color_override("font_color")
if new_value == element.get_default(attribute_name):
diff --git a/src/ui_widgets/number_field_with_slider.gd b/src/ui_widgets/number_field_with_slider.gd
index 1ab952e..6e1d256 100644
--- a/src/ui_widgets/number_field_with_slider.gd
+++ b/src/ui_widgets/number_field_with_slider.gd
@@ -11,12 +11,11 @@ const MAX_VALUE := 1.0
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):
- sync_to_attribute()
+ sync()
return
if numeric_value > MAX_VALUE:
@@ -25,8 +24,8 @@ func set_value(new_value: String, save := false) -> void:
numeric_value = MIN_VALUE
new_value = element.get_attribute(attribute_name).num_to_text(numeric_value)
- sync(new_value)
element.set_attribute(attribute_name, new_value)
+ sync()
if save:
State.queue_svg_save()
@@ -38,14 +37,14 @@ func setup_placeholder() -> void:
func _ready() -> void:
- Configs.basic_colors_changed.connect(resync)
- sync_to_attribute()
+ Configs.basic_colors_changed.connect(sync)
+ sync()
element.attribute_changed.connect(_on_element_attribute_changed)
if attribute_name in DB.propagated_attributes:
element.ancestor_attribute_changed.connect(_on_element_ancestor_attribute_changed)
text_submitted.connect(set_value.bind(true))
focus_entered.connect(reset_font_color)
- text_change_canceled.connect(sync_to_attribute)
+ text_change_canceled.connect(sync)
button_gui_input.connect(_on_slider_gui_input)
tooltip_text = attribute_name
setup_placeholder()
@@ -53,20 +52,15 @@ func _ready() -> void:
func _on_element_attribute_changed(attribute_changed: String) -> void:
if attribute_name == attribute_changed:
- sync_to_attribute()
+ sync()
func _on_element_ancestor_attribute_changed(attribute_changed: String) -> void:
if attribute_name == attribute_changed:
setup_placeholder()
- resync()
+ sync()
-func sync_to_attribute() -> void:
- set_value(element.get_attribute_value(attribute_name, true))
-
-func resync() -> void:
- sync(text)
-
-func sync(new_value: String) -> void:
+func sync() -> void:
+ var new_value := element.get_attribute_value(attribute_name)
text = new_value
reset_font_color()
if new_value == element.get_default(attribute_name):
@@ -76,7 +70,7 @@ func sync(new_value: String) -> void:
# Slider
-var initial_slider_value: float
+var initial_slider_value: String
var slider_dragged := false:
set(new_value):
if slider_dragged != new_value:
@@ -90,6 +84,7 @@ var slider_dragged := false:
remove_child(temp_button)
add_child(temp_button)
+
var slider_hovered := false:
set(new_value):
if slider_hovered != new_value:
@@ -133,12 +128,12 @@ func _on_slider_gui_input(event: InputEvent) -> void:
else:
temp_button.mouse_filter = Utils.mouse_filter_pass_non_drag_events(event)
+ if event is InputEventMouseMotion and event.button_mask == 0:
+ slider_hovered = true
if not slider_dragged:
- if event is InputEventMouseMotion and event.button_mask == 0:
- slider_hovered = true
if Utils.is_event_drag_start(event):
slider_dragged = true
- initial_slider_value = element.get_attribute_num(attribute_name)
+ initial_slider_value = element.get_attribute_value(attribute_name)
set_num(get_slider_value_at_y(event.position.y))
else:
if Utils.is_event_drag(event):
@@ -146,13 +141,14 @@ func _on_slider_gui_input(event: InputEvent) -> void:
elif Utils.is_event_drag_end(event):
slider_dragged = false
var final_slider_value := get_slider_value_at_y(event.position.y)
- if initial_slider_value != final_slider_value:
+ if initial_slider_value !=\
+ element.get_attribute(attribute_name).num_to_text(final_slider_value):
set_num(final_slider_value, true)
func _unhandled_input(event: InputEvent) -> void:
if slider_dragged and Utils.is_event_drag_cancel(event):
slider_dragged = false
- set_num(initial_slider_value)
+ set_value(initial_slider_value)
accept_event()
func get_slider_value_at_y(y_coord: float) -> float:
diff --git a/src/ui_widgets/options_dialog.gd b/src/ui_widgets/options_dialog.gd
index b591825..ed65be7 100644
--- a/src/ui_widgets/options_dialog.gd
+++ b/src/ui_widgets/options_dialog.gd
@@ -8,13 +8,24 @@ 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:
+func add_option(action_text: String, action: Callable, focused := false,
+free_on_click := true) -> 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)
+ if free_on_click:
+ button.pressed.connect(queue_free)
options_container.add_child(button)
if focused:
button.grab_focus()
+
+# For simplicity's sake.
+func add_cancel_option() -> void:
+ var button := Button.new()
+ button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
+ button.text = Translator.translate("Cancel")
+ button.size_flags_horizontal = Control.SIZE_EXPAND | Control.SIZE_SHRINK_CENTER
+ button.pressed.connect(queue_free)
+ options_container.add_child(button)
diff --git a/src/ui_widgets/palette_config.gd b/src/ui_widgets/palette_config.gd
index 988c8af..30b3d25 100644
--- a/src/ui_widgets/palette_config.gd
+++ b/src/ui_widgets/palette_config.gd
@@ -1,9 +1,9 @@
extends PanelContainer
-const ColorSwatchType = preload("res://src/ui_widgets/color_swatch_config.gd")
+const ColorSwatch = preload("res://src/ui_widgets/color_swatch_config.gd")
-const ColorSwatch = preload("res://src/ui_widgets/color_swatch_config.tscn")
-const ConfigurePopup = preload("res://src/ui_widgets/configure_color_popup.tscn")
+const ColorSwatchScene = preload("res://src/ui_widgets/color_swatch_config.tscn")
+const ConfigurePopupScene = preload("res://src/ui_widgets/configure_color_popup.tscn")
const plus_icon = preload("res://assets/icons/Plus.svg")
signal layout_changed
@@ -18,12 +18,13 @@ var currently_edited_idx := -1
func setup_theme() -> void:
palette_button.begin_bulk_theme_override()
- for theming in ["normal", "hover", "pressed"]:
- var stylebox := palette_button.get_theme_stylebox(theming).duplicate()
+ const CONST_ARR: PackedStringArray = ["normal", "hover", "pressed"]
+ for theme_type in CONST_ARR:
+ var stylebox := palette_button.get_theme_stylebox(theme_type).duplicate()
stylebox.content_margin_top -= 3
stylebox.content_margin_bottom -= 2
stylebox.content_margin_left += 1
- palette_button.add_theme_stylebox_override(theming, stylebox)
+ palette_button.add_theme_stylebox_override(theme_type, stylebox)
palette_button.end_bulk_theme_override()
var panel_stylebox := get_theme_stylebox("panel").duplicate()
panel_stylebox.content_margin_top = panel_stylebox.content_margin_bottom
@@ -56,7 +57,7 @@ func rebuild_colors() -> void:
set_label_text(palette.title)
for i in palette.get_color_count():
- var swatch := ColorSwatch.instantiate()
+ var swatch := ColorSwatchScene.instantiate()
swatch.palette = palette
swatch.idx = i
swatch.pressed.connect(popup_configure_color.bind(swatch))
@@ -75,7 +76,7 @@ func rebuild_colors() -> void:
fake_swatch.focus_mode = Control.FOCUS_NONE
fake_swatch.mouse_filter = Control.MOUSE_FILTER_PASS
fake_swatch.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
- var color_swatch_ref := ColorSwatch.instantiate()
+ var color_swatch_ref := ColorSwatchScene.instantiate()
fake_swatch.custom_minimum_size = color_swatch_ref.custom_minimum_size
color_swatch_ref.queue_free()
fake_swatch.pressed.connect(popup_add_color)
@@ -94,7 +95,7 @@ func display_warnings() -> void:
func popup_configure_color(swatch: Button) -> void:
- var configure_popup := ConfigurePopup.instantiate()
+ var configure_popup := ConfigurePopupScene.instantiate()
configure_popup.palette = swatch.palette
configure_popup.idx = swatch.idx
configure_popup.color_deletion_requested.connect(remove_color.bind(swatch.idx))
@@ -118,10 +119,13 @@ func hide_name_edit() -> void:
# Update text color to red if the title won't work (because it's a duplicate).
func _on_name_edit_text_changed(new_text: String) -> void:
- for theme_color in ["font_color", "font_hover_color"]:
- name_edit.add_theme_color_override(theme_color,
+ name_edit.begin_bulk_theme_override()
+ const CONST_ARR: PackedStringArray = ["font_color", "font_hover_color"]
+ for theme_type in CONST_ARR:
+ name_edit.add_theme_color_override(theme_type,
Configs.savedata.get_validity_color(false, new_text != palette.title and\
not Configs.savedata.is_palette_title_unused(new_text)))
+ name_edit.end_bulk_theme_override()
func _on_name_edit_text_submitted(new_title: String) -> void:
new_title = new_title.strip_edges()
@@ -150,18 +154,19 @@ func set_label_text(new_text: String) -> void:
else:
palette_button.text = new_text
palette_button.begin_bulk_theme_override()
+ const CONST_ARR: PackedStringArray = ["font_color", "font_hover_color", "font_pressed_color"]
if palette.title.is_empty():
- for theme_name in ["font_color", "font_hover_color", "font_pressed_color"]:
- palette_button.add_theme_color_override(theme_name,
+ for theme_type in CONST_ARR:
+ palette_button.add_theme_color_override(theme_type,
ThemeUtils.common_subtle_text_color)
else:
if not Configs.savedata.is_palette_valid(palette):
- for theme_name in ["font_color", "font_hover_color", "font_pressed_color"]:
- palette_button.add_theme_color_override(theme_name,
+ for theme_type in CONST_ARR:
+ palette_button.add_theme_color_override(theme_type,
Configs.savedata.basic_color_error)
else:
- for theme_name in ["font_color", "font_hover_color", "font_pressed_color"]:
- palette_button.remove_theme_color_override(theme_name)
+ for theme_type in CONST_ARR:
+ palette_button.remove_theme_color_override(theme_type)
palette_button.end_bulk_theme_override()
func delete() -> void:
@@ -234,7 +239,7 @@ func _on_palette_button_pressed() -> void:
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")))
+ save_palette.bind(palette_idx), false, load("res://assets/icons/Export.svg")))
var context_popup := ContextPopup.new()
context_popup.setup(btn_arr, true, -1, -1, PackedInt32Array([separator_idx]))
@@ -256,7 +261,7 @@ func _can_drop_data(_at_position: Vector2, data: Variant) -> bool:
var buffer := 6
var pos := colors_container.get_local_mouse_position()
- if not (data is ColorSwatchType.DropData and\
+ if not (data is ColorSwatch.DropData and\
Rect2(Vector2.ZERO, colors_container.size).grow(buffer).has_point(pos)):
clear_proposed_drop()
return false
@@ -276,7 +281,7 @@ func _can_drop_data(_at_position: Vector2, data: Variant) -> bool:
proposed_drop_idx = new_idx
for swatch in get_swatches():
- swatch.proposed_drop_data = ColorSwatchType.DropData.new(palette, new_idx)
+ swatch.proposed_drop_data = ColorSwatch.DropData.new(palette, new_idx)
swatch.queue_redraw()
return data.palette != palette or (data.palette == palette and\
data.index != new_idx and data.index != new_idx - 1)
diff --git a/src/ui_widgets/palette_config.tscn b/src/ui_widgets/palette_config.tscn
index 86b3b2c..f8cd4c8 100644
--- a/src/ui_widgets/palette_config.tscn
+++ b/src/ui_widgets/palette_config.tscn
@@ -1,7 +1,7 @@
[gd_scene load_steps=6 format=3 uid="uid://c5cavwa2xdt0b"]
-[ext_resource type="Script" path="res://src/ui_widgets/palette_config.gd" id="1_2fy8k"]
-[ext_resource type="Script" path="res://src/ui_widgets/BetterLineEdit.gd" id="2_coqnx"]
+[ext_resource type="Script" uid="uid://e73am1tdeku" path="res://src/ui_widgets/palette_config.gd" id="1_2fy8k"]
+[ext_resource type="Script" uid="uid://1hox6gd5pxku" path="res://src/ui_widgets/BetterLineEdit.gd" id="2_coqnx"]
[ext_resource type="FontFile" uid="uid://clpf84p1lfwlp" path="res://assets/fonts/Font.ttf" id="2_cttpy"]
[ext_resource type="Texture2D" uid="uid://dbyjet4nt246k" path="res://assets/icons/Warning.svg" id="4_43bpt"]
diff --git a/src/ui_widgets/path_command_button.tscn b/src/ui_widgets/path_command_button.tscn
index 4d3d78c..b5042f7 100644
--- a/src/ui_widgets/path_command_button.tscn
+++ b/src/ui_widgets/path_command_button.tscn
@@ -1,6 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://co2btefrqrm0e"]
-[ext_resource type="Script" path="res://src/ui_widgets/path_command_button.gd" id="1_q6blt"]
+[ext_resource type="Script" uid="uid://fln0r81yv0rn" path="res://src/ui_widgets/path_command_button.gd" id="1_q6blt"]
[node name="PathCommandButton" type="Button"]
custom_minimum_size = Vector2(0, 24)
diff --git a/src/ui_widgets/pathdata_field.gd b/src/ui_widgets/pathdata_field.gd
index c902cfa..c023d38 100644
--- a/src/ui_widgets/pathdata_field.gd
+++ b/src/ui_widgets/pathdata_field.gd
@@ -20,8 +20,8 @@ const STRIP_HEIGHT = 22.0
signal focused
-const MiniNumberField = preload("mini_number_field.tscn")
-const FlagField = preload("flag_field.tscn")
+const MiniNumberFieldScene = preload("mini_number_field.tscn")
+const FlagFieldScene = preload("flag_field.tscn")
const more_icon = preload("res://assets/icons/SmallMore.svg")
const plus_icon = preload("res://assets/icons/Plus.svg")
@@ -52,23 +52,20 @@ 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_value(attribute_name, true))
+ sync()
if save:
State.queue_svg_save()
-func sync_to_attribute() -> void:
- set_value(element.get_attribute_value(attribute_name, true))
-
func setup() -> void:
Configs.language_changed.connect(update_translation)
- sync_to_attribute()
+ sync()
element.attribute_changed.connect(_on_element_attribute_changed)
line_edit.tooltip_text = attribute_name
line_edit.text_submitted.connect(set_value.bind(true))
line_edit.text_changed.connect(setup_font)
- line_edit.text_change_canceled.connect(func(): setup_font(line_edit.text))
- line_edit.text_change_canceled.connect(sync_to_attribute)
+ line_edit.text_change_canceled.connect(func() -> void: setup_font(line_edit.text))
+ line_edit.text_change_canceled.connect(sync)
line_edit.focus_entered.connect(_on_line_edit_focus_entered)
commands_container.draw.connect(_commands_draw)
commands_container.gui_input.connect(_on_commands_gui_input)
@@ -90,7 +87,7 @@ func get_inner_rect(index: int) -> Rect2:
func _on_element_attribute_changed(attribute_changed: String) -> void:
if attribute_name == attribute_changed:
- sync_to_attribute()
+ sync()
func update_translation() -> void:
line_edit.placeholder_text = Translator.translate("No path data")
@@ -104,7 +101,14 @@ func setup_font(new_text: String) -> void:
else:
line_edit.remove_theme_font_override("font")
-func sync(new_value: String) -> void:
+var last_synced_value := " " # Invalid initial string.
+
+func sync() -> void:
+ var new_value := element.get_attribute_value(attribute_name)
+ if last_synced_value == new_value:
+ return
+ last_synced_value = new_value
+
line_edit.text = new_value
setup_font(new_value)
# A plus button for adding a move command if empty.
@@ -269,7 +273,7 @@ func _commands_draw() -> void:
draw_numfield(rect, "rot", cmd)
rect.position.x = rect.end.x + 4
rect.size.x = 19
- var flag_field := FlagField.instantiate()
+ var flag_field := FlagFieldScene.instantiate()
var is_large_arc: bool = (cmd.large_arc_flag == 0)
var is_sweep: bool = (cmd.sweep_flag == 0)
flag_field.get_theme_stylebox("normal" if is_large_arc\
@@ -377,7 +381,6 @@ func setup_path_command_controls(idx: int) -> Control:
relative_button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
relative_button.begin_bulk_theme_override()
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 are unused, but must be set for the correct content margins.
if is_absolute:
@@ -425,8 +428,8 @@ func setup_path_command_controls(idx: int) -> Control:
field_rx.mode = field_rx.Mode.ONLY_POSITIVE
field_ry.mode = field_ry.Mode.ONLY_POSITIVE
field_rot.mode = field_rot.Mode.HALF_ANGLE
- var field_large_arc := FlagField.instantiate()
- var field_sweep := FlagField.instantiate()
+ var field_large_arc := FlagFieldScene.instantiate()
+ var field_sweep := FlagFieldScene.instantiate()
field_large_arc.gui_input.connect(_eat_double_clicks.bind(field_large_arc))
field_sweep.gui_input.connect(_eat_double_clicks.bind(field_sweep))
fields = [field_rx, field_ry, field_rot, field_large_arc, field_sweep,
@@ -476,7 +479,7 @@ func setup_path_command_controls(idx: int) -> Control:
func numfield(cmd_idx: int) -> BetterLineEdit:
- var new_field := MiniNumberField.instantiate()
+ var new_field := MiniNumberFieldScene.instantiate()
new_field.focus_entered.connect(State.normal_select.bind(element.xid, cmd_idx))
return new_field
diff --git a/src/ui_widgets/pathdata_field.tscn b/src/ui_widgets/pathdata_field.tscn
index 443c01a..47116f6 100644
--- a/src/ui_widgets/pathdata_field.tscn
+++ b/src/ui_widgets/pathdata_field.tscn
@@ -1,7 +1,7 @@
[gd_scene load_steps=9 format=3 uid="uid://dqy5lv33sy5r7"]
-[ext_resource type="Script" path="res://src/ui_widgets/pathdata_field.gd" id="1_22rk2"]
-[ext_resource type="Script" path="res://src/ui_widgets/BetterLineEdit.gd" id="2_48xgh"]
+[ext_resource type="Script" uid="uid://p4nyhlbpwxe2" path="res://src/ui_widgets/pathdata_field.gd" id="1_22rk2"]
+[ext_resource type="Script" uid="uid://1hox6gd5pxku" path="res://src/ui_widgets/BetterLineEdit.gd" id="2_48xgh"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_2f3ln"]
content_margin_left = 5.0
@@ -100,7 +100,7 @@ corner_radius_bottom_right = 4
corner_radius_bottom_left = 4
[node name="PathdataField" type="VBoxContainer"]
-offset_right = 358.0
+offset_right = 250.0
offset_bottom = 45.0
theme_override_constants/separation = 2
script = ExtResource("1_22rk2")
@@ -112,7 +112,7 @@ relative_button_hovered = SubResource("StyleBoxFlat_7fw87")
relative_button_pressed = SubResource("StyleBoxFlat_l7drx")
[node name="LineEdit" type="LineEdit" parent="."]
-custom_minimum_size = Vector2(358, 0)
+custom_minimum_size = Vector2(250, 0)
layout_mode = 2
size_flags_horizontal = 0
focus_mode = 1
@@ -120,6 +120,6 @@ script = ExtResource("2_48xgh")
mono_font_tooltip = true
[node name="Commands" type="Control" parent="."]
-custom_minimum_size = Vector2(344, 0)
+custom_minimum_size = Vector2(250, 0)
layout_mode = 2
size_flags_horizontal = 8
diff --git a/src/ui_widgets/points_field.gd b/src/ui_widgets/points_field.gd
index a90a338..68e0afa 100644
--- a/src/ui_widgets/points_field.gd
+++ b/src/ui_widgets/points_field.gd
@@ -13,7 +13,7 @@ const STRIP_HEIGHT = 22.0
signal focused
-const MiniNumberField = preload("mini_number_field.tscn")
+const MiniNumberFieldScene = preload("mini_number_field.tscn")
const more_icon = preload("res://assets/icons/SmallMore.svg")
const plus_icon = preload("res://assets/icons/Plus.svg")
@@ -44,22 +44,19 @@ 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_value(attribute_name, true))
+ sync()
if save:
State.queue_svg_save()
-func sync_to_attribute() -> void:
- set_value(element.get_attribute_value(attribute_name))
-
func setup() -> void:
Configs.language_changed.connect(update_translation)
- sync_to_attribute()
+ sync()
element.attribute_changed.connect(_on_element_attribute_changed)
line_edit.tooltip_text = attribute_name
line_edit.text_submitted.connect(set_value.bind(true))
line_edit.text_changed.connect(setup_font)
- line_edit.text_change_canceled.connect(func(): setup_font(line_edit.text))
+ line_edit.text_change_canceled.connect(func() -> void: setup_font(line_edit.text))
line_edit.focus_entered.connect(_on_line_edit_focus_entered)
points_container.draw.connect(points_draw)
points_container.gui_input.connect(_on_points_gui_input)
@@ -81,7 +78,7 @@ func get_inner_rect(index: int) -> Rect2:
func _on_element_attribute_changed(attribute_changed: String) -> void:
if attribute_name == attribute_changed:
- sync_to_attribute()
+ sync()
func update_translation() -> void:
line_edit.placeholder_text = Translator.translate("No points")
@@ -95,7 +92,14 @@ func setup_font(new_text: String) -> void:
else:
line_edit.remove_theme_font_override("font")
-func sync(new_value: String) -> void:
+var last_synced_value := " " # Invalid initial string.
+
+func sync() -> void:
+ var new_value := element.get_attribute_value(attribute_name)
+ if last_synced_value == new_value:
+ return
+ last_synced_value = new_value
+
line_edit.text = new_value
setup_font(new_value)
# A plus button for adding a first point if empty.
@@ -194,7 +198,7 @@ func _on_points_gui_input(event: InputEvent) -> void:
if event.double_click:
State.normal_select(element.xid, 0)
State.shift_select(element.xid,
- element.get_attribute(attribute_name).get_list_size() / 2)
+ element.get_attribute(attribute_name).get_list_size() / 2 - 1)
elif event.is_command_or_control_pressed():
State.ctrl_select(element.xid, cmd_idx)
elif event.shift_pressed:
@@ -346,7 +350,7 @@ func setup_point_controls(idx: int) -> Control:
func numfield(cmd_idx: int) -> BetterLineEdit:
- var new_field := MiniNumberField.instantiate()
+ var new_field := MiniNumberFieldScene.instantiate()
new_field.focus_entered.connect(State.normal_select.bind(element.xid, cmd_idx))
return new_field
diff --git a/src/ui_widgets/points_field.tscn b/src/ui_widgets/points_field.tscn
index 112b287..8e254e0 100644
--- a/src/ui_widgets/points_field.tscn
+++ b/src/ui_widgets/points_field.tscn
@@ -1,7 +1,7 @@
[gd_scene load_steps=3 format=3 uid="uid://b2gchy70px8jt"]
-[ext_resource type="Script" path="res://src/ui_widgets/points_field.gd" id="1_pnk5w"]
-[ext_resource type="Script" path="res://src/ui_widgets/BetterLineEdit.gd" id="2_4uivs"]
+[ext_resource type="Script" uid="uid://bey3843fto5dc" path="res://src/ui_widgets/points_field.gd" id="1_pnk5w"]
+[ext_resource type="Script" uid="uid://1hox6gd5pxku" path="res://src/ui_widgets/BetterLineEdit.gd" id="2_4uivs"]
[node name="PointsField" type="VBoxContainer"]
offset_right = 358.0
diff --git a/src/ui_widgets/presented_shortcut.tscn b/src/ui_widgets/presented_shortcut.tscn
index ed30930..b27c66f 100644
--- a/src/ui_widgets/presented_shortcut.tscn
+++ b/src/ui_widgets/presented_shortcut.tscn
@@ -1,6 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://owphyjtiueai"]
-[ext_resource type="Script" path="res://src/ui_widgets/presented_shortcut.gd" id="1_mfl8k"]
+[ext_resource type="Script" uid="uid://beewlny6guury" path="res://src/ui_widgets/presented_shortcut.gd" id="1_mfl8k"]
[node name="PresentedShortcut" type="PanelContainer"]
offset_right = 172.0
@@ -21,6 +21,7 @@ theme_override_constants/separation = 6
[node name="Label" type="Label" parent="MarginContainer/MainContainer"]
layout_mode = 2
+theme_override_font_sizes/font_size = 15
horizontal_alignment = 1
[node name="ShortcutContainer" type="HBoxContainer" parent="MarginContainer/MainContainer"]
diff --git a/src/ui_widgets/preview_rect.gd b/src/ui_widgets/preview_rect.gd
index 0ad5de1..08f8907 100644
--- a/src/ui_widgets/preview_rect.gd
+++ b/src/ui_widgets/preview_rect.gd
@@ -9,6 +9,8 @@ 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:
+ if not is_node_ready():
+ await ready
var scaling_factor := size.x / maxf(dimensions.x, dimensions.y)
var img := Image.new()
var err := img.load_svg_from_string(svg_text, scaling_factor)
@@ -16,18 +18,14 @@ func setup_svg(svg_text: String, dimensions: Vector2) -> void:
img.fix_alpha_edges()
_set_image(img)
-func setup_image(config: ImageExportData, full_scale := false) -> void:
- var final_image_config: ImageExportData
- if full_scale:
- final_image_config = config
- else:
- final_image_config = ImageExportData.new()
- final_image_config.format = config.format
- final_image_config.lossy = config.lossy
- final_image_config.quality = config.quality
- 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))
+func setup_image(config: ImageExportData) -> void:
+ var final_image_config := ImageExportData.new()
+ final_image_config.format = config.format
+ final_image_config.lossy = config.lossy
+ final_image_config.quality = config.quality
+ 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))
var buffer := final_image_config.image_to_buffer(final_image_config.generate_image())
var image := Image.new()
@@ -36,12 +34,22 @@ func setup_image(config: ImageExportData, full_scale := false) -> void:
"jpg", "jpeg": image.load_jpg_from_buffer(buffer)
"webp": image.load_webp_from_buffer(buffer)
- var factor := size.x / maxf(image.get_width(), image.get_height())
- var interp := Image.INTERPOLATE_NEAREST if factor >= 3 else Image.INTERPOLATE_BILINEAR
- image.resize(int(image.get_width() * factor), int(image.get_height() * factor), interp)
+ var factor := minf(size.x / image.get_width(), size.y / image.get_height())
+ var final_width := maxi(int(image.get_width() * factor), 1)
+ var final_height := maxi(int(image.get_height() * factor), 1)
+ image.resize(final_width, final_height,
+ Image.INTERPOLATE_NEAREST if factor >= 2 else Image.INTERPOLATE_BILINEAR)
_set_image(image)
func _set_image(image: Image) -> void:
var image_texture := ImageTexture.create_from_image(image)
texture_preview.texture = image_texture
checkerboard.custom_minimum_size = image_texture.get_size()
+ checkerboard.material.set_shader_parameter("uv_scale",
+ 256 / maxf(image_texture.get_width(), image_texture.get_height()))
+
+func shrink_to_fit(true_minimum_width: float, true_minimum_height: float) -> void:
+ if not is_node_ready():
+ await ready
+ custom_minimum_size.x = maxf(checkerboard.custom_minimum_size.x, true_minimum_width)
+ custom_minimum_size.y = maxf(checkerboard.custom_minimum_size.y, true_minimum_height)
diff --git a/src/ui_widgets/preview_rect.tscn b/src/ui_widgets/preview_rect.tscn
index a91952e..924d21b 100644
--- a/src/ui_widgets/preview_rect.tscn
+++ b/src/ui_widgets/preview_rect.tscn
@@ -1,16 +1,14 @@
-[gd_scene load_steps=6 format=3 uid="uid://xh26qa68xed4"]
+[gd_scene load_steps=5 format=3 uid="uid://xh26qa68xed4"]
[ext_resource type="Script" uid="uid://cx43kir18is86" path="res://src/ui_widgets/preview_rect.gd" id="1_n2xbu"]
-[ext_resource type="Shader" uid="uid://ki2mjb6y33jl" path="res://src/shaders/zoom_shader.gdshader" id="2_qrqjp"]
+[ext_resource type="Shader" uid="uid://i2y5pyhcgra2" path="res://src/shaders/zoom_shader.gdshader" id="2_qrqjp"]
[ext_resource type="Texture2D" uid="uid://c68og6bsqt0lb" path="res://assets/icons/backgrounds/Checkerboard.svg" id="3_tuqha"]
-[ext_resource type="Texture2D" uid="uid://crx4kcj4o01bs" path="res://assets/icons/SmallQuestionMark.svg" id="4_g76n0"]
[sub_resource type="ShaderMaterial" id="ShaderMaterial_y7eee"]
shader = ExtResource("2_qrqjp")
-shader_parameter/uv_scale = 2.0
+shader_parameter/uv_scale = 1.0
[node name="PreviewRect" type="CenterContainer"]
-custom_minimum_size = Vector2(128, 128)
script = ExtResource("1_n2xbu")
[node name="Checkerboard" type="TextureRect" parent="."]
@@ -27,6 +25,5 @@ anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
-texture = ExtResource("4_g76n0")
expand_mode = 2
stretch_mode = 5
diff --git a/src/ui_widgets/profile_frame.gd b/src/ui_widgets/profile_frame.gd
index 2ed708f..16c82ce 100644
--- a/src/ui_widgets/profile_frame.gd
+++ b/src/ui_widgets/profile_frame.gd
@@ -1,22 +1,25 @@
# This is similar to SettingFrame, but specifically for dropdowns without a default value.
extends Control
+const Dropdown = preload("res://src/ui_widgets/dropdown.gd")
+
signal value_changed
-const Dropdown = preload("res://src/ui_widgets/dropdown.tscn")
-const EnumDropdown = preload("res://src/ui_widgets/enum_dropdown.tscn")
+const DropdownScene = preload("res://src/ui_widgets/dropdown.tscn")
var getter: Callable
var setter: Callable
var text: String
var ci := get_canvas_item()
-var dropdown: Control
+var dropdown: Dropdown
var is_hovered := false
-func setup_dropdown(enum_mode := false) -> void:
- dropdown = EnumDropdown.instantiate() if enum_mode else Dropdown.instantiate()
+func setup_dropdown(values: Array, value_text_map: Dictionary) -> void:
+ dropdown = DropdownScene.instantiate()
+ dropdown.values = values
+ dropdown.value_text_map = value_text_map
func _ready() -> void:
add_child(dropdown)
diff --git a/src/ui_widgets/profile_frame.tscn b/src/ui_widgets/profile_frame.tscn
index 1b7a676..0c2e84c 100644
--- a/src/ui_widgets/profile_frame.tscn
+++ b/src/ui_widgets/profile_frame.tscn
@@ -1,6 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://clp287ssgovaa"]
-[ext_resource type="Script" path="res://src/ui_widgets/profile_frame.gd" id="1_urnhl"]
+[ext_resource type="Script" uid="uid://dfp0yap8evr57" path="res://src/ui_widgets/profile_frame.gd" id="1_urnhl"]
[node name="ProfileFrame" type="Control"]
custom_minimum_size = Vector2(0, 28)
diff --git a/src/ui_widgets/setting_frame.gd b/src/ui_widgets/setting_frame.gd
index 477edc6..afeb8e8 100644
--- a/src/ui_widgets/setting_frame.gd
+++ b/src/ui_widgets/setting_frame.gd
@@ -5,9 +5,9 @@ var type := Type.NONE
signal value_changed
-const ColorEdit = preload("res://src/ui_widgets/color_edit.tscn")
-const Dropdown = preload("res://src/ui_widgets/enum_dropdown.tscn")
-const NumberDropdown = preload("res://src/ui_widgets/number_dropdown.tscn")
+const ColorEditScene = preload("res://src/ui_widgets/color_edit.tscn")
+const DropdownScene = preload("res://src/ui_widgets/dropdown.tscn")
+const NumberDropdownScene = preload("res://src/ui_widgets/number_dropdown.tscn")
var getter: Callable
var setter: Callable
@@ -33,6 +33,7 @@ var ci := get_canvas_item()
func permanent_disable_checkbox(checkbox_state: bool) -> void:
disabled = true
widget.set_pressed_no_signal(checkbox_state)
+ widget.text = "On" if checkbox_state else "Off"
func setup_checkbox() -> void:
widget = CheckBox.new()
@@ -46,7 +47,7 @@ func setup_checkbox() -> void:
panel_width = 76
func setup_color(enable_alpha: bool) -> void:
- widget = ColorEdit.instantiate()
+ widget = ColorEditScene.instantiate()
widget.enable_alpha = enable_alpha
widget.enable_palettes = false
widget.value = getter.call().to_html(enable_alpha)
@@ -55,9 +56,13 @@ func setup_color(enable_alpha: bool) -> void:
type = Type.COLOR
panel_width = 114 if enable_alpha else 100
-func setup_dropdown(values: PackedStringArray) -> void:
- widget = Dropdown.instantiate()
+# TODO Typed Dictionary wonkiness
+func setup_dropdown(values: Array[Variant],
+value_text_map: Dictionary) -> void: # Dictionary[Variant, String]
+ widget = DropdownScene.instantiate()
widget.values = values
+ widget.restricted = true
+ widget.value_text_map = value_text_map
add_child(widget)
widget.value_changed.connect(_dropdown_modification)
type = Type.DROPDOWN
@@ -65,7 +70,7 @@ func setup_dropdown(values: PackedStringArray) -> void:
func setup_number_dropdown(values: Array[float], is_integer: bool, restricted: bool,
min_value: float, max_value: float) -> void:
- widget = NumberDropdown.instantiate()
+ widget = NumberDropdownScene.instantiate()
widget.values = values
widget.is_integer = is_integer
widget.restricted = restricted
@@ -161,11 +166,11 @@ func _draw() -> void:
if is_hovered:
get_theme_stylebox("hover", "FlatButton").draw(ci, Rect2(Vector2.ZERO, size))
- var color := Color(1, 1, 1, 0.9)
+ var color := ThemeUtils.common_text_color
if disabled:
color = ThemeUtils.common_subtle_text_color
elif dim_text:
- color = Color(1, 1, 1, 0.5)
+ color = ThemeUtils.common_dimmer_text_color
var non_panel_width := size.x - panel_width
var text_obj := TextLine.new()
diff --git a/src/ui_widgets/setting_frame.tscn b/src/ui_widgets/setting_frame.tscn
index 4dbb83b..231c898 100644
--- a/src/ui_widgets/setting_frame.tscn
+++ b/src/ui_widgets/setting_frame.tscn
@@ -1,6 +1,6 @@
[gd_scene load_steps=3 format=3 uid="uid://speqrhruag5k"]
-[ext_resource type="Script" path="res://src/ui_widgets/setting_frame.gd" id="1_p1ngn"]
+[ext_resource type="Script" uid="uid://3tqaiuqu1h01" path="res://src/ui_widgets/setting_frame.gd" id="1_p1ngn"]
[ext_resource type="Texture2D" uid="uid://cvh3kwbucf2n1" path="res://assets/icons/Reload.svg" id="2_8lqnd"]
[node name="SettingFrame" type="Control"]
diff --git a/src/ui_widgets/setting_shortcut.gd b/src/ui_widgets/setting_shortcut.gd
index ab45050..acaf075 100644
--- a/src/ui_widgets/setting_shortcut.gd
+++ b/src/ui_widgets/setting_shortcut.gd
@@ -50,11 +50,16 @@ func sync() -> void:
shortcut_container.add_child.call_deferred(new_btn)
shortcut_buttons.append(new_btn)
new_btn.theme_type_variation = "TranslucentButton"
- for style in ["normal", "hover", "pressed", "disabled"]:
- var shortcut_stylebox := get_theme_stylebox(style, "TranslucentButton").duplicate()
+
+ new_btn.begin_bulk_theme_override()
+ const CONST_ARR: PackedStringArray = ["normal", "hover", "pressed", "disabled"]
+ for theme_type in CONST_ARR:
+ var shortcut_stylebox := get_theme_stylebox(theme_type,
+ "TranslucentButton").duplicate()
shortcut_stylebox.content_margin_top = 0
shortcut_stylebox.content_margin_bottom = 0
- new_btn.add_theme_stylebox_override(style, shortcut_stylebox)
+ new_btn.add_theme_stylebox_override(theme_type, shortcut_stylebox)
+ new_btn.end_bulk_theme_override()
new_btn.button_mask = MOUSE_BUTTON_MASK_LEFT | MOUSE_BUTTON_MASK_RIGHT
new_btn.size_flags_horizontal = Control.SIZE_EXPAND_FILL
@@ -156,10 +161,20 @@ func _input(event: InputEvent) -> void:
accept_event()
elif event.is_released():
if pending_event.keycode & KEY_MODIFIER_MASK == 0:
+ # Makes sure the saved event is clean.
+ var saved_event := InputEventKey.new()
+ saved_event.device = -1
+ saved_event.command_or_control_autoremap = (pending_event.ctrl_pressed or\
+ pending_event.meta_pressed)
+ saved_event.keycode = pending_event.keycode
+ saved_event.unicode = pending_event.unicode
+ saved_event.alt_pressed = pending_event.alt_pressed
+ saved_event.shift_pressed = pending_event.shift_pressed
+
if listening_idx < events.size():
- events[listening_idx] = pending_event
+ events[listening_idx] = saved_event
else:
- events.append(pending_event)
+ events.append(saved_event)
update_shortcut()
sync()
pending_event = null
@@ -190,11 +205,12 @@ func set_shortcut_button_text(button: Button, new_text: String) -> void:
func check_shortcuts_validity() -> void:
for i in events.size():
+ var event := events[i]
var shortcut_btn := shortcut_buttons[i]
- if not Configs.savedata.is_shortcut_valid(events[i]):
+ if not Configs.savedata.is_shortcut_valid(event):
setup_shortcut_button_font_colors(shortcut_btn,
Configs.savedata.basic_color_error)
- var conflicts := Configs.savedata.get_actions_with_shortcut(events[i])
+ var conflicts := Configs.savedata.get_actions_with_shortcut(event)
var action_pos := conflicts.find(action)
if action_pos != -1:
conflicts.remove_at(action_pos)
@@ -206,10 +222,20 @@ func check_shortcuts_validity() -> void:
shortcut_btn.tooltip_text = Translator.translate("Also used by") +\
":\n" + "\n".join(conflicts)
else:
- shortcut_btn.begin_bulk_theme_override()
- shortcut_btn.remove_theme_color_override("font_color")
- shortcut_btn.remove_theme_color_override("font_focus_color")
- shortcut_btn.remove_theme_color_override("font_hover_color")
- shortcut_btn.remove_theme_color_override("font_pressed_color")
- shortcut_btn.end_bulk_theme_override()
- shortcut_btn.tooltip_text = ""
+ var already_used := false
+ for ii in events.size():
+ if ii != i and event.is_match(events[ii]):
+ already_used = true
+ break
+
+ if already_used:
+ setup_shortcut_button_font_colors(shortcut_btn,
+ Configs.savedata.basic_color_warning)
+ else:
+ shortcut_btn.begin_bulk_theme_override()
+ shortcut_btn.remove_theme_color_override("font_color")
+ shortcut_btn.remove_theme_color_override("font_focus_color")
+ shortcut_btn.remove_theme_color_override("font_hover_color")
+ shortcut_btn.remove_theme_color_override("font_pressed_color")
+ shortcut_btn.end_bulk_theme_override()
+ shortcut_btn.tooltip_text = ""
diff --git a/src/ui_widgets/setting_shortcut.tscn b/src/ui_widgets/setting_shortcut.tscn
index 9b06e24..de07599 100644
--- a/src/ui_widgets/setting_shortcut.tscn
+++ b/src/ui_widgets/setting_shortcut.tscn
@@ -1,6 +1,6 @@
[gd_scene load_steps=3 format=3 uid="uid://b7fw7bodu5fbb"]
-[ext_resource type="Script" uid="uid://ds386q8jgqkr2" path="res://src/ui_widgets/setting_shortcut.gd" id="1_4c8oc"]
+[ext_resource type="Script" uid="uid://civujbqn2um1u" path="res://src/ui_widgets/setting_shortcut.gd" id="1_4c8oc"]
[ext_resource type="Texture2D" uid="uid://cvh3kwbucf2n1" path="res://assets/icons/Reload.svg" id="2_n8e0s"]
[node name="SettingShortcut" type="PanelContainer"]
@@ -17,6 +17,7 @@ theme_override_constants/separation = 6
[node name="Label" type="Label" parent="MainContainer"]
layout_mode = 2
+theme_override_font_sizes/font_size = 15
horizontal_alignment = 1
[node name="HBoxContainer" type="HBoxContainer" parent="MainContainer"]
diff --git a/src/ui_widgets/transform_editor.gd b/src/ui_widgets/transform_editor.gd
index 47582b7..1d1dbf6 100644
--- a/src/ui_widgets/transform_editor.gd
+++ b/src/ui_widgets/transform_editor.gd
@@ -85,6 +85,7 @@ func resync(new_transform: Transform) -> void:
setup_field_defaults_and_colors()
func setup_field_defaults_and_colors() -> void:
+ update_title_font_color()
match type:
"translate":
_fields[1].default = 0
@@ -105,3 +106,10 @@ func determine_field_font_color(field: BetterLineEdit, omit: bool) -> void:
func reset_field_color(field: BetterLineEdit) -> void:
field.remove_theme_color_override("font_color")
+
+func update_title_font_color() -> void:
+ if transform.is_redundant():
+ transform_button.add_theme_color_override("font_color",
+ Color(transform_button.get_theme_color("font_color"), 2/3.0))
+ else:
+ transform_button.remove_theme_color_override("font_color")
diff --git a/src/ui_widgets/transform_editor.tscn b/src/ui_widgets/transform_editor.tscn
index 0b750df..caadf22 100644
--- a/src/ui_widgets/transform_editor.tscn
+++ b/src/ui_widgets/transform_editor.tscn
@@ -1,6 +1,6 @@
[gd_scene load_steps=4 format=3 uid="uid://c7xhf7bodwnl1"]
-[ext_resource type="Script" path="res://src/ui_widgets/transform_editor.gd" id="1_q14io"]
+[ext_resource type="Script" uid="uid://cdipr84aqj703" path="res://src/ui_widgets/transform_editor.gd" id="1_q14io"]
[ext_resource type="FontFile" uid="uid://depydd16jq777" path="res://assets/fonts/FontMono.ttf" id="2_84xsl"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_af5dq"]
diff --git a/src/ui_widgets/transform_field.gd b/src/ui_widgets/transform_field.gd
index b1ed969..f57308f 100644
--- a/src/ui_widgets/transform_field.gd
+++ b/src/ui_widgets/transform_field.gd
@@ -4,24 +4,24 @@ extends LineEditButton
var element: Element
var attribute_name: String # Never propagates.
-const TransformPopup = preload("res://src/ui_widgets/transform_popup.tscn")
+const TransformPopupScene = 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_value(attribute_name, true))
+ sync()
if save:
State.queue_svg_save()
func _ready() -> void:
Configs.language_changed.connect(update_translation)
- sync_to_attribute()
+ sync()
element.attribute_changed.connect(_on_element_attribute_changed)
tooltip_text = attribute_name
text_submitted.connect(set_value.bind(true))
text_changed.connect(setup_font)
setup_font(text)
- text_change_canceled.connect(sync_to_attribute)
+ text_change_canceled.connect(sync)
button_gui_input.connect(_on_button_gui_input)
pressed.connect(_on_pressed)
update_translation()
@@ -29,7 +29,7 @@ func _ready() -> void:
func _on_element_attribute_changed(attribute_changed: String) -> void:
if attribute_name == attribute_changed:
- sync_to_attribute()
+ sync()
func update_translation() -> void:
placeholder_text = Translator.translate("No transforms")
@@ -37,17 +37,14 @@ func update_translation() -> void:
func setup_font(new_text: String) -> void:
use_mono_font = !new_text.is_empty()
-func sync(new_value: String) -> void:
- text = new_value
+func sync() -> void:
+ text = element.get_attribute_value(attribute_name)
func _on_pressed() -> void:
- var transform_popup := TransformPopup.instantiate()
+ var transform_popup := TransformPopupScene.instantiate()
transform_popup.attribute_ref = element.get_attribute(attribute_name)
HandlerGUI.popup_under_rect(transform_popup, get_global_rect(), get_viewport())
-func sync_to_attribute() -> void:
- set_value(element.get_attribute_value(attribute_name, true))
-
func _on_button_gui_input(event: InputEvent) -> void:
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_RIGHT and\
diff --git a/src/ui_widgets/transform_field.tscn b/src/ui_widgets/transform_field.tscn
index b932bed..14733a2 100644
--- a/src/ui_widgets/transform_field.tscn
+++ b/src/ui_widgets/transform_field.tscn
@@ -1,6 +1,6 @@
[gd_scene load_steps=3 format=3 uid="uid://bc45fp38kar6a"]
-[ext_resource type="Script" path="res://src/ui_widgets/transform_field.gd" id="1_5gokr"]
+[ext_resource type="Script" uid="uid://bws54nmwus6y6" path="res://src/ui_widgets/transform_field.gd" id="1_5gokr"]
[ext_resource type="Texture2D" uid="uid://coda6chhcatal" path="res://assets/icons/Arrow.svg" id="2_2uudq"]
[node name="TransformField" type="Control"]
diff --git a/src/ui_widgets/transform_popup.gd b/src/ui_widgets/transform_popup.gd
index 1f1cf35..95360c6 100644
--- a/src/ui_widgets/transform_popup.gd
+++ b/src/ui_widgets/transform_popup.gd
@@ -1,10 +1,10 @@
# A popup for editing a transform list.
extends PanelContainer
-const NumberEditType = preload("res://src/ui_widgets/number_edit.gd")
+const NumberEdit = preload("res://src/ui_widgets/number_edit.gd")
-const MiniNumberField = preload("res://src/ui_widgets/mini_number_field.tscn")
-const TransformEditor = preload("res://src/ui_widgets/transform_editor.tscn")
+const MiniNumberFieldScene = preload("res://src/ui_widgets/mini_number_field.tscn")
+const TransformEditorScene = preload("res://src/ui_widgets/transform_editor.tscn")
const _icons_dict: Dictionary[String, Texture2D] = {
"matrix": preload("res://assets/icons/Matrix.svg"),
@@ -16,14 +16,14 @@ const _icons_dict: Dictionary[String, Texture2D] = {
}
var attribute_ref: AttributeTransformList
-var undo_redo := UndoRedo.new()
-
-@onready var x1_edit: NumberEditType = %FinalMatrix/X1
-@onready var x2_edit: NumberEditType = %FinalMatrix/X2
-@onready var y1_edit: NumberEditType = %FinalMatrix/Y1
-@onready var y2_edit: NumberEditType = %FinalMatrix/Y2
-@onready var o1_edit: NumberEditType = %FinalMatrix/O1
-@onready var o2_edit: NumberEditType = %FinalMatrix/O2
+var undo_redo := UndoRedoRef.new()
+
+@onready var x1_edit: NumberEdit = %FinalMatrix/X1
+@onready var x2_edit: NumberEdit = %FinalMatrix/X2
+@onready var y1_edit: NumberEdit = %FinalMatrix/Y1
+@onready var y2_edit: NumberEdit = %FinalMatrix/Y2
+@onready var o1_edit: NumberEdit = %FinalMatrix/O1
+@onready var o2_edit: NumberEdit = %FinalMatrix/O2
@onready var transform_list: VBoxContainer = %TransformList
@onready var add_button: Button = %AddButton
@onready var apply_matrix: Button = %ApplyMatrix
@@ -37,7 +37,6 @@ func _ready() -> void:
func _exit_tree() -> void:
State.queue_svg_save()
- undo_redo.free()
func update_translation() -> void:
apply_matrix.tooltip_text = Translator.translate("Apply the matrix")
@@ -66,7 +65,7 @@ func rebuild() -> void:
child.queue_free()
while i < transform_count:
var t := attribute_ref.get_transform(i)
- var t_editor := TransformEditor.instantiate()
+ var t_editor := TransformEditorScene.instantiate()
transform_list.add_child(t_editor)
var fields: Array[BetterLineEdit]
# Setup fields.
@@ -97,7 +96,7 @@ func rebuild() -> void:
func create_mini_number_field(idx: int, property: String) -> BetterLineEdit:
- var field := MiniNumberField.instantiate()
+ var field := MiniNumberFieldScene.instantiate()
field.custom_minimum_size.x = 44
field.tooltip_text = property
field.value_changed.connect(update_value.bind(idx, property))
@@ -168,9 +167,11 @@ func popup_transform_actions(idx: int, control: Control) -> void:
func popup_new_transform_context(idx: int, control: Control) -> void:
var btn_array: Array[Button] = []
- for transform in ["matrix", "translate", "rotate", "scale", "skewX", "skewY"]:
- var btn := ContextPopup.create_button(transform,
- insert_transform.bind(idx, transform), false, _icons_dict[transform])
+ const CONST_ARR: PackedStringArray = ["matrix", "translate", "rotate", "scale",
+ "skewX", "skewY"]
+ for transform_type in CONST_ARR:
+ var btn := ContextPopup.create_button(transform_type,
+ insert_transform.bind(idx, transform_type), false, _icons_dict[transform_type])
btn.add_theme_font_override("font", ThemeUtils.mono_font)
btn_array.append(btn)
diff --git a/src/ui_widgets/transform_popup.tscn b/src/ui_widgets/transform_popup.tscn
index de4a005..e7f4702 100644
--- a/src/ui_widgets/transform_popup.tscn
+++ b/src/ui_widgets/transform_popup.tscn
@@ -1,6 +1,6 @@
[gd_scene load_steps=5 format=3 uid="uid://dyc4so8qdkmmc"]
-[ext_resource type="Script" path="res://src/ui_widgets/transform_popup.gd" id="1_ye80i"]
+[ext_resource type="Script" uid="uid://dnolxvseucte" path="res://src/ui_widgets/transform_popup.gd" id="1_ye80i"]
[ext_resource type="PackedScene" uid="uid://dad7fkhmsooc6" path="res://src/ui_widgets/number_edit.tscn" id="2_tdse4"]
[ext_resource type="Texture2D" uid="uid://eif2ioi0mw17" path="res://assets/icons/Plus.svg" id="2_w40hh"]
[ext_resource type="Texture2D" uid="uid://cqg7ga6y3m0v1" path="res://assets/icons/ApplyMatrix.svg" id="3_eegha"]
diff --git a/src/ui_widgets/unrecognized_field.gd b/src/ui_widgets/unrecognized_field.gd
index f124012..afa0ded 100644
--- a/src/ui_widgets/unrecognized_field.gd
+++ b/src/ui_widgets/unrecognized_field.gd
@@ -5,23 +5,20 @@ var element: Element
var attribute_name: String # Assume it doesn't propagate.
func set_value(new_value: String, save := false) -> void:
- sync(new_value)
element.set_attribute(attribute_name, new_value)
+ sync()
if save:
State.queue_svg_save()
-func sync_to_attribute() -> void:
- set_value(element.get_attribute_value(attribute_name, true))
-
func _ready() -> void:
Configs.language_changed.connect(update_translation)
- sync_to_attribute()
+ sync()
update_translation()
text_submitted.connect(set_value.bind(true))
-func sync(new_value: String) -> void:
- text = new_value
+func sync() -> void:
+ text = element.get_attribute_value(attribute_name)
func update_translation() -> void:
tooltip_text = attribute_name + "\n(%s)" %\
diff --git a/src/ui_widgets/unrecognized_field.tscn b/src/ui_widgets/unrecognized_field.tscn
index c23a0aa..72d7c65 100644
--- a/src/ui_widgets/unrecognized_field.tscn
+++ b/src/ui_widgets/unrecognized_field.tscn
@@ -1,6 +1,6 @@
[gd_scene load_steps=3 format=3 uid="uid://mr2c1hti43k4"]
-[ext_resource type="Script" path="res://src/ui_widgets/unrecognized_field.gd" id="1_decfb"]
+[ext_resource type="Script" uid="uid://dk0q2mcxvxiwt" path="res://src/ui_widgets/unrecognized_field.gd" id="1_decfb"]
[ext_resource type="Texture2D" uid="uid://crx4kcj4o01bs" path="res://assets/icons/SmallQuestionMark.svg" id="1_sd7sr"]
[node name="UnrecognizedField" type="LineEdit"]
diff --git a/src/utils/AttributeFieldBuilder.gd b/src/utils/AttributeFieldBuilder.gd
index 4207c9b..76dc437 100644
--- a/src/utils/AttributeFieldBuilder.gd
+++ b/src/utils/AttributeFieldBuilder.gd
@@ -1,24 +1,24 @@
class_name AttributeFieldBuilder
-const TransformField = preload("res://src/ui_widgets/transform_field.tscn")
-const NumberField = preload("res://src/ui_widgets/number_field.tscn")
-const NumberSlider = preload("res://src/ui_widgets/number_field_with_slider.tscn")
-const ColorField = preload("res://src/ui_widgets/color_field.tscn")
-const EnumField = preload("res://src/ui_widgets/enum_field.tscn")
-const IdField = preload("res://src/ui_widgets/id_field.tscn")
-const UnrecognizedField = preload("res://src/ui_widgets/unrecognized_field.tscn")
+const TransformFieldScene = preload("res://src/ui_widgets/transform_field.tscn")
+const NumberFieldScene = preload("res://src/ui_widgets/number_field.tscn")
+const NumberSliderScene = preload("res://src/ui_widgets/number_field_with_slider.tscn")
+const ColorFieldScene = preload("res://src/ui_widgets/color_field.tscn")
+const EnumFieldScene = preload("res://src/ui_widgets/enum_field.tscn")
+const IdFieldScene = preload("res://src/ui_widgets/id_field.tscn")
+const UnrecognizedFieldScene = preload("res://src/ui_widgets/unrecognized_field.tscn")
static func create(attribute: String, element: Element) -> Control:
match DB.get_attribute_type(attribute):
- DB.AttributeType.ID: return _generate_no_name(IdField, element)
- DB.AttributeType.TRANSFORM_LIST: return _generate(TransformField, element, attribute)
- DB.AttributeType.COLOR: return _generate(ColorField, element, attribute)
- DB.AttributeType.ENUM: return _generate(EnumField, element, attribute)
+ DB.AttributeType.ID: return _generate_no_name(IdFieldScene, element)
+ DB.AttributeType.TRANSFORM_LIST: return _generate(TransformFieldScene, element, attribute)
+ DB.AttributeType.COLOR: return _generate(ColorFieldScene, element, attribute)
+ DB.AttributeType.ENUM: return _generate(EnumFieldScene, element, attribute)
DB.AttributeType.NUMERIC:
match DB.attribute_number_range[attribute]:
- DB.NumberRange.UNIT: return _generate(NumberSlider, element, attribute)
- _: return _generate(NumberField, element, attribute)
- _: return _generate(UnrecognizedField, element, attribute)
+ DB.NumberRange.UNIT: return _generate(NumberSliderScene, element, attribute)
+ _: return _generate(NumberFieldScene, element, attribute)
+ _: return _generate(UnrecognizedFieldScene, element, attribute)
static func _generate(widget: PackedScene, element: Element, attribute: String) -> Control:
var widget_instance := widget.instantiate()
diff --git a/src/utils/FileUtils.gd b/src/utils/FileUtils.gd
index 755a655..0be07a6 100644
--- a/src/utils/FileUtils.gd
+++ b/src/utils/FileUtils.gd
@@ -2,12 +2,14 @@
class_name FileUtils extends RefCounted
enum FileState {SAME, DIFFERENT, DOES_NOT_EXIST}
+enum TabCloseMode {SINGLE, TO_LEFT, TO_RIGHT, ALL_OTHERS}
-const GoodFileDialogType = preload("res://src/ui_parts/good_file_dialog.gd")
+const GoodFileDialog = preload("res://src/ui_parts/good_file_dialog.gd")
-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")
+const AlertDialogScene = preload("res://src/ui_widgets/alert_dialog.tscn")
+const OptionsDialogScene = preload("res://src/ui_widgets/options_dialog.tscn")
+const ImportWarningMenuScene = preload("res://src/ui_parts/import_warning_menu.tscn")
+const GoodFileDialogScene = 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
@@ -29,17 +31,26 @@ static func compare_svg_to_disk_contents() -> FileState:
return FileState.DIFFERENT
-static func save_svg() -> void:
- var file_path := Configs.savedata.get_active_tab().svg_file_path
+static func _save_svg_with_custom_final_callback(final_callback: Callable) -> void:
+ var active_tab := Configs.savedata.get_active_tab()
+ var file_path := 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())
+ active_tab.save_to_bound_path()
+ if final_callback.is_valid():
+ final_callback.call()
else:
- save_svg_as()
+ _save_svg_as_with_custom_final_callback(final_callback)
+
+static func _save_svg_as_with_custom_final_callback(final_callback: Callable) -> void:
+ open_export_dialog(ImageExportData.new(), final_callback)
+
+static func save_svg() -> void:
+ _save_svg_with_custom_final_callback(Callable())
static func save_svg_as() -> void:
- open_export_dialog(ImageExportData.new())
+ _save_svg_as_with_custom_final_callback(Callable())
-static func open_export_dialog(export_data: ImageExportData) -> void:
+static func open_export_dialog(export_data: ImageExportData, final_callback := Callable()) -> void:
OS.request_permissions()
if OS.has_feature("web"):
var web_format_name := ImageExportData.web_formats[export_data.format]
@@ -51,23 +62,30 @@ static func open_export_dialog(export_data: ImageExportData) -> void:
else:
if _is_native_preferred():
var native_callback :=\
- func(has_selected: bool, files: PackedStringArray, _filter_idx: int):
+ func(has_selected: bool, files: PackedStringArray, _filter_idx: int) -> void:
if has_selected:
_finish_export(files[0], export_data)
+ if final_callback.is_valid():
+ final_callback.call()
DisplayServer.file_dialog_show(
Translator.translate("Save the .\"{format}\" file").format(
{"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,
+ _choose_file_name() + "." + 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_active_tab_dir(),
- Utils.get_file_name(Configs.savedata.get_active_tab().svg_file_path),
- GoodFileDialogType.FileMode.SAVE, PackedStringArray([export_data.format]))
+ var non_native_callback :=\
+ func(path: String) -> void:
+ _finish_export(path, export_data)
+ if final_callback.is_valid():
+ final_callback.call()
+
+ var export_dialog := GoodFileDialogScene.instantiate()
+ export_dialog.setup(Configs.savedata.get_active_tab_dir(), _choose_file_name(),
+ GoodFileDialog.FileMode.SAVE, PackedStringArray([export_data.format]))
HandlerGUI.add_menu(export_dialog)
- export_dialog.file_selected.connect(func(path): _finish_export(path, export_data))
+ export_dialog.file_selected.connect(non_native_callback)
static func open_xml_export_dialog(xml: String, file_name: String) -> void:
OS.request_permissions()
@@ -76,7 +94,7 @@ static func open_xml_export_dialog(xml: String, file_name: String) -> void:
else:
if _is_native_preferred():
var native_callback :=\
- func(has_selected: bool, files: PackedStringArray, _filter_idx: int):
+ func(has_selected: bool, files: PackedStringArray, _filter_idx: int) -> void:
if has_selected:
_finish_xml_export(files[0], xml)
@@ -86,11 +104,12 @@ static func open_xml_export_dialog(xml: String, file_name: String) -> void:
file_name + ".xml", false, DisplayServer.FILE_DIALOG_MODE_SAVE_FILE,
PackedStringArray(["*.xml"]), native_callback)
else:
- var export_dialog := GoodFileDialog.instantiate()
+ var export_dialog := GoodFileDialogScene.instantiate()
export_dialog.setup(Configs.savedata.get_last_dir(),
- file_name, GoodFileDialogType.FileMode.SAVE, PackedStringArray(["xml"]))
+ file_name, GoodFileDialog.FileMode.SAVE, PackedStringArray(["xml"]))
HandlerGUI.add_menu(export_dialog)
- export_dialog.file_selected.connect(func(path): _finish_xml_export(path, xml))
+ export_dialog.file_selected.connect(
+ func(path: String) -> void: _finish_xml_export(path, xml))
static func _finish_export(file_path: String, export_data: ImageExportData) -> void:
if file_path.get_extension().is_empty():
@@ -105,9 +124,11 @@ 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.get_active_tab().svg_file_path = file_path
- FileAccess.open(file_path, FileAccess.WRITE).store_string(State.get_export_text())
- HandlerGUI.remove_all_menus()
+ var active_tab := Configs.savedata.get_active_tab()
+ active_tab.svg_file_path = file_path
+ active_tab.save_to_bound_path()
+ HandlerGUI.remove_all_menus() # At least for now this is what's always needed.
+
static func _finish_xml_export(file_path: String, xml: String) -> void:
if file_path.get_extension().is_empty():
@@ -122,6 +143,9 @@ static func _is_native_preferred() -> bool:
return DisplayServer.has_feature(DisplayServer.FEATURE_NATIVE_DIALOG_FILE) and\
Configs.savedata.use_native_file_dialog
+static func _choose_file_name() -> String:
+ return Utils.get_file_name(Configs.savedata.get_active_tab().svg_file_path)
+
# No need for completion callback here yet.
static func open_svg_import_dialog() -> void:
@@ -156,7 +180,7 @@ completion_callback: Callable, native_dialog_title := "") -> void:
filters.append("*" + extension)
var native_callback :=\
- func(has_selected: bool, files: PackedStringArray, _filter_idx: int):
+ func(has_selected: bool, files: PackedStringArray, _filter_idx: int) -> void:
if has_selected:
_finish_file_import(files[0], completion_callback, extensions)
@@ -164,12 +188,14 @@ completion_callback: Callable, native_dialog_title := "") -> void:
Configs.savedata.get_last_dir(), "", false,
DisplayServer.FILE_DIALOG_MODE_OPEN_FILE, filters, native_callback)
else:
- var import_dialog := GoodFileDialog.instantiate()
+ var import_dialog := GoodFileDialogScene.instantiate()
import_dialog.setup(Configs.savedata.get_last_dir(), "",
- GoodFileDialogType.FileMode.SELECT, extensions)
+ GoodFileDialog.FileMode.SELECT, extensions)
HandlerGUI.add_menu(import_dialog)
import_dialog.file_selected.connect(
- func(path): _finish_file_import(path, completion_callback, extensions))
+ func(path: String) -> void:
+ _finish_file_import(path, completion_callback, extensions)
+ )
static func _finish_file_import(file_path: String, completion_callback: Callable,
allowed_extensions: PackedStringArray) -> Error:
@@ -194,7 +220,7 @@ allowed_extensions: PackedStringArray) -> Error:
error += "\n" + Translator.translate("Check if the file still exists in the selected file path.")
if not error.is_empty():
- var alert_dialog := AlertDialog.instantiate()
+ var alert_dialog := AlertDialogScene.instantiate()
HandlerGUI.add_dialog(alert_dialog)
alert_dialog.setup(error)
return ERR_FILE_CANT_OPEN
@@ -220,24 +246,53 @@ static func _apply_svg(data: Variant, file_path: String) -> void:
"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()
+ "If you want to revert your edits since the last save, use {reset_svg}.").format(
+ {"reset_svg": TranslationUtils.get_shortcut_description("reset_svg")})
+
+ var alert_dialog := AlertDialogScene.instantiate()
HandlerGUI.add_menu(alert_dialog)
alert_dialog.setup(alert_message)
- else:
+ return
+
+ # If the active tab is empty, replace it. Otherwise make it a new transient tab.
+ # If there are already too many tabs, do nothing.
+ if Configs.savedata.get_active_tab().empty_unsaved:
+ var warning_panel := ImportWarningMenuScene.instantiate()
+ var tab_index := Configs.savedata.get_active_tab_index()
+ Configs.savedata.add_tab_with_path(file_path)
+ Configs.savedata.remove_tab(tab_index)
+ Configs.savedata.move_tab(Configs.savedata.get_tab_count() - 1, tab_index)
+ warning_panel.canceled.connect(_on_import_panel_canceled_empty_tab_scenario)
+ warning_panel.imported.connect(_on_import_panel_accepted_empty_tab_scenario.bind(
+ data))
+ warning_panel.set_svg(data)
+ HandlerGUI.add_menu(warning_panel)
+ elif Configs.savedata.get_tab_count() < SaveData.MAX_TABS:
+ var warning_panel := ImportWarningMenuScene.instantiate()
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.canceled.connect(_on_import_panel_canceled_transient_scenario)
+ warning_panel.imported.connect(_on_import_panel_accepted_transient_scenario.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 _on_import_panel_canceled_empty_tab_scenario() -> void:
+ var tab_index := Configs.savedata.get_active_tab_index()
+ Configs.savedata.add_empty_tab()
+ Configs.savedata.remove_tab(tab_index)
+ Configs.savedata.move_tab(Configs.savedata.get_tab_count() - 1, tab_index)
-static func _on_import_panel_accepted(file_path: String, svg_text: String) -> void:
+static func _on_import_panel_accepted_empty_tab_scenario(svg_text: String) -> void:
+ Configs.savedata.get_active_tab().setup_svg_text(svg_text)
+ State.sync_elements()
+
+static func _on_import_panel_canceled_transient_scenario() -> void:
State.transient_tab_path = ""
+
+static func _on_import_panel_accepted_transient_scenario(
+file_path: String, svg_text: String) -> void:
Configs.savedata.add_tab_with_path(file_path)
+ State.transient_tab_path = ""
Configs.savedata.get_active_tab().setup_svg_text(svg_text)
State.sync_elements()
@@ -249,6 +304,70 @@ static func open_svg_folder(file_path: String) -> void:
OS.shell_show_in_file_manager(file_path)
+static func close_tabs(initial_idx: int, tab_close_mode := TabCloseMode.SINGLE) -> void:
+ var indices: Array[int] = []
+ match tab_close_mode:
+ TabCloseMode.SINGLE:
+ indices = [initial_idx]
+ TabCloseMode.TO_LEFT:
+ for i in range(initial_idx - 1, -1, -1):
+ indices.append(i)
+ TabCloseMode.TO_RIGHT:
+ for i in Configs.savedata.get_tab_count() - initial_idx - 1:
+ indices.append(initial_idx + 1)
+ TabCloseMode.ALL_OTHERS:
+ for i in initial_idx:
+ indices.append(0)
+ for i in Configs.savedata.get_tab_count() - initial_idx - 1:
+ indices.append(1)
+ _close_tabs_internal(indices)
+
+static func _close_tabs_internal(indices: Array[int]) -> void:
+ if indices.is_empty():
+ return
+
+ var idx: int = indices.pop_front()
+ if idx < 0 or idx >= Configs.savedata.get_tab_count():
+ return
+
+ var tab := Configs.savedata.get_tab(idx)
+
+ var dont_save_callback := func() -> void:
+ Configs.savedata.remove_tab(idx)
+ HandlerGUI.remove_all_menus()
+ _close_tabs_internal(indices.duplicate())
+
+ if tab.marked_unsaved or (tab.svg_file_path.is_empty() and not tab.empty_unsaved):
+ Configs.savedata.set_active_tab_index(idx)
+ var save_callback := _save_svg_with_custom_final_callback.bind(dont_save_callback)
+
+ var title := ""
+ var message := ""
+ if tab.svg_file_path.is_empty():
+ title = Translator.translate("Save the file?")
+ message = Translator.translate("Do you want to save this file?")
+ else:
+ title = Translator.translate("Save the changes?")
+ message = Translator.translate(
+ "Do you want to save the changes made to {file_name}?").format(
+ {"file_name": Configs.savedata.get_active_tab().presented_name}) + "\n\n" +\
+ Translator.translate("Your changes will be lost if you don't save them.")
+
+ var options_dialog := OptionsDialogScene.instantiate()
+ HandlerGUI.add_menu(options_dialog)
+ options_dialog.setup(title, message)
+ if OS.get_name() == "Windows":
+ options_dialog.add_option(Translator.translate("Save"), save_callback, true, false)
+ options_dialog.add_option(Translator.translate("Don't save"), dont_save_callback)
+ options_dialog.add_cancel_option()
+ else:
+ options_dialog.add_option(Translator.translate("Don't save"), dont_save_callback)
+ options_dialog.add_cancel_option()
+ options_dialog.add_option(Translator.translate("Save"), save_callback, true, false)
+ else:
+ dont_save_callback.call()
+
+
# Web stuff.
static func _web_load_file(allowed_extensions: PackedStringArray,
@@ -289,7 +408,7 @@ completion_callback: Callable) -> void:
var extension := file_name.get_extension().to_lower()
if not extension in allowed_extensions:
- var alert_dialog: Node = AlertDialog.instantiate()
+ var alert_dialog := AlertDialogScene.instantiate()
HandlerGUI.add_dialog(alert_dialog)
alert_dialog.setup(TranslationUtils.get_bad_extension_alert_text(extension,
allowed_extensions))
diff --git a/src/utils/ShortcutUtils.gd b/src/utils/ShortcutUtils.gd
index c94fbf7..be49d3d 100644
--- a/src/utils/ShortcutUtils.gd
+++ b/src/utils/ShortcutUtils.gd
@@ -8,6 +8,9 @@ const _shortcut_categories_dict: Dictionary[String, Dictionary] = {
"save": true,
"save_as": true,
"close_tab": true,
+ "close_tabs_to_left": true,
+ "close_tabs_to_right": true,
+ "close_all_other_tabs": true,
"new_tab": true,
"select_next_tab": true,
"select_previous_tab": true,
@@ -77,24 +80,34 @@ static func fn_call(shortcut: String) -> void:
fn(shortcut).call()
# The methods that should be called if these shortcuts aren't handled.
+# Should bind only constants, otherwise the binds can get outdated before being used.
static func fn(shortcut: String) -> Callable:
match shortcut:
"save": return FileUtils.save_svg
"save_as": return FileUtils.save_svg_as
"export": return HandlerGUI.open_export
"import": return FileUtils.open_svg_import_dialog
- "close_tab": return Configs.savedata.remove_active_tab
+ "close_tab": return FileUtils.close_tabs.bind(
+ Configs.savedata.get_active_tab_index())
+ "close_tabs_to_left": return FileUtils.close_tabs.bind(
+ Configs.savedata.get_active_tab_index(), FileUtils.TabCloseMode.TO_LEFT)
+ "close_tabs_to_right": return FileUtils.close_tabs.bind(
+ Configs.savedata.get_active_tab_index(), FileUtils.TabCloseMode.TO_RIGHT)
+ "close_all_other_tabs": return FileUtils.close_tabs.bind(
+ Configs.savedata.get_active_tab_index(), FileUtils.TabCloseMode.ALL_OTHERS)
"new_tab": return Configs.savedata.add_empty_tab
- "select_next_tab": return func(): 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()))
+ "select_next_tab": return func() -> void: Configs.savedata.set_active_tab_index(
+ posmod(Configs.savedata.get_active_tab_index() + 1,
+ Configs.savedata.get_tab_count()))
+ "select_previous_tab": return func() -> void: Configs.savedata.set_active_tab_index(
+ posmod(Configs.savedata.get_active_tab_index() - 1,
+ Configs.savedata.get_tab_count()))
"copy_svg_text": return DisplayServer.clipboard_set.bind(State.svg_text)
"optimize": return State.optimize
"reset_svg": return FileUtils.reset_svg
- "open_externally": return FileUtils.open_svg.bind(
+ "open_externally": return func() -> void: FileUtils.open_svg(
Configs.savedata.get_active_tab().svg_file_path)
- "open_in_folder": return FileUtils.open_svg_folder.bind(
+ "open_in_folder": return func() -> void: FileUtils.open_svg_folder(
Configs.savedata.get_active_tab().svg_file_path)
"redo": return Configs.savedata.get_active_tab().redo
"undo": return Configs.savedata.get_active_tab().undo
diff --git a/src/utils/ThemeUtils.gd b/src/utils/ThemeUtils.gd
index 2eac1ac..452daaa 100644
--- a/src/utils/ThemeUtils.gd
+++ b/src/utils/ThemeUtils.gd
@@ -10,11 +10,16 @@ const common_panel_border_color = Color("414159")
const common_caret_color = Color("ddeeffdd")
const common_selection_color = Color("668cff66")
const common_disabled_selection_color = Color("aaaaaa66")
-const common_text_color = Color("ddeeff")
-const common_subtle_text_color = Color("ffffff55")
+const common_editable_text_color = Color("ddeeff")
const common_inner_color_disabled = Color("0e0e12")
const common_border_color_disabled = Color("1e1f24")
+const common_text_color = Color("ffffffdd")
+const common_highlighted_text_color = Color("ffffff")
+const common_dim_text_color = Color("ffffffbb")
+const common_dimmer_text_color = Color("ffffff77")
+const common_subtle_text_color = Color("ffffff55")
+
const common_button_inner_color_normal = Color("1c1e38")
const common_button_border_color_normal = Color("313859")
const common_button_inner_color_hover = Color("232840")
@@ -187,14 +192,20 @@ static func _setup_panelcontainer(theme: Theme) -> void:
panel_stylebox.corner_radius_top_right = 5
panel_stylebox.corner_radius_bottom_right = 5
panel_stylebox.content_margin_left = 14
- panel_stylebox.content_margin_right = 14
- panel_stylebox.content_margin_bottom = 12
- panel_stylebox.content_margin_top = 11
+ panel_stylebox.content_margin_right = 2
+ panel_stylebox.content_margin_bottom = 2
+ panel_stylebox.content_margin_top = 2
theme.set_stylebox("panel", "SideBarContent", panel_stylebox)
static func _setup_button(theme: Theme) -> void:
theme.add_type("Button")
- theme.set_constant("h_separation", "Button", 6)
+ theme.set_constant("h_separation", "Button", 5)
+ theme.set_color("font_color", "Button", common_text_color)
+ theme.set_color("font_disabled_color", "Button", common_subtle_text_color)
+ theme.set_color("font_focus_color", "Button", common_highlighted_text_color)
+ theme.set_color("font_hover_color", "Button", common_highlighted_text_color)
+ theme.set_color("font_pressed_color", "Button", common_highlighted_text_color)
+ theme.set_color("font_hover_pressed_color", "Button", common_highlighted_text_color)
var button_stylebox := StyleBoxFlat.new()
button_stylebox.set_corner_radius_all(5)
button_stylebox.set_border_width_all(2)
@@ -480,6 +491,10 @@ static func _setup_button(theme: Theme) -> void:
theme.add_type("SideTab")
theme.set_type_variation("SideTab", "Button")
+ theme.set_color("font_color", "SideTab", common_dim_text_color)
+ theme.set_color("font_hover_color", "SideTab", common_highlighted_text_color)
+ theme.set_color("font_pressed_color", "SideTab", common_highlighted_text_color)
+ theme.set_color("font_hover_pressed_color", "SideTab", common_highlighted_text_color)
var normal_sidetab_stylebox := StyleBoxFlat.new()
normal_sidetab_stylebox.bg_color = normal_tab_color
@@ -525,6 +540,14 @@ static func _setup_button(theme: Theme) -> void:
static func _setup_checkbox(theme: Theme) -> void:
theme.add_type("CheckBox")
+ theme.set_constant("h_separation", "CheckBox", 5)
+ theme.set_color("font_color", "CheckBox", common_text_color)
+ theme.set_color("font_color", "CheckBox", common_text_color)
+ theme.set_color("font_disabled_color", "CheckBox", common_subtle_text_color)
+ theme.set_color("font_focus_color", "CheckBox", common_highlighted_text_color)
+ theme.set_color("font_hover_color", "CheckBox", common_highlighted_text_color)
+ theme.set_color("font_pressed_color", "CheckBox", common_highlighted_text_color)
+ theme.set_color("font_hover_pressed_color", "CheckBox", common_highlighted_text_color)
theme.set_icon("checked", "CheckBox", _icon("GuiBoxChecked"))
theme.set_icon("checked_disabled", "CheckBox", _icon("GuiBoxCheckedDisabled"))
theme.set_icon("unchecked", "CheckBox", _icon("GuiBoxUnchecked"))
@@ -534,14 +557,14 @@ static func _setup_checkbox(theme: Theme) -> void:
checkbox_stylebox.set_corner_radius_all(4)
checkbox_stylebox.content_margin_bottom = 2.0
checkbox_stylebox.content_margin_top = 2.0
- checkbox_stylebox.content_margin_left = 4.0
- checkbox_stylebox.content_margin_right = 4.0
+ checkbox_stylebox.content_margin_left = 3.0
+ checkbox_stylebox.content_margin_right = 3.0
var empty_checkbox_stylebox := StyleBoxEmpty.new()
empty_checkbox_stylebox.content_margin_bottom = 2.0
empty_checkbox_stylebox.content_margin_top = 2.0
- empty_checkbox_stylebox.content_margin_left = 4.0
- empty_checkbox_stylebox.content_margin_right = 4.0
+ empty_checkbox_stylebox.content_margin_left = 3.0
+ empty_checkbox_stylebox.content_margin_right = 3.0
theme.set_stylebox("normal", "CheckBox", empty_checkbox_stylebox)
theme.set_stylebox("pressed", "CheckBox", empty_checkbox_stylebox)
@@ -556,6 +579,12 @@ static func _setup_checkbox(theme: Theme) -> void:
static func _setup_checkbutton(theme: Theme) -> void:
theme.add_type("CheckButton")
+ theme.set_color("font_color", "CheckButton", common_text_color)
+ theme.set_color("font_disabled_color", "CheckButton", common_subtle_text_color)
+ theme.set_color("font_focus_color", "CheckButton", common_highlighted_text_color)
+ theme.set_color("font_hover_color", "CheckButton", common_highlighted_text_color)
+ theme.set_color("font_pressed_color", "CheckButton", common_highlighted_text_color)
+ theme.set_color("font_hover_pressed_color", "CheckButton", common_highlighted_text_color)
theme.set_icon("checked", "CheckButton", _icon("GuiToggleChecked"))
theme.set_icon("unchecked", "CheckButton", _icon("GuiToggleUnchecked"))
@@ -595,7 +624,7 @@ static func _setup_itemlist(theme: Theme) -> void:
static func _setup_lineedit(theme: Theme) -> void:
theme.add_type("LineEdit")
theme.set_color("caret_color", "LineEdit", common_caret_color)
- theme.set_color("font_color", "LineEdit", common_text_color)
+ theme.set_color("font_color", "LineEdit", common_editable_text_color)
theme.set_color("font_placeholder_color", "LineEdit", common_subtle_text_color)
theme.set_color("selection_color", "LineEdit", common_selection_color)
theme.set_color("disabled_selection_color", "LineEdit", common_disabled_selection_color)
@@ -705,7 +734,7 @@ static func _setup_lineedit(theme: Theme) -> void:
theme.set_stylebox("focus", "RightConnectedLineEdit", right_connected_focus_stylebox)
theme.add_type("MiniLineEdit")
- theme.set_color("font_color", "MiniLineEdit", common_text_color)
+ theme.set_color("font_color", "MiniLineEdit", common_editable_text_color)
theme.set_type_variation("MiniLineEdit", "LineEdit")
theme.set_font_size("font_size", "MiniLineEdit", 10)
theme.set_font("font", "MiniLineEdit", mono_font)
@@ -773,8 +802,8 @@ static func _setup_scrollbar(theme: Theme) -> void:
var h_scroll_stylebox := StyleBoxFlat.new()
h_scroll_stylebox.set_corner_radius_all(3)
- h_scroll_stylebox.content_margin_top = 4
- h_scroll_stylebox.content_margin_bottom = 4
+ h_scroll_stylebox.content_margin_top = 8
+ h_scroll_stylebox.content_margin_bottom = 8
h_scroll_stylebox.bg_color = scrollbar_background_color
theme.set_stylebox("scroll", "HScrollBar", h_scroll_stylebox)
@@ -798,8 +827,8 @@ static func _setup_scrollbar(theme: Theme) -> void:
var v_scroll_stylebox := StyleBoxFlat.new()
v_scroll_stylebox.set_corner_radius_all(3)
- v_scroll_stylebox.content_margin_left = 4.0
- v_scroll_stylebox.content_margin_right = 4.0
+ v_scroll_stylebox.content_margin_left = 8
+ v_scroll_stylebox.content_margin_right = 8
v_scroll_stylebox.bg_color = scrollbar_background_color
theme.set_stylebox("scroll", "VScrollBar", v_scroll_stylebox)
@@ -820,7 +849,6 @@ static func _setup_separator(theme: Theme) -> void:
static func _setup_label(theme: Theme) -> void:
theme.add_type("Label")
- theme.set_font_size("font_size", "Label", 15)
theme.add_type("RichTextLabel")
theme.set_color("selection_color", "RichTextLabel", common_selection_color)
@@ -828,6 +856,9 @@ static func _setup_label(theme: Theme) -> void:
static func _setup_tabcontainer(theme: Theme) -> void:
theme.add_type("TabContainer")
+ theme.set_color("font_unselected_color", "TabContainer", common_dim_text_color)
+ theme.set_color("font_hovered_color", "TabContainer", common_text_color)
+ theme.set_color("font_selected_color", "TabContainer", common_highlighted_text_color)
theme.set_constant("side_margin", "TabContainer", 0)
theme.set_font_size("font_size", "TabContainer", 14)
@@ -840,9 +871,9 @@ static func _setup_tabcontainer(theme: Theme) -> void:
panel_stylebox.corner_radius_bottom_right = 5
panel_stylebox.corner_radius_bottom_left = 5
panel_stylebox.content_margin_left = 8
- panel_stylebox.content_margin_right = 8
- panel_stylebox.content_margin_bottom = 6
- panel_stylebox.content_margin_top = 5
+ panel_stylebox.content_margin_right = 2
+ panel_stylebox.content_margin_bottom = 2
+ panel_stylebox.content_margin_top = 0
theme.set_stylebox("panel", "TabContainer", panel_stylebox)
var tab_disabled_stylebox := StyleBoxEmpty.new()
diff --git a/src/utils/TranslationUtils.gd b/src/utils/TranslationUtils.gd
index f8bcfa6..ea7b2fa 100644
--- a/src/utils/TranslationUtils.gd
+++ b/src/utils/TranslationUtils.gd
@@ -1,5 +1,21 @@
class_name TranslationUtils extends RefCounted
+static func _get_locale_name(locale: String) -> String:
+ match locale:
+ "pt_BR": return "Brazilian Portuguese"
+ "zh_CN": return "Simplified Chinese"
+ return TranslationServer.get_locale_name(locale)
+
+static func get_locale_string(locale: String) -> String:
+ if not "_" in locale:
+ return locale.to_upper()
+ var separator_pos := locale.find("_")
+ return locale.left(separator_pos) + "-" + locale.right(-separator_pos - 1).to_upper()
+
+static func get_locale_display(locale: String) -> String:
+ return "%s (%s)" % [_get_locale_name(locale), get_locale_string(locale)]
+
+
static func get_shortcut_description(action_name: String) -> String:
match action_name:
"export": return Translator.translate("Export")
@@ -7,6 +23,9 @@ static func get_shortcut_description(action_name: String) -> String:
"save": return Translator.translate("Save")
"save_as": return Translator.translate("Save as")
"close_tab": return Translator.translate("Close tab")
+ "close_tabs_to_left": return Translator.translate("Close tabs to the left")
+ "close_tabs_to_right": return Translator.translate("Close tabs to the right")
+ "close_all_other_tabs": return Translator.translate("Close all other tabs")
"new_tab": return Translator.translate("Create a new tab")
"select_next_tab": return Translator.translate("Select the next tab")
"select_previous_tab": return Translator.translate("Select the previous tab")
diff --git a/src/utils/Utils.gd b/src/utils/Utils.gd
index 92d0869..feb8d54 100644
--- a/src/utils/Utils.gd
+++ b/src/utils/Utils.gd
@@ -5,7 +5,6 @@ const MAX_ANGLE_PRECISION = 4
enum InteractionType {NONE = 0, HOVERED = 1, SELECTED = 2, HOVERED_SELECTED = 3}
-
static func num_simple(number: float, decimals := -1) -> String:
return String.num(number, decimals).trim_suffix(".0")
diff --git a/src/utils/XNodeChildrenBuilder.gd b/src/utils/XNodeChildrenBuilder.gd
index 67fb2e4..d33cdcb 100644
--- a/src/utils/XNodeChildrenBuilder.gd
+++ b/src/utils/XNodeChildrenBuilder.gd
@@ -1,34 +1,34 @@
class_name XNodeChildrenBuilder
-const ElementFrame = preload("res://src/ui_widgets/element_frame.tscn")
-const BasicXNodeFrame = preload("res://src/ui_widgets/basic_xnode_frame.tscn")
+const ElementFrameScene = preload("res://src/ui_widgets/element_frame.tscn")
+const BasicXNodeFrameScene = preload("res://src/ui_widgets/basic_xnode_frame.tscn")
static func create(element: Element) -> Array[Control]:
var arr: Array[Control] = []
for xnode in element.get_children():
if xnode.is_element():
- var element_editor := ElementFrame.instantiate()
+ var element_editor := ElementFrameScene.instantiate()
element_editor.element = xnode
arr.append(element_editor)
else:
- var xnode_editor := BasicXNodeFrame.instantiate()
+ var xnode_editor := BasicXNodeFrameScene.instantiate()
xnode_editor.xnode = xnode
arr.append(xnode_editor)
return arr
-static func generate_drag_preview(xids: Array[PackedInt32Array], ) -> Control:
+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 := State.root_element.get_xnode(drag_xid)
if drag_xnode is Element:
- var preview := ElementFrame.instantiate()
+ var preview := ElementFrameScene.instantiate()
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()
+ var preview := BasicXNodeFrameScene.instantiate()
preview.xnode = State.root_element.get_xnode(drag_xid)
preview.custom_minimum_size.x = 360.0
preview.z_index = 2
diff --git a/assets/translations/GodSVG.pot b/translations/GodSVG.pot
similarity index 90%
rename from assets/translations/GodSVG.pot
rename to translations/GodSVG.pot
index 686128d..f626b35 100644
--- a/assets/translations/GodSVG.pot
+++ b/translations/GodSVG.pot
@@ -10,7 +10,6 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Generator: Poedit 3.4.2\n"
msgid "translation-credits"
msgstr ""
@@ -32,13 +31,19 @@ msgid "Check for updates?"
msgstr ""
#: src/autoload/HandlerGUI.gd
-msgid "This requires GodSVG to connect to the internet."
+msgid ""
+"This will connect to github.com to compare version numbers. No other data is "
+"collected or transmitted."
msgstr ""
#: src/autoload/HandlerGUI.gd src/ui_widgets/alert_dialog.gd
msgid "OK"
msgstr ""
+#: src/autoload/HandlerGUI.gd
+msgid "Do you want to proceed?"
+msgstr ""
+
#: src/autoload/HandlerGUI.gd
msgid "Export SVG"
msgstr ""
@@ -50,8 +55,13 @@ msgstr ""
#: src/autoload/HandlerGUI.gd
msgid ""
-"The graphic can only be exported as SVG because its size is not defined. Do "
-"you want to proceed?"
+"The graphic can be exported only as SVG because its proportions are too "
+"extreme."
+msgstr ""
+
+#: src/autoload/HandlerGUI.gd
+msgid ""
+"The graphic can be exported only as SVG because its size is not defined."
msgstr ""
#: src/autoload/State.gd src/ui_parts/good_file_dialog.gd
@@ -102,8 +112,8 @@ msgstr ""
#: src/autoload/State.gd
msgid ""
-"The tab is bound to the file path {file_path}. Do you want to restore from "
-"this path?"
+"The tab is bound to the file path {file_path}. Do you want to restore the "
+"SVG from this path?"
msgstr ""
#: src/config_classes/Formatter.gd
@@ -142,6 +152,10 @@ msgstr ""
msgid "6-digit hex"
msgstr ""
+#: src/config_classes/TabData.gd
+msgid "Empty"
+msgstr ""
+
#: src/config_classes/TabData.gd
msgid "Unsaved"
msgstr ""
@@ -193,43 +207,63 @@ msgid "Close"
msgstr ""
#: src/ui_parts/about_menu.gd
-msgid "Project Founder and Manager"
+msgid "Authors"
msgstr ""
#: src/ui_parts/about_menu.gd
-msgid "Developers"
+msgid "Donors"
msgstr ""
#: src/ui_parts/about_menu.gd
-msgid "Translators"
+msgid "License"
msgstr ""
#: src/ui_parts/about_menu.gd
-msgid "Donors"
+msgid "Third-party licenses"
msgstr ""
#: src/ui_parts/about_menu.gd
-msgid "Golden donors"
+msgid "Project Founder and Manager"
msgstr ""
#: src/ui_parts/about_menu.gd
-msgid "Diamond donors"
+msgid "Developers"
msgstr ""
#: src/ui_parts/about_menu.gd
-msgid "Authors"
+msgid "Translators"
msgstr ""
#: src/ui_parts/about_menu.gd
-msgid "License"
+msgid "Golden donors"
msgstr ""
#: src/ui_parts/about_menu.gd
-msgid "Godot third-party components"
+msgid "Diamond donors"
msgstr ""
-#: src/ui_parts/about_menu.gd
-msgid "Third-party licenses"
+#: src/ui_parts/code_editor.gd src/utils/TranslationUtils.gd
+msgid "Copy all text"
+msgstr ""
+
+#: src/ui_parts/current_file_button.gd src/ui_parts/good_file_dialog.gd
+msgid "Save SVG"
+msgstr ""
+
+#: src/ui_parts/current_file_button.gd
+msgid "Save SVG as…"
+msgstr ""
+
+#: src/ui_parts/current_file_button.gd src/utils/TranslationUtils.gd
+msgid "Reset SVG"
+msgstr ""
+
+#: src/ui_parts/current_file_button.gd src/ui_parts/tab_bar.gd
+msgid "Open externally"
+msgstr ""
+
+#: src/ui_parts/current_file_button.gd src/ui_parts/tab_bar.gd
+msgid "Show in File Manager"
msgstr ""
#: src/ui_parts/display.gd
@@ -266,7 +300,7 @@ 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
+#: src/ui_widgets/confirm_dialog.gd src/ui_widgets/options_dialog.gd
msgid "Cancel"
msgstr ""
@@ -327,22 +361,6 @@ msgstr ""
msgid "Optimize"
msgstr ""
-#: src/ui_parts/global_actions.gd src/ui_parts/good_file_dialog.gd
-msgid "Save SVG"
-msgstr ""
-
-#: 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 ""
@@ -399,7 +417,8 @@ msgstr ""
msgid "Select"
msgstr ""
-#: src/ui_parts/good_file_dialog.gd src/utils/TranslationUtils.gd
+#: src/ui_parts/good_file_dialog.gd src/utils/FileUtils.gd
+#: src/utils/TranslationUtils.gd
msgid "Save"
msgstr ""
@@ -590,7 +609,7 @@ msgid "Invert zoom direction"
msgstr ""
#: src/ui_parts/settings_menu.gd
-msgid "Wrap mouse"
+msgid "Wrap-around panning"
msgstr ""
#: src/ui_parts/settings_menu.gd
@@ -618,7 +637,7 @@ msgid "UI scale"
msgstr ""
#: src/ui_parts/settings_menu.gd
-msgid "Auto UI scale"
+msgid "Changes the scale factor for the interface."
msgstr ""
#: src/ui_parts/settings_menu.gd
@@ -629,7 +648,7 @@ msgstr ""
msgid "Import XML"
msgstr ""
-#: src/ui_parts/settings_menu.gd src/ui_widgets/palette_config.gd
+#: src/ui_parts/settings_menu.gd
msgid "Paste XML"
msgstr ""
@@ -750,11 +769,13 @@ msgid "Remove unnecessary parameters"
msgstr ""
#: src/ui_parts/settings_menu.gd
-msgid "Swaps zoom in and zoom out with the mouse wheel."
+msgid "Swaps the scroll directions for zooming in and zooming out."
msgstr ""
#: src/ui_parts/settings_menu.gd
-msgid "Wraps the mouse cursor around when panning the viewport."
+msgid ""
+"Warps the cursor to the opposite side whenever it reaches a viewport "
+"boundary while panning."
msgstr ""
#: src/ui_parts/settings_menu.gd
@@ -779,60 +800,48 @@ msgstr ""
msgid "Changes the visual size and grabbing area of handles."
msgstr ""
-#: src/ui_parts/settings_menu.gd
-msgid "Changes the scale of the visual user interface."
-msgstr ""
-
-#: src/ui_parts/settings_menu.gd
-msgid "Scales the user interface based on the screen size."
-msgstr ""
-
-#: src/ui_parts/shortcut_panel_config.gd
-msgid "Configure Shortcut Panel"
+#: src/ui_parts/shortcut_panel.gd
+msgid "Horizontal strip"
msgstr ""
-#: src/ui_parts/shortcut_panel_config.gd
-msgid "Layout"
+#: src/ui_parts/shortcut_panel.gd
+msgid "Vertical strip"
msgstr ""
-#: src/ui_parts/shortcut_panel_config.gd
-msgid "Horizontal strip"
+#: src/ui_parts/shortcut_panel.gd
+msgid "Horizontal with two rows"
msgstr ""
#: src/ui_parts/shortcut_panel_config.gd
-msgid "Horizontal with two rows"
+msgid "Configure Shortcut Panel"
msgstr ""
#: src/ui_parts/shortcut_panel_config.gd
-msgid "Vertical strip"
+msgid "Layout"
msgstr ""
#: src/ui_parts/tab_bar.gd
msgid "Create tab"
msgstr ""
-#: src/ui_parts/tab_bar.gd
-msgid "Close all other tabs"
+#: src/ui_parts/tab_bar.gd src/utils/TranslationUtils.gd
+msgid "Create a new tab"
msgstr ""
#: src/ui_parts/tab_bar.gd
-msgid "Close tabs to the left"
+msgid "Scroll backwards"
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"
+msgid "Scroll forwards"
msgstr ""
#: src/ui_parts/tab_bar.gd
-msgid "This SVG is not bound to a location on the computer yet."
+msgid "This SVG is not bound to a file location yet."
msgstr ""
#: src/ui_parts/update_menu.gd
-msgid "Include prereleases"
+msgid "Show prereleases"
msgstr ""
#: src/ui_parts/update_menu.gd
@@ -897,10 +906,6 @@ msgstr ""
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 ""
@@ -945,12 +950,16 @@ msgstr ""
msgid "Rename"
msgstr ""
+#: src/ui_widgets/palette_config.gd
+msgid "Apply Preset"
+msgstr ""
+
#: src/ui_widgets/palette_config.gd
msgid "Copy as XML"
msgstr ""
#: src/ui_widgets/palette_config.gd
-msgid "Apply Preset"
+msgid "Save as XML"
msgstr ""
#: src/ui_widgets/path_popup.gd src/ui_widgets/pathdata_field.gd
@@ -1030,12 +1039,52 @@ msgstr ""
msgid "Check if the file still exists in the selected file path."
msgstr ""
+#: src/utils/FileUtils.gd
+msgid "Save the file?"
+msgstr ""
+
+#: src/utils/FileUtils.gd
+msgid "Do you want to save this file?"
+msgstr ""
+
+#: src/utils/FileUtils.gd
+msgid "Save the changes?"
+msgstr ""
+
+#: src/utils/FileUtils.gd
+msgid "Your changes will be lost if you don't save them."
+msgstr ""
+
+#: src/utils/FileUtils.gd
+msgid "Don't save"
+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."
+msgid "If you want to revert your edits since the last save, use {reset_svg}."
+msgstr ""
+
+#: src/utils/FileUtils.gd
+msgid "Do you want to save the changes made to {file_name}?"
+msgstr ""
+
+#: src/utils/TranslationUtils.gd
+msgid "Save as"
+msgstr ""
+
+#: src/utils/TranslationUtils.gd
+msgid "Close tabs to the left"
+msgstr ""
+
+#: src/utils/TranslationUtils.gd
+msgid "Close tabs to the right"
+msgstr ""
+
+#: src/utils/TranslationUtils.gd
+msgid "Close all other tabs"
msgstr ""
#: src/utils/TranslationUtils.gd
diff --git a/assets/translations/README.md b/translations/README.md
similarity index 100%
rename from assets/translations/README.md
rename to translations/README.md
diff --git a/assets/translations/bg.po b/translations/bg.po
similarity index 88%
rename from assets/translations/bg.po
rename to translations/bg.po
index 2b51819..6517c79 100644
--- a/assets/translations/bg.po
+++ b/translations/bg.po
@@ -32,13 +32,21 @@ msgid "Check for updates?"
msgstr "Провери за ъпдейти?"
#: src/autoload/HandlerGUI.gd
-msgid "This requires GodSVG to connect to the internet."
-msgstr "Това изисква GodSVG да се свърже с интерната."
+msgid ""
+"This will connect to github.com to compare version numbers. No other data is "
+"collected or transmitted."
+msgstr ""
+"Това ще се свърже с github.com за да сравни числата на версиите. Не се "
+"събират или изпращат никакви други данни."
#: src/autoload/HandlerGUI.gd src/ui_widgets/alert_dialog.gd
msgid "OK"
msgstr "Добре"
+#: src/autoload/HandlerGUI.gd
+msgid "Do you want to proceed?"
+msgstr "Искаш ли да продължиш?"
+
#: src/autoload/HandlerGUI.gd
msgid "Export SVG"
msgstr "Експортирай SVG-то"
@@ -50,11 +58,18 @@ msgstr "Експортирай"
#: src/autoload/HandlerGUI.gd
msgid ""
-"The graphic can only be exported as SVG because its size is not defined. Do "
-"you want to proceed?"
+"The graphic can be exported only as SVG because its proportions are too "
+"extreme."
+msgstr ""
+"Графиката може да бъде експортирана само като SVG защото височината и "
+"широчината са твърде различни."
+
+#: src/autoload/HandlerGUI.gd
+msgid ""
+"The graphic can be exported only as SVG because its size is not defined."
msgstr ""
"Графиката може да бъде експортирана само като SVG защото размерът не е "
-"определен. Искаш ли да продължиш?"
+"определен."
#: src/autoload/State.gd src/ui_parts/good_file_dialog.gd
#: src/ui_widgets/alert_dialog.gd
@@ -104,11 +119,11 @@ msgstr "Последното състояние на този раздел не
#: src/autoload/State.gd
msgid ""
-"The tab is bound to the file path {file_path}. Do you want to restore from "
-"this path?"
+"The tab is bound to the file path {file_path}. Do you want to restore the "
+"SVG from this path?"
msgstr ""
-"Този раздел е свързан с пътеката {file_path}. Искаш ли да го възстановиш от "
-"тази пътека?"
+"Този раздел е свързан с пътеката {file_path}. Искаш ли да го възстановиш SVG-"
+"то от тази пътека?"
#: src/config_classes/Formatter.gd
msgid "Compact"
@@ -146,6 +161,10 @@ msgstr "Хекс с 3 или 6 цифри"
msgid "6-digit hex"
msgstr "Хекс с 6 цифри"
+#: src/config_classes/TabData.gd
+msgid "Empty"
+msgstr "Празен"
+
#: src/config_classes/TabData.gd
msgid "Unsaved"
msgstr "Незаписан"
@@ -196,6 +215,22 @@ msgstr "Несъвместими тагове."
msgid "Close"
msgstr "Затвори"
+#: src/ui_parts/about_menu.gd
+msgid "Authors"
+msgstr "Автори"
+
+#: src/ui_parts/about_menu.gd
+msgid "Donors"
+msgstr "Дарители"
+
+#: src/ui_parts/about_menu.gd
+msgid "License"
+msgstr "Лиценз"
+
+#: src/ui_parts/about_menu.gd
+msgid "Third-party licenses"
+msgstr "Лицензи от трети партии"
+
#: src/ui_parts/about_menu.gd
msgid "Project Founder and Manager"
msgstr "Основател и мениджър на проекта"
@@ -208,10 +243,6 @@ msgstr "Програмисти"
msgid "Translators"
msgstr "Преводачи"
-#: src/ui_parts/about_menu.gd
-msgid "Donors"
-msgstr "Дарители"
-
#: src/ui_parts/about_menu.gd
msgid "Golden donors"
msgstr "Златни дарители"
@@ -220,21 +251,29 @@ msgstr "Златни дарители"
msgid "Diamond donors"
msgstr "Диамантени дарители"
-#: src/ui_parts/about_menu.gd
-msgid "Authors"
-msgstr "Автори"
+#: src/ui_parts/code_editor.gd src/utils/TranslationUtils.gd
+msgid "Copy all text"
+msgstr "Копирай всичкия текст"
-#: src/ui_parts/about_menu.gd
-msgid "License"
-msgstr "Лиценз"
+#: src/ui_parts/current_file_button.gd src/ui_parts/good_file_dialog.gd
+msgid "Save SVG"
+msgstr "Запиши SVG-то"
-#: src/ui_parts/about_menu.gd
-msgid "Godot third-party components"
-msgstr "Компоненти в Godot от трети лица"
+#: src/ui_parts/current_file_button.gd
+msgid "Save SVG as…"
+msgstr "Запиши SVG-то като…"
-#: src/ui_parts/about_menu.gd
-msgid "Third-party licenses"
-msgstr "Лицензи от трети партии"
+#: src/ui_parts/current_file_button.gd src/utils/TranslationUtils.gd
+msgid "Reset SVG"
+msgstr "Рестартирай SVG-то"
+
+#: src/ui_parts/current_file_button.gd src/ui_parts/tab_bar.gd
+msgid "Open externally"
+msgstr "Отвори с външно приложение"
+
+#: src/ui_parts/current_file_button.gd src/ui_parts/tab_bar.gd
+msgid "Show in File Manager"
+msgstr "Покажи във Файловия мениджър"
#: src/ui_parts/display.gd
msgid "Visuals"
@@ -270,7 +309,7 @@ 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
+#: src/ui_widgets/confirm_dialog.gd src/ui_widgets/options_dialog.gd
msgid "Cancel"
msgstr "Отказ"
@@ -331,22 +370,6 @@ msgstr "Настройки"
msgid "Optimize"
msgstr "Оптимизирай"
-#: src/ui_parts/global_actions.gd src/ui_parts/good_file_dialog.gd
-msgid "Save SVG"
-msgstr "Запиши SVG-то"
-
-#: 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 "Относно приложението…"
@@ -403,7 +426,8 @@ msgstr "Избери изображение"
msgid "Select"
msgstr "Избери"
-#: src/ui_parts/good_file_dialog.gd src/utils/TranslationUtils.gd
+#: src/ui_parts/good_file_dialog.gd src/utils/FileUtils.gd
+#: src/utils/TranslationUtils.gd
msgid "Save"
msgstr "Запиши"
@@ -507,6 +531,10 @@ msgstr "Бързи клавиши"
msgid "Theming"
msgstr "Гама"
+#: src/ui_parts/settings_menu.gd
+msgid "Tab bar"
+msgstr "Лента с раздели"
+
#: src/ui_parts/settings_menu.gd
msgid "Other"
msgstr "Други"
@@ -587,6 +615,10 @@ msgstr "Цвят на валидният текст"
msgid "Warning color"
msgstr "Цвят на предупрежденията"
+#: src/ui_parts/settings_menu.gd
+msgid "Close tabs with middle mouse button"
+msgstr "Затваряне на раздели със средното копче на мишката"
+
#: src/ui_parts/settings_menu.gd
msgid "Input"
msgstr "Входни сигнали"
@@ -596,7 +628,7 @@ msgid "Invert zoom direction"
msgstr "Обърни посоката на увеличение"
#: src/ui_parts/settings_menu.gd
-msgid "Wrap mouse"
+msgid "Wrap-around panning"
msgstr "Превъртане на курсора"
#: src/ui_parts/settings_menu.gd
@@ -624,8 +656,8 @@ msgid "UI scale"
msgstr "Мащаб на интерфейса"
#: src/ui_parts/settings_menu.gd
-msgid "Auto UI scale"
-msgstr "Автоматичен мащаб"
+msgid "Changes the scale factor for the interface."
+msgstr "Увеличава мащаба на интерфейса."
#: src/ui_parts/settings_menu.gd
msgid "Language"
@@ -635,7 +667,7 @@ msgstr "Език"
msgid "Import XML"
msgstr "Импортирай XML"
-#: src/ui_parts/settings_menu.gd src/ui_widgets/palette_config.gd
+#: src/ui_parts/settings_menu.gd
msgid "Paste XML"
msgstr "Постави XML"
@@ -756,12 +788,16 @@ msgid "Remove unnecessary parameters"
msgstr "Премахни ненужните параметри"
#: src/ui_parts/settings_menu.gd
-msgid "Swaps zoom in and zoom out with the mouse wheel."
-msgstr "Разменя увеличението и намалянето с колелцето на мишката."
+msgid "Swaps the scroll directions for zooming in and zooming out."
+msgstr "Разменя посоките на влачене за намаляване и увеличаване"
#: src/ui_parts/settings_menu.gd
-msgid "Wraps the mouse cursor around when panning the viewport."
-msgstr "Превърта курсора когато той достигне краищата на екрана."
+msgid ""
+"Warps the cursor to the opposite side whenever it reaches a viewport "
+"boundary while panning."
+msgstr ""
+"Превърта курсора когато той достигне краищата на екрана докато влачи "
+"гледката."
#: src/ui_parts/settings_menu.gd
msgid ""
@@ -769,7 +805,7 @@ msgid ""
"scrolling."
msgstr ""
"Когато е включено, влаченето ще премести гледката. За увеличение, натисни "
-"CTRL докато влачиш"
+"CTRL докато влачиш."
#: src/ui_parts/settings_menu.gd
msgid ""
@@ -792,13 +828,17 @@ msgstr ""
msgid "Changes the visual size and grabbing area of handles."
msgstr "Увеличава размера и площта в която дръжките могат да бъдат хванати."
-#: src/ui_parts/settings_menu.gd
-msgid "Changes the scale of the visual user interface."
-msgstr "Увеличава мащаба на потребителския интерфейс."
+#: src/ui_parts/shortcut_panel.gd
+msgid "Horizontal strip"
+msgstr "Хоризонтална лента"
-#: src/ui_parts/settings_menu.gd
-msgid "Scales the user interface based on the screen size."
-msgstr "Променя размера на потребителския интерфейс според размера на екрана."
+#: src/ui_parts/shortcut_panel.gd
+msgid "Vertical strip"
+msgstr "Вертикална лента"
+
+#: src/ui_parts/shortcut_panel.gd
+msgid "Horizontal with two rows"
+msgstr "Хоризонтален с два реда"
#: src/ui_parts/shortcut_panel_config.gd
msgid "Configure Shortcut Panel"
@@ -808,45 +848,29 @@ msgstr "Настройки на панела с бързи клавиши"
msgid "Layout"
msgstr "Оформление"
-#: src/ui_parts/shortcut_panel_config.gd
-msgid "Horizontal strip"
-msgstr "Хоризонтална лента"
-
-#: src/ui_parts/shortcut_panel_config.gd
-msgid "Horizontal with two rows"
-msgstr "Хоризонтален с два реда"
-
-#: src/ui_parts/shortcut_panel_config.gd
-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 src/utils/TranslationUtils.gd
+msgid "Create a new tab"
+msgstr "Създай нов раздел"
#: src/ui_parts/tab_bar.gd
-msgid "Close tabs to the left"
-msgstr "Затвори разделите отляво"
+msgid "Scroll backwards"
+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 "Създай нов раздел"
+msgid "Scroll forwards"
+msgstr "Превърти напред"
#: src/ui_parts/tab_bar.gd
-msgid "This SVG is not bound to a location on the computer yet."
+msgid "This SVG is not bound to a file location yet."
msgstr "Това SVG не е свързано с местоположение на компютъра."
#: src/ui_parts/update_menu.gd
-msgid "Include prereleases"
-msgstr "Включи предварителните издания"
+msgid "Show prereleases"
+msgstr "Покажи предварителните издания"
#: src/ui_parts/update_menu.gd
msgid "Retry"
@@ -910,10 +934,6 @@ msgstr "Постави"
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 "Търсене на цвят"
@@ -958,13 +978,17 @@ msgstr "Тази палета има еднакво определетни цв
msgid "Rename"
msgstr "Преименувай"
+#: src/ui_widgets/palette_config.gd
+msgid "Apply Preset"
+msgstr "Приложи шаблон"
+
#: src/ui_widgets/palette_config.gd
msgid "Copy as XML"
msgstr "Копирай като XML"
#: src/ui_widgets/palette_config.gd
-msgid "Apply Preset"
-msgstr "Приложи шаблон"
+msgid "Save as XML"
+msgstr "Запиши като XML"
#: src/ui_widgets/path_popup.gd src/ui_widgets/pathdata_field.gd
#: src/utils/TranslationUtils.gd
@@ -1043,15 +1067,55 @@ msgstr "Файлът не можа да бъде отворен."
msgid "Check if the file still exists in the selected file path."
msgstr "Проверете дали файлът все още съществува на избраното място."
+#: src/utils/FileUtils.gd
+msgid "Save the file?"
+msgstr "Записване на файла?"
+
+#: src/utils/FileUtils.gd
+msgid "Do you want to save this file?"
+msgstr "Искаш ли да запишеш този файл?"
+
+#: src/utils/FileUtils.gd
+msgid "Save the changes?"
+msgstr "Записване на промените?"
+
+#: src/utils/FileUtils.gd
+msgid "Your changes will be lost if you don't save them."
+msgstr "Промените ще бъдат изгубени ако не ги запишеш."
+
+#: src/utils/FileUtils.gd
+msgid "Don't save"
+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."
+msgid "If you want to revert your edits since the last save, use {reset_svg}."
msgstr ""
-"Ако искаш да приложиш незаписаното състояние на файла, използвай \"Reset "
-"SVG\"."
+"Ако искаш да премахнеш промените след последното записване, използвай "
+"{reset_svg}."
+
+#: src/utils/FileUtils.gd
+msgid "Do you want to save the changes made to {file_name}?"
+msgstr "Искаш ли да запишеш промените върху {file_name}?"
+
+#: src/utils/TranslationUtils.gd
+msgid "Save as"
+msgstr "Запиши като"
+
+#: src/utils/TranslationUtils.gd
+msgid "Close tabs to the left"
+msgstr "Затвори разделите отляво"
+
+#: src/utils/TranslationUtils.gd
+msgid "Close tabs to the right"
+msgstr "Затвори разделите отдясно"
+
+#: src/utils/TranslationUtils.gd
+msgid "Close all other tabs"
+msgstr "Затвори другите раздели"
#: src/utils/TranslationUtils.gd
msgid "Select the next tab"
diff --git a/assets/translations/de.po b/translations/de.po
similarity index 82%
rename from assets/translations/de.po
rename to translations/de.po
index c92c074..5540bf3 100644
--- a/assets/translations/de.po
+++ b/translations/de.po
@@ -32,13 +32,21 @@ msgid "Check for updates?"
msgstr "Nach Aktualisierungen suchen?"
#: src/autoload/HandlerGUI.gd
-msgid "This requires GodSVG to connect to the internet."
-msgstr "Hierfür muss sich GodSVG mit dem Internet verbinden."
+msgid ""
+"This will connect to github.com to compare version numbers. No other data is "
+"collected or transmitted."
+msgstr ""
+"Dadurch wird eine Verbindung zu github.com hergestellt, um Versionsnummern "
+"zu vergleichen. Es werden keine weiteren Daten gesammelt oder übertragen."
#: src/autoload/HandlerGUI.gd src/ui_widgets/alert_dialog.gd
msgid "OK"
msgstr "OK"
+#: src/autoload/HandlerGUI.gd
+msgid "Do you want to proceed?"
+msgstr "Möchten Sie fortfahren?"
+
#: src/autoload/HandlerGUI.gd
msgid "Export SVG"
msgstr "SVG exportieren"
@@ -50,9 +58,18 @@ msgstr "Exportieren"
#: src/autoload/HandlerGUI.gd
msgid ""
-"The graphic can only be exported as SVG because its size is not defined. Do "
-"you want to proceed?"
+"The graphic can be exported only as SVG because its proportions are too "
+"extreme."
+msgstr ""
+"Die Grafik kann nur als SVG exportiert werden, da ihre Proportionen zu "
+"extrem sind."
+
+#: src/autoload/HandlerGUI.gd
+msgid ""
+"The graphic can be exported only as SVG because its size is not defined."
msgstr ""
+"Die Grafik kann nur als SVG exportiert werden, da ihre Größe nicht definiert "
+"ist."
#: src/autoload/State.gd src/ui_parts/good_file_dialog.gd
#: src/ui_widgets/alert_dialog.gd
@@ -60,13 +77,12 @@ 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"
+msgstr "Registerkarte schließen"
#: src/autoload/State.gd
msgid "Restore"
-msgstr ""
+msgstr "Wiederherstellen"
#: src/autoload/State.gd
msgid "View in List"
@@ -100,12 +116,16 @@ msgstr "Danach einsetzen"
#: src/autoload/State.gd
msgid "The last edited state of this tab could not be found."
msgstr ""
+"Der letzte Bearbeitungsstand dieser Registerkarte konnte nicht gefunden "
+"werden."
#: src/autoload/State.gd
msgid ""
-"The tab is bound to the file path {file_path}. Do you want to restore from "
-"this path?"
+"The tab is bound to the file path {file_path}. Do you want to restore the "
+"SVG from this path?"
msgstr ""
+"Die Registerkarte ist an den Dateipfad {file_path} gebunden. Möchten Sie die "
+"SVG-Datei aus diesem Pfad wiederherstellen?"
#: src/config_classes/Formatter.gd
msgid "Compact"
@@ -113,7 +133,7 @@ msgstr "Kompakt"
#: src/config_classes/Formatter.gd
msgid "Pretty"
-msgstr "Hübsch"
+msgstr "Schön"
#: src/config_classes/Formatter.gd
msgid "Always"
@@ -143,9 +163,13 @@ msgstr "3-stelligen oder 6-stelligen Hex"
msgid "6-digit hex"
msgstr "6-stelligen Hex"
+#: src/config_classes/TabData.gd
+msgid "Empty"
+msgstr "Leer"
+
#: src/config_classes/TabData.gd
msgid "Unsaved"
-msgstr ""
+msgstr "Ungespeichert"
#: src/data_classes/BasicXNode.gd
msgid "Comment"
@@ -194,6 +218,22 @@ msgstr "Ungültige Formatierung."
msgid "Close"
msgstr "Schließen"
+#: src/ui_parts/about_menu.gd
+msgid "Authors"
+msgstr "Autoren"
+
+#: src/ui_parts/about_menu.gd
+msgid "Donors"
+msgstr "Spender"
+
+#: src/ui_parts/about_menu.gd
+msgid "License"
+msgstr "Lizenz"
+
+#: src/ui_parts/about_menu.gd
+msgid "Third-party licenses"
+msgstr "Drittanbieter-Lizenzen"
+
#: src/ui_parts/about_menu.gd
msgid "Project Founder and Manager"
msgstr "Projektgründer und Manager"
@@ -206,10 +246,6 @@ msgstr "Entwickler"
msgid "Translators"
msgstr "Übersetzer"
-#: src/ui_parts/about_menu.gd
-msgid "Donors"
-msgstr "Spender"
-
#: src/ui_parts/about_menu.gd
msgid "Golden donors"
msgstr "Gold-Spender"
@@ -218,21 +254,29 @@ msgstr "Gold-Spender"
msgid "Diamond donors"
msgstr "Diamant-Spender"
-#: src/ui_parts/about_menu.gd
-msgid "Authors"
-msgstr "Autoren"
+#: src/ui_parts/code_editor.gd src/utils/TranslationUtils.gd
+msgid "Copy all text"
+msgstr "Text kopieren"
-#: src/ui_parts/about_menu.gd
-msgid "License"
-msgstr "Lizenz"
+#: src/ui_parts/current_file_button.gd src/ui_parts/good_file_dialog.gd
+msgid "Save SVG"
+msgstr "SVG speichern"
-#: src/ui_parts/about_menu.gd
-msgid "Godot third-party components"
-msgstr "Godot Drittanbieter-Komponente"
+#: src/ui_parts/current_file_button.gd
+msgid "Save SVG as…"
+msgstr "SVG speichern als…"
-#: src/ui_parts/about_menu.gd
-msgid "Third-party licenses"
-msgstr "Drittanbieter-Lizenzen"
+#: src/ui_parts/current_file_button.gd src/utils/TranslationUtils.gd
+msgid "Reset SVG"
+msgstr "SVG zurücksetzen"
+
+#: src/ui_parts/current_file_button.gd src/ui_parts/tab_bar.gd
+msgid "Open externally"
+msgstr "Extern öffnen"
+
+#: src/ui_parts/current_file_button.gd src/ui_parts/tab_bar.gd
+msgid "Show in File Manager"
+msgstr "Im Dateisystem anzeigen"
#: src/ui_parts/display.gd
msgid "Visuals"
@@ -268,7 +312,7 @@ msgstr "Rastergröße"
#: 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
+#: src/ui_widgets/confirm_dialog.gd src/ui_widgets/options_dialog.gd
msgid "Cancel"
msgstr "Abbrechen"
@@ -278,7 +322,7 @@ msgstr "Neues Element"
#: src/ui_parts/export_menu.gd
msgid "Dimensions"
-msgstr ""
+msgstr "Dimensionen"
#: src/ui_parts/export_menu.gd
msgid "Size"
@@ -306,15 +350,15 @@ msgstr "Skalieren"
#: src/ui_parts/export_menu.gd
msgid "Width"
-msgstr ""
+msgstr "Breite"
#: src/ui_parts/export_menu.gd
msgid "Height"
-msgstr ""
+msgstr "Höhe"
#: src/ui_parts/export_menu.gd
msgid "Preview image size is limited to {dimensions}"
-msgstr ""
+msgstr "Vorschaubildgröße ist auf {dimensions} limitiert."
#: src/ui_parts/global_actions.gd src/ui_parts/import_warning_menu.gd
#: src/utils/TranslationUtils.gd
@@ -329,22 +373,6 @@ msgstr "Einstellungen"
msgid "Optimize"
msgstr "Optimieren"
-#: src/ui_parts/global_actions.gd src/ui_parts/good_file_dialog.gd
-msgid "Save SVG"
-msgstr "SVG speichern"
-
-#: 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…"
@@ -401,7 +429,8 @@ msgstr "Bild auswählen"
msgid "Select"
msgstr "Auswählen"
-#: src/ui_parts/good_file_dialog.gd src/utils/TranslationUtils.gd
+#: src/ui_parts/good_file_dialog.gd src/utils/FileUtils.gd
+#: src/utils/TranslationUtils.gd
msgid "Save"
msgstr "Speichern"
@@ -503,7 +532,7 @@ msgstr "Tastenkombinationen"
#: src/ui_parts/settings_menu.gd
msgid "Theming"
-msgstr "Thematisierung"
+msgstr "Farbschema"
#: src/ui_parts/settings_menu.gd
msgid "Other"
@@ -527,7 +556,7 @@ msgstr "Attributfarbe"
#: src/ui_parts/settings_menu.gd
msgid "String color"
-msgstr "Stringfarbe"
+msgstr "Zeichenkettenfarbe"
#: src/ui_parts/settings_menu.gd
msgid "Comment color"
@@ -594,8 +623,8 @@ msgid "Invert zoom direction"
msgstr "Zoomrichtung umkehren"
#: src/ui_parts/settings_menu.gd
-msgid "Wrap mouse"
-msgstr "Maus einschließen"
+msgid "Wrap-around panning"
+msgstr "Herumwickelndes Verschieben"
#: src/ui_parts/settings_menu.gd
msgid "Use CTRL for zooming"
@@ -622,8 +651,8 @@ msgid "UI scale"
msgstr "Benutzeroberflächenskalierung"
#: src/ui_parts/settings_menu.gd
-msgid "Auto UI scale"
-msgstr "Automatische Benutzeroberflächenskalierung"
+msgid "Changes the scale factor for the interface."
+msgstr "Verändert den Skalierungsfaktor der Benutzeroberfläche."
#: src/ui_parts/settings_menu.gd
msgid "Language"
@@ -633,7 +662,7 @@ msgstr "Sprache"
msgid "Import XML"
msgstr "XML importieren"
-#: src/ui_parts/settings_menu.gd src/ui_widgets/palette_config.gd
+#: src/ui_parts/settings_menu.gd
msgid "Paste XML"
msgstr "XML einfügen"
@@ -646,12 +675,10 @@ msgid "New palette from XML"
msgstr "Neue Palette aus XML"
#: src/ui_parts/settings_menu.gd
-#, fuzzy
msgid "Editor formatter"
msgstr "Editor-Formatierer"
#: src/ui_parts/settings_menu.gd
-#, fuzzy
msgid "Export formatter"
msgstr "Export-Formatierer"
@@ -660,9 +687,8 @@ msgid "Help"
msgstr "Hilfe"
#: src/ui_parts/settings_menu.gd
-#, fuzzy
msgid "Reset all to default"
-msgstr "Zurücksetzen"
+msgstr "Alle auf Standardeinstellung zurücksetzen"
#: src/ui_parts/settings_menu.gd
msgid "Preset"
@@ -710,7 +736,7 @@ msgstr "Führende Null entfernen"
#: src/ui_parts/settings_menu.gd
msgid "Use exponential when shorter"
-msgstr "Exponentialschreibweise benutzen falls kürzer"
+msgstr "Exponentialschreibweise benutzen wenn kürzer"
#: src/ui_parts/settings_menu.gd
msgid "Colors"
@@ -757,12 +783,16 @@ msgid "Remove unnecessary parameters"
msgstr "Unnötige Parameter entfernen"
#: src/ui_parts/settings_menu.gd
-msgid "Swaps zoom in and zoom out with the mouse wheel."
-msgstr "Vertauscht Hinein- und Herauszoomen mit dem Mausrad."
+msgid "Swaps the scroll directions for zooming in and zooming out."
+msgstr "Tauscht die Scrollrichtung für das Rein- und Rauszoomen."
#: src/ui_parts/settings_menu.gd
-msgid "Wraps the mouse cursor around when panning the viewport."
-msgstr "Schließt die Maus beim bewegen innerhalb des Fensters ein."
+msgid ""
+"Warps the cursor to the opposite side whenever it reaches a viewport "
+"boundary while panning."
+msgstr ""
+"Teleportiert den Mauszeiger zur gegenüberliegenden Seite, soblald eine "
+"Bildschirmgrenze während des Verschiebens erreicht wird."
#: src/ui_parts/settings_menu.gd
msgid ""
@@ -793,67 +823,49 @@ msgstr ""
msgid "Changes the visual size and grabbing area of handles."
msgstr "Erhöht die visuelle Größe und den Interaktionsbereich von Griffen."
-#: src/ui_parts/settings_menu.gd
-msgid "Changes the scale of the visual user interface."
-msgstr "Verändert die Skalierung der Benutzeroberfläche."
+#: src/ui_parts/shortcut_panel.gd
+msgid "Horizontal strip"
+msgstr "Horizontaler Streifen"
-#: src/ui_parts/settings_menu.gd
-msgid "Scales the user interface based on the screen size."
-msgstr "Skaliert die Benutzeroberfläche basierend auf der Bildschirmgröße."
+#: src/ui_parts/shortcut_panel.gd
+msgid "Vertical strip"
+msgstr "Vertikaler Streifen"
+
+#: src/ui_parts/shortcut_panel.gd
+msgid "Horizontal with two rows"
+msgstr "Horizontal mit zwei Reihen"
#: src/ui_parts/shortcut_panel_config.gd
msgid "Configure Shortcut Panel"
-msgstr ""
+msgstr "Kurzbefehlsmenü anpassen"
#: src/ui_parts/shortcut_panel_config.gd
msgid "Layout"
-msgstr ""
-
-#: src/ui_parts/shortcut_panel_config.gd
-#, fuzzy
-msgid "Horizontal strip"
-msgstr "Horizontale Linie zu"
-
-#: src/ui_parts/shortcut_panel_config.gd
-#, fuzzy
-msgid "Horizontal with two rows"
-msgstr "Horizontale Linie zu"
-
-#: src/ui_parts/shortcut_panel_config.gd
-#, fuzzy
-msgid "Vertical strip"
-msgstr "Vertikale Linue zu"
+msgstr "Layout"
#: src/ui_parts/tab_bar.gd
-#, fuzzy
msgid "Create tab"
-msgstr "Erstellen"
+msgstr "Registerkarte erstellen"
-#: src/ui_parts/tab_bar.gd
-#, fuzzy
-msgid "Close all other tabs"
-msgstr "Text kopieren"
+#: src/ui_parts/tab_bar.gd src/utils/TranslationUtils.gd
+msgid "Create a new tab"
+msgstr "Neue Registerkarte erstellen"
#: src/ui_parts/tab_bar.gd
-msgid "Close tabs to the left"
-msgstr ""
+msgid "Scroll backwards"
+msgstr "Nach hinten scrollen"
#: 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"
+msgid "Scroll forwards"
+msgstr "Nach vorne scrollen"
#: src/ui_parts/tab_bar.gd
-msgid "This SVG is not bound to a location on the computer yet."
-msgstr ""
+msgid "This SVG is not bound to a file location yet."
+msgstr "Das SVG ist noch nicht an einen Dateiort gebunden."
#: src/ui_parts/update_menu.gd
-msgid "Include prereleases"
-msgstr "Vorabversionen miteinbeziehen"
+msgid "Show prereleases"
+msgstr "Vorschauversionen anzeigen"
#: src/ui_parts/update_menu.gd
msgid "Retry"
@@ -917,10 +929,6 @@ msgstr "Einfügen"
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"
@@ -942,13 +950,12 @@ msgid "Unnamed"
msgstr "Unbenannt"
#: src/ui_widgets/good_color_picker.gd
-#, fuzzy
msgid "Color keywords"
-msgstr "Farbauswahl"
+msgstr "Farbschlüsselwörter"
#: src/ui_widgets/good_color_picker.gd
msgid "Eyedropper"
-msgstr ""
+msgstr "Pipette"
#: src/ui_widgets/palette_config.gd
msgid "Unnamed palettes won't be shown."
@@ -966,13 +973,17 @@ msgstr "Diese Palette hat identisch definierte Farben."
msgid "Rename"
msgstr "Umbenennen"
+#: src/ui_widgets/palette_config.gd
+msgid "Apply Preset"
+msgstr "Voreinstellung anwenden"
+
#: src/ui_widgets/palette_config.gd
msgid "Copy as XML"
-msgstr "Kopiere als XML"
+msgstr "Kopieren als XML"
#: src/ui_widgets/palette_config.gd
-msgid "Apply Preset"
-msgstr "Voreinstellung anwenden"
+msgid "Save as XML"
+msgstr "Speichern als XML"
#: src/ui_widgets/path_popup.gd src/ui_widgets/pathdata_field.gd
#: src/utils/TranslationUtils.gd
@@ -1051,54 +1062,91 @@ 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 "Save the file?"
+msgstr "Datei speichern?"
+
+#: src/utils/FileUtils.gd
+msgid "Do you want to save this file?"
+msgstr "Möchten Sie diese Datei speichern?"
+
+#: src/utils/FileUtils.gd
+msgid "Save the changes?"
+msgstr "Die Änderungen speichern?"
+
+#: src/utils/FileUtils.gd
+msgid "Your changes will be lost if you don't save them."
+msgstr "Ihre Änderungen gehen verloren, wenn sie nicht gespeichert werden."
+
+#: src/utils/FileUtils.gd
+msgid "Don't save"
+msgstr "Nicht speichern"
+
#: src/utils/FileUtils.gd
msgid "The imported file is already being edited inside GodSVG."
-msgstr ""
+msgstr "Die importierte Datei wird bereits in GodSVG bearbeitet."
#: src/utils/FileUtils.gd
-msgid "If you want to apply the unsaved file state, use \"Reset SVG\" instead."
+msgid "If you want to revert your edits since the last save, use {reset_svg}."
msgstr ""
+"Wenn Sie Ihre Änderungen seit dem letzten Speichern rückgängig machen "
+"wollen, verwenden Sie {reset_svg}."
+
+#: src/utils/FileUtils.gd
+msgid "Do you want to save the changes made to {file_name}?"
+msgstr "Möchten Sie die an {file_name} vorgenommenen Änderungen speichern?"
+
+#: src/utils/TranslationUtils.gd
+msgid "Save as"
+msgstr "Speichern als"
+
+#: src/utils/TranslationUtils.gd
+msgid "Close tabs to the left"
+msgstr "Alle Registerkarten links schließen"
+
+#: src/utils/TranslationUtils.gd
+msgid "Close tabs to the right"
+msgstr "Alle Registerkarten rechts schließen"
+
+#: src/utils/TranslationUtils.gd
+msgid "Close all other tabs"
+msgstr "Alle anderen Registerkarten schließen"
#: src/utils/TranslationUtils.gd
msgid "Select the next tab"
-msgstr ""
+msgstr "Nächsten Tab auswählen"
#: src/utils/TranslationUtils.gd
msgid "Select the previous tab"
-msgstr ""
+msgstr "Vorherigen Tab auswählen"
#: src/utils/TranslationUtils.gd
-#, fuzzy
msgid "Open SVG externally"
-msgstr "GodSVG-Repository öffnen"
+msgstr "SVG extern öffnen"
#: src/utils/TranslationUtils.gd
msgid "Show SVG in File Manager"
-msgstr ""
+msgstr "SVG im Dateisystem anzeigen"
#: src/utils/TranslationUtils.gd
-#, fuzzy
msgid "Select all"
-msgstr "Auswählen"
+msgstr "Alle auswählen"
#: src/utils/TranslationUtils.gd
-#, fuzzy
msgid "Duplicate the selection"
-msgstr "Auswahl löschen"
+msgstr "Auswahl duplizieren"
#: src/utils/TranslationUtils.gd
msgid "Delete the selection"
msgstr "Auswahl löschen"
#: src/utils/TranslationUtils.gd
-#, fuzzy
msgid "Move the selection up"
-msgstr "Ausgewählte Tags nach oben verschieben"
+msgstr "Auswahl nach oben verschieben"
#: src/utils/TranslationUtils.gd
-#, fuzzy
msgid "Move the selection down"
-msgstr "Ausgewählte Tags nach unten verschieben"
+msgstr "Auswahl nach unten verschieben"
#: src/utils/TranslationUtils.gd
msgid "Find"
@@ -1146,7 +1194,7 @@ msgstr "GodSVG-Webseite öffnen"
#: src/utils/TranslationUtils.gd
msgid "Quit the application"
-msgstr "Applikation verlassen"
+msgstr "Anwendung verlassen"
#: src/utils/TranslationUtils.gd
msgid "Move to"
@@ -1202,6 +1250,32 @@ msgstr ""
"Die Dateierweiterung ist leer. Nur {extension_list} Dateien werden "
"unterstützt."
+#~ msgid ""
+#~ "If you want to apply the unsaved file state, use \"Reset SVG\" instead."
+#~ msgstr ""
+#~ "Wenn Sie den ungespeicherten Dateizustand anwenden möchten, benutzen sie "
+#~ "stattdessen \"SVG zurücksetzen\"."
+
+#~ msgid "Auto UI scale"
+#~ msgstr "Automatische Benutzeroberflächenskalierung"
+
+#~ msgid "Scales the interface automatically based on the screen size."
+#~ msgstr ""
+#~ "Skaliert die Benutzeroberfläche automatisch basierend auf der "
+#~ "Bildschirmgröße."
+
+#~ msgid "Wrap mouse"
+#~ msgstr "Maus einschließen"
+
+#~ msgid "Swaps zoom in and zoom out with the mouse wheel."
+#~ msgstr "Vertauscht Hinein- und Herauszoomen mit dem Mausrad."
+
+#~ msgid "Wraps the mouse cursor around when panning the viewport."
+#~ msgstr "Schließt die Maus beim bewegen innerhalb des Fensters ein."
+
+#~ msgid "This requires GodSVG to connect to the internet."
+#~ msgstr "Hierfür muss sich GodSVG mit dem Internet verbinden."
+
#, fuzzy
#~ msgid "Add new tab"
#~ msgstr "Neues Element hinzufügen"
diff --git a/assets/translations/en.po b/translations/en.po
similarity index 90%
rename from assets/translations/en.po
rename to translations/en.po
index 649147f..c38bfaf 100644
--- a/assets/translations/en.po
+++ b/translations/en.po
@@ -32,13 +32,19 @@ msgid "Check for updates?"
msgstr ""
#: src/autoload/HandlerGUI.gd
-msgid "This requires GodSVG to connect to the internet."
+msgid ""
+"This will connect to github.com to compare version numbers. No other data is "
+"collected or transmitted."
msgstr ""
#: src/autoload/HandlerGUI.gd src/ui_widgets/alert_dialog.gd
msgid "OK"
msgstr ""
+#: src/autoload/HandlerGUI.gd
+msgid "Do you want to proceed?"
+msgstr ""
+
#: src/autoload/HandlerGUI.gd
msgid "Export SVG"
msgstr ""
@@ -50,8 +56,13 @@ msgstr ""
#: src/autoload/HandlerGUI.gd
msgid ""
-"The graphic can only be exported as SVG because its size is not defined. Do "
-"you want to proceed?"
+"The graphic can be exported only as SVG because its proportions are too "
+"extreme."
+msgstr ""
+
+#: src/autoload/HandlerGUI.gd
+msgid ""
+"The graphic can be exported only as SVG because its size is not defined."
msgstr ""
#: src/autoload/State.gd src/ui_parts/good_file_dialog.gd
@@ -102,8 +113,8 @@ msgstr ""
#: src/autoload/State.gd
msgid ""
-"The tab is bound to the file path {file_path}. Do you want to restore from "
-"this path?"
+"The tab is bound to the file path {file_path}. Do you want to restore the "
+"SVG from this path?"
msgstr ""
#: src/config_classes/Formatter.gd
@@ -142,6 +153,10 @@ msgstr ""
msgid "6-digit hex"
msgstr ""
+#: src/config_classes/TabData.gd
+msgid "Empty"
+msgstr ""
+
#: src/config_classes/TabData.gd
msgid "Unsaved"
msgstr ""
@@ -193,43 +208,63 @@ msgid "Close"
msgstr ""
#: src/ui_parts/about_menu.gd
-msgid "Project Founder and Manager"
+msgid "Authors"
msgstr ""
#: src/ui_parts/about_menu.gd
-msgid "Developers"
+msgid "Donors"
msgstr ""
#: src/ui_parts/about_menu.gd
-msgid "Translators"
+msgid "License"
msgstr ""
#: src/ui_parts/about_menu.gd
-msgid "Donors"
+msgid "Third-party licenses"
msgstr ""
#: src/ui_parts/about_menu.gd
-msgid "Golden donors"
+msgid "Project Founder and Manager"
msgstr ""
#: src/ui_parts/about_menu.gd
-msgid "Diamond donors"
+msgid "Developers"
msgstr ""
#: src/ui_parts/about_menu.gd
-msgid "Authors"
+msgid "Translators"
msgstr ""
#: src/ui_parts/about_menu.gd
-msgid "License"
+msgid "Golden donors"
msgstr ""
#: src/ui_parts/about_menu.gd
-msgid "Godot third-party components"
+msgid "Diamond donors"
msgstr ""
-#: src/ui_parts/about_menu.gd
-msgid "Third-party licenses"
+#: src/ui_parts/code_editor.gd src/utils/TranslationUtils.gd
+msgid "Copy all text"
+msgstr ""
+
+#: src/ui_parts/current_file_button.gd src/ui_parts/good_file_dialog.gd
+msgid "Save SVG"
+msgstr ""
+
+#: src/ui_parts/current_file_button.gd
+msgid "Save SVG as…"
+msgstr ""
+
+#: src/ui_parts/current_file_button.gd src/utils/TranslationUtils.gd
+msgid "Reset SVG"
+msgstr ""
+
+#: src/ui_parts/current_file_button.gd src/ui_parts/tab_bar.gd
+msgid "Open externally"
+msgstr ""
+
+#: src/ui_parts/current_file_button.gd src/ui_parts/tab_bar.gd
+msgid "Show in File Manager"
msgstr ""
#: src/ui_parts/display.gd
@@ -266,7 +301,7 @@ 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
+#: src/ui_widgets/confirm_dialog.gd src/ui_widgets/options_dialog.gd
msgid "Cancel"
msgstr ""
@@ -327,22 +362,6 @@ msgstr ""
msgid "Optimize"
msgstr ""
-#: src/ui_parts/global_actions.gd src/ui_parts/good_file_dialog.gd
-msgid "Save SVG"
-msgstr ""
-
-#: 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 ""
@@ -399,7 +418,8 @@ msgstr ""
msgid "Select"
msgstr ""
-#: src/ui_parts/good_file_dialog.gd src/utils/TranslationUtils.gd
+#: src/ui_parts/good_file_dialog.gd src/utils/FileUtils.gd
+#: src/utils/TranslationUtils.gd
msgid "Save"
msgstr ""
@@ -590,7 +610,7 @@ msgid "Invert zoom direction"
msgstr ""
#: src/ui_parts/settings_menu.gd
-msgid "Wrap mouse"
+msgid "Wrap-around panning"
msgstr ""
#: src/ui_parts/settings_menu.gd
@@ -618,7 +638,7 @@ msgid "UI scale"
msgstr ""
#: src/ui_parts/settings_menu.gd
-msgid "Auto UI scale"
+msgid "Changes the scale factor for the interface."
msgstr ""
#: src/ui_parts/settings_menu.gd
@@ -629,7 +649,7 @@ msgstr ""
msgid "Import XML"
msgstr ""
-#: src/ui_parts/settings_menu.gd src/ui_widgets/palette_config.gd
+#: src/ui_parts/settings_menu.gd
msgid "Paste XML"
msgstr ""
@@ -750,11 +770,13 @@ msgid "Remove unnecessary parameters"
msgstr ""
#: src/ui_parts/settings_menu.gd
-msgid "Swaps zoom in and zoom out with the mouse wheel."
+msgid "Swaps the scroll directions for zooming in and zooming out."
msgstr ""
#: src/ui_parts/settings_menu.gd
-msgid "Wraps the mouse cursor around when panning the viewport."
+msgid ""
+"Warps the cursor to the opposite side whenever it reaches a viewport "
+"boundary while panning."
msgstr ""
#: src/ui_parts/settings_menu.gd
@@ -779,60 +801,48 @@ msgstr ""
msgid "Changes the visual size and grabbing area of handles."
msgstr ""
-#: src/ui_parts/settings_menu.gd
-msgid "Changes the scale of the visual user interface."
-msgstr ""
-
-#: src/ui_parts/settings_menu.gd
-msgid "Scales the user interface based on the screen size."
-msgstr ""
-
-#: src/ui_parts/shortcut_panel_config.gd
-msgid "Configure Shortcut Panel"
+#: src/ui_parts/shortcut_panel.gd
+msgid "Horizontal strip"
msgstr ""
-#: src/ui_parts/shortcut_panel_config.gd
-msgid "Layout"
+#: src/ui_parts/shortcut_panel.gd
+msgid "Vertical strip"
msgstr ""
-#: src/ui_parts/shortcut_panel_config.gd
-msgid "Horizontal strip"
+#: src/ui_parts/shortcut_panel.gd
+msgid "Horizontal with two rows"
msgstr ""
#: src/ui_parts/shortcut_panel_config.gd
-msgid "Horizontal with two rows"
+msgid "Configure Shortcut Panel"
msgstr ""
#: src/ui_parts/shortcut_panel_config.gd
-msgid "Vertical strip"
+msgid "Layout"
msgstr ""
#: src/ui_parts/tab_bar.gd
msgid "Create tab"
msgstr ""
-#: src/ui_parts/tab_bar.gd
-msgid "Close all other tabs"
+#: src/ui_parts/tab_bar.gd src/utils/TranslationUtils.gd
+msgid "Create a new tab"
msgstr ""
#: src/ui_parts/tab_bar.gd
-msgid "Close tabs to the left"
+msgid "Scroll backwards"
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"
+msgid "Scroll forwards"
msgstr ""
#: src/ui_parts/tab_bar.gd
-msgid "This SVG is not bound to a location on the computer yet."
+msgid "This SVG is not bound to a file location yet."
msgstr ""
#: src/ui_parts/update_menu.gd
-msgid "Include prereleases"
+msgid "Show prereleases"
msgstr ""
#: src/ui_parts/update_menu.gd
@@ -897,10 +907,6 @@ msgstr ""
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 ""
@@ -945,12 +951,16 @@ msgstr ""
msgid "Rename"
msgstr ""
+#: src/ui_widgets/palette_config.gd
+msgid "Apply Preset"
+msgstr ""
+
#: src/ui_widgets/palette_config.gd
msgid "Copy as XML"
msgstr ""
#: src/ui_widgets/palette_config.gd
-msgid "Apply Preset"
+msgid "Save as XML"
msgstr ""
#: src/ui_widgets/path_popup.gd src/ui_widgets/pathdata_field.gd
@@ -1030,12 +1040,52 @@ msgstr ""
msgid "Check if the file still exists in the selected file path."
msgstr ""
+#: src/utils/FileUtils.gd
+msgid "Save the file?"
+msgstr ""
+
+#: src/utils/FileUtils.gd
+msgid "Do you want to save this file?"
+msgstr ""
+
+#: src/utils/FileUtils.gd
+msgid "Save the changes?"
+msgstr ""
+
+#: src/utils/FileUtils.gd
+msgid "Your changes will be lost if you don't save them."
+msgstr ""
+
+#: src/utils/FileUtils.gd
+msgid "Don't save"
+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."
+msgid "If you want to revert your edits since the last save, use {reset_svg}."
+msgstr ""
+
+#: src/utils/FileUtils.gd
+msgid "Do you want to save the changes made to {file_name}?"
+msgstr ""
+
+#: src/utils/TranslationUtils.gd
+msgid "Save as"
+msgstr ""
+
+#: src/utils/TranslationUtils.gd
+msgid "Close tabs to the left"
+msgstr ""
+
+#: src/utils/TranslationUtils.gd
+msgid "Close tabs to the right"
+msgstr ""
+
+#: src/utils/TranslationUtils.gd
+msgid "Close all other tabs"
msgstr ""
#: src/utils/TranslationUtils.gd
diff --git a/translations/et.po b/translations/et.po
new file mode 100644
index 0000000..25ade10
--- /dev/null
+++ b/translations/et.po
@@ -0,0 +1,1251 @@
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: GodSVG\n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: et\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 3.4.2\n"
+
+msgid "translation-credits"
+msgstr "Kiisu-Master"
+
+#: src/autoload/HandlerGUI.gd
+msgid "Quit GodSVG"
+msgstr "Sulge GodSVG"
+
+#: src/autoload/HandlerGUI.gd
+msgid "Do you want to quit GodSVG?"
+msgstr "Kas te tahate sulgeda GodSVG?"
+
+#: src/autoload/HandlerGUI.gd
+msgid "Quit"
+msgstr "Sulge"
+
+#: src/autoload/HandlerGUI.gd
+msgid "Check for updates?"
+msgstr "Otsi uuendusi?"
+
+#: src/autoload/HandlerGUI.gd
+msgid ""
+"This will connect to github.com to compare version numbers. No other data is "
+"collected or transmitted."
+msgstr ""
+"See ühendab github.com-i, et saada versioonide teabe. Muid andmeid ei koguta "
+"ega edastata."
+
+#: src/autoload/HandlerGUI.gd src/ui_widgets/alert_dialog.gd
+msgid "OK"
+msgstr "Okei"
+
+#: src/autoload/HandlerGUI.gd
+msgid "Do you want to proceed?"
+msgstr "Kas te tahate jätkata?"
+
+#: src/autoload/HandlerGUI.gd
+msgid "Export SVG"
+msgstr "Ekspordi SVG"
+
+#: src/autoload/HandlerGUI.gd src/ui_parts/export_menu.gd
+#: src/ui_parts/global_actions.gd src/utils/TranslationUtils.gd
+msgid "Export"
+msgstr "Ekspordi"
+
+#: src/autoload/HandlerGUI.gd
+msgid ""
+"The graphic can be exported only as SVG because its proportions are too "
+"extreme."
+msgstr ""
+"Graafika saab eksportida ainult SVG-na, sest selle proportsioonid on liiga "
+"ekstreemsed."
+
+#: src/autoload/HandlerGUI.gd
+msgid ""
+"The graphic can be exported only as SVG because its size is not defined."
+msgstr ""
+"Graafika saab eksportida ainult SVG-na, sest selle suurus pole defineeritud."
+
+#: src/autoload/State.gd src/ui_parts/good_file_dialog.gd
+#: src/ui_widgets/alert_dialog.gd
+msgid "Alert!"
+msgstr "Hoiatus!"
+
+#: src/autoload/State.gd src/ui_parts/tab_bar.gd src/utils/TranslationUtils.gd
+msgid "Close tab"
+msgstr "Sulge kaart"
+
+#: src/autoload/State.gd
+msgid "Restore"
+msgstr "Taasta"
+
+#: src/autoload/State.gd
+msgid "View in List"
+msgstr "Näita nimekirjas"
+
+#: src/autoload/State.gd
+msgid "Duplicate"
+msgstr "Dubleeri"
+
+#: src/autoload/State.gd
+msgid "Convert To"
+msgstr "Konverteeri"
+
+#: src/autoload/State.gd src/ui_widgets/palette_config.gd
+msgid "Move Up"
+msgstr "Liiguta üles"
+
+#: src/autoload/State.gd src/ui_widgets/palette_config.gd
+msgid "Move Down"
+msgstr "Liiguta alla"
+
+#: 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 "Kustuta"
+
+#: src/autoload/State.gd src/ui_widgets/transform_popup.gd
+msgid "Insert After"
+msgstr "Sisesta pärast"
+
+#: src/autoload/State.gd
+msgid "The last edited state of this tab could not be found."
+msgstr "Selle kaardi viimast seisu polnud võimalik leida."
+
+#: src/autoload/State.gd
+msgid ""
+"The tab is bound to the file path {file_path}. Do you want to restore the "
+"SVG from this path?"
+msgstr ""
+"Kaart on seotud failiga asukohas {file_path}. Kas te soovite taastada SVG "
+"sellest failist?"
+
+#: src/config_classes/Formatter.gd
+msgid "Compact"
+msgstr "Kompaktne"
+
+#: src/config_classes/Formatter.gd
+msgid "Pretty"
+msgstr "Ilus"
+
+#: src/config_classes/Formatter.gd
+msgid "Always"
+msgstr "Alati"
+
+#: src/config_classes/Formatter.gd
+msgid "All except containers"
+msgstr "Kõik, v.a. konteinerid"
+
+#: src/config_classes/Formatter.gd
+msgid "Never"
+msgstr "Mitte kunagi"
+
+#: src/config_classes/Formatter.gd
+msgid "When shorter or equal"
+msgstr "Kui on lühem või võrdne"
+
+#: src/config_classes/Formatter.gd
+msgid "When shorter"
+msgstr "Kui on lühem"
+
+#: src/config_classes/Formatter.gd
+msgid "3-digit or 6-digit hex"
+msgstr "3- või 6-kohaline hex"
+
+#: src/config_classes/Formatter.gd
+msgid "6-digit hex"
+msgstr "6-kohaline hex"
+
+#: src/config_classes/TabData.gd
+msgid "Empty"
+msgstr "Tühi"
+
+#: src/config_classes/TabData.gd
+msgid "Unsaved"
+msgstr "Salvestamata"
+
+#: src/data_classes/BasicXNode.gd
+msgid "Comment"
+msgstr "Kommentaar"
+
+#: src/data_classes/BasicXNode.gd
+msgid "Text"
+msgstr "Tekst"
+
+#: src/data_classes/Element.gd
+msgid "{element} must be inside {allowed} to have any effect."
+msgstr "{element} peab olema {allowed} sees, et sellel oleks mõju."
+
+#: src/data_classes/ElementG.gd
+msgid "This group has no elements."
+msgstr "Grupis pole ühtegi elementi."
+
+#: src/data_classes/ElementG.gd
+msgid "This group has only one element."
+msgstr "Grupis on ainult üks element."
+
+#: src/data_classes/GradientUtils.gd
+msgid "No \"id\" attribute defined."
+msgstr "\"id\" atribuut pole defineeritud."
+
+#: src/data_classes/GradientUtils.gd
+msgid "No elements under this gradient."
+msgstr "Gradiendis puuduvad elemendid."
+
+#: src/data_classes/GradientUtils.gd
+msgid "This gradient is a solid color."
+msgstr "Gradient on ühevärviline."
+
+#: src/data_classes/SVGParser.gd
+msgid "Doesn’t describe an SVG."
+msgstr "Ei kirjelda SVGd."
+
+#: src/data_classes/SVGParser.gd
+msgid "Improper nesting."
+msgstr "Vigane pesitsus."
+
+#: 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 "Sulge"
+
+#: src/ui_parts/about_menu.gd
+msgid "Authors"
+msgstr "Autorid"
+
+#: src/ui_parts/about_menu.gd
+msgid "Donors"
+msgstr "Annetajad"
+
+#: src/ui_parts/about_menu.gd
+msgid "License"
+msgstr "Litsents"
+
+#: src/ui_parts/about_menu.gd
+msgid "Third-party licenses"
+msgstr "Kolmandad litsentsid"
+
+#: src/ui_parts/about_menu.gd
+msgid "Project Founder and Manager"
+msgstr "Projekti asutaja ja juht"
+
+#: src/ui_parts/about_menu.gd
+msgid "Developers"
+msgstr "Arendajad"
+
+#: src/ui_parts/about_menu.gd
+msgid "Translators"
+msgstr "Tõlkijad"
+
+#: src/ui_parts/about_menu.gd
+msgid "Golden donors"
+msgstr "Kuldsed annetajad"
+
+#: src/ui_parts/about_menu.gd
+msgid "Diamond donors"
+msgstr "Teemant annetajad"
+
+#: src/ui_parts/code_editor.gd src/utils/TranslationUtils.gd
+msgid "Copy all text"
+msgstr "Kopeeri terve tekst"
+
+#: src/ui_parts/current_file_button.gd src/ui_parts/good_file_dialog.gd
+msgid "Save SVG"
+msgstr "Salvesta SVG"
+
+#: src/ui_parts/current_file_button.gd
+msgid "Save SVG as…"
+msgstr "Salvesta SVG kui…"
+
+#: src/ui_parts/current_file_button.gd src/utils/TranslationUtils.gd
+msgid "Reset SVG"
+msgstr "Lähtesta SVG"
+
+#: src/ui_parts/current_file_button.gd src/ui_parts/tab_bar.gd
+msgid "Open externally"
+msgstr "Ava väliselt"
+
+#: src/ui_parts/current_file_button.gd src/ui_parts/tab_bar.gd
+msgid "Show in File Manager"
+msgstr "Kuva failihalduris"
+
+#: src/ui_parts/display.gd
+msgid "Visuals"
+msgstr "Visuaalid"
+
+#: src/ui_parts/display.gd src/utils/TranslationUtils.gd
+msgid "Load reference image"
+msgstr "Lae võrdluspilt"
+
+#: src/ui_parts/display.gd
+msgid "Show reference"
+msgstr "Kuva võrdluspilti"
+
+#: src/ui_parts/display.gd
+msgid "Overlay reference"
+msgstr "Kata võrdluspildiga üle"
+
+#: src/ui_parts/display.gd src/utils/TranslationUtils.gd
+msgid "Show grid"
+msgstr "Kuva ruudustik"
+
+#: src/ui_parts/display.gd src/utils/TranslationUtils.gd
+msgid "Show handles"
+msgstr "Kuva pidemeid"
+
+#: src/ui_parts/display.gd
+msgid "Rasterized SVG"
+msgstr "Rasterdatud SVG"
+
+#: src/ui_parts/display.gd
+msgid "Snap size"
+msgstr "Joondamise mastaap"
+
+#: 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 src/ui_widgets/options_dialog.gd
+msgid "Cancel"
+msgstr "Katkesta"
+
+#: src/ui_parts/element_container.gd
+msgid "New element"
+msgstr "Uus element"
+
+#: src/ui_parts/export_menu.gd
+msgid "Dimensions"
+msgstr "Dimensioonid"
+
+#: src/ui_parts/export_menu.gd
+msgid "Size"
+msgstr "Suurus"
+
+#: src/ui_parts/export_menu.gd
+msgid "Export Configuration"
+msgstr "Ekspordi konfiguratsioon"
+
+#: src/ui_parts/export_menu.gd
+msgid "Format"
+msgstr "Formaat"
+
+#: src/ui_parts/export_menu.gd
+msgid "Lossless"
+msgstr "Kadudeta"
+
+#: src/ui_parts/export_menu.gd
+msgid "Quality"
+msgstr "Kvaliteet"
+
+#: src/ui_parts/export_menu.gd
+msgid "Scale"
+msgstr "Skaala"
+
+#: src/ui_parts/export_menu.gd
+msgid "Width"
+msgstr "Laius"
+
+#: src/ui_parts/export_menu.gd
+msgid "Height"
+msgstr "Kõrgus"
+
+#: src/ui_parts/export_menu.gd
+msgid "Preview image size is limited to {dimensions}"
+msgstr "Eelvaade on piiratud suurusele {dimensions}"
+
+#: src/ui_parts/global_actions.gd src/ui_parts/import_warning_menu.gd
+#: src/utils/TranslationUtils.gd
+msgid "Import"
+msgstr "Impordi"
+
+#: src/ui_parts/global_actions.gd
+msgid "Settings"
+msgstr "Seaded"
+
+#: src/ui_parts/global_actions.gd src/utils/TranslationUtils.gd
+msgid "Optimize"
+msgstr "Optimeeri"
+
+#: src/ui_parts/global_actions.gd
+msgid "About…"
+msgstr "Teave…"
+
+#: src/ui_parts/global_actions.gd src/utils/TranslationUtils.gd
+msgid "Check for updates"
+msgstr "Otsi uuendusi"
+
+#: src/ui_parts/global_actions.gd
+msgid "Donate…"
+msgstr "Anneta…"
+
+#: src/ui_parts/global_actions.gd
+msgid "GodSVG repository"
+msgstr "GodSVG repositoorium"
+
+#: src/ui_parts/global_actions.gd
+msgid "GodSVG website"
+msgstr "GodSVG veebileht"
+
+#: src/ui_parts/global_actions.gd
+msgid "View savedata"
+msgstr "Kuva savedata"
+
+#: src/ui_parts/good_file_dialog.gd
+msgid "Go to parent folder"
+msgstr "Mine taseme võrra üles"
+
+#: src/ui_parts/good_file_dialog.gd
+msgid "Refresh files"
+msgstr "Värskenda failinimekiri"
+
+#: src/ui_parts/good_file_dialog.gd
+msgid "Toggle the visibility of hidden files"
+msgstr "Lülita peidetud failide kuvamine sisse"
+
+#: src/ui_parts/good_file_dialog.gd
+msgid "Search files"
+msgstr "Otsi faile"
+
+#: src/ui_parts/good_file_dialog.gd
+msgid "Select an SVG"
+msgstr "Vali SVG"
+
+#: src/ui_parts/good_file_dialog.gd
+msgid "Select an XML file"
+msgstr "Vali XML fail"
+
+#: src/ui_parts/good_file_dialog.gd
+msgid "Select an image"
+msgstr "Vali pilt"
+
+#: src/ui_parts/good_file_dialog.gd
+msgid "Select"
+msgstr "Vali"
+
+#: src/ui_parts/good_file_dialog.gd src/utils/FileUtils.gd
+#: src/utils/TranslationUtils.gd
+msgid "Save"
+msgstr "Salvesta"
+
+#: src/ui_parts/good_file_dialog.gd
+msgid "Path"
+msgstr "Asukoht"
+
+#: src/ui_parts/good_file_dialog.gd
+msgid "Replace"
+msgstr "Asenda"
+
+#: src/ui_parts/good_file_dialog.gd
+msgid "Create new folder"
+msgstr "Loo uus kaust"
+
+#: src/ui_parts/good_file_dialog.gd
+msgid "Invalid name for a folder."
+msgstr "Vigane kausta nimi."
+
+#: src/ui_parts/good_file_dialog.gd
+msgid "A folder with this name already exists."
+msgstr "Kaust selle nimega on juba olemas."
+
+#: src/ui_parts/good_file_dialog.gd
+msgid "Failed to create a folder."
+msgstr "Kausta loomine ebaõnnestus."
+
+#: src/ui_parts/good_file_dialog.gd
+msgid "Open"
+msgstr "Ava"
+
+#: src/ui_parts/good_file_dialog.gd
+msgid "Copy path"
+msgstr "Kopeeri asukoht"
+
+#: src/ui_parts/good_file_dialog.gd
+msgid ""
+"A file named \"{file_name}\" already exists. Replacing will overwrite its "
+"contents!"
+msgstr ""
+"Fail nimega \"{file_name}\" on juba olemas, asendamine kirjutab selle sisu "
+"üle!"
+
+#: src/ui_parts/handles_manager.gd
+msgid "New shape"
+msgstr "Uus kujund"
+
+#: src/ui_parts/import_warning_menu.gd
+msgid "Import Problems"
+msgstr "Impordi probleemid"
+
+#: src/ui_parts/import_warning_menu.gd
+msgid "Unrecognized element"
+msgstr "Tundmatu element"
+
+#: src/ui_parts/import_warning_menu.gd
+msgid "Unrecognized attribute"
+msgstr "Tundmatu atribuut"
+
+#: src/ui_parts/import_warning_menu.gd
+msgid "Syntax error"
+msgstr "Süntaksiviga"
+
+#: src/ui_parts/inspector.gd
+msgid "Add element"
+msgstr "Lisa element"
+
+#: src/ui_parts/mac_menu.gd src/ui_parts/settings_menu.gd
+msgid "File"
+msgstr "Fail"
+
+#: src/ui_parts/mac_menu.gd src/ui_parts/settings_menu.gd
+msgid "Edit"
+msgstr "Redigeeri"
+
+#: src/ui_parts/mac_menu.gd src/ui_parts/settings_menu.gd
+msgid "Tool"
+msgstr "Tööriist"
+
+#: src/ui_parts/mac_menu.gd src/ui_parts/settings_menu.gd
+msgid "View"
+msgstr "Vaade"
+
+#: src/ui_parts/mac_menu.gd
+msgid "Snap"
+msgstr "Joonda"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Formatting"
+msgstr "Vorming"
+
+#: src/ui_parts/settings_menu.gd src/ui_widgets/color_popup.gd
+msgid "Palettes"
+msgstr "Värvipaletid"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Shortcuts"
+msgstr "Otseklahvid"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Theming"
+msgstr "Teema"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Tab bar"
+msgstr "Kaardiriba"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Other"
+msgstr "Muu"
+
+#: src/ui_parts/settings_menu.gd
+msgid "SVG Text colors"
+msgstr "SVG teksti värvid"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Symbol color"
+msgstr "Sümboli värv"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Element color"
+msgstr "Elemendi värv"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Attribute color"
+msgstr "Atribuudi värv"
+
+#: src/ui_parts/settings_menu.gd
+msgid "String color"
+msgstr "Sõne värv"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Comment color"
+msgstr "Kommentaari värv"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Text color"
+msgstr "Teksti värv"
+
+#: src/ui_parts/settings_menu.gd
+msgid "CDATA color"
+msgstr "CDATA värv"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Error color"
+msgstr "Errori värv"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Handle colors"
+msgstr "Pideme värv"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Inside color"
+msgstr "Seesmine värv"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Normal color"
+msgstr "Tavaline värv"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Hovered color"
+msgstr "Esiletõstetud värv"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Selected color"
+msgstr "Valiku värv"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Hovered selected color"
+msgstr "Esiletõstetud valiku värv"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Basic colors"
+msgstr "Alusvärvid"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Background color"
+msgstr "Tausta värv"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Valid color"
+msgstr "Kehtiva värv"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Warning color"
+msgstr "Hoiatuse värv"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Close tabs with middle mouse button"
+msgstr "Sulge kaarte keskmise hiireklahviga"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Input"
+msgstr "Sisend"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Invert zoom direction"
+msgstr "Inverteeri suurendussuund"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Wrap-around panning"
+msgstr "Takistusteta vaate liigutamine"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Use CTRL for zooming"
+msgstr "Kasuta CTRL klahvi suurendamiseks"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Miscellaneous"
+msgstr "Mitmesugust"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Use native file dialog"
+msgstr "Kasuta süsteemi failidialoogi"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Sync window title to file name"
+msgstr "Sünkroniseeri akna tiitel avatud faili nimega"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Handle size"
+msgstr "Pidemete suurus"
+
+#: src/ui_parts/settings_menu.gd
+msgid "UI scale"
+msgstr "Kasutajaliidese suurus"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Changes the scale factor for the interface."
+msgstr "Muudab kasutajaliidese suurust."
+
+#: src/ui_parts/settings_menu.gd
+msgid "Language"
+msgstr "Keel"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Import XML"
+msgstr "Impordi XML"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Paste XML"
+msgstr "Kleebi XML"
+
+#: src/ui_parts/settings_menu.gd
+msgid "New palette"
+msgstr "Uus palett"
+
+#: src/ui_parts/settings_menu.gd
+msgid "New palette from XML"
+msgstr "Uus palett XML-ist"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Editor formatter"
+msgstr "Redaktori vormindus"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Export formatter"
+msgstr "Ekspordi vormindus"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Help"
+msgstr "Abi"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Reset all to default"
+msgstr "Lähtesta kõik vaikeväärtustele"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Preset"
+msgstr "Eelseadistus"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Keep comments"
+msgstr "Hoia kommentaarid alles"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Keep unrecognized XML structures"
+msgstr "Hoia tundmatud XML struktuurid alles"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Add trailing newline"
+msgstr "Lisa tühi rida lõppu"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Use shorthand tag syntax"
+msgstr "Kasuta tag-i lühisüntaksit"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Space out the slash of shorthand tags"
+msgstr "Lisa tühikuid tag-i lühisüntaksi kaldkriipsu juurde"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Use pretty formatting"
+msgstr "Kasuta ilusat vormindust"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Use spaces instead of tabs"
+msgstr "Kasuta tühikuid tab-i asemel"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Number of indentation spaces"
+msgstr "Indenteerimise tühikute arv"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Numbers"
+msgstr "Numbrid"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Remove leading zero"
+msgstr "Eemalda üleliigsed nullid numbritest"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Use exponential when shorter"
+msgstr "Kasuta eksponentvormindust kui see on lühem"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Colors"
+msgstr "Värvid"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Use named colors"
+msgstr "Kasuta nimedega värve"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Primary syntax"
+msgstr "Esmane süntaks"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Capitalize hexadecimal letters"
+msgstr "Kasuta heksadetsimaalnumbrites suurtähti"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Pathdata"
+msgstr "Path andmed"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Compress numbers"
+msgstr "Tihenda numbrid"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Minimize spacing"
+msgstr "Minimeeri vahed"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Remove spacing after flags"
+msgstr "Eemalda tühikud omaduste (flags) järelt"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Remove consecutive commands"
+msgstr "Eemalda järjestikused samasugused käsud"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Transform lists"
+msgstr "Teisendused"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Remove unnecessary parameters"
+msgstr "Eemalda ebavajalikud parameetrid"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Swaps the scroll directions for zooming in and zooming out."
+msgstr "Vahetab kerimissuunad suurendamiseks ja vähendamiseks ümber."
+
+#: src/ui_parts/settings_menu.gd
+msgid ""
+"Warps the cursor to the opposite side whenever it reaches a viewport "
+"boundary while panning."
+msgstr ""
+"Viib kursori akna vastaspoolele, kui see jõuab vaate liigutamise ajal selle "
+"servani, et see liikumist ei takistaks."
+
+#: src/ui_parts/settings_menu.gd
+msgid ""
+"If turned on, scrolling will pan the view. To zoom, hold CTRL while "
+"scrolling."
+msgstr ""
+"Sisselülitamisel kerimine liigutab vaadet. Suurendamiseks hoia CTRL klahvi "
+"kerimise ajal."
+
+#: src/ui_parts/settings_menu.gd
+msgid ""
+"If turned on, uses your operating system's native file dialog. If turned "
+"off, uses GodSVG's built-in file dialog."
+msgstr ""
+"Sisselülitamisel kasutab süsteemi vaikefailidialoogi. Väljalülitamisel "
+"GodSVG sisseehitatud failidialoogi."
+
+#: src/ui_parts/settings_menu.gd
+msgid ""
+"If turned off, the window title will remain simply \"GodSVG\" regardless of "
+"the current file."
+msgstr ""
+"Väljalülitamisel jääb akna nimieks \"GodSVG\" sõltumata avatud failist."
+
+#: src/ui_parts/settings_menu.gd
+msgid "Changes the visual size and grabbing area of handles."
+msgstr "Muudab pidemete nähtavat ja interaktiivse ala suurust."
+
+#: src/ui_parts/shortcut_panel.gd
+msgid "Horizontal strip"
+msgstr "Horisontaalne riba"
+
+#: src/ui_parts/shortcut_panel.gd
+msgid "Vertical strip"
+msgstr "Vertikaalne riba"
+
+#: src/ui_parts/shortcut_panel.gd
+msgid "Horizontal with two rows"
+msgstr "Horisontaalne riba kahe reaga"
+
+#: src/ui_parts/shortcut_panel_config.gd
+msgid "Configure Shortcut Panel"
+msgstr "Konfigureeri otseklahvide paneeli"
+
+#: src/ui_parts/shortcut_panel_config.gd
+msgid "Layout"
+msgstr "Paigutus"
+
+#: src/ui_parts/tab_bar.gd
+msgid "Create tab"
+msgstr "Uus kaart"
+
+#: src/ui_parts/tab_bar.gd src/utils/TranslationUtils.gd
+msgid "Create a new tab"
+msgstr "Loo uus kaart"
+
+#: src/ui_parts/tab_bar.gd
+msgid "Scroll backwards"
+msgstr "Keri tagasi"
+
+#: src/ui_parts/tab_bar.gd
+msgid "Scroll forwards"
+msgstr "Keri edasi"
+
+#: src/ui_parts/tab_bar.gd
+msgid "This SVG is not bound to a file location yet."
+msgstr "See SVG ei ole veel seotud failiasukohaga."
+
+#: src/ui_parts/update_menu.gd
+msgid "Show prereleases"
+msgstr "Näita eelväljaandeid"
+
+#: src/ui_parts/update_menu.gd
+msgid "Retry"
+msgstr "Proovi uuesti"
+
+#: src/ui_parts/update_menu.gd
+msgid "Current Version"
+msgstr "Praegune versioon"
+
+#: src/ui_parts/update_menu.gd
+msgid "Retrieving information..."
+msgstr "Informatsiooni saamine..."
+
+#: src/ui_parts/update_menu.gd
+msgid "Update check failed"
+msgstr "Uuenduste otsimine ebaõnnestus"
+
+#: src/ui_parts/update_menu.gd
+msgid "GodSVG is up-to-date."
+msgstr "GodSVG on ajakohane."
+
+#: src/ui_parts/update_menu.gd
+msgid "New versions"
+msgstr "Uued versioonid"
+
+#: src/ui_parts/zoom_menu.gd src/utils/TranslationUtils.gd
+msgid "Zoom out"
+msgstr "Vähenda"
+
+#: src/ui_parts/zoom_menu.gd src/utils/TranslationUtils.gd
+msgid "Zoom in"
+msgstr "Suurenda"
+
+#: src/ui_parts/zoom_menu.gd src/utils/TranslationUtils.gd
+msgid "Zoom reset"
+msgstr "Taasta suurendus"
+
+#: src/ui_widgets/BetterLineEdit.gd src/ui_widgets/BetterTextEdit.gd
+#: src/utils/TranslationUtils.gd
+msgid "Undo"
+msgstr "Võta tagasi"
+
+#: src/ui_widgets/BetterLineEdit.gd src/ui_widgets/BetterTextEdit.gd
+#: src/utils/TranslationUtils.gd
+msgid "Redo"
+msgstr "Tee uuesti"
+
+#: src/ui_widgets/BetterLineEdit.gd src/ui_widgets/BetterTextEdit.gd
+msgid "Cut"
+msgstr "Lõika"
+
+#: src/ui_widgets/BetterLineEdit.gd src/ui_widgets/BetterTextEdit.gd
+msgid "Copy"
+msgstr "Kopeeri"
+
+#: src/ui_widgets/BetterLineEdit.gd src/ui_widgets/BetterTextEdit.gd
+msgid "Paste"
+msgstr "Kleebi"
+
+#: src/ui_widgets/choose_name_dialog.gd
+msgid "Create"
+msgstr "Loo"
+
+#: src/ui_widgets/color_popup.gd
+msgid "Search color"
+msgstr "Otsi värvi"
+
+#: src/ui_widgets/color_popup.gd
+msgid "Color Picker"
+msgstr "Värvivalija"
+
+#: src/ui_widgets/configure_color_popup.gd
+msgid "Edit color name"
+msgstr "Muuda värvi nime"
+
+#: src/ui_widgets/configure_color_popup.gd
+msgid "Delete color"
+msgstr "Kustuta värv"
+
+#: src/ui_widgets/configure_color_popup.gd src/ui_widgets/palette_config.gd
+msgid "Unnamed"
+msgstr "Nimetu"
+
+#: src/ui_widgets/good_color_picker.gd
+msgid "Color keywords"
+msgstr "Värvi võtmesõnad"
+
+#: src/ui_widgets/good_color_picker.gd
+msgid "Eyedropper"
+msgstr "Värvipipett"
+
+#: src/ui_widgets/palette_config.gd
+msgid "Unnamed palettes won't be shown."
+msgstr "Nimetuid värvipalette ei kuvata."
+
+#: src/ui_widgets/palette_config.gd
+msgid "Multiple palettes can't have the same name."
+msgstr "Mitmel värvipaletil ei saa olla sama nimi."
+
+#: src/ui_widgets/palette_config.gd
+msgid "This palette has identically defined colors."
+msgstr "Selles värvipalettis on sama värv juba olemas."
+
+#: src/ui_widgets/palette_config.gd
+msgid "Rename"
+msgstr "Nimeta ümber"
+
+#: src/ui_widgets/palette_config.gd
+msgid "Apply Preset"
+msgstr "Rakenda eelseadistus"
+
+#: src/ui_widgets/palette_config.gd
+msgid "Copy as XML"
+msgstr "Kopeeri kui XML"
+
+#: src/ui_widgets/palette_config.gd
+msgid "Save as XML"
+msgstr "Salvesta kui XML"
+
+#: src/ui_widgets/path_popup.gd src/ui_widgets/pathdata_field.gd
+#: src/utils/TranslationUtils.gd
+msgid "Relative"
+msgstr "Suhteline"
+
+#: src/ui_widgets/pathdata_field.gd
+msgid "No path data"
+msgstr "Puudub path andmed"
+
+#: src/ui_widgets/pathdata_field.gd src/utils/TranslationUtils.gd
+msgid "Absolute"
+msgstr "Absoluutne"
+
+#: src/ui_widgets/points_field.gd
+msgid "No points"
+msgstr "Puuduvad punktid"
+
+#: src/ui_widgets/presented_shortcut.gd src/ui_widgets/setting_shortcut.gd
+msgid "Also used by"
+msgstr "Seda kasutab ka"
+
+#: src/ui_widgets/setting_frame.gd src/ui_widgets/setting_shortcut.gd
+msgid "Reset to default"
+msgstr "Lähtesta vaikeväärtusele"
+
+#: src/ui_widgets/setting_shortcut.gd
+msgid "Unused"
+msgstr "Kasutamata"
+
+#: src/ui_widgets/setting_shortcut.gd
+msgid "Add shortcut"
+msgstr "Lisa otseklahv"
+
+#: src/ui_widgets/setting_shortcut.gd
+msgid "Press keys…"
+msgstr "Vajuta klahve…"
+
+#: src/ui_widgets/transform_field.gd
+msgid "No transforms"
+msgstr "Puudub teisendus"
+
+#: src/ui_widgets/transform_popup.gd
+msgid "Apply the matrix"
+msgstr "Rakenda maatriks"
+
+#: src/ui_widgets/transform_popup.gd
+msgid "Insert Before"
+msgstr "Sisesta enne"
+
+#: src/ui_widgets/transform_popup.gd
+msgid "New transform"
+msgstr "Uus teisendus"
+
+#: src/ui_widgets/unrecognized_field.gd
+msgid "GodSVG doesn’t recognize this attribute"
+msgstr "GodSVG ei tunne seda atribuuti"
+
+#: src/utils/FileUtils.gd
+msgid "Save the .\"{format}\" file"
+msgstr "Salvesta .\"{format}\" fail"
+
+#: src/utils/FileUtils.gd
+msgid "Load an image file"
+msgstr "Ava pildifail"
+
+#: src/utils/FileUtils.gd
+msgid "Import a {extension} file"
+msgstr "Impordi {extension} fail"
+
+#: src/utils/FileUtils.gd
+msgid "The file couldn't be opened."
+msgstr "Faili avamine ebaõnnestus."
+
+#: src/utils/FileUtils.gd
+msgid "Check if the file still exists in the selected file path."
+msgstr "Kontrollige, kas fail asub veel valitud asukohas."
+
+#: src/utils/FileUtils.gd
+msgid "Save the file?"
+msgstr "Salvesta fail?"
+
+#: src/utils/FileUtils.gd
+msgid "Do you want to save this file?"
+msgstr "Kas te soovite salvestada selle faili?"
+
+#: src/utils/FileUtils.gd
+msgid "Save the changes?"
+msgstr "Salvesta muudatused?"
+
+#: src/utils/FileUtils.gd
+msgid "Your changes will be lost if you don't save them."
+msgstr "Muudatused lähevad kaotsi, kui need ei salvestata."
+
+#: src/utils/FileUtils.gd
+msgid "Don't save"
+msgstr "Ära salvesta"
+
+#: src/utils/FileUtils.gd
+msgid "The imported file is already being edited inside GodSVG."
+msgstr "Imporditud fail on juba avatud GodSVGs."
+
+#: src/utils/FileUtils.gd
+msgid "If you want to revert your edits since the last save, use {reset_svg}."
+msgstr ""
+"Kui te soovite taastada muudatused alates eelmise salvestamise hetkest, "
+"kasutage {reset_svg}-d."
+
+#: src/utils/FileUtils.gd
+msgid "Do you want to save the changes made to {file_name}?"
+msgstr "Kas soovite salvestada {file_name} kallal tehtud muudatused?"
+
+#: src/utils/TranslationUtils.gd
+msgid "Save as"
+msgstr "Salvesta kui"
+
+#: src/utils/TranslationUtils.gd
+msgid "Close tabs to the left"
+msgstr "Sulge kaardid vasakul"
+
+#: src/utils/TranslationUtils.gd
+msgid "Close tabs to the right"
+msgstr "Sulge kaardid paremal"
+
+#: src/utils/TranslationUtils.gd
+msgid "Close all other tabs"
+msgstr "Sulge teised kaardid"
+
+#: src/utils/TranslationUtils.gd
+msgid "Select the next tab"
+msgstr "Vali järgmine kaart"
+
+#: src/utils/TranslationUtils.gd
+msgid "Select the previous tab"
+msgstr "Vali eelmine kaart"
+
+#: src/utils/TranslationUtils.gd
+msgid "Open SVG externally"
+msgstr "Ava SVG väliselt"
+
+#: src/utils/TranslationUtils.gd
+msgid "Show SVG in File Manager"
+msgstr "Kuva SVG failihalduris"
+
+#: src/utils/TranslationUtils.gd
+msgid "Select all"
+msgstr "Vali kõik"
+
+#: src/utils/TranslationUtils.gd
+msgid "Duplicate the selection"
+msgstr "Dubleeri valik"
+
+#: src/utils/TranslationUtils.gd
+msgid "Delete the selection"
+msgstr "Kustuta valik"
+
+#: src/utils/TranslationUtils.gd
+msgid "Move the selection up"
+msgstr "Liiguta valik üles"
+
+#: src/utils/TranslationUtils.gd
+msgid "Move the selection down"
+msgstr "Liiguta valik alla"
+
+#: src/utils/TranslationUtils.gd
+msgid "Find"
+msgstr "Leia"
+
+#: src/utils/TranslationUtils.gd
+msgid "Show rasterized SVG"
+msgstr "Kuva rasterdatud SVG"
+
+#: src/utils/TranslationUtils.gd
+msgid "Toggle snapping"
+msgstr "Lülita joondamine sisse"
+
+#: src/utils/TranslationUtils.gd
+msgid "Show reference image"
+msgstr "Kuva võrdluspilt"
+
+#: src/utils/TranslationUtils.gd
+msgid "Overlay reference image"
+msgstr "Kata võrdluspildiga üle"
+
+#: src/utils/TranslationUtils.gd
+msgid "View debug information"
+msgstr "Kuva silumisinformatsioon"
+
+#: src/utils/TranslationUtils.gd
+msgid "Open Settings menu"
+msgstr "Ava seadete menüü"
+
+#: src/utils/TranslationUtils.gd
+msgid "Open About menu"
+msgstr "Ava teabe menüü"
+
+#: src/utils/TranslationUtils.gd
+msgid "Open Donate menu"
+msgstr "Ava annetuste menüü"
+
+#: src/utils/TranslationUtils.gd
+msgid "Open GodSVG repository"
+msgstr "Ava GodSVG repositoorium"
+
+#: src/utils/TranslationUtils.gd
+msgid "Open GodSVG website"
+msgstr "Ava GodSVG veebileht"
+
+#: src/utils/TranslationUtils.gd
+msgid "Quit the application"
+msgstr "Sulge rakendus"
+
+#: src/utils/TranslationUtils.gd
+msgid "Move to"
+msgstr "Liigu"
+
+#: src/utils/TranslationUtils.gd
+msgid "Line to"
+msgstr "Joon"
+
+#: src/utils/TranslationUtils.gd
+msgid "Horizontal Line to"
+msgstr "Horisontaalne joon"
+
+#: src/utils/TranslationUtils.gd
+msgid "Vertical Line to"
+msgstr "Vertikaalne joon"
+
+#: src/utils/TranslationUtils.gd
+msgid "Close Path"
+msgstr "Sulge path"
+
+#: src/utils/TranslationUtils.gd
+msgid "Elliptical Arc to"
+msgstr "Elliptiline kaar"
+
+#: src/utils/TranslationUtils.gd
+msgid "Quadratic Bezier to"
+msgstr "Kvadratiline Bezier"
+
+#: src/utils/TranslationUtils.gd
+msgid "Shorthand Quadratic Bezier to"
+msgstr "Lihtne kvadratiline Bezier"
+
+#: src/utils/TranslationUtils.gd
+msgid "Cubic Bezier to"
+msgstr "Kuubiline Bezier"
+
+#: src/utils/TranslationUtils.gd
+msgid "Shorthand Cubic Bezier to"
+msgstr "Lihtne kuubiline Bezier"
+
+#: src/utils/TranslationUtils.gd
+msgid ""
+"The file extension {extension} is unsupported for this operation. Only "
+"{extension_list} files are supported."
+msgstr ""
+"Faililaiend {extension} ei ole selle operatsiooni jaoks toetatud. Toetatud "
+"on {extension_list} failid."
+
+#: src/utils/TranslationUtils.gd
+msgid "The file extension is empty. Only {extension_list} files are supported."
+msgstr "Faililaiend on tühi. Ainult {extension_list} on toetatud."
diff --git a/assets/translations/fr.po b/translations/fr.po
similarity index 81%
rename from assets/translations/fr.po
rename to translations/fr.po
index d0fa126..4cc726b 100644
--- a/assets/translations/fr.po
+++ b/translations/fr.po
@@ -32,16 +32,24 @@ msgid "Check for updates?"
msgstr "Vérifier les mises à jour ?"
#: src/autoload/HandlerGUI.gd
-msgid "This requires GodSVG to connect to the internet."
-msgstr "Ceci nécessite que GodSVG se connecte à Internet."
+msgid ""
+"This will connect to github.com to compare version numbers. No other data is "
+"collected or transmitted."
+msgstr ""
+"Cette action effectuera une connexion à github.com pour comparer les numéros "
+"de version. Aucune autre information ne sera transmise ou collectée."
#: src/autoload/HandlerGUI.gd src/ui_widgets/alert_dialog.gd
msgid "OK"
msgstr "OK"
+#: src/autoload/HandlerGUI.gd
+msgid "Do you want to proceed?"
+msgstr "Voulez-vous procéder ?"
+
#: src/autoload/HandlerGUI.gd
msgid "Export SVG"
-msgstr "Exporter en SVG"
+msgstr "Exporter le SVG"
#: src/autoload/HandlerGUI.gd src/ui_parts/export_menu.gd
#: src/ui_parts/global_actions.gd src/utils/TranslationUtils.gd
@@ -50,11 +58,17 @@ msgstr "Exporter"
#: src/autoload/HandlerGUI.gd
msgid ""
-"The graphic can only be exported as SVG because its size is not defined. Do "
-"you want to proceed?"
+"The graphic can be exported only as SVG because its proportions are too "
+"extreme."
msgstr ""
-"Le graphique ne peut être exporté qu'en SVG car sa taille n'est pas définie. "
-"Voulez-vous continuer?"
+"Le graphique ne peut être exporté qu'en SVG car ses proportions sont trop "
+"extrêmes."
+
+#: src/autoload/HandlerGUI.gd
+msgid ""
+"The graphic can be exported only as SVG because its size is not defined."
+msgstr ""
+"Le graphique ne peut être exporté qu'en SVG car sa taille n'est pas définie."
#: src/autoload/State.gd src/ui_parts/good_file_dialog.gd
#: src/ui_widgets/alert_dialog.gd
@@ -62,13 +76,12 @@ 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"
+msgstr "Fermer l'onglet"
#: src/autoload/State.gd
msgid "Restore"
-msgstr ""
+msgstr "Restaurer"
#: src/autoload/State.gd
msgid "View in List"
@@ -101,13 +114,15 @@ msgstr "Insérer après"
#: src/autoload/State.gd
msgid "The last edited state of this tab could not be found."
-msgstr ""
+msgstr "Le dernier état modifié de cet onglet n'a pas pu être trouvé."
#: src/autoload/State.gd
msgid ""
-"The tab is bound to the file path {file_path}. Do you want to restore from "
-"this path?"
+"The tab is bound to the file path {file_path}. Do you want to restore the "
+"SVG from this path?"
msgstr ""
+"L'onglet est lié au chemin d'accès {file_path}. Voulez-vous restaurer le"
+"SVG à partir de ce chemin ?"
#: src/config_classes/Formatter.gd
msgid "Compact"
@@ -123,7 +138,7 @@ msgstr "Toujours"
#: src/config_classes/Formatter.gd
msgid "All except containers"
-msgstr "Tout sauf les conteneurs"
+msgstr "Pour tout sauf les conteneurs"
#: src/config_classes/Formatter.gd
msgid "Never"
@@ -145,9 +160,13 @@ 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 "Empty"
+msgstr "Vide"
+
#: src/config_classes/TabData.gd
msgid "Unsaved"
-msgstr ""
+msgstr "Non enregistré"
#: src/data_classes/BasicXNode.gd
msgid "Comment"
@@ -195,6 +214,22 @@ msgstr "Formatage incorrect."
msgid "Close"
msgstr "Fermer"
+#: src/ui_parts/about_menu.gd
+msgid "Authors"
+msgstr "Auteurs"
+
+#: src/ui_parts/about_menu.gd
+msgid "Donors"
+msgstr "Donateurs"
+
+#: src/ui_parts/about_menu.gd
+msgid "License"
+msgstr "Licence"
+
+#: src/ui_parts/about_menu.gd
+msgid "Third-party licenses"
+msgstr "Licences tierces"
+
#: src/ui_parts/about_menu.gd
msgid "Project Founder and Manager"
msgstr "Fondateur et gestionnaire du projet"
@@ -207,10 +242,6 @@ msgstr "Développeurs"
msgid "Translators"
msgstr "Traducteurs"
-#: src/ui_parts/about_menu.gd
-msgid "Donors"
-msgstr "Donateurs"
-
#: src/ui_parts/about_menu.gd
msgid "Golden donors"
msgstr "Donateurs Or"
@@ -219,25 +250,33 @@ msgstr "Donateurs Or"
msgid "Diamond donors"
msgstr "Donateurs Diamant"
-#: src/ui_parts/about_menu.gd
-msgid "Authors"
-msgstr "Auteurs"
+#: src/ui_parts/code_editor.gd src/utils/TranslationUtils.gd
+msgid "Copy all text"
+msgstr "Copier tout le texte"
-#: src/ui_parts/about_menu.gd
-msgid "License"
-msgstr "Licence"
+#: src/ui_parts/current_file_button.gd src/ui_parts/good_file_dialog.gd
+msgid "Save SVG"
+msgstr "Sauvegarder le SVG"
-#: src/ui_parts/about_menu.gd
-msgid "Godot third-party components"
-msgstr "Composants Godot tiers"
+#: src/ui_parts/current_file_button.gd
+msgid "Save SVG as…"
+msgstr "Sauvegarder le SVG sous…"
-#: src/ui_parts/about_menu.gd
-msgid "Third-party licenses"
-msgstr "Licences tierces"
+#: src/ui_parts/current_file_button.gd src/utils/TranslationUtils.gd
+msgid "Reset SVG"
+msgstr "Rénitialiser le SVG"
+
+#: src/ui_parts/current_file_button.gd src/ui_parts/tab_bar.gd
+msgid "Open externally"
+msgstr "Ouvrir externement"
+
+#: src/ui_parts/current_file_button.gd src/ui_parts/tab_bar.gd
+msgid "Show in File Manager"
+msgstr "Afficher dans l'explorateur de fichiers"
#: src/ui_parts/display.gd
msgid "Visuals"
-msgstr "Visuels"
+msgstr "Vue"
#: src/ui_parts/display.gd src/utils/TranslationUtils.gd
msgid "Load reference image"
@@ -269,7 +308,7 @@ msgstr "Taille d'aimantation"
#: 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
+#: src/ui_widgets/confirm_dialog.gd src/ui_widgets/options_dialog.gd
msgid "Cancel"
msgstr "Annuler"
@@ -330,22 +369,6 @@ msgstr "Paramètres"
msgid "Optimize"
msgstr "Optimiser"
-#: src/ui_parts/global_actions.gd src/ui_parts/good_file_dialog.gd
-msgid "Save SVG"
-msgstr "Sauvegarder le SVG"
-
-#: 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…"
@@ -402,7 +425,8 @@ msgstr "Sélectionner une image"
msgid "Select"
msgstr "Sélectionner"
-#: src/ui_parts/good_file_dialog.gd src/utils/TranslationUtils.gd
+#: src/ui_parts/good_file_dialog.gd src/utils/FileUtils.gd
+#: src/utils/TranslationUtils.gd
msgid "Save"
msgstr "Enregistrer"
@@ -480,7 +504,7 @@ msgstr "Édition"
#: src/ui_parts/mac_menu.gd src/ui_parts/settings_menu.gd
msgid "Tool"
-msgstr "Outil"
+msgstr "Outils"
#: src/ui_parts/mac_menu.gd src/ui_parts/settings_menu.gd
msgid "View"
@@ -595,9 +619,8 @@ msgid "Invert zoom direction"
msgstr "Inverser la direction du zoom"
#: src/ui_parts/settings_menu.gd
-msgid "Wrap mouse"
-msgstr ""
-"Déplacer la souris d'un bout à l'autre de l'écran lorsque le bord est atteint"
+msgid "Wrap-around panning"
+msgstr "Défilement bouclé"
#: src/ui_parts/settings_menu.gd
msgid "Use CTRL for zooming"
@@ -613,7 +636,7 @@ msgstr "Utiliser la boîte de dialogue de fichiers native"
#: src/ui_parts/settings_menu.gd
msgid "Sync window title to file name"
-msgstr "Synchroniser le titre de la fenêtre au titre du fichier"
+msgstr "Synchroniser le titre de la fenêtre au nom du fichier"
#: src/ui_parts/settings_menu.gd
msgid "Handle size"
@@ -624,8 +647,8 @@ msgid "UI scale"
msgstr "Échelle de l'interface"
#: src/ui_parts/settings_menu.gd
-msgid "Auto UI scale"
-msgstr "Échelle de l'interface automatique"
+msgid "Changes the scale factor for the interface."
+msgstr "Change le facteur d'échelle de l'interface."
#: src/ui_parts/settings_menu.gd
msgid "Language"
@@ -635,7 +658,7 @@ msgstr "Langue"
msgid "Import XML"
msgstr "Importer un fichier XML"
-#: src/ui_parts/settings_menu.gd src/ui_widgets/palette_config.gd
+#: src/ui_parts/settings_menu.gd
msgid "Paste XML"
msgstr "Coller du XML"
@@ -653,7 +676,7 @@ msgstr "Formateur de l'éditeur"
#: src/ui_parts/settings_menu.gd
msgid "Export formatter"
-msgstr "Exporter le formateur"
+msgstr "Formateur d'exportation"
#: src/ui_parts/settings_menu.gd
msgid "Help"
@@ -725,11 +748,11 @@ msgstr "Syntaxe primaire"
#: src/ui_parts/settings_menu.gd
msgid "Capitalize hexadecimal letters"
-msgstr "Mettre en majuscule les lettres des nombres héxadécimaux"
+msgstr "Mettre en majuscule les nombres héxadécimaux"
#: src/ui_parts/settings_menu.gd
msgid "Pathdata"
-msgstr "Données de chemin"
+msgstr "Données de chemin d'accès"
#: src/ui_parts/settings_menu.gd
msgid "Compress numbers"
@@ -756,14 +779,17 @@ msgid "Remove unnecessary parameters"
msgstr "Retirer les paramètres inutiles"
#: src/ui_parts/settings_menu.gd
-msgid "Swaps zoom in and zoom out with the mouse wheel."
-msgstr "Échanger le zoom avant et le zoom arrière par molette de souris."
+msgid "Swaps the scroll directions for zooming in and zooming out."
+msgstr ""
+"Inverser les directions de défilement pour le zoom avant et le zoom arrière."
#: src/ui_parts/settings_menu.gd
-msgid "Wraps the mouse cursor around when panning the viewport."
+msgid ""
+"Warps the cursor to the opposite side whenever it reaches a viewport "
+"boundary while panning."
msgstr ""
-"Déplace le pointeur de souris d'un bout à l'autre de l'écran lorsque le bord "
-"est atteint lors d'un balayage."
+"Téléporte le curseur de l'autre côté de la vue à chaque fois qu'il en atteind "
+"la limite."
#: src/ui_parts/settings_menu.gd
msgid ""
@@ -787,73 +813,55 @@ msgid ""
"If turned off, the window title will remain simply \"GodSVG\" regardless of "
"the current file."
msgstr ""
-"Si désactivé, le titre de la fenêtre restera simplement \"GodSVG\" quel que "
+"Si désactivé, le titre de la fenêtre restera simplement « GodSVG » quel que "
"soit le fichier actuel."
#: src/ui_parts/settings_menu.gd
msgid "Changes the visual size and grabbing area of handles."
msgstr "Change la taille visuelle et la zone de saisie des poignées"
-#: src/ui_parts/settings_menu.gd
-msgid "Changes the scale of the visual user interface."
-msgstr "Change l'échelle de l'interface utilisateur visuelle."
+#: src/ui_parts/shortcut_panel.gd
+msgid "Horizontal strip"
+msgstr "Rayure horizontale"
-#: src/ui_parts/settings_menu.gd
-msgid "Scales the user interface based on the screen size."
-msgstr "Redimensionne l'interface utilisateur selon la taille de l'écran."
+#: src/ui_parts/shortcut_panel.gd
+msgid "Vertical strip"
+msgstr "Rayure verticale"
+
+#: src/ui_parts/shortcut_panel.gd
+msgid "Horizontal with two rows"
+msgstr "Horizontal avec deux rangées"
#: src/ui_parts/shortcut_panel_config.gd
msgid "Configure Shortcut Panel"
-msgstr ""
+msgstr "Configurer le panneau de raccourcis"
#: src/ui_parts/shortcut_panel_config.gd
msgid "Layout"
-msgstr ""
-
-#: src/ui_parts/shortcut_panel_config.gd
-#, fuzzy
-msgid "Horizontal strip"
-msgstr "Ligne horizontale vers"
-
-#: src/ui_parts/shortcut_panel_config.gd
-#, fuzzy
-msgid "Horizontal with two rows"
-msgstr "Ligne horizontale vers"
-
-#: src/ui_parts/shortcut_panel_config.gd
-#, fuzzy
-msgid "Vertical strip"
-msgstr "Ligne verticale vers"
+msgstr "Disposition"
#: src/ui_parts/tab_bar.gd
-#, fuzzy
msgid "Create tab"
-msgstr "Créer"
+msgstr "Créer un onglet"
-#: src/ui_parts/tab_bar.gd
-#, fuzzy
-msgid "Close all other tabs"
-msgstr "Copier tout le texte"
+#: src/ui_parts/tab_bar.gd src/utils/TranslationUtils.gd
+msgid "Create a new tab"
+msgstr "Créer un nouvel onglet"
#: src/ui_parts/tab_bar.gd
-msgid "Close tabs to the left"
-msgstr ""
+msgid "Scroll backwards"
+msgstr "Défiler en arrière"
#: 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"
+msgid "Scroll forwards"
+msgstr "Défiler en avant"
#: src/ui_parts/tab_bar.gd
-msgid "This SVG is not bound to a location on the computer yet."
-msgstr ""
+msgid "This SVG is not bound to a file location yet."
+msgstr "Ce SVG n'est pas encore lié à un emplacement de fichier."
#: src/ui_parts/update_menu.gd
-msgid "Include prereleases"
+msgid "Show prereleases"
msgstr "Inclure les versions préliminaires"
#: src/ui_parts/update_menu.gd
@@ -866,7 +874,7 @@ msgstr "Version actuelle"
#: src/ui_parts/update_menu.gd
msgid "Retrieving information..."
-msgstr "Récupération d'information…"
+msgstr "Récupération d'information en cours…"
#: src/ui_parts/update_menu.gd
msgid "Update check failed"
@@ -890,7 +898,7 @@ msgstr "Zoom avant"
#: src/ui_parts/zoom_menu.gd src/utils/TranslationUtils.gd
msgid "Zoom reset"
-msgstr "Rénitialisation de zoom"
+msgstr "Rénitialiser le zoom"
#: src/ui_widgets/BetterLineEdit.gd src/ui_widgets/BetterTextEdit.gd
#: src/utils/TranslationUtils.gd
@@ -918,10 +926,6 @@ msgstr "Coller"
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"
@@ -943,17 +947,16 @@ msgid "Unnamed"
msgstr "Sans nom"
#: src/ui_widgets/good_color_picker.gd
-#, fuzzy
msgid "Color keywords"
-msgstr "Pipette"
+msgstr "Colorier les mots-clés"
#: src/ui_widgets/good_color_picker.gd
msgid "Eyedropper"
-msgstr ""
+msgstr "Pipette"
#: src/ui_widgets/palette_config.gd
msgid "Unnamed palettes won't be shown."
-msgstr "Les palettes sans nom ne seront pas montrées."
+msgstr "Les palettes sans nom ne seront pas affichées."
#: src/ui_widgets/palette_config.gd
msgid "Multiple palettes can't have the same name."
@@ -967,13 +970,17 @@ msgstr "Cette palette a des couleurs définies identiquement."
msgid "Rename"
msgstr "Renommer"
+#: src/ui_widgets/palette_config.gd
+msgid "Apply Preset"
+msgstr "Appliquer un préréglage"
+
#: src/ui_widgets/palette_config.gd
msgid "Copy as XML"
msgstr "Copier en tant que XML"
#: src/ui_widgets/palette_config.gd
-msgid "Apply Preset"
-msgstr "Appliquer le préréglage"
+msgid "Save as XML"
+msgstr "Enregistrer en tant que XML"
#: src/ui_widgets/path_popup.gd src/ui_widgets/pathdata_field.gd
#: src/utils/TranslationUtils.gd
@@ -1052,52 +1059,89 @@ 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 "Save the file?"
+msgstr "Enregistrer le fichier ?"
+
+#: src/utils/FileUtils.gd
+msgid "Do you want to save this file?"
+msgstr "Voulez-vous enregistrer ce fichier ?"
+
+#: src/utils/FileUtils.gd
+msgid "Save the changes?"
+msgstr "Enregistrer les changements?"
+
+#: src/utils/FileUtils.gd
+msgid "Your changes will be lost if you don't save them."
+msgstr "Vos changements seront perdus si vous ne les enregistrés pas."
+
+#: src/utils/FileUtils.gd
+msgid "Don't save"
+msgstr "Ne pas enregistrer"
+
#: src/utils/FileUtils.gd
msgid "The imported file is already being edited inside GodSVG."
-msgstr ""
+msgstr "Le fichier importé est déjà en cours de modification dans GodSVG."
#: src/utils/FileUtils.gd
-msgid "If you want to apply the unsaved file state, use \"Reset SVG\" instead."
+msgid "If you want to revert your edits since the last save, use {reset_svg}."
msgstr ""
+"Si vous voulez rénitialiser vos changements au dernier enregistrement, "
+"utilisez {reset_svg}."
+
+#: src/utils/FileUtils.gd
+msgid "Do you want to save the changes made to {file_name}?"
+msgstr "Voulez-vous enregistrer les modifications de {file_name} ?"
+
+#: src/utils/TranslationUtils.gd
+msgid "Save as"
+msgstr "Enregistrer sous"
+
+#: src/utils/TranslationUtils.gd
+msgid "Close tabs to the left"
+msgstr "Fermer les onglets sur la gauche"
+
+#: src/utils/TranslationUtils.gd
+msgid "Close tabs to the right"
+msgstr "Fermer les onglets sur la droite"
+
+#: src/utils/TranslationUtils.gd
+msgid "Close all other tabs"
+msgstr "Fermer les autres onglets"
#: src/utils/TranslationUtils.gd
msgid "Select the next tab"
-msgstr ""
+msgstr "Sélectionner l'onglet suivant"
#: src/utils/TranslationUtils.gd
msgid "Select the previous tab"
-msgstr ""
+msgstr "Sélectionner l'onglet précédent"
#: src/utils/TranslationUtils.gd
-#, fuzzy
msgid "Open SVG externally"
-msgstr "Ouvrir le dépôt de GodSVG"
+msgstr "Ouvrir le SVG externement"
#: src/utils/TranslationUtils.gd
msgid "Show SVG in File Manager"
-msgstr ""
+msgstr "Montrer le SVG dans l'explorateur de fichiers"
#: src/utils/TranslationUtils.gd
-#, fuzzy
msgid "Select all"
-msgstr "Sélectionner"
+msgstr "Tout sélectionner"
#: src/utils/TranslationUtils.gd
-#, fuzzy
msgid "Duplicate the selection"
-msgstr "Supprimer les éléments sélectionnés"
+msgstr "Dupliquer 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
-#, fuzzy
msgid "Move the selection up"
msgstr "Remonter les éléments sélectionnés"
#: src/utils/TranslationUtils.gd
-#, fuzzy
msgid "Move the selection down"
msgstr "Descendre les éléments sélectionnés"
@@ -1115,7 +1159,7 @@ msgstr "Activer/Désactiver l'aimantation"
#: src/utils/TranslationUtils.gd
msgid "Show reference image"
-msgstr "Montrer l'image de référence"
+msgstr "Afficher l'image de référence"
#: src/utils/TranslationUtils.gd
msgid "Overlay reference image"
@@ -1131,11 +1175,11 @@ msgstr "Ouvrir le menu des paramètres"
#: src/utils/TranslationUtils.gd
msgid "Open About menu"
-msgstr "Ouvrir le menu À propos"
+msgstr "Ouvrir le menu « À propos »"
#: src/utils/TranslationUtils.gd
msgid "Open Donate menu"
-msgstr "Ouvrir le menu Faire un don"
+msgstr "Ouvrir le menu « Faire un don »"
#: src/utils/TranslationUtils.gd
msgid "Open GodSVG repository"
@@ -1203,6 +1247,29 @@ msgstr ""
"L'extension de fichier est vide. Seuls les fichiers {extension_list} sont "
"supportés."
+#~ msgid "Auto UI scale"
+#~ msgstr "Échelle de l'interface automatique"
+
+#, fuzzy
+#~ msgid "Scales the interface automatically based on the screen size."
+#~ msgstr "Redimensionne l'interface utilisateur selon la taille de l'écran."
+
+#~ msgid "Wrap mouse"
+#~ msgstr ""
+#~ "Déplacer la souris d'un bout à l'autre de l'écran lorsque le bord est "
+#~ "atteint"
+
+#~ msgid "Swaps zoom in and zoom out with the mouse wheel."
+#~ msgstr "Échanger le zoom avant et le zoom arrière par molette de souris."
+
+#~ msgid "Wraps the mouse cursor around when panning the viewport."
+#~ msgstr ""
+#~ "Déplace le pointeur de souris d'un bout à l'autre de l'écran lorsque le "
+#~ "bord est atteint lors d'un balayage."
+
+#~ msgid "This requires GodSVG to connect to the internet."
+#~ msgstr "Ceci nécessite que GodSVG se connecte à Internet."
+
#~ msgid "Open file"
#~ msgstr "Ouvrir le fichier"
diff --git a/assets/translations/nl.po b/translations/nl.po
similarity index 89%
rename from assets/translations/nl.po
rename to translations/nl.po
index f83b507..4512e12 100644
--- a/assets/translations/nl.po
+++ b/translations/nl.po
@@ -32,13 +32,20 @@ msgid "Check for updates?"
msgstr "Voor updates controleren?"
#: src/autoload/HandlerGUI.gd
-msgid "This requires GodSVG to connect to the internet."
-msgstr "Hiervoor moet GodSVG verbinding maken met het internet."
+msgid ""
+"This will connect to github.com to compare version numbers. No other data is "
+"collected or transmitted."
+msgstr ""
#: src/autoload/HandlerGUI.gd src/ui_widgets/alert_dialog.gd
msgid "OK"
msgstr "OK"
+#: src/autoload/HandlerGUI.gd
+#, fuzzy
+msgid "Do you want to proceed?"
+msgstr "Wil je GodSVG afsluiten?"
+
#: src/autoload/HandlerGUI.gd
msgid "Export SVG"
msgstr "Exporteer SVG"
@@ -49,9 +56,18 @@ msgid "Export"
msgstr "Exporteren"
#: src/autoload/HandlerGUI.gd
+#, fuzzy
+msgid ""
+"The graphic can be exported only as SVG because its proportions are too "
+"extreme."
+msgstr ""
+"Het beeld kan alleen maar als SVG geëxporteerd worden want zijn maat is niet "
+"gedefiniëerd. Wil je verder gaan?"
+
+#: src/autoload/HandlerGUI.gd
+#, fuzzy
msgid ""
-"The graphic can only be exported as SVG because its size is not defined. Do "
-"you want to proceed?"
+"The graphic can be exported only as SVG because its size is not defined."
msgstr ""
"Het beeld kan alleen maar als SVG geëxporteerd worden want zijn maat is niet "
"gedefiniëerd. Wil je verder gaan?"
@@ -105,8 +121,8 @@ msgstr ""
#: src/autoload/State.gd
msgid ""
-"The tab is bound to the file path {file_path}. Do you want to restore from "
-"this path?"
+"The tab is bound to the file path {file_path}. Do you want to restore the "
+"SVG from this path?"
msgstr ""
#: src/config_classes/Formatter.gd
@@ -145,6 +161,10 @@ msgstr "3-cijferig of 6-cijferige hex"
msgid "6-digit hex"
msgstr "6-cijferige hex"
+#: src/config_classes/TabData.gd
+msgid "Empty"
+msgstr ""
+
#: src/config_classes/TabData.gd
msgid "Unsaved"
msgstr ""
@@ -195,6 +215,22 @@ msgstr "Onjuiste nesting."
msgid "Close"
msgstr "Afluiten"
+#: src/ui_parts/about_menu.gd
+msgid "Authors"
+msgstr "Auteurs"
+
+#: src/ui_parts/about_menu.gd
+msgid "Donors"
+msgstr "Donateurs"
+
+#: src/ui_parts/about_menu.gd
+msgid "License"
+msgstr "Licentie"
+
+#: src/ui_parts/about_menu.gd
+msgid "Third-party licenses"
+msgstr "Derde partij Licenties"
+
#: src/ui_parts/about_menu.gd
msgid "Project Founder and Manager"
msgstr "Projectoprichter en Manager"
@@ -207,10 +243,6 @@ msgstr "Ontwikkelaars"
msgid "Translators"
msgstr "Vertalers"
-#: src/ui_parts/about_menu.gd
-msgid "Donors"
-msgstr "Donateurs"
-
#: src/ui_parts/about_menu.gd
msgid "Golden donors"
msgstr "Gouden Donatuers"
@@ -219,21 +251,30 @@ msgstr "Gouden Donatuers"
msgid "Diamond donors"
msgstr "Diamanten Donateurs"
-#: src/ui_parts/about_menu.gd
-msgid "Authors"
-msgstr "Auteurs"
+#: src/ui_parts/code_editor.gd src/utils/TranslationUtils.gd
+msgid "Copy all text"
+msgstr "Kopiëer alle tekst"
-#: src/ui_parts/about_menu.gd
-msgid "License"
-msgstr "Licentie"
+#: src/ui_parts/current_file_button.gd src/ui_parts/good_file_dialog.gd
+msgid "Save SVG"
+msgstr "SVG opslaan"
-#: src/ui_parts/about_menu.gd
-msgid "Godot third-party components"
-msgstr "Godot derde partij componenten"
+#: src/ui_parts/current_file_button.gd
+#, fuzzy
+msgid "Save SVG as…"
+msgstr "SVG opslaan"
-#: src/ui_parts/about_menu.gd
-msgid "Third-party licenses"
-msgstr "Derde partij Licenties"
+#: src/ui_parts/current_file_button.gd src/utils/TranslationUtils.gd
+msgid "Reset SVG"
+msgstr "SVG Resetten"
+
+#: src/ui_parts/current_file_button.gd src/ui_parts/tab_bar.gd
+msgid "Open externally"
+msgstr ""
+
+#: src/ui_parts/current_file_button.gd src/ui_parts/tab_bar.gd
+msgid "Show in File Manager"
+msgstr ""
#: src/ui_parts/display.gd
msgid "Visuals"
@@ -269,7 +310,7 @@ msgstr "Maat inknappen"
#: 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
+#: src/ui_widgets/confirm_dialog.gd src/ui_widgets/options_dialog.gd
msgid "Cancel"
msgstr "Annuleren"
@@ -330,22 +371,6 @@ msgstr "Instellingen"
msgid "Optimize"
msgstr "Optimaliseren"
-#: src/ui_parts/global_actions.gd src/ui_parts/good_file_dialog.gd
-msgid "Save SVG"
-msgstr "SVG opslaan"
-
-#: 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…"
@@ -402,7 +427,8 @@ msgstr "Kies een afbeedling"
msgid "Select"
msgstr "Selecteren"
-#: src/ui_parts/good_file_dialog.gd src/utils/TranslationUtils.gd
+#: src/ui_parts/good_file_dialog.gd src/utils/FileUtils.gd
+#: src/utils/TranslationUtils.gd
msgid "Save"
msgstr "Opslaan"
@@ -595,8 +621,8 @@ msgid "Invert zoom direction"
msgstr "inzoomrichting tegenstellen"
#: src/ui_parts/settings_menu.gd
-msgid "Wrap mouse"
-msgstr "Wikkel muis"
+msgid "Wrap-around panning"
+msgstr ""
#: src/ui_parts/settings_menu.gd
msgid "Use CTRL for zooming"
@@ -623,8 +649,9 @@ msgid "UI scale"
msgstr "Gebruiksinterface maat"
#: src/ui_parts/settings_menu.gd
-msgid "Auto UI scale"
-msgstr "Automatische gebruikersinterface maat"
+#, fuzzy
+msgid "Changes the scale factor for the interface."
+msgstr "Verandert the schaal van de visuele gebruikersinterface."
#: src/ui_parts/settings_menu.gd
msgid "Language"
@@ -634,7 +661,7 @@ msgstr "Taal"
msgid "Import XML"
msgstr "XML Importeren"
-#: src/ui_parts/settings_menu.gd src/ui_widgets/palette_config.gd
+#: src/ui_parts/settings_menu.gd
msgid "Paste XML"
msgstr "XML Plakken"
@@ -755,12 +782,14 @@ msgid "Remove unnecessary parameters"
msgstr "Verwijder onnodige parameters"
#: src/ui_parts/settings_menu.gd
-msgid "Swaps zoom in and zoom out with the mouse wheel."
-msgstr "Wisselt in- en uitzoemen met de muiswiel."
+msgid "Swaps the scroll directions for zooming in and zooming out."
+msgstr ""
#: src/ui_parts/settings_menu.gd
-msgid "Wraps the mouse cursor around when panning the viewport."
-msgstr "Wikkelt de muiscursor rond tijdens het verschuiven van de kijkvenster."
+msgid ""
+"Warps the cursor to the opposite side whenever it reaches a viewport "
+"boundary while panning."
+msgstr ""
#: src/ui_parts/settings_menu.gd
msgid ""
@@ -792,13 +821,17 @@ msgstr ""
msgid "Changes the visual size and grabbing area of handles."
msgstr "Verandert de visuele grootte en grijpgebied van Handvaten."
-#: src/ui_parts/settings_menu.gd
-msgid "Changes the scale of the visual user interface."
-msgstr "Verandert the schaal van de visuele gebruikersinterface."
+#: src/ui_parts/shortcut_panel.gd
+msgid "Horizontal strip"
+msgstr "Horizontale lijn"
-#: src/ui_parts/settings_menu.gd
-msgid "Scales the user interface based on the screen size."
-msgstr "Schaalt de gebruikersinterface gebaseerd op the schermgrootte."
+#: src/ui_parts/shortcut_panel.gd
+msgid "Vertical strip"
+msgstr "Vertikale lijn"
+
+#: src/ui_parts/shortcut_panel.gd
+msgid "Horizontal with two rows"
+msgstr "Horizontaal met twee rijen"
#: src/ui_parts/shortcut_panel_config.gd
msgid "Configure Shortcut Panel"
@@ -808,47 +841,31 @@ msgstr "Configureer Snelkoppelingspaneel"
msgid "Layout"
msgstr "Indeling"
-#: src/ui_parts/shortcut_panel_config.gd
-msgid "Horizontal strip"
-msgstr "Horizontale lijn"
-
-#: src/ui_parts/shortcut_panel_config.gd
-msgid "Horizontal with two rows"
-msgstr "Horizontaal met twee rijen"
-
-#: src/ui_parts/shortcut_panel_config.gd
-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
+#: src/ui_parts/tab_bar.gd src/utils/TranslationUtils.gd
#, fuzzy
-msgid "Close all other tabs"
-msgstr "Kopiëer alle tekst"
+msgid "Create a new tab"
+msgstr "Nieuwe map creëren"
#: src/ui_parts/tab_bar.gd
-msgid "Close tabs to the left"
+msgid "Scroll backwards"
msgstr ""
#: src/ui_parts/tab_bar.gd
-msgid "Close tabs to the right"
+msgid "Scroll forwards"
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."
+msgid "This SVG is not bound to a file location yet."
msgstr ""
#: src/ui_parts/update_menu.gd
-msgid "Include prereleases"
+#, fuzzy
+msgid "Show prereleases"
msgstr "Voorpublicaties includen"
#: src/ui_parts/update_menu.gd
@@ -913,10 +930,6 @@ msgstr "Plakken"
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"
@@ -962,13 +975,18 @@ msgstr "Dit palette heeft identiek gedefinieerde kleuren."
msgid "Rename"
msgstr "Hernoemen"
+#: src/ui_widgets/palette_config.gd
+msgid "Apply Preset"
+msgstr "Voorinstelling toepassen"
+
#: src/ui_widgets/palette_config.gd
msgid "Copy as XML"
msgstr "Als XML kopiëren"
#: src/ui_widgets/palette_config.gd
-msgid "Apply Preset"
-msgstr "Voorinstelling toepassen"
+#, fuzzy
+msgid "Save as XML"
+msgstr "Als XML kopiëren"
#: src/ui_widgets/path_popup.gd src/ui_widgets/pathdata_field.gd
#: src/utils/TranslationUtils.gd
@@ -1047,14 +1065,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
+#, fuzzy
+msgid "Save the file?"
+msgstr "Bewaar het .\"{format}\" bestand"
+
+#: src/utils/FileUtils.gd
+#, fuzzy
+msgid "Do you want to save this file?"
+msgstr "Wil je GodSVG afsluiten?"
+
+#: src/utils/FileUtils.gd
+msgid "Save the changes?"
+msgstr ""
+
+#: src/utils/FileUtils.gd
+msgid "Your changes will be lost if you don't save them."
+msgstr ""
+
+#: src/utils/FileUtils.gd
+msgid "Don't save"
+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."
+msgid "If you want to revert your edits since the last save, use {reset_svg}."
msgstr ""
+#: src/utils/FileUtils.gd
+msgid "Do you want to save the changes made to {file_name}?"
+msgstr ""
+
+#: src/utils/TranslationUtils.gd
+#, fuzzy
+msgid "Save as"
+msgstr "Opslaan"
+
+#: src/utils/TranslationUtils.gd
+msgid "Close tabs to the left"
+msgstr ""
+
+#: src/utils/TranslationUtils.gd
+msgid "Close tabs to the right"
+msgstr ""
+
+#: src/utils/TranslationUtils.gd
+#, fuzzy
+msgid "Close all other tabs"
+msgstr "Kopiëer alle tekst"
+
#: src/utils/TranslationUtils.gd
msgid "Select the next tab"
msgstr ""
@@ -1198,6 +1260,26 @@ msgstr ""
"Deze bestandsextensie is leeg. Alleen {extension_list} bestanden zijn "
"ondersteund."
+#~ msgid "Auto UI scale"
+#~ msgstr "Automatische gebruikersinterface maat"
+
+#, fuzzy
+#~ msgid "Scales the interface automatically based on the screen size."
+#~ msgstr "Schaalt de gebruikersinterface gebaseerd op the schermgrootte."
+
+#~ msgid "Wrap mouse"
+#~ msgstr "Wikkel muis"
+
+#~ msgid "Swaps zoom in and zoom out with the mouse wheel."
+#~ msgstr "Wisselt in- en uitzoemen met de muiswiel."
+
+#~ msgid "Wraps the mouse cursor around when panning the viewport."
+#~ msgstr ""
+#~ "Wikkelt de muiscursor rond tijdens het verschuiven van de kijkvenster."
+
+#~ msgid "This requires GodSVG to connect to the internet."
+#~ msgstr "Hiervoor moet GodSVG verbinding maken met het internet."
+
#~ msgid "Open file"
#~ msgstr "Open bestand"
diff --git a/translations/pt_BR.po b/translations/pt_BR.po
new file mode 100644
index 0000000..833e4b9
--- /dev/null
+++ b/translations/pt_BR.po
@@ -0,0 +1,1263 @@
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: GodSVG\n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: pt_BR\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 3.5\n"
+
+msgid "translation-credits"
+msgstr "Felipe Sena Costa "
+
+#: src/autoload/HandlerGUI.gd
+msgid "Quit GodSVG"
+msgstr "Sair do GodSVG"
+
+#: src/autoload/HandlerGUI.gd
+msgid "Do you want to quit GodSVG?"
+msgstr "Você deseja sair do GodSVG?"
+
+#: src/autoload/HandlerGUI.gd
+msgid "Quit"
+msgstr "Sair"
+
+#: src/autoload/HandlerGUI.gd
+msgid "Check for updates?"
+msgstr "Verificar atualizações?"
+
+#: src/autoload/HandlerGUI.gd
+msgid ""
+"This will connect to github.com to compare version numbers. No other data is "
+"collected or transmitted."
+msgstr ""
+"Isso irá conectar ao github.com para comparar o número de versionamento. "
+"Nenhum outro dado será colecionado ou transmitido."
+
+#: src/autoload/HandlerGUI.gd src/ui_widgets/alert_dialog.gd
+msgid "OK"
+msgstr "OK"
+
+#: src/autoload/HandlerGUI.gd
+msgid "Do you want to proceed?"
+msgstr "Você deseja prosseguir?"
+
+#: src/autoload/HandlerGUI.gd
+msgid "Export SVG"
+msgstr "Exportar SVG"
+
+#: src/autoload/HandlerGUI.gd src/ui_parts/export_menu.gd
+#: src/ui_parts/global_actions.gd src/utils/TranslationUtils.gd
+msgid "Export"
+msgstr "Exportar"
+
+#: src/autoload/HandlerGUI.gd
+msgid ""
+"The graphic can be exported only as SVG because its proportions are too "
+"extreme."
+msgstr ""
+"Esse gráfico pode ser exportado apenas como um SVG pois as suas proporções "
+"são extremas."
+
+#: src/autoload/HandlerGUI.gd
+msgid ""
+"The graphic can be exported only as SVG because its size is not defined."
+msgstr ""
+"Esse gráfico pode ser exportado apenas como SVG pois o seu tamanho não foi "
+"definido."
+
+#: src/autoload/State.gd src/ui_parts/good_file_dialog.gd
+#: src/ui_widgets/alert_dialog.gd
+msgid "Alert!"
+msgstr "Alerta!"
+
+#: src/autoload/State.gd src/ui_parts/tab_bar.gd src/utils/TranslationUtils.gd
+msgid "Close tab"
+msgstr "Fechar aba"
+
+#: src/autoload/State.gd
+msgid "Restore"
+msgstr "Restaurar"
+
+#: src/autoload/State.gd
+msgid "View in List"
+msgstr "Ver na Lista"
+
+#: src/autoload/State.gd
+msgid "Duplicate"
+msgstr "Duplicar"
+
+#: src/autoload/State.gd
+msgid "Convert To"
+msgstr "Converter Para"
+
+#: src/autoload/State.gd src/ui_widgets/palette_config.gd
+msgid "Move Up"
+msgstr "Mover para acima"
+
+#: src/autoload/State.gd src/ui_widgets/palette_config.gd
+msgid "Move Down"
+msgstr "Mover para abaixo"
+
+#: 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 "Excluir"
+
+#: src/autoload/State.gd src/ui_widgets/transform_popup.gd
+msgid "Insert After"
+msgstr "Inserir Após"
+
+#: src/autoload/State.gd
+msgid "The last edited state of this tab could not be found."
+msgstr "O último estado editado desta guia não foi encontrado."
+
+#: src/autoload/State.gd
+msgid ""
+"The tab is bound to the file path {file_path}. Do you want to restore the "
+"SVG from this path?"
+msgstr ""
+"A guia está vinculada ao caminho do arquivo {file_path}. Você deseja "
+"restaurar o SVG deste caminho?"
+
+#: src/config_classes/Formatter.gd
+msgid "Compact"
+msgstr "Compacto"
+
+#: src/config_classes/Formatter.gd
+msgid "Pretty"
+msgstr "Bonito"
+
+#: src/config_classes/Formatter.gd
+msgid "Always"
+msgstr "Sempre"
+
+#: src/config_classes/Formatter.gd
+msgid "All except containers"
+msgstr "Tudo exceto containers"
+
+#: src/config_classes/Formatter.gd
+msgid "Never"
+msgstr "Nunca"
+
+#: src/config_classes/Formatter.gd
+msgid "When shorter or equal"
+msgstr "Quando menor ou igual"
+
+#: src/config_classes/Formatter.gd
+msgid "When shorter"
+msgstr "Quando menor"
+
+#: src/config_classes/Formatter.gd
+msgid "3-digit or 6-digit hex"
+msgstr "Hexadecimal de 3 dígitos ou 6 dígitos"
+
+#: src/config_classes/Formatter.gd
+msgid "6-digit hex"
+msgstr "Hexadecimal de 6 dígitos"
+
+#: src/config_classes/TabData.gd
+msgid "Empty"
+msgstr "Vazio"
+
+#: src/config_classes/TabData.gd
+msgid "Unsaved"
+msgstr "Não salvo"
+
+#: src/data_classes/BasicXNode.gd
+msgid "Comment"
+msgstr "Comentário"
+
+#: src/data_classes/BasicXNode.gd
+msgid "Text"
+msgstr "Texto"
+
+# Needs to be tested in context
+#: src/data_classes/Element.gd
+msgid "{element} must be inside {allowed} to have any effect."
+msgstr "{element} deve estar dentro de {allowed} para ter qualquer efeito."
+
+#: src/data_classes/ElementG.gd
+msgid "This group has no elements."
+msgstr "Este grupo não possui elementos."
+
+#: src/data_classes/ElementG.gd
+msgid "This group has only one element."
+msgstr "Este grupo possui apenas um elemento."
+
+#: src/data_classes/GradientUtils.gd
+msgid "No \"id\" attribute defined."
+msgstr "Nenhum atributo \"id\" definido."
+
+#: src/data_classes/GradientUtils.gd
+msgid "No elements under this gradient."
+msgstr "Nenhum elemento sob / abaixo este gradiente."
+
+#: src/data_classes/GradientUtils.gd
+msgid "This gradient is a solid color."
+msgstr "Este gradiente é uma cor sólida."
+
+#: src/data_classes/SVGParser.gd
+msgid "Doesn’t describe an SVG."
+msgstr "Não descreve um SVG."
+
+#: src/data_classes/SVGParser.gd
+msgid "Improper nesting."
+msgstr "Aninhamento inadequado."
+
+#: 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 "Fechar"
+
+#: src/ui_parts/about_menu.gd
+msgid "Authors"
+msgstr "Autores"
+
+#: src/ui_parts/about_menu.gd
+msgid "Donors"
+msgstr "Doadores"
+
+#: src/ui_parts/about_menu.gd
+msgid "License"
+msgstr "Licença"
+
+#: src/ui_parts/about_menu.gd
+msgid "Third-party licenses"
+msgstr "Licenças de terceiros"
+
+#: src/ui_parts/about_menu.gd
+msgid "Project Founder and Manager"
+msgstr "Fundador e Gerente do Projeto"
+
+#: src/ui_parts/about_menu.gd
+msgid "Developers"
+msgstr "Desenvolvedores"
+
+#: src/ui_parts/about_menu.gd
+msgid "Translators"
+msgstr "Tradutores"
+
+#: src/ui_parts/about_menu.gd
+msgid "Golden donors"
+msgstr "Doadores ouro"
+
+#: src/ui_parts/about_menu.gd
+msgid "Diamond donors"
+msgstr "Doadores diamante"
+
+#: src/ui_parts/code_editor.gd src/utils/TranslationUtils.gd
+msgid "Copy all text"
+msgstr "Copiar todo texto"
+
+#: src/ui_parts/current_file_button.gd src/ui_parts/good_file_dialog.gd
+msgid "Save SVG"
+msgstr "Salvar SVG"
+
+#: src/ui_parts/current_file_button.gd
+msgid "Save SVG as…"
+msgstr "Salvar SVG como…"
+
+#: src/ui_parts/current_file_button.gd src/utils/TranslationUtils.gd
+msgid "Reset SVG"
+msgstr "Redefinir SVG"
+
+#: src/ui_parts/current_file_button.gd src/ui_parts/tab_bar.gd
+msgid "Open externally"
+msgstr "Abrir externalmente"
+
+#: src/ui_parts/current_file_button.gd src/ui_parts/tab_bar.gd
+msgid "Show in File Manager"
+msgstr "Mostrar no gerenciador de arquivos"
+
+#: src/ui_parts/display.gd
+msgid "Visuals"
+msgstr "Visuais"
+
+#: src/ui_parts/display.gd src/utils/TranslationUtils.gd
+msgid "Load reference image"
+msgstr "Carregar imagem de referência"
+
+#: src/ui_parts/display.gd
+msgid "Show reference"
+msgstr "Mostrar referência"
+
+#: src/ui_parts/display.gd
+msgid "Overlay reference"
+msgstr "Sobrepôr referência"
+
+#: src/ui_parts/display.gd src/utils/TranslationUtils.gd
+msgid "Show grid"
+msgstr "Mostrar grade"
+
+#: src/ui_parts/display.gd src/utils/TranslationUtils.gd
+msgid "Show handles"
+msgstr "Mostrar alças"
+
+#: src/ui_parts/display.gd
+msgid "Rasterized SVG"
+msgstr "SVG rasterizado"
+
+#: src/ui_parts/display.gd
+msgid "Snap size"
+msgstr "Tamanho de encaixe"
+
+#: 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 src/ui_widgets/options_dialog.gd
+msgid "Cancel"
+msgstr "Cancelar"
+
+#: src/ui_parts/element_container.gd
+msgid "New element"
+msgstr "Novo elemento"
+
+#: src/ui_parts/export_menu.gd
+msgid "Dimensions"
+msgstr "Dimensões"
+
+#: src/ui_parts/export_menu.gd
+msgid "Size"
+msgstr "Tamanho"
+
+#: src/ui_parts/export_menu.gd
+msgid "Export Configuration"
+msgstr "Configuração de exportação"
+
+#: src/ui_parts/export_menu.gd
+msgid "Format"
+msgstr "Formato"
+
+#: src/ui_parts/export_menu.gd
+msgid "Lossless"
+msgstr "Sem perdas"
+
+#: src/ui_parts/export_menu.gd
+msgid "Quality"
+msgstr "Qualidade"
+
+#: src/ui_parts/export_menu.gd
+msgid "Scale"
+msgstr "Escala"
+
+#: src/ui_parts/export_menu.gd
+msgid "Width"
+msgstr "Largura"
+
+#: src/ui_parts/export_menu.gd
+msgid "Height"
+msgstr "Altura"
+
+#: src/ui_parts/export_menu.gd
+msgid "Preview image size is limited to {dimensions}"
+msgstr ""
+"O tamanho da visualização da prévia de imagem é limitado à {dimensions}"
+
+#: src/ui_parts/global_actions.gd src/ui_parts/import_warning_menu.gd
+#: src/utils/TranslationUtils.gd
+msgid "Import"
+msgstr "Importar"
+
+#: src/ui_parts/global_actions.gd
+msgid "Settings"
+msgstr "Configurações"
+
+#: src/ui_parts/global_actions.gd src/utils/TranslationUtils.gd
+msgid "Optimize"
+msgstr "Otimizar"
+
+#: src/ui_parts/global_actions.gd
+msgid "About…"
+msgstr "Sobre…"
+
+#: src/ui_parts/global_actions.gd src/utils/TranslationUtils.gd
+msgid "Check for updates"
+msgstr "Verificar atualizações"
+
+#: src/ui_parts/global_actions.gd
+msgid "Donate…"
+msgstr "Doar…"
+
+#: src/ui_parts/global_actions.gd
+msgid "GodSVG repository"
+msgstr "Repositório do GodSVG"
+
+#: src/ui_parts/global_actions.gd
+msgid "GodSVG website"
+msgstr "Website do GodSVG"
+
+#: src/ui_parts/global_actions.gd
+msgid "View savedata"
+msgstr "Ver dados salvos"
+
+#: src/ui_parts/good_file_dialog.gd
+msgid "Go to parent folder"
+msgstr "Ir para pasta pai"
+
+#: src/ui_parts/good_file_dialog.gd
+msgid "Refresh files"
+msgstr "Atualizar arquivos"
+
+#: src/ui_parts/good_file_dialog.gd
+msgid "Toggle the visibility of hidden files"
+msgstr "Alternar a visibilidade de arquivos escondidos"
+
+#: src/ui_parts/good_file_dialog.gd
+msgid "Search files"
+msgstr "Procurar arquivos"
+
+#: src/ui_parts/good_file_dialog.gd
+msgid "Select an SVG"
+msgstr "Selecionar um SVG"
+
+#: src/ui_parts/good_file_dialog.gd
+msgid "Select an XML file"
+msgstr "Selecionar um arquivo XML"
+
+#: src/ui_parts/good_file_dialog.gd
+msgid "Select an image"
+msgstr "Selecionar uma imagem"
+
+#: src/ui_parts/good_file_dialog.gd
+msgid "Select"
+msgstr "Selecionar"
+
+#: src/ui_parts/good_file_dialog.gd src/utils/FileUtils.gd
+#: src/utils/TranslationUtils.gd
+msgid "Save"
+msgstr "Salvar"
+
+#: src/ui_parts/good_file_dialog.gd
+msgid "Path"
+msgstr "Caminho"
+
+#: src/ui_parts/good_file_dialog.gd
+msgid "Replace"
+msgstr "Substituir"
+
+#: src/ui_parts/good_file_dialog.gd
+msgid "Create new folder"
+msgstr "Criar nova pasta"
+
+#: src/ui_parts/good_file_dialog.gd
+msgid "Invalid name for a folder."
+msgstr "Nome inválido para uma pasta."
+
+#: src/ui_parts/good_file_dialog.gd
+msgid "A folder with this name already exists."
+msgstr "Uma pasta com esse nome já existe."
+
+#: src/ui_parts/good_file_dialog.gd
+msgid "Failed to create a folder."
+msgstr "Falha ao criar uma pasta."
+
+#: src/ui_parts/good_file_dialog.gd
+msgid "Open"
+msgstr "Abrir"
+
+#: src/ui_parts/good_file_dialog.gd
+msgid "Copy path"
+msgstr "Copiar caminho"
+
+#: src/ui_parts/good_file_dialog.gd
+msgid ""
+"A file named \"{file_name}\" already exists. Replacing will overwrite its "
+"contents!"
+msgstr ""
+"Um arquivo nomeado \"{file_name}\" já existe. Substituir este arquivo irá "
+"sobrescrever os seus conteúdos!"
+
+#: src/ui_parts/handles_manager.gd
+msgid "New shape"
+msgstr "Nova forma"
+
+#: src/ui_parts/import_warning_menu.gd
+msgid "Import Problems"
+msgstr "Problemas de Importação"
+
+#: src/ui_parts/import_warning_menu.gd
+msgid "Unrecognized element"
+msgstr "Elemento não reconhecido"
+
+#: src/ui_parts/import_warning_menu.gd
+msgid "Unrecognized attribute"
+msgstr "Atributo não reconhecido"
+
+#: src/ui_parts/import_warning_menu.gd
+msgid "Syntax error"
+msgstr "Erro de sintaxe"
+
+#: src/ui_parts/inspector.gd
+msgid "Add element"
+msgstr "Adicionar elemento"
+
+#: src/ui_parts/mac_menu.gd src/ui_parts/settings_menu.gd
+msgid "File"
+msgstr "Arquivo"
+
+#: src/ui_parts/mac_menu.gd src/ui_parts/settings_menu.gd
+msgid "Edit"
+msgstr "Editar"
+
+#: src/ui_parts/mac_menu.gd src/ui_parts/settings_menu.gd
+msgid "Tool"
+msgstr "Ferramenta"
+
+#: src/ui_parts/mac_menu.gd src/ui_parts/settings_menu.gd
+msgid "View"
+msgstr "Visualizar"
+
+#: src/ui_parts/mac_menu.gd
+msgid "Snap"
+msgstr "Encaixe"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Formatting"
+msgstr "Formatação"
+
+#: src/ui_parts/settings_menu.gd src/ui_widgets/color_popup.gd
+msgid "Palettes"
+msgstr "Paletas"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Shortcuts"
+msgstr "Atalhos"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Theming"
+msgstr "Temas"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Other"
+msgstr "Outro"
+
+#: src/ui_parts/settings_menu.gd
+msgid "SVG Text colors"
+msgstr "Cores de texto SVG"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Symbol color"
+msgstr "Cor de símbolo"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Element color"
+msgstr "Cor de elemento"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Attribute color"
+msgstr "Cor de atributo"
+
+#: src/ui_parts/settings_menu.gd
+msgid "String color"
+msgstr "Cor de string"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Comment color"
+msgstr "Cor de comentário"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Text color"
+msgstr "Cor de texto"
+
+#: src/ui_parts/settings_menu.gd
+msgid "CDATA color"
+msgstr "Cor de CDATA"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Error color"
+msgstr "Cor de erro"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Handle colors"
+msgstr "Cor de alça"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Inside color"
+msgstr "Cor interna"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Normal color"
+msgstr "Cor normal"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Hovered color"
+msgstr "Cor de pairado"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Selected color"
+msgstr "Cor de selecionado"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Hovered selected color"
+msgstr "Cor de pairado e selecionado"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Basic colors"
+msgstr "Cores básicas"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Background color"
+msgstr "Cor do plano de fundo"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Valid color"
+msgstr "Cor válida"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Warning color"
+msgstr "Cor de aviso"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Input"
+msgstr "Entrada"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Invert zoom direction"
+msgstr "Inverter direção do zoom"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Wrap-around panning"
+msgstr "Movimentação envolvente"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Use CTRL for zooming"
+msgstr "Utilizar CTRL para zoom"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Miscellaneous"
+msgstr "Variados"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Use native file dialog"
+msgstr "Utilizar diálogo de arquivos nativo"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Sync window title to file name"
+msgstr "Sincronizar título de janela com o nome do arquivo"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Handle size"
+msgstr "Tamanho de alça"
+
+#: src/ui_parts/settings_menu.gd
+msgid "UI scale"
+msgstr "Escala da interface"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Changes the scale factor for the interface."
+msgstr "Muda o fator de escala da interfaçe."
+
+#: src/ui_parts/settings_menu.gd
+msgid "Language"
+msgstr "Idioma"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Import XML"
+msgstr "Importar XML"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Paste XML"
+msgstr "Colar XML"
+
+#: src/ui_parts/settings_menu.gd
+msgid "New palette"
+msgstr "Nova paleta"
+
+#: src/ui_parts/settings_menu.gd
+msgid "New palette from XML"
+msgstr "Nova paleta a partir de um XML"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Editor formatter"
+msgstr "Formatador do editor"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Export formatter"
+msgstr "Formatador de exportação"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Help"
+msgstr "Ajuda"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Reset all to default"
+msgstr "Redefinir tudo para o padrão"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Preset"
+msgstr "Predefinição"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Keep comments"
+msgstr "Manter comentários"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Keep unrecognized XML structures"
+msgstr "Manter estrutura XML não reconhecida"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Add trailing newline"
+msgstr "Adicionar nova linha à direita"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Use shorthand tag syntax"
+msgstr "Utilizar sintaxe de tag abreviada"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Space out the slash of shorthand tags"
+msgstr "Espaçar a barra de tags abreviadas"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Use pretty formatting"
+msgstr "Utilizar formatação bonita"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Use spaces instead of tabs"
+msgstr "Utilizar espaços ao invés de tabulações"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Number of indentation spaces"
+msgstr "Número de espaços de identação"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Numbers"
+msgstr "Números"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Remove leading zero"
+msgstr "Remover zero à esquerada"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Use exponential when shorter"
+msgstr "Utilizar exponencial quando mais curto"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Colors"
+msgstr "Cores"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Use named colors"
+msgstr "Utilizar cores nomeadas"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Primary syntax"
+msgstr "Sintaxe primária"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Capitalize hexadecimal letters"
+msgstr "Letras de valores hexadecimais maiúsculas"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Pathdata"
+msgstr "Dados de caminho"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Compress numbers"
+msgstr "Comprimir números"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Minimize spacing"
+msgstr "Minimizar espaçamento"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Remove spacing after flags"
+msgstr "Remover espaçamento após sinalizadores"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Remove consecutive commands"
+msgstr "Remover comandos consecutivos"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Transform lists"
+msgstr "Transformar listas"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Remove unnecessary parameters"
+msgstr "Remover parâmetros desnecessários"
+
+#: src/ui_parts/settings_menu.gd
+msgid "Swaps the scroll directions for zooming in and zooming out."
+msgstr "Troca as direções de rolagem para aumentar e diminuir o zoom."
+
+#: src/ui_parts/settings_menu.gd
+msgid ""
+"Warps the cursor to the opposite side whenever it reaches a viewport "
+"boundary while panning."
+msgstr ""
+"Passa o cursor para o lado oposto da tela quando ele atinge uma borda da "
+"viewport quando movimentando a visualização."
+
+#: src/ui_parts/settings_menu.gd
+msgid ""
+"If turned on, scrolling will pan the view. To zoom, hold CTRL while "
+"scrolling."
+msgstr ""
+"Se habilitado, a rolagem do mouse irá movimentar a tela. Para realizar o "
+"zoom, pressione CTRL enquanto rola."
+
+#: src/ui_parts/settings_menu.gd
+msgid ""
+"If turned on, uses your operating system's native file dialog. If turned "
+"off, uses GodSVG's built-in file dialog."
+msgstr ""
+"Se habilitado, usa o diálogo de arquivo nativo do sistema operacional. Se "
+"desabilitado, usa o diálogo de arquivo embutido no GodSVG."
+
+#: src/ui_parts/settings_menu.gd
+msgid ""
+"If turned off, the window title will remain simply \"GodSVG\" regardless of "
+"the current file."
+msgstr ""
+"Se desabilitado, o título da janela permanecerá como \"GodSVG\" independente "
+"do arquivo aberto."
+
+#: src/ui_parts/settings_menu.gd
+msgid "Changes the visual size and grabbing area of handles."
+msgstr "Muda o tamanho e a área de agarramento das alças."
+
+#: src/ui_parts/shortcut_panel.gd
+msgid "Horizontal strip"
+msgstr "Faixa horizontal"
+
+#: src/ui_parts/shortcut_panel.gd
+msgid "Vertical strip"
+msgstr "Faixa vertical"
+
+#: src/ui_parts/shortcut_panel.gd
+msgid "Horizontal with two rows"
+msgstr "Horizontal com duas linhas"
+
+#: src/ui_parts/shortcut_panel_config.gd
+msgid "Configure Shortcut Panel"
+msgstr "Configurar Painel de Atalhos"
+
+#: src/ui_parts/shortcut_panel_config.gd
+msgid "Layout"
+msgstr "Layout"
+
+#: src/ui_parts/tab_bar.gd
+msgid "Create tab"
+msgstr "Criar aba"
+
+#: src/ui_parts/tab_bar.gd src/utils/TranslationUtils.gd
+msgid "Create a new tab"
+msgstr "Criar nova aba"
+
+#: src/ui_parts/tab_bar.gd
+msgid "Scroll backwards"
+msgstr "Rolar para trás"
+
+#: src/ui_parts/tab_bar.gd
+msgid "Scroll forwards"
+msgstr "Rolar para frente"
+
+#: src/ui_parts/tab_bar.gd
+msgid "This SVG is not bound to a file location yet."
+msgstr "Esse SVG ainda não está vinculado a um local de arquivo."
+
+#: src/ui_parts/update_menu.gd
+#, fuzzy
+msgid "Show prereleases"
+msgstr "Incluir pré-lançamentos"
+
+#: src/ui_parts/update_menu.gd
+msgid "Retry"
+msgstr "Tentar novamente"
+
+#: src/ui_parts/update_menu.gd
+msgid "Current Version"
+msgstr "Versão Atual"
+
+#: src/ui_parts/update_menu.gd
+msgid "Retrieving information..."
+msgstr "Pegando informação..."
+
+#: src/ui_parts/update_menu.gd
+msgid "Update check failed"
+msgstr "Verificação de atualização falhou"
+
+#: src/ui_parts/update_menu.gd
+msgid "GodSVG is up-to-date."
+msgstr "GodSVG está atualizado."
+
+#: src/ui_parts/update_menu.gd
+msgid "New versions"
+msgstr "Novas versões"
+
+#: src/ui_parts/zoom_menu.gd src/utils/TranslationUtils.gd
+msgid "Zoom out"
+msgstr "Diminuir zoom"
+
+#: src/ui_parts/zoom_menu.gd src/utils/TranslationUtils.gd
+msgid "Zoom in"
+msgstr "Aumentar zoom"
+
+#: src/ui_parts/zoom_menu.gd src/utils/TranslationUtils.gd
+msgid "Zoom reset"
+msgstr "Redefinir zoom"
+
+#: src/ui_widgets/BetterLineEdit.gd src/ui_widgets/BetterTextEdit.gd
+#: src/utils/TranslationUtils.gd
+msgid "Undo"
+msgstr "Desfazer"
+
+#: src/ui_widgets/BetterLineEdit.gd src/ui_widgets/BetterTextEdit.gd
+#: src/utils/TranslationUtils.gd
+msgid "Redo"
+msgstr "Refazer"
+
+#: src/ui_widgets/BetterLineEdit.gd src/ui_widgets/BetterTextEdit.gd
+msgid "Cut"
+msgstr "Cortar"
+
+#: src/ui_widgets/BetterLineEdit.gd src/ui_widgets/BetterTextEdit.gd
+msgid "Copy"
+msgstr "Copiar"
+
+#: src/ui_widgets/BetterLineEdit.gd src/ui_widgets/BetterTextEdit.gd
+msgid "Paste"
+msgstr "Colar"
+
+#: src/ui_widgets/choose_name_dialog.gd
+msgid "Create"
+msgstr "Criar"
+
+#: src/ui_widgets/color_popup.gd
+msgid "Search color"
+msgstr "Procurar cor"
+
+#: src/ui_widgets/color_popup.gd
+msgid "Color Picker"
+msgstr "Seletor de Cores"
+
+#: src/ui_widgets/configure_color_popup.gd
+msgid "Edit color name"
+msgstr "Editar nome da cor"
+
+#: src/ui_widgets/configure_color_popup.gd
+msgid "Delete color"
+msgstr "Remover cor"
+
+#: src/ui_widgets/configure_color_popup.gd src/ui_widgets/palette_config.gd
+msgid "Unnamed"
+msgstr "Sem nome"
+
+#: src/ui_widgets/good_color_picker.gd
+msgid "Color keywords"
+msgstr "Palavras-chave de cor"
+
+#: src/ui_widgets/good_color_picker.gd
+msgid "Eyedropper"
+msgstr "Conta-gotas"
+
+#: src/ui_widgets/palette_config.gd
+msgid "Unnamed palettes won't be shown."
+msgstr "Paletas sem nome não serão mostradas."
+
+#: src/ui_widgets/palette_config.gd
+msgid "Multiple palettes can't have the same name."
+msgstr "Várias paletas não podem ter o mesmo nome."
+
+#: src/ui_widgets/palette_config.gd
+msgid "This palette has identically defined colors."
+msgstr "Essa paleta tem cores definidas de forma idêntica."
+
+#: src/ui_widgets/palette_config.gd
+msgid "Rename"
+msgstr "Renomear"
+
+#: src/ui_widgets/palette_config.gd
+msgid "Apply Preset"
+msgstr "Aplicar Predefinição"
+
+#: src/ui_widgets/palette_config.gd
+msgid "Copy as XML"
+msgstr "Copiar como XML"
+
+#: src/ui_widgets/palette_config.gd
+msgid "Save as XML"
+msgstr "Salvar como XML"
+
+#: src/ui_widgets/path_popup.gd src/ui_widgets/pathdata_field.gd
+#: src/utils/TranslationUtils.gd
+msgid "Relative"
+msgstr "Relativo"
+
+#: src/ui_widgets/pathdata_field.gd
+msgid "No path data"
+msgstr "Sem dados de caminho"
+
+#: src/ui_widgets/pathdata_field.gd src/utils/TranslationUtils.gd
+msgid "Absolute"
+msgstr "Absoluto"
+
+#: src/ui_widgets/points_field.gd
+msgid "No points"
+msgstr "Sem pontos"
+
+#: src/ui_widgets/presented_shortcut.gd src/ui_widgets/setting_shortcut.gd
+msgid "Also used by"
+msgstr "Também usado por"
+
+#: src/ui_widgets/setting_frame.gd src/ui_widgets/setting_shortcut.gd
+msgid "Reset to default"
+msgstr "Redefinir para o padrão"
+
+#: src/ui_widgets/setting_shortcut.gd
+msgid "Unused"
+msgstr "Não utilizado"
+
+#: src/ui_widgets/setting_shortcut.gd
+msgid "Add shortcut"
+msgstr "Adicionar atalho"
+
+#: src/ui_widgets/setting_shortcut.gd
+msgid "Press keys…"
+msgstr "Pressione as teclas…"
+
+#: src/ui_widgets/transform_field.gd
+msgid "No transforms"
+msgstr "Sem transformações"
+
+#: src/ui_widgets/transform_popup.gd
+msgid "Apply the matrix"
+msgstr "Aplicar matriz"
+
+#: src/ui_widgets/transform_popup.gd
+msgid "Insert Before"
+msgstr "Inserir Antes"
+
+#: src/ui_widgets/transform_popup.gd
+msgid "New transform"
+msgstr "Nova transformação"
+
+#: src/ui_widgets/unrecognized_field.gd
+msgid "GodSVG doesn’t recognize this attribute"
+msgstr "O GodSVG não reconhece este atributo"
+
+#: src/utils/FileUtils.gd
+msgid "Save the .\"{format}\" file"
+msgstr "Salvar o arquivo .\"{format}\""
+
+#: src/utils/FileUtils.gd
+msgid "Load an image file"
+msgstr "Carregar um arquivo de imagem"
+
+#: src/utils/FileUtils.gd
+msgid "Import a {extension} file"
+msgstr "Importar um arquivo {extension}"
+
+#: src/utils/FileUtils.gd
+msgid "The file couldn't be opened."
+msgstr "Este arquivo não pode ser aberto."
+
+#: src/utils/FileUtils.gd
+msgid "Check if the file still exists in the selected file path."
+msgstr "Verificar se o arquivo ainda existe no local de arquivo selecionado."
+
+#: src/utils/FileUtils.gd
+#, fuzzy
+msgid "Save the file?"
+msgstr "Salvar o arquivo .\"{format}\""
+
+#: src/utils/FileUtils.gd
+#, fuzzy
+msgid "Do you want to save this file?"
+msgstr "Você deseja prosseguir?"
+
+#: src/utils/FileUtils.gd
+msgid "Save the changes?"
+msgstr ""
+
+#: src/utils/FileUtils.gd
+msgid "Your changes will be lost if you don't save them."
+msgstr ""
+
+#: src/utils/FileUtils.gd
+msgid "Don't save"
+msgstr ""
+
+#: src/utils/FileUtils.gd
+msgid "The imported file is already being edited inside GodSVG."
+msgstr "O arquivo importado já está sendo editado dentro do GodSVG."
+
+#: src/utils/FileUtils.gd
+msgid "If you want to revert your edits since the last save, use {reset_svg}."
+msgstr ""
+
+#: src/utils/FileUtils.gd
+msgid "Do you want to save the changes made to {file_name}?"
+msgstr ""
+
+#: src/utils/TranslationUtils.gd
+msgid "Save as"
+msgstr "Salvar como"
+
+#: src/utils/TranslationUtils.gd
+msgid "Close tabs to the left"
+msgstr "Fechar abas à esquerda"
+
+#: src/utils/TranslationUtils.gd
+msgid "Close tabs to the right"
+msgstr "Fechar abas à direita"
+
+#: src/utils/TranslationUtils.gd
+msgid "Close all other tabs"
+msgstr "Fechar todas as outras abas"
+
+#: src/utils/TranslationUtils.gd
+msgid "Select the next tab"
+msgstr "Selecionar a próxima guia"
+
+#: src/utils/TranslationUtils.gd
+msgid "Select the previous tab"
+msgstr "Selecionar a guia anterior"
+
+#: src/utils/TranslationUtils.gd
+msgid "Open SVG externally"
+msgstr "Abrir SVG externalmente"
+
+#: src/utils/TranslationUtils.gd
+msgid "Show SVG in File Manager"
+msgstr "Mostrar SVG no Explorador de Arquivos"
+
+#: src/utils/TranslationUtils.gd
+msgid "Select all"
+msgstr "Selecionar tudo"
+
+#: src/utils/TranslationUtils.gd
+msgid "Duplicate the selection"
+msgstr "Duplicar a seleção"
+
+#: src/utils/TranslationUtils.gd
+msgid "Delete the selection"
+msgstr "Excluir a seleção"
+
+#: src/utils/TranslationUtils.gd
+msgid "Move the selection up"
+msgstr "Mover a seleção para cima"
+
+#: src/utils/TranslationUtils.gd
+msgid "Move the selection down"
+msgstr "Mover a seleção para baixo"
+
+#: src/utils/TranslationUtils.gd
+msgid "Find"
+msgstr "Encontrar"
+
+#: src/utils/TranslationUtils.gd
+msgid "Show rasterized SVG"
+msgstr "Mostrar SVG rasterizado"
+
+#: src/utils/TranslationUtils.gd
+msgid "Toggle snapping"
+msgstr "Alternar ajuste"
+
+#: src/utils/TranslationUtils.gd
+msgid "Show reference image"
+msgstr "Mostrar imagem de referência"
+
+#: src/utils/TranslationUtils.gd
+msgid "Overlay reference image"
+msgstr "Sobrepor imagem de referência"
+
+#: src/utils/TranslationUtils.gd
+msgid "View debug information"
+msgstr "Visualizar informação de depuração"
+
+#: src/utils/TranslationUtils.gd
+msgid "Open Settings menu"
+msgstr "Abrir o menu Configurações"
+
+#: src/utils/TranslationUtils.gd
+msgid "Open About menu"
+msgstr "Abrir o menu Sobre"
+
+#: src/utils/TranslationUtils.gd
+msgid "Open Donate menu"
+msgstr "Abrir o menu Doar"
+
+#: src/utils/TranslationUtils.gd
+msgid "Open GodSVG repository"
+msgstr "Abrir o repositório do GodSVG"
+
+#: src/utils/TranslationUtils.gd
+msgid "Open GodSVG website"
+msgstr "Abrir website do GodSVG"
+
+#: src/utils/TranslationUtils.gd
+msgid "Quit the application"
+msgstr "Fechar programa"
+
+#: src/utils/TranslationUtils.gd
+msgid "Move to"
+msgstr "Mover para"
+
+#: src/utils/TranslationUtils.gd
+msgid "Line to"
+msgstr "Linha para"
+
+#: src/utils/TranslationUtils.gd
+msgid "Horizontal Line to"
+msgstr "Linha Horizontal para"
+
+#: src/utils/TranslationUtils.gd
+msgid "Vertical Line to"
+msgstr "Linha Vertical para"
+
+#: src/utils/TranslationUtils.gd
+msgid "Close Path"
+msgstr "Fechar Caminho"
+
+#: src/utils/TranslationUtils.gd
+msgid "Elliptical Arc to"
+msgstr "Arco Elíptico para"
+
+#: src/utils/TranslationUtils.gd
+msgid "Quadratic Bezier to"
+msgstr "Bézier Quadrático para"
+
+#: src/utils/TranslationUtils.gd
+msgid "Shorthand Quadratic Bezier to"
+msgstr "Bézier Quadrático Abreviado para"
+
+#: src/utils/TranslationUtils.gd
+msgid "Cubic Bezier to"
+msgstr "Bézier Cúbico"
+
+#: src/utils/TranslationUtils.gd
+msgid "Shorthand Cubic Bezier to"
+msgstr "Bézier Cúbico Abreviado para"
+
+#: src/utils/TranslationUtils.gd
+msgid ""
+"The file extension {extension} is unsupported for this operation. Only "
+"{extension_list} files are supported."
+msgstr ""
+"A extensão de arquivo {extension} não é compatível com esta operação. "
+"Somente arquivos {extension_list} são suportados."
+
+#: src/utils/TranslationUtils.gd
+msgid "The file extension is empty. Only {extension_list} files are supported."
+msgstr ""
+"A extensão de arquivo está vazia. Somente arquivos {extension_list} são "
+"suportados."
+
+# Wonky translation but source is alwo wonky
+#~ msgid ""
+#~ "If you want to apply the unsaved file state, use \"Reset SVG\" instead."
+#~ msgstr ""
+#~ "Se você deseja aplicar o estado de arquivo não salvo, utilize \"Redefinir "
+#~ "SVG\"."
+
+#~ msgid "Auto UI scale"
+#~ msgstr "Escala de interface automárica"
+
+#~ msgid "Scales the interface automatically based on the screen size."
+#~ msgstr "Escalona a interface automaticamente baseado no tamanho da tela."
diff --git a/assets/translations/ru.po b/translations/ru.po
similarity index 80%
rename from assets/translations/ru.po
rename to translations/ru.po
index 8dc1064..378d39c 100644
--- a/assets/translations/ru.po
+++ b/translations/ru.po
@@ -3,8 +3,8 @@ msgid ""
msgstr ""
"Project-Id-Version: GodSVG\n"
"POT-Creation-Date: \n"
-"PO-Revision-Date: 2024-12-30 11:19+0200\n"
-"Last-Translator: volkov\n"
+"PO-Revision-Date: \n"
+"Last-Translator: \n"
"Language-Team: \n"
"Language: ru\n"
"MIME-Version: 1.0\n"
@@ -33,13 +33,21 @@ msgid "Check for updates?"
msgstr "Проверить обновления?"
#: src/autoload/HandlerGUI.gd
-msgid "This requires GodSVG to connect to the internet."
-msgstr "Для этого необходимо подключение к Интернету."
+msgid ""
+"This will connect to github.com to compare version numbers. No other data is "
+"collected or transmitted."
+msgstr ""
+"Требуется подключение к github.com, чтобы сравнить номер версии. Никакие "
+"другие данные не собираются и не передаются."
#: src/autoload/HandlerGUI.gd src/ui_widgets/alert_dialog.gd
msgid "OK"
msgstr "Хорошо"
+#: src/autoload/HandlerGUI.gd
+msgid "Do you want to proceed?"
+msgstr "Продолжить?"
+
#: src/autoload/HandlerGUI.gd
msgid "Export SVG"
msgstr "Экспортировать SVG"
@@ -50,12 +58,20 @@ msgid "Export"
msgstr "Экспортовать"
#: src/autoload/HandlerGUI.gd
+#, fuzzy
msgid ""
-"The graphic can only be exported as SVG because its size is not defined. Do "
-"you want to proceed?"
+"The graphic can be exported only as SVG because its proportions are too "
+"extreme."
+msgstr ""
+"Изображение можно экспортировать только как SVG, поскольку его пропорции "
+"слишком экстремальные."
+
+#: src/autoload/HandlerGUI.gd
+msgid ""
+"The graphic can be exported only as SVG because its size is not defined."
msgstr ""
"Изображение можно экспортировать только как SVG, поскольку его размеры не "
-"заданы. Желаете продолжить?"
+"заданы."
#: src/autoload/State.gd src/ui_parts/good_file_dialog.gd
#: src/ui_widgets/alert_dialog.gd
@@ -63,13 +79,12 @@ msgid "Alert!"
msgstr "Внимание!"
#: src/autoload/State.gd src/ui_parts/tab_bar.gd src/utils/TranslationUtils.gd
-#, fuzzy
msgid "Close tab"
-msgstr "Закрыть путь"
+msgstr "Закрыть вкладку"
#: src/autoload/State.gd
msgid "Restore"
-msgstr ""
+msgstr "Восстановить"
#: src/autoload/State.gd
msgid "View in List"
@@ -102,13 +117,15 @@ msgstr "Вставить после"
#: src/autoload/State.gd
msgid "The last edited state of this tab could not be found."
-msgstr ""
+msgstr "Последнее редактированное состояние этой вкладки невозможно найти."
#: src/autoload/State.gd
msgid ""
-"The tab is bound to the file path {file_path}. Do you want to restore from "
-"this path?"
+"The tab is bound to the file path {file_path}. Do you want to restore the "
+"SVG from this path?"
msgstr ""
+"Вкладка связана с путем к файлу {file_path}. Вы желаете восстановить SVG из "
+"этого пути?"
#: src/config_classes/Formatter.gd
msgid "Compact"
@@ -146,9 +163,13 @@ msgstr "3 или 6 цифровое шестандацитиричное зна
msgid "6-digit hex"
msgstr "6 цифровое шестандцатяричное значение"
+#: src/config_classes/TabData.gd
+msgid "Empty"
+msgstr "Пусто"
+
#: src/config_classes/TabData.gd
msgid "Unsaved"
-msgstr ""
+msgstr "Не сохранено"
#: src/data_classes/BasicXNode.gd
msgid "Comment"
@@ -172,15 +193,15 @@ msgstr "В этой группе только один элемент."
#: src/data_classes/GradientUtils.gd
msgid "No \"id\" attribute defined."
-msgstr "Атрибут \"id\" не определен."
+msgstr "Атрибут \"id\" не задан."
#: src/data_classes/GradientUtils.gd
msgid "No elements under this gradient."
-msgstr "Нет елементов под этим градиентом."
+msgstr "Нет элемента в этом градиенте."
#: src/data_classes/GradientUtils.gd
msgid "This gradient is a solid color."
-msgstr "Этот градиент это обычный цвет."
+msgstr "Этот градиент описывает сплошной цвет."
#: src/data_classes/SVGParser.gd
msgid "Doesn’t describe an SVG."
@@ -196,6 +217,22 @@ msgstr "Неправильное вложение."
msgid "Close"
msgstr "Закрыть"
+#: src/ui_parts/about_menu.gd
+msgid "Authors"
+msgstr "Авторы"
+
+#: src/ui_parts/about_menu.gd
+msgid "Donors"
+msgstr "Благотворители"
+
+#: src/ui_parts/about_menu.gd
+msgid "License"
+msgstr "Лицензия"
+
+#: src/ui_parts/about_menu.gd
+msgid "Third-party licenses"
+msgstr "Лицензии третьих лиц"
+
#: src/ui_parts/about_menu.gd
msgid "Project Founder and Manager"
msgstr "Основатель и руководитель проекта"
@@ -208,10 +245,6 @@ msgstr "Разработчики"
msgid "Translators"
msgstr "Переводчики"
-#: src/ui_parts/about_menu.gd
-msgid "Donors"
-msgstr "Благотворители"
-
#: src/ui_parts/about_menu.gd
msgid "Golden donors"
msgstr "Золотые благотворители"
@@ -220,21 +253,29 @@ msgstr "Золотые благотворители"
msgid "Diamond donors"
msgstr "Алмазные благотворители"
-#: src/ui_parts/about_menu.gd
-msgid "Authors"
-msgstr "Авторы"
+#: src/ui_parts/code_editor.gd src/utils/TranslationUtils.gd
+msgid "Copy all text"
+msgstr "Скопировать весь текст"
-#: src/ui_parts/about_menu.gd
-msgid "License"
-msgstr "Лицензия"
+#: src/ui_parts/current_file_button.gd src/ui_parts/good_file_dialog.gd
+msgid "Save SVG"
+msgstr "Сохранить SVG"
-#: src/ui_parts/about_menu.gd
-msgid "Godot third-party components"
-msgstr "Компоненты Godot от третьих лиц"
+#: src/ui_parts/current_file_button.gd
+msgid "Save SVG as…"
+msgstr "Сохранить SVG как…"
-#: src/ui_parts/about_menu.gd
-msgid "Third-party licenses"
-msgstr "Лицензии третьих лиц"
+#: src/ui_parts/current_file_button.gd src/utils/TranslationUtils.gd
+msgid "Reset SVG"
+msgstr "Сбросить SVG"
+
+#: src/ui_parts/current_file_button.gd src/ui_parts/tab_bar.gd
+msgid "Open externally"
+msgstr "Открыть во внешней программе"
+
+#: src/ui_parts/current_file_button.gd src/ui_parts/tab_bar.gd
+msgid "Show in File Manager"
+msgstr "Показать в файловом менеджере"
#: src/ui_parts/display.gd
msgid "Visuals"
@@ -270,7 +311,7 @@ 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
+#: src/ui_widgets/confirm_dialog.gd src/ui_widgets/options_dialog.gd
msgid "Cancel"
msgstr "Отменить"
@@ -332,22 +373,6 @@ msgstr "Настройки"
msgid "Optimize"
msgstr "Оптимизировать"
-#: src/ui_parts/global_actions.gd src/ui_parts/good_file_dialog.gd
-msgid "Save SVG"
-msgstr "Сохранить SVG"
-
-#: 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 "Про программу…"
@@ -404,7 +429,8 @@ msgstr "Выбрать изображение"
msgid "Select"
msgstr "Выбрать"
-#: src/ui_parts/good_file_dialog.gd src/utils/TranslationUtils.gd
+#: src/ui_parts/good_file_dialog.gd src/utils/FileUtils.gd
+#: src/utils/TranslationUtils.gd
msgid "Save"
msgstr "Сохранить"
@@ -597,8 +623,8 @@ msgid "Invert zoom direction"
msgstr "Инвертировать направление масштабирования"
#: src/ui_parts/settings_menu.gd
-msgid "Wrap mouse"
-msgstr "Захватить мышу"
+msgid "Wrap-around panning"
+msgstr "Захват курсора при прокрутке"
#: src/ui_parts/settings_menu.gd
msgid "Use CTRL for zooming"
@@ -625,8 +651,8 @@ msgid "UI scale"
msgstr "Масштаб интерфейса"
#: src/ui_parts/settings_menu.gd
-msgid "Auto UI scale"
-msgstr "Автоматический маштаб интерфейса пользователя"
+msgid "Changes the scale factor for the interface."
+msgstr "Изменить масштаб пользовательского интерфейса."
#: src/ui_parts/settings_menu.gd
msgid "Language"
@@ -636,7 +662,7 @@ msgstr "Язык"
msgid "Import XML"
msgstr "Импортировать XML"
-#: src/ui_parts/settings_menu.gd src/ui_widgets/palette_config.gd
+#: src/ui_parts/settings_menu.gd
msgid "Paste XML"
msgstr "Вставить XML"
@@ -662,7 +688,7 @@ msgstr "Помощь"
#: src/ui_parts/settings_menu.gd
msgid "Reset all to default"
-msgstr "Восстановить по умолчанию"
+msgstr "Восстановить значения по умолчанию"
#: src/ui_parts/settings_menu.gd
msgid "Preset"
@@ -674,7 +700,7 @@ msgstr "Сохранять коментарии"
#: src/ui_parts/settings_menu.gd
msgid "Keep unrecognized XML structures"
-msgstr "Сохранять не распознанные XML"
+msgstr "Сохранять нераспознанные XML"
#: src/ui_parts/settings_menu.gd
msgid "Add trailing newline"
@@ -710,7 +736,7 @@ msgstr "Удалить начальный ноль"
#: src/ui_parts/settings_menu.gd
msgid "Use exponential when shorter"
-msgstr "Использовать экспоненту когда короткий"
+msgstr "Использовать экспоненту когда короче"
#: src/ui_parts/settings_menu.gd
msgid "Colors"
@@ -757,14 +783,16 @@ msgid "Remove unnecessary parameters"
msgstr "Удалить ненужные параметры"
#: src/ui_parts/settings_menu.gd
-msgid "Swaps zoom in and zoom out with the mouse wheel."
-msgstr "Поменяет местами увеличение и уменьшение масштаба через колесико мыши."
+msgid "Swaps the scroll directions for zooming in and zooming out."
+msgstr "Поменяет местами направление прокрутки при масштабировании."
#: src/ui_parts/settings_menu.gd
-msgid "Wraps the mouse cursor around when panning the viewport."
+msgid ""
+"Warps the cursor to the opposite side whenever it reaches a viewport "
+"boundary while panning."
msgstr ""
-"Курсор будет телепортироваться от одного до противоположного края при "
-"передвижении окна просмотра."
+"Курсор будет телепортироваться от одного до противоположного края на границе "
+"окна просмотра."
#: src/ui_parts/settings_menu.gd
msgid ""
@@ -795,67 +823,49 @@ msgstr ""
msgid "Changes the visual size and grabbing area of handles."
msgstr "Изменит визуальный размер и область захвата ручек редактирования."
-#: src/ui_parts/settings_menu.gd
-msgid "Changes the scale of the visual user interface."
-msgstr "Изменить масштаб пользовательского интерфейса."
+#: src/ui_parts/shortcut_panel.gd
+msgid "Horizontal strip"
+msgstr "Горизонтальная полоса"
-#: src/ui_parts/settings_menu.gd
-msgid "Scales the user interface based on the screen size."
-msgstr "Масштабировать интерфейс пользователя в зависимости от размера экрана."
+#: src/ui_parts/shortcut_panel.gd
+msgid "Vertical strip"
+msgstr "Вертикальная полоска"
+
+#: src/ui_parts/shortcut_panel.gd
+msgid "Horizontal with two rows"
+msgstr "Горизонтальная с двумя колонками"
#: src/ui_parts/shortcut_panel_config.gd
msgid "Configure Shortcut Panel"
-msgstr ""
+msgstr "Настроить в панели сочетаний клавиш"
#: src/ui_parts/shortcut_panel_config.gd
msgid "Layout"
-msgstr ""
-
-#: src/ui_parts/shortcut_panel_config.gd
-#, fuzzy
-msgid "Horizontal strip"
-msgstr "Горизонтальная линия до"
-
-#: src/ui_parts/shortcut_panel_config.gd
-#, fuzzy
-msgid "Horizontal with two rows"
-msgstr "Горизонтальная линия до"
-
-#: src/ui_parts/shortcut_panel_config.gd
-#, fuzzy
-msgid "Vertical strip"
-msgstr "Вертикальная линия до"
+msgstr "Макет"
#: src/ui_parts/tab_bar.gd
-#, fuzzy
msgid "Create tab"
-msgstr "Создать"
+msgstr "Создать вкладку"
-#: src/ui_parts/tab_bar.gd
-#, fuzzy
-msgid "Close all other tabs"
-msgstr "Скопировать весь текст"
+#: src/ui_parts/tab_bar.gd src/utils/TranslationUtils.gd
+msgid "Create a new tab"
+msgstr "Создать новую вкладку"
#: src/ui_parts/tab_bar.gd
-msgid "Close tabs to the left"
-msgstr ""
+msgid "Scroll backwards"
+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 "Создать новую папку"
+msgid "Scroll forwards"
+msgstr "Прокрутить вперед"
#: src/ui_parts/tab_bar.gd
-msgid "This SVG is not bound to a location on the computer yet."
-msgstr ""
+msgid "This SVG is not bound to a file location yet."
+msgstr "Это SVG изображение еще не связано с расположением файла."
#: src/ui_parts/update_menu.gd
-msgid "Include prereleases"
-msgstr "Включить пре-релизы"
+msgid "Show prereleases"
+msgstr "Показывать тестовые сборки"
#: src/ui_parts/update_menu.gd
msgid "Retry"
@@ -871,7 +881,7 @@ msgstr "Загрузка информации..."
#: src/ui_parts/update_menu.gd
msgid "Update check failed"
-msgstr "Не удалось проверит обновления"
+msgstr "Не удалось проверить обновления"
#: src/ui_parts/update_menu.gd
msgid "GodSVG is up-to-date."
@@ -883,11 +893,11 @@ msgstr "Новые версии"
#: src/ui_parts/zoom_menu.gd src/utils/TranslationUtils.gd
msgid "Zoom out"
-msgstr "Отдалить масштаб"
+msgstr "Уменьшить масштаб"
#: src/ui_parts/zoom_menu.gd src/utils/TranslationUtils.gd
msgid "Zoom in"
-msgstr "Приблизить масштаб"
+msgstr "Увеличить масштаб"
#: src/ui_parts/zoom_menu.gd src/utils/TranslationUtils.gd
msgid "Zoom reset"
@@ -919,10 +929,6 @@ msgstr "Вставить"
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 "Искать цвет"
@@ -944,13 +950,12 @@ msgid "Unnamed"
msgstr "Без названия"
#: src/ui_widgets/good_color_picker.gd
-#, fuzzy
msgid "Color keywords"
-msgstr "Цветовая пипетка"
+msgstr "Ключевые слова цвета"
#: src/ui_widgets/good_color_picker.gd
msgid "Eyedropper"
-msgstr ""
+msgstr "Пипетка"
#: src/ui_widgets/palette_config.gd
msgid "Unnamed palettes won't be shown."
@@ -962,19 +967,23 @@ msgstr "Несколько палитр не могут иметь одно и
#: src/ui_widgets/palette_config.gd
msgid "This palette has identically defined colors."
-msgstr "Эти палитры имеют идентичные цвета."
+msgstr "В этой палитре имеются цвета с идентичными названиями."
#: src/ui_widgets/palette_config.gd
msgid "Rename"
msgstr "Переименовать"
+#: src/ui_widgets/palette_config.gd
+msgid "Apply Preset"
+msgstr "Применить пресет"
+
#: src/ui_widgets/palette_config.gd
msgid "Copy as XML"
msgstr "Скопировать как XML"
#: src/ui_widgets/palette_config.gd
-msgid "Apply Preset"
-msgstr "Принять пресет"
+msgid "Save as XML"
+msgstr "Сохранить как XML"
#: src/ui_widgets/path_popup.gd src/ui_widgets/pathdata_field.gd
#: src/utils/TranslationUtils.gd
@@ -1023,7 +1032,7 @@ msgstr "Применить матрицу"
#: src/ui_widgets/transform_popup.gd
msgid "Insert Before"
-msgstr "Вставить после"
+msgstr "Вставить до"
#: src/ui_widgets/transform_popup.gd
msgid "New transform"
@@ -1051,56 +1060,93 @@ msgstr "Файл невозможно открыть."
#: src/utils/FileUtils.gd
msgid "Check if the file still exists in the selected file path."
-msgstr "Проверить, если файл все еще существует по выбранному пути."
+msgstr "Проверьте, что файл все еще существует по выбранному пути."
+
+#: src/utils/FileUtils.gd
+msgid "Save the file?"
+msgstr "Сохранить файл?"
+
+#: src/utils/FileUtils.gd
+msgid "Do you want to save this file?"
+msgstr "Вы хотите сохранить этот файл?"
+
+#: src/utils/FileUtils.gd
+msgid "Save the changes?"
+msgstr "Сохранить изменения?"
+
+#: src/utils/FileUtils.gd
+msgid "Your changes will be lost if you don't save them."
+msgstr "Ваши изменения будут утеряны если вы их не сохраните."
+
+#: src/utils/FileUtils.gd
+msgid "Don't save"
+msgstr "Не сохранять"
#: src/utils/FileUtils.gd
msgid "The imported file is already being edited inside GodSVG."
-msgstr ""
+msgstr "Импортированный файл уже редактируется в GodSVG."
#: src/utils/FileUtils.gd
-msgid "If you want to apply the unsaved file state, use \"Reset SVG\" instead."
+msgid "If you want to revert your edits since the last save, use {reset_svg}."
msgstr ""
+"Если вы хотите отменить внесенные после после сохранения изменения, "
+"воспользуйтесь {reset_svg}."
+
+#: src/utils/FileUtils.gd
+msgid "Do you want to save the changes made to {file_name}?"
+msgstr "Сохранить изменения в {file_name}?"
+
+#: src/utils/TranslationUtils.gd
+msgid "Save as"
+msgstr "Сохранить как"
+
+#: src/utils/TranslationUtils.gd
+msgid "Close tabs to the left"
+msgstr "Закрыть вкладки слева"
+
+#: src/utils/TranslationUtils.gd
+msgid "Close tabs to the right"
+msgstr "Закрыть вкладки справа"
+
+#: src/utils/TranslationUtils.gd
+msgid "Close all other tabs"
+msgstr "Закрыть все остальные вкладки"
#: src/utils/TranslationUtils.gd
msgid "Select the next tab"
-msgstr ""
+msgstr "Выбрать следующую вкладку"
#: src/utils/TranslationUtils.gd
msgid "Select the previous tab"
-msgstr ""
+msgstr "Выбрать предыдущую вкладку"
#: src/utils/TranslationUtils.gd
-#, fuzzy
msgid "Open SVG externally"
-msgstr "Открыть репозиторий GodSVG"
+msgstr "Открыть SVG в внешней программе"
#: src/utils/TranslationUtils.gd
msgid "Show SVG in File Manager"
-msgstr ""
+msgstr "Показать SVG в файловом менеджере"
#: src/utils/TranslationUtils.gd
-#, fuzzy
msgid "Select all"
-msgstr "Выбрать"
+msgstr "Выбрать все"
#: src/utils/TranslationUtils.gd
-#, fuzzy
msgid "Duplicate the selection"
-msgstr "Удалить все выбранные теги"
+msgstr "Дублировать выбранное"
#: src/utils/TranslationUtils.gd
msgid "Delete the selection"
msgstr "Удалить все выбранные теги"
#: src/utils/TranslationUtils.gd
-#, fuzzy
msgid "Move the selection up"
-msgstr "Подвинуть вверх все выбранные теги"
+msgstr "Подвинуть выбранное вверх"
#: src/utils/TranslationUtils.gd
-#, fuzzy
msgid "Move the selection down"
-msgstr "Подвинуть вниз все выбранные элементы"
+msgstr "Подвинуть выбранное вниз"
#: src/utils/TranslationUtils.gd
msgid "Find"
@@ -1108,7 +1154,7 @@ msgstr "Найти"
#: src/utils/TranslationUtils.gd
msgid "Show rasterized SVG"
-msgstr "Показать растеризованый SVG"
+msgstr "Показать растеризованный SVG"
#: src/utils/TranslationUtils.gd
msgid "Toggle snapping"
@@ -1202,128 +1248,16 @@ 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 "Активировать цвет"
-
-#~ msgid "Disable the color"
-#~ msgstr "Деактивировать цвет"
-
-#~ msgid "Invalid"
-#~ msgstr "Неправильно"
-
-#~ msgid "Final size"
-#~ msgstr "Финальный размер"
-
#~ msgid ""
-#~ "The file couldn't be opened.\n"
-#~ "Try checking the file path, ensure that the file is not deleted, or "
-#~ "choose a different file."
+#~ "If you want to apply the unsaved file state, use \"Reset SVG\" instead."
#~ msgstr ""
-#~ "Невозможно открыть этот файл.\n"
-#~ "Попробуйте проверить путь до файла, убедитесь что файл не удален или "
-#~ "выберите другой файл."
-
-#~ msgid "Enable snap"
-#~ msgstr "Включить привязку"
-
-#~ msgid "General"
-#~ msgstr "Общие"
+#~ "Если вы хотите применить не сохраненное состояние файла, используйте "
+#~ "\"Скинуть SVG\"."
-#~ msgid "Any changes will apply immediately."
-#~ msgstr "Любые изменения будут применены немедленно."
+#~ msgid "Auto UI scale"
+#~ msgstr "Автоматический маштаб интерфейса пользователя"
-#~ msgid "Enable autoformatting"
-#~ msgstr "Включить автоформатирование"
-
-#~ msgid "Number precision digits"
-#~ msgstr "Значащие цифры углов"
-
-#~ msgid "Angle precision digits"
-#~ msgstr "Значащие цифры чисел"
-
-#~ msgid "Remove zero padding"
-#~ msgstr "Удалить заполняющие нули"
-
-#~ msgid "Convert rgb format to hex"
-#~ msgstr "Конвертировать rgb формат в hex"
-
-#~ msgid "Convert named colors to hex"
-#~ msgstr "Конвертировать цвета с названиями в hex"
-
-#~ msgid "Use shorthand hex code"
-#~ msgstr "Использовать короткие hex коды"
-
-#~ msgid "Edit palette name"
-#~ msgstr "Редактировать название палитры"
-
-#~ msgid "Import SVG"
-#~ msgstr "Импортировать SVG"
-
-#~ msgid "Tag color"
-#~ msgstr "Цвет тега"
-
-#~ msgid "New tag"
-#~ msgstr "Новый тег"
-
-#~ msgid "Paths"
-#~ msgstr "Пути"
-
-#~ msgid "Remove"
-#~ msgstr "Удалить"
-
-#~ msgid "Import Reference Image"
-#~ msgstr "Импортировать референс-изображение"
-
-#~ msgid "Show Reference Image"
-#~ msgstr "Показать референс-изображение"
-
-#~ msgid "Overlay Reference Image"
-#~ msgstr "Наложить референс-изображение"
-
-#~ msgid "Unknown tag"
-#~ msgstr "Неизвестный тег"
-
-#~ msgid "Load Reference Image"
-#~ msgstr "Загрузить референс-изображение"
-
-#~ msgid "Documentation…"
-#~ msgstr "Документация…"
-
-#~ msgid "Couple/Decouple"
-#~ msgstr "Объединить/Разъединить"
-
-#~ msgid "Add color"
-#~ msgstr "Добавить цвет"
-
-#~ msgid "Remove plus sign"
-#~ msgstr "Удалить знак плюса"
-
-#~ msgid "Default value opacity"
-#~ msgstr "Стандартное значение непрозрачности"
-
-#, fuzzy
-#~ msgid "Clear association"
-#~ msgstr "Удалить"
+#~ msgid "Scales the interface automatically based on the screen size."
+#~ msgstr ""
+#~ "Автоматически масштабировать интерфейс пользователя в зависимости от "
+#~ "размера экрана."
diff --git a/assets/translations/uk.po b/translations/uk.po
similarity index 83%
rename from assets/translations/uk.po
rename to translations/uk.po
index 13cbbec..b79d9a3 100644
--- a/assets/translations/uk.po
+++ b/translations/uk.po
@@ -4,7 +4,7 @@ msgstr ""
"Project-Id-Version: GodSVG\n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
-"Last-Translator: volkov\n"
+"Last-Translator: \n"
"Language-Team: \n"
"Language: uk\n"
"MIME-Version: 1.0\n"
@@ -32,13 +32,21 @@ msgid "Check for updates?"
msgstr "Перевірити оновлення?"
#: src/autoload/HandlerGUI.gd
-msgid "This requires GodSVG to connect to the internet."
-msgstr "Ця опція потребує інтернет-з'єднання."
+msgid ""
+"This will connect to github.com to compare version numbers. No other data is "
+"collected or transmitted."
+msgstr ""
+"Це потребує з'єднання з github.com, щоб порівняти цифри версії. Жодна інша "
+"інформація не збирається і не передається."
#: src/autoload/HandlerGUI.gd src/ui_widgets/alert_dialog.gd
msgid "OK"
msgstr "Добре"
+#: src/autoload/HandlerGUI.gd
+msgid "Do you want to proceed?"
+msgstr "Ви дійсно бажаєте продовжити?"
+
#: src/autoload/HandlerGUI.gd
msgid "Export SVG"
msgstr "Експортувати SVG"
@@ -50,11 +58,18 @@ msgstr "Експортувати"
#: src/autoload/HandlerGUI.gd
msgid ""
-"The graphic can only be exported as SVG because its size is not defined. Do "
-"you want to proceed?"
+"The graphic can be exported only as SVG because its proportions are too "
+"extreme."
msgstr ""
-"Зображення можна експортувати тільки як SVG, оскільки його розміри не "
-"задані. Бажаєте продовжити?"
+"Це зображення може бути експортоване тільки як SVG, оскільки його пропорції "
+"занадто екстремальні."
+
+#: src/autoload/HandlerGUI.gd
+msgid ""
+"The graphic can be exported only as SVG because its size is not defined."
+msgstr ""
+"Це зображення може бути експортоване тільки як SVG, оскільки його розмір не "
+"завданий."
#: src/autoload/State.gd src/ui_parts/good_file_dialog.gd
#: src/ui_widgets/alert_dialog.gd
@@ -62,13 +77,12 @@ msgid "Alert!"
msgstr "Увага!"
#: src/autoload/State.gd src/ui_parts/tab_bar.gd src/utils/TranslationUtils.gd
-#, fuzzy
msgid "Close tab"
-msgstr "Закрити шлях"
+msgstr "Закрити вкладку"
#: src/autoload/State.gd
msgid "Restore"
-msgstr ""
+msgstr "Відновити"
#: src/autoload/State.gd
msgid "View in List"
@@ -101,13 +115,16 @@ msgstr "Вставити після"
#: src/autoload/State.gd
msgid "The last edited state of this tab could not be found."
-msgstr ""
+msgstr "Останній редагований стан цієї вкладки не може бути знайдено."
#: src/autoload/State.gd
+#, fuzzy
msgid ""
-"The tab is bound to the file path {file_path}. Do you want to restore from "
-"this path?"
+"The tab is bound to the file path {file_path}. Do you want to restore the "
+"SVG from this path?"
msgstr ""
+"Ця вкладка пов'язана з шляхом до файлу {file_path}. Бажаєте відновити з "
+"цього шляху?"
#: src/config_classes/Formatter.gd
msgid "Compact"
@@ -145,9 +162,13 @@ msgstr "3 або 6 значне шістнадцятирічне число"
msgid "6-digit hex"
msgstr "6 значне шістнадцятирічне число"
+#: src/config_classes/TabData.gd
+msgid "Empty"
+msgstr "Порожньо"
+
#: src/config_classes/TabData.gd
msgid "Unsaved"
-msgstr ""
+msgstr "Не збережено"
#: src/data_classes/BasicXNode.gd
msgid "Comment"
@@ -195,6 +216,22 @@ msgstr "Неправильне вкладення."
msgid "Close"
msgstr "Закрити"
+#: src/ui_parts/about_menu.gd
+msgid "Authors"
+msgstr "Автори"
+
+#: src/ui_parts/about_menu.gd
+msgid "Donors"
+msgstr "Благодійники"
+
+#: src/ui_parts/about_menu.gd
+msgid "License"
+msgstr "Ліцензія"
+
+#: src/ui_parts/about_menu.gd
+msgid "Third-party licenses"
+msgstr "Ліцензії третіх осіб"
+
#: src/ui_parts/about_menu.gd
msgid "Project Founder and Manager"
msgstr "Засновник і керівник проекту"
@@ -207,10 +244,6 @@ msgstr "Розробники"
msgid "Translators"
msgstr "Перекладачі"
-#: src/ui_parts/about_menu.gd
-msgid "Donors"
-msgstr "Благодійники"
-
#: src/ui_parts/about_menu.gd
msgid "Golden donors"
msgstr "Золоті благодійники"
@@ -219,21 +252,29 @@ msgstr "Золоті благодійники"
msgid "Diamond donors"
msgstr "Діамантові благодійники"
-#: src/ui_parts/about_menu.gd
-msgid "Authors"
-msgstr "Автори"
+#: src/ui_parts/code_editor.gd src/utils/TranslationUtils.gd
+msgid "Copy all text"
+msgstr "Скопіювати весь текст"
-#: src/ui_parts/about_menu.gd
-msgid "License"
-msgstr "Ліцензія"
+#: src/ui_parts/current_file_button.gd src/ui_parts/good_file_dialog.gd
+msgid "Save SVG"
+msgstr "Зберегти SVG"
-#: src/ui_parts/about_menu.gd
-msgid "Godot third-party components"
-msgstr "Компоненти Godot від третіх сторін"
+#: src/ui_parts/current_file_button.gd
+msgid "Save SVG as…"
+msgstr "Зберегти SVG як…"
-#: src/ui_parts/about_menu.gd
-msgid "Third-party licenses"
-msgstr "Ліцензії третіх осіб"
+#: src/ui_parts/current_file_button.gd src/utils/TranslationUtils.gd
+msgid "Reset SVG"
+msgstr "Скинути SVG"
+
+#: src/ui_parts/current_file_button.gd src/ui_parts/tab_bar.gd
+msgid "Open externally"
+msgstr "Відкрити у зовнішній програмі"
+
+#: src/ui_parts/current_file_button.gd src/ui_parts/tab_bar.gd
+msgid "Show in File Manager"
+msgstr "Показати у файловому менеджері"
#: src/ui_parts/display.gd
msgid "Visuals"
@@ -270,7 +311,7 @@ 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
+#: src/ui_widgets/confirm_dialog.gd src/ui_widgets/options_dialog.gd
msgid "Cancel"
msgstr "Скасувати"
@@ -331,22 +372,6 @@ msgstr "Налаштування"
msgid "Optimize"
msgstr "Оптимізувати"
-#: src/ui_parts/global_actions.gd src/ui_parts/good_file_dialog.gd
-msgid "Save SVG"
-msgstr "Зберегти SVG"
-
-#: 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 "Про додаток…"
@@ -403,7 +428,8 @@ msgstr "Обрати зображення"
msgid "Select"
msgstr "Обрати"
-#: src/ui_parts/good_file_dialog.gd src/utils/TranslationUtils.gd
+#: src/ui_parts/good_file_dialog.gd src/utils/FileUtils.gd
+#: src/utils/TranslationUtils.gd
msgid "Save"
msgstr "Зберегти"
@@ -596,8 +622,8 @@ msgid "Invert zoom direction"
msgstr "Інвертувати напрямок масштабування"
#: src/ui_parts/settings_menu.gd
-msgid "Wrap mouse"
-msgstr "Захопити мишу"
+msgid "Wrap-around panning"
+msgstr "Захопити курсор при гортанні"
#: src/ui_parts/settings_menu.gd
msgid "Use CTRL for zooming"
@@ -624,8 +650,8 @@ msgid "UI scale"
msgstr "Масштаб інтерфейсу"
#: src/ui_parts/settings_menu.gd
-msgid "Auto UI scale"
-msgstr "Автоматичний масштаб інтерфейсу"
+msgid "Changes the scale factor for the interface."
+msgstr "Змінити масштаб інтерфейсу користувача."
#: src/ui_parts/settings_menu.gd
msgid "Language"
@@ -635,7 +661,7 @@ msgstr "Мова"
msgid "Import XML"
msgstr "Імпортувати XML"
-#: src/ui_parts/settings_menu.gd src/ui_widgets/palette_config.gd
+#: src/ui_parts/settings_menu.gd
msgid "Paste XML"
msgstr "Вставити XML"
@@ -760,12 +786,13 @@ msgid "Remove unnecessary parameters"
msgstr "Видалити необов'язкові параметри"
#: src/ui_parts/settings_menu.gd
-msgid "Swaps zoom in and zoom out with the mouse wheel."
-msgstr ""
-"Міняє місцями збільшення і зменшення масштабування через коліщатко миші."
+msgid "Swaps the scroll directions for zooming in and zooming out."
+msgstr "Міняє напрям гортання при масштабуванні."
#: src/ui_parts/settings_menu.gd
-msgid "Wraps the mouse cursor around when panning the viewport."
+msgid ""
+"Warps the cursor to the opposite side whenever it reaches a viewport "
+"boundary while panning."
msgstr ""
"Курсор буде телепортуватися від одного краю до протилежного при пересуванні "
"вікна перегляду."
@@ -799,66 +826,49 @@ msgstr ""
msgid "Changes the visual size and grabbing area of handles."
msgstr "Змінює візуальний розмір та область захоплення для ручок."
-#: src/ui_parts/settings_menu.gd
-msgid "Changes the scale of the visual user interface."
-msgstr "Змінити масштаб інтерфейсу користувача."
+#: src/ui_parts/shortcut_panel.gd
+msgid "Horizontal strip"
+msgstr "Горизонтальна смужка"
-#: src/ui_parts/settings_menu.gd
-msgid "Scales the user interface based on the screen size."
-msgstr "Масштабувати інтерфейс користувача в залежності від розміру екрану."
+#: src/ui_parts/shortcut_panel.gd
+msgid "Vertical strip"
+msgstr "Вертикальна смужка"
+
+#: src/ui_parts/shortcut_panel.gd
+msgid "Horizontal with two rows"
+msgstr "Горизонтальна з двома рядками"
#: src/ui_parts/shortcut_panel_config.gd
msgid "Configure Shortcut Panel"
-msgstr ""
+msgstr "Налаштувати у панелі клавіатурних скорочень"
#: src/ui_parts/shortcut_panel_config.gd
msgid "Layout"
-msgstr ""
-
-#: src/ui_parts/shortcut_panel_config.gd
-#, fuzzy
-msgid "Horizontal strip"
-msgstr "Горизонтальна лінія до"
-
-#: src/ui_parts/shortcut_panel_config.gd
-#, fuzzy
-msgid "Horizontal with two rows"
-msgstr "Горизонтальна лінія до"
-
-#: src/ui_parts/shortcut_panel_config.gd
-#, fuzzy
-msgid "Vertical strip"
-msgstr "Вертикальна лінія до"
+msgstr "Макет"
#: src/ui_parts/tab_bar.gd
-#, fuzzy
msgid "Create tab"
-msgstr "Створити"
+msgstr "Створити вкладку"
-#: src/ui_parts/tab_bar.gd
-#, fuzzy
-msgid "Close all other tabs"
-msgstr "Скопіювати весь текст"
+#: src/ui_parts/tab_bar.gd src/utils/TranslationUtils.gd
+msgid "Create a new tab"
+msgstr "Створити нову вкладку"
#: src/ui_parts/tab_bar.gd
-msgid "Close tabs to the left"
-msgstr ""
+msgid "Scroll backwards"
+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 "Створити нову теку"
+msgid "Scroll forwards"
+msgstr "Гортати вперед"
#: src/ui_parts/tab_bar.gd
-msgid "This SVG is not bound to a location on the computer yet."
-msgstr ""
+msgid "This SVG is not bound to a file location yet."
+msgstr "Це SVG зображення поки що не пов'язано з розташуванням файла."
#: src/ui_parts/update_menu.gd
-msgid "Include prereleases"
+#, fuzzy
+msgid "Show prereleases"
msgstr "Включати тестові версії"
#: src/ui_parts/update_menu.gd
@@ -923,10 +933,6 @@ msgstr "Вставити"
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 "Шукати колір"
@@ -948,13 +954,12 @@ msgid "Unnamed"
msgstr "Без назви"
#: src/ui_widgets/good_color_picker.gd
-#, fuzzy
msgid "Color keywords"
-msgstr "Кольорова піпетка"
+msgstr "Ключові слова кольорів"
#: src/ui_widgets/good_color_picker.gd
msgid "Eyedropper"
-msgstr ""
+msgstr "Піпетка"
#: src/ui_widgets/palette_config.gd
msgid "Unnamed palettes won't be shown."
@@ -972,13 +977,17 @@ msgstr "Ця палітра має ідентичні кольори."
msgid "Rename"
msgstr "Перейменувати"
+#: src/ui_widgets/palette_config.gd
+msgid "Apply Preset"
+msgstr "Застосувати пресет"
+
#: src/ui_widgets/palette_config.gd
msgid "Copy as XML"
msgstr "Скопіювати як XML"
#: src/ui_widgets/palette_config.gd
-msgid "Apply Preset"
-msgstr "Застосувати пресет"
+msgid "Save as XML"
+msgstr "Зберегти як XML"
#: src/ui_widgets/path_popup.gd src/ui_widgets/pathdata_field.gd
#: src/utils/TranslationUtils.gd
@@ -1057,54 +1066,91 @@ msgstr "Цей файл неможливо відкрити."
msgid "Check if the file still exists in the selected file path."
msgstr "Перевірити, чи файл все щє існує по обраному шляху."
+#: src/utils/FileUtils.gd
+#, fuzzy
+msgid "Save the file?"
+msgstr "Зберегти .\"{format}\" файл"
+
+#: src/utils/FileUtils.gd
+#, fuzzy
+msgid "Do you want to save this file?"
+msgstr "Ви дійсно бажаєте продовжити?"
+
+#: src/utils/FileUtils.gd
+msgid "Save the changes?"
+msgstr ""
+
+#: src/utils/FileUtils.gd
+msgid "Your changes will be lost if you don't save them."
+msgstr ""
+
+#: src/utils/FileUtils.gd
+msgid "Don't save"
+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 revert your edits since the last save, use {reset_svg}."
msgstr ""
#: src/utils/FileUtils.gd
-msgid "If you want to apply the unsaved file state, use \"Reset SVG\" instead."
+msgid "Do you want to save the changes made to {file_name}?"
msgstr ""
+#: src/utils/TranslationUtils.gd
+msgid "Save as"
+msgstr "Зберегти як"
+
+#: src/utils/TranslationUtils.gd
+msgid "Close tabs to the left"
+msgstr "Закрити усі вкладки зліва"
+
+#: src/utils/TranslationUtils.gd
+msgid "Close tabs to the right"
+msgstr "Закрити усі вкладки справа"
+
+#: src/utils/TranslationUtils.gd
+msgid "Close all other tabs"
+msgstr "Закрити усі інші вкладки"
+
#: src/utils/TranslationUtils.gd
msgid "Select the next tab"
-msgstr ""
+msgstr "Обрати наступну вкладку"
#: src/utils/TranslationUtils.gd
msgid "Select the previous tab"
-msgstr ""
+msgstr "Обрати попередню вкладку"
#: src/utils/TranslationUtils.gd
-#, fuzzy
msgid "Open SVG externally"
-msgstr "Відкрити репозиторій GodSVG"
+msgstr "Відкрити SVG у зовнішній програмі"
#: src/utils/TranslationUtils.gd
msgid "Show SVG in File Manager"
-msgstr ""
+msgstr "Показати SVG у файловому менеджері"
#: src/utils/TranslationUtils.gd
-#, fuzzy
msgid "Select all"
-msgstr "Обрати"
+msgstr "Обрати усе"
#: src/utils/TranslationUtils.gd
-#, fuzzy
msgid "Duplicate the selection"
-msgstr "Видалити усі обрані теги"
+msgstr "Продублювати обране"
#: src/utils/TranslationUtils.gd
msgid "Delete the selection"
msgstr "Видалити усі обрані теги"
#: src/utils/TranslationUtils.gd
-#, fuzzy
msgid "Move the selection up"
-msgstr "Пересунути всі обрані елементи вгору"
+msgstr "Пересунути обране вгору"
#: src/utils/TranslationUtils.gd
-#, fuzzy
msgid "Move the selection down"
-msgstr "Пересунути всі обрані елементи вниз"
+msgstr "Пересунути обране вниз"
#: src/utils/TranslationUtils.gd
msgid "Find"
@@ -1207,116 +1253,16 @@ 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 "Активувати колір"
-
-#~ msgid "Disable the color"
-#~ msgstr "Деактивувати колір"
-
-#~ msgid "Invalid"
-#~ msgstr "Неправильно"
-
-#~ msgid "Final size"
-#~ msgstr "Фінальний розмір"
-
#~ msgid ""
-#~ "The file couldn't be opened.\n"
-#~ "Try checking the file path, ensure that the file is not deleted, or "
-#~ "choose a different file."
+#~ "If you want to apply the unsaved file state, use \"Reset SVG\" instead."
#~ msgstr ""
-#~ "Неможливо відкрити файл.\n"
-#~ "Спробуйте перевірити шлях до файлу, впевніться що файл не видалений або "
-#~ "оберіть інший файл."
-
-#~ msgid "Enable snap"
-#~ msgstr "Активувати прилипання"
-
-#~ msgid "General"
-#~ msgstr "Загальні"
-
-#~ msgid "Any changes will apply immediately."
-#~ msgstr "Будь-які зміни будуть застосовані одразу."
-
-#~ msgid "Enable autoformatting"
-#~ msgstr "Активувати автоформатування"
-
-#~ msgid "Number precision digits"
-#~ msgstr "Кількість цифр точності чисел"
-
-#~ msgid "Angle precision digits"
-#~ msgstr "Кількість цифр точності кутів"
-
-#~ msgid "Remove zero padding"
-#~ msgstr "Видалити заповнюючи нулі"
-
-#~ msgid "Convert rgb format to hex"
-#~ msgstr "Конвертувати rgb формат у hex"
-
-#~ msgid "Convert named colors to hex"
-#~ msgstr "Конвертувати кольори з назвами у hex"
-
-#~ msgid "Use shorthand hex code"
-#~ msgstr "Використовувати скорочені hex коди"
-
-#~ msgid "Edit palette name"
-#~ msgstr "Редагувати назву палітри"
+#~ "Якщо ви бажаєте застосувати не збережений стан файлу, використайте "
+#~ "\"Скинути SVG\"."
-#~ msgid "Import SVG"
-#~ msgstr "Імпортувати SVG"
+#~ msgid "Auto UI scale"
+#~ msgstr "Автоматичний масштаб інтерфейсу"
-#~ msgid "Tag color"
-#~ msgstr "Колір тегу"
-
-#~ msgid "New tag"
-#~ msgstr "Новий тег"
-
-#~ msgid "Paths"
-#~ msgstr "Шляхи"
-
-#~ msgid "Remove"
-#~ msgstr "Видалити"
-
-#~ msgid "Unknown tag"
-#~ msgstr "Невідомий тег"
-
-#~ msgid "Documentation…"
-#~ msgstr "Документація…"
-
-#~ msgid "Couple/Decouple"
-#~ msgstr "З’єднати/Роз’єднати"
-
-#~ msgid "Add color"
-#~ msgstr "Додати колір"
-
-#~ msgid "Remove plus sign"
-#~ msgstr "Видалити знак плюс"
-
-#~ msgid "Default value opacity"
-#~ msgstr "Стандартне значення непрозорості"
-
-#, fuzzy
-#~ msgid "Clear association"
-#~ msgstr "Видалити асоціацію"
+#~ msgid "Scales the interface automatically based on the screen size."
+#~ msgstr ""
+#~ "Автоматично масштабувати інтерфейс користувача в залежності від розміру "
+#~ "екрану."
diff --git a/assets/translations/zh.po b/translations/zh_CN.po
similarity index 89%
rename from assets/translations/zh.po
rename to translations/zh_CN.po
index e2741f6..748b4d1 100644
--- a/assets/translations/zh.po
+++ b/translations/zh_CN.po
@@ -6,7 +6,7 @@ msgstr ""
"PO-Revision-Date: \n"
"Last-Translator: Mike Lei \n"
"Language-Team: Hamster5295, Mike Lei\n"
-"Language: zh\n"
+"Language: zh_CN\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
@@ -32,13 +32,20 @@ msgid "Check for updates?"
msgstr "检查更新?"
#: src/autoload/HandlerGUI.gd
-msgid "This requires GodSVG to connect to the internet."
-msgstr "这需要 GodSVG 连接互联网。"
+msgid ""
+"This will connect to github.com to compare version numbers. No other data is "
+"collected or transmitted."
+msgstr ""
#: src/autoload/HandlerGUI.gd src/ui_widgets/alert_dialog.gd
msgid "OK"
msgstr "确定"
+#: src/autoload/HandlerGUI.gd
+#, fuzzy
+msgid "Do you want to proceed?"
+msgstr "您确定要退出 GodSVG 吗?"
+
#: src/autoload/HandlerGUI.gd
msgid "Export SVG"
msgstr "导出 SVG"
@@ -49,9 +56,16 @@ msgid "Export"
msgstr "导出"
#: src/autoload/HandlerGUI.gd
+#, fuzzy
+msgid ""
+"The graphic can be exported only as SVG because its proportions are too "
+"extreme."
+msgstr "此图像只能以 SVG 格式导出,因为其大小未定义。您确定要继续吗?"
+
+#: src/autoload/HandlerGUI.gd
+#, fuzzy
msgid ""
-"The graphic can only be exported as SVG because its size is not defined. Do "
-"you want to proceed?"
+"The graphic can be exported only as SVG because its size is not defined."
msgstr "此图像只能以 SVG 格式导出,因为其大小未定义。您确定要继续吗?"
#: src/autoload/State.gd src/ui_parts/good_file_dialog.gd
@@ -103,8 +117,8 @@ msgstr ""
#: src/autoload/State.gd
msgid ""
-"The tab is bound to the file path {file_path}. Do you want to restore from "
-"this path?"
+"The tab is bound to the file path {file_path}. Do you want to restore the "
+"SVG from this path?"
msgstr ""
#: src/config_classes/Formatter.gd
@@ -143,6 +157,10 @@ msgstr "3 位或 6 位十六进制"
msgid "6-digit hex"
msgstr "6 位十六进制"
+#: src/config_classes/TabData.gd
+msgid "Empty"
+msgstr ""
+
#: src/config_classes/TabData.gd
msgid "Unsaved"
msgstr ""
@@ -193,6 +211,22 @@ msgstr "错误的嵌套。"
msgid "Close"
msgstr "关闭"
+#: src/ui_parts/about_menu.gd
+msgid "Authors"
+msgstr "作者"
+
+#: src/ui_parts/about_menu.gd
+msgid "Donors"
+msgstr "捐赠者"
+
+#: src/ui_parts/about_menu.gd
+msgid "License"
+msgstr "协议"
+
+#: src/ui_parts/about_menu.gd
+msgid "Third-party licenses"
+msgstr "第三方协议"
+
#: src/ui_parts/about_menu.gd
msgid "Project Founder and Manager"
msgstr "项目创立者与管理者"
@@ -205,10 +239,6 @@ msgstr "开发者"
msgid "Translators"
msgstr "翻译者"
-#: src/ui_parts/about_menu.gd
-msgid "Donors"
-msgstr "捐赠者"
-
#: src/ui_parts/about_menu.gd
msgid "Golden donors"
msgstr "金牌捐赠者"
@@ -217,21 +247,30 @@ msgstr "金牌捐赠者"
msgid "Diamond donors"
msgstr "钻石捐赠者"
-#: src/ui_parts/about_menu.gd
-msgid "Authors"
-msgstr "作者"
+#: src/ui_parts/code_editor.gd src/utils/TranslationUtils.gd
+msgid "Copy all text"
+msgstr "复制所有文本"
-#: src/ui_parts/about_menu.gd
-msgid "License"
-msgstr "协议"
+#: src/ui_parts/current_file_button.gd src/ui_parts/good_file_dialog.gd
+msgid "Save SVG"
+msgstr "保存 SVG"
-#: src/ui_parts/about_menu.gd
-msgid "Godot third-party components"
-msgstr "Godot 第三方组件"
+#: src/ui_parts/current_file_button.gd
+#, fuzzy
+msgid "Save SVG as…"
+msgstr "保存 SVG"
-#: src/ui_parts/about_menu.gd
-msgid "Third-party licenses"
-msgstr "第三方协议"
+#: src/ui_parts/current_file_button.gd src/utils/TranslationUtils.gd
+msgid "Reset SVG"
+msgstr "重置 SVG"
+
+#: src/ui_parts/current_file_button.gd src/ui_parts/tab_bar.gd
+msgid "Open externally"
+msgstr ""
+
+#: src/ui_parts/current_file_button.gd src/ui_parts/tab_bar.gd
+msgid "Show in File Manager"
+msgstr ""
#: src/ui_parts/display.gd
msgid "Visuals"
@@ -267,7 +306,7 @@ 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
+#: src/ui_widgets/confirm_dialog.gd src/ui_widgets/options_dialog.gd
msgid "Cancel"
msgstr "取消"
@@ -328,22 +367,6 @@ msgstr "设置"
msgid "Optimize"
msgstr "优化"
-#: src/ui_parts/global_actions.gd src/ui_parts/good_file_dialog.gd
-msgid "Save SVG"
-msgstr "保存 SVG"
-
-#: 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 "关于…"
@@ -400,7 +423,8 @@ msgstr "选择图像"
msgid "Select"
msgstr "选择"
-#: src/ui_parts/good_file_dialog.gd src/utils/TranslationUtils.gd
+#: src/ui_parts/good_file_dialog.gd src/utils/FileUtils.gd
+#: src/utils/TranslationUtils.gd
msgid "Save"
msgstr "保存"
@@ -591,8 +615,8 @@ msgid "Invert zoom direction"
msgstr "反转缩放方向"
#: src/ui_parts/settings_menu.gd
-msgid "Wrap mouse"
-msgstr "连续拖拽"
+msgid "Wrap-around panning"
+msgstr ""
#: src/ui_parts/settings_menu.gd
msgid "Use CTRL for zooming"
@@ -619,8 +643,9 @@ msgid "UI scale"
msgstr "UI 缩放"
#: src/ui_parts/settings_menu.gd
-msgid "Auto UI scale"
-msgstr "自动 UI 大小"
+#, fuzzy
+msgid "Changes the scale factor for the interface."
+msgstr "改变用户界面的缩放尺寸。"
#: src/ui_parts/settings_menu.gd
msgid "Language"
@@ -630,7 +655,7 @@ msgstr "语言"
msgid "Import XML"
msgstr "导入 XML"
-#: src/ui_parts/settings_menu.gd src/ui_widgets/palette_config.gd
+#: src/ui_parts/settings_menu.gd
msgid "Paste XML"
msgstr "粘贴 XML"
@@ -754,12 +779,14 @@ msgid "Remove unnecessary parameters"
msgstr "移除不必要的参数"
#: src/ui_parts/settings_menu.gd
-msgid "Swaps zoom in and zoom out with the mouse wheel."
-msgstr "反转鼠标滚轮缩放控制。"
+msgid "Swaps the scroll directions for zooming in and zooming out."
+msgstr ""
#: src/ui_parts/settings_menu.gd
-msgid "Wraps the mouse cursor around when panning the viewport."
-msgstr "拖拽视图时使鼠标保持连续运动。"
+msgid ""
+"Warps the cursor to the opposite side whenever it reaches a viewport "
+"boundary while panning."
+msgstr ""
#: src/ui_parts/settings_menu.gd
msgid ""
@@ -785,66 +812,54 @@ msgstr "如果禁用,不论当前打开的文件是什么,窗口标题都将
msgid "Changes the visual size and grabbing area of handles."
msgstr "改变拖拽框的视觉和操作区域大小。"
-#: src/ui_parts/settings_menu.gd
-msgid "Changes the scale of the visual user interface."
-msgstr "改变用户界面的缩放尺寸。"
-
-#: src/ui_parts/settings_menu.gd
-msgid "Scales the user interface based on the screen size."
-msgstr "根据屏幕大小缩放用户界面。"
-
-#: src/ui_parts/shortcut_panel_config.gd
-msgid "Configure Shortcut Panel"
-msgstr ""
-
-#: src/ui_parts/shortcut_panel_config.gd
-msgid "Layout"
-msgstr ""
-
-#: src/ui_parts/shortcut_panel_config.gd
+#: src/ui_parts/shortcut_panel.gd
#, fuzzy
msgid "Horizontal strip"
msgstr "横线"
-#: src/ui_parts/shortcut_panel_config.gd
+#: src/ui_parts/shortcut_panel.gd
+#, fuzzy
+msgid "Vertical strip"
+msgstr "竖线"
+
+#: src/ui_parts/shortcut_panel.gd
#, fuzzy
msgid "Horizontal with two rows"
msgstr "横线"
#: src/ui_parts/shortcut_panel_config.gd
-#, fuzzy
-msgid "Vertical strip"
-msgstr "竖线"
+msgid "Configure Shortcut Panel"
+msgstr ""
+
+#: src/ui_parts/shortcut_panel_config.gd
+msgid "Layout"
+msgstr ""
#: src/ui_parts/tab_bar.gd
#, fuzzy
msgid "Create tab"
msgstr "创建"
-#: src/ui_parts/tab_bar.gd
+#: src/ui_parts/tab_bar.gd src/utils/TranslationUtils.gd
#, fuzzy
-msgid "Close all other tabs"
-msgstr "复制所有文本"
+msgid "Create a new tab"
+msgstr "新建文件夹"
#: src/ui_parts/tab_bar.gd
-msgid "Close tabs to the left"
+msgid "Scroll backwards"
msgstr ""
#: src/ui_parts/tab_bar.gd
-msgid "Close tabs to the right"
+msgid "Scroll forwards"
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."
+msgid "This SVG is not bound to a file location yet."
msgstr ""
#: src/ui_parts/update_menu.gd
-msgid "Include prereleases"
+#, fuzzy
+msgid "Show prereleases"
msgstr "包括预发布版本"
#: src/ui_parts/update_menu.gd
@@ -909,10 +924,6 @@ msgstr "粘贴"
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 "搜索颜色"
@@ -958,13 +969,18 @@ msgstr "此调色盘使用了完全相同的颜色定义。"
msgid "Rename"
msgstr "重命名"
+#: src/ui_widgets/palette_config.gd
+msgid "Apply Preset"
+msgstr "应用预设"
+
#: src/ui_widgets/palette_config.gd
msgid "Copy as XML"
msgstr "复制为 XML"
#: src/ui_widgets/palette_config.gd
-msgid "Apply Preset"
-msgstr "应用预设"
+#, fuzzy
+msgid "Save as XML"
+msgstr "复制为 XML"
#: src/ui_widgets/path_popup.gd src/ui_widgets/pathdata_field.gd
#: src/utils/TranslationUtils.gd
@@ -1043,14 +1059,58 @@ msgstr "无法打开文件。"
msgid "Check if the file still exists in the selected file path."
msgstr "检查文件是否还存在于选中的路径。"
+#: src/utils/FileUtils.gd
+#, fuzzy
+msgid "Save the file?"
+msgstr "保存 .\"{format}\" 文件"
+
+#: src/utils/FileUtils.gd
+#, fuzzy
+msgid "Do you want to save this file?"
+msgstr "您确定要退出 GodSVG 吗?"
+
+#: src/utils/FileUtils.gd
+msgid "Save the changes?"
+msgstr ""
+
+#: src/utils/FileUtils.gd
+msgid "Your changes will be lost if you don't save them."
+msgstr ""
+
+#: src/utils/FileUtils.gd
+msgid "Don't save"
+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."
+msgid "If you want to revert your edits since the last save, use {reset_svg}."
+msgstr ""
+
+#: src/utils/FileUtils.gd
+msgid "Do you want to save the changes made to {file_name}?"
+msgstr ""
+
+#: src/utils/TranslationUtils.gd
+#, fuzzy
+msgid "Save as"
+msgstr "保存"
+
+#: src/utils/TranslationUtils.gd
+msgid "Close tabs to the left"
msgstr ""
+#: src/utils/TranslationUtils.gd
+msgid "Close tabs to the right"
+msgstr ""
+
+#: src/utils/TranslationUtils.gd
+#, fuzzy
+msgid "Close all other tabs"
+msgstr "复制所有文本"
+
#: src/utils/TranslationUtils.gd
msgid "Select the next tab"
msgstr ""
@@ -1191,6 +1251,25 @@ msgstr ""
msgid "The file extension is empty. Only {extension_list} files are supported."
msgstr "文件扩展名为空。只有 {extension_list} 内的文件受支持。"
+#~ msgid "Auto UI scale"
+#~ msgstr "自动 UI 大小"
+
+#, fuzzy
+#~ msgid "Scales the interface automatically based on the screen size."
+#~ msgstr "根据屏幕大小缩放用户界面。"
+
+#~ msgid "Wrap mouse"
+#~ msgstr "连续拖拽"
+
+#~ msgid "Swaps zoom in and zoom out with the mouse wheel."
+#~ msgstr "反转鼠标滚轮缩放控制。"
+
+#~ msgid "Wraps the mouse cursor around when panning the viewport."
+#~ msgstr "拖拽视图时使鼠标保持连续运动。"
+
+#~ msgid "This requires GodSVG to connect to the internet."
+#~ msgstr "这需要 GodSVG 连接互联网。"
+
#, fuzzy
#~ msgid "Add new tab"
#~ msgstr "添加新元素"