Browse Source

Merge branch 'master' into release

Krzysztof Krysiński 1 week ago
parent
commit
92c3666eda
100 changed files with 4676 additions and 1829 deletions
  1. 23 0
      .github/workflows/localization-key-reference.yml
  2. 1 0
      .github/workflows/localization/check-key-references.pip.txt
  3. 70 0
      .github/workflows/localization/check-key-references.py
  4. 10 2
      src/ChunkyImageLib/Chunk.cs
  5. 6 6
      src/ChunkyImageLib/ChunkyImage.cs
  6. 8 7
      src/ChunkyImageLib/ChunkyImageEx.cs
  7. 2 2
      src/ChunkyImageLib/IReadOnlyChunkyImage.cs
  8. 5 5
      src/ChunkyImageLib/Operations/ChunkyImageOperation.cs
  9. 0 81
      src/Custom.ruleset
  10. 2 8
      src/Directory.Build.props
  11. 1 1
      src/Drawie
  12. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/SceneObjectRenderContext.cs
  13. 3 4
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CreateImageNode.cs
  14. 5 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Effects/OutlineNode.cs
  15. 84 27
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/FilterNodes/ApplyFilterNode.cs
  16. 15 4
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Image/MaskNode.cs
  17. 29 3
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ImageLayerNode.cs
  18. 14 4
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/LayerNode.cs
  19. 163 3
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/NoiseNode.cs
  20. 1 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/OutputNode.cs
  21. 11 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/RenderNode.cs
  22. 10 4
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ShaderNode.cs
  23. 32 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/LineNode.cs
  24. 37 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/RectangleNode.cs
  25. 7 4
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/StructureNode.cs
  26. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/TextureCache.cs
  27. 8 3
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/VectorLayerNode.cs
  28. 2 1
      src/PixiEditor.ChangeableDocument/Changes/NodeGraph/EvaluateGraph_Change.cs
  29. 21 6
      src/PixiEditor.ChangeableDocument/Rendering/DocumentRenderer.cs
  30. 5 3
      src/PixiEditor.ChangeableDocument/Rendering/RenderContext.cs
  31. 5 0
      src/PixiEditor.Extensions.CommonApi/UserPreferences/PreferencesConstants.cs
  32. 44 17
      src/PixiEditor.Extensions.CommonApi/UserPreferences/Settings/PixiEditor/PixiEditorSettings.cs
  33. BIN
      src/PixiEditor.Extensions.Sdk/build/PixiEditor.Api.CGlueMSBuild.dll
  34. 1 0
      src/PixiEditor.IdentityProvider.PixiAuth/PixiAuthIdentityProvider.cs
  35. 4 0
      src/PixiEditor.UI.Common/Accents/Base.axaml
  36. 0 1
      src/PixiEditor.sln
  37. 124 124
      src/PixiEditor/Data/Localization/Languages/ar.json
  38. 1133 1129
      src/PixiEditor/Data/Localization/Languages/en.json
  39. 106 100
      src/PixiEditor/Data/Localization/Languages/es.json
  40. 131 1
      src/PixiEditor/Data/Localization/Languages/ru.json
  41. 1127 0
      src/PixiEditor/Data/Localization/Languages/tr.json
  42. 10 3
      src/PixiEditor/Data/Localization/LocalizationData.json
  43. 27 17
      src/PixiEditor/Helpers/Converters/EnumToLocalizedStringConverter.cs
  44. 0 19
      src/PixiEditor/Helpers/EnumDescriptionConverter.cs
  45. 135 1
      src/PixiEditor/Helpers/Extensions/EnumerableExtensions.cs
  46. 18 0
      src/PixiEditor/Helpers/LocalizeEnumAttribute.cs
  47. 26 7
      src/PixiEditor/Models/DocumentModels/ActionAccumulator.cs
  48. 2 1
      src/PixiEditor/Models/DocumentModels/DocumentStructureHelper.cs
  49. 9 0
      src/PixiEditor/Models/DocumentModels/DocumentTransformMode.cs
  50. 3 2
      src/PixiEditor/Models/DocumentModels/Public/DocumentOperationsModule.cs
  51. 16 15
      src/PixiEditor/Models/DocumentModels/Public/DocumentStructureModule.cs
  52. 7 0
      src/PixiEditor/Models/DocumentPassthroughActions/RefreshPreviews_PassthroughAction.cs
  53. 126 0
      src/PixiEditor/Models/EnumTranslations.cs
  54. 2 1
      src/PixiEditor/Models/Handlers/IDocument.cs
  55. 11 7
      src/PixiEditor/Models/Handlers/INodeHandler.cs
  56. 1 0
      src/PixiEditor/Models/Handlers/INodePropertyHandler.cs
  57. 1 0
      src/PixiEditor/Models/Handlers/IToolsHandler.cs
  58. 5 1
      src/PixiEditor/Models/Handlers/Toolbars/PaintBrushShape.cs
  59. 29 11
      src/PixiEditor/Models/Rendering/AffectedAreasGatherer.cs
  60. 47 22
      src/PixiEditor/Models/Rendering/MemberPreviewUpdater.cs
  61. 40 3
      src/PixiEditor/Models/Rendering/PreviewPainter.cs
  62. 35 13
      src/PixiEditor/Models/Rendering/SceneRenderer.cs
  63. 144 0
      src/PixiEditor/Models/Structures/ObservableHashSet.cs
  64. 5 1
      src/PixiEditor/Models/Tools/BrightnessMode.cs
  65. 2 2
      src/PixiEditor/Properties/AssemblyInfo.cs
  66. 4 7
      src/PixiEditor/Styles/Templates/NodeFrameView.axaml
  67. 4 10
      src/PixiEditor/Styles/Templates/NodeGraphView.axaml
  68. 15 1
      src/PixiEditor/Styles/Templates/NodePicker.axaml
  69. 15 0
      src/PixiEditor/ViewModels/Document/DocumentViewModel.cs
  70. 81 0
      src/PixiEditor/ViewModels/Document/NodeGraphViewModel.cs
  71. 21 1
      src/PixiEditor/ViewModels/Document/Nodes/FilterNodes/ApplyFilterNodeViewModel.cs
  72. 1 1
      src/PixiEditor/ViewModels/Document/Nodes/ModifyImageLeftNodeViewModel.cs
  73. 1 1
      src/PixiEditor/ViewModels/Document/Nodes/ModifyImageRightNodeViewModel.cs
  74. 29 1
      src/PixiEditor/ViewModels/Document/Nodes/NoiseNodeViewModel.cs
  75. 9 0
      src/PixiEditor/ViewModels/Document/Nodes/Shapes/LineNodeViewModel.cs
  76. 9 0
      src/PixiEditor/ViewModels/Document/Nodes/Shapes/RectangleNodeViewModel.cs
  77. 2 2
      src/PixiEditor/ViewModels/Document/Nodes/StructureMemberViewModel.cs
  78. 2 1
      src/PixiEditor/ViewModels/Document/StructureTree.cs
  79. 6 0
      src/PixiEditor/ViewModels/Nodes/IPairNodeEndViewModel.cs
  80. 6 0
      src/PixiEditor/ViewModels/Nodes/IPairNodeStartViewModel.cs
  81. 2 28
      src/PixiEditor/ViewModels/Nodes/NodeFrameViewModel.cs
  82. 23 21
      src/PixiEditor/ViewModels/Nodes/NodeFrameViewModelBase.cs
  83. 2 0
      src/PixiEditor/ViewModels/Nodes/NodePropertyViewModel.cs
  84. 99 24
      src/PixiEditor/ViewModels/Nodes/NodeViewModel.cs
  85. 222 35
      src/PixiEditor/ViewModels/Nodes/NodeZoneViewModel.cs
  86. 19 0
      src/PixiEditor/ViewModels/Nodes/Traverse.cs
  87. 4 0
      src/PixiEditor/ViewModels/PixiObservableObject.cs
  88. 16 3
      src/PixiEditor/ViewModels/SettingsWindowViewModel.cs
  89. 17 0
      src/PixiEditor/ViewModels/SubViewModels/ToolsViewModel.cs
  90. 19 0
      src/PixiEditor/ViewModels/SubViewModels/ViewOptionsViewModel.cs
  91. 4 3
      src/PixiEditor/ViewModels/SubViewModels/ViewportWindowViewModel.cs
  92. 28 0
      src/PixiEditor/ViewModels/SubViewModels/WindowViewModel.cs
  93. 21 0
      src/PixiEditor/ViewModels/UserPreferences/Settings/PerformanceSettings.cs
  94. 9 0
      src/PixiEditor/ViewModels/UserPreferences/Settings/SceneSettings.cs
  95. 16 1
      src/PixiEditor/ViewModels/UserPreferences/SettingsGroup.cs
  96. 2 0
      src/PixiEditor/ViewModels/UserPreferences/SettingsViewModel.cs
  97. 1 0
      src/PixiEditor/Views/Dock/DocumentTemplate.axaml
  98. 5 9
      src/PixiEditor/Views/Main/CommandSearch/CommandSearchControl.axaml.cs
  99. 1 0
      src/PixiEditor/Views/Main/ViewportControls/Viewport.axaml
  100. 8 0
      src/PixiEditor/Views/Main/ViewportControls/Viewport.axaml.cs

+ 23 - 0
.github/workflows/localization-key-reference.yml

@@ -0,0 +1,23 @@
+name: Localization Reference Key Check
+
+on:
+  push:
+    branches: [ "master" ]
+  pull_request:
+    branches: [ "master" ]
+
+jobs:
+  check-key-references:
+    name: "Check Localization Keys are referenced"
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+      - uses: actions/setup-python@v5
+        with:
+          python-version: '3.13'
+          cache: 'pip'
+          cache-dependency-path: .github/workflows/localization/check-key-references.pip.txt
+      - name: Install dependencies
+        run: pip install -r .github/workflows/localization/check-key-references.pip.txt
+      - name: Check Localization Key References
+        run: python .github/workflows/localization/check-key-references.py

+ 1 - 0
.github/workflows/localization/check-key-references.pip.txt

@@ -0,0 +1 @@
+pyahocorasick

+ 70 - 0
.github/workflows/localization/check-key-references.py

@@ -0,0 +1,70 @@
+import json
+import os
+import logging
+from collections import OrderedDict
+import time
+import ahocorasick
+
+logging.basicConfig(level=logging.INFO)
+logger = logging.getLogger()
+
+# PATHS
+REFERENCE_LANGUAGE = "src/PixiEditor/Data/Localization/Languages/en.json"
+SEARCH_DIRECTORIES = ["src/PixiEditor/Views", "src/PixiEditor/ViewModels", "src/PixiEditor", "src/"]
+IGNORE_DIRECTORIES = ["src/PixiEditor/Data/Localization"]
+
+def load_json(file_path):
+    """Load language JSON"""
+    try:
+        with open(file_path, "r", encoding="utf-8-sig") as f:
+            return json.load(f, object_pairs_hook=OrderedDict)
+    except FileNotFoundError:
+        print(f"::error::File not found: {file_path}")
+        return OrderedDict()
+    except json.JSONDecodeError as e:
+        print(f"::error::Failed to parse JSON in {file_path}: {e}")
+        return OrderedDict()
+
+def build_automaton(keys: list[str]) -> ahocorasick.Automaton:
+    A = ahocorasick.Automaton()
+    for i, k in enumerate(keys):
+        A.add_word(k, (i, k))
+    A.make_automaton()
+    return A
+
+def find_missing_keys(keys):
+    automaton = build_automaton(keys)
+    present = set()
+
+    ignore_prefixes = tuple(os.path.abspath(p) for p in IGNORE_DIRECTORIES)
+    for base_dir in SEARCH_DIRECTORIES:
+        for root, dirs, files in os.walk(base_dir, topdown=True):
+            dirs[:] = [d for d in dirs if not os.path.abspath(os.path.join(root, d)).startswith(ignore_prefixes)]
+            for file in files:
+                with open(os.path.join(root, file), "r", encoding="utf‑8", errors="ignore") as f:
+                    for _, (_, k) in automaton.iter(f.read()):
+                        present.add(k)
+                        if len(present) == len(keys):
+                            return []
+    return sorted(set(keys) - present)
+
+def main():
+    keys = load_json(REFERENCE_LANGUAGE)
+
+    print("Searching trough keys...")
+    start = time.time()
+    missing_keys = find_missing_keys(keys)
+    end = time.time()
+    print(f"Done, searching took {end - start}s")
+
+    if len(missing_keys) > 0:
+        print("Unreferenced keys have been found")
+        for key in missing_keys:
+            print(f"::error file={REFERENCE_LANGUAGE},title=Unreferenced key::No reference to '{key}' found")
+        return 1
+    else:
+        print("All keys have been referenced")
+        return 0
+    
+if __name__ == "__main__":
+    exit(main())

+ 10 - 2
src/ChunkyImageLib/Chunk.cs

@@ -83,9 +83,17 @@ public class Chunk : IDisposable
     /// </summary>
     /// </summary>
     /// <param name="pos">The destination for the <paramref name="surface"/></param>
     /// <param name="pos">The destination for the <paramref name="surface"/></param>
     /// <param name="paint">The paint to use while drawing</param>
     /// <param name="paint">The paint to use while drawing</param>
-    public void DrawChunkOn(DrawingSurface surface, VecD pos, Paint? paint = null)
+    public void DrawChunkOn(DrawingSurface surface, VecD pos, Paint? paint = null, SamplingOptions? samplingOptions = null)
     {
     {
-        surface.Canvas.DrawSurface(Surface.DrawingSurface, (float)pos.X, (float)pos.Y, paint);
+        if (samplingOptions == null || samplingOptions == SamplingOptions.Default)
+        {
+            surface.Canvas.DrawSurface(Surface.DrawingSurface, (float)pos.X, (float)pos.Y, paint);
+        }
+        else
+        {
+            using var snapshot = Surface.DrawingSurface.Snapshot();
+            surface.Canvas.DrawImage(snapshot, (float)pos.X, (float)pos.Y, samplingOptions.Value, paint);
+        }
     }
     }
 
 
     public unsafe RectI? FindPreciseBounds(RectI? passedSearchRegion = null)
     public unsafe RectI? FindPreciseBounds(RectI? passedSearchRegion = null)

+ 6 - 6
src/ChunkyImageLib/ChunkyImage.cs

@@ -365,7 +365,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
     /// </returns>
     /// </returns>
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
     public bool DrawMostUpToDateChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecD pos,
     public bool DrawMostUpToDateChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecD pos,
-        Paint? paint = null)
+        Paint? paint = null, SamplingOptions? samplingOptions = null)
     {
     {
         lock (lockObject)
         lock (lockObject)
         {
         {
@@ -390,7 +390,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
             {
             {
                 if (committedChunk is null)
                 if (committedChunk is null)
                     return false;
                     return false;
-                committedChunk.DrawChunkOn(surface, pos, paint);
+                committedChunk.DrawChunkOn(surface, pos, paint, samplingOptions);
                 return true;
                 return true;
             }
             }
 
 
@@ -399,7 +399,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
             {
             {
                 if (latestChunk.IsT2)
                 if (latestChunk.IsT2)
                 {
                 {
-                    latestChunk.AsT2.DrawChunkOn(surface, pos, paint);
+                    latestChunk.AsT2.DrawChunkOn(surface, pos, paint, samplingOptions);
                     return true;
                     return true;
                 }
                 }
 
 
@@ -415,7 +415,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
                 blendModePaint);
                 blendModePaint);
             if (lockTransparency)
             if (lockTransparency)
                 OperationHelper.ClampAlpha(tempChunk.Surface.DrawingSurface, committedChunk.Surface.DrawingSurface);
                 OperationHelper.ClampAlpha(tempChunk.Surface.DrawingSurface, committedChunk.Surface.DrawingSurface);
-            tempChunk.DrawChunkOn(surface, pos, paint);
+            tempChunk.DrawChunkOn(surface, pos, paint, samplingOptions);
 
 
             return true;
             return true;
         }
         }
@@ -458,7 +458,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
 
 
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
     public bool DrawCommittedChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecD pos,
     public bool DrawCommittedChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecD pos,
-        Paint? paint = null)
+        Paint? paint = null, SamplingOptions? samplingOptions = null)
     {
     {
         lock (lockObject)
         lock (lockObject)
         {
         {
@@ -466,7 +466,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
             var chunk = GetCommittedChunk(chunkPos, resolution);
             var chunk = GetCommittedChunk(chunkPos, resolution);
             if (chunk is null)
             if (chunk is null)
                 return false;
                 return false;
-            chunk.DrawChunkOn(surface, pos, paint);
+            chunk.DrawChunkOn(surface, pos, paint, samplingOptions);
             return true;
             return true;
         }
         }
     }
     }

+ 8 - 7
src/ChunkyImageLib/ChunkyImageEx.cs

@@ -19,9 +19,10 @@ public static class IReadOnlyChunkyImageEx
     /// <param name="pos">Starting position on the surface</param>
     /// <param name="pos">Starting position on the surface</param>
     /// <param name="paint">Paint to use for drawing</param>
     /// <param name="paint">Paint to use for drawing</param>
     public static void DrawMostUpToDateRegionOn
     public static void DrawMostUpToDateRegionOn
-        (this IReadOnlyChunkyImage image, RectI fullResRegion, ChunkResolution resolution, DrawingSurface surface, VecD pos, Paint? paint = null)
+    (this IReadOnlyChunkyImage image, RectI fullResRegion, ChunkResolution resolution, DrawingSurface surface,
+        VecD pos, Paint? paint = null, SamplingOptions? sampling = null)
     {
     {
-        DrawRegionOn(fullResRegion, resolution, surface, pos, image.DrawMostUpToDateChunkOn, paint);
+        DrawRegionOn(fullResRegion, resolution, surface, pos, image.DrawMostUpToDateChunkOn, paint, sampling);
     }
     }
     
     
     /// <summary>
     /// <summary>
@@ -35,9 +36,9 @@ public static class IReadOnlyChunkyImageEx
     /// <param name="pos">Starting position on the surface</param>
     /// <param name="pos">Starting position on the surface</param>
     /// <param name="paint">Paint to use for drawing</param>
     /// <param name="paint">Paint to use for drawing</param>
     public static void DrawCommittedRegionOn
     public static void DrawCommittedRegionOn
-        (this IReadOnlyChunkyImage image, RectI fullResRegion, ChunkResolution resolution, DrawingSurface surface, VecI pos, Paint? paint = null)
+        (this IReadOnlyChunkyImage image, RectI fullResRegion, ChunkResolution resolution, DrawingSurface surface, VecI pos, Paint? paint = null, SamplingOptions? samplingOptions = null)
     {
     {
-        DrawRegionOn(fullResRegion, resolution, surface, pos, image.DrawCommittedChunkOn, paint);
+        DrawRegionOn(fullResRegion, resolution, surface, pos, image.DrawCommittedChunkOn, paint, samplingOptions);
     }
     }
     
     
     private static void DrawRegionOn(
     private static void DrawRegionOn(
@@ -45,8 +46,8 @@ public static class IReadOnlyChunkyImageEx
         ChunkResolution resolution,
         ChunkResolution resolution,
         DrawingSurface surface,
         DrawingSurface surface,
         VecD pos,
         VecD pos,
-        Func<VecI, ChunkResolution, DrawingSurface, VecD, Paint?, bool> drawingFunc,
-        Paint? paint = null)
+        Func<VecI, ChunkResolution, DrawingSurface, VecD, Paint?, SamplingOptions?, bool> drawingFunc,
+        Paint? paint = null, SamplingOptions? samplingOptions = null)
     {
     {
         int count = surface.Canvas.Save();
         int count = surface.Canvas.Save();
         surface.Canvas.ClipRect(new RectD(pos, fullResRegion.Size));
         surface.Canvas.ClipRect(new RectD(pos, fullResRegion.Size));
@@ -61,7 +62,7 @@ public static class IReadOnlyChunkyImageEx
             for (int i = chunkTopLeft.X; i <= chunkBotRight.X; i++)
             for (int i = chunkTopLeft.X; i <= chunkBotRight.X; i++)
             {
             {
                 var chunkPos = new VecI(i, j);
                 var chunkPos = new VecI(i, j);
-                drawingFunc(chunkPos, resolution, surface, offsetTargetRes + (chunkPos - chunkTopLeft) * resolution.PixelSize() + pos, paint);
+                drawingFunc(chunkPos, resolution, surface, offsetTargetRes + (chunkPos - chunkTopLeft) * resolution.PixelSize() + pos, paint, samplingOptions);
             }
             }
         }
         }
 
 

+ 2 - 2
src/ChunkyImageLib/IReadOnlyChunkyImage.cs

@@ -10,8 +10,8 @@ namespace ChunkyImageLib;
 
 
 public interface IReadOnlyChunkyImage
 public interface IReadOnlyChunkyImage
 {
 {
-    bool DrawMostUpToDateChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecD pos, Paint? paint = null);
-    bool DrawCommittedChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecD pos, Paint? paint = null);
+    bool DrawMostUpToDateChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecD pos, Paint? paint = null, SamplingOptions? sampling = null);
+    bool DrawCommittedChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecD pos, Paint? paint = null, SamplingOptions? sampling = null);
     RectI? FindChunkAlignedMostUpToDateBounds();
     RectI? FindChunkAlignedMostUpToDateBounds();
     RectI? FindChunkAlignedCommittedBounds();
     RectI? FindChunkAlignedCommittedBounds();
     RectI? FindTightCommittedBounds(ChunkResolution precision = ChunkResolution.Full, bool fallbackToChunkAligned = false);
     RectI? FindTightCommittedBounds(ChunkResolution precision = ChunkResolution.Full, bool fallbackToChunkAligned = false);

+ 5 - 5
src/ChunkyImageLib/Operations/ChunkyImageOperation.cs

@@ -63,13 +63,13 @@ internal class ChunkyImageOperation : IMirroredDrawOperation
         VecI bottomLeft = OperationHelper.GetChunkPos(
         VecI bottomLeft = OperationHelper.GetChunkPos(
             new VecI(chunkCenterOnImage.X - halfChunk.X, chunkCenterOnImage.Y + halfChunk.Y), ChunkyImage.FullChunkSize);
             new VecI(chunkCenterOnImage.X - halfChunk.X, chunkCenterOnImage.Y + halfChunk.Y), ChunkyImage.FullChunkSize);
 
 
-        Func<VecI, ChunkResolution, DrawingSurface, VecD, Paint?, bool> drawMethod = drawUpToDate ? imageToDraw.DrawMostUpToDateChunkOn : imageToDraw.DrawCommittedChunkOn;
+        Func<VecI, ChunkResolution, DrawingSurface, VecD, Paint?, SamplingOptions?, bool> drawMethod = drawUpToDate ? imageToDraw.DrawMostUpToDateChunkOn : imageToDraw.DrawCommittedChunkOn;
         
         
         drawMethod(
         drawMethod(
             topLeft,
             topLeft,
             targetChunk.Resolution,
             targetChunk.Resolution,
             targetChunk.Surface.DrawingSurface,
             targetChunk.Surface.DrawingSurface,
-            (VecI)((topLeft * ChunkyImage.FullChunkSize - chunkCenterOnImage).Add(ChunkyImage.FullChunkSize / 2) * targetChunk.Resolution.Multiplier()), null);
+            (VecI)((topLeft * ChunkyImage.FullChunkSize - chunkCenterOnImage).Add(ChunkyImage.FullChunkSize / 2) * targetChunk.Resolution.Multiplier()), null, null);
 
 
         VecI gridShift = targetPos % ChunkyImage.FullChunkSize;
         VecI gridShift = targetPos % ChunkyImage.FullChunkSize;
         if (gridShift.X != 0)
         if (gridShift.X != 0)
@@ -79,7 +79,7 @@ internal class ChunkyImageOperation : IMirroredDrawOperation
             targetChunk.Resolution,
             targetChunk.Resolution,
             targetChunk.Surface.DrawingSurface,
             targetChunk.Surface.DrawingSurface,
             (VecI)((topRight * ChunkyImage.FullChunkSize - chunkCenterOnImage).Add(ChunkyImage.FullChunkSize / 2) * targetChunk.Resolution.Multiplier()),
             (VecI)((topRight * ChunkyImage.FullChunkSize - chunkCenterOnImage).Add(ChunkyImage.FullChunkSize / 2) * targetChunk.Resolution.Multiplier()),
-            null);
+            null, null);
         }
         }
         if (gridShift.Y != 0)
         if (gridShift.Y != 0)
         {
         {
@@ -88,7 +88,7 @@ internal class ChunkyImageOperation : IMirroredDrawOperation
             targetChunk.Resolution,
             targetChunk.Resolution,
             targetChunk.Surface.DrawingSurface,
             targetChunk.Surface.DrawingSurface,
             (VecI)((bottomLeft * ChunkyImage.FullChunkSize - chunkCenterOnImage).Add(ChunkyImage.FullChunkSize / 2) * targetChunk.Resolution.Multiplier()),
             (VecI)((bottomLeft * ChunkyImage.FullChunkSize - chunkCenterOnImage).Add(ChunkyImage.FullChunkSize / 2) * targetChunk.Resolution.Multiplier()),
-            null);
+            null, null);
         }
         }
         if (gridShift.X != 0 && gridShift.Y != 0)
         if (gridShift.X != 0 && gridShift.Y != 0)
         {
         {
@@ -97,7 +97,7 @@ internal class ChunkyImageOperation : IMirroredDrawOperation
             targetChunk.Resolution,
             targetChunk.Resolution,
             targetChunk.Surface.DrawingSurface,
             targetChunk.Surface.DrawingSurface,
             (VecI)((bottomRight * ChunkyImage.FullChunkSize - chunkCenterOnImage).Add(ChunkyImage.FullChunkSize / 2) * targetChunk.Resolution.Multiplier()),
             (VecI)((bottomRight * ChunkyImage.FullChunkSize - chunkCenterOnImage).Add(ChunkyImage.FullChunkSize / 2) * targetChunk.Resolution.Multiplier()),
-            null);
+            null, null);
         }
         }
 
 
         targetChunk.Surface.DrawingSurface.Canvas.Restore();
         targetChunk.Surface.DrawingSurface.Canvas.Restore();

+ 0 - 81
src/Custom.ruleset

@@ -13,85 +13,4 @@
     <Rule Id="CA1303" Action="None" />
     <Rule Id="CA1303" Action="None" />
     <Rule Id="CA1416" Action="None" />
     <Rule Id="CA1416" Action="None" />
   </Rules>
   </Rules>
-  <Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.Analyzers">
-    <Rule Id="SA0001" Action="None" />
-    <Rule Id="SA1000" Action="None" />
-    <Rule Id="SA1005" Action="None" />
-    <Rule Id="SA1008" Action="None" />
-    <Rule Id="SA1009" Action="None" />
-    <Rule Id="SA1011" Action="None" />
-    <Rule Id="SA1023" Action="None" />
-    <Rule Id="SA1028" Action="None" />
-    <Rule Id="SA1101" Action="None" />
-    <Rule Id="SA1110" Action="None" />
-    <Rule Id="SA1111" Action="None" />
-    <Rule Id="SA1112" Action="None" />
-    <Rule Id="SA1117" Action="None" />
-    <Rule Id="SA1119" Action="None" />
-    <Rule Id="SA1121" Action="None" />
-    <Rule Id="SA1122" Action="None" />
-    <Rule Id="SA1124" Action="None" />
-    <Rule Id="SA1127" Action="None" />
-    <Rule Id="SA1128" Action="None" />
-    <Rule Id="SA1129" Action="None" />
-    <Rule Id="SA1130" Action="None" />
-    <Rule Id="SA1132" Action="None" />
-    <Rule Id="SA1135" Action="None" />
-    <Rule Id="SA1136" Action="None" />
-    <Rule Id="SA1139" Action="None" />
-    <Rule Id="SA1200" Action="None" />
-    <Rule Id="SA1201" Action="None" />
-    <Rule Id="SA1202" Action="None" />
-    <Rule Id="SA1204" Action="None" />
-    <Rule Id="SA1207" Action="None" />
-    <Rule Id="SA1208" Action="None" />
-    <Rule Id="SA1209" Action="None" />
-    <Rule Id="SA1210" Action="None" />
-    <Rule Id="SA1211" Action="None" />
-    <Rule Id="SA1214" Action="None" />
-    <Rule Id="SA1216" Action="None" />
-    <Rule Id="SA1217" Action="None" />
-    <Rule Id="SA1303" Action="None" />
-    <Rule Id="SA1304" Action="None" />
-    <Rule Id="SA1307" Action="None" />
-    <Rule Id="SA1309" Action="None" />
-    <Rule Id="SA1310" Action="None" />
-    <Rule Id="SA1311" Action="None" />
-    <Rule Id="SA1313" Action="None" />
-    <Rule Id="SA1316" Action="None" />
-    <Rule Id="SA1400" Action="None" />
-    <Rule Id="SA1401" Action="None" />
-    <Rule Id="SA1402" Action="None" />
-    <Rule Id="SA1405" Action="None" />
-    <Rule Id="SA1406" Action="None" />
-    <Rule Id="SA1407" Action="None" />
-    <Rule Id="SA1408" Action="None" />
-    <Rule Id="SA1410" Action="None" />
-    <Rule Id="SA1411" Action="None" />
-    <Rule Id="SA1413" Action="None" />
-    <Rule Id="SA1501" Action="None" />
-    <Rule Id="SA1502" Action="None" />
-    <Rule Id="SA1503" Action="None" />
-    <Rule Id="SA1505" Action="None" />
-    <Rule Id="SA1507" Action="None" />
-    <Rule Id="SA1508" Action="None" />
-    <Rule Id="SA1512" Action="None" />
-    <Rule Id="SA1513" Action="None" />
-    <Rule Id="SA1515" Action="None" />
-    <Rule Id="SA1516" Action="None" />
-    <Rule Id="SA1518" Action="None" />
-    <Rule Id="SA1600" Action="None" />
-    <Rule Id="SA1601" Action="None" />
-    <Rule Id="SA1602" Action="None" />
-    <Rule Id="SA1604" Action="None" />
-    <Rule Id="SA1605" Action="None" />
-    <Rule Id="SA1606" Action="None" />
-    <Rule Id="SA1607" Action="None" />
-    <Rule Id="SA1623" Action="None" />
-    <Rule Id="SA1629" Action="None" />
-    <Rule Id="SA1633" Action="None" />
-    <Rule Id="SA1642" Action="None" />
-    <Rule Id="SA1643" Action="None" />
-    <Rule Id="SA1648" Action="None" />
-  </Rules>
 </RuleSet>
 </RuleSet>

+ 2 - 8
src/Directory.Build.props

@@ -1,15 +1,9 @@
 <Project>
 <Project>
     <PropertyGroup>
     <PropertyGroup>
-        <CodeAnalysisRuleSet>../Custom.ruleset</CodeAnalysisRuleSet>
+        <CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)Custom.ruleset</CodeAnalysisRuleSet>
 		    <AvaloniaVersion>11.3.0</AvaloniaVersion>
 		    <AvaloniaVersion>11.3.0</AvaloniaVersion>
     </PropertyGroup>
     </PropertyGroup>
-    <ItemGroup>
-        <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />
-    </ItemGroup>
-    <ItemGroup>
-        <AdditionalFiles Include="$(SolutionDir)/stylecop.json" />
-    </ItemGroup>
-
+  
   <PropertyGroup Condition="$([MSBuild]::IsOsPlatform('Windows')) AND '$(Platform)' == 'x64'">
   <PropertyGroup Condition="$([MSBuild]::IsOsPlatform('Windows')) AND '$(Platform)' == 'x64'">
     <RuntimeIdentifier>win-x64</RuntimeIdentifier>
     <RuntimeIdentifier>win-x64</RuntimeIdentifier>
   </PropertyGroup>
   </PropertyGroup>

+ 1 - 1
src/Drawie

@@ -1 +1 @@
-Subproject commit 1be85ac9f4bc6b584e6a3a5a3d0287201c6a5f03
+Subproject commit b6c34c96ac5b01abad69604465445270270270d2

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/SceneObjectRenderContext.cs

@@ -13,7 +13,7 @@ public class SceneObjectRenderContext : RenderContext
     public RenderOutputProperty TargetPropertyOutput { get; }
     public RenderOutputProperty TargetPropertyOutput { get; }
 
 
     public SceneObjectRenderContext(RenderOutputProperty targetPropertyOutput, DrawingSurface surface, RectD localBounds, KeyFrameTime frameTime,
     public SceneObjectRenderContext(RenderOutputProperty targetPropertyOutput, DrawingSurface surface, RectD localBounds, KeyFrameTime frameTime,
-        ChunkResolution chunkResolution, VecI renderOutputSize, VecI documentSize, bool renderSurfaceIsScene, ColorSpace processingColorSpace, double opacity) : base(surface, frameTime, chunkResolution, renderOutputSize, documentSize, processingColorSpace, opacity)
+        ChunkResolution chunkResolution, VecI renderOutputSize, VecI documentSize, bool renderSurfaceIsScene, ColorSpace processingColorSpace, SamplingOptions desiredSampling, double opacity) : base(surface, frameTime, chunkResolution, renderOutputSize, documentSize, processingColorSpace, desiredSampling, opacity)
     {
     {
         TargetPropertyOutput = targetPropertyOutput;
         TargetPropertyOutput = targetPropertyOutput;
         LocalBounds = localBounds;
         LocalBounds = localBounds;

+ 3 - 4
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CreateImageNode.cs

@@ -72,10 +72,9 @@ public class CreateImageNode : Node, IPreviewRenderable
 
 
         int saved = surface.DrawingSurface.Canvas.Save();
         int saved = surface.DrawingSurface.Canvas.Save();
 
 
-        RenderContext ctx = new RenderContext(surface.DrawingSurface, context.FrameTime, context.ChunkResolution,
-            surface.Size, context.DocumentSize, context.ProcessingColorSpace);
-        ctx.FullRerender = context.FullRerender;
-        ctx.TargetOutput = context.TargetOutput;
+        RenderContext ctx = context.Clone();
+        ctx.RenderSurface = surface.DrawingSurface;
+        ctx.RenderOutputSize = surface.Size;
 
 
         float chunkMultiplier = (float)context.ChunkResolution.Multiplier();
         float chunkMultiplier = (float)context.ChunkResolution.Multiplier();
 
 

+ 5 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Effects/OutlineNode.cs

@@ -88,7 +88,8 @@ public class OutlineNode : RenderNode, IRenderInput
 
 
             var ctx = context.Clone();
             var ctx = context.Clone();
             ctx.ChunkResolution = ChunkResolution.Full;
             ctx.ChunkResolution = ChunkResolution.Full;
-            ctx.RenderOutputSize = (VecI)(context.RenderOutputSize * context.ChunkResolution.InvertedMultiplier());
+            bool isAdjusted = context.DocumentSize == context.RenderOutputSize;
+            ctx.RenderOutputSize = isAdjusted ? context.RenderOutputSize : (VecI)(context.RenderOutputSize * context.ChunkResolution.InvertedMultiplier());
 
 
             Background.Value.Paint(ctx, temp.DrawingSurface);
             Background.Value.Paint(ctx, temp.DrawingSurface);
 
 
@@ -124,7 +125,10 @@ public class OutlineNode : RenderNode, IRenderInput
 
 
     public override bool RenderPreview(DrawingSurface renderOn, RenderContext context, string elementToRenderName)
     public override bool RenderPreview(DrawingSurface renderOn, RenderContext context, string elementToRenderName)
     {
     {
+        int saved = renderOn.Canvas.Save();
+        renderOn.Canvas.Scale((float)context.ChunkResolution.Multiplier());
         OnPaint(context, renderOn);
         OnPaint(context, renderOn);
+        renderOn.Canvas.RestoreToCount(saved);
         return true;
         return true;
     }
     }
 
 

+ 84 - 27
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/FilterNodes/ApplyFilterNode.cs

@@ -11,67 +11,124 @@ using Drawie.Numerics;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.FilterNodes;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.FilterNodes;
 
 
 [NodeInfo("ApplyFilter")]
 [NodeInfo("ApplyFilter")]
-public class ApplyFilterNode : RenderNode, IRenderInput
+public sealed class ApplyFilterNode : RenderNode, IRenderInput
 {
 {
-    private Paint _paint = new();
+    private readonly Paint _paint = new();
+    private readonly Paint _maskPaint = new()
+    {
+        BlendMode = BlendMode.DstIn,
+        ColorFilter = Filters.MaskFilter
+    };
+
     public InputProperty<Filter?> Filter { get; }
     public InputProperty<Filter?> Filter { get; }
 
 
     public RenderInputProperty Background { get; }
     public RenderInputProperty Background { get; }
 
 
+    public RenderInputProperty Mask { get; }
+    
+    public InputProperty<bool> InvertMask { get; }
+
     public ApplyFilterNode()
     public ApplyFilterNode()
     {
     {
         Background = CreateRenderInput("Input", "IMAGE");
         Background = CreateRenderInput("Input", "IMAGE");
         Filter = CreateInput<Filter>("Filter", "FILTER", null);
         Filter = CreateInput<Filter>("Filter", "FILTER", null);
+        Mask = CreateRenderInput("Mask", "MASK");
+        InvertMask = CreateInput("InvertMask", "INVERT_MASK", false);
         Output.FirstInChain = null;
         Output.FirstInChain = null;
         AllowHighDpiRendering = true;
         AllowHighDpiRendering = true;
     }
     }
 
 
-
     protected override void Paint(RenderContext context, DrawingSurface surface)
     protected override void Paint(RenderContext context, DrawingSurface surface)
     {
     {
         AllowHighDpiRendering = (Background.Connection.Node as RenderNode)?.AllowHighDpiRendering ?? true;
         AllowHighDpiRendering = (Background.Connection.Node as RenderNode)?.AllowHighDpiRendering ?? true;
         base.Paint(context, surface);
         base.Paint(context, surface);
     }
     }
 
 
-    protected override void OnPaint(RenderContext context, DrawingSurface surface)
+    protected override void OnPaint(RenderContext context, DrawingSurface outputSurface)
     {
     {
-        if (_paint == null)
-            return;
+        using var _ = DetermineTargetSurface(context, outputSurface, out var processingSurface);
+
+        DrawWithFilter(context, outputSurface, processingSurface);
+        
+        // If the Mask is null, we already drew to the output surface, otherwise we still need to draw to it (and apply the mask)
+        if (processingSurface != outputSurface)
+        {
+            ApplyWithMask(context, processingSurface, outputSurface);
+        }
+    }
 
 
+    private void DrawWithFilter(RenderContext context, DrawingSurface outputSurface, DrawingSurface processingSurface)
+    {
         _paint.SetFilters(Filter.Value);
         _paint.SetFilters(Filter.Value);
 
 
         if (!context.ProcessingColorSpace.IsSrgb)
         if (!context.ProcessingColorSpace.IsSrgb)
         {
         {
-            var intermediate = Texture.ForProcessing(surface, context.ProcessingColorSpace);
+            HandleNonSrgbContext(context, outputSurface, processingSurface);
+            return;
+        }
+
+        var layer = processingSurface.Canvas.SaveLayer(_paint);
+        Background.Value?.Paint(context, processingSurface);
+        processingSurface.Canvas.RestoreToCount(layer);
+    }
 
 
-            int saved = surface.Canvas.Save();
-            surface.Canvas.SetMatrix(Matrix3X3.Identity);
+    private void HandleNonSrgbContext(RenderContext context, DrawingSurface surface, DrawingSurface targetSurface)
+    {
+        using var intermediate = Texture.ForProcessing(surface, context.ProcessingColorSpace);
 
 
-            Background.Value?.Paint(context, intermediate.DrawingSurface);
+        Background.Value?.Paint(context, intermediate.DrawingSurface);
 
 
-            var srgbSurface = Texture.ForProcessing(intermediate.Size, ColorSpace.CreateSrgb());
+        using var srgbSurface = Texture.ForProcessing(intermediate.Size, ColorSpace.CreateSrgb());
 
 
-            srgbSurface.DrawingSurface.Canvas.SaveLayer(_paint);
-            srgbSurface.DrawingSurface.Canvas.DrawSurface(intermediate.DrawingSurface, 0, 0);
-            srgbSurface.DrawingSurface.Canvas.Restore();
+        srgbSurface.DrawingSurface.Canvas.SaveLayer(_paint);
+        srgbSurface.DrawingSurface.Canvas.DrawSurface(intermediate.DrawingSurface, 0, 0);
+        srgbSurface.DrawingSurface.Canvas.Restore();
 
 
-            surface.Canvas.DrawSurface(srgbSurface.DrawingSurface, 0, 0);
-            surface.Canvas.RestoreToCount(saved);
-            intermediate.Dispose();
-            srgbSurface.Dispose();
-        }
-        else
-        {
-            int layer = surface.Canvas.SaveLayer(_paint);
-            Background.Value?.Paint(context, surface);
-            surface.Canvas.RestoreToCount(layer);
-        }
+        var saved = targetSurface.Canvas.Save();
+        targetSurface.Canvas.SetMatrix(Matrix3X3.Identity);
+
+        targetSurface.Canvas.DrawSurface(srgbSurface.DrawingSurface, 0, 0);
+        targetSurface.Canvas.RestoreToCount(saved);
+    }
+
+    private Texture? DetermineTargetSurface(RenderContext context, DrawingSurface outputSurface, out DrawingSurface targetSurface)
+    {
+        targetSurface = outputSurface;
+        
+        if (Mask.Value == null)
+            return null;
+        
+        Background.Value?.Paint(context, outputSurface);
+        var texture = Texture.ForProcessing(outputSurface, context.ProcessingColorSpace);
+        targetSurface = texture.DrawingSurface;
+        
+        return texture;
     }
     }
 
 
-    public override RectD? GetPreviewBounds(int frame, string elementToRenderName = "")
+    private void ApplyWithMask(RenderContext context, DrawingSurface processedSurface, DrawingSurface finalSurface)
     {
     {
-        return PreviewUtils.FindPreviewBounds(Background.Connection, frame, elementToRenderName);
+        _maskPaint.BlendMode = !InvertMask.Value ? BlendMode.DstIn : BlendMode.DstOut;
+        var maskLayer = processedSurface.Canvas.SaveLayer(_maskPaint);
+        Mask.Value?.Paint(context, processedSurface);
+        processedSurface.Canvas.RestoreToCount(maskLayer);
+
+        var saved = finalSurface.Canvas.Save();
+        finalSurface.Canvas.SetMatrix(Matrix3X3.Identity);
+
+        finalSurface.Canvas.DrawSurface(processedSurface, 0, 0);
+        finalSurface.Canvas.RestoreToCount(saved);
     }
     }
 
 
+    public override RectD? GetPreviewBounds(int frame, string elementToRenderName = "") =>
+        PreviewUtils.FindPreviewBounds(Background.Connection, frame, elementToRenderName);
+
     public override Node CreateCopy() => new ApplyFilterNode();
     public override Node CreateCopy() => new ApplyFilterNode();
+
+    public override void Dispose()
+    {
+        base.Dispose();
+        
+        _paint.Dispose();
+        _maskPaint.Dispose();
+    }
 }
 }

+ 15 - 4
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Image/MaskNode.cs

@@ -6,20 +6,23 @@ using PixiEditor.ChangeableDocument.Rendering;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Image;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Image;
 
 
 [NodeInfo("Mask")]
 [NodeInfo("Mask")]
-public class MaskNode : RenderNode, IRenderInput
+public sealed class MaskNode : RenderNode, IRenderInput
 {
 {
     public RenderInputProperty Background { get; }
     public RenderInputProperty Background { get; }
     public RenderInputProperty Mask { get; }
     public RenderInputProperty Mask { get; }
+    public InputProperty<bool> Invert { get; }
 
 
-    protected Paint maskPaint = new Paint()
+    private readonly Paint maskPaint = new()
     {
     {
-        BlendMode = Drawie.Backend.Core.Surfaces.BlendMode.DstIn, ColorFilter = Nodes.Filters.MaskFilter
+        BlendMode = BlendMode.DstIn,
+        ColorFilter = Filters.MaskFilter
     };
     };
 
 
     public MaskNode()
     public MaskNode()
     {
     {
         Background = CreateRenderInput("Background", "INPUT");
         Background = CreateRenderInput("Background", "INPUT");
         Mask = CreateRenderInput("Mask", "MASK");
         Mask = CreateRenderInput("Mask", "MASK");
+        Invert = CreateInput("Invert", "INVERT", false);
         AllowHighDpiRendering = true;
         AllowHighDpiRendering = true;
         Output.FirstInChain = null;
         Output.FirstInChain = null;
     }
     }
@@ -38,14 +41,22 @@ public class MaskNode : RenderNode, IRenderInput
             return;
             return;
         }
         }
 
 
+        maskPaint.BlendMode = !Invert.Value ? BlendMode.DstIn : BlendMode.DstOut;
+
         int layer = surface.Canvas.SaveLayer(maskPaint);
         int layer = surface.Canvas.SaveLayer(maskPaint);
         Mask.Value.Paint(context, surface);
         Mask.Value.Paint(context, surface);
         surface.Canvas.RestoreToCount(layer);
         surface.Canvas.RestoreToCount(layer);
     }
     }
 
 
-
     public override Node CreateCopy()
     public override Node CreateCopy()
     {
     {
         return new MaskNode();
         return new MaskNode();
     }
     }
+
+    public override void Dispose()
+    {
+        base.Dispose();
+        
+        maskPaint.Dispose();
+    }
 }
 }

+ 29 - 3
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ImageLayerNode.cs

@@ -5,6 +5,7 @@ using PixiEditor.ChangeableDocument.Helpers;
 using PixiEditor.ChangeableDocument.Rendering;
 using PixiEditor.ChangeableDocument.Rendering;
 using Drawie.Backend.Core;
 using Drawie.Backend.Core;
 using Drawie.Backend.Core.ColorsImpl;
 using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces.ImageData;
 using Drawie.Backend.Core.Surfaces.ImageData;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
@@ -130,6 +131,7 @@ public class ImageLayerNode : LayerNode, IReadOnlyImageNode
 
 
         var sceneSize = GetSceneSize(ctx.FrameTime);
         var sceneSize = GetSceneSize(ctx.FrameTime);
         VecD topLeft = sceneSize / 2f;
         VecD topLeft = sceneSize / 2f;
+
         if (renderedSurfaceFrame == null || ctx.FullRerender || ctx.FrameTime.Frame != renderedSurfaceFrame)
         if (renderedSurfaceFrame == null || ctx.FullRerender || ctx.FrameTime.Frame != renderedSurfaceFrame)
         {
         {
             GetLayerImageAtFrame(ctx.FrameTime.Frame).DrawMostUpToDateRegionOn(
             GetLayerImageAtFrame(ctx.FrameTime.Frame).DrawMostUpToDateRegionOn(
@@ -139,7 +141,18 @@ public class ImageLayerNode : LayerNode, IReadOnlyImageNode
         }
         }
         else
         else
         {
         {
-            workingSurface.Canvas.DrawSurface(fullResrenderedSurface.DrawingSurface, -topLeft, paint);
+            if (ctx.DesiredSamplingOptions == SamplingOptions.Default)
+            {
+                workingSurface.Canvas.DrawSurface(
+                    fullResrenderedSurface.DrawingSurface, -(float)topLeft.X, -(float)topLeft.Y, paint);
+            }
+            else
+            {
+                using var snapshot = fullResrenderedSurface.DrawingSurface.Snapshot();
+                workingSurface.Canvas.DrawImage(snapshot, -(float)topLeft.X, -(float)topLeft.Y,
+                    ctx.DesiredSamplingOptions,
+                    paint);
+            }
         }
         }
 
 
         workingSurface.Canvas.RestoreToCount(saved);
         workingSurface.Canvas.RestoreToCount(saved);
@@ -242,14 +255,27 @@ public class ImageLayerNode : LayerNode, IReadOnlyImageNode
 
 
         if (renderedSurfaceFrame == cacheFrame)
         if (renderedSurfaceFrame == cacheFrame)
         {
         {
-            renderOnto.Canvas.DrawSurface(fullResrenderedSurface.DrawingSurface, VecI.Zero, blendPaint);
+            int saved = renderOnto.Canvas.Save();
+            renderOnto.Canvas.Scale((float)context.ChunkResolution.Multiplier());
+            if (context.DesiredSamplingOptions == SamplingOptions.Default)
+            {
+                renderOnto.Canvas.DrawSurface(
+                    fullResrenderedSurface.DrawingSurface, 0, 0, blendPaint);
+            }
+            else
+            {
+                using var snapshot = fullResrenderedSurface.DrawingSurface.Snapshot();
+                renderOnto.Canvas.DrawImage(snapshot, 0, 0, context.DesiredSamplingOptions, blendPaint);
+            }
+
+            renderOnto.Canvas.RestoreToCount(saved);
         }
         }
         else
         else
         {
         {
             img.DrawMostUpToDateRegionOn(
             img.DrawMostUpToDateRegionOn(
                 new RectI(0, 0, img.LatestSize.X, img.LatestSize.Y),
                 new RectI(0, 0, img.LatestSize.X, img.LatestSize.Y),
                 context.ChunkResolution,
                 context.ChunkResolution,
-                renderOnto, VecI.Zero, blendPaint);
+                renderOnto, VecI.Zero, blendPaint, context.DesiredSamplingOptions);
         }
         }
 
 
         return true;
         return true;

+ 14 - 4
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/LayerNode.cs

@@ -62,7 +62,8 @@ public abstract class LayerNode : StructureNode, IReadOnlyLayerNode, IClipSource
                 DrawLayerOnTexture(context, tempSurface.DrawingSurface, context.ChunkResolution, useFilters, targetPaint);
                 DrawLayerOnTexture(context, tempSurface.DrawingSurface, context.ChunkResolution, useFilters, targetPaint);
 
 
                 blendPaint.SetFilters(null);
                 blendPaint.SetFilters(null);
-                DrawWithResolution(tempSurface.DrawingSurface, renderOnto, context.ChunkResolution);
+                DrawWithResolution(tempSurface.DrawingSurface, renderOnto, context.ChunkResolution,
+                    context.DesiredSamplingOptions);
             }
             }
 
 
             return;
             return;
@@ -130,7 +131,8 @@ public abstract class LayerNode : StructureNode, IReadOnlyLayerNode, IClipSource
             blendPaint.SetFilters(null);
             blendPaint.SetFilters(null);
         }
         }
 
 
-        DrawWithResolution(outputWorkingSurface.DrawingSurface, renderOnto, adjustedResolution);
+        DrawWithResolution(outputWorkingSurface.DrawingSurface, renderOnto, adjustedResolution,
+            context.DesiredSamplingOptions);
 
 
         renderOnto.Canvas.RestoreToCount(saved);
         renderOnto.Canvas.RestoreToCount(saved);
         outputWorkingSurface.DrawingSurface.Canvas.Restore();
         outputWorkingSurface.DrawingSurface.Canvas.Restore();
@@ -149,13 +151,21 @@ public abstract class LayerNode : StructureNode, IReadOnlyLayerNode, IClipSource
         workingSurface.Canvas.RestoreToCount(scaled);
         workingSurface.Canvas.RestoreToCount(scaled);
     }
     }
 
 
-    private void DrawWithResolution(DrawingSurface source, DrawingSurface target, ChunkResolution resolution)
+    private void DrawWithResolution(DrawingSurface source, DrawingSurface target, ChunkResolution resolution, SamplingOptions sampling)
     {
     {
         int scaled = target.Canvas.Save();
         int scaled = target.Canvas.Save();
         float multiplier = (float)resolution.InvertedMultiplier();
         float multiplier = (float)resolution.InvertedMultiplier();
         target.Canvas.Scale(multiplier, multiplier);
         target.Canvas.Scale(multiplier, multiplier);
 
 
-        target.Canvas.DrawSurface(source, 0, 0, blendPaint);
+        if (sampling == SamplingOptions.Default)
+        {
+            target.Canvas.DrawSurface(source, 0, 0, blendPaint);
+        }
+        else
+        {
+            using var snapshot = source.Snapshot();
+            target.Canvas.DrawImage(snapshot, 0, 0, sampling, blendPaint);
+        }
 
 
         target.Canvas.RestoreToCount(scaled);
         target.Canvas.RestoreToCount(scaled);
     }
     }

+ 163 - 3
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/NoiseNode.cs

@@ -18,6 +18,11 @@ public class NoiseNode : RenderNode
     private NoiseType previousNoiseType = Nodes.NoiseType.FractalPerlin;
     private NoiseType previousNoiseType = Nodes.NoiseType.FractalPerlin;
     private int previousOctaves = -1;
     private int previousOctaves = -1;
     private VecD previousOffset = new VecD(0d, 0d);
     private VecD previousOffset = new VecD(0d, 0d);
+    private VoronoiFeature previousVoronoiFeature = Nodes.VoronoiFeature.F1;
+    private double previousRandomness = double.NaN;
+    private double previousAngleOffset = double.NaN;
+    
+    private Shader voronoiShader;
 
 
     private Paint paint = new();
     private Paint paint = new();
 
 
@@ -25,7 +30,7 @@ public class NoiseNode : RenderNode
         ColorMatrix.MapAlphaToRedGreenBlue + ColorMatrix.OpaqueAlphaOffset);
         ColorMatrix.MapAlphaToRedGreenBlue + ColorMatrix.OpaqueAlphaOffset);
 
 
     public InputProperty<NoiseType> NoiseType { get; }
     public InputProperty<NoiseType> NoiseType { get; }
-
+    
     public InputProperty<VecD> Offset { get; }
     public InputProperty<VecD> Offset { get; }
     
     
     public InputProperty<double> Scale { get; }
     public InputProperty<double> Scale { get; }
@@ -33,6 +38,12 @@ public class NoiseNode : RenderNode
     public InputProperty<int> Octaves { get; }
     public InputProperty<int> Octaves { get; }
 
 
     public InputProperty<double> Seed { get; }
     public InputProperty<double> Seed { get; }
+    
+    public InputProperty<VoronoiFeature> VoronoiFeature { get; }
+    
+    public InputProperty<double> Randomness { get; }
+    
+    public InputProperty<double> AngleOffset { get; }
 
 
     public NoiseNode()
     public NoiseNode()
     {
     {
@@ -45,6 +56,13 @@ public class NoiseNode : RenderNode
             .WithRules(validator => validator.Min(1));
             .WithRules(validator => validator.Min(1));
 
 
         Seed = CreateInput(nameof(Seed), "SEED", 0d);
         Seed = CreateInput(nameof(Seed), "SEED", 0d);
+        
+        VoronoiFeature = CreateInput(nameof(VoronoiFeature), "VORONOI_FEATURE", Nodes.VoronoiFeature.F1);
+        
+        Randomness = CreateInput(nameof(Randomness), "RANDOMNESS", 1d)
+            .WithRules(v => v.Min(0d).Max(1d));
+        
+        AngleOffset = CreateInput(nameof(AngleOffset), "ANGLE_OFFSET", 0d);
     }
     }
 
 
     protected override void OnPaint(RenderContext context, DrawingSurface target)
     protected override void OnPaint(RenderContext context, DrawingSurface target)
@@ -54,6 +72,9 @@ public class NoiseNode : RenderNode
             || previousOctaves != Octaves.Value
             || previousOctaves != Octaves.Value
             || previousNoiseType != NoiseType.Value
             || previousNoiseType != NoiseType.Value
             || previousOffset != Offset.Value
             || previousOffset != Offset.Value
+            || previousVoronoiFeature != VoronoiFeature.Value
+            || Math.Abs(previousRandomness - Randomness.Value) > 0.000001
+            || Math.Abs(previousAngleOffset - AngleOffset.Value) > 0.000001
             || double.IsNaN(previousScale))
             || double.IsNaN(previousScale))
         {
         {
             if (Scale.Value < 0.000001)
             if (Scale.Value < 0.000001)
@@ -67,6 +88,10 @@ public class NoiseNode : RenderNode
                 return;
                 return;
             }
             }
 
 
+            if (paint.Shader != voronoiShader)
+            {
+                paint?.Shader?.Dispose();
+            }
             paint.Shader = shader;
             paint.Shader = shader;
 
 
             // Define a grayscale color filter to apply to the image
             // Define a grayscale color filter to apply to the image
@@ -76,6 +101,9 @@ public class NoiseNode : RenderNode
             previousSeed = Seed.Value;
             previousSeed = Seed.Value;
             previousOctaves = Octaves.Value;
             previousOctaves = Octaves.Value;
             previousNoiseType = NoiseType.Value;
             previousNoiseType = NoiseType.Value;
+            previousVoronoiFeature = VoronoiFeature.Value;
+            previousRandomness = Randomness.Value;
+            previousAngleOffset = AngleOffset.Value;
         }
         }
 
 
         RenderNoise(target);
         RenderNoise(target);
@@ -101,7 +129,11 @@ public class NoiseNode : RenderNode
         {
         {
             return false;
             return false;
         }
         }
-        
+
+        if (paint.Shader != voronoiShader)
+        {
+            paint?.Shader?.Dispose();
+        }
         paint.Shader = shader;
         paint.Shader = shader;
         paint.ColorFilter = grayscaleFilter;
         paint.ColorFilter = grayscaleFilter;
         
         
@@ -122,17 +154,145 @@ public class NoiseNode : RenderNode
                 (float)(1d / Scale.Value),
                 (float)(1d / Scale.Value),
                 (float)(1d / Scale.Value),
                 (float)(1d / Scale.Value),
                 octaves, (float)Seed.Value),
                 octaves, (float)Seed.Value),
+            Nodes.NoiseType.Voronoi => GetVoronoiShader((float)(1d / (Scale.Value)), octaves, (float)Seed.Value, (int)VoronoiFeature.Value, (float)Randomness.Value, (float)AngleOffset.Value),
             _ => null
             _ => null
         };
         };
 
 
         return shader;
         return shader;
     }
     }
 
 
+    private Shader GetVoronoiShader(float frequency, int octaves, float seed, int feature, float randomness, float angleOffset)
+    {
+        string voronoiShaderCode = """
+                                   uniform float iSeed;
+                                   uniform float iFrequency;
+                                   uniform int iOctaves;
+                                   uniform float iRandomness;
+                                   uniform int iFeature;
+                                   uniform float iAngleOffset;
+                                   
+                                   const int MAX_OCTAVES = 8;
+                                   const float LARGE_NUMBER = 1e9;
+                                   const float FEATURE_SEED_SCALE = 10.0;
+                                   const float PI = 3.14159265;
+                                   
+                                   float hashPoint(float2 p, float seed) {
+                                       p = fract(p * float2(0.3183099, 0.3678794) + seed);
+                                       p += dot(p, p.yx + 19.19);
+                                       return fract(p.x * p.y);
+                                   }
+                                   
+                                   float2 getFeaturePoint(float2 cell, float seed, float randomness, float angleOffset) {
+                                       float2 randomCellOffset = float2(
+                                           hashPoint(cell, seed),
+                                           hashPoint(cell, seed + 17.0)
+                                       );
+                                       
+                                       float2 featurePoint = mix(float2(0.5, 0.5), randomCellOffset, randomness);
+                                       
+                                       float angle = hashPoint(cell, seed + 53.0) * PI * 2.0;
+                                       angle += angleOffset;
+                                       
+                                       float2 dir = float2(cos(angle), sin(angle));
+                                       float offsetAmount = 0.15;
+                                       featurePoint += dir * offsetAmount * randomness;
+                                       
+                                       featurePoint = clamp(featurePoint, 0.0, 1.0);
+                                       
+                                       return featurePoint;
+                                   }
+                                   
+                                   float2 getVoronoiDistances(float2 pos, float seed) {
+                                       float2 cell = floor(pos);
+                                       float minDist = LARGE_NUMBER;
+                                       float secondMinDist = LARGE_NUMBER;
+                                   
+                                       for (int y = -1; y <= 1; y++) {
+                                           for (int x = -1; x <= 1; x++) {
+                                               float2 neighborCell = cell + float2(float(x), float(y));
+                                               float2 featurePoint = getFeaturePoint(neighborCell, seed, iRandomness, iAngleOffset);
+                                               float2 delta = pos - (neighborCell + featurePoint);
+                                               float dist = length(delta);
+                                               
+                                               if (dist < minDist) {
+                                                   secondMinDist = minDist;
+                                                   minDist = dist;
+                                               } else if (dist < secondMinDist) {
+                                                   secondMinDist = dist;
+                                               }
+                                           }
+                                       }
+                                       return float2(minDist, secondMinDist);
+                                   }
+                                   
+                                   half4 main(float2 uv) {
+                                       float noiseSum = 0.0;
+                                       float amplitude = 1.0;
+                                       float amplitudeSum = 0.0;
+                                   
+                                       for (int octave = 0; octave < MAX_OCTAVES; octave++) {
+                                           if (octave >= iOctaves) break;
+                                   
+                                           float freq = iFrequency * exp2(float(octave));
+                                           float2 samplePos = uv * freq;
+                                           
+                                           float dist = 0.0;
+                                           float2 distances = getVoronoiDistances(samplePos, iSeed + float(octave) * FEATURE_SEED_SCALE);
+                                           float f1 = distances.x;
+                                           float f2 = distances.y;
+                                           
+                                           if (iFeature == 0) {
+                                               dist = f1;
+                                           }
+                                           else if (iFeature == 1) {
+                                               dist = f2;
+                                           }
+                                           else if (iFeature == 2) {
+                                               dist = f2 - f1;
+                                           }
+                                   
+                                           noiseSum += dist * amplitude;
+                                           amplitudeSum += amplitude;
+                                           amplitude *= 0.5;
+                                       }
+                                   
+                                       return half4(noiseSum / amplitudeSum);
+                                   }
+                                   """;
+        
+        Uniforms uniforms = new Uniforms();
+        uniforms.Add("iSeed", new Uniform("iSeed", seed));
+        uniforms.Add("iFrequency", new Uniform("iFrequency", frequency));
+        uniforms.Add("iOctaves", new Uniform("iOctaves", octaves));
+        uniforms.Add("iRandomness", new Uniform("iRandomness", randomness));
+        uniforms.Add("iFeature", new Uniform("iFeature", feature));
+        uniforms.Add("iAngleOffset", new Uniform("iAngleOffset", angleOffset));
+
+        if (voronoiShader == null)
+        {
+            voronoiShader = Shader.Create(voronoiShaderCode, uniforms, out _);
+        }
+        else
+        {
+            voronoiShader = voronoiShader.WithUpdatedUniforms(uniforms);
+        }
+        
+        return voronoiShader;
+    }
+
     public override Node CreateCopy() => new NoiseNode();
     public override Node CreateCopy() => new NoiseNode();
 }
 }
 
 
 public enum NoiseType
 public enum NoiseType
 {
 {
     TurbulencePerlin,
     TurbulencePerlin,
-    FractalPerlin
+    FractalPerlin,
+    Voronoi
+}
+
+public enum VoronoiFeature
+{
+    F1 = 0, // Distance to the closest feature point
+    F2 = 1, // Distance to the second-closest feature point
+    F2MinusF1 = 2
 }
 }

+ 1 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/OutputNode.cs

@@ -56,6 +56,7 @@ public class OutputNode : Node, IRenderInput, IPreviewRenderable
         }
         }
 
 
         int saved = renderOn.Canvas.Save();
         int saved = renderOn.Canvas.Save();
+        renderOn.Canvas.Scale((float)context.ChunkResolution.Multiplier());
         Input.Value.Paint(context, renderOn);
         Input.Value.Paint(context, renderOn);
 
 
         renderOn.Canvas.RestoreToCount(saved);
         renderOn.Canvas.RestoreToCount(saved);

+ 11 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/RenderNode.cs

@@ -66,7 +66,15 @@ public abstract class RenderNode : Node, IPreviewRenderable, IHighDpiRenderNode
                 surface.Canvas.Scale((float)context.ChunkResolution.InvertedMultiplier());
                 surface.Canvas.Scale((float)context.ChunkResolution.InvertedMultiplier());
             }
             }
 
 
-            surface.Canvas.DrawSurface(target, 0, 0);
+            if (context.DesiredSamplingOptions != SamplingOptions.Default)
+            {
+                using var snapshot = target.Snapshot();
+                surface.Canvas.DrawImage(snapshot, 0, 0, context.DesiredSamplingOptions);
+            }
+            else
+            {
+                surface.Canvas.DrawSurface(target, 0, 0);
+            }
 
 
             if (RendersInAbsoluteCoordinates)
             if (RendersInAbsoluteCoordinates)
             {
             {
@@ -85,7 +93,9 @@ public abstract class RenderNode : Node, IPreviewRenderable, IHighDpiRenderNode
     public virtual bool RenderPreview(DrawingSurface renderOn, RenderContext context,
     public virtual bool RenderPreview(DrawingSurface renderOn, RenderContext context,
         string elementToRenderName)
         string elementToRenderName)
     {
     {
+        int saved = renderOn.Canvas.Save();
         OnPaint(context, renderOn);
         OnPaint(context, renderOn);
+        renderOn.Canvas.RestoreToCount(saved);
         return true;
         return true;
     }
     }
 
 

+ 10 - 4
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ShaderNode.cs

@@ -104,7 +104,8 @@ public class ShaderNode : RenderNode, IRenderInput, ICustomShaderNode
         Uniforms uniforms;
         Uniforms uniforms;
         uniforms = new Uniforms();
         uniforms = new Uniforms();
 
 
-        VecI finalSize = (VecI)(context.RenderOutputSize * context.ChunkResolution.InvertedMultiplier());
+        bool isAdjusted = context.RenderOutputSize == context.DocumentSize;
+        VecI finalSize = isAdjusted ? context.RenderOutputSize : (VecI)(context.RenderOutputSize * context.ChunkResolution.InvertedMultiplier());
 
 
         uniforms.Add("iResolution", new Uniform("iResolution", (VecD)finalSize));
         uniforms.Add("iResolution", new Uniform("iResolution", (VecD)finalSize));
         uniforms.Add("iNormalizedTime", new Uniform("iNormalizedTime", (float)context.FrameTime.NormalizedTime));
         uniforms.Add("iNormalizedTime", new Uniform("iNormalizedTime", (float)context.FrameTime.NormalizedTime));
@@ -123,8 +124,11 @@ public class ShaderNode : RenderNode, IRenderInput, ICustomShaderNode
         int saved = texture.DrawingSurface.Canvas.Save();
         int saved = texture.DrawingSurface.Canvas.Save();
         //texture.DrawingSurface.Canvas.Scale((float)context.ChunkResolution.Multiplier(), (float)context.ChunkResolution.Multiplier());
         //texture.DrawingSurface.Canvas.Scale((float)context.ChunkResolution.Multiplier(), (float)context.ChunkResolution.Multiplier());
 
 
-        var ctx = new RenderContext(texture.DrawingSurface, context.FrameTime, ChunkResolution.Full, finalSize,
-            context.DocumentSize, context.ProcessingColorSpace, context.Opacity);
+        var ctx = context.Clone();
+        ctx.RenderSurface = texture.DrawingSurface;
+        ctx.RenderOutputSize = finalSize;
+        ctx.ChunkResolution = ChunkResolution.Full;
+
         Background.Value.Paint(ctx, texture.DrawingSurface);
         Background.Value.Paint(ctx, texture.DrawingSurface);
         texture.DrawingSurface.Canvas.RestoreToCount(saved);
         texture.DrawingSurface.Canvas.RestoreToCount(saved);
 
 
@@ -156,8 +160,10 @@ public class ShaderNode : RenderNode, IRenderInput, ICustomShaderNode
 
 
         if (context.ChunkResolution != ChunkResolution.Full)
         if (context.ChunkResolution != ChunkResolution.Full)
         {
         {
+            bool isAdjusted = context.RenderOutputSize == context.DocumentSize;
+            VecI finalSize = isAdjusted ? context.RenderOutputSize : (VecI)(context.RenderOutputSize * context.ChunkResolution.InvertedMultiplier());
             var intermediateSurface = RequestTexture(51,
             var intermediateSurface = RequestTexture(51,
-                (VecI)(context.RenderOutputSize * context.ChunkResolution.InvertedMultiplier()),
+                finalSize,
                 ColorSpace.Value == ColorSpaceType.Inherit
                 ColorSpace.Value == ColorSpaceType.Inherit
                     ? context.ProcessingColorSpace
                     ? context.ProcessingColorSpace
                     : ColorSpace.Value == ColorSpaceType.Srgb
                     : ColorSpace.Value == ColorSpaceType.Srgb

+ 32 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/LineNode.cs

@@ -0,0 +1,32 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes.Data;
+using PixiEditor.ChangeableDocument.Rendering;
+using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.ColorsImpl.Paintables;
+using Drawie.Numerics;
+
+namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes;
+
+[NodeInfo("Line")]
+public class LineNode : ShapeNode<LineVectorData>
+{
+    public InputProperty<VecD> Start { get; }
+    public InputProperty<VecD> End { get; }
+    public InputProperty<Paintable> StrokeColor { get; }
+    public InputProperty<double> StrokeWidth { get; }
+
+    public LineNode()
+    {
+        Start = CreateInput<VecD>("LineStart", "LINE_START", VecD.Zero);
+        End = CreateInput<VecD>("LineEnd", "LINE_END", new VecD(32, 32));
+        StrokeColor = CreateInput<Paintable>("StrokeColor", "STROKE_COLOR", new Color(0, 0, 0, 255));
+        StrokeWidth = CreateInput<double>("StrokeWidth", "STROKE_WIDTH", 1);
+    }
+
+    protected override LineVectorData? GetShapeData(RenderContext context)
+    {
+        return new LineVectorData(Start.Value, End.Value)
+            { Stroke = StrokeColor.Value, StrokeWidth = (float)StrokeWidth.Value };
+    }
+
+    public override Node CreateCopy() => new LineNode();
+}

+ 37 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/RectangleNode.cs

@@ -0,0 +1,37 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes.Data;
+using PixiEditor.ChangeableDocument.Rendering;
+using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.ColorsImpl.Paintables;
+using Drawie.Numerics;
+
+namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes;
+
+[NodeInfo("Rectangle")]
+public class RectangleNode : ShapeNode<RectangleVectorData>
+{
+    public InputProperty<VecD> Center { get; }
+    public InputProperty<VecD> Size { get; }
+    public InputProperty<double> CornerRadius { get; }
+    public InputProperty<Paintable> StrokeColor { get; }
+    public InputProperty<Paintable> FillColor { get; }
+    public InputProperty<double> StrokeWidth { get; }
+
+    public RectangleNode()
+    {
+        Center = CreateInput<VecD>("Position", "POSITION", VecI.Zero);
+        Size = CreateInput<VecD>("Size", "SIZE", new VecD(32, 32)).WithRules(
+            v => v.Min(new VecD(0)));
+        CornerRadius = CreateInput<double>("CornerRadius", "RADIUS", 0);
+        StrokeColor = CreateInput<Paintable>("StrokeColor", "STROKE_COLOR", new Color(0, 0, 0, 255));
+        FillColor = CreateInput<Paintable>("FillColor", "FILL_COLOR", new Color(0, 0, 0, 255));
+        StrokeWidth = CreateInput<double>("StrokeWidth", "STROKE_WIDTH", 1);
+    }
+
+    protected override RectangleVectorData? GetShapeData(RenderContext context)
+    {
+        return new RectangleVectorData(Center.Value, Size.Value)
+            { CornerRadius = CornerRadius.Value, Stroke = StrokeColor.Value, FillPaintable = FillColor.Value, StrokeWidth = (float)StrokeWidth.Value };
+    }
+
+    public override Node CreateCopy() => new RectangleNode();
+}

+ 7 - 4
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/StructureNode.cs

@@ -184,8 +184,7 @@ public abstract class StructureNode : RenderNode, IReadOnlyStructureNode, IRende
 
 
         SceneObjectRenderContext renderObjectContext = new SceneObjectRenderContext(output, renderTarget, localBounds,
         SceneObjectRenderContext renderObjectContext = new SceneObjectRenderContext(output, renderTarget, localBounds,
             context.FrameTime, context.ChunkResolution, context.RenderOutputSize, context.DocumentSize, renderTarget == context.RenderSurface,
             context.FrameTime, context.ChunkResolution, context.RenderOutputSize, context.DocumentSize, renderTarget == context.RenderSurface,
-            context.ProcessingColorSpace,
-            context.Opacity);
+            context.ProcessingColorSpace, context.DesiredSamplingOptions, context.Opacity);
         renderObjectContext.FullRerender = context.FullRerender;
         renderObjectContext.FullRerender = context.FullRerender;
         return renderObjectContext;
         return renderObjectContext;
     }
     }
@@ -247,7 +246,12 @@ public abstract class StructureNode : RenderNode, IReadOnlyStructureNode, IRende
 
 
         VecI targetSize = img.LatestSize;
         VecI targetSize = img.LatestSize;
 
 
-        var ctx = DrawingBackendApi.Current.RenderingDispatcher.EnsureContext();
+        if (targetSize.X <= 0 || targetSize.Y <= 0)
+        {
+            return;
+        }
+
+        using var ctx = DrawingBackendApi.Current.RenderingDispatcher.EnsureContext();
         renderSurface = RequestTexture(textureId, targetSize, processingColorSpace, false);
         renderSurface = RequestTexture(textureId, targetSize, processingColorSpace, false);
 
 
         int saved = renderSurface.DrawingSurface.Canvas.Save();
         int saved = renderSurface.DrawingSurface.Canvas.Save();
@@ -265,7 +269,6 @@ public abstract class StructureNode : RenderNode, IReadOnlyStructureNode, IRende
         }
         }
 
 
         renderSurface.DrawingSurface.Canvas.RestoreToCount(saved);
         renderSurface.DrawingSurface.Canvas.RestoreToCount(saved);
-        ctx?.Dispose();
     }
     }
 
 
     protected void ApplyRasterClip(DrawingSurface toClip, DrawingSurface clipSource)
     protected void ApplyRasterClip(DrawingSurface toClip, DrawingSurface clipSource)

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/TextureCache.cs

@@ -14,7 +14,7 @@ public class TextureCache : IDisposable
     {
     {
         if (_managedTextures.TryGetValue(id, out var texture))
         if (_managedTextures.TryGetValue(id, out var texture))
         {
         {
-            if (texture.Size != size || texture.IsDisposed || texture.ColorSpace != processingCs)
+            if (texture.Size != size || texture.IsDisposed || !texture.ColorSpace.Equals(processingCs))
             {
             {
                 texture.Dispose();
                 texture.Dispose();
                 texture = new Texture(CreateImageInfo(size, processingCs));
                 texture = new Texture(CreateImageInfo(size, processingCs));

+ 8 - 3
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/VectorLayerNode.cs

@@ -132,7 +132,11 @@ public class VectorLayerNode : LayerNode, ITransformableObject, IReadOnlyVectorN
             return false;
             return false;
         }
         }
 
 
+
+        int savedCount = renderOn.Canvas.Save();
+        renderOn.Canvas.Scale((float)context.ChunkResolution.Multiplier());
         Rasterize(renderOn, paint);
         Rasterize(renderOn, paint);
+        renderOn.Canvas.RestoreToCount(savedCount);
 
 
         return true;
         return true;
     }
     }
@@ -188,8 +192,9 @@ public class VectorLayerNode : LayerNode, ITransformableObject, IReadOnlyVectorN
     {
     {
         int layer;
         int layer;
         // TODO: This can be further optimized by passing opacity, blend mode and filters directly to the rasterization method
         // TODO: This can be further optimized by passing opacity, blend mode and filters directly to the rasterization method
-        if (paint != null && (paint.Color.A < 255 || paint.ColorFilter != null || paint.ImageFilter != null || paint.Shader != null ||
-            paint.BlendMode != Drawie.Backend.Core.Surfaces.BlendMode.SrcOver))
+        if (paint != null && (paint.Color.A < 255 || paint.ColorFilter != null || paint.ImageFilter != null ||
+                              paint.Shader != null ||
+                              paint.BlendMode != Drawie.Backend.Core.Surfaces.BlendMode.SrcOver))
         {
         {
             layer = surface.Canvas.SaveLayer(paint);
             layer = surface.Canvas.SaveLayer(paint);
         }
         }
@@ -197,7 +202,7 @@ public class VectorLayerNode : LayerNode, ITransformableObject, IReadOnlyVectorN
         {
         {
             layer = surface.Canvas.Save();
             layer = surface.Canvas.Save();
         }
         }
-        
+
         RenderableShapeData?.RasterizeTransformed(surface.Canvas);
         RenderableShapeData?.RasterizeTransformed(surface.Canvas);
 
 
         surface.Canvas.RestoreToCount(layer);
         surface.Canvas.RestoreToCount(layer);

+ 2 - 1
src/PixiEditor.ChangeableDocument/Changes/NodeGraph/EvaluateGraph_Change.cs

@@ -1,4 +1,5 @@
 using Drawie.Backend.Core;
 using Drawie.Backend.Core;
+using Drawie.Backend.Core.Surfaces;
 using PixiEditor.ChangeableDocument.Changeables.Animations;
 using PixiEditor.ChangeableDocument.Changeables.Animations;
 using PixiEditor.ChangeableDocument.Changeables.Graph;
 using PixiEditor.ChangeableDocument.Changeables.Graph;
 using PixiEditor.ChangeableDocument.Rendering;
 using PixiEditor.ChangeableDocument.Rendering;
@@ -33,7 +34,7 @@ internal class EvaluateGraph_Change : Change
         using Texture renderTexture = Texture.ForProcessing(target.Size, target.ProcessingColorSpace);
         using Texture renderTexture = Texture.ForProcessing(target.Size, target.ProcessingColorSpace);
         RenderContext context =
         RenderContext context =
             new(renderTexture.DrawingSurface, frameTime, ChunkResolution.Full, target.Size, target.Size,
             new(renderTexture.DrawingSurface, frameTime, ChunkResolution.Full, target.Size, target.Size,
-                target.ProcessingColorSpace) { FullRerender = true };
+                target.ProcessingColorSpace, SamplingOptions.Default) { FullRerender = true };
         foreach (var nodeToEvaluate in queue)
         foreach (var nodeToEvaluate in queue)
         {
         {
             nodeToEvaluate.Execute(context);
             nodeToEvaluate.Execute(context);

+ 21 - 6
src/PixiEditor.ChangeableDocument/Rendering/DocumentRenderer.cs

@@ -21,6 +21,7 @@ public class DocumentRenderer : IPreviewRenderable, IDisposable
 {
 {
     private Queue<RenderRequest> renderRequests = new();
     private Queue<RenderRequest> renderRequests = new();
     private Texture renderTexture;
     private Texture renderTexture;
+    private int lastExecutedGraphFrame = -1;
 
 
     public DocumentRenderer(IReadOnlyDocument document)
     public DocumentRenderer(IReadOnlyDocument document)
     {
     {
@@ -68,7 +69,7 @@ public class DocumentRenderer : IPreviewRenderable, IDisposable
         toRenderOn.Canvas.SetMatrix(Matrix3X3.Identity);
         toRenderOn.Canvas.SetMatrix(Matrix3X3.Identity);
 
 
         RenderContext context = new(renderTexture.DrawingSurface, frame, resolution, Document.Size, Document.Size,
         RenderContext context = new(renderTexture.DrawingSurface, frame, resolution, Document.Size, Document.Size,
-            Document.ProcessingColorSpace);
+            Document.ProcessingColorSpace, SamplingOptions.Default);
         context.FullRerender = true;
         context.FullRerender = true;
         IReadOnlyNodeGraph membersOnlyGraph = ConstructMembersOnlyGraph(layersToCombine, Document.NodeGraph);
         IReadOnlyNodeGraph membersOnlyGraph = ConstructMembersOnlyGraph(layersToCombine, Document.NodeGraph);
         try
         try
@@ -114,7 +115,7 @@ public class DocumentRenderer : IPreviewRenderable, IDisposable
         toRenderOn.Canvas.SetMatrix(Matrix3X3.Identity);
         toRenderOn.Canvas.SetMatrix(Matrix3X3.Identity);
 
 
         RenderContext context = new(renderTexture.DrawingSurface, frameTime, resolution, Document.Size, Document.Size,
         RenderContext context = new(renderTexture.DrawingSurface, frameTime, resolution, Document.Size, Document.Size,
-            Document.ProcessingColorSpace);
+            Document.ProcessingColorSpace, SamplingOptions.Default);
         context.FullRerender = true;
         context.FullRerender = true;
 
 
         node.RenderForOutput(context, toRenderOn, null);
         node.RenderForOutput(context, toRenderOn, null);
@@ -134,7 +135,7 @@ public class DocumentRenderer : IPreviewRenderable, IDisposable
         RenderRequest request = new(tcs, context, renderOn, previewRenderable, elementToRenderName);
         RenderRequest request = new(tcs, context, renderOn, previewRenderable, elementToRenderName);
 
 
         renderRequests.Enqueue(request);
         renderRequests.Enqueue(request);
-        ExecuteRenderRequests();
+        ExecuteRenderRequests(context.FrameTime);
 
 
         return await tcs.Task;
         return await tcs.Task;
     }
     }
@@ -201,8 +202,12 @@ public class DocumentRenderer : IPreviewRenderable, IDisposable
         IsBusy = true;
         IsBusy = true;
 
 
         renderOn.Canvas.Clear();
         renderOn.Canvas.Clear();
+        int savedCount = renderOn.Canvas.Save();
+        renderOn.Canvas.Scale((float)context.ChunkResolution.Multiplier());
         context.RenderSurface = renderOn;
         context.RenderSurface = renderOn;
         Document.NodeGraph.Execute(context);
         Document.NodeGraph.Execute(context);
+        lastExecutedGraphFrame = context.FrameTime.Frame;
+        renderOn.Canvas.RestoreToCount(savedCount);
 
 
         IsBusy = false;
         IsBusy = false;
 
 
@@ -236,8 +241,9 @@ public class DocumentRenderer : IPreviewRenderable, IDisposable
             : Document.NodeGraph;
             : Document.NodeGraph;
 
 
         RenderContext context =
         RenderContext context =
-            new(renderTexture.DrawingSurface, frameTime, ChunkResolution.Full, SolveRenderOutputSize(customOutput, graph, Document.Size),
-                Document.Size, Document.ProcessingColorSpace) { FullRerender = true };
+            new(renderTexture.DrawingSurface, frameTime, ChunkResolution.Full,
+                SolveRenderOutputSize(customOutput, graph, Document.Size),
+                Document.Size, Document.ProcessingColorSpace, SamplingOptions.Default) { FullRerender = true };
 
 
         if (hasCustomOutput)
         if (hasCustomOutput)
         {
         {
@@ -264,19 +270,28 @@ public class DocumentRenderer : IPreviewRenderable, IDisposable
         renderTexture.DrawingSurface.Canvas.Restore();
         renderTexture.DrawingSurface.Canvas.Restore();
         toRenderOn.Canvas.Restore();
         toRenderOn.Canvas.Restore();
 
 
+        lastExecutedGraphFrame = frameTime.Frame;
+
         IsBusy = false;
         IsBusy = false;
     }
     }
 
 
-    private void ExecuteRenderRequests()
+    private void ExecuteRenderRequests(KeyFrameTime frameTime)
     {
     {
         if (isExecuting) return;
         if (isExecuting) return;
 
 
         isExecuting = true;
         isExecuting = true;
         using var ctx = DrawingBackendApi.Current?.RenderingDispatcher.EnsureContext();
         using var ctx = DrawingBackendApi.Current?.RenderingDispatcher.EnsureContext();
+
         while (renderRequests.Count > 0)
         while (renderRequests.Count > 0)
         {
         {
             RenderRequest request = renderRequests.Dequeue();
             RenderRequest request = renderRequests.Dequeue();
 
 
+            if (frameTime.Frame != lastExecutedGraphFrame && request.PreviewRenderable != this)
+            {
+                using Texture executeSurface = Texture.ForDisplay(new VecI(1));
+                RenderDocument(executeSurface.DrawingSurface, frameTime, VecI.One);
+            }
+
             try
             try
             {
             {
                 bool result = true;
                 bool result = true;

+ 5 - 3
src/PixiEditor.ChangeableDocument/Rendering/RenderContext.cs

@@ -13,6 +13,7 @@ public class RenderContext
 
 
     public KeyFrameTime FrameTime { get; }
     public KeyFrameTime FrameTime { get; }
     public ChunkResolution ChunkResolution { get; set; }
     public ChunkResolution ChunkResolution { get; set; }
+    public SamplingOptions DesiredSamplingOptions { get; set; } = SamplingOptions.Default;
     public VecI RenderOutputSize { get; set; }
     public VecI RenderOutputSize { get; set; }
 
 
     public VecI DocumentSize { get; set; }
     public VecI DocumentSize { get; set; }
@@ -24,7 +25,7 @@ public class RenderContext
 
 
 
 
     public RenderContext(DrawingSurface renderSurface, KeyFrameTime frameTime, ChunkResolution chunkResolution,
     public RenderContext(DrawingSurface renderSurface, KeyFrameTime frameTime, ChunkResolution chunkResolution,
-        VecI renderOutputSize, VecI documentSize, ColorSpace processingColorSpace, double opacity = 1)
+        VecI renderOutputSize, VecI documentSize, ColorSpace processingColorSpace, SamplingOptions desiredSampling, double opacity = 1)
     {
     {
         RenderSurface = renderSurface;
         RenderSurface = renderSurface;
         FrameTime = frameTime;
         FrameTime = frameTime;
@@ -33,6 +34,7 @@ public class RenderContext
         Opacity = opacity;
         Opacity = opacity;
         ProcessingColorSpace = processingColorSpace;
         ProcessingColorSpace = processingColorSpace;
         DocumentSize = documentSize;
         DocumentSize = documentSize;
+        DesiredSamplingOptions = desiredSampling;
     }
     }
 
 
     public static DrawingApiBlendMode GetDrawingBlendMode(BlendMode blendMode)
     public static DrawingApiBlendMode GetDrawingBlendMode(BlendMode blendMode)
@@ -63,10 +65,10 @@ public class RenderContext
 
 
     public RenderContext Clone()
     public RenderContext Clone()
     {
     {
-        return new RenderContext(RenderSurface, FrameTime, ChunkResolution, RenderOutputSize, DocumentSize, ProcessingColorSpace, Opacity)
+        return new RenderContext(RenderSurface, FrameTime, ChunkResolution, RenderOutputSize, DocumentSize, ProcessingColorSpace, DesiredSamplingOptions, Opacity)
         {
         {
             FullRerender = FullRerender,
             FullRerender = FullRerender,
-            TargetOutput = TargetOutput
+            TargetOutput = TargetOutput,
         };
         };
     }
     }
 }
 }

+ 5 - 0
src/PixiEditor.Extensions.CommonApi/UserPreferences/PreferencesConstants.cs

@@ -50,4 +50,9 @@ public static class PreferencesConstants
     public const string SecondaryBackgroundColorDefault = "#353535";
     public const string SecondaryBackgroundColorDefault = "#353535";
     public const string SecondaryBackgroundColor = "SecondaryBackgroundColor";
     public const string SecondaryBackgroundColor = "SecondaryBackgroundColor";
 
 
+    public const string MaxBilinearSampleSize = "MaxBilinearSampleSize";
+    public const int MaxBilinearSampleSizeDefault = 4096;
+
+    public const string DisablePreviews = "DisablePreviews";
+    public const bool DisablePreviewsDefault = false;
 }
 }

+ 44 - 17
src/PixiEditor.Extensions.CommonApi/UserPreferences/Settings/PixiEditor/PixiEditorSettings.cs

@@ -3,10 +3,11 @@
 public static class PixiEditorSettings
 public static class PixiEditorSettings
 {
 {
     private const string PixiEditor = "PixiEditor";
     private const string PixiEditor = "PixiEditor";
-    
+
     public static class Palettes
     public static class Palettes
     {
     {
-        public static LocalSetting<IEnumerable<string>> FavouritePalettes { get; } = LocalSetting.NonOwned<IEnumerable<string>>(PixiEditor);
+        public static LocalSetting<IEnumerable<string>> FavouritePalettes { get; } =
+            LocalSetting.NonOwned<IEnumerable<string>>(PixiEditor);
     }
     }
 
 
     public static class Update
     public static class Update
@@ -22,16 +23,20 @@ public static class PixiEditorSettings
 
 
         public static LocalSetting<string> PoEditorApiKey { get; } = new($"{PixiEditor}:POEditor_API_Key");
         public static LocalSetting<string> PoEditorApiKey { get; } = new($"{PixiEditor}:POEditor_API_Key");
     }
     }
-    
+
     public static class Tools
     public static class Tools
     {
     {
         public static SyncedSetting<bool> EnableSharedToolbar { get; } = SyncedSetting.NonOwned<bool>(PixiEditor);
         public static SyncedSetting<bool> EnableSharedToolbar { get; } = SyncedSetting.NonOwned<bool>(PixiEditor);
 
 
-        public static SyncedSetting<RightClickMode> RightClickMode { get; } = SyncedSetting.NonOwned<RightClickMode>(PixiEditor);
-        
+        public static SyncedSetting<bool> SelectionTintingEnabled { get; } = SyncedSetting.NonOwned(PixiEditor, true);
+
+        public static SyncedSetting<RightClickMode> RightClickMode { get; } =
+            SyncedSetting.NonOwned<RightClickMode>(PixiEditor);
+
         public static SyncedSetting<bool> IsPenModeEnabled { get; } = SyncedSetting.NonOwned<bool>(PixiEditor);
         public static SyncedSetting<bool> IsPenModeEnabled { get; } = SyncedSetting.NonOwned<bool>(PixiEditor);
 
 
-        public static SyncedSetting<string> PrimaryToolset { get; } = SyncedSetting.NonOwned<string>(PixiEditor, "PAINT_TOOLSET");
+        public static SyncedSetting<string> PrimaryToolset { get; } =
+            SyncedSetting.NonOwned<string>(PixiEditor, "PAINT_TOOLSET");
     }
     }
 
 
     public static class File
     public static class File
@@ -39,12 +44,13 @@ public static class PixiEditorSettings
         public static SyncedSetting<int> DefaultNewFileWidth { get; } = SyncedSetting.NonOwned(PixiEditor, 64);
         public static SyncedSetting<int> DefaultNewFileWidth { get; } = SyncedSetting.NonOwned(PixiEditor, 64);
 
 
         public static SyncedSetting<int> DefaultNewFileHeight { get; } = SyncedSetting.NonOwned(PixiEditor, 64);
         public static SyncedSetting<int> DefaultNewFileHeight { get; } = SyncedSetting.NonOwned(PixiEditor, 64);
-        
-        public static LocalSetting<IEnumerable<string>> RecentlyOpened { get; } = LocalSetting.NonOwned<IEnumerable<string>>(PixiEditor, []);
-    
+
+        public static LocalSetting<IEnumerable<string>> RecentlyOpened { get; } =
+            LocalSetting.NonOwned<IEnumerable<string>>(PixiEditor, []);
+
         public static SyncedSetting<int> MaxOpenedRecently { get; } = SyncedSetting.NonOwned(PixiEditor, 8);
         public static SyncedSetting<int> MaxOpenedRecently { get; } = SyncedSetting.NonOwned(PixiEditor, 8);
     }
     }
-    
+
     public static class StartupWindow
     public static class StartupWindow
     {
     {
         public static SyncedSetting<bool> ShowStartupWindow { get; } = SyncedSetting.NonOwned(PixiEditor, true);
         public static SyncedSetting<bool> ShowStartupWindow { get; } = SyncedSetting.NonOwned(PixiEditor, true);
@@ -53,9 +59,10 @@ public static class PixiEditorSettings
 
 
         public static SyncedSetting<bool> NewsPanelCollapsed { get; } = SyncedSetting.NonOwned<bool>(PixiEditor);
         public static SyncedSetting<bool> NewsPanelCollapsed { get; } = SyncedSetting.NonOwned<bool>(PixiEditor);
 
 
-        public static SyncedSetting<IEnumerable<int>> LastCheckedNewsIds { get; } = SyncedSetting.NonOwned<IEnumerable<int>>(PixiEditor);
+        public static SyncedSetting<IEnumerable<int>> LastCheckedNewsIds { get; } =
+            SyncedSetting.NonOwned<IEnumerable<int>>(PixiEditor);
     }
     }
-    
+
     public static class Discord
     public static class Discord
     {
     {
         public static SyncedSetting<bool> EnableRichPresence { get; } = SyncedSetting.NonOwned(PixiEditor, true);
         public static SyncedSetting<bool> EnableRichPresence { get; } = SyncedSetting.NonOwned(PixiEditor, true);
@@ -74,12 +81,32 @@ public static class PixiEditorSettings
 
 
     public static class Scene
     public static class Scene
     {
     {
-        public static SyncedSetting<bool> AutoScaleBackground { get; } = SyncedSetting.NonOwned(PixiEditor, PreferencesConstants.AutoScaleBackgroundDefault, PreferencesConstants.AutoScaleBackground);
-        public static SyncedSetting<double> CustomBackgroundScaleX { get; } = SyncedSetting.NonOwned(PixiEditor, PreferencesConstants.CustomBackgroundScaleDefault, PreferencesConstants.CustomBackgroundScaleX);
-        public static SyncedSetting<double> CustomBackgroundScaleY { get; } = SyncedSetting.NonOwned(PixiEditor, PreferencesConstants.CustomBackgroundScaleDefault, PreferencesConstants.CustomBackgroundScaleY);
+        public static SyncedSetting<bool> AutoScaleBackground { get; } = SyncedSetting.NonOwned(PixiEditor,
+            PreferencesConstants.AutoScaleBackgroundDefault, PreferencesConstants.AutoScaleBackground);
+
+        public static SyncedSetting<double> CustomBackgroundScaleX { get; } = SyncedSetting.NonOwned(PixiEditor,
+            PreferencesConstants.CustomBackgroundScaleDefault, PreferencesConstants.CustomBackgroundScaleX);
+
+        public static SyncedSetting<double> CustomBackgroundScaleY { get; } = SyncedSetting.NonOwned(PixiEditor,
+            PreferencesConstants.CustomBackgroundScaleDefault, PreferencesConstants.CustomBackgroundScaleY);
 
 
-        public static SyncedSetting<string> PrimaryBackgroundColor { get; } = SyncedSetting.NonOwned(PixiEditor, PreferencesConstants.PrimaryBackgroundColorDefault, PreferencesConstants.PrimaryBackgroundColor);
-        public static SyncedSetting<string> SecondaryBackgroundColor { get; } = SyncedSetting.NonOwned(PixiEditor, PreferencesConstants.SecondaryBackgroundColorDefault, PreferencesConstants.SecondaryBackgroundColor);
+        public static SyncedSetting<string> PrimaryBackgroundColor { get; } = SyncedSetting.NonOwned(PixiEditor,
+            PreferencesConstants.PrimaryBackgroundColorDefault, PreferencesConstants.PrimaryBackgroundColor);
 
 
+        public static SyncedSetting<string> SecondaryBackgroundColor { get; } = SyncedSetting.NonOwned(PixiEditor,
+            PreferencesConstants.SecondaryBackgroundColorDefault, PreferencesConstants.SecondaryBackgroundColor);
+    }
+
+    public static class Performance
+    {
+        public static SyncedSetting<int> MaxBilinearSampleSize { get; } = SyncedSetting.NonOwned(
+            PixiEditor,
+            PreferencesConstants.MaxBilinearSampleSizeDefault,
+            PreferencesConstants.MaxBilinearSampleSize);
+
+        public static SyncedSetting<bool> DisablePreviews { get; } = SyncedSetting.NonOwned(
+            PixiEditor,
+            PreferencesConstants.DisablePreviewsDefault,
+            PreferencesConstants.DisablePreviews);
     }
     }
 }
 }

BIN
src/PixiEditor.Extensions.Sdk/build/PixiEditor.Api.CGlueMSBuild.dll


+ 1 - 0
src/PixiEditor.IdentityProvider.PixiAuth/PixiAuthIdentityProvider.cs

@@ -129,6 +129,7 @@ public class PixiAuthIdentityProvider : IIdentityProvider
             Error(e.Message, e.TimeLeft);
             Error(e.Message, e.TimeLeft);
             LoginTimeout?.Invoke(e.TimeLeft);
             LoginTimeout?.Invoke(e.TimeLeft);
         }
         }
+        // Can produce SESSION_NOT_FOUND or USER_NOT_FOUND as a message, this comment ensure it's catched by the localization key checker
         catch (PixiAuthException authException)
         catch (PixiAuthException authException)
         {
         {
             Error(authException.Message);
             Error(authException.Message);

+ 4 - 0
src/PixiEditor.UI.Common/Accents/Base.axaml

@@ -60,6 +60,8 @@
             <Color x:Key="IntSocketColor">#4C64B1</Color>
             <Color x:Key="IntSocketColor">#4C64B1</Color>
             <Color x:Key="StringSocketColor">#C9E4C6</Color>
             <Color x:Key="StringSocketColor">#C9E4C6</Color>
             <Color x:Key="EllipseDataSocketColor">#a473a5</Color>
             <Color x:Key="EllipseDataSocketColor">#a473a5</Color>
+            <Color x:Key="LineDataSocketColor">#816382</Color>
+            <Color x:Key="RectangleDataSocketColor">#825e8a</Color>
             <Color x:Key="PointsDataSocketColor">#e1d0e1</Color>
             <Color x:Key="PointsDataSocketColor">#e1d0e1</Color>
             <Color x:Key="TextDataSocketColor">#f2f2f2</Color>
             <Color x:Key="TextDataSocketColor">#f2f2f2</Color>
             <Color x:Key="Matrix3X3SocketColor">#ffea4f</Color>
             <Color x:Key="Matrix3X3SocketColor">#ffea4f</Color>
@@ -174,6 +176,8 @@
             <ConicGradientBrush x:Key="ShapeVectorDataSocketBrush"
             <ConicGradientBrush x:Key="ShapeVectorDataSocketBrush"
                                 GradientStops="{StaticResource ShapeDataSocketGradient}" />
                                 GradientStops="{StaticResource ShapeDataSocketGradient}" />
             <SolidColorBrush x:Key="EllipseVectorDataSocketBrush" Color="{StaticResource EllipseDataSocketColor}" />
             <SolidColorBrush x:Key="EllipseVectorDataSocketBrush" Color="{StaticResource EllipseDataSocketColor}" />
+            <SolidColorBrush x:Key="LineVectorDataSocketBrush" Color="{StaticResource LineDataSocketColor}" />
+            <SolidColorBrush x:Key="RectangleVectorDataSocketBrush" Color="{StaticResource RectangleDataSocketColor}" />
             <SolidColorBrush x:Key="PointsVectorDataSocketBrush" Color="{StaticResource PointsDataSocketColor}" />
             <SolidColorBrush x:Key="PointsVectorDataSocketBrush" Color="{StaticResource PointsDataSocketColor}" />
             <SolidColorBrush x:Key="TextVectorDataSocketBrush" Color="{StaticResource TextDataSocketColor}" />
             <SolidColorBrush x:Key="TextVectorDataSocketBrush" Color="{StaticResource TextDataSocketColor}" />
 
 

+ 0 - 1
src/PixiEditor.sln

@@ -11,7 +11,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "BuildConfiguration", "Build
 	ProjectSection(SolutionItems) = preProject
 	ProjectSection(SolutionItems) = preProject
 		Custom.ruleset = Custom.ruleset
 		Custom.ruleset = Custom.ruleset
 		Directory.Build.props = Directory.Build.props
 		Directory.Build.props = Directory.Build.props
-		stylecop.json = stylecop.json
 	EndProjectSection
 	EndProjectSection
 EndProject
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChunkyImageLib", "ChunkyImageLib\ChunkyImageLib.csproj", "{6A9DA760-1E47-414C-B8E8-3B4927F18131}"
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChunkyImageLib", "ChunkyImageLib\ChunkyImageLib.csproj", "{6A9DA760-1E47-414C-B8E8-3B4927F18131}"

+ 124 - 124
src/PixiEditor/Data/Localization/Languages/ar.json

@@ -8,16 +8,16 @@
   "DISCORD": "ديسكورد",
   "DISCORD": "ديسكورد",
   "KEY_BINDINGS": "ارتباطات المفاتيح",
   "KEY_BINDINGS": "ارتباطات المفاتيح",
   "MISC": "متنوع",
   "MISC": "متنوع",
-  "SHOW_STARTUP_WINDOW": "عرض نافذة بدء التشغيل",
+  "SHOW_STARTUP_WINDOW": "عرض نافذة بدئ التشغيل",
   "RECENT_FILE_LENGTH": "طول قائمة الملفات الاخيرة",
   "RECENT_FILE_LENGTH": "طول قائمة الملفات الاخيرة",
   "RECENT_FILE_LENGTH_TOOLTIP": "كم عدد المستندات التي يتم اضهارها في ملف > الملفات الاخيرة. الافتراضي: 8",
   "RECENT_FILE_LENGTH_TOOLTIP": "كم عدد المستندات التي يتم اضهارها في ملف > الملفات الاخيرة. الافتراضي: 8",
   "DEFAULT_NEW_SIZE": "الحجم الافتراضي للملف الجديد",
   "DEFAULT_NEW_SIZE": "الحجم الافتراضي للملف الجديد",
   "WIDTH": "العرض",
   "WIDTH": "العرض",
   "HEIGHT": "الطول",
   "HEIGHT": "الطول",
-  "TOOLS": "ألادوات",
+  "TOOLS": "الأدوات",
   "ENABLE_SHARED_TOOLBAR": "تمكين شريط الادوات المشترك",
   "ENABLE_SHARED_TOOLBAR": "تمكين شريط الادوات المشترك",
   "AUTOMATIC_UPDATES": "التحديثات التلقائية",
   "AUTOMATIC_UPDATES": "التحديثات التلقائية",
-  "CHECK_FOR_UPDATES": "تحقق من التحديثات عند بدء التشغيل",
+  "CHECK_FOR_UPDATES": "تحقق من التحديثات عند بدئ التشغيل",
   "UPDATE_STREAM": "مصدر التحديث",
   "UPDATE_STREAM": "مصدر التحديث",
   "UPDATE_CHANNEL_HELP_TOOLTIP": "لا يمكن تغيير قنوات التحديث إلا في إصدار مستقل (يتم تنزيله من https://pixieditor.net).\nتتعامل إصدارات Steam و Microsoft Store مع التحديثات بشكل منفصل.",
   "UPDATE_CHANNEL_HELP_TOOLTIP": "لا يمكن تغيير قنوات التحديث إلا في إصدار مستقل (يتم تنزيله من https://pixieditor.net).\nتتعامل إصدارات Steam و Microsoft Store مع التحديثات بشكل منفصل.",
   "DEBUG": "معالجة",
   "DEBUG": "معالجة",
@@ -25,14 +25,14 @@
   "OPEN_CRASH_REPORTS_DIR": "افتح دليل تقارير الاعطال",
   "OPEN_CRASH_REPORTS_DIR": "افتح دليل تقارير الاعطال",
   "DISCORD_RICH_PRESENCE": "Rich Presence",
   "DISCORD_RICH_PRESENCE": "Rich Presence",
   "ENABLED": "مفعل",
   "ENABLED": "مفعل",
-  "SHOW_IMAGE_NAME": "اضهار اسم الصورة",
-  "SHOW_IMAGE_SIZE": "اضهار حجم الصورة",
-  "SHOW_LAYER_COUNT": "اضهار عدد الطبقات",
+  "SHOW_IMAGE_NAME": "إظهار اسم الصورة",
+  "SHOW_IMAGE_SIZE": "إظهار حجم الصورة",
+  "SHOW_LAYER_COUNT": "إظهار عدد الطبقات",
   "FILE": "ملف",
   "FILE": "ملف",
   "RECENT": "مؤخرًا",
   "RECENT": "مؤخرًا",
   "OPEN": "فتح",
   "OPEN": "فتح",
-  "SAVE_PIXI": "حفض ( .pixi )",
-  "SAVE_AS_PIXI": "حفض جديد ك ( .pixi )",
+  "SAVE_PIXI": "حفظ ( .pixi )",
+  "SAVE_AS_PIXI": "حفظ جديد كـ( .pixi )",
   "EXPORT_IMG": "تصدير (png, .jpg., الخ.)",
   "EXPORT_IMG": "تصدير (png, .jpg., الخ.)",
   "EDIT": "تعديل",
   "EDIT": "تعديل",
   "EXIT": "خروج",
   "EXIT": "خروج",
@@ -85,7 +85,7 @@
   "PATH_DOES_NOT_EXIST": "{0} غير موجود.",
   "PATH_DOES_NOT_EXIST": "{0} غير موجود.",
   "LOCATION_DOES_NOT_EXIST": "الموقع غير موجود.",
   "LOCATION_DOES_NOT_EXIST": "الموقع غير موجود.",
   "FILE_NOT_FOUND": "لم يتم العثور على الملف.",
   "FILE_NOT_FOUND": "لم يتم العثور على الملف.",
-  "ARE_YOU_SURE": "هل انت متاكد؟",
+  "ARE_YOU_SURE": "هل انت متأكد؟",
   "ARE_YOU_SURE_PATH_FULL_PATH": "هل أنت متأكد أنك تريد حذف {0}؟\nستفقد هذه البيانات لجميع التركيبات.\n(مسار كامل: {1})",
   "ARE_YOU_SURE_PATH_FULL_PATH": "هل أنت متأكد أنك تريد حذف {0}؟\nستفقد هذه البيانات لجميع التركيبات.\n(مسار كامل: {1})",
   "FAILED_TO_OPEN_FILE": "فشل في فتح الملف",
   "FAILED_TO_OPEN_FILE": "فشل في فتح الملف",
   "OLD_FILE_FORMAT": "تنسيق الملف القديم",
   "OLD_FILE_FORMAT": "تنسيق الملف القديم",
@@ -93,14 +93,14 @@
   "NOTHING_FOUND": "لم يتم العثور على شيء",
   "NOTHING_FOUND": "لم يتم العثور على شيء",
   "EXPORT": "تصدير",
   "EXPORT": "تصدير",
   "EXPORT_IMAGE": "تصدير الصورة",
   "EXPORT_IMAGE": "تصدير الصورة",
-  "IMPORT": "اضافة",
-  "SHORTCUT_TEMPLATES": "اختصارات القوالب",
+  "IMPORT": "إضافة",
+  "SHORTCUT_TEMPLATES": "إختصارات القوالب",
   "RESET_ALL": "إعادة ضبط الكل",
   "RESET_ALL": "إعادة ضبط الكل",
   "LAYER": "طبقة",
   "LAYER": "طبقة",
   "LAYER_DELETE_SELECTED": "حذف الطبقة/المجلد النشط",
   "LAYER_DELETE_SELECTED": "حذف الطبقة/المجلد النشط",
   "LAYER_DELETE_SELECTED_DESCRIPTIVE": "حذف الطبقة أو المجلد النشط",
   "LAYER_DELETE_SELECTED_DESCRIPTIVE": "حذف الطبقة أو المجلد النشط",
-  "LAYER_DELETE_ALL_SELECTED": "احذف جميع الطبقات / المجلدات المحددة",
-  "LAYER_DELETE_ALL_SELECTED_DESCRIPTIVE": "احذف كل الطبقات و / أو المجلدات المحددة",
+  "LAYER_DELETE_ALL_SELECTED": "حذف جميع الطبقات / المجلدات المحددة",
+  "LAYER_DELETE_ALL_SELECTED_DESCRIPTIVE": "حذف كل الطبقات و / أو المجلدات المحددة",
   "DELETE_SELECTED_PIXELS": "حذف البكسلات المحددة",
   "DELETE_SELECTED_PIXELS": "حذف البكسلات المحددة",
   "NEW_FOLDER": "مجلد جديد",
   "NEW_FOLDER": "مجلد جديد",
   "CREATE_NEW_FOLDER": "انشاء مجلد جديد",
   "CREATE_NEW_FOLDER": "انشاء مجلد جديد",
@@ -108,34 +108,34 @@
   "CREATE_NEW_LAYER": "انشاء طبقة جديدة",
   "CREATE_NEW_LAYER": "انشاء طبقة جديدة",
   "NEW_IMAGE": "صورة جديدة",
   "NEW_IMAGE": "صورة جديدة",
   "CREATE_NEW_IMAGE": "انشاء صورة جديدة",
   "CREATE_NEW_IMAGE": "انشاء صورة جديدة",
-  "SAVE": "حفض",
-  "SAVE_AS": "حفض جديد...",
+  "SAVE": "حفظ",
+  "SAVE_AS": "حفظ جديد...",
   "IMAGE": "صورة",
   "IMAGE": "صورة",
-  "SAVE_IMAGE": "حفض الصورة",
-  "SAVE_IMAGE_AS": "حفض الصورة كجديدة",
+  "SAVE_IMAGE": "حفظ الصورة",
+  "SAVE_IMAGE_AS": "حفظ الصورة كجديدة",
   "DUPLICATE": "تكرار",
   "DUPLICATE": "تكرار",
   "DUPLICATE_SELECTED_LAYER": "تكرار الطبقة المجددة",
   "DUPLICATE_SELECTED_LAYER": "تكرار الطبقة المجددة",
-  "CREATE_MASK": "انشاء قناع",
-  "DELETE_MASK": "حذف قناع",
+  "CREATE_MASK": "إنشاء قناع",
+  "DELETE_MASK": "حذف القناع",
   "TOGGLE_MASK": "تبديل القناع",
   "TOGGLE_MASK": "تبديل القناع",
   "APPLY_MASK": "تطبيق القناع",
   "APPLY_MASK": "تطبيق القناع",
   "TOGGLE_VISIBILITY": "تبديل الرؤية",
   "TOGGLE_VISIBILITY": "تبديل الرؤية",
-  "MOVE_MEMBER_UP": "حرك العضو لأعلى",
-  "MOVE_MEMBER_UP_DESCRIPTIVE": "انقل الطبقة أو المجلد المحدد لأعلى",
-  "MOVE_MEMBER_DOWN": "حرك العضو لأسفل",
-  "MOVE_MEMBER_DOWN_DESCRIPTIVE": "انقل الطبقة أو المجلد المحدد لأسفل",
+  "MOVE_MEMBER_UP": "تحريك العضو لأعلى",
+  "MOVE_MEMBER_UP_DESCRIPTIVE": "نقل الطبقة أو المجلد المحدد لأعلى",
+  "MOVE_MEMBER_DOWN": "تحريك العضو لأسفل",
+  "MOVE_MEMBER_DOWN_DESCRIPTIVE": "نقل الطبقة أو المجلد المحدد لأسفل",
   "MERGE_ALL_SELECTED_LAYERS": "دمج كل الطبقات المحددة",
   "MERGE_ALL_SELECTED_LAYERS": "دمج كل الطبقات المحددة",
-  "MERGE_WITH_ABOVE": "دمج الطبقة المحددة أعلاه",
+  "MERGE_WITH_ABOVE": "دمج الطبقة المحددة مع أعلاها",
   "MERGE_WITH_ABOVE_DESCRIPTIVE": "دمج الطبقة المحددة مع الطبقة التي فوقها",
   "MERGE_WITH_ABOVE_DESCRIPTIVE": "دمج الطبقة المحددة مع الطبقة التي فوقها",
-  "MERGE_WITH_BELOW": "دمج الطبقة المحددة أدناه",
+  "MERGE_WITH_BELOW": "دمج الطبقة المحددة مع أدناها",
   "MERGE_WITH_BELOW_DESCRIPTIVE": "دمج الطبقة المحددة مع الطبقة الموجودة تحتها",
   "MERGE_WITH_BELOW_DESCRIPTIVE": "دمج الطبقة المحددة مع الطبقة الموجودة تحتها",
-  "ADD_REFERENCE_LAYER": "أضف طبقة مرجعية",
-  "DELETE_REFERENCE_LAYER": "احذف الطبقة المرجعية",
+  "ADD_REFERENCE_LAYER": "إضافة طبقة مرجعية",
+  "DELETE_REFERENCE_LAYER": "حذف الطبقة المرجعية",
   "TRANSFORM_REFERENCE_LAYER": "تحويل الطبقة المرجعية",
   "TRANSFORM_REFERENCE_LAYER": "تحويل الطبقة المرجعية",
   "TOGGLE_REFERENCE_LAYER_POS": "تبديل موضع الطبقة المرجعية",
   "TOGGLE_REFERENCE_LAYER_POS": "تبديل موضع الطبقة المرجعية",
   "TOGGLE_REFERENCE_LAYER_POS_DESCRIPTIVE": "تبديل الطبقة المرجعية بين الأعلى أو الأكثر أدناه",
   "TOGGLE_REFERENCE_LAYER_POS_DESCRIPTIVE": "تبديل الطبقة المرجعية بين الأعلى أو الأكثر أدناه",
   "RESET_REFERENCE_LAYER_POS": "إعادة تعيين موضع الطبقة المرجعية",
   "RESET_REFERENCE_LAYER_POS": "إعادة تعيين موضع الطبقة المرجعية",
-  "CLIP_CANVAS": "مقطع الصورة",
+  "CLIP_CANVAS": "حف الصورة",
   "FLIP_IMG_VERTICALLY": "قلب الصورة عموديًا",
   "FLIP_IMG_VERTICALLY": "قلب الصورة عموديًا",
   "FLIP_IMG_HORIZONTALLY": "قلب الصورة أفقيًا",
   "FLIP_IMG_HORIZONTALLY": "قلب الصورة أفقيًا",
   "FLIP_LAYERS_VERTICALLY": "قلب الطبقات المحددة عموديًا",
   "FLIP_LAYERS_VERTICALLY": "قلب الطبقات المحددة عموديًا",
@@ -149,40 +149,40 @@
   "TOGGLE_VERT_SYMMETRY_AXIS": "تبديل محور التناظر العمودي",
   "TOGGLE_VERT_SYMMETRY_AXIS": "تبديل محور التناظر العمودي",
   "TOGGLE_HOR_SYMMETRY_AXIS": "تبديل محور التناظر الأفقي",
   "TOGGLE_HOR_SYMMETRY_AXIS": "تبديل محور التناظر الأفقي",
   "RESIZE_DOCUMENT": "تغيير حجم العنصر",
   "RESIZE_DOCUMENT": "تغيير حجم العنصر",
-  "RESIZE_CANVAS": "تغيير حجم الصورة",
+  "RESIZE_CANVAS": "تغيير حجم اللوحة",
   "CENTER_CONTENT": "توسيط المحتوى",
   "CENTER_CONTENT": "توسيط المحتوى",
   "CUT": "قص",
   "CUT": "قص",
   "CUT_DESCRIPTIVE": "قص المنطقة/الطبقات المحددة",
   "CUT_DESCRIPTIVE": "قص المنطقة/الطبقات المحددة",
   "PASTE": "لصق",
   "PASTE": "لصق",
   "PASTE_DESCRIPTIVE": "لصق محتويات الحافظة",
   "PASTE_DESCRIPTIVE": "لصق محتويات الحافظة",
   "PASTE_AS_NEW_LAYER": "لصق كطبقة جديدة",
   "PASTE_AS_NEW_LAYER": "لصق كطبقة جديدة",
-  "PASTE_AS_NEW_LAYER_DESCRIPTIVE": "لصق من الحافضةكطبقة جديدة",
+  "PASTE_AS_NEW_LAYER_DESCRIPTIVE": "لصق من الحافظة كطبقة جديدة",
   "PASTE_REFERENCE_LAYER": "لصق الطبقة المرجعية",
   "PASTE_REFERENCE_LAYER": "لصق الطبقة المرجعية",
   "PASTE_REFERENCE_LAYER_DESCRIPTIVE": "لصق محتويات الحافظة كطبقة مرجعية",
   "PASTE_REFERENCE_LAYER_DESCRIPTIVE": "لصق محتويات الحافظة كطبقة مرجعية",
   "PASTE_COLOR": "لصق اللون",
   "PASTE_COLOR": "لصق اللون",
   "PASTE_COLOR_DESCRIPTIVE": "لصق اللون من الحافظة",
   "PASTE_COLOR_DESCRIPTIVE": "لصق اللون من الحافظة",
-  "PASTE_COLOR_SECONDARY": "لصق اللون على أنه ثانوي",
-  "PASTE_COLOR_SECONDARY_DESCRIPTIVE": "الصق اللون من الحافظة كلون ثانوي",
+  "PASTE_COLOR_SECONDARY": "لصق اللون كلون ثانوي",
+  "PASTE_COLOR_SECONDARY_DESCRIPTIVE": "لصق اللون من الحافظة كلون ثانوي",
   "CLIPBOARD": "الحافظة",
   "CLIPBOARD": "الحافظة",
   "COPY": "نسخ",
   "COPY": "نسخ",
-  "COPY_DESCRIPTIVE": "نسخ الي الحافضة",
+  "COPY_DESCRIPTIVE": "نسخ الي الحافظة",
   "COPY_COLOR_HEX": "نسخ اللون الاساسي (HEX)",
   "COPY_COLOR_HEX": "نسخ اللون الاساسي (HEX)",
-  "COPY_COLOR_HEX_DESCRIPTIVE": "نسخ اللون الاساسي ك HEX",
+  "COPY_COLOR_HEX_DESCRIPTIVE": "نسخ اللون الاساسي كـ HEX",
   "COPY_COLOR_RGB": "نسخ اللون الاساسي (RGB)",
   "COPY_COLOR_RGB": "نسخ اللون الاساسي (RGB)",
-  "COPY_COLOR_RGB_DESCRIPTIVE": "نسخ اللون الاساسي ك RGB",
+  "COPY_COLOR_RGB_DESCRIPTIVE": "نسخ اللون الاساسي كـ RGB",
   "COPY_COLOR_SECONDARY_HEX": "نسخ اللون الثانوي (HEX)",
   "COPY_COLOR_SECONDARY_HEX": "نسخ اللون الثانوي (HEX)",
-  "COPY_COLOR_SECONDARY_HEX_DESCRIPTIVE": "نسخ اللون الثانوي ك HEX",
+  "COPY_COLOR_SECONDARY_HEX_DESCRIPTIVE": "نسخ اللون الثانوي كـ HEX",
   "COPY_COLOR_SECONDARY_RGB": "نسخ اللون الثانوي (RGB)",
   "COPY_COLOR_SECONDARY_RGB": "نسخ اللون الثانوي (RGB)",
-  "COPY_COLOR_SECONDARY_RGB_DESCRIPTIVE": "نسخ اللون الثانوي ك RGB",
-  "PALETTE_COLORS": "لوحة الالوان",
-  "REPLACE_SECONDARY_BY_PRIMARY": "استبدل اللون الثانوي باللون الأساسي",
-  "REPLACE_SECONDARY_BY_PRIMARY_DESCRIPTIVE": "استبدل اللون الثانوي باللون الأساسي",
-  "REPLACE_PRIMARY_BY_SECONDARY": "استبدل اللون الأساسي باللون الثانوي",
-  "REPLACE_PRIMARY_BY_SECONDARY_DESCRIPTIVE": "استبدل اللون الأساسي باللون الثانوي",
-  "OPEN_PALETTE_BROWSER": "افتح متصفح لوحة الألوان",
+  "COPY_COLOR_SECONDARY_RGB_DESCRIPTIVE": "نسخ اللون الثانوي كـ RGB",
+  "PALETTE_COLORS": "لوحة الألوان",
+  "REPLACE_SECONDARY_BY_PRIMARY": "استبدال اللون الأساسي بالثانوي",
+  "REPLACE_SECONDARY_BY_PRIMARY_DESCRIPTIVE": "استبدال اللون الأساسي باللون الثانوي",
+  "REPLACE_PRIMARY_BY_SECONDARY": "استبدال اللون الثانوي بالأساسي",
+  "REPLACE_PRIMARY_BY_SECONDARY_DESCRIPTIVE": "استبدال اللون الثانوي باللون الأساسي",
+  "OPEN_PALETTE_BROWSER": "فتح متصفح لوحة الألوان",
   "OVERWRITE_PALETTE_CONSENT": "اللوحة '{0}' موجودة بالفعل ، هل تريد استبدالها؟",
   "OVERWRITE_PALETTE_CONSENT": "اللوحة '{0}' موجودة بالفعل ، هل تريد استبدالها؟",
   "PALETTE_EXISTS": "لوح الألوان موجود بالفعل",
   "PALETTE_EXISTS": "لوح الألوان موجود بالفعل",
-  "REPLACE_PALETTE_CONSENT": "هل تريد استبدال اللوحة الحالية باللوحة المحددة؟",
+  "REPLACE_PALETTE_CONSENT": "هل تريد استبدال اللوحة المحددة باللوحة الحالية؟",
   "REPLACE_PALETTE": "استبدال اللوحة الحالية",
   "REPLACE_PALETTE": "استبدال اللوحة الحالية",
   "SELECT_COLOR_1": "حدد اللون 1",
   "SELECT_COLOR_1": "حدد اللون 1",
   "SELECT_COLOR_2": "حدد اللون 2",
   "SELECT_COLOR_2": "حدد اللون 2",
@@ -194,7 +194,7 @@
   "SELECT_COLOR_8": "حدد اللون 8",
   "SELECT_COLOR_8": "حدد اللون 8",
   "SELECT_COLOR_9": "حدد اللون 9",
   "SELECT_COLOR_9": "حدد اللون 9",
   "SELECT_COLOR_10": "حدد اللون 10",
   "SELECT_COLOR_10": "حدد اللون 10",
-  "SELECT_TOOL": "حدد الاداة {0}",
+  "SELECT_TOOL": "حدد الأداة {0}",
   "SELECT_COLOR_1_DESCRIPTIVE": "حدد اللون الأول في اللوحة",
   "SELECT_COLOR_1_DESCRIPTIVE": "حدد اللون الأول في اللوحة",
   "SELECT_COLOR_2_DESCRIPTIVE": "حدد اللون الثاني في اللوحة",
   "SELECT_COLOR_2_DESCRIPTIVE": "حدد اللون الثاني في اللوحة",
   "SELECT_COLOR_3_DESCRIPTIVE": "حدد اللون الثالث في اللوحة",
   "SELECT_COLOR_3_DESCRIPTIVE": "حدد اللون الثالث في اللوحة",
@@ -208,23 +208,23 @@
   "SWAP_COLORS": "تبديل الألوان",
   "SWAP_COLORS": "تبديل الألوان",
   "SWAP_COLORS_DESCRIPTIVE": "تبديل الألوان الأساسية والثانوية",
   "SWAP_COLORS_DESCRIPTIVE": "تبديل الألوان الأساسية والثانوية",
   "SEARCH": "بحث",
   "SEARCH": "بحث",
-  "COMMAND_SEARCH": "اوامر البحث",
-  "OPEN_COMMAND_SEARCH": "فتح نافذة اوامر البحث",
+  "COMMAND_SEARCH": "أوامر البحث",
+  "OPEN_COMMAND_SEARCH": "فتح نافذة أوامر البحث",
   "SELECT": "حدد",
   "SELECT": "حدد",
-  "DESELECT": "الغاء التحديد",
+  "DESELECT": "إلغاء التحديد",
   "INVERT": "عكس",
   "INVERT": "عكس",
-  "SELECTION": "اختيار",
-  "SELECT_ALL": "حدد الكل",
-  "SELECT_ALL_DESCRIPTIVE": "حدد كل شيء",
+  "SELECTION": "تحديد",
+  "SELECT_ALL": "تحديد الكل",
+  "SELECT_ALL_DESCRIPTIVE": "تحديد كل شيء",
   "CLEAR_SELECTION": "حذف المحدد",
   "CLEAR_SELECTION": "حذف المحدد",
-  "INVERT_SELECTION": "اقلب المحدد",
-  "INVERT_SELECTION_DESCRIPTIVE": "اقلب المنطقة المحددة",
+  "INVERT_SELECTION": "عكس المحدد",
+  "INVERT_SELECTION_DESCRIPTIVE": "عكس المنطقة المحددة",
   "TRANSFORM_SELECTED_AREA": "تحويل المنطقة المحددة",
   "TRANSFORM_SELECTED_AREA": "تحويل المنطقة المحددة",
   "NUDGE_SELECTED_LEFT": "دفع العنصر المحدد لليسار",
   "NUDGE_SELECTED_LEFT": "دفع العنصر المحدد لليسار",
   "NUDGE_SELECTED_RIGHT": "دفع العنصر المحدد لليمين",
   "NUDGE_SELECTED_RIGHT": "دفع العنصر المحدد لليمين",
   "NUDGE_SELECTED_UP": "دفع العنصر المحدد لأعلى",
   "NUDGE_SELECTED_UP": "دفع العنصر المحدد لأعلى",
   "NUDGE_SELECTED_DOWN": "دفع العنصر المحدد لأسفل",
   "NUDGE_SELECTED_DOWN": "دفع العنصر المحدد لأسفل",
-  "MASK_FROM_SELECTION": "قناع جديد من الاختيار",
+  "MASK_FROM_SELECTION": "قناع جديد من المحدد",
   "MASK_FROM_SELECTION_DESCRIPTIVE": "التحديد لقناع جديد",
   "MASK_FROM_SELECTION_DESCRIPTIVE": "التحديد لقناع جديد",
   "ADD_SELECTION_TO_MASK": "إضافة التحديد إلى القناع",
   "ADD_SELECTION_TO_MASK": "إضافة التحديد إلى القناع",
   "SUBTRACT_SELECTION_FROM_MASK": "قطع التحديد من القناع",
   "SUBTRACT_SELECTION_FROM_MASK": "قطع التحديد من القناع",
@@ -232,8 +232,8 @@
   "SELECTION_TO_MASK": "التحديد للقناع",
   "SELECTION_TO_MASK": "التحديد للقناع",
   "TO_NEW_MASK": "إلى قناع جديد",
   "TO_NEW_MASK": "إلى قناع جديد",
   "ADD_TO_MASK": "أضف إلى القناع",
   "ADD_TO_MASK": "أضف إلى القناع",
-  "SUBTRACT_FROM_MASK": "قص من القناع",
-  "INTERSECT_WITH_MASK": "تتقاطع مع القناع",
+  "SUBTRACT_FROM_MASK": "إنقاص من القناع",
+  "INTERSECT_WITH_MASK": "تقاطع مع القناع",
   "STYLUS": "قلم",
   "STYLUS": "قلم",
   "TOGGLE_PEN_MODE": "تبديل وضع القلم",
   "TOGGLE_PEN_MODE": "تبديل وضع القلم",
   "UNDO": "تراجع",
   "UNDO": "تراجع",
@@ -247,12 +247,12 @@
   "NEW_WINDOW_FOR_IMG": "نافذة جديدة للصورة الحالية",
   "NEW_WINDOW_FOR_IMG": "نافذة جديدة للصورة الحالية",
   "CENTER_ACTIVE_VIEWPORT": "توسيط إطار العرض النشط",
   "CENTER_ACTIVE_VIEWPORT": "توسيط إطار العرض النشط",
   "FLIP_VIEWPORT_HORIZONTALLY": "قلب إطار العرض أفقيًا",
   "FLIP_VIEWPORT_HORIZONTALLY": "قلب إطار العرض أفقيًا",
-  "FLIP_VIEWPORT_VERTICALLY": "قلب إطار العرض رأسيًا",
+  "FLIP_VIEWPORT_VERTICALLY": "قلب إطار العرض عموديًا",
   "SETTINGS": "الاعدادات",
   "SETTINGS": "الاعدادات",
   "OPEN_SETTINGS": "فتح الاعدادات",
   "OPEN_SETTINGS": "فتح الاعدادات",
   "OPEN_SETTINGS_DESCRIPTIVE": "فتح نافذة الاعدادات",
   "OPEN_SETTINGS_DESCRIPTIVE": "فتح نافذة الاعدادات",
   "OPEN_STARTUP_WINDOW": "فتح نافذة التشغيل",
   "OPEN_STARTUP_WINDOW": "فتح نافذة التشغيل",
-  "OPEN_SHORTCUT_WINDOW": "افتح نافذة الاختصارات",
+  "OPEN_SHORTCUT_WINDOW": "فتح نافذة الاختصارات",
   "OPEN_ABOUT_WINDOW": "فتح نافذة المعلومات",
   "OPEN_ABOUT_WINDOW": "فتح نافذة المعلومات",
   "ERROR": "خطأ",
   "ERROR": "خطأ",
   "INTERNAL_ERROR": "خطأ داخلي",
   "INTERNAL_ERROR": "خطأ داخلي",
@@ -266,51 +266,51 @@
   "DONATE": "تبرع",
   "DONATE": "تبرع",
   "YES": "نعم",
   "YES": "نعم",
   "NO": "لا",
   "NO": "لا",
-  "CANCEL": "الغاء",
+  "CANCEL": "إلغاء",
   "UNNAMED": "بدون اسم",
   "UNNAMED": "بدون اسم",
-  "OPEN_COMMAND_DEBUG_WINDOW": "افتح نافذة تصحيح أخطاء الأمر",
+  "OPEN_COMMAND_DEBUG_WINDOW": "افتح نافذة معالجة أخطاء الأمر",
   "DELETE": "حذف",
   "DELETE": "حذف",
   "USER_PREFS": "تفضيلات المستخدم (Roaming)",
   "USER_PREFS": "تفضيلات المستخدم (Roaming)",
-  "SHORTCUT_FILE": "اختصار الملف (Roaming)",
+  "SHORTCUT_FILE": "ملف الاختصارات (Roaming)",
   "EDITOR_DATA": "بيانات البرنامج (Local)",
   "EDITOR_DATA": "بيانات البرنامج (Local)",
   "MOVE_VIEWPORT_TOOLTIP": "ينقل إطار العرض. ({0})",
   "MOVE_VIEWPORT_TOOLTIP": "ينقل إطار العرض. ({0})",
-  "MOVE_VIEWPORT_ACTION_DISPLAY": "انقر وانتقل لتحريك منفذ العرض",
+  "MOVE_VIEWPORT_ACTION_DISPLAY": "انقر وانتقل لتحريك إطار العرض",
   "MOVE_TOOL_TOOLTIP": "ينقل وحدات البكسل المحددة ({0}). اضغط مع الاستمرار على Ctrl لتحريك كل الطبقات.",
   "MOVE_TOOL_TOOLTIP": "ينقل وحدات البكسل المحددة ({0}). اضغط مع الاستمرار على Ctrl لتحريك كل الطبقات.",
   "MOVE_TOOL_ACTION_DISPLAY": "استمر في الضغط على الماوس لتحريك وحدات البكسل المحددة. اضغط مع الاستمرار على Ctrl لتحريك كل الطبقات.",
   "MOVE_TOOL_ACTION_DISPLAY": "استمر في الضغط على الماوس لتحريك وحدات البكسل المحددة. اضغط مع الاستمرار على Ctrl لتحريك كل الطبقات.",
   "PEN_TOOL_TOOLTIP": "قلم. ({0})",
   "PEN_TOOL_TOOLTIP": "قلم. ({0})",
   "PEN_TOOL_ACTION_DISPLAY": "اضغط وحرك للرسم.",
   "PEN_TOOL_ACTION_DISPLAY": "اضغط وحرك للرسم.",
   "PIXEL_PERFECT_SETTING": "بكسل مثالي",
   "PIXEL_PERFECT_SETTING": "بكسل مثالي",
-  "RECTANGLE_TOOL_TOOLTIP": "رسم مستطيل على الصورة ({0}). اضغط مع الاستمرار على Shift لرسم مربع.",
+  "RECTANGLE_TOOL_TOOLTIP": "رسم مستطيل على اللوحة ({0}). اضغط مع الاستمرار على Shift لرسم مربع.",
   "RECTANGLE_TOOL_ACTION_DISPLAY_DEFAULT": "انقر وتحرك لرسم مستطيل. اضغط مع الاستمرار على Shift لرسم مربع.",
   "RECTANGLE_TOOL_ACTION_DISPLAY_DEFAULT": "انقر وتحرك لرسم مستطيل. اضغط مع الاستمرار على Shift لرسم مربع.",
   "RECTANGLE_TOOL_ACTION_DISPLAY_SHIFT": "انقر وحرك لرسم مربع.",
   "RECTANGLE_TOOL_ACTION_DISPLAY_SHIFT": "انقر وحرك لرسم مربع.",
   "KEEP_ORIGINAL_IMAGE_SETTING": "احتفظ بالصورة الأصلية",
   "KEEP_ORIGINAL_IMAGE_SETTING": "احتفظ بالصورة الأصلية",
-  "ROTATE_VIEWPORT_TOOLTIP": "يدور منفذ العرض. ({0})",
+  "ROTATE_VIEWPORT_TOOLTIP": "يدير إطار العرض. ({0})",
   "ROTATE_VIEWPORT_ACTION_DISPLAY": "انقر وحرك لتدوير منفذ العرض",
   "ROTATE_VIEWPORT_ACTION_DISPLAY": "انقر وحرك لتدوير منفذ العرض",
   "SELECT_TOOL_TOOLTIP": "يختار المنطقة. ({0})",
   "SELECT_TOOL_TOOLTIP": "يختار المنطقة. ({0})",
-  "SELECT_TOOL_ACTION_DISPLAY_DEFAULT": "انقر وتحرك لتحديد منطقة. اضغط مع الاستمرار على Shift للإضافة إلى التحديد الحالي. استمر في الضغط على Ctrl لقصها منه.",
+  "SELECT_TOOL_ACTION_DISPLAY_DEFAULT": "انقر وتحرك لتحديد منطقة. اضغط مع الاستمرار على Shift للإضافة إلى التحديد الحالي. استمر في الضغط على Ctrl لإنقاصها منه.",
   "SELECT_TOOL_ACTION_DISPLAY_SHIFT": "انقر وانتقل للإضافة إلى التحديد الحالي.",
   "SELECT_TOOL_ACTION_DISPLAY_SHIFT": "انقر وانتقل للإضافة إلى التحديد الحالي.",
-  "SELECT_TOOL_ACTION_DISPLAY_CTRL": "انقر وانتقل للقص من التحديد الحالي.",
+  "SELECT_TOOL_ACTION_DISPLAY_CTRL": "انقر وانتقل للإنقاص من التحديد الحالي.",
   "ZOOM_TOOL_TOOLTIP": "تكبير / تصغير منفذ العرض ({0}). انقر للتكبير ، او مع الاستمرار في الضغط على مفتاح alt وانقر للتصغير.",
   "ZOOM_TOOL_TOOLTIP": "تكبير / تصغير منفذ العرض ({0}). انقر للتكبير ، او مع الاستمرار في الضغط على مفتاح alt وانقر للتصغير.",
   "ZOOM_TOOL_ACTION_DISPLAY_DEFAULT": "انقر وتحرك للتكبير. انقر للتكبير ، واضغط باستمرار على ctrl وانقر للتصغير.",
   "ZOOM_TOOL_ACTION_DISPLAY_DEFAULT": "انقر وتحرك للتكبير. انقر للتكبير ، واضغط باستمرار على ctrl وانقر للتصغير.",
   "ZOOM_TOOL_ACTION_DISPLAY_CTRL": "انقر وتحرك للتكبير. انقر للتصغير ، وحرر مفتاح التحكم وانقر للتكبير.",
   "ZOOM_TOOL_ACTION_DISPLAY_CTRL": "انقر وتحرك للتكبير. انقر للتصغير ، وحرر مفتاح التحكم وانقر للتكبير.",
   "BRIGHTNESS_TOOL_TOOLTIP": "جعل وحدات البكسل أفتح أو أغمق ({0}). اضغط باستمرار على مفتاح Ctrl لجعل البكسل أغمق.",
   "BRIGHTNESS_TOOL_TOOLTIP": "جعل وحدات البكسل أفتح أو أغمق ({0}). اضغط باستمرار على مفتاح Ctrl لجعل البكسل أغمق.",
-  "BRIGHTNESS_TOOL_ACTION_DISPLAY_DEFAULT": "ارسم على وحدات البكسل لجعلها أكثر إشراقًا. اضغط باستمرار على Ctrl للتغميق.",
-  "BRIGHTNESS_TOOL_ACTION_DISPLAY_CTRL": "ارسم بالبكسل لجعلها أكثر قتامة. اترك Ctrl لتفتيح.",
+  "BRIGHTNESS_TOOL_ACTION_DISPLAY_DEFAULT": "ارسم على وحدات البكسل لجعلها أفتح. اضغط باستمرار على Ctrl للتغميق.",
+  "BRIGHTNESS_TOOL_ACTION_DISPLAY_CTRL": "ارسم بالبكسل لجعلها أغمق. اترك Ctrl لتفتيح.",
   "COLOR_PICKER_TOOLTIP": "يختار اللون الأساسي من الصورة. ({0})",
   "COLOR_PICKER_TOOLTIP": "يختار اللون الأساسي من الصورة. ({0})",
-  "COLOR_PICKER_ACTION_DISPLAY_DEFAULT": "انقر لاختيار الألوان. اضغط باستمرار على Ctrl لإخفاء الصورة. اضغط مع الاستمرار على Shift لإخفاء الطبقة المرجعية",
+  "COLOR_PICKER_ACTION_DISPLAY_DEFAULT": "انقر لاختيار الألوان. اضغط باستمرار على Ctrl لإخفاء اللوحة. اضغط مع الاستمرار على Shift لإخفاء الطبقة المرجعية",
   "ELLIPSE_TOOL_TOOLTIP": "رسم قطع ناقص على الصورة ({0}). اضغط مع الاستمرار على Shift لرسم دائرة.",
   "ELLIPSE_TOOL_TOOLTIP": "رسم قطع ناقص على الصورة ({0}). اضغط مع الاستمرار على Shift لرسم دائرة.",
   "ELLIPSE_TOOL_ACTION_DISPLAY_DEFAULT": "انقر وحرك الماوس لرسم شكل بيضاوي. اضغط مع الاستمرار على Shift لرسم دائرة.",
   "ELLIPSE_TOOL_ACTION_DISPLAY_DEFAULT": "انقر وحرك الماوس لرسم شكل بيضاوي. اضغط مع الاستمرار على Shift لرسم دائرة.",
   "ELLIPSE_TOOL_ACTION_DISPLAY_SHIFT": "انقر وحرك الماوس لرسم دائرة.",
   "ELLIPSE_TOOL_ACTION_DISPLAY_SHIFT": "انقر وحرك الماوس لرسم دائرة.",
   "ERASER_TOOL_TOOLTIP": "يمحو اللون من البكسل. ({0})",
   "ERASER_TOOL_TOOLTIP": "يمحو اللون من البكسل. ({0})",
   "ERASER_TOOL_ACTION_DISPLAY": "انقر وحرك للمسح.",
   "ERASER_TOOL_ACTION_DISPLAY": "انقر وحرك للمسح.",
   "FLOOD_FILL_TOOL_TOOLTIP": "املأ المنطقة باللون. ({0})",
   "FLOOD_FILL_TOOL_TOOLTIP": "املأ المنطقة باللون. ({0})",
-  "FLOOD_FILL_TOOL_ACTION_DISPLAY_DEFAULT": "اضغط على منطقة لملئها. اضغط باستمرار على مفتاح Ctrl للتطبيق على جميع الطبقات.",
-  "FLOOD_FILL_TOOL_ACTION_DISPLAY_CTRL": "اضغط على منطقة لملئها. حرر Ctrl للنظر في الطبقات الحالية فقط.",
+  "FLOOD_FILL_TOOL_ACTION_DISPLAY_DEFAULT": "اضغط على منطقة لملئها. اضغط باستمرار على مفتاح Ctrl لاعتبار جميع الطبقات.",
+  "FLOOD_FILL_TOOL_ACTION_DISPLAY_CTRL": "اضغط على منطقة لملئها. حرر Ctrl لاعتبار الطبقات الحالية فقط.",
   "LASSO_TOOL_TOOLTIP": "لاسو. ({0})",
   "LASSO_TOOL_TOOLTIP": "لاسو. ({0})",
-  "LASSO_TOOL_ACTION_DISPLAY_DEFAULT": "انقر وتحرك لتحديد وحدات البكسل داخل لاسو. اضغط مع الاستمرار على Shift للإضافة إلى التحديد الحالي. استمر في الضغط على Ctrl للحذف منه.",
+  "LASSO_TOOL_ACTION_DISPLAY_DEFAULT": "انقر وتحرك لتحديد وحدات البكسل داخل لاسو. اضغط مع الاستمرار على Shift للإضافة إلى التحديد الحالي. استمر في الضغط على Ctrl للإنقاص منه.",
   "LASSO_TOOL_ACTION_DISPLAY_SHIFT": "انقر وتحرك لإضافة وحدات بكسل داخل لاسو إلى التحديد.",
   "LASSO_TOOL_ACTION_DISPLAY_SHIFT": "انقر وتحرك لإضافة وحدات بكسل داخل لاسو إلى التحديد.",
   "LASSO_TOOL_ACTION_DISPLAY_CTRL": "انقر وتحرك لحذف وحدات البكسل داخل لاسو من التحديد.",
   "LASSO_TOOL_ACTION_DISPLAY_CTRL": "انقر وتحرك لحذف وحدات البكسل داخل لاسو من التحديد.",
-  "LINE_TOOL_TOOLTIP": "رسم خط على الصورة ({0}). اضغط مع الاستمرار على Shift لتمكين الالتقاط.",
+  "LINE_TOOL_TOOLTIP": "رسم خط على اللوحة ({0}). اضغط مع الاستمرار على Shift لتمكين الالتقاط.",
   "LINE_TOOL_ACTION_DISPLAY_DEFAULT": "انقر وتحرك لرسم خط. اضغط مع الاستمرار على Shift لتمكين الالتقاط.",
   "LINE_TOOL_ACTION_DISPLAY_DEFAULT": "انقر وتحرك لرسم خط. اضغط مع الاستمرار على Shift لتمكين الالتقاط.",
   "LINE_TOOL_ACTION_DISPLAY_SHIFT": "انقر وحرك الماوس لرسم خط مع تمكين الالتقاط.",
   "LINE_TOOL_ACTION_DISPLAY_SHIFT": "انقر وحرك الماوس لرسم خط مع تمكين الالتقاط.",
   "MAGIC_WAND_TOOL_TOOLTIP": "العصا السحرية ({0}). ملء المحدد",
   "MAGIC_WAND_TOOL_TOOLTIP": "العصا السحرية ({0}). ملء المحدد",
@@ -320,42 +320,42 @@
   "COLOR_PICKER_TOOL": "أداة انتقاء اللون",
   "COLOR_PICKER_TOOL": "أداة انتقاء اللون",
   "ELLIPSE_TOOL": "القطع الناقص",
   "ELLIPSE_TOOL": "القطع الناقص",
   "ERASER_TOOL": "ممحاة",
   "ERASER_TOOL": "ممحاة",
-  "FLOOD_FILL_TOOL": "ملء الفيضانات",
+  "FLOOD_FILL_TOOL": "ملؤ الفيضانات",
   "LASSO_TOOL": "لاسو",
   "LASSO_TOOL": "لاسو",
   "LINE_TOOL": "خط",
   "LINE_TOOL": "خط",
   "MAGIC_WAND_TOOL": "العصا السحرية",
   "MAGIC_WAND_TOOL": "العصا السحرية",
   "MOVE_TOOL": "تحريك",
   "MOVE_TOOL": "تحريك",
-  "MOVE_VIEWPORT_TOOL": "تحريك منفذ العرض",
+  "MOVE_VIEWPORT_TOOL": "تحريك إطار العرض",
   "RECTANGLE_TOOL": "مستطيل",
   "RECTANGLE_TOOL": "مستطيل",
-  "ROTATE_VIEWPORT_TOOL": "تدوير منفذ العرض",
+  "ROTATE_VIEWPORT_TOOL": "تدوير إطار العرض",
   "SELECT_TOOL_NAME": "حدد",
   "SELECT_TOOL_NAME": "حدد",
   "ZOOM_TOOL": "تكبير",
   "ZOOM_TOOL": "تكبير",
   "SHAPE_LABEL": "شكل",
   "SHAPE_LABEL": "شكل",
   "MODE_LABEL": "الوضع",
   "MODE_LABEL": "الوضع",
   "SCOPE_LABEL": "نطاق",
   "SCOPE_LABEL": "نطاق",
-  "FILL_SHAPE_LABEL": "ملء الشكل",
-  "FILL_COLOR_LABEL": "ملء اللون",
+  "FILL_SHAPE_LABEL": "ملؤ الشكل",
+  "FILL_COLOR_LABEL": "ملؤ اللون",
   "TOOL_SIZE_LABEL": "حجم الأداة",
   "TOOL_SIZE_LABEL": "حجم الأداة",
   "STRENGTH_LABEL": "قوة",
   "STRENGTH_LABEL": "قوة",
   "NEW": "جديد",
   "NEW": "جديد",
-  "ADD": "اضافة",
-  "SUBTRACT": "قص",
-  "INTERSECT": "تتقاطع",
+  "ADD": "إضافة",
+  "SUBTRACT": "إنقاص",
+  "INTERSECT": "تقاطع",
   "RECTANGLE": "مستطيل",
   "RECTANGLE": "مستطيل",
   "CIRCLE": "دائرة",
   "CIRCLE": "دائرة",
   "ABOUT": "حول البرنامج",
   "ABOUT": "حول البرنامج",
   "MINIMIZE": "تصغير",
   "MINIMIZE": "تصغير",
-  "RESTORE": "اعادة",
+  "RESTORE": "إعادة",
   "MAXIMIZE": "تكبير",
   "MAXIMIZE": "تكبير",
-  "CLOSE": "اغلق",
-  "EXPORT_SIZE_HINT": "إذا كنت ترغب في مشاركة الصورة ، فجرّب {0}٪ للحصول على أفضل وضوح",
-  "CREATE": "انشاء",
+  "CLOSE": "إغلاق",
+  "EXPORT_SIZE_HINT": "إن كنت ترغب في مشاركة الصورة ، فجرّب {0}٪ للحصول على أفضل وضوح",
+  "CREATE": "إنشاء",
   "BASE_LAYER_NAME": "الطبقة الرئيسية",
   "BASE_LAYER_NAME": "الطبقة الرئيسية",
   "ENABLE_MASK": "تمكين القناع",
   "ENABLE_MASK": "تمكين القناع",
   "SELECTED_AREA_EMPTY": "المنطقة المحددة فارغة",
   "SELECTED_AREA_EMPTY": "المنطقة المحددة فارغة",
   "NOTHING_TO_COPY": "لا يوجد شي لنسخه",
   "NOTHING_TO_COPY": "لا يوجد شي لنسخه",
   "REFERENCE_LAYER_PATH": "مسار الطبقة المرجعية",
   "REFERENCE_LAYER_PATH": "مسار الطبقة المرجعية",
-  "FLIP": "توجيه",
+  "FLIP": "قلب",
   "ROTATION": "دوران",
   "ROTATION": "دوران",
   "ROT_IMG_90_D": "تدوير الصورة 90 درجة",
   "ROT_IMG_90_D": "تدوير الصورة 90 درجة",
   "ROT_IMG_180_D": "تدوير الصورة 180 درجة",
   "ROT_IMG_180_D": "تدوير الصورة 180 درجة",
@@ -366,63 +366,63 @@
   "UNNAMED_PALETTE": "لوح غير مسمى",
   "UNNAMED_PALETTE": "لوح غير مسمى",
   "CLICK_SELECT_PRIMARY": "انقر لتحديد اللون الرئيسي.",
   "CLICK_SELECT_PRIMARY": "انقر لتحديد اللون الرئيسي.",
   "PEN_MODE": "وضع القلم",
   "PEN_MODE": "وضع القلم",
-  "VIEW": "منظر",
+  "VIEW": "إظهار",
   "HORIZONTAL_LINE_SYMMETRY": "تناظر الخط الأفقي",
   "HORIZONTAL_LINE_SYMMETRY": "تناظر الخط الأفقي",
   "VERTICAL_LINE_SYMMETRY": "تناظر الخط العمودي",
   "VERTICAL_LINE_SYMMETRY": "تناظر الخط العمودي",
   "COLOR_PICKER_TITLE": "أداة اختيار اللون",
   "COLOR_PICKER_TITLE": "أداة اختيار اللون",
-  "COLOR_SLIDERS_TITLE": "لوحة الالوان",
+  "COLOR_SLIDERS_TITLE": "أشرطة الألوان",
   "PALETTE_TITLE": "اللوحة",
   "PALETTE_TITLE": "اللوحة",
   "SWATCHES_TITLE": "حوامل",
   "SWATCHES_TITLE": "حوامل",
   "LAYERS_TITLE": "الطبقات",
   "LAYERS_TITLE": "الطبقات",
   "NORMAL_BLEND_MODE": "عادي",
   "NORMAL_BLEND_MODE": "عادي",
-  "DARKEN_BLEND_MODE": "أغمق",
+  "DARKEN_BLEND_MODE": "تغميق",
   "MULTIPLY_BLEND_MODE": "تضاعف",
   "MULTIPLY_BLEND_MODE": "تضاعف",
   "COLOR_BURN_BLEND_MODE": "احتراق الالوان",
   "COLOR_BURN_BLEND_MODE": "احتراق الالوان",
-  "LIGHTEN_BLEND_MODE": "فاتح",
+  "LIGHTEN_BLEND_MODE": "تفتيح",
   "SCREEN_BLEND_MODE": "شاشة",
   "SCREEN_BLEND_MODE": "شاشة",
   "COLOR_DODGE_BLEND_MODE": "انقاص كثافة اللون",
   "COLOR_DODGE_BLEND_MODE": "انقاص كثافة اللون",
   "OVERLAY_BLEND_MODE": "تراكب",
   "OVERLAY_BLEND_MODE": "تراكب",
   "SOFT_LIGHT_BLEND_MODE": "ضوء خافت",
   "SOFT_LIGHT_BLEND_MODE": "ضوء خافت",
-  "HARD_LIGHT_BLEND_MODE": "ضوء غامق",
-  "DIFFERENCE_BLEND_MODE": "اختلاف",
+  "HARD_LIGHT_BLEND_MODE": "ضوء شديد",
+  "DIFFERENCE_BLEND_MODE": "الفرق",
   "EXCLUSION_BLEND_MODE": "استبعاد",
   "EXCLUSION_BLEND_MODE": "استبعاد",
-  "HUE_BLEND_MODE": "مسحة",
+  "HUE_BLEND_MODE": "المسحة",
   "SATURATION_BLEND_MODE": "التشبع",
   "SATURATION_BLEND_MODE": "التشبع",
-  "LUMINOSITY_BLEND_MODE": "لمعان",
+  "LUMINOSITY_BLEND_MODE": "اللمعان",
   "COLOR_BLEND_MODE": "اللون",
   "COLOR_BLEND_MODE": "اللون",
   "NOT_SUPPORTED_BLEND_MODE": "غير مدعوم",
   "NOT_SUPPORTED_BLEND_MODE": "غير مدعوم",
-  "RESTART": "اعد تشغيل",
+  "RESTART": "إعادة التشغيل",
   "SORT_BY": "ترتيب حسب",
   "SORT_BY": "ترتيب حسب",
   "NAME": "الاسم",
   "NAME": "الاسم",
-  "COLORS": "الالوان",
+  "COLORS": "الألوان",
   "DEFAULT": "الافتراضي",
   "DEFAULT": "الافتراضي",
-  "ALPHABETICAL": "مرتب حسب الحروف الأبجدية",
+  "ALPHABETICAL": "أبجدي",
   "COLOR_COUNT": "عدد الألوان",
   "COLOR_COUNT": "عدد الألوان",
-  "ANY": "اي",
+  "ANY": "أي",
   "MAX": "الاقصى",
   "MAX": "الاقصى",
-  "MIN": "الاقل",
+  "MIN": "الأدنى",
   "EXACT": "بالضبط",
   "EXACT": "بالضبط",
   "ASCENDING": "تصاعدي",
   "ASCENDING": "تصاعدي",
   "DESCENDING": "تنازلي",
   "DESCENDING": "تنازلي",
-  "NAME_IS_TOO_LONG": "الاسم طويل جدا",
+  "NAME_IS_TOO_LONG": "الاسم طويل جدًا",
   "STOP_IT_TEXT1": "هذا يكفي. رتب أسماء ملفاتك.",
   "STOP_IT_TEXT1": "هذا يكفي. رتب أسماء ملفاتك.",
   "STOP_IT_TEXT2": "هل يمكنك التوقف عن نسخ هذه الأسماء من فضلك؟",
   "STOP_IT_TEXT2": "هل يمكنك التوقف عن نسخ هذه الأسماء من فضلك؟",
-  "REPLACER_TOOLTIP": "انقر بزر الماوس الأيمن على لوحة الألوان واختر 'استبدال' أو أسقطها هنا.",
+  "REPLACER_TOOLTIP": "انقر زر الماوس الأيمن على لوحة الألوان واختر 'استبدال' أو ألقها هنا.",
   "CLICK_TO_CHOOSE_COLOR": "انقر لاختيار اللون",
   "CLICK_TO_CHOOSE_COLOR": "انقر لاختيار اللون",
   "REPLACE_COLOR": "استبدل اللون",
   "REPLACE_COLOR": "استبدل اللون",
-  "PALETTE_COLOR_TOOLTIP": "انقر لتحديد اللون الرئيسي. قم بالسحب والإفلات على لون لوح آخر لتبديلها.",
+  "PALETTE_COLOR_TOOLTIP": "انقر لتحديده كلون رئيسي. قم بالسحب والإفلات على لون لوح آخر لتبديلها.",
   "ADD_FROM_SWATCHES": "أضف من العينات",
   "ADD_FROM_SWATCHES": "أضف من العينات",
   "ADD_COLOR_TO_PALETTE": "أضف اللون إلى لوح الألوان",
   "ADD_COLOR_TO_PALETTE": "أضف اللون إلى لوح الألوان",
   "USE_IN_CURRENT_IMAGE": "استخدم في الصورة الحالية",
   "USE_IN_CURRENT_IMAGE": "استخدم في الصورة الحالية",
-  "ADD_TO_FAVORITES": "اضافة الى المفضلة",
+  "ADD_TO_FAVORITES": "إضافة الى المفضلة",
   "BROWSE_PALETTES": "تصفح اللوحات",
   "BROWSE_PALETTES": "تصفح اللوحات",
   "LOAD_PALETTE": "تحميل لوحة",
   "LOAD_PALETTE": "تحميل لوحة",
-  "SAVE_PALETTE": "حفض اللوحة",
+  "SAVE_PALETTE": "حفظ اللوحة",
   "FAVORITES": "المفضلة",
   "FAVORITES": "المفضلة",
   "ADD_FROM_CURRENT_PALETTE": "أضف من اللوحة الحالية",
   "ADD_FROM_CURRENT_PALETTE": "أضف من اللوحة الحالية",
   "OPEN_PALETTES_DIR_TOOLTIP": "افتح دليل اللوحات في المستكشف",
   "OPEN_PALETTES_DIR_TOOLTIP": "افتح دليل اللوحات في المستكشف",
   "BROWSE_ON_LOSPEC_TOOLTIP": "تصفح اللوحات على Lospec",
   "BROWSE_ON_LOSPEC_TOOLTIP": "تصفح اللوحات على Lospec",
-  "IMPORT_FROM_FILE_TOOLTIP": "اضافة من ملف",
+  "IMPORT_FROM_FILE_TOOLTIP": "استيراد من ملف",
   "TOP_LEFT": "أعلى اليسار",
   "TOP_LEFT": "أعلى اليسار",
   "TOP_CENTER": "اعلى الوسط",
   "TOP_CENTER": "اعلى الوسط",
   "TOP_RIGHT": "اعلى اليمين",
   "TOP_RIGHT": "اعلى اليمين",
@@ -433,29 +433,29 @@
   "BOTTOM_CENTER": "اسفل الوسط",
   "BOTTOM_CENTER": "اسفل الوسط",
   "BOTTOM_RIGHT": "أسفل اليمين",
   "BOTTOM_RIGHT": "أسفل اليمين",
   "CLIP_TO_BELOW": "مقطع للطبقة أدناه",
   "CLIP_TO_BELOW": "مقطع للطبقة أدناه",
-  "MOVE_UPWARDS": "تحرك لأعلى",
+  "MOVE_UPWARDS": "تحريك لأعلى",
   "MOVE_DOWNWARDS": "تحريك للاسفل",
   "MOVE_DOWNWARDS": "تحريك للاسفل",
   "MERGE_SELECTED": "دمج المحدد",
   "MERGE_SELECTED": "دمج المحدد",
   "LOCK_TRANSPARENCY": "قفل الشفافية",
   "LOCK_TRANSPARENCY": "قفل الشفافية",
   "COULD_NOT_LOAD_PALETTE": "تعذر إحضار اللوحات",
   "COULD_NOT_LOAD_PALETTE": "تعذر إحضار اللوحات",
   "NO_PALETTES_FOUND": "لم يتم العثور على لوحات.",
   "NO_PALETTES_FOUND": "لم يتم العثور على لوحات.",
   "LOSPEC_LINK_TEXT": "سمعت أنه يمكنك العثور على بعضها هنا: lospec.com/palette-list",
   "LOSPEC_LINK_TEXT": "سمعت أنه يمكنك العثور على بعضها هنا: lospec.com/palette-list",
-  "PALETTE_BROWSER": "متصفح لوح الألوان",
-  "DELETE_PALETTE_CONFIRMATION": "هل أنت متأكد أنك تريد حذف هذه اللوحة؟ هذا لا يمكن التراجع عنها.",
-  "SHORTCUTS_IMPORTED": "تم اضافة الاختصارات من {0} بنجاح.",
-  "SHORTCUT_PROVIDER_DETECTED": "لقد اكتشفنا أنك قمت بتثبيت {0}. هل تريد اضافة الاختصارات منه؟",
-  "IMPORT_FROM_INSTALLATION": "اضافة من التثبيت",
-  "IMPORT_INSTALLATION_OPTION1": "اضافة من التنزيلات",
-  "IMPORT_INSTALLATION_OPTION2": "استخدام الافتراضي",
-  "IMPORT_FROM_TEMPLATE": "اضافة من قالب",
+  "PALETTE_BROWSER": "متصفح ألواح الألوان",
+  "DELETE_PALETTE_CONFIRMATION": "هل أنت متأكد أنك تريد حذف هذه اللوحة؟ لا يمكن التراجع عن هذا الفعل.",
+  "SHORTCUTS_IMPORTED": "تم استيراد الاختصارات من {0} بنجاح.",
+  "SHORTCUT_PROVIDER_DETECTED": "لقد وجدنا أنك قمت بتثبيت {0}. هل تريد إضافة الاختصارات منها؟",
+  "IMPORT_FROM_INSTALLATION": "إضافة من التثبيت",
+  "IMPORT_INSTALLATION_OPTION1": "إضافة من التثبيت",
+  "IMPORT_INSTALLATION_OPTION2": "استخدام الافتراضيات",
+  "IMPORT_FROM_TEMPLATE": "استيراد من قالب",
   "SHORTCUTS_IMPORTED_SUCCESS": "تم اضافة الاختصارات بنجاح.",
   "SHORTCUTS_IMPORTED_SUCCESS": "تم اضافة الاختصارات بنجاح.",
-  "WARNING_RESET_SHORTCUTS_DEFAULT": "هل أنت متأكد من أنك تريد إعادة تعيين جميع الاختصارات إلى قيمتها الافتراضية؟",
+  "WARNING_RESET_SHORTCUTS_DEFAULT": "هل أنت متأكد من أنك تريد إعادة تعيين جميع الاختصارات إلى قيمها الافتراضية؟",
   "SUCCESS": "نجاح",
   "SUCCESS": "نجاح",
   "WARNING": "تحذير",
   "WARNING": "تحذير",
   "ERROR_IMPORTING_IMAGE": "حدث خطأ أثناء استيراد الصورة.",
   "ERROR_IMPORTING_IMAGE": "حدث خطأ أثناء استيراد الصورة.",
   "SHORTCUTS_CORRUPTED_TITLE": "ملف الاختصارات تالف",
   "SHORTCUTS_CORRUPTED_TITLE": "ملف الاختصارات تالف",
-  "SHORTCUTS_CORRUPTED": "تعرض ملف الاختصارات للتلف ، اعادة التعيين إلى الوضع الافتراضي.",
-  "FAILED_DOWNLOAD_PALETTE": "فشل تحميل لوح الألوان",
+  "SHORTCUTS_CORRUPTED": "كان ملف الاختصارات مصابًا بالتلف، اعادة التعيين إلى الوضع الافتراضي.",
+  "FAILED_DOWNLOAD_PALETTE": "فشل تنزيل لوح الألوان",
   "FILE_INCORRECT_FORMAT": "لم يكن الملف بتنسيق صحيح",
   "FILE_INCORRECT_FORMAT": "لم يكن الملف بتنسيق صحيح",
   "INVALID_FILE": "ملف غير صالح",
   "INVALID_FILE": "ملف غير صالح",
   "SHORTCUTS_FILE_INCORRECT_FORMAT": "تنسيق ملف الاختصارات ليس صحيحًا",
   "SHORTCUTS_FILE_INCORRECT_FORMAT": "تنسيق ملف الاختصارات ليس صحيحًا",
@@ -465,14 +465,14 @@
   "SWAP": "تبديل",
   "SWAP": "تبديل",
   "SHORTCUT_ALREADY_ASSIGNED_SWAP": "تم بالفعل تعيين هذا الاختصار لـ '{0}'\nهل تريد استبدال الاختصار الحالي أم تبديل الاختصارين؟",
   "SHORTCUT_ALREADY_ASSIGNED_SWAP": "تم بالفعل تعيين هذا الاختصار لـ '{0}'\nهل تريد استبدال الاختصار الحالي أم تبديل الاختصارين؟",
   "SHORTCUT_ALREADY_ASSIGNED_OVERWRITE": "تم بالفعل تعيين هذا الاختصار لـ '{0}'\nهل تريد استبدال الاختصار الموجود؟",
   "SHORTCUT_ALREADY_ASSIGNED_OVERWRITE": "تم بالفعل تعيين هذا الاختصار لـ '{0}'\nهل تريد استبدال الاختصار الموجود؟",
-  "UNSAVED_CHANGES": "تغييرات غير محفوضة",
+  "UNSAVED_CHANGES": "تغييرات غير محفوظة",
   "DOCUMENT_MODIFIED_SAVE": "تم تعديل العنصر. هل تريد ان تحفظ التغييرات؟",
   "DOCUMENT_MODIFIED_SAVE": "تم تعديل العنصر. هل تريد ان تحفظ التغييرات؟",
   "SESSION_UNSAVED_DATA": "{0} ببيانات غير محفوظة. هل أنت متأكد؟",
   "SESSION_UNSAVED_DATA": "{0} ببيانات غير محفوظة. هل أنت متأكد؟",
   "PROJECT_MAINTAINERS": "مشرفو المشروع",
   "PROJECT_MAINTAINERS": "مشرفو المشروع",
   "OTHER_AWESOME_CONTRIBUTORS": "وغيرهم من المساهمين الرائعين",
   "OTHER_AWESOME_CONTRIBUTORS": "وغيرهم من المساهمين الرائعين",
-  "HELP": "مساعد",
-  "STOP_IT_TEXT3": "لا ، حقًا ، توقف عن ذلك.",
-  "STOP_IT_TEXT4": "أليس لديك أي شيء أفضل لتفعله؟",
+  "HELP": "مساعدة",
+  "STOP_IT_TEXT3": "لا، حقًا ، توقف عن ذلك.",
+  "STOP_IT_TEXT4": "أليس لديك شيء أفضل تفعله؟",
   "LINEAR_DODGE_BLEND_MODE": "مراوغة خطية (إضافة)",
   "LINEAR_DODGE_BLEND_MODE": "مراوغة خطية (إضافة)",
   "PRESS_ANY_KEY": "اضغط اي مفتاح",
   "PRESS_ANY_KEY": "اضغط اي مفتاح",
   "NONE_SHORTCUT": "غير محدد",
   "NONE_SHORTCUT": "غير محدد",
@@ -481,15 +481,15 @@
   "PUT_REFERENCE_LAYER_BELOW": "ضع طبقة مرجعية أدناه",
   "PUT_REFERENCE_LAYER_BELOW": "ضع طبقة مرجعية أدناه",
   "TOGGLE_VERTICAL_SYMMETRY": "تبديل التناظر العمودي",
   "TOGGLE_VERTICAL_SYMMETRY": "تبديل التناظر العمودي",
   "TOGGLE_HORIZONTAL_SYMMETRY": "تبديل التناظر الأفقي",
   "TOGGLE_HORIZONTAL_SYMMETRY": "تبديل التناظر الأفقي",
-  "RESET_VIEWPORT": "إعادة تعيين منفذ العرض",
-  "VIEWPORT_SETTINGS": "إعدادات منفذ العرض",
+  "RESET_VIEWPORT": "إعادة تعيين إطار العرض",
+  "VIEWPORT_SETTINGS": "إعدادات إطار العرض",
   "MOVE_TOOL_ACTION_DISPLAY_TRANSFORMING": "انقر بالماوس مع الاستمرار لتحريك وحدات البكسل في الطبقات المحددة.",
   "MOVE_TOOL_ACTION_DISPLAY_TRANSFORMING": "انقر بالماوس مع الاستمرار لتحريك وحدات البكسل في الطبقات المحددة.",
   "CTRL_KEY": "Ctrl",
   "CTRL_KEY": "Ctrl",
   "SHIFT_KEY": "Shift",
   "SHIFT_KEY": "Shift",
   "ALT_KEY": "Alt",
   "ALT_KEY": "Alt",
   "RENAME": "إعادة تسمية",
   "RENAME": "إعادة تسمية",
   "PIXEL_UNIT": "بكسل",
   "PIXEL_UNIT": "بكسل",
-  "OPEN_LOCALIZATION_DEBUG_WINDOW": "افتح نافذة تصحيح أخطاء الترجمة",
+  "OPEN_LOCALIZATION_DEBUG_WINDOW": "افتح نافذة معالجة أخطاء الترجمة",
   "FORCE_OTHER_FLOW_DIRECTION": "فرض اتجاه تدفق آخر",
   "FORCE_OTHER_FLOW_DIRECTION": "فرض اتجاه تدفق آخر",
   "API_KEY": "مفتاح API",
   "API_KEY": "مفتاح API",
   "LOCALIZATION_VIEW_TYPE": "نوع عرض الترجمة",
   "LOCALIZATION_VIEW_TYPE": "نوع عرض الترجمة",

+ 1133 - 1129
src/PixiEditor/Data/Localization/Languages/en.json

@@ -1,1131 +1,1135 @@
 {
 {
-  "RECENT_FILES": "Recent Files",
-  "OPEN_FILE": "Open file",
-  "NEW_FILE": "New",
-  "RECENT_EMPTY_TEXT": "So much empty space",
-  "LANGUAGE": "Language",
-  "GENERAL": "General",
-  "DISCORD": "Discord",
-  "KEY_BINDINGS": "Key Bindings",
-  "MISC": "Misc",
-  "SHOW_STARTUP_WINDOW": "Show Startup Window",
-  "RECENT_FILE_LENGTH": "Recent file list length",
-  "RECENT_FILE_LENGTH_TOOLTIP": "How many documents are shown under File > Recent. Default: 8",
-  "DEFAULT_NEW_SIZE": "Default new file size",
-  "WIDTH": "Width",
-  "HEIGHT": "Height",
-  "TOOLS": "Tools",
-  "ENABLE_SHARED_TOOLBAR": "Enable shared toolbar",
-  "AUTOMATIC_UPDATES": "Automatic Updates",
-  "CHECK_FOR_UPDATES": "Check updates on startup",
-  "UPDATE_STREAM": "Update stream",
-  "UPDATE_CHANNEL_HELP_TOOLTIP": "Update channels can only be changed in standalone version (downloaded from https://pixieditor.net).\nSteam and Microsoft Store versions handle updates separately.",
-  "DEBUG": "Debug",
-  "ENABLE_DEBUG_MODE": "Enable Debug mode",
-  "OPEN_CRASH_REPORTS_DIR": "Open crash reports directory",
-  "DISCORD_RICH_PRESENCE": "Rich Presence",
-  "ENABLED": "Enabled",
-  "SHOW_IMAGE_NAME": "Show image name",
-  "SHOW_IMAGE_SIZE": "Show image size",
-  "SHOW_LAYER_COUNT": "Show layer count",
-  "FILE": "File",
-  "RECENT": "Recent",
-  "OPEN": "Open",
-  "SAVE_PIXI": "Save (.pixi)",
-  "SAVE_AS_PIXI": "Save as... (.pixi)",
-  "EXPORT_IMG": "Export (.png, .jpg, etc.)",
-  "EDIT": "Edit",
-  "EXIT": "Exit",
-  "PERCENTAGE": "Percentage",
-  "ABSOLUTE": "Absolute",
-  "PRESERVE_ASPECT_RATIO": "Preserve aspect ratio",
-  "ANCHOR_POINT": "Anchor point",
-  "RESIZE_IMAGE": "Resize image",
-  "RESIZE": "Resize",
-  "DOCUMENTATION": "Documentation",
-  "WEBSITE": "Website",
-  "OPEN_WEBSITE": "Open website",
-  "REPOSITORY": "Repository",
-  "OPEN_REPOSITORY": "Open repository",
-  "OPEN_DOCUMENTATION": "Open documentation",
-  "LICENSE": "License",
-  "OPEN_LICENSE": "Open license",
-  "THIRD_PARTY_LICENSES": "Third party licenses",
-  "OPEN_THIRD_PARTY_LICENSES": "Open third party licenses",
-  "APPLY_TRANSFORM": "Apply transform",
-  "INCREASE_TOOL_SIZE": "Increase tool size",
-  "DECREASE_TOOL_SIZE": "Decrease tool size",
-  "UPDATE_READY": "Update is ready to be installed. Do you want to install it now?",
-  "NEW_UPDATE": "New update",
-  "COULD_NOT_UPDATE_WITHOUT_ADMIN": "Couldn't update without admin privileges. Please run PixiEditor as administrator.",
-  "INSUFFICIENT_PERMISSIONS": "Insufficient permissions",
-  "UPDATE_CHECK_FAILED": "Update check failed",
-  "COULD_NOT_CHECK_FOR_UPDATES": "Could not check if there is an update available.",
-  "VERSION": "Version {0}",
-  "BUILD_ID": "Build ID: {0}",
-  "OPEN_TEMP_DIR": "Open temp directory",
-  "OPEN_LOCAL_APPDATA_DIR": "Open Local AppData directory",
-  "OPEN_ROAMING_APPDATA_DIR": "Open Roaming AppData directory",
-  "OPEN_INSTALLATION_DIR": "Open installation directory",
-  "DUMP_ALL_COMMANDS": "Dump all commands",
-  "DUMP_ALL_COMMANDS_DESCRIPTIVE": "Dump all commands to a text file",
-  "CRASH": "Crash",
-  "CRASH_APP": "Crash application",
-  "DELETE_USR_PREFS": "Delete user preferences (Roaming AppData)",
-  "DELETE_SHORTCUT_FILE": "Delete shortcut file (Roaming AppData)",
-  "DELETE_EDITOR_DATA": "Delete editor data (Local AppData)",
-  "GENERATE_KEY_BINDINGS_TEMPLATE": "Generate key bindings template",
-  "GENERATE_KEY_BINDINGS_TEMPLATE_DESCRIPTIVE": "Generate key bindings json template",
-  "VALIDATE_SHORTCUT_MAP": "Validate shortcut map",
-  "VALIDATE_SHORTCUT_MAP_DESCRIPTIVE": "Validates shortcut map",
-  "VALIDATION_KEYS_NOTICE_DIALOG": "Empty keys: {0}\nUnknown Commands: {1}",
-  "RESULT": "Result",
-  "CLEAR_RECENT_DOCUMENTS": "Clear recent documents",
-  "CLEAR_RECENTLY_OPENED_DOCUMENTS": "Clear recently opened documents",
-  "OPEN_CMD_DEBUG_WINDOW": "Open command debug window",
-  "PATH_DOES_NOT_EXIST": "{0} does not exist.",
-  "LOCATION_DOES_NOT_EXIST": "Location does not exist.",
-  "FILE_NOT_FOUND": "File not found.",
-  "ARE_YOU_SURE": "Are you sure?",
-  "ARE_YOU_SURE_PATH_FULL_PATH": "Are you sure you want to delete {0}?\nThis data will be lost for all installations.\n(Full Path: {1})",
-  "FAILED_TO_OPEN_FILE": "Failed to open the file",
-  "OLD_FILE_FORMAT": "Old file format",
-  "OLD_FILE_FORMAT_DESCRIPTION": "This .pixi file uses the old format,\n which is no longer supported and can't be opened.",
-  "NOTHING_FOUND": "Nothing found",
-  "EXPORT": "Export",
-  "EXPORT_IMAGE": "Export image",
-  "IMPORT": "Import",
-  "SHORTCUT_TEMPLATES": "Shortcut templates",
-  "RESET_ALL": "Reset all",
-  "LAYER": "Layer",
-  "LAYER_DELETE_SELECTED": "Delete active layer/folder",
-  "LAYER_DELETE_SELECTED_DESCRIPTIVE": "Delete active layer or folder",
-  "LAYER_DELETE_ALL_SELECTED": "Delete all selected layers/folders",
-  "LAYER_DELETE_ALL_SELECTED_DESCRIPTIVE": "Delete all selected layers and/or folders",
-  "DELETE_SELECTED_PIXELS": "Delete selected pixels",
-  "NEW_FOLDER": "New folder",
-  "CREATE_NEW_FOLDER": "Create new folder",
-  "NEW_LAYER": "New layer",
-  "CREATE_NEW_LAYER": "Create new layer",
-  "NEW_IMAGE": "New image",
-  "CREATE_NEW_IMAGE": "Create new image",
-  "SAVE": "Save",
-  "SAVE_AS": "Save as...",
-  "IMAGE": "Image",
-  "SAVE_IMAGE": "Save image",
-  "SAVE_IMAGE_AS": "Save image as new",
-  "DUPLICATE": "Duplicate",
-  "DUPLICATE_SELECTED_LAYER": "Duplicate selected layer",
-  "CREATE_MASK": "Create mask",
-  "DELETE_MASK": "Delete mask",
-  "TOGGLE_MASK": "Toggle mask",
-  "APPLY_MASK": "Apply mask",
-  "TOGGLE_VISIBILITY": "Toggle visibility",
-  "MOVE_MEMBER_UP": "Move member upwards",
-  "MOVE_MEMBER_UP_DESCRIPTIVE": "Move selected layer or folder upwards",
-  "MOVE_MEMBER_DOWN": "Move member downwards",
-  "MOVE_MEMBER_DOWN_DESCRIPTIVE": "Move selected layer or folder downwards",
-  "MERGE_ALL_SELECTED_LAYERS": "Merge all selected layers",
-  "MERGE_WITH_ABOVE": "Merge selected layer with above",
-  "MERGE_WITH_ABOVE_DESCRIPTIVE": "Merge selected layer with the one above it",
-  "MERGE_WITH_BELOW": "Merge selected layer with below",
-  "MERGE_WITH_BELOW_DESCRIPTIVE": "Merge selected layer with the one below it",
-  "ADD_REFERENCE_LAYER": "Add Reference Layer",
-  "DELETE_REFERENCE_LAYER": "Delete reference layer",
-  "TRANSFORM_REFERENCE_LAYER": "Transform reference layer",
-  "TOGGLE_REFERENCE_LAYER_POS": "Toggle reference layer position",
-  "TOGGLE_REFERENCE_LAYER_POS_DESCRIPTIVE": "Toggle reference layer between topmost or most below",
-  "RESET_REFERENCE_LAYER_POS": "Reset reference layer position",
-  "CLIP_CANVAS": "Clip Canvas",
-  "FLIP_IMG_VERTICALLY": "Flip Image Vertically",
-  "FLIP_IMG_HORIZONTALLY": "Flip Image Horizontally",
-  "FLIP_LAYERS_VERTICALLY": "Flip Selected Layers Vertically",
-  "FLIP_LAYERS_HORIZONTALLY": "Flip Selected Layers Horizontally",
-  "ROT_IMG_90": "Rotate Image 90 degrees",
-  "ROT_IMG_180": "Rotate Image 180 degrees",
-  "ROT_IMG_-90": "Rotate Image -90 degrees",
-  "ROT_LAYERS_90": "Rotate Selected Layers 90 degrees",
-  "ROT_LAYERS_180": "Rotate Selected Layers 180 degrees",
-  "ROT_LAYERS_-90": "Rotate Selected Layers -90 degrees",
-  "TOGGLE_VERT_SYMMETRY_AXIS": "Toggle vertical symmetry axis",
-  "TOGGLE_HOR_SYMMETRY_AXIS": "Toggle horizontal symmetry axis",
-  "DELETE_SELECTED": "Delete selected",
-  "DELETE_SELECTED_DESCRIPTIVE": "Delete selected element (layer, pixels, etc.)",
-  "RESIZE_DOCUMENT": "Resize document",
-  "RESIZE_CANVAS": "Resize canvas",
-  "CENTER_CONTENT": "Center content",
-  "CUT": "Cut",
-  "CUT_DESCRIPTIVE": "Cut selected area/layers",
-  "PASTE": "Paste",
-  "PASTE_DESCRIPTIVE": "Paste clipboard contents",
-  "PASTE_AS_NEW_LAYER": "Paste as new layer",
-  "PASTE_AS_NEW_LAYER_DESCRIPTIVE": "Paste from clipboard as new layer",
-  "PASTE_REFERENCE_LAYER": "Paste reference layer",
-  "PASTE_REFERENCE_LAYER_DESCRIPTIVE": "Paste clipboard contents as reference layer",
-  "PASTE_COLOR": "Paste color",
-  "PASTE_COLOR_DESCRIPTIVE": "Paste color from clipboard",
-  "PASTE_COLOR_SECONDARY": "Paste color as secondary",
-  "PASTE_COLOR_SECONDARY_DESCRIPTIVE": "Paste color from clipboard as secondary color",
-  "CLIPBOARD": "Clipboard",
-  "COPY": "Copy",
-  "COPY_DESCRIPTIVE": "Copy to clipboard",
-  "COPY_COLOR_HEX": "Copy primary color (HEX)",
-  "COPY_COLOR_HEX_DESCRIPTIVE": "Copy primary color as HEX code",
-  "COPY_COLOR_RGB": "Copy primary color (RGB)",
-  "COPY_COLOR_RGB_DESCRIPTIVE": "Copy primary color as RGB code",
-  "COPY_COLOR_SECONDARY_HEX": "Copy secondary color (HEX)",
-  "COPY_COLOR_SECONDARY_HEX_DESCRIPTIVE": "Copy secondary color as HEX code",
-  "COPY_COLOR_SECONDARY_RGB": "Copy secondary color (RGB)",
-  "COPY_COLOR_SECONDARY_RGB_DESCRIPTIVE": "Copy secondary color as RGB code",
-  "PALETTE_COLORS": "Palette Colors",
-  "REPLACE_SECONDARY_BY_PRIMARY": "Replace secondary color by primary",
-  "REPLACE_SECONDARY_BY_PRIMARY_DESCRIPTIVE": "Replace the secondary color by the primary color",
-  "REPLACE_PRIMARY_BY_SECONDARY": "Replace primary color by secondary",
-  "REPLACE_PRIMARY_BY_SECONDARY_DESCRIPTIVE": "Replace the primary color by the secondary color",
-  "OPEN_PALETTE_BROWSER": "Open palette browser",
-  "OVERWRITE_PALETTE_CONSENT": "Palette '{0}' already exists, do you want to overwrite it?",
-  "PALETTE_EXISTS": "Palette already exists",
-  "REPLACE_PALETTE_CONSENT": "Replace current palette with selected one?",
-  "REPLACE_PALETTE": "Replace current palette",
-  "SELECT_COLOR_1": "Select color 1",
-  "SELECT_COLOR_2": "Select color 2",
-  "SELECT_COLOR_3": "Select color 3",
-  "SELECT_COLOR_4": "Select color 4",
-  "SELECT_COLOR_5": "Select color 5",
-  "SELECT_COLOR_6": "Select color 6",
-  "SELECT_COLOR_7": "Select color 7",
-  "SELECT_COLOR_8": "Select color 8",
-  "SELECT_COLOR_9": "Select color 9",
-  "SELECT_COLOR_10": "Select color 10",
-  "SELECT_TOOL": "Select {0} Tool",
-  "SELECT_COLOR_1_DESCRIPTIVE": "Select the first color in the palette",
-  "SELECT_COLOR_2_DESCRIPTIVE": "Select the second color in the palette",
-  "SELECT_COLOR_3_DESCRIPTIVE": "Select the third color in the palette",
-  "SELECT_COLOR_4_DESCRIPTIVE": "Select the fourth color in the palette",
-  "SELECT_COLOR_5_DESCRIPTIVE": "Select the fifth color in the palette",
-  "SELECT_COLOR_6_DESCRIPTIVE": "Select the sixth color in the palette",
-  "SELECT_COLOR_7_DESCRIPTIVE": "Select the seventh color in the palette",
-  "SELECT_COLOR_8_DESCRIPTIVE": "Select the eighth color in the palette",
-  "SELECT_COLOR_9_DESCRIPTIVE": "Select the ninth color in the palette",
-  "SELECT_COLOR_10_DESCRIPTIVE": "Select the tenth color in the palette",
-  "SWAP_COLORS": "Swap colors",
-  "SWAP_COLORS_DESCRIPTIVE": "Swap primary and secondary colors",
-  "SEARCH": "Search",
-  "COMMAND_SEARCH": "Command search",
-  "OPEN_COMMAND_SEARCH": "Open command search window",
-  "SELECT": "Select",
-  "DESELECT": "Deselect",
-  "INVERT": "Invert",
-  "SELECTION": "Selection",
-  "SELECT_ALL": "Select all",
-  "SELECT_ALL_DESCRIPTIVE": "Select everything",
-  "CLEAR_SELECTION": "Clear selection",
-  "INVERT_SELECTION": "Invert selection",
-  "INVERT_SELECTION_DESCRIPTIVE": "Invert the selection",
-  "TRANSFORM_SELECTED_AREA": "Transform selected area",
-  "NUDGE_SELECTED_LEFT": "Nudge selected object left",
-  "NUDGE_SELECTED_RIGHT": "Nudge selected object right",
-  "NUDGE_SELECTED_UP": "Nudge selected object up",
-  "NUDGE_SELECTED_DOWN": "Nudge selected object down",
-  "MASK_FROM_SELECTION": "New mask from selection",
-  "MASK_FROM_SELECTION_DESCRIPTIVE": "Selection to new mask",
-  "ADD_SELECTION_TO_MASK": "Add selection to mask",
-  "SUBTRACT_SELECTION_FROM_MASK": "Subtract selection from mask",
-  "INTERSECT_SELECTION_MASK": "Intersect selection with mask",
-  "SELECTION_TO_MASK": "Selection to mask",
-  "TO_NEW_MASK": "to new mask",
-  "ADD_TO_MASK": "add to mask",
-  "SUBTRACT_FROM_MASK": "subtract from mask",
-  "INTERSECT_WITH_MASK": "intersect with mask",
-  "STYLUS": "Stylus",
-  "TOGGLE_PEN_MODE": "Toggle pen mode",
-  "UNDO": "Undo",
-  "UNDO_DESCRIPTIVE": "Undo last action",
-  "REDO": "Redo",
-  "REDO_DESCRIPTIVE": "Redo last action",
-  "WINDOWS": "Windows",
-  "TOGGLE_GRIDLINES": "Toggle gridlines",
-  "GRIDLINES_SIZE": "Grid Size",
-  "ZOOM_IN": "Zoom in",
-  "ZOOM_OUT": "Zoom out",
-  "NEW_WINDOW_FOR_IMG": "New window for current image",
-  "CENTER_ACTIVE_VIEWPORT": "Center active viewport",
-  "FLIP_VIEWPORT_HORIZONTALLY": "Flip viewport horizontally",
-  "FLIP_VIEWPORT_VERTICALLY": "Flip viewport vertically",
-  "SETTINGS": "Settings",
-  "OPEN_SETTINGS": "Open settings",
-  "OPEN_SETTINGS_DESCRIPTIVE": "Open settings window",
-  "OPEN_STARTUP_WINDOW": "Open startup window",
-  "OPEN_SHORTCUT_WINDOW": "Open shortcuts window",
-  "OPEN_ABOUT_WINDOW": "Open about window",
-  "OPEN_PREVIEW_WINDOW": "Open preview window",
-  "ERROR": "Error",
-  "INTERNAL_ERROR": "Internal error",
-  "ERROR_SAVE_LOCATION": "Couldn't save the file to the specified location",
-  "ERROR_WHILE_SAVING": "An internal error occured while saving. Please try again.",
-  "UNKNOWN_ERROR_SAVING": "An error occured while saving.",
-  "FAILED_ASSOCIATE_LOSPEC": "Failed to associate Lospec Palette protocol.",
-  "REDDIT": "Reddit",
-  "GITHUB": "GitHub",
-  "YOUTUBE": "YouTube",
-  "DONATE": "Donate",
-  "YES": "Yes",
-  "NO": "No",
-  "CANCEL": "Cancel",
-  "UNNAMED": "Unnamed",
-  "OPEN_COMMAND_DEBUG_WINDOW": "Open command debug window",
-  "DELETE": "Delete",
-  "USER_PREFS": "User preferences (Roaming)",
-  "SHORTCUT_FILE": "Shortcut file (Roaming)",
-  "EDITOR_DATA": "Editor data (Local)",
-  "MOVE_VIEWPORT_TOOLTIP": "Moves viewport. ({0})",
-  "MOVE_VIEWPORT_ACTION_DISPLAY": "Click and move to pan the viewport",
-  "MOVE_TOOL_TOOLTIP": "Select and transform layers ({0}).",
-  "MOVE_TOOL_ACTION_DISPLAY": "Hold mouse to move selected pixels. Hold Ctrl to move all layers.",
-  "PEN_TOOL_TOOLTIP": "Pen. ({0})",
-  "PEN_TOOL_ACTION_DISPLAY": "Click and move to draw.",
-  "PIXEL_PERFECT_SETTING": "Pixel perfect",
-  "RECTANGLE_TOOL_TOOLTIP": "Draws rectangle on canvas ({0}). Hold Shift to draw a square.",
-  "RECTANGLE_TOOL_ACTION_DISPLAY_DEFAULT": "Click and move to draw a rectangle. Hold Shift to draw a square.",
-  "RECTANGLE_TOOL_ACTION_DISPLAY_SHIFT": "Click and move to draw a square.",
-  "KEEP_ORIGINAL_IMAGE_SETTING": "Keep original image",
-  "ROTATE_VIEWPORT_TOOLTIP": "Rotates viewport. ({0})",
-  "ROTATE_VIEWPORT_ACTION_DISPLAY": "Click and move to rotate the viewport",
-  "SELECT_TOOL_TOOLTIP": "Selects area. ({0})",
-  "SELECT_TOOL_ACTION_DISPLAY_DEFAULT": "Click and move to select an area. Hold Shift to add to existing selection. Hold Ctrl to subtract from it.",
-  "SELECT_TOOL_ACTION_DISPLAY_SHIFT": "Click and move to add to the current selection.",
-  "SELECT_TOOL_ACTION_DISPLAY_CTRL": "Click and move to subtract from the current selection.",
-  "ZOOM_TOOL_TOOLTIP": "Zooms viewport ({0}). Click to zoom in, hold alt and click to zoom out.",
-  "ZOOM_TOOL_ACTION_DISPLAY_DEFAULT": "Click and move to zoom. Click to zoom in, hold ctrl and click to zoom out.",
-  "ZOOM_TOOL_ACTION_DISPLAY_CTRL": "Click and move to zoom. Click to zoom out, release ctrl and click to zoom in.",
-  "BRIGHTNESS_TOOL_TOOLTIP": "Makes pixels brighter or darker ({0}). Hold Ctrl to make pixels darker.",
-  "BRIGHTNESS_TOOL_ACTION_DISPLAY_DEFAULT": "Draw on pixels to make them brighter. Hold Ctrl to darken.",
-  "BRIGHTNESS_TOOL_ACTION_DISPLAY_CTRL": "Draw on pixels to make them darker. Release Ctrl to brighten.",
-  "COLOR_PICKER_TOOLTIP": "Picks the primary color from the canvas. ({0})",
-  "COLOR_PICKER_ACTION_DISPLAY_DEFAULT": "Click to pick colors. Hold Ctrl to hide the canvas. Hold Shift to hide the reference layer",
-  "ELLIPSE_TOOL_TOOLTIP": "Draws an ellipse on canvas ({0}). Hold Shift to draw a circle.",
-  "ELLIPSE_TOOL_ACTION_DISPLAY_DEFAULT": "Click and move mouse to draw an ellipse. Hold Shift to draw a circle.",
-  "ELLIPSE_TOOL_ACTION_DISPLAY_SHIFT": "Click and move mouse to draw a circle.",
-  "ERASER_TOOL_TOOLTIP": "Erases color from pixel. ({0})",
-  "ERASER_TOOL_ACTION_DISPLAY": "Click and move to erase.",
-  "FLOOD_FILL_TOOL_TOOLTIP": "Fills area with color. ({0})",
-  "FLOOD_FILL_TOOL_ACTION_DISPLAY_DEFAULT": "Press on an area to fill it. Hold down Ctrl to consider all layers.",
-  "FLOOD_FILL_TOOL_ACTION_DISPLAY_CTRL": "Press on an area to fill it. Release Ctrl to only consider the current layers.",
-  "LASSO_TOOL_TOOLTIP": "Lasso. ({0})",
-  "LASSO_TOOL_ACTION_DISPLAY_DEFAULT": "Click and move to select pixels inside of the lasso. Hold Shift to add to existing selection. Hold Ctrl to subtract from it.",
-  "LASSO_TOOL_ACTION_DISPLAY_SHIFT": "Click and move to add pixels inside of the lasso to the selection.",
-  "LASSO_TOOL_ACTION_DISPLAY_CTRL": "Click and move to subtract pixels inside of the lasso from the selection.",
-  "LINE_TOOL_TOOLTIP": "Draws line on canvas ({0}). Hold Shift to enable snapping.",
-  "LINE_TOOL_ACTION_DISPLAY_DEFAULT": "Click and move to draw a line. Hold Shift to enable snapping.",
-  "LINE_TOOL_ACTION_DISPLAY_SHIFT": "Click and move mouse to draw a line with snapping enabled.",
-  "MAGIC_WAND_TOOL_TOOLTIP": "Magic Wand ({0}). Flood's the selection",
-  "MAGIC_WAND_ACTION_DISPLAY": "Click to flood the selection.",
-  "PEN_TOOL": "Pen",
-  "BRIGHTNESS_TOOL": "Brightness",
-  "COLOR_PICKER_TOOL": "Color Picker",
-  "ELLIPSE_TOOL": "Ellipse",
-  "ERASER_TOOL": "Eraser",
-  "FLOOD_FILL_TOOL": "Flood Fill",
-  "LASSO_TOOL": "Lasso",
-  "LINE_TOOL": "Line",
-  "MAGIC_WAND_TOOL": "Magic Wand",
-  "MOVE_TOOL": "Move",
-  "MOVE_VIEWPORT_TOOL": "Move Viewport",
-  "RECTANGLE_TOOL": "Rectangle",
-  "ROTATE_VIEWPORT_TOOL": "Rotate Viewport",
-  "SELECT_TOOL_NAME": "Select",
-  "ZOOM_TOOL": "Zoom",
-  "SHAPE_LABEL": "Shape",
-  "MODE_LABEL": "Mode",
-  "SCOPE_LABEL": "Scope",
-  "FILL_SHAPE_LABEL": "Fill shape",
-  "FILL_COLOR_LABEL": "Fill color",
-  "TOOL_SIZE_LABEL": "Tool size",
-  "STRENGTH_LABEL": "Strength",
-  "NEW": "New",
-  "ADD": "Add",
-  "SUBTRACT": "Subtract",
-  "INTERSECT": "Intersect",
-  "RECTANGLE": "Rectangle",
-  "CIRCLE": "Circle",
-  "ABOUT": "About",
-  "MINIMIZE": "Minimize",
-  "RESTORE": "Restore",
-  "MAXIMIZE": "Maximize",
-  "CLOSE": "Close",
-  "EXPORT_SIZE_HINT": "If you want to share the image, try {0}% for the best clarity",
-  "CREATE": "Create",
-  "BASE_LAYER_NAME": "Base layer",
-  "ENABLE_MASK": "Enable mask",
-  "SELECTED_AREA_EMPTY": "Selected area is empty",
-  "NOTHING_TO_COPY": "Nothing to copy",
-  "REFERENCE_LAYER_PATH": "Reference layer path",
-  "FLIP": "Flip",
-  "ROTATION": "Rotation",
-  "ROT_IMG_90_D": "Rotate Image 90°",
-  "ROT_IMG_180_D": "Rotate Image 180°",
-  "ROT_IMG_-90_D": "Rotate Image -90°",
-  "ROT_LAYERS_90_D": "Rotate Selected Layers 90°",
-  "ROT_LAYERS_180_D": "Rotate Selected Layers 180°",
-  "ROT_LAYERS_-90_D": "Rotate Selected Layers -90°",
-  "UNNAMED_PALETTE": "Unnamed Palette",
-  "CLICK_SELECT_PRIMARY": "Click to select as main color.",
-  "PEN_MODE": "Pen mode",
-  "VIEW": "View",
-  "HORIZONTAL_LINE_SYMMETRY": "Horizontal line symmetry",
-  "VERTICAL_LINE_SYMMETRY": "Vertical line symmetry",
-  "COLOR_PICKER_TITLE": "Color Picker",
-  "COLOR_SLIDERS_TITLE": "Color Sliders",
-  "PALETTE_TITLE": "Palette",
-  "SWATCHES_TITLE": "Swatches",
-  "LAYERS_TITLE": "Layers",
-  "PREVIEW_TITLE": "Preview",
-  "NORMAL_BLEND_MODE": "Normal",
-  "ERASE_BLEND_MODE": "Erase",
-  "DARKEN_BLEND_MODE": "Darken",
-  "MULTIPLY_BLEND_MODE": "Multiply",
-  "COLOR_BURN_BLEND_MODE": "Color burn",
-  "LIGHTEN_BLEND_MODE": "Lighten",
-  "SCREEN_BLEND_MODE": "Screen",
-  "COLOR_DODGE_BLEND_MODE": "Color dodge",
-  "OVERLAY_BLEND_MODE": "Overlay",
-  "SOFT_LIGHT_BLEND_MODE": "Soft light",
-  "HARD_LIGHT_BLEND_MODE": "Hard light",
-  "DIFFERENCE_BLEND_MODE": "Difference",
-  "EXCLUSION_BLEND_MODE": "Exclusion",
-  "HUE_BLEND_MODE": "Hue",
-  "SATURATION_BLEND_MODE": "Saturation",
-  "LUMINOSITY_BLEND_MODE": "Luminosity",
-  "COLOR_BLEND_MODE": "Color",
-  "NOT_SUPPORTED_BLEND_MODE": "Not supported",
-  "RESTART": "Restart",
-  "SORT_BY": "Sort by",
-  "NAME": "Name",
-  "COLORS": "Colors",
-  "DEFAULT": "Default",
-  "ALPHABETICAL": "Alphabetical",
-  "COLOR_COUNT": "Color count",
-  "ANY": "Any",
-  "MAX": "Max",
-  "MIN": "Min",
-  "EXACT": "Exact",
-  "ASCENDING": "Ascending",
-  "DESCENDING": "Descending",
-  "NAME_IS_TOO_LONG": "The name is too long",
-  "STOP_IT_TEXT1": "That's enough. Tidy up your file names.",
-  "STOP_IT_TEXT2": "Can you stop copying these names please?",
-  "REPLACER_TOOLTIP": "Right click on palette color and choose 'Replace' or drop it here.",
-  "CLICK_TO_CHOOSE_COLOR": "Click to choose the color",
-  "REPLACE_COLOR": "Replace color",
-  "PALETTE_COLOR_TOOLTIP": "Click to select as main color. Drag and drop onto another palette color to swap them.",
-  "ADD_FROM_SWATCHES": "Add from swatches",
-  "ADD_COLOR_TO_PALETTE": "Add color to palette",
-  "USE_IN_CURRENT_IMAGE": "Use in current image",
-  "ADD_TO_FAVORITES": "Add to favorites",
-  "BROWSE_PALETTES": "Browse palettes",
-  "LOAD_PALETTE": "Load palette",
-  "SAVE_PALETTE": "Save palette",
-  "FAVORITES": "Favorites",
-  "ADD_FROM_CURRENT_PALETTE": "Add from current palette",
-  "OPEN_PALETTES_DIR_TOOLTIP": "Open palettes directory in explorer",
-  "BROWSE_ON_LOSPEC_TOOLTIP": "Browse palettes on Lospec",
-  "IMPORT_FROM_FILE_TOOLTIP": "Import from file",
-  "TOP_LEFT": "Top left",
-  "TOP_CENTER": "Top center",
-  "TOP_RIGHT": "Top right",
-  "MIDDLE_LEFT": "Middle left",
-  "MIDDLE_CENTER": "Middle center",
-  "MIDDLE_RIGHT": "Middle right",
-  "BOTTOM_LEFT": "Bottom left",
-  "BOTTOM_CENTER": "Bottom center",
-  "BOTTOM_RIGHT": "Bottom right",
-  "CLIP_TO_BELOW": "Clip to member below",
-  "MOVE_UPWARDS": "Move upwards",
-  "MOVE_DOWNWARDS": "Move downwards",
-  "MERGE_SELECTED": "Merge selected",
-  "LOCK_TRANSPARENCY": "Lock transparency",
-  "COULD_NOT_LOAD_PALETTE": "Couldn't fetch palettes",
-  "NO_PALETTES_FOUND": "No palettes found.",
-  "LOSPEC_LINK_TEXT": "I heard you can find some here: lospec.com/palette-list",
-  "PALETTE_BROWSER": "Palette Browser",
-  "DELETE_PALETTE_CONFIRMATION": "Are you sure you want to delete this palette? This cannot be undone.",
-  "SHORTCUTS_IMPORTED": "Shortcuts from {0} were imported successfully.",
-  "SHORTCUT_PROVIDER_DETECTED": "We've detected, that you have {0} installed. Do you want to import shortcuts from it?",
-  "IMPORT_FROM_INSTALLATION": "Import from installation",
-  "IMPORT_INSTALLATION_OPTION1": "Import from installation",
-  "IMPORT_INSTALLATION_OPTION2": "Use defaults",
-  "IMPORT_FROM_TEMPLATE": "Import from template",
-  "SHORTCUTS_IMPORTED_SUCCESS": "Shortcuts were imported successfully.",
-  "WARNING_RESET_SHORTCUTS_DEFAULT": "Are you sure you want to reset all shortcuts to their default value?",
-  "SUCCESS": "Success",
-  "WARNING": "Warning",
-  "ERROR_IMPORTING_IMAGE": "An error occured while importing the image.",
-  "SHORTCUTS_CORRUPTED_TITLE": "Corrupted shortcuts file",
-  "SHORTCUTS_CORRUPTED": "Shortcuts file was corrupted, resetting to default.",
-  "FAILED_DOWNLOAD_PALETTE": "Failed to download palette",
-  "FILE_INCORRECT_FORMAT": "The file was not in a correct format",
-  "INVALID_FILE": "Invalid file",
-  "SHORTCUTS_FILE_INCORRECT_FORMAT": "Shortcuts file was not in a correct format",
-  "UNSUPPORTED_FILE_FORMAT": "This file format is unsupported",
-  "ALREADY_ASSIGNED": "Already assigned",
-  "REPLACE": "Replace",
-  "SWAP": "Swap",
-  "SHORTCUT_ALREADY_ASSIGNED_SWAP": "This shortcut is already assigned to '{0}'\nDo you want to replace the existing shortcut or swap the two?",
-  "SHORTCUT_ALREADY_ASSIGNED_OVERWRITE": "This shortcut is already assigned to '{0}'\nDo you want to replace the existing shortcut?",
-  "UNSAVED_CHANGES": "Unsaved changes",
-  "DOCUMENT_MODIFIED_SAVE": "The document has been modified. Do you want to save changes?",
-  "SESSION_UNSAVED_DATA": "{0} with unsaved data. Are you sure?",
-  "PROJECT_MAINTAINERS": "Project Maintainers",
-  "OTHER_AWESOME_CONTRIBUTORS": "And other awesome contributors",
-  "HELP": "Help",
-  "STOP_IT_TEXT3": "No, really, stop it.",
-  "STOP_IT_TEXT4": "Don't you have anything better to do?",
-  "LINEAR_DODGE_BLEND_MODE": "Linear dodge (Add)",
-  "PRESS_ANY_KEY": "Press any key",
-  "NONE_SHORTCUT": "None",
-  "REFERENCE": "Reference",
-  "PUT_REFERENCE_LAYER_ABOVE": "Put reference layer above",
-  "PUT_REFERENCE_LAYER_BELOW": "Put reference layer below",
-  "TOGGLE_VERTICAL_SYMMETRY": "Toggle vertical symmetry",
-  "TOGGLE_HORIZONTAL_SYMMETRY": "Toggle horizontal symmetry",
-  "RESET_VIEWPORT": "Reset viewport",
-  "VIEWPORT_SETTINGS": "Viewport settings",
-  "MOVE_TOOL_ACTION_DISPLAY_TRANSFORMING": "Click and hold mouse to move pixels in selected layers.",
-  "CTRL_KEY": "Ctrl",
-  "SHIFT_KEY": "Shift",
-  "ALT_KEY": "Alt",
-  "RENAME": "Rename",
-  "PIXEL_UNIT": "px",
-  "OPEN_LOCALIZATION_DEBUG_WINDOW": "Open Localization Debug Window",
-  "FORCE_OTHER_FLOW_DIRECTION": "Force other flow direction",
-  "API_KEY": "API Key",
-  "LOCALIZATION_VIEW_TYPE": "Localization View Type",
-  "LOAD_LANGUAGE_FROM_FILE": "Load language from file",
-  "LOG_IN": "Log in",
-  "SYNC": "Sync",
-  "NOT_LOGGED_IN": "Not logged in",
-  "POE_EDITOR_ERROR": "POEditor Error: {0} {1}",
-  "HTTP_ERROR_MESSAGE": "HTTP Error: {0} {1}",
-  "LOGGED_IN": "Logged in",
-  "SYNCED_SUCCESSFULLY": "Synced successfully",
-  "EXCEPTION_ERROR": "Exception: {0}",
-  "DROP_PALETTE": "Drop palette here",
-  "SECURITY_ERROR": "Security error",
-  "SECURITY_ERROR_MSG": "No rights to write to the specified location.",
-  "IO_ERROR": "IO error",
-  "IO_ERROR_MSG": "Error while writing to disk.",
-  "FAILED_ASSOCIATE_PIXI": "Failed to associate .pixi file with PixiEditor.",
-  "COULD_NOT_SAVE_PALETTE": "There was an error while saving the palette.",
-  "NO_COLORS_TO_SAVE": "There are no colors to save.",
-  "CANVAS": "Canvas",
-  "SINGLE_LAYER": "Single Layer",
-  "CHOOSE": "Choose",
-  "REMOVE": "Remove",
-  "FILE_FORMAT_NOT_ASEPRITE_KEYS": "File is not a \".aseprite-keys\" file",
-  "FILE_HAS_INVALID_SHORTCUT": "The file contains an invalid shortcut",
-  "FILE_EXTENSION_NOT_SUPPORTED": "The file type '{0}' is not supported",
-  "ERROR_READING_FILE": "Error while reading the file",
-  "DISCARD_PALETTE": "Discard palette",
-  "DISCARD_PALETTE_CONFIRMATION": "Are you sure you want to discard current palette? This cannot be undone.",
-  "IMPORT_AS_NEW_LAYER": "Import as new layer",
-  "PASTE_AS_PRIMARY_COLOR": "Paste as primary color",
-  "IMPORT_AS_NEW_FILE": "Import as new file",
-  "IMPORT_PALETTE_FILE": "Import palette file",
-  "IMPORT_MULTIPLE_PALETTE_COLORS": "Import colors into palette",
-  "IMPORT_SINGLE_PALETTE_COLOR": "Import color into palette",
-  "IMPORT_AS_REFERENCE_LAYER": "Import as reference layer",
-  "NAVIGATOR_PICK_ACTION_DISPLAY": "Right-click to pick color, Shift-right-click to copy color to clipboard",
-  "OPEN_FILE_FROM_CLIPBOARD": "Open from clipboard",
-  "OPEN_FILE_FROM_CLIPBOARD_DESCRIPTIVE": "Open from clipboard",
-  "OPEN_LOCALIZATION_DATA": "Do you want to open the LocalizationData.json?\nThe updated date has been put in the clipboard.\nNote that changes wont be applied until a restart",
-  "DOWNLOADING_LANGUAGE_FAILED": "Downloading language failed.\nAPI Key might have been overused.",
-  "LOCALIZATION_DATA_NOT_FOUND": "Localization data path not found",
-  "APPLY": "Apply",
-  "UPDATE_SOURCE": "Update source",
-  "COPY_TO_CLIPBOARD": "Copy to clipboard",
-  "LANGUAGE_FILE_NOT_FOUND": "Language file not found.\nLooking for {0}",
-  "PROJECT_ROOT_NOT_FOUND": "PixiEditor Project root not found.\nLooking for PixiEditor.csproj",
-  "LOCALIZATION_FOLDER_NOT_FOUND": "Localization folder not found.\nLooking for /Data/Localization",
-  "SELECT_A_LANGUAGE": "Select a language",
-  "DONE": "Done",
-  "SOURCE_UNSET_OR_MISSING": "Source missing/unset",
-  "SOURCE_NEWER": "Source newer",
-  "SOURCE_UP_TO_DATE": "Source is up to date",
-  "SOURCE_OLDER": "Cloud newer",
-  "COLOR_PICKER_ACTION_DISPLAY_REFERENCE_ONLY": "Click to pick colors from the reference layer.",
-  "COLOR_PICKER_ACTION_DISPLAY_CANVAS_ONLY": "Click to pick colors from the canvas.",
-  "LOCALIZATION_DEBUG_WINDOW_TITLE": "Localization Debug Window",
-  "COMMAND_DEBUG_WINDOW_TITLE": "Command Debug Window",
-  "SHORTCUTS_TITLE": "Shortcuts",
-  "TRANSFORM_ACTION_DISPLAY_SCALE_ROTATE_SHEAR_PERSPECTIVE": "Drag handles to scale transform. Hold Ctrl and drag a handle to scale from center. Hold Shift to scale proportionally. Hold Alt and drag a side handle to shear. Drag outside handles to rotate.",
-  "TRANSFORM_ACTION_DISPLAY_SCALE_ROTATE_SHEAR_NOPERSPECTIVE": "Drag handles to scale transform. Hold Ctrl and drag a handle to scale from center. Hold Shift to scale proportionally. Hold Alt and drag a side handle to shear. Drag outside handles to rotate.",
-  "TRANSFORM_ACTION_DISPLAY_SCALE_ROTATE_NOSHEAR_NOPERSPECTIVE": "Drag handles to scale transform. Hold Ctrl and drag a handle to scale from center. Hold Shift to scale proportionally. Drag outside handles to rotate.",
-  "TRANSFORM_ACTION_DISPLAY_SCALE_NOROTATE_NOSHEAR_NOPERSPECTIVE": "Drag handles to scale transform. Hold Ctrl and drag a handle to scale from center. Hold Shift to scale proportionally.",
-  "LOCAL_PALETTE_SOURCE_NAME": "Local",
-  "ERROR_FORBIDDEN_UNIQUE_NAME": "Extension unique name cannot start with 'pixieditor'.",
-  "ERROR_MISSING_METADATA": "Extension metadata key '{0}' is missing.",
-  "ERROR_NO_CLASS_ENTRY": "Extension class entry is missing on path '{0}'.",
-  "ERROR_NO_ENTRY_ASSEMBLY": "Extension entry assembly is missing on path '{0}'.",
-  "ERROR_MISSING_ADDITIONAL_CONTENT": "Your current setup doesn't allow loading this extension. Perhaps you don't own it or don't have it installed. You can purchase it here '{0}'.",
-  "BUY_SUPPORTER_PACK": "Buy Supporter Pack",
-  "NEWS": "News",
-  "DISABLE_NEWS_PANEL": "Disable News panel in startup window",
-  "FAILED_FETCH_NEWS": "Failed to fetch news",
-  "CROP_TO_SELECTION": "Crop to selection",
-  "CROP_TO_SELECTION_DESCRIPTIVE": "Crop image to selection",
-  "SHOW_CONTEXT_MENU": "Show context menu",
-  "ERASE": "Erase",
-  "USE_SECONDARY_COLOR": "Use secondary color",
-  "RIGHT_CLICK_MODE": "Right click mode",
-  "ADD_PRIMARY_COLOR_TO_PALETTE": "Add primary color to palette",
-  "ADD_PRIMARY_COLOR_TO_PALETTE_DESCRIPTIVE": "Add primary color to current palette",
-  "EXPORT_SAVE_TITLE": "Choose a location to save the image",
-  "BROWSE_DIRECTORY": "Browse Directory",
-  "CRASH_NOT_ALL_DOCUMENTS_RECOVERED_TITLE": "Not all documents were recovered",
-  "CRASH_NOT_ALL_DOCUMENTS_RECOVERED": "Could not recover all documents. Git gud at saving your work.",
-  "SEND": "Send report",
-  "OPEN_DOCKABLE_MENU": "Open Tab",
-  "TIMELINE_TITLE": "Timeline",
-  "EXPORT_IMAGE_HEADER": "Image",
-  "EXPORT_ANIMATION_HEADER": "Animation",
-  "EXPORT_SPRITESHEET_HEADER": "Spritesheet",
-  "PIXI_FILE": "PixiEditor Files",
-  "PNG_FILE": "PNG Images",
-  "JPEG_FILE": "JPEG Images",
-  "WEBP_FILE": "WebP Images",
-  "GIF_FILE": "GIFs",
-  "BMP_FILE": "BMP Images",
-  "IMAGE_FILES": "Image Files",
-  "VIDEO_FILES": "Video Files",
-  "OPEN_TYPE_FONT": "OpenType Fonts",
-  "TRUE_TYPE_FONT": "TrueType Fonts",
-  "SVG_FILE": "Scalable Vector Graphics",
-  "MP4_FILE": "MP4 Videos",
-  "COLUMNS": "Columns",
-  "ROWS": "Rows",
-  "BACKGROUND": "Background",
-  "OPACITY": "Opacity",
-  "IS_VISIBLE": "Is visible",
-  "CLIP_TO_MEMBER_BELOW": "Clip to member below",
-  "BLEND_MODE": "Blend mode",
-  "MASK": "Mask",
-  "MASK_IS_VISIBLE": "Mask is visible",
-  "OUTPUT": "Output",
-  "INPUT": "Input",
-  "NODE_GRAPH_TITLE": "Graph View",
-  "CONTENT": "Content",
-  "RADIUS": "Radius",
-  "STROKE_COLOR": "Stroke color",
-  "STROKE_WIDTH": "Stroke width",
-  "FILL_COLOR": "Fill color",
-  "TOP": "Top",
-  "BOTTOM": "Bottom",
-  "CHANNELS_DOCK_TITLE": "Channels",
-  "RED": "Red",
-  "GREEN": "Green",
-  "BLUE": "Blue",
-  "ALPHA": "Alpha",
-  "COLOR": "Color",
-  "COORDINATE": "Coordinate",
-  "VECTOR": "Vector",
-  "MATRIX": "Matrix",
-  "TRANSFORMED": "Transformed",
-  "GRAYSCALE": "Grayscale",
-  "CLAMP": "Clamp",
-  "SIZE": "Size",
-  "NOISE": "Noise",
-  "SCALE": "Scale",
-  "SEED": "Seed",
-  "KERNEL": "Kernel",
-  "KERNEL_VIEW_SUM": "Sum:",
-  "KERNEL_VIEW_SUM_TOOLTIP": "The sum of all values. You likely want to aim for a value of 1 or 0",
-  "GAIN": "Gain",
-  "BIAS": "Bias",
-  "TILE_MODE": "Tile Mode",
-  "ON_ALPHA": "On Alpha",
-  "PIXEL_COORDINATE": "Pixel Coordinate",
-  "OUTPUT_NODE": "Output",
-  "NOISE_NODE": "Noise",
-  "ELLIPSE_NODE": "Ellipse",
-  "CREATE_IMAGE_NODE": "Create Image",
-  "FOLDER_NODE": "Folder",
-  "IMAGE_LAYER_NODE": "Image Layer",
-  "KERNEL_FILTER_NODE": "Kernel Filter",
-  "MATH_NODE": "Math",
-  "COLOR_MATRIX_TRANSFORM_FILTER_NODE": "Matrix Transform Filter",
-  "MERGE_NODE": "Merge",
-  "MODIFY_IMAGE_LEFT_NODE": "Begin Modify Image",
-  "MODIFY_IMAGE_RIGHT_NODE": "End Modify Image",
-  "MODIFY_IMAGE_PAIR_NODE": "Modify Image",
-  "COMBINE_CHANNELS_NODE": "Combine Channels",
-  "COMBINE_COLOR_NODE": "Combine Color",
-  "COMBINE_VECD_NODE": "Combine Vector",
-  "COMBINE_VECI_NODE": "Combine Integer Vector",
-  "SEPARATE_CHANNELS_NODE": "Separate Channels",
-  "SEPARATE_VECD_NODE": "Separate Vector",
-  "SEPARATE_VECI_NODE": "Separate Integer Vector",
-  "SEPARATE_COLOR_NODE": "Separate Color",
-  "TIME_NODE": "Time",
-  "FILTERS": "Filters",
-  "PREVIOUS": "Previous",
-  "FILL": "Fill",
-  "MATH_MODE": "Math Mode",
-  "NOISE_TYPE": "Noise Type",
-  "OCTAVES": "Octaves",
-  "ACTIVE_FRAME": "Active Frame",
-  "NORMALIZED_TIME": "Normalized Time",
-  "WITHOUT_FILTERS": "Without filters",
-  "RAW_LAYER_OUTPUT": "Raw",
-  "EXAMPLE_FILES": "Example Files",
-  "PROCEDURAL_GENERATION": "Procedural Animation",
-  "POND_EXAMPLE": "Pond",
-  "TREE_EXAMPLE": "Windy Tree",
-  "OUTLINE_EXAMPLE": "Automatic Outline",
-  "BETA_ANIMATIONS": "Animations",
-  "SLIME_EXAMPLE": "Animated Slime",
-  "SHOW_ALL_EXAMPLES": "Show all",
-  "APPLY_FILTER_NODE": "Apply Filter",
-  "FILTER": "Filter",
-  "LERP_NODE": "Lerp",
-  "GRAYSCALE_FILTER_NODE": "Grayscale Filter",
-  "FROM": "From",
-  "TO": "To",
-  "TIME": "Time",
-  "WARMING_UP": "Warming up",
-  "RENDERING_FRAME": "Generating Frame {0}/{1}",
-  "RENDERING_VIDEO": "Rendering Video",
-  "FINISHED": "Finished",
-  "GENERATING_SPRITE_SHEET": "Generating Sprite Sheet",
-  "RENDERING_IMAGE": "Rendering Image",
-  "PROGRESS_POPUP_TITLE": "Progress",
-  "POINTS": "Points",
-  "MIN_DISTANCE": "Min. Distance",
-  "MAX_POINTS": "Max. Points",
-  "PROBABILITY": "Probability",
-  "DISTRIBUTE_POINTS": "Distribute points",
-  "REMOVE_CLOSE_POINTS": "Remove close points",
-  "RASTERIZE_SHAPE": "Rasterize Shape",
-  "MODE": "Mode",
-  "Factor": "Factor",
-  "NORMALIZE": "Normalize",
-  "WEIGHT_FACTOR": "Weight",
-  "STARS_EXAMPLE": "Stars",
-  "ADD_EMPTY_FRAME": "Add empty frame",
-  "DUPLICATE_FRAME": "Duplicate frame",
-  "DELETE_FRAME": "Remove frame",
-  "DEFAULT_MEMBER_NAME": "New Element",
-  "NO_PARSER_FOUND": "No file parser found for extension '{0}'",
-  "SELECT_FILE_FORMAT": "Select file format",
-  "SELECT_FILE_FORMAT_DESCRIPTION": "Multiple file types of the same format are supported. Please select the one you want to use.",
-  "NEW_PALETTE_FILE": "palette",
-  "ISLAND_EXAMPLE": "Islands",
-  "ONION_FRAMES_COUNT": "Onion frames",
-  "ONION_OPACITY": "Onion opacity",
-  "TOGGLE_ONION_SKINNING": "Toggle onion skinning",
-  "CHANGE_ACTIVE_FRAME_PREVIOUS": "Change active frame to previous",
-  "CHANGE_ACTIVE_FRAME_NEXT": "Change active frame to next",
-  "TOGGLE_ANIMATION": "Toggle animation",
-  "NEW_FROM_CLIPBOARD": "New from clipboard",
-  "OFFSET": "Offset",
-  "SHAPE": "Shape",
-  "STRUCTURE": "Structure",
-  "NUMBERS": "Numbers",
-  "OPERATIONS": "Operations",
-  "GENERATION": "Generation",
-  "NUMBER": "Number",
-  "ANIMATION": "Animation",
-  "SAMPLE_IMAGE": "Sample Image",
-  "POSITION": "Position",
-  "MATH_ADD": "Add",
-  "MATH_SUBTRACT": "Subtract",
-  "MULTIPLY": "Multiply",
-  "DIVIDE": "Divide",
-  "SIN": "Sin",
-  "COS": "Cos",
-  "TAN": "Tan",
-  "GREATER_THAN": "Greater than",
-  "LESS_THAN": "Less than",
-  "LESS_THAN_OR_EQUAL": "Less than or equal",
-  "COMPARE": "Compare",
-  "MATH_POWER": "Power",
-  "LOGARITHM": "Logarithm",
-  "NATURAL_LOGARITHM": "Natural logarithm",
-  "ROOT": "Root",
-  "INVERSE_ROOT": "Inverse root",
-  "FRACTION": "Fraction",
-  "NEGATE": "Negate",
-  "FLOOR": "Floor",
-  "CEIL": "Ceil",
-  "ROUND": "Round",
-  "MODULO": "Modulo",
-  "STEP": "Step",
-  "SMOOTH_STEP": "Smoothstep",
-  "PIXEL_ART_TOOLSET": "Pixel Art",
-  "VECTOR_TOOLSET": "Vector",
-  "VECTOR_LAYER": "Vector Layer",
-  "STROKE_COLOR_LABEL": "Stroke",
-  "SYNC_WITH_PRIMARY_COLOR_LABEL": "Sync with primary color",
-  "RASTERIZE": "Rasterize",
-  "RASTERIZE_ACTIVE_LAYER": "Rasterize active layer",
-  "RASTERIZE_ACTIVE_LAYER_DESCRIPTIVE": "Convert/Rasterize the active layer into a image (raster) layer.",
-  "NEW_ELLIPSE_LAYER_NAME": "Ellipse",
-  "NEW_RECTANGLE_LAYER_NAME": "Rectangle",
-  "NEW_LINE_LAYER_NAME": "Line",
-  "RENDER_OUTPUT": "Render Output",
-  "PAINT_TOOLSET": "Painting",
-  "HARDNESS_SETTING": "Hardness",
-  "SPACING_SETTING": "Spacing",
-  "ANTI_ALIASING_SETTING": "Anti-aliasing",
-  "TOLERANCE_LABEL": "Tolerance",
-  "TOGGLE_SNAPPING": "Toggle snapping",
-  "HIGH_RES_PREVIEW": "High Resolution Preview",
-  "LOW_RES_PREVIEW": "Document Resolution Preview",
-  "TOGGLE_HIGH_RES_PREVIEW": "Toggle high resolution preview",
-  "FACTOR": "Factor",
-  "PATH_TOOL": "Path",
-  "PATH_TOOL_TOOLTIP": "Create vector paths and curves ({0}).",
-  "PATH_TOOL_ACTION_DISPLAY": "Click to add a point.",
-  "PATH_TOOL_ACTION_DISPLAY_CTRL": "Click on existing point and drag to make it a curve. Tap on a control point to select it.",
-  "PATH_TOOL_ACTION_DISPLAY_SHIFT": "Click to create a new layer.",
-  "PATH_TOOL_ACTION_DISPLAY_CTRL_SHIFT": "Tap on a control point to add it to the selection.",
-  "PATH_TOOL_ACTION_DISPLAY_ALT": "Click on a control point and move to adjust only one side of the curve.",
-  "DEFAULT_PATH_LAYER_NAME": "Path",
-  "DELETE_NODES": "Delete nodes",
-  "DELETE_NODES_DESCRIPTIVE": "Delete selected nodes",
-  "DELETE_CELS": "Delete cels",
-  "DELETE_CELS_DESCRIPTIVE": "Delete selected cels",
-  "COPY_COLOR_TO_CLIPBOARD": "Copy color to clipboard",
-  "VIEWPORT_ROTATION": "Viewport rotation",
-  "NEXT_TOOL_SET": "Next tool set",
-  "PREVIOUS_TOOL_SET": "Previous tool set",
-  "FILL_MODE": "Fill mode",
-  "USE_LINEAR_SRGB_PROCESSING": "Use linear sRGB for processing colors",
-  "USE_LINEAR_SRGB_PROCESSING_DESC": "Convert document using sRGB blending mode to linear sRGB for processing colors. This will affect the colors of the document, but will make blending more accurate.",
-  "FILL_TYPE_WINDING": "Winding",
-  "FILL_TYPE_EVEN_ODD": "Even Odd",
-  "FILL_TYPE_INVERSE_WINDING": "Inverse Winding",
-  "FILL_TYPE_INVERSE_EVEN_ODD": "Inverse Even Odd",
-  "STROKE_CAP": "Stroke Cap",
-  "STROKE_JOIN": "Stroke Join",
-  "COPY_VISIBLE": "Copy visible",
-  "COPY_VISIBLE_DESCRIPTIVE": "Copy visible pixels",
-  "COLOR_SAMPLE_MODE": "Sample mode",
-  "CREATE_CEL": "Create cel",
-  "CREATE_CEL_DESCRIPTIVE": "Create a new cel",
-  "DUPLICATE_CEL": "Duplicate cel",
-  "DUPLICATE_CEL_DESCRIPTIVE": "Duplicate cel in the current frame",
-  "RENDER_PREVIEW": "Render preview",
-  "OUTPUT_NAME": "Output name",
-  "CUSTOM_OUTPUT_NODE": "Custom Output",
-  "TOGGLE_HUD": "Toggle HUD",
-  "OPEN_TIMELINE": "Open timeline",
-  "OPEN_NODE_GRAPH": "Open node graph",
-  "TOGGLE_PLAY": "Play/Pause animation",
-  "COPY_NODES": "Copy nodes",
-  "COPY_NODES_DESCRIPTIVE": "Copy selected nodes",
-  "PASTE_NODES": "Paste nodes",
-  "PASTE_NODES_DESCRIPTIVE": "Paste copied nodes",
-  "COPY_CELS": "Copy cels",
-  "COPY_CELS_DESCRIPTIVE": "Copy selected cels",
-  "TOGGLE_ONION_SKINNING_DESCRIPTIVE": "Toggle onion skinning",
-  "VALUE": "Value",
-  "TARGET": "Target",
-  "EPSILON": "Epsilon",
-  "PRESERVE_ALPHA": "Preserve alpha",
-  "BLUR_FILTER_NODE": "Gaussian Blur Filter",
-  "LENGTH": "Length",
-  "GREATER_THAN_OR_EQUAL": "Greater than or equal",
-  "COLOR_NODE": "Color",
-  "CONVERT_TO_CURVE": "Convert to curve",
-  "CONVERT_TO_CURVE_DESCRIPTIVE": "Convert selected vector layer to a curve/path",
-  "FONT_FILES": "Font Files",
-  "UNIT_PT": "pt",
-  "FONT_LABEL": "Family",
-  "FONT_SIZE_LABEL": "Size",
-  "SPACING_LABEL": "Spacing",
-  "TEXT_TOOL": "Text",
-  "MISSING_FONT": "Missing font",
-  "TEXT_LAYER_NAME": "Text",
-  "TEXT_TOOL_TOOLTIP": "Create text ({0}).",
-  "BOLD_TOOLTIP": "Bold",
-  "ITALIC_TOOLTIP": "Italic",
-  "CUSTOM_FONT": "Custom font",
-  "DUMP_GPU_DIAGNOSTICS": "Dump GPU diagnostics",
-  "USE_SRGB_PROCESSING": "Use sRGB for processing colors",
-  "USE_SRGB_PROCESSING_DESC": "Convert document using linear sRGB to sRGB for processing colors. This will affect the colors of the document.",
-  "TEXT_NODE": "Text",
-  "TEXT_LABEL": "Text",
-  "TEXT_ON_PATH_NODE": "Text on Path",
-  "HIGH_DPI_RENDERING": "High DPI Rendering",
-  "THICKNESS": "Thickness",
-  "TYPE": "Type",
-  "EFFECTS": "Effects",
-  "OUTLINE_NODE": "Outline",
-  "SHADER_CODE": "Shader Code",
-  "SHADER_NODE": "Shader",
-  "FAILED_TO_OPEN_EDITABLE_STRING_TITLE": "Failed to open file",
-  "FAILED_TO_OPEN_EDITABLE_STRING_MESSAGE": "Failed to edit this string in external editor. Reason: {0}",
-  "STRING_EDIT_IN_DEFAULT_APP": "Edit in default app",
-  "STRING_OPEN_IN_FOLDER": "Open in folder",
-  "DISCO_BALL_EXAMPLE": "Disco Ball",
-  "COLOR_SPACE": "Color Space",
-  "PHOTO_EXAMPLES": "Photo",
-  "MASK_EXAMPLE": "Mask",
-  "SHADOW_NODE": "Shadow Filter",
-  "INPUT_MATRIX": "Input Matrix",
-  "OUTPUT_MATRIX": "Output Matrix",
-  "CENTER": "Center",
-  "CONTENT_OFFSET": "Content Offset",
-  "CANVAS_POSITION": "Canvas Position",
-  "CENTER_POSITION": "Center Position",
-  "TILE_MODE_X": "Tile Mode X",
-  "TILE_MODE_Y": "Tile Mode Y",
-  "TILE_NODE": "Tile",
-  "SKEW": "Skew",
-  "OFFSET_NODE": "Offset",
-  "SKEW_NODE": "Skew",
-  "ROTATION_NODE": "Rotation",
-  "SCALE_NODE": "Scale",
-  "ROTATE_NODE": "Rotate",
-  "TRANSFORM_NODE": "Transform",
-  "UNIT": "Unit",
-  "ANGLE": "Angle",
-  "DOCUMENT_INFO_NODE": "Document Info",
-  "MASK_NODE": "Mask",
-  "SEPIA_FILTER_NODE": "Sepia Filter",
-  "INTENSITY": "Intensity",
-  "INVERT_FILTER_NODE": "Invert Filter",
-  "COLOR_ADJUSTMENTS_FILTER": "Color Adjustments Filter",
-  "ADJUST_BRIGHTNESS": "Adjust Brightness",
-  "ADJUST_CONTRAST": "Adjust Contrast",
-  "ADJUST_SATURATION": "Adjust Saturation",
-  "ADJUST_TEMPERATURE": "Adjust Temperature",
-  "ADJUST_TINT": "Adjust Tint",
-  "ADJUST_HUE": "Adjust Hue",
-  "HUE_VALUE": "Hue",
-  "SATURATION_VALUE": "Saturation",
-  "BRIGHTNESS_VALUE": "Brightness",
-  "CONTRAST_VALUE": "Contrast",
-  "TEMPERATURE_VALUE": "Temperature",
-  "TINT_VALUE": "Tint",
-  "FAILED_DOWNLOADING_UPDATE_TITLE": "Failed to download update",
-  "FAILED_DOWNLOADING_UPDATE": "Failed to download the update. Try again later.",
-  "UNEXPECTED_SHUTDOWN": "Unexpected shutdown",
-  "UNEXPECTED_SHUTDOWN_MSG": "PixiEditor was unexpectedly shut down. We've loaded latest autosave of your files.",
-  "OK": "OK",
-  "OPEN_AUTOSAVES": "Browse Autosaves",
-  "AUTOSAVE_SETTINGS_HEADER": "Autosave",
-  "AUTOSAVE_SETTINGS_SAVE_STATE": "Reopen last files on startup",
-  "AUTOSAVE_SETTINGS_PERIOD": "Autosave period",
-  "AUTOSAVE_ENABLED": "Autosave enabled",
-  "MINUTE_UNIVERSAL": "min",
-  "AUTOSAVE_SETTINGS_SAVE_USER_FILE": "Autosave to selected file",
-  "LOAD_LAZY_FILE_MESSAGE": "To improve startup time, PixiEditor didn't load this file. Click the button below to load it.",
-  "EASING_NODE": "Easing",
-  "EASING_TYPE": "Easing Type",
-  "OPEN_DIRECTORY_ON_EXPORT": "Open directory on export",
-  "ERROR_LOOP_DETECTED_MESSAGE": "Moving this layer will create a loop. Fix it in the Node Graph.",
-  "LINEAR_EASING_TYPE": "Linear",
-  "IN_SINE_EASING_TYPE": "In Sine",
-  "OUT_SINE_EASING_TYPE": "Out Sine",
-  "IN_OUT_SINE_EASING_TYPE": "In Out Sine",
-  "IN_QUAD_EASING_TYPE": "In Quad",
-  "OUT_QUAD_EASING_TYPE": "Out Quad",
-  "IN_OUT_QUAD_EASING_TYPE": "In Out Quad",
-  "IN_CUBIC_EASING_TYPE": "In Cubic",
-  "OUT_CUBIC_EASING_TYPE": "Out Cubic",
-  "IN_OUT_CUBIC_EASING_TYPE": "In Out Cubic",
-  "IN_QUART_EASING_TYPE": "In Quart",
-  "OUT_QUART_EASING_TYPE": "Out Quart",
-  "IN_OUT_QUART_EASING_TYPE": "In Out Quart",
-  "IN_QUINT_EASING_TYPE": "In Quint",
-  "OUT_QUINT_EASING_TYPE": "Out Quint",
-  "IN_OUT_QUINT_EASING_TYPE": "In Out Quint",
-  "IN_EXPO_EASING_TYPE": "In Expo",
-  "OUT_EXPO_EASING_TYPE": "Out Expo",
-  "IN_OUT_EXPO_EASING_TYPE": "In Out Expo",
-  "IN_CIRC_EASING_TYPE": "In Circ",
-  "OUT_CIRC_EASING_TYPE": "Out Circ",
-  "IN_OUT_CIRC_EASING_TYPE": "In Out Circ",
-  "IN_BACK_EASING_TYPE": "In Back",
-  "OUT_BACK_EASING_TYPE": "Out Back",
-  "IN_OUT_BACK_EASING_TYPE": "In Out Back",
-  "IN_ELASTIC_EASING_TYPE": "In Elastic",
-  "OUT_ELASTIC_EASING_TYPE": "Out Elastic",
-  "IN_OUT_ELASTIC_EASING_TYPE": "In Out Elastic",
-  "IN_BOUNCE_EASING_TYPE": "In Bounce",
-  "OUT_BOUNCE_EASING_TYPE": "Out Bounce",
-  "IN_OUT_BOUNCE_EASING_TYPE": "In Out Bounce",
-  "CLAMP_SHADER_TILE_NODE": "Clamp",
-  "REPEAT_SHADER_TILE_NODE": "Repeat",
-  "MIRROR_SHADER_TILE_NODE": "Mirror",
-  "DECAL_SHADER_TILE_NODE": "Decal",
-  "R_G_B_COMBINE_SEPARATE_COLOR_MODE": "RGB",
-  "H_S_V_COMBINE_SEPARATE_COLOR_MODE": "HSV",
-  "H_S_L_COMBINE_SEPARATE_COLOR_MODE": "HSL",
-  "COLOR_MANAGED_COLOR_SAMPLE_MODE": "Color Managed",
-  "RAW_COLOR_SAMPLE_MODE": "Raw",
-  "FRACTAL_PERLIN_NOISE_TYPE": "Perlin",
-  "TURBULENCE_PERLIN_NOISE_TYPE": "Turbulence",
-  "INHERIT_COLOR_SPACE_TYPE": "Inherit",
-  "SRGB_COLOR_SPACE_TYPE": "sRGB",
-  "LINEAR_SRGB_COLOR_SPACE_TYPE": "Linear sRGB",
-  "SIMPLE_OUTLINE_TYPE": "Simple",
-  "GAUSSIAN_OUTLINE_TYPE": "Gaussian",
-  "PIXEL_PERFECT_OUTLINE_TYPE": "Pixel Perfect",
-  "DEGREES_ROTATION_TYPE": "Degrees",
-  "RADIANS_ROTATION_TYPE": "Radians",
-  "WEIGHTED_GRAYSCALE_MODE": "Weighted",
-  "AVERAGE_GRAYSCALE_MODE": "Average",
-  "CUSTOM_GRAYSCALE_MODE": "Custom",
-  "CLAMP_TILE_MODE": "Clamp",
-  "REPEAT_TILE_MODE": "Repeat",
-  "MIRROR_TILE_MODE": "Mirror",
-  "DECAL_TILE_MODE": "Decal",
-  "ERR_UNKNOWN_FILE_FORMAT": "Unknown file format",
-  "ERR_EXPORT_SIZE_INVALID": "Invalid export size. Values must be greater than 0.",
-  "ERR_UNKNOWN_IMG_FORMAT": "Unknown image format '{0}'.",
-  "ERR_FAILED_GENERATE_SPRITE_SHEET": "Failed generating sprite sheet",
-  "ERR_NO_RENDERER": "Animation renderer not found.",
-  "ERR_RENDERING_FAILED": "Rendering failed",
-  "ENABLE_ANALYTICS": "Send anonymous analytics",
-  "ANALYTICS_INFO": "We collect anonymous usage data to improve PixiEditor. No personal data is collected.",
-  "LANGUAGE_INFO": "All translations are community-driven. Join our Discord server for more information.",
-  "UP_TO_DATE_UNKNOWN": "Couldn't check for updates",
-  "UP_TO_DATE": "PixiEditor is up to date",
-  "UPDATE_AVAILABLE": "Update {0} is available",
-  "CHECKING_UPDATES": "Checking for updates...",
-  "UPDATE_FAILED_DOWNLOAD": "Failed to download the update",
-  "UPDATE_READY_TO_INSTALL": "Update is ready. Switch to {0}?",
-  "SWITCH_TO_NEW_VERSION": "Switch",
-  "DOWNLOAD_UPDATE": "Download",
-  "DOWNLOADING_UPDATE": "Downloading update...",
-  "CHECKING_FOR_UPDATES": "Checking for updates...",
-  "PAINT_SHAPE_SETTING": "Brush shape",
-  "BOOL_OPERATION_NODE": "Boolean Operation",
-  "FIRST_SHAPE": "First shape",
-  "SECOND_SHAPE": "Second shape",
-  "OPERATION": "Operation",
-  "UNION_VECTOR_PATH_OP": "Union",
-  "DIFFERENCE_VECTOR_PATH_OP": "Difference",
-  "INTERSECT_VECTOR_PATH_OP": "Intersect",
-  "XOR_VECTOR_PATH_OP": "XOR",
-  "REVERSE_DIFFERENCE_VECTOR_PATH_OP": "Reverse Difference",
-  "NO_DOCUMENT_OPEN": "Nothing's here",
-  "EMPTY_DOCUMENT_ACTION_BTN": "Start creating",
-  "ONBOARDING_TITLE": "Welcome to",
-  "ONBOARDING_DESCRIPTION": "Let's set up your workspace!",
-  "ONBOARDING_SKIP_BTN": "Skip",
-  "ONBOARDING_ACTION_BTN": "Let's begin",
-  "ONB_SELECT_PRIMARY_TOOLSET": "Select Your Primary Toolset",
-  "ONB_NEXT_BTN": "Next",
-  "ONB_FINISH_BTN": "Finish",
-  "ONB_BACK_BTN": "Previous",
-  "ONB_ANALYTICS": "Anonymous Analytics",
-  "ONB_ALL_SET": "You are all set!",
-  "ONB_ALL_SET_BTN": "Start creating",
-  "ANALYTICS_INFO_DETAILED": "PixiEditor collects anonymous usage data to improve the app. The data does not contain any personal information. Among other things, PixiEditor tracks the following:\n- Which and how the tools are used\n- How long you've used the app for\n- Which commands are used\n- Performance data\n\n You can opt out of analytics at any time in the settings.",
-  "PRIVACY_POLICY": "Privacy Policy",
-  "ONB_SHORTCUTS": "Select Your Shortcuts",
-  "GRAPH_STATE_UNABLE_TO_CREATE_MEMBER": "Current Node Graph setup disallows creation of a new layer next to the selected one.",
-  "PRIMARY_TOOLSET": "Primary Toolset",
-  "OPEN_ONBOARDING_WINDOW": "Open onboarding window",
-  "USER_NOT_FOUND": "Please enter the email you used to purchase the Founder's Edition.",
-  "SESSION_NOT_VALID": "Session is not valid, please log in again",
-  "SESSION_NOT_FOUND": "Session not found, try logging in again",
-  "INTERNAL_SERVER_ERROR": "There was an internal server error. Please try again later.",
-  "TOO_MANY_REQUESTS": "Too many requests. Try again in {0} seconds.",
-  "SESSION_EXPIRED": "Session expired. Please log in again.",
-  "CONNECTION_ERROR": "Connection error. Please check your internet connection.",
-  "FAIL_LOAD_USER_DATA": "Failed to load saved user data",
-  "LOGOUT": "Logout",
-  "LOGGED_IN_AS": "Hello",
-  "EMAIL_SENT": "Email sent! Check your inbox.",
-  "RESEND_ACTIVATION": "Resend",
-  "INVALID_TOKEN": "Session is invalid or expired. Please log in again.",
-  "ENTER_EMAIL": "Enter your email",
-  "LOGIN_LINK": "Send Login Link",
-  "LOGIN_LINK_INFO": "We'll email you a secure link to log in. No password needed.",
-  "ACCOUNT_WINDOW_TITLE": "Account",
-  "CONNECTION_TIMEOUT": "Connection timed out. Please try again.",
-  "OPEN_ACCOUNT_WINDOW": "Manage Account",
-  "AUTO_SCALE_BACKGROUND": "Auto scale background",
-  "UPDATES": "Updates",
-  "SCENE": "Scene",
-  "CUSTOM_BACKGROUND_SCALE": "Custom background scale",
-  "PRIMARY_BG_COLOR": "Primary background color",
-  "SECONDARY_BG_COLOR": "Secondary background color",
-  "RESET": "Reset",
-  "INSTALL": "Install",
-  "MANAGE_ACCOUNT": "Manage",
-  "OWNED_PRODUCTS": "Owned Content",
-  "INSTALLING": "Installing",
-  "INSTALLED": "Installed",
-  "ACCOUNT_PROVIDER_INFO": "Account handled by",
-  "UPDATE": "Update",
-  "AUTOSAVE_OPEN_FOLDER": "Open autosave folder",
-  "AUTOSAVE_OPEN_FOLDER_DESCRIPTIVE": "Open the folder where autosaves are stored",
-  "AUTOSAVE_TOGGLE_DESCRIPTIVE": "Enable/disable autosave",
-  "FOUNDERS_BUNDLE": "Founder's Bundle",
-  "FOUNDERS_BUNDLE_SUBTEXT": "Support PixiEditor and boost your productivity!",
-  "BECOME_A_FOUNDER": "Become a Founder",
-  "LOGIN": "Login",
-  "NOT_FOUNDER_YET": "Not a Founder yet?",
-  "ERROR_GRAPH": "Graph setup produced an error. Fix it in the node graph",
-  "COLOR_MATRIX_FILTER_NODE": "Color Matrix Filter",
-  "WORKSPACE": "Workspace",
-  "EXPORT_ZONE_NODE": "Export Zone",
-  "IS_DEFAULT_EXPORT": "Is Default Export",
-  "EXPORT_OUTPUT": "Export Output",
-  "RENDER_OUTPUT_SIZE": "Render Output Size",
-  "RENDER_OUTPUT_CENTER": "Render Output Center",
-  "COLOR_PICKER": "Color Picker",
-  "UNAUTHORIZED_ACCESS": "Unauthorized access",
-  "SEPARATE_SHAPES": "Separate Shapes",
-  "SEPARATE_SHAPES_DESCRIPTIVE": "Separate shapes from current vector into individual layers",
-  "TEXT": "Text",
-  "EXTRACT_SELECTED_TEXT": "Extract selected text",
-  "EXTRACT_SELECTED_TEXT_DESCRIPTIVE": "Extract selected text into new layer.",
-  "EXTRACT_SELECTED_CHARACTERS": "Extract selected characters",
-  "EXTRACT_SELECTED_CHARACTERS_DESCRIPTIVE": "Extract individual characters from selection into new layers.",
-  "STEP_START": "Step back to closest cel",
-  "STEP_END": "Step forward to closest cel",
-  "STEP_FORWARD": "Step forward one frame",
-  "STEP_BACK": "Step back one frame",
-  "ANIMATION_QUALITY_PRESET": "Quality Preset",
-  "VERY_LOW_QUALITY_PRESET": "Very Low",
-  "LOW_QUALITY_PRESET": "Low",
-  "MEDIUM_QUALITY_PRESET": "Medium",
-  "HIGH_QUALITY_PRESET": "High",
-  "VERY_HIGH_QUALITY_PRESET": "Very High",
-  "EXPORT_FRAMES": "Export Frames",
-  "NORMALIZE_OFFSET": "Normalize Offset",
-  "TANGENT": "Tangent",
-  "EVALUATE_PATH_NODE": "Evaluate Path",
-  "OLD_MIN": "Old Min",
-  "OLD_MAX": "Old Max",
-  "NEW_MIN": "New Min",
-  "NEW_MAX": "New Max",
-  "REMAP_NODE": "Remap",
-  "TEXT_TOOL_ACTION_DISPLAY": "Click on the canvas to add a new text (drag while clicking to set the size). Click on existing text to edit it.",
-  "PASTE_CELS": "Paste cels",
-  "SCALE_X": "Scale X",
-  "SCALE_Y": "Scale Y",
-  "TRANSLATE_X": "Translate X",
-  "TRANSLATE_Y": "Translate Y",
-  "SKEW_X": "Skew X",
-  "SKEW_Y": "Skew Y",
-  "PERSPECTIVE_0": "Perspective 0",
-  "PERSPECTIVE_1": "Perspective 1",
-  "PERSPECTIVE_2": "Perspective 2",
-  "COMPOSE_MATRIX": "Compose Matrix",
-  "DECOMPOSE_MATRIX": "Decompose Matrix",
-  "NORMALIZE_COORDINATES": "Normalize Coordinates",
-  "TRANSFORMED_POSITION": "Transformed Position",
-  "ACCOUNT_PROVIDER_NOT_AVAILABLE": "This build of PixiEditor does not support accounts. Use the official build from pixieditor.net to manage your account.",
-  "STEAM_OFFLINE": "Cannot validate the account. Steam is offline. Make sure Steam client is running and you are logged in.",
-  "ERROR_GPU_RESOURCES_CREATION": "Failed to create resources: Try updating your GPU drivers or try setting different rendering api in settings. \nError: '{0}'",
-  "ERROR_SAVING_PREFERENCES_DESC": "Failed to save preferences with error: '{0}'. Please check if you have write permissions to the PixiEditor data folder.",
-  "ERROR_SAVING_PREFERENCES": "Failed to save preferences",
-  "PREFERRED_RENDERER": "Preferred Render Api"
+    "RECENT_FILES": "Recent Files",
+    "OPEN_FILE": "Open file",
+    "NEW_FILE": "New",
+    "RECENT_EMPTY_TEXT": "So much empty space",
+    "LANGUAGE": "Language",
+    "GENERAL": "General",
+    "DISCORD": "Discord",
+    "KEY_BINDINGS": "Key Bindings",
+    "MISC": "Misc",
+    "SHOW_STARTUP_WINDOW": "Show Startup Window",
+    "RECENT_FILE_LENGTH": "Recent file list length",
+    "RECENT_FILE_LENGTH_TOOLTIP": "How many documents are shown under File > Recent. Default: 8",
+    "DEFAULT_NEW_SIZE": "Default new file size",
+    "WIDTH": "Width",
+    "HEIGHT": "Height",
+    "TOOLS": "Tools",
+    "ENABLE_SHARED_TOOLBAR": "Enable shared toolbar",
+    "AUTOMATIC_UPDATES": "Automatic Updates",
+    "CHECK_FOR_UPDATES": "Check updates on startup",
+    "UPDATE_STREAM": "Update stream",
+    "UPDATE_CHANNEL_HELP_TOOLTIP": "Update channels can only be changed in standalone version (downloaded from https://pixieditor.net).\nSteam and Microsoft Store versions handle updates separately.",
+    "DEBUG": "Debug",
+    "ENABLE_DEBUG_MODE": "Enable Debug mode",
+    "OPEN_CRASH_REPORTS_DIR": "Open crash reports directory",
+    "DISCORD_RICH_PRESENCE": "Rich Presence",
+    "ENABLED": "Enabled",
+    "SHOW_IMAGE_NAME": "Show image name",
+    "SHOW_IMAGE_SIZE": "Show image size",
+    "SHOW_LAYER_COUNT": "Show layer count",
+    "FILE": "File",
+    "RECENT": "Recent",
+    "OPEN": "Open",
+    "SAVE_PIXI": "Save (.pixi)",
+    "SAVE_AS_PIXI": "Save as... (.pixi)",
+    "EXPORT_IMG": "Export (.png, .jpg, etc.)",
+    "EDIT": "Edit",
+    "EXIT": "Exit",
+    "PERCENTAGE": "Percentage",
+    "ABSOLUTE": "Absolute",
+    "PRESERVE_ASPECT_RATIO": "Preserve aspect ratio",
+    "ANCHOR_POINT": "Anchor point",
+    "RESIZE_IMAGE": "Resize image",
+    "RESIZE": "Resize",
+    "DOCUMENTATION": "Documentation",
+    "WEBSITE": "Website",
+    "OPEN_WEBSITE": "Open website",
+    "REPOSITORY": "Repository",
+    "OPEN_REPOSITORY": "Open repository",
+    "OPEN_DOCUMENTATION": "Open documentation",
+    "LICENSE": "License",
+    "OPEN_LICENSE": "Open license",
+    "THIRD_PARTY_LICENSES": "Third party licenses",
+    "OPEN_THIRD_PARTY_LICENSES": "Open third party licenses",
+    "APPLY_TRANSFORM": "Apply transform",
+    "INCREASE_TOOL_SIZE": "Increase tool size",
+    "DECREASE_TOOL_SIZE": "Decrease tool size",
+    "UPDATE_READY": "Update is ready to be installed. Do you want to install it now?",
+    "COULD_NOT_UPDATE_WITHOUT_ADMIN": "Couldn't update without admin privileges. Please run PixiEditor as administrator.",
+    "INSUFFICIENT_PERMISSIONS": "Insufficient permissions",
+    "VERSION": "Version {0}",
+    "BUILD_ID": "Build ID: {0}",
+    "OPEN_TEMP_DIR": "Open temp directory",
+    "OPEN_LOCAL_APPDATA_DIR": "Open Local AppData directory",
+    "OPEN_ROAMING_APPDATA_DIR": "Open Roaming AppData directory",
+    "OPEN_INSTALLATION_DIR": "Open installation directory",
+    "DUMP_ALL_COMMANDS": "Dump all commands",
+    "DUMP_ALL_COMMANDS_DESCRIPTIVE": "Dump all commands to a text file",
+    "CRASH": "Crash",
+    "CRASH_APP": "Crash application",
+    "DELETE_USR_PREFS": "Delete user preferences (Roaming AppData)",
+    "DELETE_SHORTCUT_FILE": "Delete shortcut file (Roaming AppData)",
+    "DELETE_EDITOR_DATA": "Delete editor data (Local AppData)",
+    "GENERATE_KEY_BINDINGS_TEMPLATE": "Generate key bindings template",
+    "GENERATE_KEY_BINDINGS_TEMPLATE_DESCRIPTIVE": "Generate key bindings json template",
+    "VALIDATE_SHORTCUT_MAP": "Validate shortcut map",
+    "VALIDATE_SHORTCUT_MAP_DESCRIPTIVE": "Validates shortcut map",
+    "VALIDATION_KEYS_NOTICE_DIALOG": "Empty keys: {0}\nUnknown Commands: {1}",
+    "RESULT": "Result",
+    "CLEAR_RECENT_DOCUMENTS": "Clear recent documents",
+    "CLEAR_RECENTLY_OPENED_DOCUMENTS": "Clear recently opened documents",
+    "OPEN_CMD_DEBUG_WINDOW": "Open command debug window",
+    "PATH_DOES_NOT_EXIST": "{0} does not exist.",
+    "LOCATION_DOES_NOT_EXIST": "Location does not exist.",
+    "FILE_NOT_FOUND": "File not found.",
+    "ARE_YOU_SURE": "Are you sure?",
+    "ARE_YOU_SURE_PATH_FULL_PATH": "Are you sure you want to delete {0}?\nThis data will be lost for all installations.\n(Full Path: {1})",
+    "FAILED_TO_OPEN_FILE": "Failed to open the file",
+    "OLD_FILE_FORMAT": "Old file format",
+    "OLD_FILE_FORMAT_DESCRIPTION": "This .pixi file uses the old format,\n which is no longer supported and can't be opened.",
+    "NOTHING_FOUND": "Nothing found",
+    "EXPORT": "Export",
+    "EXPORT_IMAGE": "Export image",
+    "IMPORT": "Import",
+    "SHORTCUT_TEMPLATES": "Shortcut templates",
+    "RESET_ALL": "Reset all",
+    "LAYER": "Layer",
+    "LAYER_DELETE_ALL_SELECTED": "Delete all selected layers/folders",
+    "LAYER_DELETE_ALL_SELECTED_DESCRIPTIVE": "Delete all selected layers and/or folders",
+    "NEW_FOLDER": "New folder",
+    "CREATE_NEW_FOLDER": "Create new folder",
+    "NEW_LAYER": "New layer",
+    "CREATE_NEW_LAYER": "Create new layer",
+    "NEW_IMAGE": "New image",
+    "CREATE_NEW_IMAGE": "Create new image",
+    "SAVE": "Save",
+    "SAVE_AS": "Save as...",
+    "IMAGE": "Image",
+    "SAVE_IMAGE": "Save image",
+    "SAVE_IMAGE_AS": "Save image as new",
+    "DUPLICATE": "Duplicate",
+    "DUPLICATE_SELECTED_LAYER": "Duplicate selected layer",
+    "CREATE_MASK": "Create mask",
+    "DELETE_MASK": "Delete mask",
+    "TOGGLE_MASK": "Toggle mask",
+    "APPLY_MASK": "Apply mask",
+    "TOGGLE_VISIBILITY": "Toggle visibility",
+    "MOVE_MEMBER_UP": "Move member upwards",
+    "MOVE_MEMBER_UP_DESCRIPTIVE": "Move selected layer or folder upwards",
+    "MOVE_MEMBER_DOWN": "Move member downwards",
+    "MOVE_MEMBER_DOWN_DESCRIPTIVE": "Move selected layer or folder downwards",
+    "MERGE_ALL_SELECTED_LAYERS": "Merge all selected layers",
+    "MERGE_WITH_ABOVE": "Merge selected layer with above",
+    "MERGE_WITH_ABOVE_DESCRIPTIVE": "Merge selected layer with the one above it",
+    "MERGE_WITH_BELOW": "Merge selected layer with below",
+    "MERGE_WITH_BELOW_DESCRIPTIVE": "Merge selected layer with the one below it",
+    "ADD_REFERENCE_LAYER": "Add Reference Layer",
+    "DELETE_REFERENCE_LAYER": "Delete reference layer",
+    "TRANSFORM_REFERENCE_LAYER": "Transform reference layer",
+    "TOGGLE_REFERENCE_LAYER_POS": "Toggle reference layer position",
+    "TOGGLE_REFERENCE_LAYER_POS_DESCRIPTIVE": "Toggle reference layer between topmost or most below",
+    "RESET_REFERENCE_LAYER_POS": "Reset reference layer position",
+    "CLIP_CANVAS": "Clip Canvas",
+    "FLIP_IMG_VERTICALLY": "Flip Image Vertically",
+    "FLIP_IMG_HORIZONTALLY": "Flip Image Horizontally",
+    "FLIP_LAYERS_VERTICALLY": "Flip Selected Layers Vertically",
+    "FLIP_LAYERS_HORIZONTALLY": "Flip Selected Layers Horizontally",
+    "ROT_IMG_90": "Rotate Image 90 degrees",
+    "ROT_IMG_180": "Rotate Image 180 degrees",
+    "ROT_IMG_-90": "Rotate Image -90 degrees",
+    "ROT_LAYERS_90": "Rotate Selected Layers 90 degrees",
+    "ROT_LAYERS_180": "Rotate Selected Layers 180 degrees",
+    "ROT_LAYERS_-90": "Rotate Selected Layers -90 degrees",
+    "TOGGLE_VERT_SYMMETRY_AXIS": "Toggle vertical symmetry axis",
+    "TOGGLE_HOR_SYMMETRY_AXIS": "Toggle horizontal symmetry axis",
+    "DELETE_SELECTED": "Delete selected",
+    "DELETE_SELECTED_DESCRIPTIVE": "Delete selected element (layer, pixels, etc.)",
+    "RESIZE_DOCUMENT": "Resize document",
+    "RESIZE_CANVAS": "Resize canvas",
+    "CENTER_CONTENT": "Center content",
+    "CUT": "Cut",
+    "CUT_DESCRIPTIVE": "Cut selected area/layers",
+    "PASTE": "Paste",
+    "PASTE_DESCRIPTIVE": "Paste clipboard contents",
+    "PASTE_AS_NEW_LAYER": "Paste as new layer",
+    "PASTE_AS_NEW_LAYER_DESCRIPTIVE": "Paste from clipboard as new layer",
+    "PASTE_REFERENCE_LAYER": "Paste reference layer",
+    "PASTE_REFERENCE_LAYER_DESCRIPTIVE": "Paste clipboard contents as reference layer",
+    "PASTE_COLOR": "Paste color",
+    "PASTE_COLOR_DESCRIPTIVE": "Paste color from clipboard",
+    "PASTE_COLOR_SECONDARY": "Paste color as secondary",
+    "PASTE_COLOR_SECONDARY_DESCRIPTIVE": "Paste color from clipboard as secondary color",
+    "CLIPBOARD": "Clipboard",
+    "COPY": "Copy",
+    "COPY_DESCRIPTIVE": "Copy to clipboard",
+    "COPY_COLOR_HEX": "Copy primary color (HEX)",
+    "COPY_COLOR_HEX_DESCRIPTIVE": "Copy primary color as HEX code",
+    "COPY_COLOR_RGB": "Copy primary color (RGB)",
+    "COPY_COLOR_RGB_DESCRIPTIVE": "Copy primary color as RGB code",
+    "COPY_COLOR_SECONDARY_HEX": "Copy secondary color (HEX)",
+    "COPY_COLOR_SECONDARY_HEX_DESCRIPTIVE": "Copy secondary color as HEX code",
+    "COPY_COLOR_SECONDARY_RGB": "Copy secondary color (RGB)",
+    "COPY_COLOR_SECONDARY_RGB_DESCRIPTIVE": "Copy secondary color as RGB code",
+    "PALETTE_COLORS": "Palette Colors",
+    "REPLACE_SECONDARY_BY_PRIMARY": "Replace secondary color by primary",
+    "REPLACE_PRIMARY_BY_SECONDARY": "Replace primary color by secondary",
+    "REPLACE_PRIMARY_BY_SECONDARY_DESCRIPTIVE": "Replace the primary color by the secondary color",
+    "OPEN_PALETTE_BROWSER": "Open palette browser",
+    "OVERWRITE_PALETTE_CONSENT": "Palette '{0}' already exists, do you want to overwrite it?",
+    "PALETTE_EXISTS": "Palette already exists",
+    "REPLACE_PALETTE_CONSENT": "Replace current palette with selected one?",
+    "REPLACE_PALETTE": "Replace current palette",
+    "SELECT_COLOR_1": "Select color 1",
+    "SELECT_COLOR_2": "Select color 2",
+    "SELECT_COLOR_3": "Select color 3",
+    "SELECT_COLOR_4": "Select color 4",
+    "SELECT_COLOR_5": "Select color 5",
+    "SELECT_COLOR_6": "Select color 6",
+    "SELECT_COLOR_7": "Select color 7",
+    "SELECT_COLOR_8": "Select color 8",
+    "SELECT_COLOR_9": "Select color 9",
+    "SELECT_COLOR_10": "Select color 10",
+    "SELECT_TOOL": "Select {0} Tool",
+    "SELECT_COLOR_1_DESCRIPTIVE": "Select the first color in the palette",
+    "SELECT_COLOR_2_DESCRIPTIVE": "Select the second color in the palette",
+    "SELECT_COLOR_3_DESCRIPTIVE": "Select the third color in the palette",
+    "SELECT_COLOR_4_DESCRIPTIVE": "Select the fourth color in the palette",
+    "SELECT_COLOR_5_DESCRIPTIVE": "Select the fifth color in the palette",
+    "SELECT_COLOR_6_DESCRIPTIVE": "Select the sixth color in the palette",
+    "SELECT_COLOR_7_DESCRIPTIVE": "Select the seventh color in the palette",
+    "SELECT_COLOR_8_DESCRIPTIVE": "Select the eighth color in the palette",
+    "SELECT_COLOR_9_DESCRIPTIVE": "Select the ninth color in the palette",
+    "SELECT_COLOR_10_DESCRIPTIVE": "Select the tenth color in the palette",
+    "SWAP_COLORS": "Swap colors",
+    "SWAP_COLORS_DESCRIPTIVE": "Swap primary and secondary colors",
+    "SEARCH": "Search",
+    "COMMAND_SEARCH": "Command search",
+    "OPEN_COMMAND_SEARCH": "Open command search window",
+    "SELECT": "Select",
+    "DESELECT": "Deselect",
+    "INVERT": "Invert",
+    "SELECTION": "Selection",
+    "SELECT_ALL": "Select all",
+    "SELECT_ALL_DESCRIPTIVE": "Select everything",
+    "CLEAR_SELECTION": "Clear selection",
+    "INVERT_SELECTION": "Invert selection",
+    "INVERT_SELECTION_DESCRIPTIVE": "Invert the selection",
+    "TRANSFORM_SELECTED_AREA": "Transform selected area",
+    "NUDGE_SELECTED_LEFT": "Nudge selected object left",
+    "NUDGE_SELECTED_RIGHT": "Nudge selected object right",
+    "NUDGE_SELECTED_UP": "Nudge selected object up",
+    "NUDGE_SELECTED_DOWN": "Nudge selected object down",
+    "MASK_FROM_SELECTION": "New mask from selection",
+    "MASK_FROM_SELECTION_DESCRIPTIVE": "Selection to new mask",
+    "ADD_SELECTION_TO_MASK": "Add selection to mask",
+    "SUBTRACT_SELECTION_FROM_MASK": "Subtract selection from mask",
+    "INTERSECT_SELECTION_MASK": "Intersect selection with mask",
+    "SELECTION_TO_MASK": "Selection to mask",
+    "TO_NEW_MASK": "to new mask",
+    "ADD_TO_MASK": "add to mask",
+    "SUBTRACT_FROM_MASK": "subtract from mask",
+    "INTERSECT_WITH_MASK": "intersect with mask",
+    "STYLUS": "Stylus",
+    "TOGGLE_PEN_MODE": "Toggle pen mode",
+    "UNDO": "Undo",
+    "UNDO_DESCRIPTIVE": "Undo last action",
+    "REDO": "Redo",
+    "REDO_DESCRIPTIVE": "Redo last action",
+    "WINDOWS": "Windows",
+    "TOGGLE_GRIDLINES": "Toggle gridlines",
+    "GRIDLINES_SIZE": "Grid Size",
+    "ZOOM_IN": "Zoom in",
+    "ZOOM_OUT": "Zoom out",
+    "NEW_WINDOW_FOR_IMG": "New window for current image",
+    "CENTER_ACTIVE_VIEWPORT": "Center active viewport",
+    "FLIP_VIEWPORT_HORIZONTALLY": "Flip viewport horizontally",
+    "FLIP_VIEWPORT_VERTICALLY": "Flip viewport vertically",
+    "SETTINGS": "Settings",
+    "OPEN_SETTINGS": "Open settings",
+    "OPEN_SETTINGS_DESCRIPTIVE": "Open settings window",
+    "OPEN_STARTUP_WINDOW": "Open startup window",
+    "OPEN_SHORTCUT_WINDOW": "Open shortcuts window",
+    "OPEN_ABOUT_WINDOW": "Open about window",
+    "OPEN_PREVIEW_WINDOW": "Open preview window",
+    "ERROR": "Error",
+    "INTERNAL_ERROR": "Internal error",
+    "ERROR_SAVE_LOCATION": "Couldn't save the file to the specified location",
+    "ERROR_WHILE_SAVING": "An internal error occured while saving. Please try again.",
+    "UNKNOWN_ERROR_SAVING": "An error occured while saving.",
+    "FAILED_ASSOCIATE_LOSPEC": "Failed to associate Lospec Palette protocol.",
+    "REDDIT": "Reddit",
+    "GITHUB": "GitHub",
+    "YOUTUBE": "YouTube",
+    "DONATE": "Donate",
+    "YES": "Yes",
+    "NO": "No",
+    "CANCEL": "Cancel",
+    "UNNAMED": "Unnamed",
+    "OPEN_COMMAND_DEBUG_WINDOW": "Open command debug window",
+    "DELETE": "Delete",
+    "USER_PREFS": "User preferences (Roaming)",
+    "SHORTCUT_FILE": "Shortcut file (Roaming)",
+    "EDITOR_DATA": "Editor data (Local)",
+    "MOVE_VIEWPORT_TOOLTIP": "Moves viewport. ({0})",
+    "MOVE_VIEWPORT_ACTION_DISPLAY": "Click and move to pan the viewport",
+    "MOVE_TOOL_TOOLTIP": "Select and transform layers ({0}).",
+    "MOVE_TOOL_ACTION_DISPLAY": "Hold mouse to move selected pixels. Hold Ctrl to move all layers.",
+    "PEN_TOOL_TOOLTIP": "Pen. ({0})",
+    "PEN_TOOL_ACTION_DISPLAY": "Click and move to draw.",
+    "PIXEL_PERFECT_SETTING": "Pixel perfect",
+    "RECTANGLE_TOOL_TOOLTIP": "Draws rectangle on canvas ({0}). Hold Shift to draw a square.",
+    "RECTANGLE_TOOL_ACTION_DISPLAY_DEFAULT": "Click and move to draw a rectangle. Hold Shift to draw a square.",
+    "RECTANGLE_TOOL_ACTION_DISPLAY_SHIFT": "Click and move to draw a square.",
+    "KEEP_ORIGINAL_IMAGE_SETTING": "Keep original image",
+    "ROTATE_VIEWPORT_TOOLTIP": "Rotates viewport. ({0})",
+    "ROTATE_VIEWPORT_ACTION_DISPLAY": "Click and move to rotate the viewport",
+    "SELECT_TOOL_TOOLTIP": "Selects area. ({0})",
+    "SELECT_TOOL_ACTION_DISPLAY_DEFAULT": "Click and move to select an area. Hold Shift to add to existing selection. Hold Ctrl to subtract from it.",
+    "SELECT_TOOL_ACTION_DISPLAY_SHIFT": "Click and move to add to the current selection.",
+    "SELECT_TOOL_ACTION_DISPLAY_CTRL": "Click and move to subtract from the current selection.",
+    "ZOOM_TOOL_TOOLTIP": "Zooms viewport ({0}). Click to zoom in, hold alt and click to zoom out.",
+    "ZOOM_TOOL_ACTION_DISPLAY_DEFAULT": "Click and move to zoom. Click to zoom in, hold ctrl and click to zoom out.",
+    "ZOOM_TOOL_ACTION_DISPLAY_CTRL": "Click and move to zoom. Click to zoom out, release ctrl and click to zoom in.",
+    "BRIGHTNESS_TOOL_TOOLTIP": "Makes pixels brighter or darker ({0}). Hold Ctrl to make pixels darker.",
+    "BRIGHTNESS_TOOL_ACTION_DISPLAY_DEFAULT": "Draw on pixels to make them brighter. Hold Ctrl to darken.",
+    "BRIGHTNESS_TOOL_ACTION_DISPLAY_CTRL": "Draw on pixels to make them darker. Release Ctrl to brighten.",
+    "COLOR_PICKER_TOOLTIP": "Picks the primary color from the canvas. ({0})",
+    "COLOR_PICKER_ACTION_DISPLAY_DEFAULT": "Click to pick colors. Hold Ctrl to hide the canvas. Hold Shift to hide the reference layer",
+    "ELLIPSE_TOOL_TOOLTIP": "Draws an ellipse on canvas ({0}). Hold Shift to draw a circle.",
+    "ELLIPSE_TOOL_ACTION_DISPLAY_DEFAULT": "Click and move mouse to draw an ellipse. Hold Shift to draw a circle.",
+    "ELLIPSE_TOOL_ACTION_DISPLAY_SHIFT": "Click and move mouse to draw a circle.",
+    "ERASER_TOOL_TOOLTIP": "Erases color from pixel. ({0})",
+    "ERASER_TOOL_ACTION_DISPLAY": "Click and move to erase.",
+    "FLOOD_FILL_TOOL_TOOLTIP": "Fills area with color. ({0})",
+    "FLOOD_FILL_TOOL_ACTION_DISPLAY_DEFAULT": "Press on an area to fill it. Hold down Ctrl to consider all layers.",
+    "FLOOD_FILL_TOOL_ACTION_DISPLAY_CTRL": "Press on an area to fill it. Release Ctrl to only consider the current layers.",
+    "LASSO_TOOL_TOOLTIP": "Lasso. ({0})",
+    "LASSO_TOOL_ACTION_DISPLAY_DEFAULT": "Click and move to select pixels inside of the lasso. Hold Shift to add to existing selection. Hold Ctrl to subtract from it.",
+    "LASSO_TOOL_ACTION_DISPLAY_SHIFT": "Click and move to add pixels inside of the lasso to the selection.",
+    "LASSO_TOOL_ACTION_DISPLAY_CTRL": "Click and move to subtract pixels inside of the lasso from the selection.",
+    "LINE_TOOL_TOOLTIP": "Draws line on canvas ({0}). Hold Shift to enable snapping.",
+    "LINE_TOOL_ACTION_DISPLAY_DEFAULT": "Click and move to draw a line. Hold Shift to enable snapping.",
+    "LINE_TOOL_ACTION_DISPLAY_SHIFT": "Click and move mouse to draw a line with snapping enabled.",
+    "MAGIC_WAND_TOOL_TOOLTIP": "Magic Wand ({0}). Flood's the selection",
+    "MAGIC_WAND_ACTION_DISPLAY": "Click to flood the selection.",
+    "PEN_TOOL": "Pen",
+    "BRIGHTNESS_TOOL": "Brightness",
+    "COLOR_PICKER_TOOL": "Color Picker",
+    "ELLIPSE_TOOL": "Ellipse",
+    "ERASER_TOOL": "Eraser",
+    "FLOOD_FILL_TOOL": "Flood Fill",
+    "LASSO_TOOL": "Lasso",
+    "LINE_TOOL": "Line",
+    "MAGIC_WAND_TOOL": "Magic Wand",
+    "MOVE_TOOL": "Move",
+    "MOVE_VIEWPORT_TOOL": "Move Viewport",
+    "RECTANGLE_TOOL": "Rectangle",
+    "ROTATE_VIEWPORT_TOOL": "Rotate Viewport",
+    "SELECT_TOOL_NAME": "Select",
+    "ZOOM_TOOL": "Zoom",
+    "SHAPE_LABEL": "Shape",
+    "MODE_LABEL": "Mode",
+    "SCOPE_LABEL": "Scope",
+    "FILL_SHAPE_LABEL": "Fill shape",
+    "FILL_COLOR_LABEL": "Fill color",
+    "TOOL_SIZE_LABEL": "Tool size",
+    "STRENGTH_LABEL": "Strength",
+    "NEW": "New",
+    "ADD": "Add",
+    "SUBTRACT": "Subtract",
+    "INTERSECT": "Intersect",
+    "RECTANGLE": "Rectangle",
+    "CIRCLE": "Circle",
+    "ABOUT": "About",
+    "MINIMIZE": "Minimize",
+    "RESTORE": "Restore",
+    "MAXIMIZE": "Maximize",
+    "CLOSE": "Close",
+    "EXPORT_SIZE_HINT": "If you want to share the image, try {0}% for the best clarity",
+    "CREATE": "Create",
+    "BASE_LAYER_NAME": "Base layer",
+    "ENABLE_MASK": "Enable mask",
+    "SELECTED_AREA_EMPTY": "Selected area is empty",
+    "NOTHING_TO_COPY": "Nothing to copy",
+    "REFERENCE_LAYER_PATH": "Reference layer path",
+    "FLIP": "Flip",
+    "ROTATION": "Rotation",
+    "ROT_IMG_90_D": "Rotate Image 90°",
+    "ROT_IMG_180_D": "Rotate Image 180°",
+    "ROT_IMG_-90_D": "Rotate Image -90°",
+    "ROT_LAYERS_90_D": "Rotate Selected Layers 90°",
+    "ROT_LAYERS_180_D": "Rotate Selected Layers 180°",
+    "ROT_LAYERS_-90_D": "Rotate Selected Layers -90°",
+    "UNNAMED_PALETTE": "Unnamed Palette",
+    "CLICK_SELECT_PRIMARY": "Click to select as main color.",
+    "PEN_MODE": "Pen mode",
+    "VIEW": "View",
+    "HORIZONTAL_LINE_SYMMETRY": "Horizontal line symmetry",
+    "VERTICAL_LINE_SYMMETRY": "Vertical line symmetry",
+    "COLOR_PICKER_TITLE": "Color Picker",
+    "COLOR_SLIDERS_TITLE": "Color Sliders",
+    "PALETTE_TITLE": "Palette",
+    "SWATCHES_TITLE": "Swatches",
+    "LAYERS_TITLE": "Layers",
+    "PREVIEW_TITLE": "Preview",
+    "NORMAL_BLEND_MODE": "Normal",
+    "ERASE_BLEND_MODE": "Erase",
+    "DARKEN_BLEND_MODE": "Darken",
+    "MULTIPLY_BLEND_MODE": "Multiply",
+    "COLOR_BURN_BLEND_MODE": "Color burn",
+    "LIGHTEN_BLEND_MODE": "Lighten",
+    "SCREEN_BLEND_MODE": "Screen",
+    "COLOR_DODGE_BLEND_MODE": "Color dodge",
+    "OVERLAY_BLEND_MODE": "Overlay",
+    "SOFT_LIGHT_BLEND_MODE": "Soft light",
+    "HARD_LIGHT_BLEND_MODE": "Hard light",
+    "DIFFERENCE_BLEND_MODE": "Difference",
+    "EXCLUSION_BLEND_MODE": "Exclusion",
+    "HUE_BLEND_MODE": "Hue",
+    "SATURATION_BLEND_MODE": "Saturation",
+    "LUMINOSITY_BLEND_MODE": "Luminosity",
+    "COLOR_BLEND_MODE": "Color",
+    "NOT_SUPPORTED_BLEND_MODE": "Not supported",
+    "RESTART": "Restart",
+    "SORT_BY": "Sort by",
+    "NAME": "Name",
+    "COLORS": "Colors",
+    "DEFAULT": "Default",
+    "ALPHABETICAL": "Alphabetical",
+    "COLOR_COUNT": "Color count",
+    "ANY": "Any",
+    "MAX": "Max",
+    "MIN": "Min",
+    "EXACT": "Exact",
+    "ASCENDING": "Ascending",
+    "DESCENDING": "Descending",
+    "NAME_IS_TOO_LONG": "The name is too long",
+    "STOP_IT_TEXT1": "That's enough. Tidy up your file names.",
+    "STOP_IT_TEXT2": "Can you stop copying these names please?",
+    "REPLACER_TOOLTIP": "Right click on palette color and choose 'Replace' or drop it here.",
+    "CLICK_TO_CHOOSE_COLOR": "Click to choose the color",
+    "REPLACE_COLOR": "Replace color",
+    "PALETTE_COLOR_TOOLTIP": "Click to select as main color. Drag and drop onto another palette color to swap them.",
+    "ADD_FROM_SWATCHES": "Add from swatches",
+    "ADD_COLOR_TO_PALETTE": "Add color to palette",
+    "USE_IN_CURRENT_IMAGE": "Use in current image",
+    "ADD_TO_FAVORITES": "Add to favorites",
+    "BROWSE_PALETTES": "Browse palettes",
+    "LOAD_PALETTE": "Load palette",
+    "SAVE_PALETTE": "Save palette",
+    "FAVORITES": "Favorites",
+    "ADD_FROM_CURRENT_PALETTE": "Add from current palette",
+    "OPEN_PALETTES_DIR_TOOLTIP": "Open palettes directory in explorer",
+    "BROWSE_ON_LOSPEC_TOOLTIP": "Browse palettes on Lospec",
+    "IMPORT_FROM_FILE_TOOLTIP": "Import from file",
+    "TOP_LEFT": "Top left",
+    "TOP_CENTER": "Top center",
+    "TOP_RIGHT": "Top right",
+    "MIDDLE_LEFT": "Middle left",
+    "MIDDLE_CENTER": "Middle center",
+    "MIDDLE_RIGHT": "Middle right",
+    "BOTTOM_LEFT": "Bottom left",
+    "BOTTOM_CENTER": "Bottom center",
+    "BOTTOM_RIGHT": "Bottom right",
+    "CLIP_TO_BELOW": "Clip to member below",
+    "MOVE_UPWARDS": "Move upwards",
+    "MOVE_DOWNWARDS": "Move downwards",
+    "MERGE_SELECTED": "Merge selected",
+    "LOCK_TRANSPARENCY": "Lock transparency",
+    "COULD_NOT_LOAD_PALETTE": "Couldn't fetch palettes",
+    "NO_PALETTES_FOUND": "No palettes found.",
+    "LOSPEC_LINK_TEXT": "I heard you can find some here: lospec.com/palette-list",
+    "PALETTE_BROWSER": "Palette Browser",
+    "DELETE_PALETTE_CONFIRMATION": "Are you sure you want to delete this palette? This cannot be undone.",
+    "SHORTCUTS_IMPORTED": "Shortcuts from {0} were imported successfully.",
+    "SHORTCUT_PROVIDER_DETECTED": "We've detected, that you have {0} installed. Do you want to import shortcuts from it?",
+    "IMPORT_INSTALLATION_OPTION1": "Import from installation",
+    "IMPORT_INSTALLATION_OPTION2": "Use defaults",
+    "IMPORT_FROM_TEMPLATE": "Import from template",
+    "SHORTCUTS_IMPORTED_SUCCESS": "Shortcuts were imported successfully.",
+    "WARNING_RESET_SHORTCUTS_DEFAULT": "Are you sure you want to reset all shortcuts to their default value?",
+    "SUCCESS": "Success",
+    "WARNING": "Warning",
+    "ERROR_IMPORTING_IMAGE": "An error occured while importing the image.",
+    "SHORTCUTS_CORRUPTED_TITLE": "Corrupted shortcuts file",
+    "SHORTCUTS_CORRUPTED": "Shortcuts file was corrupted, resetting to default.",
+    "FAILED_DOWNLOAD_PALETTE": "Failed to download palette",
+    "FILE_INCORRECT_FORMAT": "The file was not in a correct format",
+    "INVALID_FILE": "Invalid file",
+    "SHORTCUTS_FILE_INCORRECT_FORMAT": "Shortcuts file was not in a correct format",
+    "UNSUPPORTED_FILE_FORMAT": "This file format is unsupported",
+    "ALREADY_ASSIGNED": "Already assigned",
+    "REPLACE": "Replace",
+    "SWAP": "Swap",
+    "SHORTCUT_ALREADY_ASSIGNED_SWAP": "This shortcut is already assigned to '{0}'\nDo you want to replace the existing shortcut or swap the two?",
+    "SHORTCUT_ALREADY_ASSIGNED_OVERWRITE": "This shortcut is already assigned to '{0}'\nDo you want to replace the existing shortcut?",
+    "UNSAVED_CHANGES": "Unsaved changes",
+    "DOCUMENT_MODIFIED_SAVE": "The document has been modified. Do you want to save changes?",
+    "PROJECT_MAINTAINERS": "Project Maintainers",
+    "OTHER_AWESOME_CONTRIBUTORS": "And other awesome contributors",
+    "HELP": "Help",
+    "STOP_IT_TEXT3": "No, really, stop it.",
+    "STOP_IT_TEXT4": "Don't you have anything better to do?",
+    "LINEAR_DODGE_BLEND_MODE": "Linear dodge (Add)",
+    "PRESS_ANY_KEY": "Press any key",
+    "NONE_SHORTCUT": "None",
+    "REFERENCE": "Reference",
+    "PUT_REFERENCE_LAYER_ABOVE": "Put reference layer above",
+    "PUT_REFERENCE_LAYER_BELOW": "Put reference layer below",
+    "TOGGLE_VERTICAL_SYMMETRY": "Toggle vertical symmetry",
+    "TOGGLE_HORIZONTAL_SYMMETRY": "Toggle horizontal symmetry",
+    "RESET_VIEWPORT": "Reset viewport",
+    "MOVE_TOOL_ACTION_DISPLAY_TRANSFORMING": "Click and hold mouse to move pixels in selected layers.",
+    "CTRL_KEY": "Ctrl",
+    "SHIFT_KEY": "Shift",
+    "ALT_KEY": "Alt",
+    "RENAME": "Rename",
+    "PIXEL_UNIT": "px",
+    "OPEN_LOCALIZATION_DEBUG_WINDOW": "Open Localization Debug Window",
+    "FORCE_OTHER_FLOW_DIRECTION": "Force other flow direction",
+    "API_KEY": "API Key",
+    "LOCALIZATION_VIEW_TYPE": "Localization View Type",
+    "LOAD_LANGUAGE_FROM_FILE": "Load language from file",
+    "LOG_IN": "Log in",
+    "SYNC": "Sync",
+    "NOT_LOGGED_IN": "Not logged in",
+    "POE_EDITOR_ERROR": "POEditor Error: {0} {1}",
+    "HTTP_ERROR_MESSAGE": "HTTP Error: {0} {1}",
+    "LOGGED_IN": "Logged in",
+    "SYNCED_SUCCESSFULLY": "Synced successfully",
+    "EXCEPTION_ERROR": "Exception: {0}",
+    "DROP_PALETTE": "Drop palette here",
+    "SECURITY_ERROR": "Security error",
+    "SECURITY_ERROR_MSG": "No rights to write to the specified location.",
+    "IO_ERROR": "IO error",
+    "IO_ERROR_MSG": "Error while writing to disk.",
+    "COULD_NOT_SAVE_PALETTE": "There was an error while saving the palette.",
+    "NO_COLORS_TO_SAVE": "There are no colors to save.",
+    "CANVAS": "Canvas",
+    "SINGLE_LAYER": "Single Layer",
+    "CHOOSE": "Choose",
+    "REMOVE": "Remove",
+    "FILE_FORMAT_NOT_ASEPRITE_KEYS": "File is not a \".aseprite-keys\" file",
+    "FILE_HAS_INVALID_SHORTCUT": "The file contains an invalid shortcut",
+    "FILE_EXTENSION_NOT_SUPPORTED": "The file type '{0}' is not supported",
+    "ERROR_READING_FILE": "Error while reading the file",
+    "DISCARD_PALETTE": "Discard palette",
+    "DISCARD_PALETTE_CONFIRMATION": "Are you sure you want to discard current palette? This cannot be undone.",
+    "IMPORT_AS_NEW_LAYER": "Import as new layer",
+    "PASTE_AS_PRIMARY_COLOR": "Paste as primary color",
+    "IMPORT_AS_NEW_FILE": "Import as new file",
+    "IMPORT_PALETTE_FILE": "Import palette file",
+    "IMPORT_MULTIPLE_PALETTE_COLORS": "Import colors into palette",
+    "IMPORT_SINGLE_PALETTE_COLOR": "Import color into palette",
+    "IMPORT_AS_REFERENCE_LAYER": "Import as reference layer",
+    "NAVIGATOR_PICK_ACTION_DISPLAY": "Right-click to pick color, Shift-right-click to copy color to clipboard",
+    "OPEN_FILE_FROM_CLIPBOARD": "Open from clipboard",
+    "OPEN_FILE_FROM_CLIPBOARD_DESCRIPTIVE": "Open from clipboard",
+    "OPEN_LOCALIZATION_DATA": "Do you want to open the LocalizationData.json?\nThe updated date has been put in the clipboard.\nNote that changes wont be applied until a restart",
+    "DOWNLOADING_LANGUAGE_FAILED": "Downloading language failed.\nAPI Key might have been overused.",
+    "LOCALIZATION_DATA_NOT_FOUND": "Localization data path not found",
+    "APPLY": "Apply",
+    "UPDATE_SOURCE": "Update source",
+    "COPY_TO_CLIPBOARD": "Copy to clipboard",
+    "LANGUAGE_FILE_NOT_FOUND": "Language file not found.\nLooking for {0}",
+    "PROJECT_ROOT_NOT_FOUND": "PixiEditor Project root not found.\nLooking for PixiEditor.csproj",
+    "LOCALIZATION_FOLDER_NOT_FOUND": "Localization folder not found.\nLooking for /Data/Localization",
+    "SELECT_A_LANGUAGE": "Select a language",
+    "DONE": "Done",
+    "SOURCE_UNSET_OR_MISSING": "Source missing/unset",
+    "SOURCE_NEWER": "Source newer",
+    "SOURCE_UP_TO_DATE": "Source is up to date",
+    "SOURCE_OLDER": "Cloud newer",
+    "COLOR_PICKER_ACTION_DISPLAY_REFERENCE_ONLY": "Click to pick colors from the reference layer.",
+    "COLOR_PICKER_ACTION_DISPLAY_CANVAS_ONLY": "Click to pick colors from the canvas.",
+    "LOCALIZATION_DEBUG_WINDOW_TITLE": "Localization Debug Window",
+    "SHORTCUTS_TITLE": "Shortcuts",
+    "TRANSFORM_ACTION_DISPLAY_SCALE_ROTATE_SHEAR_PERSPECTIVE": "Drag handles to scale transform. Hold Ctrl and drag a handle to scale from center. Hold Shift to scale proportionally. Hold Alt and drag a side handle to shear. Drag outside handles to rotate.",
+    "TRANSFORM_ACTION_DISPLAY_SCALE_ROTATE_SHEAR_NOPERSPECTIVE": "Drag handles to scale transform. Hold Ctrl and drag a handle to scale from center. Hold Shift to scale proportionally. Hold Alt and drag a side handle to shear. Drag outside handles to rotate.",
+    "TRANSFORM_ACTION_DISPLAY_SCALE_ROTATE_NOSHEAR_NOPERSPECTIVE": "Drag handles to scale transform. Hold Ctrl and drag a handle to scale from center. Hold Shift to scale proportionally. Drag outside handles to rotate.",
+    "TRANSFORM_ACTION_DISPLAY_SCALE_NOROTATE_NOSHEAR_NOPERSPECTIVE": "Drag handles to scale transform. Hold Ctrl and drag a handle to scale from center. Hold Shift to scale proportionally.",
+    "LOCAL_PALETTE_SOURCE_NAME": "Local",
+    "ERROR_FORBIDDEN_UNIQUE_NAME": "Extension unique name cannot start with 'pixieditor'.",
+    "ERROR_MISSING_METADATA": "Extension metadata key '{0}' is missing.",
+    "ERROR_NO_CLASS_ENTRY": "Extension class entry is missing on path '{0}'.",
+    "ERROR_NO_ENTRY_ASSEMBLY": "Extension entry assembly is missing on path '{0}'.",
+    "ERROR_MISSING_ADDITIONAL_CONTENT": "Your current setup doesn't allow loading this extension. Perhaps you don't own it or don't have it installed. You can purchase it here '{0}'.",
+    "BUY_SUPPORTER_PACK": "Buy Supporter Pack",
+    "NEWS": "News",
+    "DISABLE_NEWS_PANEL": "Disable News panel in startup window",
+    "FAILED_FETCH_NEWS": "Failed to fetch news",
+    "CROP_TO_SELECTION": "Crop to selection",
+    "CROP_TO_SELECTION_DESCRIPTIVE": "Crop image to selection",
+    "SHOW_CONTEXT_MENU": "Show context menu",
+    "ERASE": "Erase",
+    "USE_SECONDARY_COLOR": "Use secondary color",
+    "RIGHT_CLICK_MODE": "Right click mode",
+    "ADD_PRIMARY_COLOR_TO_PALETTE": "Add primary color to palette",
+    "ADD_PRIMARY_COLOR_TO_PALETTE_DESCRIPTIVE": "Add primary color to current palette",
+    "EXPORT_SAVE_TITLE": "Choose a location to save the image",
+    "BROWSE_DIRECTORY": "Browse Directory",
+    "CRASH_NOT_ALL_DOCUMENTS_RECOVERED_TITLE": "Not all documents were recovered",
+    "CRASH_NOT_ALL_DOCUMENTS_RECOVERED": "Could not recover all documents. Git gud at saving your work.",
+    "SEND": "Send report",
+    "OPEN_DOCKABLE_MENU": "Open Tab",
+    "TIMELINE_TITLE": "Timeline",
+    "EXPORT_IMAGE_HEADER": "Image",
+    "EXPORT_ANIMATION_HEADER": "Animation",
+    "EXPORT_SPRITESHEET_HEADER": "Spritesheet",
+    "PIXI_FILE": "PixiEditor Files",
+    "PNG_FILE": "PNG Images",
+    "JPEG_FILE": "JPEG Images",
+    "WEBP_FILE": "WebP Images",
+    "GIF_FILE": "GIFs",
+    "BMP_FILE": "BMP Images",
+    "IMAGE_FILES": "Image Files",
+    "VIDEO_FILES": "Video Files",
+    "OPEN_TYPE_FONT": "OpenType Fonts",
+    "TRUE_TYPE_FONT": "TrueType Fonts",
+    "SVG_FILE": "Scalable Vector Graphics",
+    "MP4_FILE": "MP4 Videos",
+    "COLUMNS": "Columns",
+    "ROWS": "Rows",
+    "BACKGROUND": "Background",
+    "OPACITY": "Opacity",
+    "IS_VISIBLE": "Is visible",
+    "BLEND_MODE": "Blend mode",
+    "MASK": "Mask",
+    "MASK_IS_VISIBLE": "Mask is visible",
+    "OUTPUT": "Output",
+    "INPUT": "Input",
+    "NODE_GRAPH_TITLE": "Graph View",
+    "CONTENT": "Content",
+    "RADIUS": "Radius",
+    "STROKE_COLOR": "Stroke color",
+    "STROKE_WIDTH": "Stroke width",
+    "FILL_COLOR": "Fill color",
+    "TOP": "Top",
+    "BOTTOM": "Bottom",
+    "CHANNELS_DOCK_TITLE": "Channels",
+    "RED": "Red",
+    "GREEN": "Green",
+    "BLUE": "Blue",
+    "ALPHA": "Alpha",
+    "COLOR": "Color",
+    "COORDINATE": "Coordinate",
+    "VECTOR": "Vector",
+    "MATRIX": "Matrix",
+    "TRANSFORMED": "Transformed",
+    "GRAYSCALE": "Grayscale",
+    "CLAMP": "Clamp",
+    "SIZE": "Size",
+    "NOISE": "Noise",
+    "SCALE": "Scale",
+    "SEED": "Seed",
+    "KERNEL": "Kernel",
+    "KERNEL_VIEW_SUM": "Sum:",
+    "KERNEL_VIEW_SUM_TOOLTIP": "The sum of all values. You likely want to aim for a value of 1 or 0",
+    "GAIN": "Gain",
+    "BIAS": "Bias",
+    "TILE_MODE": "Tile Mode",
+    "ON_ALPHA": "On Alpha",
+    "OUTPUT_NODE": "Output",
+    "NOISE_NODE": "Noise",
+    "ELLIPSE_NODE": "Ellipse",
+    "LINE_NODE": "Line",
+    "LINE_START": "Start",
+    "LINE_END": "End",
+    "RECTANGLE_NODE": "Rectangle",
+    "CREATE_IMAGE_NODE": "Create Image",
+    "FOLDER_NODE": "Folder",
+    "IMAGE_LAYER_NODE": "Image Layer",
+    "KERNEL_FILTER_NODE": "Kernel Filter",
+    "MATH_NODE": "Math",
+    "COLOR_MATRIX_TRANSFORM_FILTER_NODE": "Matrix Transform Filter",
+    "MERGE_NODE": "Merge",
+    "MODIFY_IMAGE_LEFT_NODE": "Begin Modify Image",
+    "MODIFY_IMAGE_RIGHT_NODE": "End Modify Image",
+    "COMBINE_CHANNELS_NODE": "Combine Channels",
+    "COMBINE_COLOR_NODE": "Combine Color",
+    "COMBINE_VECD_NODE": "Combine Vector",
+    "COMBINE_VECI_NODE": "Combine Integer Vector",
+    "SEPARATE_CHANNELS_NODE": "Separate Channels",
+    "SEPARATE_VECD_NODE": "Separate Vector",
+    "SEPARATE_VECI_NODE": "Separate Integer Vector",
+    "SEPARATE_COLOR_NODE": "Separate Color",
+    "TIME_NODE": "Time",
+    "FILTERS": "Filters",
+    "PREVIOUS": "Previous",
+    "FILL": "Fill",
+    "MATH_MODE": "Math Mode",
+    "NOISE_TYPE": "Noise Type",
+    "OCTAVES": "Octaves",
+    "ACTIVE_FRAME": "Active Frame",
+    "NORMALIZED_TIME": "Normalized Time",
+    "WITHOUT_FILTERS": "Without filters",
+    "RAW_LAYER_OUTPUT": "Raw",
+    "EXAMPLE_FILES": "Example Files",
+    "PROCEDURAL_GENERATION": "Procedural Animation",
+    "POND_EXAMPLE": "Pond",
+    "TREE_EXAMPLE": "Windy Tree",
+    "OUTLINE_EXAMPLE": "Automatic Outline",
+    "BETA_ANIMATIONS": "Animations",
+    "SLIME_EXAMPLE": "Animated Slime",
+    "APPLY_FILTER_NODE": "Apply Filter",
+    "FILTER": "Filter",
+    "LERP_NODE": "Lerp",
+    "GRAYSCALE_FILTER_NODE": "Grayscale Filter",
+    "FROM": "From",
+    "TO": "To",
+    "TIME": "Time",
+    "WARMING_UP": "Warming up",
+    "RENDERING_FRAME": "Generating Frame {0}/{1}",
+    "RENDERING_VIDEO": "Rendering Video",
+    "FINISHED": "Finished",
+    "GENERATING_SPRITE_SHEET": "Generating Sprite Sheet",
+    "RENDERING_IMAGE": "Rendering Image",
+    "PROGRESS_POPUP_TITLE": "Progress",
+    "POINTS": "Points",
+    "MIN_DISTANCE": "Min. Distance",
+    "MAX_POINTS": "Max. Points",
+    "DISTRIBUTE_POINTS": "Distribute points",
+    "REMOVE_CLOSE_POINTS": "Remove close points",
+    "RASTERIZE_SHAPE": "Rasterize Shape",
+    "MODE": "Mode",
+    "Factor": "Factor",
+    "NORMALIZE": "Normalize",
+    "WEIGHT_FACTOR": "Weight",
+    "STARS_EXAMPLE": "Stars",
+    "ADD_EMPTY_FRAME": "Add empty frame",
+    "DUPLICATE_FRAME": "Duplicate frame",
+    "DELETE_FRAME": "Remove frame",
+    "DEFAULT_MEMBER_NAME": "New Element",
+    "NO_PARSER_FOUND": "No file parser found for extension '{0}'",
+    "SELECT_FILE_FORMAT": "Select file format",
+    "SELECT_FILE_FORMAT_DESCRIPTION": "Multiple file types of the same format are supported. Please select the one you want to use.",
+    "NEW_PALETTE_FILE": "palette",
+    "ISLAND_EXAMPLE": "Islands",
+    "ONION_FRAMES_COUNT": "Onion frames",
+    "ONION_OPACITY": "Onion opacity",
+    "TOGGLE_ONION_SKINNING": "Toggle onion skinning",
+    "CHANGE_ACTIVE_FRAME_PREVIOUS": "Change active frame to previous",
+    "CHANGE_ACTIVE_FRAME_NEXT": "Change active frame to next",
+    "TOGGLE_ANIMATION": "Toggle animation",
+    "NEW_FROM_CLIPBOARD": "New from clipboard",
+    "OFFSET": "Offset",
+    "SHAPE": "Shape",
+    "STRUCTURE": "Structure",
+    "NUMBERS": "Numbers",
+    "OPERATIONS": "Operations",
+    "GENERATION": "Generation",
+    "NUMBER": "Number",
+    "ANIMATION": "Animation",
+    "SAMPLE_IMAGE": "Sample Image",
+    "POSITION": "Position",
+    "MATH_ADD": "Add",
+    "MATH_SUBTRACT": "Subtract",
+    "MULTIPLY": "Multiply",
+    "DIVIDE": "Divide",
+    "SIN": "Sin",
+    "COS": "Cos",
+    "TAN": "Tan",
+    "GREATER_THAN": "Greater than",
+    "LESS_THAN": "Less than",
+    "LESS_THAN_OR_EQUAL": "Less than or equal",
+    "COMPARE": "Compare",
+    "MATH_POWER": "Power",
+    "LOGARITHM": "Logarithm",
+    "NATURAL_LOGARITHM": "Natural logarithm",
+    "ROOT": "Root",
+    "INVERSE_ROOT": "Inverse root",
+    "FRACTION": "Fraction",
+    "NEGATE": "Negate",
+    "FLOOR": "Floor",
+    "CEIL": "Ceil",
+    "ROUND": "Round",
+    "MODULO": "Modulo",
+    "STEP": "Step",
+    "SMOOTH_STEP": "Smoothstep",
+    "PIXEL_ART_TOOLSET": "Pixel Art",
+    "VECTOR_TOOLSET": "Vector",
+    "VECTOR_LAYER": "Vector Layer",
+    "STROKE_COLOR_LABEL": "Stroke",
+    "SYNC_WITH_PRIMARY_COLOR_LABEL": "Sync with primary color",
+    "RASTERIZE": "Rasterize",
+    "RASTERIZE_ACTIVE_LAYER": "Rasterize active layer",
+    "RASTERIZE_ACTIVE_LAYER_DESCRIPTIVE": "Convert/Rasterize the active layer into a image (raster) layer.",
+    "NEW_ELLIPSE_LAYER_NAME": "Ellipse",
+    "NEW_RECTANGLE_LAYER_NAME": "Rectangle",
+    "NEW_LINE_LAYER_NAME": "Line",
+    "RENDER_OUTPUT": "Render Output",
+    "PAINT_TOOLSET": "Painting",
+    "HARDNESS_SETTING": "Hardness",
+    "SPACING_SETTING": "Spacing",
+    "ANTI_ALIASING_SETTING": "Anti-aliasing",
+    "TOLERANCE_LABEL": "Tolerance",
+    "TOGGLE_SNAPPING": "Toggle snapping",
+    "HIGH_RES_PREVIEW": "High Resolution Preview",
+    "LOW_RES_PREVIEW": "Document Resolution Preview",
+    "TOGGLE_HIGH_RES_PREVIEW": "Toggle high resolution preview",
+    "FACTOR": "Factor",
+    "PATH_TOOL": "Path",
+    "PATH_TOOL_TOOLTIP": "Create vector paths and curves ({0}).",
+    "PATH_TOOL_ACTION_DISPLAY": "Click to add a point.",
+    "PATH_TOOL_ACTION_DISPLAY_CTRL": "Click on existing point and drag to make it a curve. Tap on a control point to select it.",
+    "PATH_TOOL_ACTION_DISPLAY_SHIFT": "Click to create a new layer.",
+    "PATH_TOOL_ACTION_DISPLAY_CTRL_SHIFT": "Tap on a control point to add it to the selection.",
+    "PATH_TOOL_ACTION_DISPLAY_ALT": "Click on a control point and move to adjust only one side of the curve.",
+    "DEFAULT_PATH_LAYER_NAME": "Path",
+    "DELETE_NODES": "Delete nodes",
+    "DELETE_NODES_DESCRIPTIVE": "Delete selected nodes",
+    "DELETE_CELS": "Delete cels",
+    "DELETE_CELS_DESCRIPTIVE": "Delete selected cels",
+    "COPY_COLOR_TO_CLIPBOARD": "Copy color to clipboard",
+    "VIEWPORT_ROTATION": "Viewport rotation",
+    "NEXT_TOOL_SET": "Next tool set",
+    "PREVIOUS_TOOL_SET": "Previous tool set",
+    "FILL_MODE": "Fill mode",
+    "USE_LINEAR_SRGB_PROCESSING": "Use linear sRGB for processing colors",
+    "USE_LINEAR_SRGB_PROCESSING_DESC": "Convert document using sRGB blending mode to linear sRGB for processing colors. This will affect the colors of the document, but will make blending more accurate.",
+    "FILL_TYPE_WINDING": "Winding",
+    "FILL_TYPE_EVEN_ODD": "Even Odd",
+    "FILL_TYPE_INVERSE_WINDING": "Inverse Winding",
+    "FILL_TYPE_INVERSE_EVEN_ODD": "Inverse Even Odd",
+    "STROKE_CAP": "Stroke Cap",
+    "STROKE_JOIN": "Stroke Join",
+    "COPY_VISIBLE": "Copy visible",
+    "COPY_VISIBLE_DESCRIPTIVE": "Copy visible pixels",
+    "COLOR_SAMPLE_MODE": "Sample mode",
+    "CREATE_CEL": "Create cel",
+    "CREATE_CEL_DESCRIPTIVE": "Create a new cel",
+    "DUPLICATE_CEL": "Duplicate cel",
+    "DUPLICATE_CEL_DESCRIPTIVE": "Duplicate cel in the current frame",
+    "RENDER_PREVIEW": "Render preview",
+    "OUTPUT_NAME": "Output name",
+    "CUSTOM_OUTPUT_NODE": "Custom Output",
+    "TOGGLE_HUD": "Toggle HUD",
+    "OPEN_TIMELINE": "Open timeline",
+    "OPEN_NODE_GRAPH": "Open node graph",
+    "TOGGLE_PLAY": "Play/Pause animation",
+    "COPY_NODES": "Copy nodes",
+    "COPY_NODES_DESCRIPTIVE": "Copy selected nodes",
+    "PASTE_NODES": "Paste nodes",
+    "PASTE_NODES_DESCRIPTIVE": "Paste copied nodes",
+    "COPY_CELS": "Copy cels",
+    "COPY_CELS_DESCRIPTIVE": "Copy selected cels",
+    "TOGGLE_ONION_SKINNING_DESCRIPTIVE": "Toggle onion skinning",
+    "VALUE": "Value",
+    "TARGET": "Target",
+    "EPSILON": "Epsilon",
+    "PRESERVE_ALPHA": "Preserve alpha",
+    "BLUR_FILTER_NODE": "Gaussian Blur Filter",
+    "LENGTH": "Length",
+    "GREATER_THAN_OR_EQUAL": "Greater than or equal",
+    "COLOR_NODE": "Color",
+    "CONVERT_TO_CURVE": "Convert to curve",
+    "CONVERT_TO_CURVE_DESCRIPTIVE": "Convert selected vector layer to a curve/path",
+    "FONT_FILES": "Font Files",
+    "UNIT_PT": "pt",
+    "FONT_LABEL": "Family",
+    "FONT_SIZE_LABEL": "Size",
+    "SPACING_LABEL": "Spacing",
+    "TEXT_TOOL": "Text",
+    "MISSING_FONT": "Missing font",
+    "TEXT_LAYER_NAME": "Text",
+    "TEXT_TOOL_TOOLTIP": "Create text ({0}).",
+    "BOLD_TOOLTIP": "Bold",
+    "ITALIC_TOOLTIP": "Italic",
+    "CUSTOM_FONT": "Custom font",
+    "DUMP_GPU_DIAGNOSTICS": "Dump GPU diagnostics",
+    "USE_SRGB_PROCESSING": "Use sRGB for processing colors",
+    "USE_SRGB_PROCESSING_DESC": "Convert document using linear sRGB to sRGB for processing colors. This will affect the colors of the document.",
+    "TEXT_NODE": "Text",
+    "TEXT_LABEL": "Text",
+    "TEXT_ON_PATH_NODE": "Text on Path",
+    "HIGH_DPI_RENDERING": "High DPI Rendering",
+    "THICKNESS": "Thickness",
+    "TYPE": "Type",
+    "EFFECTS": "Effects",
+    "OUTLINE_NODE": "Outline",
+    "SHADER_CODE": "Shader Code",
+    "SHADER_NODE": "Shader",
+    "FAILED_TO_OPEN_EDITABLE_STRING_TITLE": "Failed to open file",
+    "FAILED_TO_OPEN_EDITABLE_STRING_MESSAGE": "Failed to edit this string in external editor. Reason: {0}",
+    "STRING_EDIT_IN_DEFAULT_APP": "Edit in default app",
+    "STRING_OPEN_IN_FOLDER": "Open in folder",
+    "DISCO_BALL_EXAMPLE": "Disco Ball",
+    "COLOR_SPACE": "Color Space",
+    "PHOTO_EXAMPLES": "Photo",
+    "MASK_EXAMPLE": "Mask",
+    "SHADOW_NODE": "Shadow Filter",
+    "INPUT_MATRIX": "Input Matrix",
+    "OUTPUT_MATRIX": "Output Matrix",
+    "CENTER": "Center",
+    "CANVAS_POSITION": "Canvas Position",
+    "CENTER_POSITION": "Center Position",
+    "TILE_MODE_X": "Tile Mode X",
+    "TILE_MODE_Y": "Tile Mode Y",
+    "TILE_NODE": "Tile",
+    "SKEW": "Skew",
+    "OFFSET_NODE": "Offset",
+    "SKEW_NODE": "Skew",
+    "SCALE_NODE": "Scale",
+    "ROTATE_NODE": "Rotate",
+    "TRANSFORM_NODE": "Transform",
+    "UNIT": "Unit",
+    "ANGLE": "Angle",
+    "DOCUMENT_INFO_NODE": "Document Info",
+    "MASK_NODE": "Mask",
+    "SEPIA_FILTER_NODE": "Sepia Filter",
+    "INTENSITY": "Intensity",
+    "INVERT_FILTER_NODE": "Invert Filter",
+    "COLOR_ADJUSTMENTS_FILTER": "Color Adjustments Filter",
+    "ADJUST_BRIGHTNESS": "Adjust Brightness",
+    "ADJUST_CONTRAST": "Adjust Contrast",
+    "ADJUST_SATURATION": "Adjust Saturation",
+    "ADJUST_TEMPERATURE": "Adjust Temperature",
+    "ADJUST_TINT": "Adjust Tint",
+    "ADJUST_HUE": "Adjust Hue",
+    "HUE_VALUE": "Hue",
+    "SATURATION_VALUE": "Saturation",
+    "BRIGHTNESS_VALUE": "Brightness",
+    "CONTRAST_VALUE": "Contrast",
+    "TEMPERATURE_VALUE": "Temperature",
+    "TINT_VALUE": "Tint",
+    "UNEXPECTED_SHUTDOWN": "Unexpected shutdown",
+    "UNEXPECTED_SHUTDOWN_MSG": "PixiEditor was unexpectedly shut down. We've loaded latest autosave of your files.",
+    "OK": "OK",
+    "OPEN_AUTOSAVES": "Browse Autosaves",
+    "AUTOSAVE_SETTINGS_HEADER": "Autosave",
+    "AUTOSAVE_SETTINGS_SAVE_STATE": "Reopen last files on startup",
+    "AUTOSAVE_SETTINGS_PERIOD": "Autosave period",
+    "AUTOSAVE_ENABLED": "Autosave enabled",
+    "MINUTE_UNIVERSAL": "min",
+    "AUTOSAVE_SETTINGS_SAVE_USER_FILE": "Autosave to selected file",
+    "LOAD_LAZY_FILE_MESSAGE": "To improve startup time, PixiEditor didn't load this file. Click the button below to load it.",
+    "EASING_NODE": "Easing",
+    "EASING_TYPE": "Easing Type",
+    "OPEN_DIRECTORY_ON_EXPORT": "Open directory on export",
+    "ERROR_LOOP_DETECTED_MESSAGE": "Moving this layer will create a loop. Fix it in the Node Graph.",
+    "LINEAR_EASING_TYPE": "Linear",
+    "IN_SINE_EASING_TYPE": "In Sine",
+    "OUT_SINE_EASING_TYPE": "Out Sine",
+    "IN_OUT_SINE_EASING_TYPE": "In Out Sine",
+    "IN_QUAD_EASING_TYPE": "In Quad",
+    "OUT_QUAD_EASING_TYPE": "Out Quad",
+    "IN_OUT_QUAD_EASING_TYPE": "In Out Quad",
+    "IN_CUBIC_EASING_TYPE": "In Cubic",
+    "OUT_CUBIC_EASING_TYPE": "Out Cubic",
+    "IN_OUT_CUBIC_EASING_TYPE": "In Out Cubic",
+    "IN_QUART_EASING_TYPE": "In Quart",
+    "OUT_QUART_EASING_TYPE": "Out Quart",
+    "IN_OUT_QUART_EASING_TYPE": "In Out Quart",
+    "IN_QUINT_EASING_TYPE": "In Quint",
+    "OUT_QUINT_EASING_TYPE": "Out Quint",
+    "IN_OUT_QUINT_EASING_TYPE": "In Out Quint",
+    "IN_EXPO_EASING_TYPE": "In Expo",
+    "OUT_EXPO_EASING_TYPE": "Out Expo",
+    "IN_OUT_EXPO_EASING_TYPE": "In Out Expo",
+    "IN_CIRC_EASING_TYPE": "In Circ",
+    "OUT_CIRC_EASING_TYPE": "Out Circ",
+    "IN_OUT_CIRC_EASING_TYPE": "In Out Circ",
+    "IN_BACK_EASING_TYPE": "In Back",
+    "OUT_BACK_EASING_TYPE": "Out Back",
+    "IN_OUT_BACK_EASING_TYPE": "In Out Back",
+    "IN_ELASTIC_EASING_TYPE": "In Elastic",
+    "OUT_ELASTIC_EASING_TYPE": "Out Elastic",
+    "IN_OUT_ELASTIC_EASING_TYPE": "In Out Elastic",
+    "IN_BOUNCE_EASING_TYPE": "In Bounce",
+    "OUT_BOUNCE_EASING_TYPE": "Out Bounce",
+    "IN_OUT_BOUNCE_EASING_TYPE": "In Out Bounce",
+    "R_G_B_COMBINE_SEPARATE_COLOR_MODE": "RGB",
+    "H_S_V_COMBINE_SEPARATE_COLOR_MODE": "HSV",
+    "H_S_L_COMBINE_SEPARATE_COLOR_MODE": "HSL",
+    "COLOR_MANAGED_COLOR_SAMPLE_MODE": "Color Managed",
+    "RAW_COLOR_SAMPLE_MODE": "Raw",
+    "FRACTAL_PERLIN_NOISE_TYPE": "Perlin",
+    "TURBULENCE_PERLIN_NOISE_TYPE": "Turbulence",
+    "VORONOI_NOISE_TYPE": "Voronoi",
+    "VORONOI_FEATURE": "Feature",
+    "F1_VORONOI_FEATURE": "F1",
+    "F2_VORONOI_FEATURE": "F2",
+    "F2_MINUS_F1_VORONOI_FEATURE": "F2-F1",
+    "RANDOMNESS": "Randomness",
+    "ANGLE_OFFSET": "Angle Offset",
+    "INHERIT_COLOR_SPACE_TYPE": "Inherit",
+    "SRGB_COLOR_SPACE_TYPE": "sRGB",
+    "LINEAR_SRGB_COLOR_SPACE_TYPE": "Linear sRGB",
+    "SIMPLE_OUTLINE_TYPE": "Simple",
+    "GAUSSIAN_OUTLINE_TYPE": "Gaussian",
+    "PIXEL_PERFECT_OUTLINE_TYPE": "Pixel Perfect",
+    "DEGREES_ROTATION_TYPE": "Degrees",
+    "RADIANS_ROTATION_TYPE": "Radians",
+    "WEIGHTED_GRAYSCALE_MODE": "Weighted",
+    "AVERAGE_GRAYSCALE_MODE": "Average",
+    "CUSTOM_GRAYSCALE_MODE": "Custom",
+    "CLAMP_TILE_MODE": "Clamp",
+    "REPEAT_TILE_MODE": "Repeat",
+    "MIRROR_TILE_MODE": "Mirror",
+    "DECAL_TILE_MODE": "Decal",
+    "ERR_UNKNOWN_FILE_FORMAT": "Unknown file format",
+    "ERR_EXPORT_SIZE_INVALID": "Invalid export size. Values must be greater than 0.",
+    "ERR_UNKNOWN_IMG_FORMAT": "Unknown image format '{0}'.",
+    "ERR_FAILED_GENERATE_SPRITE_SHEET": "Failed generating sprite sheet",
+    "ERR_NO_RENDERER": "Animation renderer not found.",
+    "ERR_RENDERING_FAILED": "Rendering failed",
+    "ENABLE_ANALYTICS": "Send anonymous analytics",
+    "ANALYTICS_INFO": "We collect anonymous usage data to improve PixiEditor. No personal data is collected.",
+    "LANGUAGE_INFO": "All translations are community-driven. Join our Discord server for more information.",
+    "UP_TO_DATE_UNKNOWN": "Couldn't check for updates",
+    "UP_TO_DATE": "PixiEditor is up to date",
+    "UPDATE_AVAILABLE": "Update {0} is available",
+    "UPDATE_FAILED_DOWNLOAD": "Failed to download the update",
+    "UPDATE_READY_TO_INSTALL": "Update is ready. Switch to {0}?",
+    "SWITCH_TO_NEW_VERSION": "Switch",
+    "DOWNLOAD_UPDATE": "Download",
+    "DOWNLOADING_UPDATE": "Downloading update...",
+    "CHECKING_FOR_UPDATES": "Checking for updates...",
+    "PAINT_SHAPE_SETTING": "Brush shape",
+    "BOOL_OPERATION_NODE": "Boolean Operation",
+    "FIRST_SHAPE": "First shape",
+    "SECOND_SHAPE": "Second shape",
+    "OPERATION": "Operation",
+    "UNION_VECTOR_PATH_OP": "Union",
+    "DIFFERENCE_VECTOR_PATH_OP": "Difference",
+    "INTERSECT_VECTOR_PATH_OP": "Intersect",
+    "XOR_VECTOR_PATH_OP": "XOR",
+    "REVERSE_DIFFERENCE_VECTOR_PATH_OP": "Reverse Difference",
+    "NO_DOCUMENT_OPEN": "Nothing's here",
+    "EMPTY_DOCUMENT_ACTION_BTN": "Start creating",
+    "ONBOARDING_TITLE": "Welcome to",
+    "ONBOARDING_DESCRIPTION": "Let's set up your workspace!",
+    "ONBOARDING_SKIP_BTN": "Skip",
+    "ONBOARDING_ACTION_BTN": "Let's begin",
+    "ONB_SELECT_PRIMARY_TOOLSET": "Select Your Primary Toolset",
+    "ONB_NEXT_BTN": "Next",
+    "ONB_FINISH_BTN": "Finish",
+    "ONB_BACK_BTN": "Previous",
+    "ONB_ANALYTICS": "Anonymous Analytics",
+    "ONB_ALL_SET": "You are all set!",
+    "ONB_ALL_SET_BTN": "Start creating",
+    "ANALYTICS_INFO_DETAILED": "PixiEditor collects anonymous usage data to improve the app. The data does not contain any personal information. Among other things, PixiEditor tracks the following:\n- Which and how the tools are used\n- How long you've used the app for\n- Which commands are used\n- Performance data\n\n You can opt out of analytics at any time in the settings.",
+    "PRIVACY_POLICY": "Privacy Policy",
+    "ONB_SHORTCUTS": "Select Your Shortcuts",
+    "GRAPH_STATE_UNABLE_TO_CREATE_MEMBER": "Current Node Graph setup disallows creation of a new layer next to the selected one.",
+    "PRIMARY_TOOLSET": "Primary Toolset",
+    "OPEN_ONBOARDING_WINDOW": "Open onboarding window",
+    "USER_NOT_FOUND": "Please enter the email you used to purchase the Founder's Edition.",
+    "SESSION_NOT_VALID": "Session is not valid, please log in again",
+    "SESSION_NOT_FOUND": "Session not found, try logging in again",
+    "INTERNAL_SERVER_ERROR": "There was an internal server error. Please try again later.",
+    "TOO_MANY_REQUESTS": "Too many requests. Try again in {0} seconds.",
+    "SESSION_EXPIRED": "Session expired. Please log in again.",
+    "CONNECTION_ERROR": "Connection error. Please check your internet connection.",
+    "FAIL_LOAD_USER_DATA": "Failed to load saved user data",
+    "LOGOUT": "Logout",
+    "LOGGED_IN_AS": "Hello",
+    "EMAIL_SENT": "Email sent! Check your inbox.",
+    "RESEND_ACTIVATION": "Resend",
+    "INVALID_TOKEN": "Session is invalid or expired. Please log in again.",
+    "ENTER_EMAIL": "Enter your email",
+    "LOGIN_LINK": "Send Login Link",
+    "LOGIN_LINK_INFO": "We'll email you a secure link to log in. No password needed.",
+    "ACCOUNT_WINDOW_TITLE": "Account",
+    "CONNECTION_TIMEOUT": "Connection timed out. Please try again.",
+    "OPEN_ACCOUNT_WINDOW": "Manage Account",
+    "AUTO_SCALE_BACKGROUND": "Auto scale background",
+    "UPDATES": "Updates",
+    "SCENE": "Scene",
+    "CUSTOM_BACKGROUND_SCALE": "Custom background scale",
+    "PRIMARY_BG_COLOR": "Primary background color",
+    "SECONDARY_BG_COLOR": "Secondary background color",
+    "RESET": "Reset",
+    "INSTALL": "Install",
+    "OWNED_PRODUCTS": "Owned Content",
+    "INSTALLING": "Installing",
+    "INSTALLED": "Installed",
+    "ACCOUNT_PROVIDER_INFO": "Account handled by",
+    "UPDATE": "Update",
+    "AUTOSAVE_OPEN_FOLDER": "Open autosave folder",
+    "AUTOSAVE_OPEN_FOLDER_DESCRIPTIVE": "Open the folder where autosaves are stored",
+    "AUTOSAVE_TOGGLE_DESCRIPTIVE": "Enable/disable autosave",
+    "FOUNDERS_BUNDLE": "Founder's Bundle",
+    "FOUNDERS_BUNDLE_SUBTEXT": "Support PixiEditor and boost your productivity!",
+    "BECOME_A_FOUNDER": "Become a Founder",
+    "LOGIN": "Login",
+    "NOT_FOUNDER_YET": "Not a Founder yet?",
+    "ERROR_GRAPH": "Graph setup produced an error. Fix it in the node graph",
+    "COLOR_MATRIX_FILTER_NODE": "Color Matrix Filter",
+    "WORKSPACE": "Workspace",
+    "IS_DEFAULT_EXPORT": "Is Default Export",
+    "EXPORT_OUTPUT": "Export Output",
+    "RENDER_OUTPUT_SIZE": "Render Output Size",
+    "RENDER_OUTPUT_CENTER": "Render Output Center",
+    "COLOR_PICKER": "Color Picker",
+    "UNAUTHORIZED_ACCESS": "Unauthorized access",
+    "SEPARATE_SHAPES": "Separate Shapes",
+    "SEPARATE_SHAPES_DESCRIPTIVE": "Separate shapes from current vector into individual layers",
+    "TEXT": "Text",
+    "EXTRACT_SELECTED_TEXT": "Extract selected text",
+    "EXTRACT_SELECTED_TEXT_DESCRIPTIVE": "Extract selected text into new layer.",
+    "EXTRACT_SELECTED_CHARACTERS": "Extract selected characters",
+    "EXTRACT_SELECTED_CHARACTERS_DESCRIPTIVE": "Extract individual characters from selection into new layers.",
+    "STEP_START": "Step back to closest cel",
+    "STEP_END": "Step forward to closest cel",
+    "STEP_FORWARD": "Step forward one frame",
+    "STEP_BACK": "Step back one frame",
+    "ANIMATION_QUALITY_PRESET": "Quality Preset",
+    "VERY_LOW_QUALITY_PRESET": "Very Low",
+    "LOW_QUALITY_PRESET": "Low",
+    "MEDIUM_QUALITY_PRESET": "Medium",
+    "HIGH_QUALITY_PRESET": "High",
+    "VERY_HIGH_QUALITY_PRESET": "Very High",
+    "EXPORT_FRAMES": "Export Frames",
+    "NORMALIZE_OFFSET": "Normalize Offset",
+    "TANGENT": "Tangent",
+    "EVALUATE_PATH_NODE": "Evaluate Path",
+    "OLD_MIN": "Old Min",
+    "OLD_MAX": "Old Max",
+    "NEW_MIN": "New Min",
+    "NEW_MAX": "New Max",
+    "REMAP_NODE": "Remap",
+    "TEXT_TOOL_ACTION_DISPLAY": "Click on the canvas to add a new text (drag while clicking to set the size). Click on existing text to edit it.",
+    "PASTE_CELS": "Paste cels",
+    "PASTE_CELS_DESCRIPTIVE": "Paste cels from clipboard into the current frame",
+    "SCALE_X": "Scale X",
+    "SCALE_Y": "Scale Y",
+    "TRANSLATE_X": "Translate X",
+    "TRANSLATE_Y": "Translate Y",
+    "SKEW_X": "Skew X",
+    "SKEW_Y": "Skew Y",
+    "PERSPECTIVE_0": "Perspective 0",
+    "PERSPECTIVE_1": "Perspective 1",
+    "PERSPECTIVE_2": "Perspective 2",
+    "COMPOSE_MATRIX": "Compose Matrix",
+    "DECOMPOSE_MATRIX": "Decompose Matrix",
+    "NORMALIZE_COORDINATES": "Normalize Coordinates",
+    "TRANSFORMED_POSITION": "Transformed Position",
+    "ACCOUNT_PROVIDER_NOT_AVAILABLE": "This build of PixiEditor does not support accounts. Use the official build from pixieditor.net to manage your account.",
+    "STEAM_OFFLINE": "Cannot validate the account. Steam is offline. Make sure Steam client is running and you are logged in.",
+    "ERROR_GPU_RESOURCES_CREATION": "Failed to create resources: Try updating your GPU drivers or try setting different rendering api in settings. \nError: '{0}'",
+    "ERROR_SAVING_PREFERENCES_DESC": "Failed to save preferences with error: '{0}'. Please check if you have write permissions to the PixiEditor data folder.",
+    "ERROR_SAVING_PREFERENCES": "Failed to save preferences",
+    "PREFERRED_RENDERER": "Preferred Render Api",
+    "PERFORMANCE": "Performance",
+    "DISABLE_PREVIEWS": "Disable Previews",
+    "MAX_BILINEAR_CANVAS_SIZE": "Max Bilinear Canvas Size",
+    "MAX_BILINEAR_CANVAS_SIZE_DESC": "Maximum canvas size for bilinear filtering. Set to 0 to disable bilinear filtering. Bilinear filtering improves the quality of the canvas, but can cause performance issues on large canvases.",
+    "INVERT_MASK": "Invert mask",
+    "TOGGLE_TINTING_SELECTION": "Toggle selection tinting",
+    "TOGGLE_TINTING_SELECTION_DESCRIPTIVE": "Toggle selection tinting",
+    "TINT_SELECTION": "Selection tinting",
+    "PAINT_BRUSH_SHAPE_CIRCLE": "Circle",
+    "PAINT_BRUSH_SHAPE_SQUARE": "Square",
+    "BRIGHTNESS_MODE_DEFAULT": "Default",
+    "BRIGHTNESS_MODE_REPEAT": "Repeat",
+    "ROUND_STROKE_CAP": "Round",
+    "BUTT_STROKE_CAP": "Butt",
+    "SQUARE_STROKE_CAP": "Square",
+    "ROUND_STROKE_JOIN": "Round",
+    "MITER_STROKE_JOIN": "Miter",
+    "BEVEL_STROKE_JOIN": "Bevel",
+    "TOGGLE_FULLSCREEN": "Toggle Fullscreen",
+    "TOGGLE_FULLSCREEN_DESCRIPTIVE": "Enter or Exit Fullscreen Mode"
 }
 }

+ 106 - 100
src/PixiEditor/Data/Localization/Languages/es.json

@@ -1,33 +1,33 @@
 {
 {
-  "RECENT_FILES": "Archivo Reciente",
-  "OPEN_FILE": "Abrir Archivo",
+  "RECENT_FILES": "Archivos recientes",
+  "OPEN_FILE": "Abrir archivo",
   "NEW_FILE": "Nuevo",
   "NEW_FILE": "Nuevo",
-  "RECENT_EMPTY_TEXT": "Demasiado espacio vacio",
+  "RECENT_EMPTY_TEXT": "Bastante vacío aquí",
   "LANGUAGE": "Idioma",
   "LANGUAGE": "Idioma",
   "GENERAL": "General",
   "GENERAL": "General",
   "DISCORD": "Discord",
   "DISCORD": "Discord",
-  "KEY_BINDINGS": "Fijaciones de teclas",
-  "MISC": "Otros",
-  "SHOW_STARTUP_WINDOW": "Abrir Al Iniciar Windows",
+  "KEY_BINDINGS": "Atajos del teclado",
+  "MISC": "Misceláneo",
+  "SHOW_STARTUP_WINDOW": "Mostrar ventana de bienvenida",
   "RECENT_FILE_LENGTH": "Longitud de la lista de archivos reciente",
   "RECENT_FILE_LENGTH": "Longitud de la lista de archivos reciente",
-  "RECENT_FILE_LENGTH_TOOLTIP": "Cuántos documentos se muestran en Archivo > Recientes. Por defecto: 8",
-  "DEFAULT_NEW_SIZE": "Nuevo archivo por defecto",
-  "WIDTH": "Anchura",
-  "HEIGHT": "Altura",
+  "RECENT_FILE_LENGTH_TOOLTIP": "Cuántos documentos se muestran en Archivo > Recientes. Predeterminado: 8",
+  "DEFAULT_NEW_SIZE": "Tamaño de imagen predeterminado",
+  "WIDTH": "Ancho",
+  "HEIGHT": "Alto",
   "TOOLS": "Herramientas",
   "TOOLS": "Herramientas",
   "ENABLE_SHARED_TOOLBAR": "Activar la barra de herramientas compartida",
   "ENABLE_SHARED_TOOLBAR": "Activar la barra de herramientas compartida",
-  "AUTOMATIC_UPDATES": "Actualizaciones Automáticas",
+  "AUTOMATIC_UPDATES": "Actualizaciones automáticas",
   "CHECK_FOR_UPDATES": "Buscar actualizaciones al iniciar",
   "CHECK_FOR_UPDATES": "Buscar actualizaciones al iniciar",
-  "UPDATE_STREAM": "Actualizar stream",
-  "UPDATE_CHANNEL_HELP_TOOLTIP": "Los canales de actualización sólo se pueden cambiar en la versión independiente (descargada de https://pixieditor.net).\nLas versiones de Steam y Microsoft Store gestionan las actualizaciones por separado.",
-  "DEBUG": "Debug",
-  "ENABLE_DEBUG_MODE": "Activar modo Debug",
-  "OPEN_CRASH_REPORTS_DIR": "Abrir lista de reportes",
+  "UPDATE_STREAM": "Frecuencia de actualizaciones",
+  "UPDATE_CHANNEL_HELP_TOOLTIP": "Las actualizaciones sólo se pueden gestionar manualmente en la versión descargada por el sitio del programa (https://pixieditor.net).\nSteam y la tienda de Microsoft gestionan las actualizaciones por separado.",
+  "DEBUG": "Depuración",
+  "ENABLE_DEBUG_MODE": "Activar modo de depuración",
+  "OPEN_CRASH_REPORTS_DIR": "Abrir carpeta de reportes de colapsos",
   "DISCORD_RICH_PRESENCE": "Rich Presence",
   "DISCORD_RICH_PRESENCE": "Rich Presence",
   "ENABLED": "Activado",
   "ENABLED": "Activado",
-  "SHOW_IMAGE_NAME": "Mostrar nombre del archivo",
-  "SHOW_IMAGE_SIZE": "Mostrar tamaño del archivo",
-  "SHOW_LAYER_COUNT": "Mostrar recuento de capas",
+  "SHOW_IMAGE_NAME": "Mostrar nombre de la imagen",
+  "SHOW_IMAGE_SIZE": "Mostrar tamaño de la imagen",
+  "SHOW_LAYER_COUNT": "Mostrar cantidad de capas",
   "FILE": "Archivo",
   "FILE": "Archivo",
   "RECENT": "Reciente",
   "RECENT": "Reciente",
   "OPEN": "Abrir",
   "OPEN": "Abrir",
@@ -38,119 +38,119 @@
   "EXIT": "Salir",
   "EXIT": "Salir",
   "PERCENTAGE": "Porcentaje",
   "PERCENTAGE": "Porcentaje",
   "ABSOLUTE": "Absoluto",
   "ABSOLUTE": "Absoluto",
-  "PRESERVE_ASPECT_RATIO": "Conservar la relación de aspecto",
-  "ANCHOR_POINT": "Punto de anclaje",
-  "RESIZE_IMAGE": "Cambiar el tamaño de la imagen",
-  "RESIZE": "Cambie el tamaño de",
-  "DOCUMENTATION": "Documentacion",
-  "WEBSITE": "Pagina Web",
-  "OPEN_WEBSITE": "Abrir Pagina Web",
+  "PRESERVE_ASPECT_RATIO": "Preservar la relación de aspecto",
+  "ANCHOR_POINT": "Anclaje",
+  "RESIZE_IMAGE": "Reescalar la imagen",
+  "RESIZE": "Confirmar",
+  "DOCUMENTATION": "Documentación",
+  "WEBSITE": "Pagina web",
+  "OPEN_WEBSITE": "Abrir Pagina web",
   "REPOSITORY": "Repositorio",
   "REPOSITORY": "Repositorio",
   "OPEN_REPOSITORY": "Abrir Repositorio",
   "OPEN_REPOSITORY": "Abrir Repositorio",
   "LICENSE": "Licencia",
   "LICENSE": "Licencia",
-  "OPEN_LICENSE": "Abrir Licencia",
+  "OPEN_LICENSE": "Abrir licencia",
   "THIRD_PARTY_LICENSES": "Licencias de terceros",
   "THIRD_PARTY_LICENSES": "Licencias de terceros",
   "OPEN_THIRD_PARTY_LICENSES": "Abrir licencias de terceros",
   "OPEN_THIRD_PARTY_LICENSES": "Abrir licencias de terceros",
   "APPLY_TRANSFORM": "Aplicar transformacion",
   "APPLY_TRANSFORM": "Aplicar transformacion",
-  "INCREASE_TOOL_SIZE": "Aumentar el tamaño de la herramienta",
-  "DECREASE_TOOL_SIZE": "Disminuir el tamaño de la herramienta",
-  "DOWNLOADING_UPDATE": "Instalando actualizacion...",
-  "UPDATE_READY": "Actualización listo para instalar. ¿Desea instalarlo ahora?",
-  "NEW_UPDATE": "Nueva actualizacion",
-  "COULD_NOT_UPDATE_WITHOUT_ADMIN": "No se ha podido actualizar sin privilegios de administrador. Por favor, ejecute PixiEditor como administrador.",
+  "INCREASE_TOOL_SIZE": "Agrandar la herramienta",
+  "DECREASE_TOOL_SIZE": "Desagrandar la herramienta",
+  "DOWNLOADING_UPDATE": "Descargando actualización...",
+  "UPDATE_READY": "La actualización está lista para instalar. ¿Quieres instalarla ahora?",
+  "NEW_UPDATE": "Nueva actualización",
+  "COULD_NOT_UPDATE_WITHOUT_ADMIN": "No se puede actualizar sin privilegios de administrador. Ejecuta PixiEditor como administrador.",
   "INSUFFICIENT_PERMISSIONS": "Permisos insuficientes",
   "INSUFFICIENT_PERMISSIONS": "Permisos insuficientes",
-  "UPDATE_CHECK_FAILED": "Error en la comprobación de la actualización",
-  "COULD_NOT_CHECK_FOR_UPDATES": "No se ha podido comprobar si hay una actualización disponible.",
-  "VERSION": "Version {0}",
+  "UPDATE_CHECK_FAILED": "Error en detección de actualizaciones",
+  "COULD_NOT_CHECK_FOR_UPDATES": "No se pudo detectar si hay actualizaciones.",
+  "VERSION": "Versión {0}",
   "OPEN_TEMP_DIR": "Abrir directorio temporal",
   "OPEN_TEMP_DIR": "Abrir directorio temporal",
-  "OPEN_LOCAL_APPDATA_DIR": "Abra el directorio local AppData",
-  "OPEN_ROAMING_APPDATA_DIR": "Abrir el directorio Roaming AppData",
+  "OPEN_LOCAL_APPDATA_DIR": "Abra la carpeta Local AppData",
+  "OPEN_ROAMING_APPDATA_DIR": "Abrir la carpeta Roaming AppData",
   "OPEN_INSTALLATION_DIR": "Abrir el directorio de instalación",
   "OPEN_INSTALLATION_DIR": "Abrir el directorio de instalación",
-  "DUMP_ALL_COMMANDS": "Volcar todos los comandos",
-  "DUMP_ALL_COMMANDS_DESCRIPTIVE": "Volcar todos los comandos a un archivo de texto",
-  "CRASH": "Error",
-  "CRASH_APP": "Error en la aplicacion",
-  "DELETE_USR_PREFS": "Eliminar las preferencias del usuario (Roaming AppData)",
+  "DUMP_ALL_COMMANDS": "Exportar todos los comandos",
+  "DUMP_ALL_COMMANDS_DESCRIPTIVE": "Exportar todos los comandos a un archivo de texto",
+  "CRASH": "Forzar cierre",
+  "CRASH_APP": "Forzar el cierre de la aplicación",
+  "DELETE_USR_PREFS": "Eliminar las preferencias de usuario (Roaming AppData)",
   "DELETE_SHORTCUT_FILE": "Eliminar el archivo de acceso directo (Roaming AppData)",
   "DELETE_SHORTCUT_FILE": "Eliminar el archivo de acceso directo (Roaming AppData)",
   "DELETE_EDITOR_DATA": "Eliminar datos del editor (Local AppData)",
   "DELETE_EDITOR_DATA": "Eliminar datos del editor (Local AppData)",
-  "GENERATE_KEY_BINDINGS_TEMPLATE": "Generar plantilla de enlaces de teclas",
-  "GENERATE_KEY_BINDINGS_TEMPLATE_DESCRIPTIVE": "Generar plantilla json de enlaces de claves",
-  "VALIDATE_SHORTCUT_MAP": "Validar dirección de accesos directos",
-  "VALIDATE_SHORTCUT_MAP_DESCRIPTIVE": "Validados dirección de accesos directos",
-  "VALIDATION_KEYS_NOTICE_DIALOG": "Llaves vacías: {0}\nComandos desconocidos: {1}",
+  "GENERATE_KEY_BINDINGS_TEMPLATE": "Generar plantilla de atajos del teclado",
+  "GENERATE_KEY_BINDINGS_TEMPLATE_DESCRIPTIVE": "Generar plantilla de atajos de teclado en formato JSON",
+  "VALIDATE_SHORTCUT_MAP": "Validar mapa de atajos del teclado",
+  "VALIDATE_SHORTCUT_MAP_DESCRIPTIVE": "Valida los atajos del teclado",
+  "VALIDATION_KEYS_NOTICE_DIALOG": "Claves vacías: {0}\nComandos desconocidos: {1}",
   "RESULT": "Resultado",
   "RESULT": "Resultado",
-  "CLEAR_RECENT_DOCUMENTS": "Limpiar archivos recientes",
-  "CLEAR_RECENTLY_OPENED_DOCUMENTS": "Limpiar documentos recientemente abiertos",
-  "OPEN_CMD_DEBUG_WINDOW": "Abrir caja de comandos debug de windows",
+  "CLEAR_RECENT_DOCUMENTS": "Limpiar documentos recientes",
+  "CLEAR_RECENTLY_OPENED_DOCUMENTS": "Limpiar documentos abiertos recientemente",
+  "OPEN_CMD_DEBUG_WINDOW": "Abrir ventana de comandos de depuración",
   "PATH_DOES_NOT_EXIST": "{0} no existe.",
   "PATH_DOES_NOT_EXIST": "{0} no existe.",
   "LOCATION_DOES_NOT_EXIST": "La ubicación no existe.",
   "LOCATION_DOES_NOT_EXIST": "La ubicación no existe.",
   "FILE_NOT_FOUND": "Archivo no encontrado.",
   "FILE_NOT_FOUND": "Archivo no encontrado.",
   "ARE_YOU_SURE": "¿Estas seguro?",
   "ARE_YOU_SURE": "¿Estas seguro?",
   "ARE_YOU_SURE_PATH_FULL_PATH": "¿Está seguro de que desea borrar {0}?\nEstos datos se perderán para todas las instalaciones.\n(Ruta completa: {1})",
   "ARE_YOU_SURE_PATH_FULL_PATH": "¿Está seguro de que desea borrar {0}?\nEstos datos se perderán para todas las instalaciones.\n(Ruta completa: {1})",
-  "FAILED_TO_OPEN_FILE": "Error al abrir el archivo",
-  "OLD_FILE_FORMAT": "Formato de archivo viejo",
+  "FAILED_TO_OPEN_FILE": "No se pudo abrir el archivo",
+  "OLD_FILE_FORMAT": "Formato de archivo antiguo",
   "OLD_FILE_FORMAT_DESCRIPTION": "Este archivo .pixi utiliza el formato antiguo,\nque ya no es compatible y no puede abrirse.",
   "OLD_FILE_FORMAT_DESCRIPTION": "Este archivo .pixi utiliza el formato antiguo,\nque ya no es compatible y no puede abrirse.",
   "NOTHING_FOUND": "Nada encontrado",
   "NOTHING_FOUND": "Nada encontrado",
   "EXPORT": "Exportar",
   "EXPORT": "Exportar",
   "EXPORT_IMAGE": "Exportar imagen",
   "EXPORT_IMAGE": "Exportar imagen",
   "IMPORT": "Importar",
   "IMPORT": "Importar",
-  "SHORTCUT_TEMPLATES": "Plantillas de acceso directo",
-  "RESET_ALL": "Reiniciar todo",
+  "SHORTCUT_TEMPLATES": "Plantillas de atajos del teclado",
+  "RESET_ALL": "Restaurar todos",
   "LAYER": "Capa",
   "LAYER": "Capa",
   "LAYER_DELETE_SELECTED": "Borrar capa/carpeta activa",
   "LAYER_DELETE_SELECTED": "Borrar capa/carpeta activa",
   "LAYER_DELETE_SELECTED_DESCRIPTIVE": "Borrar capa o carpeta activa",
   "LAYER_DELETE_SELECTED_DESCRIPTIVE": "Borrar capa o carpeta activa",
   "LAYER_DELETE_ALL_SELECTED": "Eliminar todas las capas/carpetas seleccionadas",
   "LAYER_DELETE_ALL_SELECTED": "Eliminar todas las capas/carpetas seleccionadas",
   "LAYER_DELETE_ALL_SELECTED_DESCRIPTIVE": "Eliminar todas las capas y/o carpetas seleccionadas",
   "LAYER_DELETE_ALL_SELECTED_DESCRIPTIVE": "Eliminar todas las capas y/o carpetas seleccionadas",
-  "DELETE_SELECTED_PIXELS": "Borrar píxeles seleccionados",
+  "DELETE_SELECTED_PIXELS": "Eliminar pixeles seleccionados",
   "NEW_FOLDER": "Nueva carpeta",
   "NEW_FOLDER": "Nueva carpeta",
   "CREATE_NEW_FOLDER": "Crear nueva carpeta",
   "CREATE_NEW_FOLDER": "Crear nueva carpeta",
   "NEW_LAYER": "Nueva capa",
   "NEW_LAYER": "Nueva capa",
   "CREATE_NEW_LAYER": "Crear nueva capa",
   "CREATE_NEW_LAYER": "Crear nueva capa",
-  "NEW_IMAGE": "Imagen nueva",
-  "CREATE_NEW_IMAGE": "Crear imagen nueva",
+  "NEW_IMAGE": "Nueva imagen",
+  "CREATE_NEW_IMAGE": "Crear nueva imagen",
   "SAVE": "Guardar",
   "SAVE": "Guardar",
   "SAVE_AS": "Guardar como...",
   "SAVE_AS": "Guardar como...",
   "IMAGE": "Imagen",
   "IMAGE": "Imagen",
   "SAVE_IMAGE": "Guardar imagen",
   "SAVE_IMAGE": "Guardar imagen",
-  "SAVE_IMAGE_AS": "Guardar imagen como nuevo",
+  "SAVE_IMAGE_AS": "Guardar imagen como nuevo archivo",
   "DUPLICATE": "Duplicar",
   "DUPLICATE": "Duplicar",
   "DUPLICATE_SELECTED_LAYER": "Duplicar capa selecionada",
   "DUPLICATE_SELECTED_LAYER": "Duplicar capa selecionada",
-  "CREATE_MASK": "Crear mascara",
-  "DELETE_MASK": "Borrar mascara",
-  "TOGGLE_MASK": "Máscara de conmutación",
-  "APPLY_MASK": "Aplicar mascara",
+  "CREATE_MASK": "Crear máscara",
+  "DELETE_MASK": "Borrar máscara",
+  "TOGGLE_MASK": "Alternar máscara",
+  "APPLY_MASK": "Aplicar máscara",
   "TOGGLE_VISIBILITY": "Alternar visibilidad",
   "TOGGLE_VISIBILITY": "Alternar visibilidad",
-  "MOVE_MEMBER_UP": "Desplazar al miembro hacia arriba",
-  "MOVE_MEMBER_UP_DESCRIPTIVE": "Mover hacia arriba la capa o carpeta seleccionada",
-  "MOVE_MEMBER_DOWN": "Desplazar el miembro hacia abajo",
-  "MOVE_MEMBER_DOWN_DESCRIPTIVE": "Mover hacia abajo la capa o carpeta seleccionada",
-  "MERGE_ALL_SELECTED_LAYERS": "Fusionar todas las capas seleccionadas",
-  "MERGE_WITH_ABOVE": "Fusionar la capa seleccionada con la anterior",
-  "MERGE_WITH_ABOVE_DESCRIPTIVE": "Fusionar la capa seleccionada con la superior",
-  "MERGE_WITH_BELOW": "Fusionar la capa seleccionada con la de abajo",
-  "MERGE_WITH_BELOW_DESCRIPTIVE": "Fusionar la capa seleccionada con la inferior",
+  "MOVE_MEMBER_UP": "Mover miembro hacia arriba",
+  "MOVE_MEMBER_UP_DESCRIPTIVE": "Mover la capa o carpeta seleccionada hacia arriba",
+  "MOVE_MEMBER_DOWN": "Desplazar miembro hacia abajo",
+  "MOVE_MEMBER_DOWN_DESCRIPTIVE": "Mover la capa o carpeta seleccionada hacia abajo",
+  "MERGE_ALL_SELECTED_LAYERS": "Combinar todas las capas seleccionadas",
+  "MERGE_WITH_ABOVE": "Combinar la capa seleccionada con la de arriba",
+  "MERGE_WITH_ABOVE_DESCRIPTIVE": "Combinar la capa seleccionada con la que esté por encima",
+  "MERGE_WITH_BELOW": "Combinar la capa seleccionada con la de abajo",
+  "MERGE_WITH_BELOW_DESCRIPTIVE": "Combinar la capa seleccionada con la inferior",
   "ADD_REFERENCE_LAYER": "Añadir capa de referencia",
   "ADD_REFERENCE_LAYER": "Añadir capa de referencia",
   "DELETE_REFERENCE_LAYER": "Eliminar capa de referencia",
   "DELETE_REFERENCE_LAYER": "Eliminar capa de referencia",
   "TRANSFORM_REFERENCE_LAYER": "Transformar capa de referencia",
   "TRANSFORM_REFERENCE_LAYER": "Transformar capa de referencia",
-  "TOGGLE_REFERENCE_LAYER_POS": "Conmutar la posición de la capa de referencia",
-  "TOGGLE_REFERENCE_LAYER_POS_DESCRIPTIVE": "Conmutar la capa de referencia entre la superior o la inferior",
+  "TOGGLE_REFERENCE_LAYER_POS": "Alternar la posición de la capa de referencia",
+  "TOGGLE_REFERENCE_LAYER_POS_DESCRIPTIVE": "Configurar el si la capa de referencia se encuentra arriba o abajo de todo",
   "RESET_REFERENCE_LAYER_POS": "Restablecer la posición de la capa de referencia",
   "RESET_REFERENCE_LAYER_POS": "Restablecer la posición de la capa de referencia",
-  "CLIP_CANVAS": "Adjuntar Canvas",
-  "FLIP_IMG_VERTICALLY": "Voltear imagen verticalmente",
-  "FLIP_IMG_HORIZONTALLY": "Voltear imagen horizontalmente",
-  "FLIP_LAYERS_VERTICALLY": "Voltear verticalmente las capas seleccionadas",
-  "FLIP_LAYERS_HORIZONTALLY": "Voltear horizontalmente las capas seleccionadas",
-  "ROT_IMG_90": "Girar la imagen 90 grados",
-  "ROT_IMG_180": "Girar la imagen 180 grados",
-  "ROT_IMG_-90": "Girar la imagen -90 grados",
-  "ROT_LAYERS_90": "Girar 90 grados las capas seleccionadas",
-  "ROT_LAYERS_180": "Girar 180 grados las capas seleccionadas",
-  "ROT_LAYERS_-90": "Girar -90 grados las capas seleccionadas",
-  "TOGGLE_VERT_SYMMETRY_AXIS": "Conmutar el eje de simetría vertical",
-  "TOGGLE_HOR_SYMMETRY_AXIS": "Conmutar el eje de simetría horizontal",
-  "RESIZE_DOCUMENT": "Cambiar el tamaño del documento",
-  "RESIZE_CANVAS": "Cambiar el tamaño del canvas",
-  "CENTER_CONTENT": "Contenido central",
+  "CLIP_CANVAS": "Recortar lienzo",
+  "FLIP_IMG_VERTICALLY": "Espejar imagen verticalmente",
+  "FLIP_IMG_HORIZONTALLY": "Espejar imagen horizontalmente",
+  "FLIP_LAYERS_VERTICALLY": "Espejar verticalmente las capas seleccionadas",
+  "FLIP_LAYERS_HORIZONTALLY": "Espejar horizontalmente las capas seleccionadas",
+  "ROT_IMG_90": "Rotar la imagen 90 grados",
+  "ROT_IMG_180": "Rotar la imagen 180 grados",
+  "ROT_IMG_-90": "Rotar la imagen -90 grados",
+  "ROT_LAYERS_90": "Rotar las capas seleccionadas 90 grados",
+  "ROT_LAYERS_180": "Rotar las capas seleccionadas 180 grados",
+  "ROT_LAYERS_-90": "Rotar las capas seleccionadas -90 grados",
+  "TOGGLE_VERT_SYMMETRY_AXIS": "Alternar el eje de simetría vertical",
+  "TOGGLE_HOR_SYMMETRY_AXIS": "Alternar el eje de simetría horizontal",
+  "RESIZE_DOCUMENT": "Reescalar documento",
+  "RESIZE_CANVAS": "Reescalar lienzo",
+  "CENTER_CONTENT": "Centrar contenido",
   "CUT": "Cortar",
   "CUT": "Cortar",
   "CUT_DESCRIPTIVE": "Cortar el área o las capas seleccionadas",
   "CUT_DESCRIPTIVE": "Cortar el área o las capas seleccionadas",
   "PASTE": "Pegar",
   "PASTE": "Pegar",
@@ -167,20 +167,20 @@
   "COPY": "Copiar",
   "COPY": "Copiar",
   "COPY_DESCRIPTIVE": "Copiar al portapapeles",
   "COPY_DESCRIPTIVE": "Copiar al portapapeles",
   "COPY_COLOR_HEX": "Copiar color primario (HEX)",
   "COPY_COLOR_HEX": "Copiar color primario (HEX)",
-  "COPY_COLOR_HEX_DESCRIPTIVE": "Copiar color primario como código HEX",
+  "COPY_COLOR_HEX_DESCRIPTIVE": "Copiar color primario como código hexadecimal",
   "COPY_COLOR_RGB": "Copiar color primario (RGB)",
   "COPY_COLOR_RGB": "Copiar color primario (RGB)",
   "COPY_COLOR_RGB_DESCRIPTIVE": "Copiar color primario como código RGB",
   "COPY_COLOR_RGB_DESCRIPTIVE": "Copiar color primario como código RGB",
   "COPY_COLOR_SECONDARY_HEX": "Copiar color secundario (HEX)",
   "COPY_COLOR_SECONDARY_HEX": "Copiar color secundario (HEX)",
-  "COPY_COLOR_SECONDARY_HEX_DESCRIPTIVE": "Copiar color secundario como código HEX",
+  "COPY_COLOR_SECONDARY_HEX_DESCRIPTIVE": "Copiar color secundario como código hexadecimal",
   "COPY_COLOR_SECONDARY_RGB": "Copiar color secundario (RGB)",
   "COPY_COLOR_SECONDARY_RGB": "Copiar color secundario (RGB)",
   "COPY_COLOR_SECONDARY_RGB_DESCRIPTIVE": "Copiar color secundario como código RGB",
   "COPY_COLOR_SECONDARY_RGB_DESCRIPTIVE": "Copiar color secundario como código RGB",
   "PALETTE_COLORS": "Colores de la paleta",
   "PALETTE_COLORS": "Colores de la paleta",
-  "REPLACE_SECONDARY_BY_PRIMARY": "Sustituir el color secundario por el primario",
+  "REPLACE_SECONDARY_BY_PRIMARY": "Reemplazar el color secundario por el primario",
   "REPLACE_SECONDARY_BY_PRIMARY_DESCRIPTIVE": "Sustituye el color secundario por el primario",
   "REPLACE_SECONDARY_BY_PRIMARY_DESCRIPTIVE": "Sustituye el color secundario por el primario",
   "REPLACE_PRIMARY_BY_SECONDARY": "Sustituir el color primario por el secundario",
   "REPLACE_PRIMARY_BY_SECONDARY": "Sustituir el color primario por el secundario",
   "REPLACE_PRIMARY_BY_SECONDARY_DESCRIPTIVE": "Sustituir el color primario por el secundario",
   "REPLACE_PRIMARY_BY_SECONDARY_DESCRIPTIVE": "Sustituir el color primario por el secundario",
   "OPEN_PALETTE_BROWSER": "Abrir el navegador de paletas",
   "OPEN_PALETTE_BROWSER": "Abrir el navegador de paletas",
-  "OVERWRITE_PALETTE_CONSENT": "La paleta '{0}' ya existe, ¿desea sobrescribirla?",
+  "OVERWRITE_PALETTE_CONSENT": "La paleta '{0}' ya existe, ¿deseas sobrescribirla?",
   "PALETTE_EXISTS": "La paleta ya existe",
   "PALETTE_EXISTS": "La paleta ya existe",
   "REPLACE_PALETTE_CONSENT": "¿Reemplazar la paleta actual por la seleccionada?",
   "REPLACE_PALETTE_CONSENT": "¿Reemplazar la paleta actual por la seleccionada?",
   "REPLACE_PALETTE": "Sustituir la paleta actual",
   "REPLACE_PALETTE": "Sustituir la paleta actual",
@@ -194,11 +194,11 @@
   "SELECT_COLOR_8": "Seleccionar color  8",
   "SELECT_COLOR_8": "Seleccionar color  8",
   "SELECT_COLOR_9": "Seleccionar color 9",
   "SELECT_COLOR_9": "Seleccionar color 9",
   "SELECT_COLOR_10": "Seleccionar color 10",
   "SELECT_COLOR_10": "Seleccionar color 10",
-  "SELECT_TOOL": "Seleccionar {0} Herramienta",
-  "SELECT_COLOR_1_DESCRIPTIVE": "Seleccione el primer color de la paleta",
-  "SELECT_COLOR_2_DESCRIPTIVE": "Seleccione el segundo color de la paleta",
-  "SELECT_COLOR_3_DESCRIPTIVE": "Seleccione el tercero color de la paleta",
-  "SELECT_COLOR_4_DESCRIPTIVE": "Seleccione el cuarto color de la paleta",
+  "SELECT_TOOL": "Seleccionar la herramienta de {0}",
+  "SELECT_COLOR_1_DESCRIPTIVE": "Seleccione el primer color en la paleta",
+  "SELECT_COLOR_2_DESCRIPTIVE": "Seleccione el segundo color en la paleta",
+  "SELECT_COLOR_3_DESCRIPTIVE": "Seleccione el tercero color en la paleta",
+  "SELECT_COLOR_4_DESCRIPTIVE": "Seleccione el cuarto color en la paleta",
   "SELECT_COLOR_5_DESCRIPTIVE": "Seleccione el quinto color de la paleta",
   "SELECT_COLOR_5_DESCRIPTIVE": "Seleccione el quinto color de la paleta",
   "SELECT_COLOR_6_DESCRIPTIVE": "Seleccione el sexto color de la paleta",
   "SELECT_COLOR_6_DESCRIPTIVE": "Seleccione el sexto color de la paleta",
   "SELECT_COLOR_7_DESCRIPTIVE": "Seleccione el séptimo color de la paleta",
   "SELECT_COLOR_7_DESCRIPTIVE": "Seleccione el séptimo color de la paleta",
@@ -543,5 +543,11 @@
   "SOURCE_UNSET_OR_MISSING": "Fuente ausente/desactivada",
   "SOURCE_UNSET_OR_MISSING": "Fuente ausente/desactivada",
   "SOURCE_NEWER": "Fuente más reciente",
   "SOURCE_NEWER": "Fuente más reciente",
   "SOURCE_UP_TO_DATE": "La fuente está actualizada",
   "SOURCE_UP_TO_DATE": "La fuente está actualizada",
-  "SOURCE_OLDER": "Nube más reciente"
+  "SOURCE_OLDER": "Nube más reciente",
+  "NEWS": "Noticias",
+  "DISABLE_NEWS_PANEL": "No mostrar el panel de noticias en la ventana de bienvenida",
+  "FAILED_FETCH_NEWS": "No se pudieron cargar las noticias",
+  "CRASH_NOT_ALL_DOCUMENTS_RECOVERED_TITLE": "Pérdida de documentos tras colapso",
+  "CRASH_NOT_ALL_DOCUMENTS_RECOVERED": "No todos los documentos pudieron ser recuperados tras un cierre inesperado. Metele ganas a guardar tu trabajo.",
+  "EXAMPLE_FILES": "Archivos de ejemplo"
 }
 }

+ 131 - 1
src/PixiEditor/Data/Localization/Languages/ru.json

@@ -678,5 +678,135 @@
   "REMOVE_CLOSE_POINTS": "Удалить близкие точки",
   "REMOVE_CLOSE_POINTS": "Удалить близкие точки",
   "RASTERIZE_SHAPE": "Растрировать фигуру",
   "RASTERIZE_SHAPE": "Растрировать фигуру",
   "MODE": "Режим",
   "MODE": "Режим",
-  "Factor": "Степень"
+  "Factor": "Степень",
+  "SEND": "Отправить отчет",
+  "INPUT": "Ввод",
+  "CHANNELS_DOCK_TITLE": "Каналы",
+  "COORDINATE": "Координата",
+  "NOISE": "Шум",
+  "PIXEL_COORDINATE": "Координаты пикселя",
+  "MODIFY_IMAGE_PAIR_NODE": "Редактировать изображение",
+  "WARMING_UP": "Разогреваемся",
+  "RENDERING_FRAME": "Генерация кадра {0}/{1}",
+  "RENDERING_VIDEO": "Рендеринг видео",
+  "GENERATING_SPRITE_SHEET": "Создание атласа спрайтов",
+  "PROBABILITY": "Вероятность",
+  "STARS_EXAMPLE": "Звёзды",
+  "ADD_EMPTY_FRAME": "Создать пустой кадр",
+  "DUPLICATE_FRAME": "Дублировать кадр",
+  "DELETE_FRAME": "Удалить кадр",
+  "NO_PARSER_FOUND": "Не найден парсер файлов для расширения '{0}'",
+  "SELECT_FILE_FORMAT": "Выберите формат файла",
+  "ISLAND_EXAMPLE": "Острова",
+  "SHAPE": "Фигура",
+  "STRUCTURE": "Структура",
+  "NUMBERS": "Числа",
+  "OPERATIONS": "Операции",
+  "GENERATION": "Генерация",
+  "NUMBER": "Число",
+  "ANIMATION": "Анимация",
+  "SAMPLE_IMAGE": "Цвет с изображения",
+  "POSITION": "Позиция",
+  "MATH_ADD": "Сложение",
+  "MATH_SUBTRACT": "Вычитание",
+  "MULTIPLY": "Умножение",
+  "DIVIDE": "Деление",
+  "SIN": "Синус",
+  "COS": "Косинус",
+  "TAN": "Тангенс",
+  "PIXEL_ART_TOOLSET": "Пиксель-арт",
+  "VECTOR_TOOLSET": "Векторная графика",
+  "VECTOR_LAYER": "Векторный слой",
+  "STROKE_COLOR_LABEL": "Контур",
+  "SYNC_WITH_PRIMARY_COLOR_LABEL": "Синхронизировать с основным цветом",
+  "RASTERIZE_ACTIVE_LAYER_DESCRIPTIVE": "Преобразовать/растеризовать выбранный слой в растровый слой (в изображение)",
+  "NEW_ELLIPSE_LAYER_NAME": "Овал",
+  "NEW_RECTANGLE_LAYER_NAME": "Прямоугольник",
+  "NEW_LINE_LAYER_NAME": "Линия",
+  "PAINT_TOOLSET": "Рисование",
+  "HARDNESS_SETTING": "Жесткость",
+  "SPACING_SETTING": "Промежуток",
+  "ANTI_ALIASING_SETTING": "Сглаживание",
+  "TOLERANCE_LABEL": "Допуск",
+  "HIGH_RES_PREVIEW": "Предварительный просмотр в высоком разрешении",
+  "LOW_RES_PREVIEW": "Предварительный просмотр в разрешении документа",
+  "TOGGLE_HIGH_RES_PREVIEW": "Вкл/выкл предварительный просмотр в высоком разрешении",
+  "PATH_TOOL": "Кривая",
+  "PATH_TOOL_TOOLTIP": "Создание векторных кривых ({0}).",
+  "PATH_TOOL_ACTION_DISPLAY": "Клик для добавления новой точки",
+  "PATH_TOOL_ACTION_DISPLAY_CTRL": "Потяните за точку для создания кривой. Кликните точку для выделения.",
+  "PATH_TOOL_ACTION_DISPLAY_SHIFT": "Кликните для создания нового слоя.",
+  "PATH_TOOL_ACTION_DISPLAY_CTRL_SHIFT": "Кликните точку чтобы добавить её в выделение",
+  "PATH_TOOL_ACTION_DISPLAY_ALT": "Потяните за контрольную точку для изменения кривой только с одной стороны.",
+  "DEFAULT_PATH_LAYER_NAME": "Кривая",
+  "DELETE_NODES": "Удалить ноды",
+  "DELETE_NODES_DESCRIPTIVE": "Удалить выбранные ноды",
+  "DELETE_CELS": "Удалить кадры",
+  "DELETE_CELS_DESCRIPTIVE": "Удалить выбранные кадры",
+  "COPY_COLOR_TO_CLIPBOARD": "Скопировать цвет в буфер обмена",
+  "VIEWPORT_ROTATION": "Поворот камеры",
+  "NEXT_TOOL_SET": "Следующий набор инструментов",
+  "PREVIOUS_TOOL_SET": "Предыдущий набор инструментов",
+  "FILL_MODE": "Режим заливки",
+  "USE_LINEAR_SRGB_PROCESSING": "Использовать линейный sRGB при смешивании цветов",
+  "USE_LINEAR_SRGB_PROCESSING_DESC": "Включить линейное смешивание цветов в этом документе. После включения цвета будут смешиваться более естественно, но внешний вид изображения может измениться.",
+  "STROKE_CAP": "Конец контура",
+  "STROKE_JOIN": "Стык контура",
+  "COPY_VISIBLE": "Копировать видимое",
+  "COPY_VISIBLE_DESCRIPTIVE": "Копировать видимые пиксели",
+  "CREATE_CEL": "Создать кадр",
+  "CREATE_CEL_DESCRIPTIVE": "Создать новый кадр",
+  "DUPLICATE_CEL": "Дублировать кадр",
+  "DUPLICATE_CEL_DESCRIPTIVE": "Дублировать выбранный кадр",
+  "RENDER_PREVIEW": "Предварительный просмотр",
+  "TOGGLE_PLAY": "Плей/пауза анимации",
+  "OPEN_PREVIEW_WINDOW": "Открыть окно предварительного просмотра",
+  "PREVIEW_TITLE": "Предварительный просмотр",
+  "GREATER_THAN": "Больше",
+  "LESS_THAN": "Меньше",
+  "LESS_THAN_OR_EQUAL": "Меньше или равно",
+  "COMPARE": "Сравнить",
+  "MATH_POWER": "Степень",
+  "LOGARITHM": "Логарифм",
+  "ROOT": "Корень",
+  "COPY_NODES_DESCRIPTIVE": "Копировать выбранные ноды",
+  "PASTE_NODES": "Вставить ноды",
+  "PASTE_NODES_DESCRIPTIVE": "Вставить скопированные ноды",
+  "COPY_CELS": "Копировать кадры",
+  "COPY_CELS_DESCRIPTIVE": "Копировать выбранные кадры",
+  "VALUE": "Значение",
+  "PRESERVE_ALPHA": "Не затрагивать alpha",
+  "BLUR_FILTER_NODE": "Размытие по гауссу",
+  "LENGTH": "Длина",
+  "GREATER_THAN_OR_EQUAL": "Больше или равно",
+  "WEBP_FILE": "Изображения WebP",
+  "COLOR_NODE": "Цвет",
+  "CONVERT_TO_CURVE": "Преобразовать в кривую",
+  "CONVERT_TO_CURVE_DESCRIPTIVE": "Преобразовать выбранный векторный слой в кривую",
+  "FONT_FILES": "Файлы шрифтов",
+  "UNIT_PT": "тч",
+  "FONT_LABEL": "Шрифт",
+  "FONT_SIZE_LABEL": "Размер",
+  "SPACING_LABEL": "Интервал",
+  "TEXT_TOOL": "Текст",
+  "MISSING_FONT": "Отсутствует шрифт",
+  "TEXT_LAYER_NAME": "Текст",
+  "TEXT_TOOL_TOOLTIP": "Добавить текст ({0}).",
+  "BOLD_TOOLTIP": "Жирный",
+  "ITALIC_TOOLTIP": "Курсив",
+  "STRING_OPEN_IN_FOLDER": "Показать в проводнике",
+  "DISCO_BALL_EXAMPLE": "Диско-шар",
+  "COLOR_SPACE": "Пространство цветов",
+  "PHOTO_EXAMPLES": "Фотография",
+  "MASK_EXAMPLE": "Маска",
+  "SHADOW_NODE": "Тень",
+  "INPUT_MATRIX": "Входная матрица",
+  "OUTPUT_MATRIX": "Выходная матрица",
+  "CENTER": "Центр",
+  "CONTENT_OFFSET": "Отступ содержимого",
+  "CANVAS_POSITION": "Позиция холста",
+  "ROTATION_NODE": "Поворот",
+  "SCALE_NODE": "Масштабирование",
+  "ROTATE_NODE": "Поворот",
+  "HUE_VALUE": "Тон"
 }
 }

+ 1127 - 0
src/PixiEditor/Data/Localization/Languages/tr.json

@@ -0,0 +1,1127 @@
+{
+  "RECENT_FILES": "Son Dosyalar",
+  "OPEN_FILE": "Dosya Aç",
+  "NEW_FILE": "Yeni",
+  "RECENT_EMPTY_TEXT": "Çok fazla boş alan",
+  "LANGUAGE": "Dil",
+  "GENERAL": "Genel",
+  "DISCORD": "Discord",
+  "KEY_BINDINGS": "Klavye Kısayolları",
+  "MISC": "Çeşitli",
+  "SHOW_STARTUP_WINDOW": "Başlangıç Penceresini Göster",
+  "RECENT_FILE_LENGTH": "Son dosyalar listesi uzunluğu",
+  "RECENT_FILE_LENGTH_TOOLTIP": "Dosya > Son Kullanılanlar altında kaç belgenin gösterileceği. Varsayılan: 8",
+  "DEFAULT_NEW_SIZE": "Varsayılan yeni dosya boyutu",
+  "WIDTH": "Genişlik",
+  "HEIGHT": "Yükseklik",
+  "TOOLS": "Araçlar",
+  "ENABLE_SHARED_TOOLBAR": "Paylaşılan araç çubuğunu etkinleştir",
+  "AUTOMATIC_UPDATES": "Otomatik Güncellemeler",
+  "CHECK_FOR_UPDATES": "Başlangıçta güncellemeleri kontrol et",
+  "UPDATE_STREAM": "Güncelleme akışı",
+  "UPDATE_CHANNEL_HELP_TOOLTIP": "Güncelleme kanalları yalnızca bağımsız sürümde değiştirilebilir (https://pixieditor.net adresinden indirilir).\nSteam ve Microsoft Store sürümleri güncellemeleri ayrı olarak yönetir.",
+  "DEBUG": "Hata Ayıklama",
+  "ENABLE_DEBUG_MODE": "Hata ayıklama modunu etkinleştir",
+  "OPEN_CRASH_REPORTS_DIR": "Kilitlenme raporları dizinini aç",
+  "DISCORD_RICH_PRESENCE": "Rich Presence",
+  "ENABLED": "Etkin",
+  "SHOW_IMAGE_NAME": "Resim adını göster",
+  "SHOW_IMAGE_SIZE": "Resim boyutunu göster",
+  "SHOW_LAYER_COUNT": "Katman sayısını göster",
+  "FILE": "Dosya",
+  "RECENT": "Son Kullanılanlar",
+  "OPEN": "Aç",
+  "SAVE_PIXI": "Kaydet (.pixi)",
+  "SAVE_AS_PIXI": "Farklı Kaydet... (.pixi)",
+  "EXPORT_IMG": "Dışa Aktar (.png, .jpg, vb.)",
+  "EDIT": "Düzenle",
+  "EXIT": "Çıkış",
+  "PERCENTAGE": "Yüzde",
+  "ABSOLUTE": "Mutlak",
+  "PRESERVE_ASPECT_RATIO": "En boy oranını koru",
+  "ANCHOR_POINT": "Bağlantı noktası",
+  "RESIZE_IMAGE": "Resmi yeniden boyutlandır",
+  "RESIZE": "Yeniden Boyutlandır",
+  "DOCUMENTATION": "Belgelendirme",
+  "WEBSITE": "Web Sitesi",
+  "OPEN_WEBSITE": "Web sitesini aç",
+  "REPOSITORY": "Depo",
+  "OPEN_REPOSITORY": "Depoyu aç",
+  "OPEN_DOCUMENTATION": "Belgelendirmeyi aç",
+  "LICENSE": "Lisans",
+  "OPEN_LICENSE": "Lisansı aç",
+  "THIRD_PARTY_LICENSES": "Üçüncü taraf lisansları",
+  "OPEN_THIRD_PARTY_LICENSES": "Üçüncü taraf lisanslarını aç",
+  "APPLY_TRANSFORM": "Dönüşümü uygula",
+  "INCREASE_TOOL_SIZE": "Araç boyutunu artır",
+  "DECREASE_TOOL_SIZE": "Araç boyutunu azalt",
+  "UPDATE_READY": "Güncelleme yüklenmeye hazır. Şimdi yüklemek ister misiniz?",
+  "NEW_UPDATE": "Yeni güncelleme",
+  "COULD_NOT_UPDATE_WITHOUT_ADMIN": "Yönetici ayrıcalıkları olmadan güncelleme yapılamadı. Lütfen PixiEditor'ı yönetici olarak çalıştırın.",
+  "INSUFFICIENT_PERMISSIONS": "Yetersiz izinler",
+  "UPDATE_CHECK_FAILED": "Güncelleme kontrolü başarısız oldu",
+  "COULD_NOT_CHECK_FOR_UPDATES": "Yeni bir güncelleme olup olmadığı kontrol edilemedi.",
+  "VERSION": "Sürüm {0}",
+  "BUILD_ID": "Yapı ID: {0}",
+  "OPEN_TEMP_DIR": "Temp dizini aç",
+  "OPEN_LOCAL_APPDATA_DIR": "Local AppData dizinini aç",
+  "OPEN_ROAMING_APPDATA_DIR": "Roaming AppData dizinini aç",
+  "OPEN_INSTALLATION_DIR": "Kurulum dizinini aç",
+  "DUMP_ALL_COMMANDS": "Tüm komutları dök",
+  "DUMP_ALL_COMMANDS_DESCRIPTIVE": "Tüm komutları bir metin dosyasına dök",
+  "CRASH": "Çökme",
+  "CRASH_APP": "Uygulamayı çökert",
+  "DELETE_USR_PREFS": "Kullanıcı tercihlerini sil (Roaming AppData)",
+  "DELETE_SHORTCUT_FILE": "Kısayol dosyasını sil (Roaming AppData)",
+  "DELETE_EDITOR_DATA": "Editör verilerini sil (Local AppData)",
+  "GENERATE_KEY_BINDINGS_TEMPLATE": "Klavye kısayolları şablonu oluştur",
+  "GENERATE_KEY_BINDINGS_TEMPLATE_DESCRIPTIVE": "Klavye kısayolları json şablonu oluştur",
+  "VALIDATE_SHORTCUT_MAP": "Kısayol haritasını doğrula",
+  "VALIDATE_SHORTCUT_MAP_DESCRIPTIVE": "Kısayol haritasını doğrular",
+  "VALIDATION_KEYS_NOTICE_DIALOG": "Boş anahtarlar: {0}\nBilinmeyen Komutlar: {1}",
+  "RESULT": "Sonuç",
+  "CLEAR_RECENT_DOCUMENTS": "Son belgeleri temizle",
+  "CLEAR_RECENTLY_OPENED_DOCUMENTS": "Son açılan belgeleri temizle",
+  "OPEN_CMD_DEBUG_WINDOW": "Komut hata ayıklama penceresini aç",
+  "PATH_DOES_NOT_EXIST": "{0} mevcut değil.",
+  "LOCATION_DOES_NOT_EXIST": "Konum mevcut değil.",
+  "FILE_NOT_FOUND": "Dosya bulunamadı.",
+  "ARE_YOU_SURE": "Emin misiniz?",
+  "ARE_YOU_SURE_PATH_FULL_PATH": "{0} dosyasını silmek istediğinizden emin misiniz?\nBu veriler tüm kurulumlar için kaybolacaktır.\n(Tam Yol: {1})",
+  "FAILED_TO_OPEN_FILE": "Dosya açılamadı",
+  "OLD_FILE_FORMAT": "Eski dosya biçimi",
+  "OLD_FILE_FORMAT_DESCRIPTION": "Bu .pixi dosyası artık desteklenmeyen\nve açılamayan eski biçimi kullanıyor.",
+  "NOTHING_FOUND": "Hiçbir şey bulunamadı",
+  "EXPORT": "Dışa Aktar",
+  "EXPORT_IMAGE": "Resmi dışa aktar",
+  "IMPORT": "İçe Aktar",
+  "SHORTCUT_TEMPLATES": "Kısayol şablonları",
+  "RESET_ALL": "Tümünü sıfırla",
+  "LAYER": "Katman",
+  "LAYER_DELETE_SELECTED": "Aktif katmanı/klasörü sil",
+  "LAYER_DELETE_SELECTED_DESCRIPTIVE": "Aktif katmanı veya klasörü sil",
+  "LAYER_DELETE_ALL_SELECTED": "Seçili tüm katmanları/klasörleri sil",
+  "LAYER_DELETE_ALL_SELECTED_DESCRIPTIVE": "Seçili tüm katmanları ve/veya klasörleri sil",
+  "DELETE_SELECTED_PIXELS": "Seçili pikselleri sil",
+  "NEW_FOLDER": "Yeni klasör",
+  "CREATE_NEW_FOLDER": "Yeni klasör oluştur",
+  "NEW_LAYER": "Yeni katman",
+  "CREATE_NEW_LAYER": "Yeni katman oluştur",
+  "NEW_IMAGE": "Yeni resim",
+  "CREATE_NEW_IMAGE": "Yeni resim oluştur",
+  "SAVE": "Kaydet",
+  "SAVE_AS": "Farklı kaydet...",
+  "IMAGE": "Resim",
+  "SAVE_IMAGE": "Resmi kaydet",
+  "SAVE_IMAGE_AS": "Resmi yeni olarak kaydet",
+  "DUPLICATE": "Çoğalt",
+  "DUPLICATE_SELECTED_LAYER": "Seçili katmanı çoğalt",
+  "CREATE_MASK": "Maske oluştur",
+  "DELETE_MASK": "Maskeyi sil",
+  "TOGGLE_MASK": "Maskeyi aç/kapat",
+  "APPLY_MASK": "Maskeyi uygula",
+  "TOGGLE_VISIBILITY": "Görünürlüğü aç/kapat",
+  "MOVE_MEMBER_UP": "Öğeyi yukarı taşı",
+  "MOVE_MEMBER_UP_DESCRIPTIVE": "Seçili katmanı veya klasörü yukarı taşı",
+  "MOVE_MEMBER_DOWN": "Öğeyi aşağı taşı",
+  "MOVE_MEMBER_DOWN_DESCRIPTIVE": "Seçili katmanı veya klasörü aşağı taşı",
+  "MERGE_ALL_SELECTED_LAYERS": "Seçili tüm katmanları birleştir",
+  "MERGE_WITH_ABOVE": "Seçili katmanı üsttekiyle birleştir",
+  "MERGE_WITH_ABOVE_DESCRIPTIVE": "Seçili katmanı üstündekiyle birleştir",
+  "MERGE_WITH_BELOW": "Seçili katmanı alttakiyle birleştir",
+  "MERGE_WITH_BELOW_DESCRIPTIVE": "Seçili katmanı altındakiyle birleştir",
+  "ADD_REFERENCE_LAYER": "Referans Katmanı Ekle",
+  "DELETE_REFERENCE_LAYER": "Referans katmanını sil",
+  "TRANSFORM_REFERENCE_LAYER": "Referans katmanını dönüştür",
+  "TOGGLE_REFERENCE_LAYER_POS": "Referans katmanı konumunu değiştir",
+  "TOGGLE_REFERENCE_LAYER_POS_DESCRIPTIVE": "Referans katmanını en üst veya en alt arasında değiştir",
+  "RESET_REFERENCE_LAYER_POS": "Referans katmanı konumunu sıfırla",
+  "CLIP_CANVAS": "Tuvali Kırp",
+  "FLIP_IMG_VERTICALLY": "Resmi Dikey Çevir",
+  "FLIP_IMG_HORIZONTALLY": "Resmi Yatay Çevir",
+  "FLIP_LAYERS_VERTICALLY": "Seçili Katmanları Dikey Çevir",
+  "FLIP_LAYERS_HORIZONTALLY": "Seçili Katmanları Yatay Çevir",
+  "ROT_IMG_90": "Resmi 90 derece döndür",
+  "ROT_IMG_180": "Resmi 180 derece döndür",
+  "ROT_IMG_-90": "Resmi -90 derece döndür",
+  "ROT_LAYERS_90": "Seçili Katmanları 90 derece döndür",
+  "ROT_LAYERS_180": "Seçili Katmanları 180 derece döndür",
+  "ROT_LAYERS_-90": "Seçili Katmanları -90 derece döndür",
+  "TOGGLE_VERT_SYMMETRY_AXIS": "Dikey simetri eksenini aç/kapat",
+  "TOGGLE_HOR_SYMMETRY_AXIS": "Yatay simetri eksenini aç/kapat",
+  "DELETE_SELECTED": "Seçili olanı sil",
+  "DELETE_SELECTED_DESCRIPTIVE": "Seçili öğeyi sil (katman, pikseller, vb.)",
+  "RESIZE_DOCUMENT": "Belgeyi yeniden boyutlandır",
+  "RESIZE_CANVAS": "Tuvali yeniden boyutlandır",
+  "CENTER_CONTENT": "İçeriği ortala",
+  "CUT": "Kes",
+  "CUT_DESCRIPTIVE": "Seçili alanı/katmanları kes",
+  "PASTE": "Yapıştır",
+  "PASTE_DESCRIPTIVE": "Pano içeriğini yapıştır",
+  "PASTE_AS_NEW_LAYER": "Yeni katman olarak yapıştır",
+  "PASTE_AS_NEW_LAYER_DESCRIPTIVE": "Panodan yeni katman olarak yapıştır",
+  "PASTE_REFERENCE_LAYER": "Referans katmanı yapıştır",
+  "PASTE_REFERENCE_LAYER_DESCRIPTIVE": "Pano içeriğini referans katmanı olarak yapıştır",
+  "PASTE_COLOR": "Rengi yapıştır",
+  "PASTE_COLOR_DESCRIPTIVE": "Panodan rengi yapıştır",
+  "PASTE_COLOR_SECONDARY": "Rengi ikincil olarak yapıştır",
+  "PASTE_COLOR_SECONDARY_DESCRIPTIVE": "Panodan rengi ikincil renk olarak yapıştır",
+  "CLIPBOARD": "Pano",
+  "COPY": "Kopyala",
+  "COPY_DESCRIPTIVE": "Panoya kopyala",
+  "COPY_COLOR_HEX": "Birincil rengi kopyala (HEX)",
+  "COPY_COLOR_HEX_DESCRIPTIVE": "Birincil rengi HEX kodu olarak kopyala",
+  "COPY_COLOR_RGB": "Birincil rengi kopyala (RGB)",
+  "COPY_COLOR_RGB_DESCRIPTIVE": "Birincil rengi RGB kodu olarak kopyala",
+  "COPY_COLOR_SECONDARY_HEX": "İkincil rengi kopyala (HEX)",
+  "COPY_COLOR_SECONDARY_HEX_DESCRIPTIVE": "İkincil rengi HEX kodu olarak kopyala",
+  "COPY_COLOR_SECONDARY_RGB": "İkincil rengi kopyala (RGB)",
+  "COPY_COLOR_SECONDARY_RGB_DESCRIPTIVE": "İkincil rengi RGB kodu olarak kopyala",
+  "PALETTE_COLORS": "Palet Renkleri",
+  "REPLACE_SECONDARY_BY_PRIMARY": "İkincil rengi birincil ile değiştir",
+  "REPLACE_SECONDARY_BY_PRIMARY_DESCRIPTIVE": "İkincil rengi birincil renk ile değiştir",
+  "REPLACE_PRIMARY_BY_SECONDARY": "Birincil rengi ikincil ile değiştir",
+  "REPLACE_PRIMARY_BY_SECONDARY_DESCRIPTIVE": "Birincil rengi ikincil renk ile değiştir",
+  "OPEN_PALETTE_BROWSER": "Palet tarayıcısını aç",
+  "OVERWRITE_PALETTE_CONSENT": "'{0}' paleti zaten mevcut, üzerine yazmak istiyor musunuz?",
+  "PALETTE_EXISTS": "Palet zaten mevcut",
+  "REPLACE_PALETTE_CONSENT": "Mevcut paleti seçilenle değiştirmek istiyor musunuz?",
+  "REPLACE_PALETTE": "Mevcut paleti değiştir",
+  "SELECT_COLOR_1": "Renk 1'i seç",
+  "SELECT_COLOR_2": "Renk 2'yi seç",
+  "SELECT_COLOR_3": "Renk 3'ü seç",
+  "SELECT_COLOR_4": "Renk 4'ü seç",
+  "SELECT_COLOR_5": "Renk 5'i seç",
+  "SELECT_COLOR_6": "Renk 6'yı seç",
+  "SELECT_COLOR_7": "Renk 7'yi seç",
+  "SELECT_COLOR_8": "Renk 8'i seç",
+  "SELECT_COLOR_9": "Renk 9'u seç",
+  "SELECT_COLOR_10": "Renk 10'u seç",
+  "SELECT_TOOL": "{0} Aracını Seç",
+  "SELECT_COLOR_1_DESCRIPTIVE": "Paletteki ilk rengi seç",
+  "SELECT_COLOR_2_DESCRIPTIVE": "Paletteki ikinci rengi seç",
+  "SELECT_COLOR_3_DESCRIPTIVE": "Paletteki üçüncü rengi seç",
+  "SELECT_COLOR_4_DESCRIPTIVE": "Paletteki dördüncü rengi seç",
+  "SELECT_COLOR_5_DESCRIPTIVE": "Paletteki beşinci rengi seç",
+  "SELECT_COLOR_6_DESCRIPTIVE": "Paletteki altıncı rengi seç",
+  "SELECT_COLOR_7_DESCRIPTIVE": "Paletteki yedinci rengi seç",
+  "SELECT_COLOR_8_DESCRIPTIVE": "Paletteki sekizinci rengi seç",
+  "SELECT_COLOR_9_DESCRIPTIVE": "Paletteki dokuzuncu rengi seç",
+  "SELECT_COLOR_10_DESCRIPTIVE": "Paletteki onuncu rengi seç",
+  "SWAP_COLORS": "Renkleri değiştir",
+  "SWAP_COLORS_DESCRIPTIVE": "Birincil ve ikincil renkleri değiştir",
+  "SEARCH": "Ara",
+  "COMMAND_SEARCH": "Komut arama",
+  "OPEN_COMMAND_SEARCH": "Komut arama penceresini aç",
+  "SELECT": "Seç",
+  "DESELECT": "Seçimi Kaldır",
+  "INVERT": "Ters Çevir",
+  "SELECTION": "Seçim",
+  "SELECT_ALL": "Tümünü seç",
+  "SELECT_ALL_DESCRIPTIVE": "Her şeyi seç",
+  "CLEAR_SELECTION": "Seçimi temizle",
+  "INVERT_SELECTION": "Seçimi ters çevir",
+  "INVERT_SELECTION_DESCRIPTIVE": "Seçimi ters çevir",
+  "TRANSFORM_SELECTED_AREA": "Seçili alanı dönüştür",
+  "NUDGE_SELECTED_LEFT": "Seçili nesneyi sola kaydır",
+  "NUDGE_SELECTED_RIGHT": "Seçili nesneyi sağa kaydır",
+  "NUDGE_SELECTED_UP": "Seçili nesneyi yukarı kaydır",
+  "NUDGE_SELECTED_DOWN": "Seçili nesneyi aşağı kaydır",
+  "MASK_FROM_SELECTION": "Seçimden yeni maske",
+  "MASK_FROM_SELECTION_DESCRIPTIVE": "Seçimi yeni maskeye dönüştür",
+  "ADD_SELECTION_TO_MASK": "Seçimi maskeye ekle",
+  "SUBTRACT_SELECTION_FROM_MASK": "Seçimi maskeden çıkar",
+  "INTERSECT_SELECTION_MASK": "Seçimi maskeyle kesiştir",
+  "SELECTION_TO_MASK": "Seçimi maskeye",
+  "TO_NEW_MASK": "yeni maskeye",
+  "ADD_TO_MASK": "maskeye ekle",
+  "SUBTRACT_FROM_MASK": "maskeden çıkar",
+  "INTERSECT_WITH_MASK": "maskeyle kesiştir",
+  "STYLUS": "Kalem",
+  "TOGGLE_PEN_MODE": "Kalem modunu aç/kapat",
+  "UNDO": "Geri Al",
+  "UNDO_DESCRIPTIVE": "Son eylemi geri al",
+  "REDO": "Yinele",
+  "REDO_DESCRIPTIVE": "Son eylemi yinele",
+  "WINDOWS": "Pencereler",
+  "TOGGLE_GRIDLINES": "Kılavuz çizgilerini aç/kapat",
+  "GRIDLINES_SIZE": "Kılavuz Boyutu",
+  "ZOOM_IN": "Yakınlaştır",
+  "ZOOM_OUT": "Uzaklaştır",
+  "NEW_WINDOW_FOR_IMG": "Mevcut resim için yeni pencere",
+  "CENTER_ACTIVE_VIEWPORT": "Aktif görünüm alanını ortala",
+  "FLIP_VIEWPORT_HORIZONTALLY": "Görünüm alanını yatay çevir",
+  "FLIP_VIEWPORT_VERTICALLY": "Görünüm alanını dikey çevir",
+  "SETTINGS": "Ayarlar",
+  "OPEN_SETTINGS": "Ayarları aç",
+  "OPEN_SETTINGS_DESCRIPTIVE": "Ayarlar penceresini aç",
+  "OPEN_STARTUP_WINDOW": "Başlangıç penceresini aç",
+  "OPEN_SHORTCUT_WINDOW": "Kısayollar penceresini aç",
+  "OPEN_ABOUT_WINDOW": "Hakkında penceresini aç",
+  "OPEN_PREVIEW_WINDOW": "Önizleme penceresini aç",
+  "ERROR": "Hata",
+  "INTERNAL_ERROR": "İç Hata",
+  "ERROR_SAVE_LOCATION": "Dosya belirtilen konuma kaydedilemedi",
+  "ERROR_WHILE_SAVING": "Kaydederken bir iç hata oluştu. Lütfen tekrar deneyin.",
+  "UNKNOWN_ERROR_SAVING": "Kaydederken bir hata oluştu.",
+  "FAILED_ASSOCIATE_LOSPEC": "Lospec Palet protokolü ilişkilendirilemedi.",
+  "REDDIT": "Reddit",
+  "GITHUB": "GitHub",
+  "YOUTUBE": "YouTube",
+  "DONATE": "Bağış Yap",
+  "YES": "Evet",
+  "NO": "Hayır",
+  "CANCEL": "İptal",
+  "UNNAMED": "Adsız",
+  "DELETE": "Sil",
+  "USER_PREFS": "Kullanıcı tercihleri (Roaming)",
+  "SHORTCUT_FILE": "Kısayol dosyası (Roaming)",
+  "EDITOR_DATA": "Editör verileri (Local)",
+  "MOVE_VIEWPORT_TOOLTIP": "Görünüm alanını taşır. ({0})",
+  "MOVE_VIEWPORT_ACTION_DISPLAY": "Görünüm alanını kaydırmak için tıklayın ve hareket ettirin",
+  "MOVE_TOOL_TOOLTIP": "Katmanları seç ve dönüştür ({0}).",
+  "MOVE_TOOL_ACTION_DISPLAY": "Seçili pikselleri taşımak için fareyi basılı tutun. Tüm katmanları taşımak için Ctrl tuşunu basılı tutun.",
+  "PEN_TOOL_TOOLTIP": "Kalem. ({0})",
+  "PEN_TOOL_ACTION_DISPLAY": "Çizmek için tıklayın ve hareket ettirin.",
+  "PIXEL_PERFECT_SETTING": "Piksel mükemmelliği",
+  "RECTANGLE_TOOL_TOOLTIP": "Tuval üzerine dikdörtgen çizer ({0}). Kare çizmek için Shift tuşunu basılı tutun.",
+  "RECTANGLE_TOOL_ACTION_DISPLAY_DEFAULT": "Bir dikdörtgen çizmek için tıklayın ve hareket ettirin. Kare çizmek için Shift tuşunu basılı tutun.",
+  "RECTANGLE_TOOL_ACTION_DISPLAY_SHIFT": "Bir kare çizmek için tıklayın ve hareket ettirin.",
+  "KEEP_ORIGINAL_IMAGE_SETTING": "Orijinal resmi koru",
+  "ROTATE_VIEWPORT_TOOLTIP": "Görünüm alanını döndürür. ({0})",
+  "ROTATE_VIEWPORT_ACTION_DISPLAY": "Görünüm alanını döndürmek için tıklayın ve hareket ettirin",
+  "SELECT_TOOL_TOOLTIP": "Alan seçer. ({0})",
+  "SELECT_TOOL_ACTION_DISPLAY_DEFAULT": "Bir alan seçmek için tıklayın ve hareket ettirin. Mevcut seçime eklemek için Shift tuşunu basılı tutun. Ondan çıkarmak için Ctrl tuşunu basılı tutun.",
+  "SELECT_TOOL_ACTION_DISPLAY_SHIFT": "Mevcut seçime eklemek için tıklayın ve hareket ettirin.",
+  "SELECT_TOOL_ACTION_DISPLAY_CTRL": "Mevcut seçimden çıkarmak için tıklayın ve hareket ettirin.",
+  "ZOOM_TOOL_TOOLTIP": "Görünüm alanını yakınlaştırır ({0}). Yakınlaştırmak için tıklayın, uzaklaştırmak için alt tuşunu basılı tutup tıklayın.",
+  "ZOOM_TOOL_ACTION_DISPLAY_DEFAULT": "Yakınlaştırmak için tıklayın ve hareket ettirin. Yakınlaştırmak için tıklayın, uzaklaştırmak için ctrl tuşunu basılı tutup tıklayın.",
+  "ZOOM_TOOL_ACTION_DISPLAY_CTRL": "Yakınlaştırmak için tıklayın ve hareket ettirin. Uzaklaştırmak için tıklayın, yakınlaştırmak için ctrl tuşunu bırakıp tıklayın.",
+  "BRIGHTNESS_TOOL_TOOLTIP": "Pikselleri daha parlak veya daha koyu yapar ({0}). Pikselleri daha koyu yapmak için Ctrl tuşunu basılı tutun.",
+  "BRIGHTNESS_TOOL_ACTION_DISPLAY_DEFAULT": "Pikselleri daha parlak yapmak için üzerine çizin. Koyulaştırmak için Ctrl tuşunu basılı tutun.",
+  "BRIGHTNESS_TOOL_ACTION_DISPLAY_CTRL": "Pikselleri daha koyu yapmak için üzerine çizin. Parlatmak için Ctrl tuşunu bırakın.",
+  "COLOR_PICKER_TOOLTIP": "Tuvalden birincil rengi seçer. ({0})",
+  "COLOR_PICKER_ACTION_DISPLAY_DEFAULT": "Renkleri seçmek için tıklayın. Tuvali gizlemek için Ctrl tuşunu basılı tutun. Referans katmanını gizlemek için Shift tuşunu basılı tutun",
+  "ELLIPSE_TOOL_TOOLTIP": "Tuval üzerine bir elips çizer ({0}). Bir daire çizmek için Shift tuşunu basılı tutun.",
+  "ELLIPSE_TOOL_ACTION_DISPLAY_DEFAULT": "Bir elips çizmek için fareyi tıklayın ve hareket ettirin. Bir daire çizmek için Shift tuşunu basılı tutun.",
+  "ELLIPSE_TOOL_ACTION_DISPLAY_SHIFT": "Bir daire çizmek için fareyi tıklayın ve hareket ettirin.",
+  "ERASER_TOOL_TOOLTIP": "Pikselden rengi siler. ({0})",
+  "ERASER_TOOL_ACTION_DISPLAY": "Silmek için tıklayın ve hareket ettirin.",
+  "FLOOD_FILL_TOOL_TOOLTIP": "Alanı renkle doldurur. ({0})",
+  "FLOOD_FILL_TOOL_ACTION_DISPLAY_DEFAULT": "Doldurmak için bir alana basın. Tüm katmanları dikkate almak için Ctrl tuşunu basılı tutun.",
+  "FLOOD_FILL_TOOL_ACTION_DISPLAY_CTRL": "Doldurmak için bir alana basın. Sadece mevcut katmanları dikkate almak için Ctrl tuşunu bırakın.",
+  "LASSO_TOOL_TOOLTIP": "Kement. ({0})",
+  "LASSO_TOOL_ACTION_DISPLAY_DEFAULT": "Kementin içindeki pikselleri seçmek için tıklayın ve hareket ettirin. Mevcut seçime eklemek için Shift tuşunu basılı tutun. Ondan çıkarmak için Ctrl tuşunu basılı tutun.",
+  "LASSO_TOOL_ACTION_DISPLAY_SHIFT": "Kementin içindeki pikselleri seçime eklemek için tıklayın ve hareket ettirin.",
+  "LASSO_TOOL_ACTION_DISPLAY_CTRL": "Kementin içindeki pikselleri seçimden çıkarmak için tıklayın ve hareket ettirin.",
+  "LINE_TOOL_TOOLTIP": "Tuval üzerine çizgi çizer ({0}). Hizalamayı etkinleştirmek için Shift tuşunu basılı tutun.",
+  "LINE_TOOL_ACTION_DISPLAY_DEFAULT": "Bir çizgi çizmek için tıklayın ve hareket ettirin. Hizalamayı etkinleştirmek için Shift tuşunu basılı tutun.",
+  "LINE_TOOL_ACTION_DISPLAY_SHIFT": "Hizalama etkinken bir çizgi çizmek için fareyi tıklayın ve hareket ettirin.",
+  "MAGIC_WAND_TOOL_TOOLTIP": "Sihirli Değnek ({0}). Seçimi doldurur",
+  "MAGIC_WAND_ACTION_DISPLAY": "Seçimi doldurmak için tıklayın.",
+  "PEN_TOOL": "Kalem",
+  "BRIGHTNESS_TOOL": "Parlaklık",
+  "COLOR_PICKER_TOOL": "Renk Seçici",
+  "ELLIPSE_TOOL": "Elips",
+  "ERASER_TOOL": "Silgi",
+  "FLOOD_FILL_TOOL": "Boya Kovası",
+  "LASSO_TOOL": "Kement",
+  "LINE_TOOL": "Çizgi",
+  "MAGIC_WAND_TOOL": "Sihirli Değnek",
+  "MOVE_TOOL": "Taşı",
+  "MOVE_VIEWPORT_TOOL": "Görünüm Alanını Taşı",
+  "RECTANGLE_TOOL": "Dikdörtgen",
+  "ROTATE_VIEWPORT_TOOL": "Görünüm Alanını Döndür",
+  "SELECT_TOOL_NAME": "Seç",
+  "ZOOM_TOOL": "Yakınlaştır",
+  "SHAPE_LABEL": "Şekil",
+  "MODE_LABEL": "Mod",
+  "SCOPE_LABEL": "Kapsam",
+  "FILL_SHAPE_LABEL": "Şekli doldur",
+  "FILL_COLOR_LABEL": "Dolgu rengi",
+  "TOOL_SIZE_LABEL": "Araç boyutu",
+  "STRENGTH_LABEL": "Güç",
+  "NEW": "Yeni",
+  "ADD": "Ekle",
+  "SUBTRACT": "Çıkar",
+  "INTERSECT": "Kesiştir",
+  "RECTANGLE": "Dikdörtgen",
+  "CIRCLE": "Daire",
+  "ABOUT": "Hakkında",
+  "MINIMIZE": "Simge Durumuna Küçült",
+  "RESTORE": "Geri Yükle",
+  "MAXIMIZE": "Ekranı Kapla",
+  "CLOSE": "Kapat",
+  "EXPORT_SIZE_HINT": "Resmi paylaşmak istiyorsanız, en iyi netlik için %{0} deneyin",
+  "CREATE": "Oluştur",
+  "BASE_LAYER_NAME": "Temel katman",
+  "ENABLE_MASK": "Maskeyi etkinleştir",
+  "SELECTED_AREA_EMPTY": "Seçili alan boş",
+  "NOTHING_TO_COPY": "Kopyalanacak bir şey yok",
+  "REFERENCE_LAYER_PATH": "Referans katmanı yolu",
+  "FLIP": "Çevir",
+  "ROTATION": "Döndürme",
+  "ROT_IMG_90_D": "Resmi 90° Döndür",
+  "ROT_IMG_180_D": "Resmi 180° Döndür",
+  "ROT_IMG_-90_D": "Resmi -90° Döndür",
+  "ROT_LAYERS_90_D": "Seçili Katmanları 90° Döndür",
+  "ROT_LAYERS_180_D": "Seçili Katmanları 180° Döndür",
+  "ROT_LAYERS_-90_D": "Seçili Katmanları -90° Döndür",
+  "UNNAMED_PALETTE": "Adsız Palet",
+  "CLICK_SELECT_PRIMARY": "Ana renk olarak seçmek için tıklayın.",
+  "PEN_MODE": "Kalem modu",
+  "VIEW": "Görünüm",
+  "HORIZONTAL_LINE_SYMMETRY": "Yatay çizgi simetrisi",
+  "VERTICAL_LINE_SYMMETRY": "Dikey çizgi simetrisi",
+  "COLOR_PICKER_TITLE": "Renk Seçici",
+  "COLOR_SLIDERS_TITLE": "Renk Kaydırıcıları",
+  "PALETTE_TITLE": "Palet",
+  "SWATCHES_TITLE": "Renk Örnekleri",
+  "LAYERS_TITLE": "Katmanlar",
+  "PREVIEW_TITLE": "Önizleme",
+  "NORMAL_BLEND_MODE": "Normal",
+  "ERASE_BLEND_MODE": "Sil",
+  "DARKEN_BLEND_MODE": "Koyulaştır",
+  "MULTIPLY_BLEND_MODE": "Çarp",
+  "COLOR_BURN_BLEND_MODE": "Renk Yanması",
+  "LIGHTEN_BLEND_MODE": "Açıklaştır",
+  "SCREEN_BLEND_MODE": "Ekran",
+  "COLOR_DODGE_BLEND_MODE": "Renk Soldurma",
+  "OVERLAY_BLEND_MODE": "Kaplama",
+  "SOFT_LIGHT_BLEND_MODE": "Yumuşak Işık",
+  "HARD_LIGHT_BLEND_MODE": "Sert Işık",
+  "DIFFERENCE_BLEND_MODE": "Fark",
+  "EXCLUSION_BLEND_MODE": "Dışlama",
+  "HUE_BLEND_MODE": "Ton",
+  "SATURATION_BLEND_MODE": "Doygunluk",
+  "LUMINOSITY_BLEND_MODE": "Parlaklık",
+  "COLOR_BLEND_MODE": "Renk",
+  "NOT_SUPPORTED_BLEND_MODE": "Desteklenmiyor",
+  "RESTART": "Yeniden Başlat",
+  "SORT_BY": "Sırala",
+  "NAME": "Ad",
+  "COLORS": "Renkler",
+  "DEFAULT": "Varsayılan",
+  "ALPHABETICAL": "Alfabetik",
+  "COLOR_COUNT": "Renk sayısı",
+  "ANY": "Herhangi biri",
+  "MAX": "Maks",
+  "MIN": "Min",
+  "EXACT": "Tam",
+  "ASCENDING": "Artan",
+  "DESCENDING": "Azalan",
+  "NAME_IS_TOO_LONG": "Ad çok uzun",
+  "STOP_IT_TEXT1": "Bu kadar yeter. Dosya adlarını düzenle.",
+  "STOP_IT_TEXT2": "Lütfen bu adları kopyalamayı bırakır mısın?",
+  "REPLACER_TOOLTIP": "Palet rengine sağ tıklayın ve 'Değiştir'i seçin veya buraya bırakın.",
+  "CLICK_TO_CHOOSE_COLOR": "Rengi seçmek için tıklayın",
+  "REPLACE_COLOR": "Rengi değiştir",
+  "PALETTE_COLOR_TOOLTIP": "Ana renk olarak seçmek için tıklayın. Değiştirmek için başka bir palet renginin üzerine sürükleyip bırakın.",
+  "ADD_FROM_SWATCHES": "Renk örneklerinden ekle",
+  "ADD_COLOR_TO_PALETTE": "Palete renk ekle",
+  "USE_IN_CURRENT_IMAGE": "Mevcut resimde kullan",
+  "ADD_TO_FAVORITES": "Favorilere ekle",
+  "BROWSE_PALETTES": "Paletlere göz at",
+  "LOAD_PALETTE": "Palet yükle",
+  "SAVE_PALETTE": "Paleti kaydet",
+  "FAVORITES": "Favoriler",
+  "ADD_FROM_CURRENT_PALETTE": "Mevcut paletten ekle",
+  "OPEN_PALETTES_DIR_TOOLTIP": "Paletler dizinini gezginde aç",
+  "BROWSE_ON_LOSPEC_TOOLTIP": "Lospec'te paletlere göz at",
+  "IMPORT_FROM_FILE_TOOLTIP": "Dosyadan içe aktar",
+  "TOP_LEFT": "Sol üst",
+  "TOP_CENTER": "Orta üst",
+  "TOP_RIGHT": "Sağ üst",
+  "MIDDLE_LEFT": "Sol orta",
+  "MIDDLE_CENTER": "Orta orta",
+  "MIDDLE_RIGHT": "Sağ orta",
+  "BOTTOM_LEFT": "Sol alt",
+  "BOTTOM_CENTER": "Orta alt",
+  "BOTTOM_RIGHT": "Sağ alt",
+  "CLIP_TO_BELOW": "Alttaki üyeye kırp",
+  "MOVE_UPWARDS": "Yukarı taşı",
+  "MOVE_DOWNWARDS": "Aşağı taşı",
+  "MERGE_SELECTED": "Seçilenleri birleştir",
+  "LOCK_TRANSPARENCY": "Şeffaflığı kilitle",
+  "COULD_NOT_LOAD_PALETTE": "Paletler getirilemedi",
+  "NO_PALETTES_FOUND": "Palet bulunamadı.",
+  "LOSPEC_LINK_TEXT": "Bazılarını burada bulabileceğinizi duydum: lospec.com/palette-list",
+  "PALETTE_BROWSER": "Palet Tarayıcısı",
+  "DELETE_PALETTE_CONFIRMATION": "Bu paleti silmek istediğinizden emin misiniz? Bu işlem geri alınamaz.",
+  "SHORTCUTS_IMPORTED": "{0} konumundan kısayollar başarıyla içe aktarıldı.",
+  "SHORTCUT_PROVIDER_DETECTED": "{0} yüklü olduğunu tespit ettik. Kısayolları oradan içe aktarmak ister misiniz?",
+  "IMPORT_FROM_INSTALLATION": "Kurulumdan içe aktar",
+  "IMPORT_INSTALLATION_OPTION1": "Kurulumdan içe aktar",
+  "IMPORT_INSTALLATION_OPTION2": "Varsayılanları kullan",
+  "IMPORT_FROM_TEMPLATE": "Şablondan içe aktar",
+  "SHORTCUTS_IMPORTED_SUCCESS": "Kısayollar başarıyla içe aktarıldı.",
+  "WARNING_RESET_SHORTCUTS_DEFAULT": "Tüm kısayolları varsayılan değerlerine sıfırlamak istediğinizden emin misiniz?",
+  "SUCCESS": "Başarılı",
+  "WARNING": "Uyarı",
+  "ERROR_IMPORTING_IMAGE": "Resim içe aktarılırken bir hata oluştu.",
+  "SHORTCUTS_CORRUPTED_TITLE": "Bozuk kısayollar dosyası",
+  "SHORTCUTS_CORRUPTED": "Kısayollar dosyası bozulmuştu, varsayılana sıfırlanıyor.",
+  "FAILED_DOWNLOAD_PALETTE": "Palet indirilemedi",
+  "FILE_INCORRECT_FORMAT": "Dosya doğru biçimde değildi",
+  "INVALID_FILE": "Geçersiz dosya",
+  "SHORTCUTS_FILE_INCORRECT_FORMAT": "Kısayollar dosyası doğru biçimde değildi",
+  "UNSUPPORTED_FILE_FORMAT": "Bu dosya biçimi desteklenmiyor",
+  "ALREADY_ASSIGNED": "Zaten atanmış",
+  "REPLACE": "Değiştir",
+  "SWAP": "Takas Et",
+  "SHORTCUT_ALREADY_ASSIGNED_SWAP": "Bu kısayol zaten '{0}' öğesine atanmış\nMevcut kısayolu değiştirmek mi yoksa ikisini takas etmek mi istersiniz?",
+  "SHORTCUT_ALREADY_ASSIGNED_OVERWRITE": "Bu kısayol zaten '{0}' öğesine atanmış\nMevcut kısayolu değiştirmek ister misiniz?",
+  "UNSAVED_CHANGES": "Kaydedilmemiş değişiklikler",
+  "DOCUMENT_MODIFIED_SAVE": "Belge değiştirildi. Değişiklikleri kaydetmek ister misiniz?",
+  "SESSION_UNSAVED_DATA": "{0} kaydedilmemiş veri içeriyor. Emin misiniz?",
+  "PROJECT_MAINTAINERS": "Proje Sorumluları",
+  "OTHER_AWESOME_CONTRIBUTORS": "Ve diğer harika katkıda bulunanlar",
+  "HELP": "Yardım",
+  "STOP_IT_TEXT3": "Hayır, gerçekten, dur.",
+  "STOP_IT_TEXT4": "Yapacak daha iyi bir işin yok mu?",
+  "LINEAR_DODGE_BLEND_MODE": "Doğrusal Soldurma (Ekle)",
+  "PRESS_ANY_KEY": "Herhangi bir tuşa basın",
+  "NONE_SHORTCUT": "Yok",
+  "REFERENCE": "Referans",
+  "PUT_REFERENCE_LAYER_ABOVE": "Referans katmanını üste koy",
+  "PUT_REFERENCE_LAYER_BELOW": "Referans katmanını alta koy",
+  "TOGGLE_VERTICAL_SYMMETRY": "Dikey simetriyi aç/kapat",
+  "TOGGLE_HORIZONTAL_SYMMETRY": "Yatay simetriyi aç/kapat",
+  "RESET_VIEWPORT": "Görünüm alanını sıfırla",
+  "VIEWPORT_SETTINGS": "Görünüm alanı ayarları",
+  "MOVE_TOOL_ACTION_DISPLAY_TRANSFORMING": "Seçili katmanlardaki pikselleri taşımak için fareyi tıklayıp basılı tutun.",
+  "CTRL_KEY": "Ctrl",
+  "SHIFT_KEY": "Shift",
+  "ALT_KEY": "Alt",
+  "RENAME": "Yeniden Adlandır",
+  "PIXEL_UNIT": "px",
+  "OPEN_LOCALIZATION_DEBUG_WINDOW": "Yerelleştirme Hata Ayıklama Penceresini Aç",
+  "FORCE_OTHER_FLOW_DIRECTION": "Diğer akış yönünü zorla",
+  "API_KEY": "API Anahtarı",
+  "LOCALIZATION_VIEW_TYPE": "Yerelleştirme Görünüm Türü",
+  "LOAD_LANGUAGE_FROM_FILE": "Dili dosyadan yükle",
+  "LOG_IN": "Giriş Yap",
+  "SYNC": "Senkronize Et",
+  "NOT_LOGGED_IN": "Giriş yapılmadı",
+  "POE_EDITOR_ERROR": "POEditor Hatası: {0} {1}",
+  "HTTP_ERROR_MESSAGE": "HTTP Hatası: {0} {1}",
+  "LOGGED_IN": "Giriş yapıldı",
+  "SYNCED_SUCCESSFULLY": "Başarıyla senkronize edildi",
+  "EXCEPTION_ERROR": "İstisna: {0}",
+  "DROP_PALETTE": "Paleti buraya bırakın",
+  "SECURITY_ERROR": "Güvenlik hatası",
+  "SECURITY_ERROR_MSG": "Belirtilen konuma yazma hakkı yok.",
+  "IO_ERROR": "G/Ç hatası",
+  "IO_ERROR_MSG": "Diske yazarken hata oluştu.",
+  "FAILED_ASSOCIATE_PIXI": ".pixi dosyası PixiEditor ile ilişkilendirilemedi.",
+  "COULD_NOT_SAVE_PALETTE": "Palet kaydedilirken bir hata oluştu.",
+  "NO_COLORS_TO_SAVE": "Kaydedilecek renk yok.",
+  "CANVAS": "Tuval",
+  "SINGLE_LAYER": "Tek Katman",
+  "CHOOSE": "Seç",
+  "REMOVE": "Kaldır",
+  "FILE_FORMAT_NOT_ASEPRITE_KEYS": "Dosya bir \".aseprite-keys\" dosyası değil",
+  "FILE_HAS_INVALID_SHORTCUT": "Dosya geçersiz bir kısayol içeriyor",
+  "FILE_EXTENSION_NOT_SUPPORTED": "'{0}' dosya türü desteklenmiyor",
+  "ERROR_READING_FILE": "Dosya okunurken hata oluştu",
+  "DISCARD_PALETTE": "Paleti at",
+  "DISCARD_PALETTE_CONFIRMATION": "Mevcut paleti atmak istediğinizden emin misiniz? Bu işlem geri alınamaz.",
+  "IMPORT_AS_NEW_LAYER": "Yeni katman olarak içe aktar",
+  "PASTE_AS_PRIMARY_COLOR": "Birincil renk olarak yapıştır",
+  "IMPORT_AS_NEW_FILE": "Yeni dosya olarak içe aktar",
+  "IMPORT_PALETTE_FILE": "Palet dosyasını içe aktar",
+  "IMPORT_MULTIPLE_PALETTE_COLORS": "Renkleri palete aktar",
+  "IMPORT_SINGLE_PALETTE_COLOR": "Rengi palete aktar",
+  "IMPORT_AS_REFERENCE_LAYER": "Referans katmanı olarak içe aktar",
+  "NAVIGATOR_PICK_ACTION_DISPLAY": "Renk seçmek için sağ tıklayın, rengi panoya kopyalamak için Shift-sağ tıklayın",
+  "OPEN_FILE_FROM_CLIPBOARD": "Panodan aç",
+  "OPEN_FILE_FROM_CLIPBOARD_DESCRIPTIVE": "Panodan aç",
+  "OPEN_LOCALIZATION_DATA": "LocalizationData.json dosyasını açmak istiyor musunuz?\nGüncellenmiş tarih panoya kopyalandı.\nDeğişikliklerin yeniden başlatılana kadar uygulanmayacağını unutmayın",
+  "DOWNLOADING_LANGUAGE_FAILED": "Dil indirilemedi.\nAPI Anahtarı aşırı kullanılmış olabilir.",
+  "LOCALIZATION_DATA_NOT_FOUND": "Yerelleştirme veri yolu bulunamadı",
+  "APPLY": "Uygula",
+  "UPDATE_SOURCE": "Kaynağı güncelle",
+  "COPY_TO_CLIPBOARD": "Panoya kopyala",
+  "LANGUAGE_FILE_NOT_FOUND": "Dil dosyası bulunamadı.\nAranan: {0}",
+  "PROJECT_ROOT_NOT_FOUND": "PixiEditor Proje kökü bulunamadı.\nAranan: PixiEditor.csproj",
+  "LOCALIZATION_FOLDER_NOT_FOUND": "Yerelleştirme klasörü bulunamadı.\nAranan: /Data/Localization",
+  "SELECT_A_LANGUAGE": "Bir dil seçin",
+  "DONE": "Bitti",
+  "SOURCE_UNSET_OR_MISSING": "Kaynak eksik/ayarlanmamış",
+  "SOURCE_NEWER": "Kaynak daha yeni",
+  "SOURCE_UP_TO_DATE": "Kaynak güncel",
+  "SOURCE_OLDER": "Bulut daha yeni",
+  "COLOR_PICKER_ACTION_DISPLAY_REFERENCE_ONLY": "Referans katmanından renkleri seçmek için tıklayın.",
+  "COLOR_PICKER_ACTION_DISPLAY_CANVAS_ONLY": "Tuvalden renkleri seçmek için tıklayın.",
+  "LOCALIZATION_DEBUG_WINDOW_TITLE": "Yerelleştirme Hata Ayıklama Penceresi",
+  "COMMAND_DEBUG_WINDOW_TITLE": "Komut Hata Ayıklama Penceresi",
+  "SHORTCUTS_TITLE": "Kısayollar",
+  "TRANSFORM_ACTION_DISPLAY_SCALE_ROTATE_SHEAR_PERSPECTIVE": "Dönüşümü ölçeklemek için tutamaçları sürükleyin. Merkezden ölçeklemek için Ctrl tuşunu basılı tutun ve bir tutamacı sürükleyin. Orantılı olarak ölçeklemek için Shift tuşunu basılı tutun. Eğim vermek için Alt tuşunu basılı tutun ve bir yan tutamacı sürükleyin. Döndürmek için dış tutamaçları sürükleyin.",
+  "TRANSFORM_ACTION_DISPLAY_SCALE_ROTATE_SHEAR_NOPERSPECTIVE": "Dönüşümü ölçeklemek için tutamaçları sürükleyin. Merkezden ölçeklemek için Ctrl tuşunu basılı tutun ve bir tutamacı sürükleyin. Orantılı olarak ölçeklemek için Shift tuşunu basılı tutun. Eğim vermek için Alt tuşunu basılı tutun ve bir yan tutamacı sürükleyin. Döndürmek için dış tutamaçları sürükleyin.",
+  "TRANSFORM_ACTION_DISPLAY_SCALE_ROTATE_NOSHEAR_NOPERSPECTIVE": "Dönüşümü ölçeklemek için tutamaçları sürükleyin. Merkezden ölçeklemek için Ctrl tuşunu basılı tutun ve bir tutamacı sürükleyin. Orantılı olarak ölçeklemek için Shift tuşunu basılı tutun. Döndürmek için dış tutamaçları sürükleyin.",
+  "TRANSFORM_ACTION_DISPLAY_SCALE_NOROTATE_NOSHEAR_NOPERSPECTIVE": "Dönüşümü ölçeklemek için tutamaçları sürükleyin. Merkezden ölçeklemek için Ctrl tuşunu basılı tutun ve bir tutamacı sürükleyin. Orantılı olarak ölçeklemek için Shift tuşunu basılı tutun.",
+  "LOCAL_PALETTE_SOURCE_NAME": "Yerel",
+  "ERROR_FORBIDDEN_UNIQUE_NAME": "Uzantı benzersiz adı 'pixieditor' ile başlayamaz.",
+  "ERROR_MISSING_METADATA": "Uzantı meta veri anahtarı '{0}' eksik.",
+  "ERROR_NO_CLASS_ENTRY": "Uzantı sınıf girişi '{0}' yolunda eksik.",
+  "ERROR_NO_ENTRY_ASSEMBLY": "Uzantı giriş derlemesi '{0}' yolunda eksik.",
+  "ERROR_MISSING_ADDITIONAL_CONTENT": "Mevcut kurulumunuz bu uzantının yüklenmesine izin vermiyor. Belki de sahip değilsiniz veya kurulu değil. Buradan satın alabilirsiniz: '{0}'.",
+  "BUY_SUPPORTER_PACK": "Destekçi Paketini Satın Al",
+  "NEWS": "Haberler",
+  "DISABLE_NEWS_PANEL": "Başlangıç penceresindeki Haberler panelini devre dışı bırak",
+  "FAILED_FETCH_NEWS": "Haberler getirilemedi",
+  "CROP_TO_SELECTION": "Seçime göre kırp",
+  "CROP_TO_SELECTION_DESCRIPTIVE": "Resmi seçime göre kırp",
+  "SHOW_CONTEXT_MENU": "Bağlam menüsünü göster",
+  "ERASE": "Sil",
+  "USE_SECONDARY_COLOR": "İkincil rengi kullan",
+  "RIGHT_CLICK_MODE": "Sağ tıklama modu",
+  "ADD_PRIMARY_COLOR_TO_PALETTE": "Birincil rengi palete ekle",
+  "ADD_PRIMARY_COLOR_TO_PALETTE_DESCRIPTIVE": "Birincil rengi mevcut palete ekle",
+  "EXPORT_SAVE_TITLE": "Resmi kaydetmek için bir konum seçin",
+  "BROWSE_DIRECTORY": "Dizine Göz At",
+  "CRASH_NOT_ALL_DOCUMENTS_RECOVERED_TITLE": "Tüm belgeler kurtarılamadı",
+  "CRASH_NOT_ALL_DOCUMENTS_RECOVERED": "Tüm belgeler kurtarılamadı. Çalışmalarınızı kaydetmede daha iyi olun.",
+  "SEND": "Raporu Gönder",
+  "OPEN_DOCKABLE_MENU": "Sekmeyi Aç",
+  "TIMELINE_TITLE": "Zaman Çizelgesi",
+  "EXPORT_IMAGE_HEADER": "Resim",
+  "EXPORT_ANIMATION_HEADER": "Animasyon",
+  "EXPORT_SPRITESHEET_HEADER": "Spritesheet",
+  "PIXI_FILE": "PixiEditor Dosyaları",
+  "PNG_FILE": "PNG Resimleri",
+  "JPEG_FILE": "JPEG Resimleri",
+  "WEBP_FILE": "WebP Resimleri",
+  "GIF_FILE": "GIF'ler",
+  "BMP_FILE": "BMP Resimleri",
+  "IMAGE_FILES": "Resim Dosyaları",
+  "VIDEO_FILES": "Video Dosyaları",
+  "OPEN_TYPE_FONT": "OpenType Yazı Tipleri",
+  "TRUE_TYPE_FONT": "TrueType Yazı Tipleri",
+  "SVG_FILE": "Ölçeklenebilir Vektör Grafikleri",
+  "MP4_FILE": "MP4 Videoları",
+  "COLUMNS": "Sütunlar",
+  "ROWS": "Satırlar",
+  "BACKGROUND": "Arka Plan",
+  "OPACITY": "Opaklık",
+  "IS_VISIBLE": "Görünür",
+  "CLIP_TO_MEMBER_BELOW": "Alttaki üyeye kırp",
+  "BLEND_MODE": "Karıştırma modu",
+  "MASK": "Maske",
+  "MASK_IS_VISIBLE": "Maske görünür",
+  "OUTPUT": "Çıktı",
+  "INPUT": "Girdi",
+  "NODE_GRAPH_TITLE": "Düğüm Grafiği",
+  "CONTENT": "İçerik",
+  "RADIUS": "Yarıçap",
+  "STROKE_COLOR": "Kontur rengi",
+  "STROKE_WIDTH": "Kontur genişliği",
+  "FILL_COLOR": "Dolgu rengi",
+  "TOP": "Üst",
+  "BOTTOM": "Alt",
+  "CHANNELS_DOCK_TITLE": "Kanallar",
+  "RED": "Kırmızı",
+  "GREEN": "Yeşil",
+  "BLUE": "Mavi",
+  "ALPHA": "Alfa",
+  "COLOR": "Renk",
+  "COORDINATE": "Koordinat",
+  "VECTOR": "Vektör",
+  "MATRIX": "Matris",
+  "TRANSFORMED": "Dönüştürülmüş",
+  "GRAYSCALE": "Gri Tonlama",
+  "CLAMP": "Sıkıştır",
+  "SIZE": "Boyut",
+  "NOISE": "Gürültü",
+  "SCALE": "Ölçek",
+  "SEED": "Tohum",
+  "KERNEL": "Çekirdek",
+  "KERNEL_VIEW_SUM": "Toplam:",
+  "KERNEL_VIEW_SUM_TOOLTIP": "Tüm değerlerin toplamı. Muhtemelen 1 veya 0 değerini hedeflemek istersiniz",
+  "GAIN": "Kazanç",
+  "BIAS": "Eğilim",
+  "TILE_MODE": "Döşeme Modu",
+  "ON_ALPHA": "Alfa Üzerinde",
+  "PIXEL_COORDINATE": "Piksel Koordinatı",
+  "OUTPUT_NODE": "Çıktı",
+  "NOISE_NODE": "Gürültü",
+  "ELLIPSE_NODE": "Elips",
+  "CREATE_IMAGE_NODE": "Resim Oluştur",
+  "FOLDER_NODE": "Klasör",
+  "IMAGE_LAYER_NODE": "Resim Katmanı",
+  "KERNEL_FILTER_NODE": "Çekirdek Filtresi",
+  "MATH_NODE": "Matematik",
+  "COLOR_MATRIX_TRANSFORM_FILTER_NODE": "Matris Dönüşüm Filtresi",
+  "MERGE_NODE": "Birleştir",
+  "MODIFY_IMAGE_LEFT_NODE": "Resmi Değiştirmeye Başla",
+  "MODIFY_IMAGE_RIGHT_NODE": "Resmi Değiştirmeyi Bitir",
+  "MODIFY_IMAGE_PAIR_NODE": "Resmi Değiştir",
+  "COMBINE_CHANNELS_NODE": "Kanalları Birleştir",
+  "COMBINE_COLOR_NODE": "Rengi Birleştir",
+  "COMBINE_VECD_NODE": "Vektörü Birleştir",
+  "COMBINE_VECI_NODE": "Tamsayı Vektörünü Birleştir",
+  "SEPARATE_CHANNELS_NODE": "Kanalları Ayır",
+  "SEPARATE_VECD_NODE": "Vektörü Ayır",
+  "SEPARATE_VECI_NODE": "Tamsayı Vektörünü Ayır",
+  "SEPARATE_COLOR_NODE": "Rengi Ayır",
+  "TIME_NODE": "Zaman",
+  "FILTERS": "Filtreler",
+  "PREVIOUS": "Önceki",
+  "FILL": "Doldur",
+  "MATH_MODE": "Matematik Modu",
+  "NOISE_TYPE": "Gürültü Tipi",
+  "OCTAVES": "Oktavlar",
+  "ACTIVE_FRAME": "Aktif Kare",
+  "NORMALIZED_TIME": "Normalleştirilmiş Zaman",
+  "WITHOUT_FILTERS": "Filtresiz",
+  "RAW_LAYER_OUTPUT": "Ham",
+  "EXAMPLE_FILES": "Örnek Dosyalar",
+  "PROCEDURAL_GENERATION": "Prosedürel Animasyon",
+  "POND_EXAMPLE": "Göl",
+  "TREE_EXAMPLE": "Rüzgarlı Ağaç",
+  "OUTLINE_EXAMPLE": "Otomatik Anahat",
+  "BETA_ANIMATIONS": "Animasyonlar",
+  "SLIME_EXAMPLE": "Animasyonlu Balçık",
+  "SHOW_ALL_EXAMPLES": "Tümünü göster",
+  "APPLY_FILTER_NODE": "Filtre Uygula",
+  "FILTER": "Filtre",
+  "LERP_NODE": "Lerp",
+  "GRAYSCALE_FILTER_NODE": "Gri Tonlama Filtresi",
+  "FROM": "Başlangıç",
+  "TO": "Bitiş",
+  "TIME": "Zaman",
+  "WARMING_UP": "Isınıyor",
+  "RENDERING_FRAME": "Kare Oluşturuluyor {0}/{1}",
+  "RENDERING_VIDEO": "Video Oluşturuluyor",
+  "FINISHED": "Bitti",
+  "GENERATING_SPRITE_SHEET": "Sprite Sayfası Oluşturuluyor",
+  "RENDERING_IMAGE": "Resim Oluşturuluyor",
+  "PROGRESS_POPUP_TITLE": "İlerleme",
+  "POINTS": "Noktalar",
+  "MIN_DISTANCE": "Min. Mesafe",
+  "MAX_POINTS": "Maks. Nokta",
+  "PROBABILITY": "Olasılık",
+  "DISTRIBUTE_POINTS": "Noktaları dağıt",
+  "REMOVE_CLOSE_POINTS": "Yakın noktaları kaldır",
+  "RASTERIZE_SHAPE": "Şekli Rasterleştir",
+  "MODE": "Mod",
+  "Factor": "Faktör",
+  "NORMALIZE": "Normalleştir",
+  "WEIGHT_FACTOR": "Ağırlık",
+  "STARS_EXAMPLE": "Yıldızlar",
+  "ADD_EMPTY_FRAME": "Boş kare ekle",
+  "DUPLICATE_FRAME": "Kareyi çoğalt",
+  "DELETE_FRAME": "Kareyi kaldır",
+  "DEFAULT_MEMBER_NAME": "Yeni Eleman",
+  "NO_PARSER_FOUND": "'{0}' uzantısı için dosya ayrıştırıcı bulunamadı",
+  "SELECT_FILE_FORMAT": "Dosya biçimini seç",
+  "SELECT_FILE_FORMAT_DESCRIPTION": "Aynı biçimde birden fazla dosya türü desteklenmektedir. Lütfen kullanmak istediğinizi seçin.",
+  "NEW_PALETTE_FILE": "palet",
+  "ISLAND_EXAMPLE": "Adalar",
+  "ONION_FRAMES_COUNT": "Soğan katman sayısı",
+  "ONION_OPACITY": "Soğan opaklığı",
+  "TOGGLE_ONION_SKINNING": "Soğan görünümünü aç/kapat",
+  "CHANGE_ACTIVE_FRAME_PREVIOUS": "Aktif kareyi bir öncekine değiştir",
+  "CHANGE_ACTIVE_FRAME_NEXT": "Aktif kareyi bir sonrakine değiştir",
+  "TOGGLE_ANIMATION": "Animasyonu aç/kapat",
+  "NEW_FROM_CLIPBOARD": "Panodan yeni",
+  "OFFSET": "Ofset",
+  "SHAPE": "Şekil",
+  "STRUCTURE": "Yapı",
+  "NUMBERS": "Sayılar",
+  "OPERATIONS": "İşlemler",
+  "GENERATION": "Oluşturma",
+  "NUMBER": "Sayı",
+  "ANIMATION": "Animasyon",
+  "SAMPLE_IMAGE": "Örnek Resim",
+  "POSITION": "Konum",
+  "MATH_ADD": "Topla",
+  "MATH_SUBTRACT": "Çıkar",
+  "MULTIPLY": "Çarp",
+  "DIVIDE": "Böl",
+  "SIN": "Sin",
+  "COS": "Cos",
+  "TAN": "Tan",
+  "GREATER_THAN": "Büyüktür",
+  "LESS_THAN": "Küçüktür",
+  "LESS_THAN_OR_EQUAL": "Küçüktür veya eşittir",
+  "COMPARE": "Karşılaştır",
+  "MATH_POWER": "Üs",
+  "LOGARITHM": "Logaritma",
+  "NATURAL_LOGARITHM": "Doğal logaritma",
+  "ROOT": "Kök",
+  "INVERSE_ROOT": "Ters kök",
+  "FRACTION": "Kesir",
+  "NEGATE": "Olumsuzla",
+  "FLOOR": "Taban",
+  "CEIL": "Tavan",
+  "ROUND": "Yuvarla",
+  "MODULO": "Modulo",
+  "STEP": "Adım",
+  "SMOOTH_STEP": "Yumuşak Adım",
+  "PIXEL_ART_TOOLSET": "Piksel Sanatı",
+  "VECTOR_TOOLSET": "Vektör",
+  "VECTOR_LAYER": "Vektör Katmanı",
+  "STROKE_COLOR_LABEL": "Kontur",
+  "SYNC_WITH_PRIMARY_COLOR_LABEL": "Birincil renkle senkronize et",
+  "RASTERIZE": "Rasterleştir",
+  "RASTERIZE_ACTIVE_LAYER": "Aktif katmanı rasterleştir",
+  "RASTERIZE_ACTIVE_LAYER_DESCRIPTIVE": "Aktif katmanı bir resim (raster) katmanına dönüştür/rasterleştir.",
+  "NEW_ELLIPSE_LAYER_NAME": "Elips",
+  "NEW_RECTANGLE_LAYER_NAME": "Dikdörtgen",
+  "NEW_LINE_LAYER_NAME": "Çizgi",
+  "RENDER_OUTPUT": "Çıktıyı Oluştur",
+  "PAINT_TOOLSET": "Boya",
+  "HARDNESS_SETTING": "Sertlik",
+  "SPACING_SETTING": "Aralık",
+  "ANTI_ALIASING_SETTING": "Kenar Yumuşatma",
+  "TOLERANCE_LABEL": "Tolerans",
+  "TOGGLE_SNAPPING": "Hizalamayı aç/kapat",
+  "HIGH_RES_PREVIEW": "Yüksek Çözünürlüklü Önizleme",
+  "LOW_RES_PREVIEW": "Belge Çözünürlüğünde Önizleme",
+  "TOGGLE_HIGH_RES_PREVIEW": "Yüksek çözünürlüklü önizlemeyi aç/kapat",
+  "FACTOR": "Faktör",
+  "PATH_TOOL": "Yol",
+  "PATH_TOOL_TOOLTIP": "Vektör yolları ve eğrileri oluşturun ({0}).",
+  "PATH_TOOL_ACTION_DISPLAY": "Bir nokta eklemek için tıklayın.",
+  "PATH_TOOL_ACTION_DISPLAY_CTRL": "Mevcut bir noktaya tıklayın ve bir eğri yapmak için sürükleyin. Seçmek için bir kontrol noktasına dokunun.",
+  "PATH_TOOL_ACTION_DISPLAY_SHIFT": "Yeni bir katman oluşturmak için tıklayın.",
+  "PATH_TOOL_ACTION_DISPLAY_CTRL_SHIFT": "Seçime eklemek için bir kontrol noktasına dokunun.",
+  "PATH_TOOL_ACTION_DISPLAY_ALT": "Eğrinin sadece bir tarafını ayarlamak için bir kontrol noktasına tıklayın ve hareket ettirin.",
+  "DEFAULT_PATH_LAYER_NAME": "Yol",
+  "DELETE_NODES": "Düğümleri sil",
+  "DELETE_NODES_DESCRIPTIVE": "Seçili düğümleri sil",
+  "DELETE_CELS": "Kareleri sil",
+  "DELETE_CELS_DESCRIPTIVE": "Seçili kareleri sil",
+  "COPY_COLOR_TO_CLIPBOARD": "Rengi panoya kopyala",
+  "VIEWPORT_ROTATION": "Görünüm alanı döndürme",
+  "NEXT_TOOL_SET": "Sonraki araç seti",
+  "PREVIOUS_TOOL_SET": "Önceki araç seti",
+  "FILL_MODE": "Doldurma modu",
+  "USE_LINEAR_SRGB_PROCESSING": "Renkleri işlemek için doğrusal sRGB kullan",
+  "USE_LINEAR_SRGB_PROCESSING_DESC": "Renkleri işlemek için sRGB harmanlama modunu kullanarak belgeyi doğrusal sRGB'ye dönüştürün. Bu, belgenin renklerini etkileyecek, ancak harmanlamayı daha doğru hale getirecektir.",
+  "FILL_TYPE_WINDING": "Sarma",
+  "FILL_TYPE_EVEN_ODD": "Çift Tek",
+  "FILL_TYPE_INVERSE_WINDING": "Ters Sarma",
+  "FILL_TYPE_INVERSE_EVEN_ODD": "Ters Çift Tek",
+  "STROKE_CAP": "Kontür Ucu",
+  "STROKE_JOIN": "Kontür Birleşimi",
+  "COPY_VISIBLE": "Görünürü kopyala",
+  "COPY_VISIBLE_DESCRIPTIVE": "Görünür pikselleri kopyala",
+  "COLOR_SAMPLE_MODE": "Örnek modu",
+  "CREATE_CEL": "Kare oluştur",
+  "CREATE_CEL_DESCRIPTIVE": "Yeni bir kare oluştur",
+  "DUPLICATE_CEL": "Kareyi çoğalt",
+  "DUPLICATE_CEL_DESCRIPTIVE": "Mevcut karede kareyi çoğalt",
+  "RENDER_PREVIEW": "Önizlemeyi oluştur",
+  "OUTPUT_NAME": "Çıktı adı",
+  "CUSTOM_OUTPUT_NODE": "Özel Çıktı",
+  "TOGGLE_HUD": "HUD'u aç/kapat",
+  "OPEN_TIMELINE": "Zaman çizelgesini aç",
+  "OPEN_NODE_GRAPH": "Düğüm grafiğini aç",
+  "TOGGLE_PLAY": "Animasyonu Oynat/Duraklat",
+  "COPY_NODES": "Düğümleri kopyala",
+  "COPY_NODES_DESCRIPTIVE": "Seçili düğümleri kopyala",
+  "PASTE_NODES": "Düğümleri yapıştır",
+  "PASTE_NODES_DESCRIPTIVE": "Kopyalanan düğümleri yapıştır",
+  "COPY_CELS": "Kareleri kopyala",
+  "COPY_CELS_DESCRIPTIVE": "Seçili kareleri kopyala",
+  "TOGGLE_ONION_SKINNING_DESCRIPTIVE": "Soğan zarı görünümünü aç/kapat",
+  "VALUE": "Değer",
+  "TARGET": "Hedef",
+  "EPSILON": "Epsilon",
+  "PRESERVE_ALPHA": "Alfayı koru",
+  "BLUR_FILTER_NODE": "Gauss Bulanıklık Filtresi",
+  "LENGTH": "Uzunluk",
+  "GREATER_THAN_OR_EQUAL": "Büyüktür veya eşittir",
+  "COLOR_NODE": "Renk",
+  "CONVERT_TO_CURVE": "Eğriye dönüştür",
+  "CONVERT_TO_CURVE_DESCRIPTIVE": "Seçili vektör katmanını bir eğriye/yola dönüştür",
+  "FONT_FILES": "Yazı Tipi Dosyaları",
+  "UNIT_PT": "pt",
+  "FONT_LABEL": "Aile",
+  "FONT_SIZE_LABEL": "Boyut",
+  "SPACING_LABEL": "Aralık",
+  "TEXT_TOOL": "Metin",
+  "MISSING_FONT": "Eksik yazı tipi",
+  "TEXT_LAYER_NAME": "Metin",
+  "TEXT_TOOL_TOOLTIP": "Metin oluştur ({0}).",
+  "BOLD_TOOLTIP": "Kalın",
+  "ITALIC_TOOLTIP": "İtalik",
+  "CUSTOM_FONT": "Özel yazı tipi",
+  "DUMP_GPU_DIAGNOSTICS": "GPU tanılamalarını dök",
+  "USE_SRGB_PROCESSING": "Renkleri işlemek için sRGB kullan",
+  "USE_SRGB_PROCESSING_DESC": "Renkleri işlemek için doğrusal sRGB kullanarak belgeyi sRGB'ye dönüştürün. Bu, belgenin renklerini etkileyecektir.",
+  "TEXT_NODE": "Metin",
+  "TEXT_LABEL": "Metin",
+  "TEXT_ON_PATH_NODE": "Yol Üzerinde Metin",
+  "HIGH_DPI_RENDERING": "Yüksek DPI Oluşturma",
+  "THICKNESS": "Kalınlık",
+  "TYPE": "Tür",
+  "EFFECTS": "Efektler",
+  "OUTLINE_NODE": "Anahat",
+  "SHADER_CODE": "Gölgelendirici Kodu",
+  "SHADER_NODE": "Gölgelendirici",
+  "FAILED_TO_OPEN_EDITABLE_STRING_TITLE": "Dosya açılamadı",
+  "FAILED_TO_OPEN_EDITABLE_STRING_MESSAGE": "Bu dize harici düzenleyicide düzenlenemedi. Sebep: {0}",
+  "STRING_EDIT_IN_DEFAULT_APP": "Varsayılan uygulamada düzenle",
+  "STRING_OPEN_IN_FOLDER": "Klasörde aç",
+  "DISCO_BALL_EXAMPLE": "Disko Topu",
+  "COLOR_SPACE": "Renk Uzayı",
+  "PHOTO_EXAMPLES": "Fotoğraf",
+  "MASK_EXAMPLE": "Maske",
+  "SHADOW_NODE": "Gölge Filtresi",
+  "INPUT_MATRIX": "Giriş Matrisi",
+  "OUTPUT_MATRIX": "Çıkış Matrisi",
+  "CENTER": "Merkez",
+  "CONTENT_OFFSET": "İçerik Ofseti",
+  "CANVAS_POSITION": "Tuval Konumu",
+  "CENTER_POSITION": "Merkez Konumu",
+  "TILE_MODE_X": "Döşeme Modu X",
+  "TILE_MODE_Y": "Döşeme Modu Y",
+  "TILE_NODE": "Döşeme",
+  "SKEW": "Eğme",
+  "OFFSET_NODE": "Ofset",
+  "SKEW_NODE": "Eğme",
+  "ROTATION_NODE": "Döndürme",
+  "SCALE_NODE": "Ölçek",
+  "ROTATE_NODE": "Döndür",
+  "TRANSFORM_NODE": "Dönüştür",
+  "UNIT": "Birim",
+  "ANGLE": "Açı",
+  "DOCUMENT_INFO_NODE": "Belge Bilgisi",
+  "MASK_NODE": "Maske",
+  "SEPIA_FILTER_NODE": "Sepya Filtresi",
+  "INTENSITY": "Yoğunluk",
+  "INVERT_FILTER_NODE": "Ters Çevirme Filtresi",
+  "COLOR_ADJUSTMENTS_FILTER": "Renk Ayarları Filtresi",
+  "ADJUST_BRIGHTNESS": "Parlaklığı Ayarla",
+  "ADJUST_CONTRAST": "Kontrastı Ayarla",
+  "ADJUST_SATURATION": "Doygunluğu Ayarla",
+  "ADJUST_TEMPERATURE": "Sıcaklığı Ayarla",
+  "ADJUST_TINT": "Rengi Ayarla",
+  "ADJUST_HUE": "Tonu Ayarla",
+  "HUE_VALUE": "Ton",
+  "SATURATION_VALUE": "Doygunluk",
+  "BRIGHTNESS_VALUE": "Parlaklık",
+  "CONTRAST_VALUE": "Kontrast",
+  "TEMPERATURE_VALUE": "Sıcaklık",
+  "TINT_VALUE": "Renk Tonu",
+  "FAILED_DOWNLOADING_UPDATE_TITLE": "Güncelleme indirilemedi",
+  "FAILED_DOWNLOADING_UPDATE": "Güncelleme indirilemedi. Daha sonra tekrar deneyin.",
+  "UNEXPECTED_SHUTDOWN": "Beklenmedik kapanma",
+  "UNEXPECTED_SHUTDOWN_MSG": "PixiEditor beklenmedik bir şekilde kapandı. Dosyalarınızın en son otomatik kaydını yükledik.",
+  "OK": "Tamam",
+  "OPEN_AUTOSAVES": "Otomatik Kayıtlara Göz At",
+  "AUTOSAVE_SETTINGS_HEADER": "Otomatik Kaydetme",
+  "AUTOSAVE_SETTINGS_SAVE_STATE": "Başlangıçta son dosyaları yeniden aç",
+  "AUTOSAVE_SETTINGS_PERIOD": "Otomatik kaydetme periyodu",
+  "AUTOSAVE_ENABLED": "Otomatik kaydetme etkin",
+  "MINUTE_UNIVERSAL": "dk",
+  "AUTOSAVE_SETTINGS_SAVE_USER_FILE": "Seçili dosyaya otomatik kaydet",
+  "LOAD_LAZY_FILE_MESSAGE": "Başlatma süresini iyileştirmek için, PixiEditor bu dosyayı yüklemedi. Yüklemek için aşağıdaki düğmeye tıklayın.",
+  "EASING_NODE": "Yumuşatma",
+  "EASING_TYPE": "Yumuşatma Türü",
+  "OPEN_DIRECTORY_ON_EXPORT": "Dışa aktarma sırasında dizini aç",
+  "ERROR_LOOP_DETECTED_MESSAGE": "Bu katmanı taşımak bir döngü oluşturacaktır. Düğüm Grafiği'nde düzeltin.",
+  "LINEAR_EASING_TYPE": "Doğrusal",
+  "IN_SINE_EASING_TYPE": "İçeri Sinüs",
+  "OUT_SINE_EASING_TYPE": "Dışarı Sinüs",
+  "IN_OUT_SINE_EASING_TYPE": "İçeri Dışarı Sinüs",
+  "IN_QUAD_EASING_TYPE": "İçeri Karesel",
+  "OUT_QUAD_EASING_TYPE": "Dışarı Karesel",
+  "IN_OUT_QUAD_EASING_TYPE": "İçeri Dışarı Karesel",
+  "IN_CUBIC_EASING_TYPE": "İçeri Kübik",
+  "OUT_CUBIC_EASING_TYPE": "Dışarı Kübik",
+  "IN_OUT_CUBIC_EASING_TYPE": "İçeri Dışarı Kübik",
+  "IN_QUART_EASING_TYPE": "İçeri Dördüncü Dereceden",
+  "OUT_QUART_EASING_TYPE": "Dışarı Dördüncü Dereceden",
+  "IN_OUT_QUART_EASING_TYPE": "İçeri Dışarı Dördüncü Dereceden",
+  "IN_QUINT_EASING_TYPE": "İçeri Beşinci Dereceden",
+  "OUT_QUINT_EASING_TYPE": "Dışarı Beşinci Dereceden",
+  "IN_OUT_QUINT_EASING_TYPE": "İçeri Dışarı Beşinci Dereceden",
+  "IN_EXPO_EASING_TYPE": "İçeri Üstel",
+  "OUT_EXPO_EASING_TYPE": "Dışarı Üstel",
+  "IN_OUT_EXPO_EASING_TYPE": "İçeri Dışarı Üstel",
+  "IN_CIRC_EASING_TYPE": "İçeri Dairesel",
+  "OUT_CIRC_EASING_TYPE": "Dışarı Dairesel",
+  "IN_OUT_CIRC_EASING_TYPE": "İçeri Dışarı Dairesel",
+  "IN_BACK_EASING_TYPE": "İçeri Geri",
+  "OUT_BACK_EASING_TYPE": "Dışarı Geri",
+  "IN_OUT_BACK_EASING_TYPE": "İçeri Dışarı Geri",
+  "IN_ELASTIC_EASING_TYPE": "İçeri Elastik",
+  "OUT_ELASTIC_EASING_TYPE": "Dışarı Elastik",
+  "IN_OUT_ELASTIC_EASING_TYPE": "İçeri Dışarı Elastik",
+  "IN_BOUNCE_EASING_TYPE": "İçeri Sıçrama",
+  "OUT_BOUNCE_EASING_TYPE": "Dışarı Sıçrama",
+  "IN_OUT_BOUNCE_EASING_TYPE": "İçeri Dışarı Sıçrama",
+  "CLAMP_SHADER_TILE_NODE": "Sıkıştır",
+  "REPEAT_SHADER_TILE_NODE": "Tekrarla",
+  "MIRROR_SHADER_TILE_NODE": "Ayna",
+  "DECAL_SHADER_TILE_NODE": "Çıkartma",
+  "R_G_B_COMBINE_SEPARATE_COLOR_MODE": "RGB",
+  "H_S_V_COMBINE_SEPARATE_COLOR_MODE": "HSV",
+  "H_S_L_COMBINE_SEPARATE_COLOR_MODE": "HSL",
+  "COLOR_MANAGED_COLOR_SAMPLE_MODE": "Renk Yönetimli",
+  "RAW_COLOR_SAMPLE_MODE": "Ham",
+  "FRACTAL_PERLIN_NOISE_TYPE": "Perlin",
+  "TURBULENCE_PERLIN_NOISE_TYPE": "Türbülans",
+  "INHERIT_COLOR_SPACE_TYPE": "Miras Al",
+  "SRGB_COLOR_SPACE_TYPE": "sRGB",
+  "LINEAR_SRGB_COLOR_SPACE_TYPE": "Doğrusal sRGB",
+  "SIMPLE_OUTLINE_TYPE": "Basit",
+  "GAUSSIAN_OUTLINE_TYPE": "Gauss",
+  "PIXEL_PERFECT_OUTLINE_TYPE": "Piksel Mükemmel",
+  "DEGREES_ROTATION_TYPE": "Derece",
+  "RADIANS_ROTATION_TYPE": "Radyan",
+  "WEIGHTED_GRAYSCALE_MODE": "Ağırlıklı",
+  "AVERAGE_GRAYSCALE_MODE": "Ortalama",
+  "CUSTOM_GRAYSCALE_MODE": "Özel",
+  "CLAMP_TILE_MODE": "Sıkıştır",
+  "REPEAT_TILE_MODE": "Tekrarla",
+  "MIRROR_TILE_MODE": "Ayna",
+  "DECAL_TILE_MODE": "Çıkartma",
+  "ERR_UNKNOWN_FILE_FORMAT": "Bilinmeyen dosya biçimi",
+  "ERR_EXPORT_SIZE_INVALID": "Geçersiz dışa aktarma boyutu. Değerler 0'dan büyük olmalıdır.",
+  "ERR_UNKNOWN_IMG_FORMAT": "Bilinmeyen resim biçimi '{0}'.",
+  "ERR_FAILED_GENERATE_SPRITE_SHEET": "Spritesheet oluşturulamadı",
+  "ERR_NO_RENDERER": "Animasyon oluşturucu bulunamadı.",
+  "ERR_RENDERING_FAILED": "Oluşturma başarısız oldu",
+  "ENABLE_ANALYTICS": "Anonim analiz gönder",
+  "ANALYTICS_INFO": "PixiEditor'ı geliştirmek için anonim kullanım verileri topluyoruz. Hiçbir kişisel veri toplanmaz.",
+  "LANGUAGE_INFO": "Tüm çeviriler topluluk tarafından yapılmaktadır. Daha fazla bilgi için Discord sunucumuza katılın.",
+  "UP_TO_DATE_UNKNOWN": "Güncellemeler kontrol edilemedi",
+  "UP_TO_DATE": "PixiEditor güncel",
+  "UPDATE_AVAILABLE": "{0} güncellemesi mevcut",
+  "CHECKING_UPDATES": "Güncellemeler kontrol ediliyor...",
+  "UPDATE_FAILED_DOWNLOAD": "Güncelleme indirilemedi",
+  "UPDATE_READY_TO_INSTALL": "Güncelleme hazır. {0} sürümüne geçilsin mi?",
+  "SWITCH_TO_NEW_VERSION": "Geç",
+  "DOWNLOAD_UPDATE": "İndir",
+  "DOWNLOADING_UPDATE": "Güncelleme indiriliyor...",
+  "CHECKING_FOR_UPDATES": "Güncellemeler kontrol ediliyor...",
+  "PAINT_SHAPE_SETTING": "Fırça şekli",
+  "BOOL_OPERATION_NODE": "Boole İşlemi",
+  "FIRST_SHAPE": "İlk şekil",
+  "SECOND_SHAPE": "İkinci şekil",
+  "OPERATION": "İşlem",
+  "UNION_VECTOR_PATH_OP": "Birleşim",
+  "DIFFERENCE_VECTOR_PATH_OP": "Fark",
+  "INTERSECT_VECTOR_PATH_OP": "Kesişim",
+  "XOR_VECTOR_PATH_OP": "XOR",
+  "REVERSE_DIFFERENCE_VECTOR_PATH_OP": "Ters Fark",
+  "NO_DOCUMENT_OPEN": "Burada hiçbir şey yok",
+  "EMPTY_DOCUMENT_ACTION_BTN": "Oluşturmaya başla",
+  "ONBOARDING_TITLE": "Hoş geldiniz",
+  "ONBOARDING_DESCRIPTION": "Çalışma alanınızı ayarlayalım!",
+  "ONBOARDING_SKIP_BTN": "Atla",
+  "ONBOARDING_ACTION_BTN": "Hadi başlayalım",
+  "ONB_SELECT_PRIMARY_TOOLSET": "Birincil Araç Setinizi Seçin",
+  "ONB_NEXT_BTN": "İleri",
+  "ONB_FINISH_BTN": "Bitir",
+  "ONB_BACK_BTN": "Önceki",
+  "ONB_ANALYTICS": "Anonim Analiz",
+  "ONB_ALL_SET": "Her şey hazır!",
+  "ONB_ALL_SET_BTN": "Oluşturmaya başla",
+  "ANALYTICS_INFO_DETAILED": "PixiEditor, uygulamayı geliştirmek için anonim kullanım verileri toplar. Veriler hiçbir kişisel bilgi içermez. Diğer şeylerin yanı sıra, PixiEditor şunları izler:\n- Hangi araçların nasıl kullanıldığı\n- Uygulamayı ne kadar süre kullandığınız\n- Hangi komutların kullanıldığı\n- Performans verileri\n\n Ayarlardan istediğiniz zaman analizlerden çıkabilirsiniz.",
+  "PRIVACY_POLICY": "Gizlilik Politikası",
+  "ONB_SHORTCUTS": "Kısayollarınızı Seçin",
+  "GRAPH_STATE_UNABLE_TO_CREATE_MEMBER": "Mevcut Düğüm Grafiği kurulumu, seçilenin yanında yeni bir katman oluşturulmasına izin vermiyor.",
+  "PRIMARY_TOOLSET": "Birincil Araç Seti",
+  "OPEN_ONBOARDING_WINDOW": "Alıştırma penceresini aç",
+  "USER_NOT_FOUND": "Lütfen Founder's Edition'ı satın alırken kullandığınız e-postayı girin.",
+  "SESSION_NOT_VALID": "Oturum geçerli değil, lütfen tekrar giriş yapın",
+  "SESSION_NOT_FOUND": "Oturum bulunamadı, tekrar giriş yapmayı deneyin",
+  "INTERNAL_SERVER_ERROR": "Dahili bir sunucu hatası oluştu. Lütfen daha sonra tekrar deneyin.",
+  "TOO_MANY_REQUESTS": "Çok fazla istek. {0} saniye içinde tekrar deneyin.",
+  "SESSION_EXPIRED": "Oturum süresi doldu. Lütfen tekrar giriş yapın.",
+  "CONNECTION_ERROR": "Bağlantı hatası. Lütfen internet bağlantınızı kontrol edin.",
+  "FAIL_LOAD_USER_DATA": "Kaydedilmiş kullanıcı verileri yüklenemedi",
+  "LOGOUT": "Çıkış Yap",
+  "LOGGED_IN_AS": "Merhaba",
+  "EMAIL_SENT": "E-posta gönderildi! Gelen kutunuzu kontrol edin.",
+  "RESEND_ACTIVATION": "Yeniden Gönder",
+  "INVALID_TOKEN": "Oturum geçersiz veya süresi dolmuş. Lütfen tekrar giriş yapın.",
+  "ENTER_EMAIL": "E-postanızı girin",
+  "LOGIN_LINK": "Giriş Bağlantısı Gönder",
+  "LOGIN_LINK_INFO": "Giriş yapmanız için size güvenli bir bağlantı e-postayla göndereceğiz. Şifre gerekmez.",
+  "ACCOUNT_WINDOW_TITLE": "Hesap",
+  "CONNECTION_TIMEOUT": "Bağlantı zaman aşımına uğradı. Lütfen tekrar deneyin.",
+  "OPEN_ACCOUNT_WINDOW": "Hesabı Yönet",
+  "AUTO_SCALE_BACKGROUND": "Arka planı otomatik ölçeklendir",
+  "UPDATES": "Güncellemeler",
+  "SCENE": "Sahne",
+  "CUSTOM_BACKGROUND_SCALE": "Özel arka plan ölçeği",
+  "PRIMARY_BG_COLOR": "Birincil arka plan rengi",
+  "SECONDARY_BG_COLOR": "İkincil arka plan rengi",
+  "RESET": "Sıfırla",
+  "INSTALL": "Yükle",
+  "MANAGE_ACCOUNT": "Yönet",
+  "OWNED_PRODUCTS": "Sahip Olunan İçerik",
+  "INSTALLING": "Yükleniyor",
+  "INSTALLED": "Yüklendi",
+  "ACCOUNT_PROVIDER_INFO": "Hesap şunun tarafından yönetiliyor",
+  "UPDATE": "Güncelle",
+  "AUTOSAVE_OPEN_FOLDER": "Otomatik kaydetme klasörünü aç",
+  "AUTOSAVE_OPEN_FOLDER_DESCRIPTIVE": "Otomatik kayıtların saklandığı klasörü aç",
+  "AUTOSAVE_TOGGLE_DESCRIPTIVE": "Otomatik kaydetmeyi etkinleştir/devre dışı bırak",
+  "FOUNDERS_BUNDLE": "Kurucu Paketi",
+  "FOUNDERS_BUNDLE_SUBTEXT": "PixiEditor'ı destekleyin ve üretkenliğinizi artırın!",
+  "BECOME_A_FOUNDER": "Kurucu Ol",
+  "LOGIN": "Giriş Yap",
+  "NOT_FOUNDER_YET": "Henüz Kurucu değil misiniz?",
+  "ERROR_GRAPH": "Grafik kurulumu bir hata üretti. Düğüm grafiğinde düzeltin",
+  "COLOR_MATRIX_FILTER_NODE": "Renk Matris Filtresi",
+  "WORKSPACE": "Çalışma Alanı",
+  "EXPORT_ZONE_NODE": "Dışa Aktarma Bölgesi",
+  "IS_DEFAULT_EXPORT": "Varsayılan Dışa Aktarma mı",
+  "EXPORT_OUTPUT": "Dışa Aktarma Çıktısı",
+  "RENDER_OUTPUT_SIZE": "Oluşturma Çıktı Boyutu",
+  "RENDER_OUTPUT_CENTER": "Oluşturma Çıktı Merkezi",
+  "COLOR_PICKER": "Renk Seçici",
+  "UNAUTHORIZED_ACCESS": "Yetkisiz erişim",
+  "SEPARATE_SHAPES": "Şekilleri Ayır",
+  "SEPARATE_SHAPES_DESCRIPTIVE": "Mevcut vektörden şekilleri ayrı katmanlara ayır",
+  "TEXT": "Metin",
+  "EXTRACT_SELECTED_TEXT": "Seçili metni çıkar",
+  "EXTRACT_SELECTED_TEXT_DESCRIPTIVE": "Seçili metni yeni katmana çıkar.",
+  "EXTRACT_SELECTED_CHARACTERS": "Seçili karakterleri çıkar",
+  "EXTRACT_SELECTED_CHARACTERS_DESCRIPTIVE": "Seçimden bireysel karakterleri yeni katmanlara çıkar.",
+  "STEP_START": "En yakın kareye geri adım at",
+  "STEP_END": "En yakın kareye ileri adım at",
+  "STEP_FORWARD": "Bir kare ileri adım at",
+  "STEP_BACK": "Bir kare geri adım at",
+  "ANIMATION_QUALITY_PRESET": "Kalite Önayarı",
+  "VERY_LOW_QUALITY_PRESET": "Çok Düşük",
+  "LOW_QUALITY_PRESET": "Düşük",
+  "MEDIUM_QUALITY_PRESET": "Orta",
+  "HIGH_QUALITY_PRESET": "Yüksek",
+  "VERY_HIGH_QUALITY_PRESET": "Çok Yüksek",
+  "EXPORT_FRAMES": "Kareleri Dışa Aktar",
+  "NORMALIZE_OFFSET": "Ofseti Normalleştir",
+  "TANGENT": "Teğet",
+  "EVALUATE_PATH_NODE": "Yolu Değerlendir",
+  "OLD_MIN": "Eski Min",
+  "OLD_MAX": "Eski Maks",
+  "NEW_MIN": "Yeni Min",
+  "NEW_MAX": "Yeni Maks",
+  "REMAP_NODE": "Yeniden Haritala",
+  "TEXT_TOOL_ACTION_DISPLAY": "Yeni bir metin eklemek için tuvale tıklayın (boyutu ayarlamak için tıklarken sürükleyin). Düzenlemek için mevcut metne tıklayın.",
+  "PASTE_CELS": "Kareleri yapıştır",
+  "SCALE_X": "Ölçek X",
+  "SCALE_Y": "Ölçek Y",
+  "TRANSLATE_X": "Öteleme X",
+  "TRANSLATE_Y": "Öteleme Y",
+  "SKEW_X": "Eğme X",
+  "SKEW_Y": "Eğme Y",
+  "PERSPECTIVE_0": "Perspektif 0",
+  "PERSPECTIVE_1": "Perspektif 1",
+  "PERSPECTIVE_2": "Perspektif 2",
+  "COMPOSE_MATRIX": "Matris Oluştur",
+  "DECOMPOSE_MATRIX": "Matrisi Ayrıştır",
+  "NORMALIZE_COORDINATES": "Koordinatları Normalleştir",
+  "TRANSFORMED_POSITION": "Dönüştürülmüş Konum",
+  "ACCOUNT_PROVIDER_NOT_AVAILABLE": "PixiEditor'ın bu yapısı hesapları desteklemiyor. Hesabınızı yönetmek için pixieditor.net'teki resmi yapıyı kullanın.",
+  "STEAM_OFFLINE": "Hesap doğrulanamıyor. Steam çevrimdışı. Steam istemcisinin çalıştığından ve giriş yaptığınızdan emin olun.",
+  "OPEN_COMMAND_DEBUG_WINDOW": "Komut hata ayıklama penceresini aç"
+}

+ 10 - 3
src/PixiEditor/Data/Localization/LocalizationData.json

@@ -27,7 +27,7 @@
       "code": "es",
       "code": "es",
       "localeFileName": "es.json",
       "localeFileName": "es.json",
       "iconFileName": "es.png",
       "iconFileName": "es.png",
-      "lastUpdated": "2023-05-17 16:46:19"
+      "lastUpdated": "2025-09-07 02:23:00"
     },
     },
     {
     {
       "name": "中文",
       "name": "中文",
@@ -42,7 +42,7 @@
       "code": "ru",
       "code": "ru",
       "localeFileName": "ru.json",
       "localeFileName": "ru.json",
       "iconFileName": "ru.png",
       "iconFileName": "ru.png",
-      "lastUpdated": "2025-06-30 11:13:26"
+      "lastUpdated": "2025-07-28 08:51:44"
     },
     },
     {
     {
       "name": "Українська",
       "name": "Українська",
@@ -57,7 +57,7 @@
       "localeFileName": "ar.json",
       "localeFileName": "ar.json",
       "iconFileName": "ar.png",
       "iconFileName": "ar.png",
       "rightToLeft": true,
       "rightToLeft": true,
-      "lastUpdated": "2025-06-04 18:20:46"
+      "lastUpdated": "2025-09-06 22:22:52"
     },
     },
     {
     {
       "name": "Čeština",
       "name": "Čeština",
@@ -79,6 +79,13 @@
       "localeFileName": "hu.json",
       "localeFileName": "hu.json",
       "iconFileName": "hu.png",
       "iconFileName": "hu.png",
       "lastUpdated": "2023-05-08 22:07:37"
       "lastUpdated": "2023-05-08 22:07:37"
+    },
+    {
+      "name": "Türkçe",
+      "code": "tr",
+      "localeFileName": "tr.json",
+      "iconFileName": "tr.png",
+      "lastUpdated": "2025-08-07 18:24:00"
     }
     }
   ]
   ]
 }
 }

+ 27 - 17
src/PixiEditor/Helpers/Converters/EnumToLocalizedStringConverter.cs

@@ -1,4 +1,6 @@
-using System.Globalization;
+using System.Diagnostics;
+using System.Globalization;
+using System.Reflection;
 using PixiEditor.Extensions.Helpers;
 using PixiEditor.Extensions.Helpers;
 using PixiEditor.UI.Common.Localization;
 using PixiEditor.UI.Common.Localization;
 
 
@@ -6,30 +8,38 @@ namespace PixiEditor.Helpers.Converters;
 
 
 internal class EnumToLocalizedStringConverter : SingleInstanceConverter<EnumToLocalizedStringConverter>
 internal class EnumToLocalizedStringConverter : SingleInstanceConverter<EnumToLocalizedStringConverter>
 {
 {
+    private Dictionary<object, string> enumTranslations = new(
+        typeof(EnumToLocalizedStringConverter).Assembly
+            .GetCustomAttributes()
+            .OfType<ILocalizeEnumInfo>()
+            .Select(x => new KeyValuePair<object, string>(x.GetEnumValue(), x.LocalizationKey)));
+    
     public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
     public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
     {
     {
-        if (value is Enum enumValue)
+        if (value is not Enum enumValue)
         {
         {
-            if (EnumHelpers.HasDescription(enumValue))
-            {
-                return EnumHelpers.GetDescription(enumValue);
-            }
+            return value;
+        }
+
+        if (enumTranslations.TryGetValue(enumValue, out var assemblyDefinedKey))
+        {
+            return assemblyDefinedKey;
+        }
 
 
-            return ToLocalizedStringFormat(enumValue);
+        if (EnumHelpers.HasDescription(enumValue))
+        {
+            return EnumHelpers.GetDescription(enumValue);
         }
         }
 
 
-        return value;
+        ThrowUntranslatedEnumValue(enumValue);
+        return enumValue;
     }
     }
 
 
-    private string ToLocalizedStringFormat(Enum enumValue)
+    [Conditional("DEBUG")]
+    private static void ThrowUntranslatedEnumValue(object value)
     {
     {
-        // VALUE_ENUMTYPE
-        // for example BlendMode.Normal becomes NORMAL_BLEND_MODE
-
-        string enumType = enumValue.GetType().Name;
-
-        string value = enumValue.ToString();
-
-        return $"{value.ToSnakeCase()}_{enumType.ToSnakeCase()}".ToUpper();
+        throw new ArgumentException(
+            $"Enum value '{value.GetType()}.{value}' has no value defined. Either add a Description attribute to the enum values or a LocalizeEnum attribute in EnumTranslations.cs for third party enums",
+            nameof(value));
     }
     }
 }
 }

+ 0 - 19
src/PixiEditor/Helpers/EnumDescriptionConverter.cs

@@ -1,19 +0,0 @@
-using System.Globalization;
-using PixiEditor.Extensions.Helpers;
-using PixiEditor.Helpers.Converters;
-using PixiEditor.UI.Common.Localization;
-
-namespace PixiEditor.Helpers;
-
-internal class EnumDescriptionConverter : SingleInstanceConverter<EnumDescriptionConverter>
-{
-    public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
-    {
-        if (value is Enum enumValue)
-        {
-            return EnumHelpers.GetDescription(enumValue);
-        }
-
-        return value;
-    }
-}

+ 135 - 1
src/PixiEditor/Helpers/Extensions/EnumerableExtensions.cs

@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System.Buffers;
+using System.Collections.Generic;
 
 
 namespace PixiEditor.Helpers.Extensions;
 namespace PixiEditor.Helpers.Extensions;
 
 
@@ -95,4 +96,137 @@ internal static class EnumerableExtensions
 
 
         return IndexOrNext(collection, predicate, index, false);
         return IndexOrNext(collection, predicate, index, false);
     }
     }
+
+    public static T IndexOrNextInDirection<T>(this IEnumerable<T> collection, Predicate<T> predicate, int index, NextToDirection direction, bool overrun = true) => direction switch
+    {
+        NextToDirection.Forwards => IndexOrNext(collection, predicate, index, overrun),
+        NextToDirection.Backwards => IndexOrPrevious(collection, predicate, index, overrun),
+        _ => throw new ArgumentOutOfRangeException(nameof(direction)),
+    };
+    
+    /// <summary>
+    /// Returns the element that comes immediately after the specified <paramref name="index"/> 
+    /// in the given <paramref name="enumerable"/>, wrapping around to the first element if 
+    /// the end of the sequence is reached.
+    /// </summary>
+    /// <typeparam name="T">The type of the elements in the enumerable.</typeparam>
+    /// <param name="enumerable">The source enumerable.</param>
+    /// <param name="index">The index of the reference element. Must be non-negative.</param>
+    /// <returns>
+    /// The element immediately after the specified index, or the first element if the index 
+    /// refers to the last element. Returns <c>default</c> if the enumerable is empty.
+    /// </returns>
+    /// <remarks>
+    /// This method does not check whether the specified <paramref name="index"/> is within the 
+    /// bounds of the enumerable's size. Passing a positive out-of-range index may yield unexpected results.
+    /// </remarks>
+    /// <exception cref="ArgumentNullException">Thrown if <paramref name="enumerable"/> is <c>null</c>.</exception>
+    /// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="index"/> is negative.</exception>
+    public static T? WrapNextAfterIndex<T>(this IEnumerable<T> enumerable, int index)
+    {
+        ArgumentOutOfRangeException.ThrowIfNegative(index);
+        ArgumentNullException.ThrowIfNull(enumerable);
+
+        switch (enumerable)
+        {
+            case ICollection<T> collection:
+                return NextWithKnownCount(collection, index, collection.Count);
+            case IReadOnlyCollection<T> readOnlyCollection:
+                return NextWithKnownCount(readOnlyCollection, index, readOnlyCollection.Count);
+        }
+
+        using var enumerator = enumerable.GetEnumerator();
+
+        // If the enumerable is empty, return null
+        if (!enumerator.MoveNext())
+            return default;
+
+        var steps = index + 1;
+        var firstElement = enumerator.Current;
+
+        while (steps-- > 0)
+        {
+            if (!enumerator.MoveNext())
+                return firstElement;
+        }
+        
+        return enumerator.Current;
+    }
+
+    /// <summary>
+    /// Returns the element that comes immediately before the specified <paramref name="index"/> 
+    /// in the given <paramref name="enumerable"/>, wrapping around to the last element if 
+    /// the start of the sequence is reached.
+    /// </summary>
+    /// <typeparam name="T">The type of the elements in the enumerable.</typeparam>
+    /// <param name="enumerable">The source enumerable.</param>
+    /// <param name="index">The index of the reference element. Must be non-negative.</param>
+    /// <returns>
+    /// The element immediately before the specified index, or the last element if the index 
+    /// is <c>0</c>. Returns <c>default</c> if the enumerable is empty.
+    /// </returns>
+    /// <remarks>
+    /// This method does not check whether the specified <paramref name="index"/> is within the 
+    /// bounds of the enumerable's size. Passing a positive out-of-range index may yield unexpected results.
+    /// </remarks>
+    /// <exception cref="ArgumentNullException">Thrown if <paramref name="enumerable"/> is <c>null</c>.</exception>
+    /// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="index"/> is negative.</exception>
+    public static T? WrapPreviousBeforeIndex<T>(this IEnumerable<T> enumerable, int index)
+    {
+        ArgumentOutOfRangeException.ThrowIfNegative(index);
+        ArgumentNullException.ThrowIfNull(enumerable);
+        
+        return index == 0
+            ? enumerable.LastOrDefault()
+            : enumerable.ElementAtOrDefault(index - 1);
+    }
+
+    /// <summary>
+    /// Returns the element next to the specified <paramref name="index"/> in the given 
+    /// <paramref name="enumerable"/>, in the direction specified by <paramref name="direction"/>, 
+    /// wrapping around if necessary.
+    /// </summary>
+    /// <typeparam name="T">The type of the elements in the enumerable.</typeparam>
+    /// <param name="enumerable">The source enumerable.</param>
+    /// <param name="index">The index of the reference element. Must be non-negative.</param>
+    /// <param name="direction">
+    /// The direction in which to look for the next element (forwards or backwards).
+    /// </param>
+    /// <returns>
+    /// The element next to the specified index in the chosen direction, with wrap-around behavior.
+    /// Returns <c>default</c> if the enumerable is empty.
+    /// </returns>
+    /// <remarks>
+    /// This method does not check whether the specified <paramref name="index"/> is within the 
+    /// bounds of the enumerable's size. Passing a positive out-of-range index may yield unexpected results.
+    /// </remarks>
+    /// <exception cref="ArgumentNullException">Thrown if <paramref name="enumerable"/> is <c>null</c>.</exception>
+    /// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="index"/> is negative.</exception>
+    /// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="direction"/> is not a valid <see cref="NextToDirection"/> value.</exception>
+    public static T? WrapInDirectionOfIndex<T>(this IEnumerable<T> enumerable, int index, NextToDirection direction) =>
+        direction switch
+        {
+            NextToDirection.Forwards => WrapNextAfterIndex(enumerable, index),
+            NextToDirection.Backwards => WrapPreviousBeforeIndex(enumerable, index),
+            _ => throw new ArgumentOutOfRangeException(nameof(direction)),
+        };
+
+    private static T? NextWithKnownCount<T>(IEnumerable<T> collection, int index, int count)
+    {
+        if (count == 0)
+            return default;
+        
+        var newIndex = index + 1;
+
+        if (newIndex < 0 || newIndex >= count)
+            newIndex = newIndex < 0 ? count - 1 : 0;
+        
+        return collection.ElementAtOrDefault(newIndex);
+    }
+}
+
+enum NextToDirection
+{
+    Forwards = 1,
+    Backwards = -1
 }
 }

+ 18 - 0
src/PixiEditor/Helpers/LocalizeEnumAttribute.cs

@@ -0,0 +1,18 @@
+namespace PixiEditor.Helpers;
+
+public interface ILocalizeEnumInfo
+{
+    public object GetEnumValue();
+    
+    public string LocalizationKey { get; }
+}
+
+[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
+public class LocalizeEnumAttribute<T>(T value, string key) : Attribute, ILocalizeEnumInfo where T : Enum
+{
+    public T Value { get; } = value;
+
+    object ILocalizeEnumInfo.GetEnumValue() => Value;
+    
+    public string LocalizationKey { get; } = key;
+}

+ 26 - 7
src/PixiEditor/Models/DocumentModels/ActionAccumulator.cs

@@ -6,6 +6,7 @@ using PixiEditor.ChangeableDocument.Actions.Generated;
 using PixiEditor.ChangeableDocument.Actions.Undo;
 using PixiEditor.ChangeableDocument.Actions.Undo;
 using PixiEditor.ChangeableDocument.ChangeInfos;
 using PixiEditor.ChangeableDocument.ChangeInfos;
 using Drawie.Backend.Core.Bridge;
 using Drawie.Backend.Core.Bridge;
+using PixiEditor.Extensions.CommonApi.UserPreferences.Settings.PixiEditor;
 using PixiEditor.Helpers;
 using PixiEditor.Helpers;
 using PixiEditor.Models.DocumentPassthroughActions;
 using PixiEditor.Models.DocumentPassthroughActions;
 using PixiEditor.Models.Handlers;
 using PixiEditor.Models.Handlers;
@@ -104,7 +105,8 @@ internal class ActionAccumulator
                 queuedActions = new();
                 queuedActions = new();
 
 
                 List<IChangeInfo?> changes;
                 List<IChangeInfo?> changes;
-                if (AreAllPassthrough(toExecute))
+                bool allPassthrough = AreAllPassthrough(toExecute);
+                if (allPassthrough)
                 {
                 {
                     changes = toExecute.Select(a => (IChangeInfo?)a.action).ToList();
                     changes = toExecute.Select(a => (IChangeInfo?)a.action).ToList();
                 }
                 }
@@ -119,6 +121,8 @@ internal class ActionAccumulator
                         action.action is ChangeBoundary_Action or Redo_Action or Undo_Action);
                         action.action is ChangeBoundary_Action or Redo_Action or Undo_Action);
                 bool viewportRefreshRequest =
                 bool viewportRefreshRequest =
                     toExecute.Any(static action => action.action is RefreshViewport_PassthroughAction);
                     toExecute.Any(static action => action.action is RefreshViewport_PassthroughAction);
+                bool refreshPreviewsRequest =
+                    toExecute.Any(static action => action.action is RefreshPreviews_PassthroughAction);
                 bool changeFrameRequest =
                 bool changeFrameRequest =
                     toExecute.Any(static action => action.action is SetActiveFrame_PassthroughAction);
                     toExecute.Any(static action => action.action is SetActiveFrame_PassthroughAction);
 
 
@@ -130,10 +134,12 @@ internal class ActionAccumulator
                 if (undoBoundaryPassed)
                 if (undoBoundaryPassed)
                     internals.Updater.AfterUndoBoundaryPassed();
                     internals.Updater.AfterUndoBoundaryPassed();
 
 
+
                 var affectedAreas = new AffectedAreasGatherer(document.AnimationHandler.ActiveFrameTime,
                 var affectedAreas = new AffectedAreasGatherer(document.AnimationHandler.ActiveFrameTime,
                     internals.Tracker,
                     internals.Tracker,
-                    optimizedChanges);
-                if (DrawingBackendApi.Current.IsHardwareAccelerated)
+                    optimizedChanges, refreshPreviewsRequest);
+
+                if (DrawingBackendApi.Current.IsHardwareAccelerated && !allPassthrough)
                 {
                 {
                     canvasUpdater.UpdateGatheredChunksSync(affectedAreas,
                     canvasUpdater.UpdateGatheredChunksSync(affectedAreas,
                         undoBoundaryPassed || viewportRefreshRequest);
                         undoBoundaryPassed || viewportRefreshRequest);
@@ -144,10 +150,21 @@ internal class ActionAccumulator
                         undoBoundaryPassed || viewportRefreshRequest);
                         undoBoundaryPassed || viewportRefreshRequest);
                 }*/
                 }*/
 
 
-                previewUpdater.UpdatePreviews(
-                    affectedAreas.ChangedMembers,
-                    affectedAreas.ChangedMasks,
-                    affectedAreas.ChangedNodes, affectedAreas.ChangedKeyFrames);
+                bool previewsDisabled = PixiEditorSettings.Performance.DisablePreviews.Value;
+
+                if (!previewsDisabled)
+                {
+                    if (undoBoundaryPassed || viewportRefreshRequest || changeFrameRequest ||
+                        document.SizeBindable.LongestAxis <= LiveUpdatePerformanceThreshold)
+                    {
+                        previewUpdater.UpdatePreviews(
+                            affectedAreas.ChangedMembers,
+                            affectedAreas.ChangedMasks,
+                            affectedAreas.ChangedNodes, affectedAreas.ChangedKeyFrames,
+                            affectedAreas.IgnoreAnimationPreviews,
+                            undoBoundaryPassed || refreshPreviewsRequest);
+                    }
+                }
 
 
                 // force refresh viewports for better responsiveness
                 // force refresh viewports for better responsiveness
                 foreach (var (_, value) in internals.State.Viewports)
                 foreach (var (_, value) in internals.State.Viewports)
@@ -175,6 +192,8 @@ internal class ActionAccumulator
         executing = false;
         executing = false;
     }
     }
 
 
+    private const int LiveUpdatePerformanceThreshold = 2048;
+
     private bool AreAllPassthrough(List<(ActionSource, IAction)> actions)
     private bool AreAllPassthrough(List<(ActionSource, IAction)> actions)
     {
     {
         foreach (var action in actions)
         foreach (var action in actions)

+ 2 - 1
src/PixiEditor/Models/DocumentModels/DocumentStructureHelper.cs

@@ -8,6 +8,7 @@ using PixiEditor.ChangeableDocument.Enums;
 using PixiEditor.Models.Handlers;
 using PixiEditor.Models.Handlers;
 using PixiEditor.Models.Layers;
 using PixiEditor.Models.Layers;
 using PixiEditor.UI.Common.Localization;
 using PixiEditor.UI.Common.Localization;
+using PixiEditor.ViewModels.Nodes;
 
 
 namespace PixiEditor.Models.DocumentModels;
 namespace PixiEditor.Models.DocumentModels;
 #nullable enable
 #nullable enable
@@ -34,7 +35,7 @@ internal class DocumentStructureHelper
                     count++;
                     count++;
             }
             }
 
 
-            return true;
+            return Traverse.Further;
         });
         });
         return $"{name} {count}";
         return $"{name} {count}";
     }
     }

+ 9 - 0
src/PixiEditor/Models/DocumentModels/DocumentTransformMode.cs

@@ -3,12 +3,21 @@
 namespace PixiEditor.Models.DocumentModels;
 namespace PixiEditor.Models.DocumentModels;
 internal enum DocumentTransformMode
 internal enum DocumentTransformMode
 {
 {
+    // Comments show localization in DocumentTransformViewModel.cs, comments are needed for localization key check pipeline
+    
+    // TRANSFORM_ACTION_DISPLAY_SCALE_NOROTATE_NOSHEAR_NOPERSPECTIVE
     [Description("SCALE_NOROTATE_NOSHEAR_NOPERSPECTIVE")]
     [Description("SCALE_NOROTATE_NOSHEAR_NOPERSPECTIVE")]
     Scale_NoRotate_NoShear_NoPerspective,
     Scale_NoRotate_NoShear_NoPerspective,
+    
+    // TRANSFORM_ACTION_DISPLAY_SCALE_ROTATE_NOSHEAR_NOPERSPECTIVE
     [Description("SCALE_ROTATE_NOSHEAR_NOPERSPECTIVE")]
     [Description("SCALE_ROTATE_NOSHEAR_NOPERSPECTIVE")]
     Scale_Rotate_NoShear_NoPerspective,
     Scale_Rotate_NoShear_NoPerspective,
+    
+    // TRANSFORM_ACTION_DISPLAY_SCALE_ROTATE_SHEAR_NOPERSPECTIVE
     [Description("SCALE_ROTATE_SHEAR_NOPERSPECTIVE")]
     [Description("SCALE_ROTATE_SHEAR_NOPERSPECTIVE")]
     Scale_Rotate_Shear_NoPerspective,
     Scale_Rotate_Shear_NoPerspective,
+    
+    // TRANSFORM_ACTION_DISPLAY_SCALE_ROTATE_SHEAR_PERSPECTIVE
     [Description("SCALE_ROTATE_SHEAR_PERSPECTIVE")]
     [Description("SCALE_ROTATE_SHEAR_PERSPECTIVE")]
     Scale_Rotate_Shear_Perspective
     Scale_Rotate_Shear_Perspective
 }
 }

+ 3 - 2
src/PixiEditor/Models/DocumentModels/Public/DocumentOperationsModule.cs

@@ -21,6 +21,7 @@ using PixiEditor.Models.Position;
 using PixiEditor.Models.Tools;
 using PixiEditor.Models.Tools;
 using Drawie.Numerics;
 using Drawie.Numerics;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+using PixiEditor.ViewModels.Nodes;
 
 
 namespace PixiEditor.Models.DocumentModels.Public;
 namespace PixiEditor.Models.DocumentModels.Public;
 #nullable enable
 #nullable enable
@@ -617,10 +618,10 @@ internal class DocumentOperationsModule : IDocumentOperations
             if (!members.Contains(traversedNode.Id))
             if (!members.Contains(traversedNode.Id))
             {
             {
                 parent = traversedNode;
                 parent = traversedNode;
-                return false;
+                return Traverse.Exit;
             }
             }
 
 
-            return true;
+            return Traverse.Further;
         });
         });
 
 
         if (parent is null)
         if (parent is null)

+ 16 - 15
src/PixiEditor/Models/DocumentModels/Public/DocumentStructureModule.cs

@@ -1,5 +1,6 @@
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.Models.Handlers;
 using PixiEditor.Models.Handlers;
+using PixiEditor.ViewModels.Nodes;
 
 
 namespace PixiEditor.Models.DocumentModels.Public;
 namespace PixiEditor.Models.DocumentModels.Public;
 #nullable enable
 #nullable enable
@@ -45,10 +46,10 @@ internal class DocumentStructureModule
             if (!guids.Contains(traversedNode.Id) && traversedNode is IStructureMemberHandler)
             if (!guids.Contains(traversedNode.Id) && traversedNode is IStructureMemberHandler)
             {
             {
                 parent = traversedNode;
                 parent = traversedNode;
-                return false;
+                return Traverse.Exit;
             }
             }
 
 
-            return true;
+            return Traverse.Further;
         });
         });
 
 
         if (parent is null)
         if (parent is null)
@@ -62,10 +63,10 @@ internal class DocumentStructureModule
                 if (!guids.Contains(traversedNode.Id) && traversedNode is IStructureMemberHandler)
                 if (!guids.Contains(traversedNode.Id) && traversedNode is IStructureMemberHandler)
                 {
                 {
                     parent = traversedNode;
                     parent = traversedNode;
-                    return false;
+                    return Traverse.Exit;
                 }
                 }
 
 
-                return true;
+                return Traverse.Further;
             });
             });
         }
         }
 
 
@@ -110,7 +111,7 @@ internal class DocumentStructureModule
         {
         {
             if (node is IStructureMemberHandler parent && input is { PropertyName: FolderNode.ContentInternalName })
             if (node is IStructureMemberHandler parent && input is { PropertyName: FolderNode.ContentInternalName })
                 parents.Add(parent);
                 parents.Add(parent);
-            return true;
+            return Traverse.Further;
         });
         });
 
 
         return parents;
         return parents;
@@ -187,7 +188,7 @@ internal class DocumentStructureModule
                 toFill.Add(strNode);
                 toFill.Add(strNode);
             }
             }
 
 
-            return true;
+            return Traverse.Further;
         });
         });
     }
     }
 
 
@@ -197,10 +198,10 @@ internal class DocumentStructureModule
         startNode.TraverseForwards(node =>
         startNode.TraverseForwards(node =>
         {
         {
             if (node == startNode)
             if (node == startNode)
-                return true;
+                return Traverse.Further;
 
 
             result = node;
             result = node;
-            return false;
+            return Traverse.Exit;
         });
         });
 
 
         return result;
         return result;
@@ -218,13 +219,13 @@ internal class DocumentStructureModule
             if (node != member && node is IStructureMemberHandler structureMemberNode)
             if (node != member && node is IStructureMemberHandler structureMemberNode)
             {
             {
                 if (node is IFolderHandler && !includeFolders)
                 if (node is IFolderHandler && !includeFolders)
-                    return true;
+                    return Traverse.Further;
 
 
                 result = structureMemberNode;
                 result = structureMemberNode;
-                return false;
+                return Traverse.Exit;
             }
             }
 
 
-            return true;
+            return Traverse.Further;
         });
         });
 
 
         return result;
         return result;
@@ -242,13 +243,13 @@ internal class DocumentStructureModule
             if (node != member && node is IStructureMemberHandler structureMemberNode)
             if (node != member && node is IStructureMemberHandler structureMemberNode)
             {
             {
                 if (node is IFolderHandler && !includeFolders)
                 if (node is IFolderHandler && !includeFolders)
-                    return true;
+                    return Traverse.Further;
 
 
                 result = structureMemberNode;
                 result = structureMemberNode;
-                return false;
+                return Traverse.Exit;
             }
             }
 
 
-            return true;
+            return Traverse.Further;
         });
         });
 
 
         return result;
         return result;
@@ -268,7 +269,7 @@ internal class DocumentStructureModule
             if (node is IStructureMemberHandler structureMemberNode)
             if (node is IStructureMemberHandler structureMemberNode)
                 children.Add(structureMemberNode);
                 children.Add(structureMemberNode);
 
 
-            return true;
+            return Traverse.Further;
         });
         });
 
 
         return children;
         return children;

+ 7 - 0
src/PixiEditor/Models/DocumentPassthroughActions/RefreshPreviews_PassthroughAction.cs

@@ -0,0 +1,7 @@
+using PixiEditor.ChangeableDocument.Actions;
+using PixiEditor.ChangeableDocument.ChangeInfos;
+using PixiEditor.Models.Position;
+
+namespace PixiEditor.Models.DocumentPassthroughActions;
+
+internal record class RefreshPreviews_PassthroughAction() : IAction, IChangeInfo;

+ 126 - 0
src/PixiEditor/Models/EnumTranslations.cs

@@ -0,0 +1,126 @@
+using Drawie.Backend.Core.Shaders.Generation;
+using Drawie.Backend.Core.Surfaces;
+using Drawie.Backend.Core.Surfaces.PaintImpl;
+using Drawie.Backend.Core.Vector;
+using PixiEditor.AnimationRenderer.Core;
+using PixiEditor.ChangeableDocument.Changeables.Graph.ColorSpaces;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Animable;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.CombineSeparate;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Effects;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.FilterNodes;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Matrix;
+using PixiEditor.Helpers;
+using PixiEditor.Models.Handlers.Toolbars;
+using PixiEditor.Models.Tools;
+using PixiEditor.Views.Overlays.BrushShapeOverlay;
+using BlendMode = PixiEditor.ChangeableDocument.Enums.BlendMode;
+
+[assembly: LocalizeEnum<StrokeCap>(StrokeCap.Butt, "BUTT_STROKE_CAP")]
+[assembly: LocalizeEnum<StrokeCap>(StrokeCap.Round, "ROUND_STROKE_CAP")]
+[assembly: LocalizeEnum<StrokeCap>(StrokeCap.Square, "SQUARE_STROKE_CAP")]
+
+[assembly: LocalizeEnum<StrokeJoin>(StrokeJoin.Bevel, "BEVEL_STROKE_JOIN")]
+[assembly: LocalizeEnum<StrokeJoin>(StrokeJoin.Round, "ROUND_STROKE_JOIN")]
+[assembly: LocalizeEnum<StrokeJoin>(StrokeJoin.Miter, "MITER_STROKE_JOIN")]
+
+[assembly: LocalizeEnum<GrayscaleNode.GrayscaleMode>(GrayscaleNode.GrayscaleMode.Weighted, "WEIGHTED_GRAYSCALE_MODE")]
+[assembly: LocalizeEnum<GrayscaleNode.GrayscaleMode>(GrayscaleNode.GrayscaleMode.Average, "AVERAGE_GRAYSCALE_MODE")]
+[assembly: LocalizeEnum<GrayscaleNode.GrayscaleMode>(GrayscaleNode.GrayscaleMode.Custom, "CUSTOM_GRAYSCALE_MODE")]
+
+[assembly: LocalizeEnum<ColorSampleMode>(ColorSampleMode.ColorManaged, "COLOR_MANAGED_COLOR_SAMPLE_MODE")]
+[assembly: LocalizeEnum<ColorSampleMode>(ColorSampleMode.Raw, "RAW_COLOR_SAMPLE_MODE")]
+
+[assembly: LocalizeEnum<TileMode>(TileMode.Clamp, "CLAMP_TILE_MODE")]
+[assembly: LocalizeEnum<TileMode>(TileMode.Decal, "DECAL_TILE_MODE")]
+[assembly: LocalizeEnum<TileMode>(TileMode.Mirror, "MIRROR_TILE_MODE")]
+[assembly: LocalizeEnum<TileMode>(TileMode.Repeat, "REPEAT_TILE_MODE")]
+
+[assembly: LocalizeEnum<CombineSeparateColorMode>(CombineSeparateColorMode.RGB, "R_G_B_COMBINE_SEPARATE_COLOR_MODE")] 
+[assembly: LocalizeEnum<CombineSeparateColorMode>(CombineSeparateColorMode.HSV, "H_S_V_COMBINE_SEPARATE_COLOR_MODE")]
+[assembly: LocalizeEnum<CombineSeparateColorMode>(CombineSeparateColorMode.HSL, "H_S_L_COMBINE_SEPARATE_COLOR_MODE")]
+
+[assembly: LocalizeEnum<VectorPathOp>(VectorPathOp.Difference, "DIFFERENCE_VECTOR_PATH_OP")]
+[assembly: LocalizeEnum<VectorPathOp>(VectorPathOp.Intersect, "INTERSECT_VECTOR_PATH_OP")]
+[assembly: LocalizeEnum<VectorPathOp>(VectorPathOp.Union, "UNION_VECTOR_PATH_OP")]
+[assembly: LocalizeEnum<VectorPathOp>(VectorPathOp.Xor, "XOR_VECTOR_PATH_OP")]
+[assembly: LocalizeEnum<VectorPathOp>(VectorPathOp.ReverseDifference, "REVERSE_DIFFERENCE_VECTOR_PATH_OP")]
+
+[assembly: LocalizeEnum<QualityPreset>(QualityPreset.VeryLow, "VERY_LOW_QUALITY_PRESET")]
+[assembly: LocalizeEnum<QualityPreset>(QualityPreset.Low, "LOW_QUALITY_PRESET")]
+[assembly: LocalizeEnum<QualityPreset>(QualityPreset.Medium, "MEDIUM_QUALITY_PRESET")]
+[assembly: LocalizeEnum<QualityPreset>(QualityPreset.High, "HIGH_QUALITY_PRESET")]
+[assembly: LocalizeEnum<QualityPreset>(QualityPreset.VeryHigh, "VERY_HIGH_QUALITY_PRESET")]
+
+[assembly: LocalizeEnum<ColorSpaceType>(ColorSpaceType.Inherit, "INHERIT_COLOR_SPACE_TYPE")]
+[assembly: LocalizeEnum<ColorSpaceType>(ColorSpaceType.Srgb, "SRGB_COLOR_SPACE_TYPE")]
+[assembly: LocalizeEnum<ColorSpaceType>(ColorSpaceType.LinearSrgb, "LINEAR_SRGB_COLOR_SPACE_TYPE")]
+
+[assembly: LocalizeEnum<EasingType>(EasingType.Linear, "LINEAR_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.InSine, "IN_SINE_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.OutSine, "OUT_SINE_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.InOutSine, "IN_OUT_SINE_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.InQuad, "IN_QUAD_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.OutQuad, "OUT_QUAD_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.InOutQuad, "IN_OUT_QUAD_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.InCubic, "IN_CUBIC_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.OutCubic, "OUT_CUBIC_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.InOutCubic, "IN_OUT_CUBIC_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.InQuart, "IN_QUART_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.OutQuart, "OUT_QUART_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.InOutQuart, "IN_OUT_QUART_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.InQuint, "IN_QUINT_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.OutQuint, "OUT_QUINT_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.InOutQuint, "IN_OUT_QUINT_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.InExpo, "IN_EXPO_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.OutExpo, "OUT_EXPO_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.InOutExpo, "IN_OUT_EXPO_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.InCirc, "IN_CIRC_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.OutCirc, "OUT_CIRC_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.InOutCirc, "IN_OUT_CIRC_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.InBack, "IN_BACK_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.OutBack, "OUT_BACK_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.InOutBack, "IN_OUT_BACK_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.InElastic, "IN_ELASTIC_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.OutElastic, "OUT_ELASTIC_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.InOutElastic, "IN_OUT_ELASTIC_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.InBounce, "IN_BOUNCE_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.OutBounce, "OUT_BOUNCE_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.InOutBounce, "IN_OUT_BOUNCE_EASING_TYPE")]
+
+[assembly: LocalizeEnum<RotationType>(RotationType.Degrees, "DEGREES_ROTATION_TYPE")]
+[assembly: LocalizeEnum<RotationType>(RotationType.Radians, "RADIANS_ROTATION_TYPE")]
+
+[assembly: LocalizeEnum<NoiseType>(NoiseType.TurbulencePerlin, "TURBULENCE_PERLIN_NOISE_TYPE")]
+[assembly: LocalizeEnum<NoiseType>(NoiseType.FractalPerlin, "FRACTAL_PERLIN_NOISE_TYPE")]
+[assembly: LocalizeEnum<NoiseType>(NoiseType.Voronoi, "VORONOI_NOISE_TYPE")]
+
+[assembly: LocalizeEnum<OutlineType>(OutlineType.Simple, "SIMPLE_OUTLINE_TYPE")]
+[assembly: LocalizeEnum<OutlineType>(OutlineType.Gaussian, "GAUSSIAN_OUTLINE_TYPE")]
+[assembly: LocalizeEnum<OutlineType>(OutlineType.PixelPerfect, "PIXEL_PERFECT_OUTLINE_TYPE")]
+
+[assembly: LocalizeEnum<VoronoiFeature>(VoronoiFeature.F1, "F1_VORONOI_FEATURE")]
+[assembly: LocalizeEnum<VoronoiFeature>(VoronoiFeature.F2, "F2_VORONOI_FEATURE")]
+[assembly: LocalizeEnum<VoronoiFeature>(VoronoiFeature.F2MinusF1, "F2_MINUS_F1_VORONOI_FEATURE")]
+
+[assembly: LocalizeEnum<BlendMode>(BlendMode.Normal, "NORMAL_BLEND_MODE")]
+[assembly: LocalizeEnum<BlendMode>(BlendMode.Darken, "DARKEN_BLEND_MODE")]
+[assembly: LocalizeEnum<BlendMode>(BlendMode.Lighten, "LIGHTEN_BLEND_MODE")]
+[assembly: LocalizeEnum<BlendMode>(BlendMode.Multiply, "MULTIPLY_BLEND_MODE")]
+[assembly: LocalizeEnum<BlendMode>(BlendMode.Screen, "SCREEN_BLEND_MODE")]
+[assembly: LocalizeEnum<BlendMode>(BlendMode.Overlay, "OVERLAY_BLEND_MODE")]
+[assembly: LocalizeEnum<BlendMode>(BlendMode.HardLight, "HARD_LIGHT_BLEND_MODE")]
+[assembly: LocalizeEnum<BlendMode>(BlendMode.SoftLight, "SOFT_LIGHT_BLEND_MODE")]
+[assembly: LocalizeEnum<BlendMode>(BlendMode.ColorDodge, "COLOR_DODGE_BLEND_MODE")]
+[assembly: LocalizeEnum<BlendMode>(BlendMode.ColorBurn, "COLOR_BURN_BLEND_MODE")]
+[assembly: LocalizeEnum<BlendMode>(BlendMode.Hue, "HUE_BLEND_MODE")]
+[assembly: LocalizeEnum<BlendMode>(BlendMode.Saturation, "SATURATION_BLEND_MODE")]
+[assembly: LocalizeEnum<BlendMode>(BlendMode.Color, "COLOR_BLEND_MODE")]
+[assembly: LocalizeEnum<BlendMode>(BlendMode.Luminosity, "LUMINOSITY_BLEND_MODE")]
+[assembly: LocalizeEnum<BlendMode>(BlendMode.Difference, "DIFFERENCE_BLEND_MODE")]
+[assembly: LocalizeEnum<BlendMode>(BlendMode.Exclusion, "EXCLUSION_BLEND_MODE")]
+[assembly: LocalizeEnum<BlendMode>(BlendMode.Erase, "ERASE_BLEND_MODE")]
+[assembly: LocalizeEnum<BlendMode>(BlendMode.LinearDodge, "LINEAR_DODGE_BLEND_MODE")]
+
+[assembly: LocalizeEnum<PaintBrushShape>(PaintBrushShape.Circle, "PAINT_BRUSH_SHAPE_CIRCLE")]
+[assembly: LocalizeEnum<PaintBrushShape>(PaintBrushShape.Square, "PAINT_BRUSH_SHAPE_SQUARE")]

+ 2 - 1
src/PixiEditor/Models/Handlers/IDocument.cs

@@ -33,7 +33,7 @@ internal interface IDocument : IHandler, Extensions.CommonApi.Documents.IDocumen
     public VectorPath SelectionPathBindable { get; }
     public VectorPath SelectionPathBindable { get; }
     public INodeGraphHandler NodeGraphHandler { get; }
     public INodeGraphHandler NodeGraphHandler { get; }
     public DocumentStructureModule StructureHelper { get; }
     public DocumentStructureModule StructureHelper { get; }
-    public PreviewPainter PreviewPainter { get; set; }
+    public PreviewPainter? PreviewPainter { get; set; }
     public bool AllChangesSaved { get; }
     public bool AllChangesSaved { get; }
     public string CoordinatesString { get; set; }
     public string CoordinatesString { get; set; }
     public IReadOnlyCollection<IStructureMemberHandler> SoftSelectedStructureMembers { get; }
     public IReadOnlyCollection<IStructureMemberHandler> SoftSelectedStructureMembers { get; }
@@ -50,6 +50,7 @@ internal interface IDocument : IHandler, Extensions.CommonApi.Documents.IDocumen
     public DocumentRenderer Renderer { get; }
     public DocumentRenderer Renderer { get; }
     public ISnappingHandler SnappingHandler { get; }
     public ISnappingHandler SnappingHandler { get; }
     public IReadOnlyCollection<Guid> SelectedMembers { get; }
     public IReadOnlyCollection<Guid> SelectedMembers { get; }
+    public PreviewPainter? MiniPreviewPainter { get; set; }
     public void RemoveSoftSelectedMember(IStructureMemberHandler member);
     public void RemoveSoftSelectedMember(IStructureMemberHandler member);
     public void ClearSoftSelectedMembers();
     public void ClearSoftSelectedMembers();
     public void AddSoftSelectedMember(IStructureMemberHandler member);
     public void AddSoftSelectedMember(IStructureMemberHandler member);

+ 11 - 7
src/PixiEditor/Models/Handlers/INodeHandler.cs

@@ -1,5 +1,6 @@
 using System.Collections.ObjectModel;
 using System.Collections.ObjectModel;
 using System.ComponentModel;
 using System.ComponentModel;
+using Avalonia;
 using Avalonia.Media;
 using Avalonia.Media;
 using ChunkyImageLib;
 using ChunkyImageLib;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
@@ -8,6 +9,7 @@ using Drawie.Backend.Core;
 using PixiEditor.Models.Rendering;
 using PixiEditor.Models.Rendering;
 using PixiEditor.Models.Structures;
 using PixiEditor.Models.Structures;
 using Drawie.Numerics;
 using Drawie.Numerics;
+using PixiEditor.ViewModels.Nodes;
 
 
 namespace PixiEditor.Models.Handlers;
 namespace PixiEditor.Models.Handlers;
 
 
@@ -22,15 +24,17 @@ public interface INodeHandler : INotifyPropertyChanged, IDisposable
     public ObservableRangeCollection<INodePropertyHandler> Outputs { get; }
     public ObservableRangeCollection<INodePropertyHandler> Outputs { get; }
     public PreviewPainter? ResultPainter { get; set; }
     public PreviewPainter? ResultPainter { get; set; }
     public VecD PositionBindable { get; set; }
     public VecD PositionBindable { get; set; }
+    public Rect UiSize { get; set; }
     public bool IsNodeSelected { get; set; }
     public bool IsNodeSelected { get; set; }
     public string Icon { get; }
     public string Icon { get; }
-    public void TraverseBackwards(Func<INodeHandler, bool> func);
-    public void TraverseBackwards(Func<INodeHandler, INodeHandler, bool> func);
-    public void TraverseBackwards(Func<INodeHandler, INodeHandler, INodePropertyHandler, bool> func);
-    public void TraverseForwards(Func<INodeHandler, bool> func);
-    public void TraverseForwards(Func<INodeHandler, INodeHandler, bool> func);
-    public void TraverseForwards(Func<INodeHandler, INodeHandler, INodePropertyHandler, bool> func);
-    public void TraverseForwards(Func<INodeHandler, INodeHandler, INodePropertyHandler, INodePropertyHandler, bool> func);
+    public void TraverseBackwards(Func<INodeHandler, Traverse> func);
+    public void TraverseBackwards(Func<INodeHandler, INodeHandler, Traverse> func);
+    public void TraverseBackwards(Func<INodeHandler, INodeHandler, INodePropertyHandler, Traverse> func);
+    public void TraverseForwards(Func<INodeHandler, Traverse> func);
+    public void TraverseForwards(Func<INodeHandler, INodeHandler, Traverse> func);
+    public void TraverseForwards(Func<INodeHandler, INodeHandler, INodePropertyHandler, Traverse> func);
+    public void TraverseForwards(Func<INodeHandler, INodeHandler, INodePropertyHandler, INodePropertyHandler, Traverse> func);
+    public HashSet<NodeFrameViewModelBase> Frames { get; }
     public IReadOnlyDictionary<string, INodePropertyHandler> InputPropertyMap { get; }
     public IReadOnlyDictionary<string, INodePropertyHandler> InputPropertyMap { get; }
     public IReadOnlyDictionary<string, INodePropertyHandler> OutputPropertyMap { get; }
     public IReadOnlyDictionary<string, INodePropertyHandler> OutputPropertyMap { get; }
 }
 }

+ 1 - 0
src/PixiEditor/Models/Handlers/INodePropertyHandler.cs

@@ -15,6 +15,7 @@ public interface INodePropertyHandler
     public ObservableCollection<INodePropertyHandler> ConnectedInputs { get; }
     public ObservableCollection<INodePropertyHandler> ConnectedInputs { get; }
 
 
     public event NodePropertyValueChanged ValueChanged;
     public event NodePropertyValueChanged ValueChanged;
+    public event EventHandler ConnectedOutputChanged;
     public INodeHandler Node { get; set; }
     public INodeHandler Node { get; set; }
     public Type PropertyType { get; }
     public Type PropertyType { get; }
     public void UpdateComputedValue();
     public void UpdateComputedValue();

+ 1 - 0
src/PixiEditor/Models/Handlers/IToolsHandler.cs

@@ -19,6 +19,7 @@ internal interface IToolsHandler : IHandler
     public ICollection<IToolSetHandler> AllToolSets { get; }
     public ICollection<IToolSetHandler> AllToolSets { get; }
     public RightClickMode RightClickMode { get; set; }
     public RightClickMode RightClickMode { get; set; }
     public bool EnableSharedToolbar { get; set; }
     public bool EnableSharedToolbar { get; set; }
+    public bool SelectionTintingEnabled { get; set; }
     public event EventHandler<SelectedToolEventArgs> SelectedToolChanged;
     public event EventHandler<SelectedToolEventArgs> SelectedToolChanged;
     public void SetupTools(IServiceProvider services, ToolSetsConfig toolSetConfig);
     public void SetupTools(IServiceProvider services, ToolSetsConfig toolSetConfig);
     public void SetupToolsTooltipShortcuts();
     public void SetupToolsTooltipShortcuts();

+ 5 - 1
src/PixiEditor/Models/Handlers/Toolbars/PaintBrushShape.cs

@@ -1,7 +1,11 @@
-namespace PixiEditor.Models.Handlers.Toolbars;
+using System.ComponentModel;
+
+namespace PixiEditor.Models.Handlers.Toolbars;
 
 
 public enum PaintBrushShape
 public enum PaintBrushShape
 {
 {
+    [Description("PAINT_BRUSH_SHAPE_CIRCLE")]
     Circle,
     Circle,
+    [Description("PAINT_BRUSH_SHAPE_SQUARE")]
     Square,
     Square,
 }
 }

+ 29 - 11
src/PixiEditor/Models/Rendering/AffectedAreasGatherer.cs

@@ -30,6 +30,7 @@ internal class AffectedAreasGatherer
 
 
     public AffectedArea MainImageArea { get; private set; } = new();
     public AffectedArea MainImageArea { get; private set; } = new();
     public HashSet<Guid> ChangedMembers { get; private set; } = new();
     public HashSet<Guid> ChangedMembers { get; private set; } = new();
+    public bool IgnoreAnimationPreviews { get; private set; }
     public HashSet<Guid> ChangedMasks { get; private set; } = new();
     public HashSet<Guid> ChangedMasks { get; private set; } = new();
     public HashSet<Guid> ChangedKeyFrames { get; private set; } = new();
     public HashSet<Guid> ChangedKeyFrames { get; private set; } = new();
 
 
@@ -40,10 +41,20 @@ internal class AffectedAreasGatherer
     private bool alreadyAddedWholeCanvasToEveryImagePreview = false;
     private bool alreadyAddedWholeCanvasToEveryImagePreview = false;
 
 
     public AffectedAreasGatherer(KeyFrameTime activeFrame, DocumentChangeTracker tracker,
     public AffectedAreasGatherer(KeyFrameTime activeFrame, DocumentChangeTracker tracker,
-        IReadOnlyList<IChangeInfo> changes)
+        IReadOnlyList<IChangeInfo> changes, bool refreshAllPreviews)
     {
     {
         this.tracker = tracker;
         this.tracker = tracker;
         ActiveFrame = activeFrame;
         ActiveFrame = activeFrame;
+
+        if (refreshAllPreviews)
+        {
+            AddWholeCanvasToMainImage();
+            AddWholeCanvasToEveryImagePreview(false);
+            AddWholeCanvasToEveryMaskPreview();
+            AddAllNodesToImagePreviews();
+            return;
+        }
+
         ProcessChanges(changes);
         ProcessChanges(changes);
 
 
         var outputNode = tracker.Document.NodeGraph.OutputNode;
         var outputNode = tracker.Document.NodeGraph.OutputNode;
@@ -102,7 +113,7 @@ internal class AffectedAreasGatherer
                     break;
                     break;
                 case Size_ChangeInfo:
                 case Size_ChangeInfo:
                     AddWholeCanvasToMainImage();
                     AddWholeCanvasToMainImage();
-                    AddWholeCanvasToEveryImagePreview();
+                    AddWholeCanvasToEveryImagePreview(false);
                     AddWholeCanvasToEveryMaskPreview();
                     AddWholeCanvasToEveryMaskPreview();
                     break;
                     break;
                 case StructureMemberMask_ChangeInfo info:
                 case StructureMemberMask_ChangeInfo info:
@@ -151,24 +162,25 @@ internal class AffectedAreasGatherer
                     break;
                     break;
                 case SetActiveFrame_PassthroughAction:
                 case SetActiveFrame_PassthroughAction:
                     AddWholeCanvasToMainImage();
                     AddWholeCanvasToMainImage();
-                    AddWholeCanvasToEveryImagePreview();
+                    AddWholeCanvasToEveryImagePreview(true);
                     AddAllNodesToImagePreviews();
                     AddAllNodesToImagePreviews();
+                    IgnoreAnimationPreviews = true;
                     break;
                     break;
                 case KeyFrameLength_ChangeInfo:
                 case KeyFrameLength_ChangeInfo:
                     AddWholeCanvasToMainImage();
                     AddWholeCanvasToMainImage();
-                    AddWholeCanvasToEveryImagePreview();
+                    AddWholeCanvasToEveryImagePreview(true);
                     break;
                     break;
                 case DeleteKeyFrame_ChangeInfo:
                 case DeleteKeyFrame_ChangeInfo:
                     AddWholeCanvasToMainImage();
                     AddWholeCanvasToMainImage();
-                    AddWholeCanvasToEveryImagePreview();
+                    AddWholeCanvasToEveryImagePreview(true);
                     break;
                     break;
                 case KeyFrameVisibility_ChangeInfo:
                 case KeyFrameVisibility_ChangeInfo:
                     AddWholeCanvasToMainImage();
                     AddWholeCanvasToMainImage();
-                    AddWholeCanvasToEveryImagePreview();
+                    AddWholeCanvasToEveryImagePreview(true);
                     break;
                     break;
                 case ConnectProperty_ChangeInfo info:
                 case ConnectProperty_ChangeInfo info:
                     AddWholeCanvasToMainImage();
                     AddWholeCanvasToMainImage();
-                    AddWholeCanvasToEveryImagePreview();
+                    AddWholeCanvasToEveryImagePreview(false);
                     AddToNodePreviews(info.InputNodeId);
                     AddToNodePreviews(info.InputNodeId);
                     if (info.OutputNodeId.HasValue)
                     if (info.OutputNodeId.HasValue)
                     {
                     {
@@ -178,7 +190,7 @@ internal class AffectedAreasGatherer
                     break;
                     break;
                 case PropertyValueUpdated_ChangeInfo info:
                 case PropertyValueUpdated_ChangeInfo info:
                     AddWholeCanvasToMainImage();
                     AddWholeCanvasToMainImage();
-                    AddWholeCanvasToEveryImagePreview();
+                    AddWholeCanvasToEveryImagePreview(false);
                     AddToNodePreviews(info.NodeId);
                     AddToNodePreviews(info.NodeId);
                     break;
                     break;
                 case ToggleOnionSkinning_PassthroughAction:
                 case ToggleOnionSkinning_PassthroughAction:
@@ -194,7 +206,7 @@ internal class AffectedAreasGatherer
                     break;
                     break;
                 case ProcessingColorSpace_ChangeInfo:
                 case ProcessingColorSpace_ChangeInfo:
                     AddWholeCanvasToMainImage();
                     AddWholeCanvasToMainImage();
-                    AddWholeCanvasToEveryImagePreview();
+                    AddWholeCanvasToEveryImagePreview(false);
                     AddWholeCanvasToEveryMaskPreview();
                     AddWholeCanvasToEveryMaskPreview();
                     break;
                     break;
             }
             }
@@ -381,12 +393,18 @@ internal class AffectedAreasGatherer
         }
         }
     }
     }
 
 
-    private void AddWholeCanvasToEveryImagePreview()
+    private void AddWholeCanvasToEveryImagePreview(bool onlyWithKeyFrames)
     {
     {
         if (alreadyAddedWholeCanvasToEveryImagePreview)
         if (alreadyAddedWholeCanvasToEveryImagePreview)
             return;
             return;
 
 
-        tracker.Document.ForEveryReadonlyMember((member) => AddWholeCanvasToImagePreviews(member.Id));
+        tracker.Document.ForEveryReadonlyMember((member) =>
+        {
+            if (!onlyWithKeyFrames || member.KeyFrames.Count > 0)
+            {
+                AddWholeCanvasToImagePreviews(member.Id);
+            }
+        });
         alreadyAddedWholeCanvasToEveryImagePreview = true;
         alreadyAddedWholeCanvasToEveryImagePreview = true;
     }
     }
 
 

+ 47 - 22
src/PixiEditor/Models/Rendering/MemberPreviewUpdater.cs

@@ -8,6 +8,8 @@ using PixiEditor.Helpers;
 using PixiEditor.Models.DocumentModels;
 using PixiEditor.Models.DocumentModels;
 using PixiEditor.Models.Handlers;
 using PixiEditor.Models.Handlers;
 using Drawie.Numerics;
 using Drawie.Numerics;
+using PixiEditor.ViewModels.Nodes;
+using PixiEditor.ChangeableDocument.Rendering;
 
 
 namespace PixiEditor.Models.Rendering;
 namespace PixiEditor.Models.Rendering;
 
 
@@ -26,13 +28,15 @@ internal class MemberPreviewUpdater
     }
     }
 
 
     public void UpdatePreviews(HashSet<Guid> membersToUpdate,
     public void UpdatePreviews(HashSet<Guid> membersToUpdate,
-        HashSet<Guid> masksToUpdate, HashSet<Guid> nodesToUpdate, HashSet<Guid> keyFramesToUpdate)
+        HashSet<Guid> masksToUpdate, HashSet<Guid> nodesToUpdate, HashSet<Guid> keyFramesToUpdate,
+        bool ignoreAnimationPreviews, bool renderMiniPreviews)
     {
     {
         if (!membersToUpdate.Any() && !masksToUpdate.Any() && !nodesToUpdate.Any() &&
         if (!membersToUpdate.Any() && !masksToUpdate.Any() && !nodesToUpdate.Any() &&
             !keyFramesToUpdate.Any())
             !keyFramesToUpdate.Any())
             return;
             return;
 
 
-        UpdatePreviewPainters(membersToUpdate, masksToUpdate, nodesToUpdate, keyFramesToUpdate);
+        UpdatePreviewPainters(membersToUpdate, masksToUpdate, nodesToUpdate, keyFramesToUpdate, ignoreAnimationPreviews,
+            renderMiniPreviews);
     }
     }
 
 
     /// <summary>
     /// <summary>
@@ -41,13 +45,20 @@ internal class MemberPreviewUpdater
     /// <param name="members">Members that should be rendered</param>
     /// <param name="members">Members that should be rendered</param>
     /// <param name="masksToUpdate">Masks that should be rendered</param>
     /// <param name="masksToUpdate">Masks that should be rendered</param>
     private void UpdatePreviewPainters(HashSet<Guid> members, HashSet<Guid> masksToUpdate,
     private void UpdatePreviewPainters(HashSet<Guid> members, HashSet<Guid> masksToUpdate,
-        HashSet<Guid> nodesToUpdate, HashSet<Guid> keyFramesToUpdate)
+        HashSet<Guid> nodesToUpdate, HashSet<Guid> keyFramesToUpdate, bool ignoreAnimationPreviews,
+        bool renderLowPriorityPreviews)
     {
     {
-        RenderWholeCanvasPreview();
-        RenderLayersPreview(members);
-        RenderMaskPreviews(masksToUpdate);
+        RenderWholeCanvasPreview(renderLowPriorityPreviews);
+        if (renderLowPriorityPreviews)
+        {
+            RenderLayersPreview(members);
+            RenderMaskPreviews(masksToUpdate);
+        }
 
 
-        RenderAnimationPreviews(members, keyFramesToUpdate);
+        if (!ignoreAnimationPreviews)
+        {
+            RenderAnimationPreviews(members, keyFramesToUpdate);
+        }
 
 
         RenderNodePreviews(nodesToUpdate);
         RenderNodePreviews(nodesToUpdate);
     }
     }
@@ -55,21 +66,33 @@ internal class MemberPreviewUpdater
     /// <summary>
     /// <summary>
     /// Re-renders the preview of the whole canvas which is shown as the tab icon
     /// Re-renders the preview of the whole canvas which is shown as the tab icon
     /// </summary>
     /// </summary>
-    private void RenderWholeCanvasPreview()
+    /// <param name="renderMiniPreviews">Decides whether to re-render mini previews for the document</param>
+    private void RenderWholeCanvasPreview(bool renderMiniPreviews)
     {
     {
         var previewSize = StructureHelpers.CalculatePreviewSize(internals.Tracker.Document.Size);
         var previewSize = StructureHelpers.CalculatePreviewSize(internals.Tracker.Document.Size);
         //float scaling = (float)previewSize.X / doc.SizeBindable.X;
         //float scaling = (float)previewSize.X / doc.SizeBindable.X;
 
 
-        if (doc.PreviewPainter == null)
-        {
-            doc.PreviewPainter = new PreviewPainter(doc.Renderer, doc.Renderer, doc.AnimationHandler.ActiveFrameTime,
-                doc.SizeBindable, internals.Tracker.Document.ProcessingColorSpace);
-        }
+        doc.PreviewPainter ??= new PreviewPainter(doc.Renderer, doc.Renderer, doc.AnimationHandler.ActiveFrameTime,
+            doc.SizeBindable, internals.Tracker.Document.ProcessingColorSpace);
+
+        UpdateDocPreviewPainter(doc.PreviewPainter);
 
 
-        doc.PreviewPainter.DocumentSize = doc.SizeBindable;
-        doc.PreviewPainter.ProcessingColorSpace = internals.Tracker.Document.ProcessingColorSpace;
-        doc.PreviewPainter.FrameTime = doc.AnimationHandler.ActiveFrameTime;
-        doc.PreviewPainter.Repaint();
+        if (!renderMiniPreviews)
+            return;
+
+        doc.MiniPreviewPainter ??= new PreviewPainter(doc.Renderer, doc.Renderer,
+            doc.AnimationHandler.ActiveFrameTime,
+            doc.SizeBindable, internals.Tracker.Document.ProcessingColorSpace);
+
+        UpdateDocPreviewPainter(doc.MiniPreviewPainter);
+    }
+
+    private void UpdateDocPreviewPainter(PreviewPainter painter)
+    {
+        painter.DocumentSize = doc.SizeBindable;
+        painter.ProcessingColorSpace = internals.Tracker.Document.ProcessingColorSpace;
+        painter.FrameTime = doc.AnimationHandler.ActiveFrameTime;
+        painter.Repaint();
     }
     }
 
 
     private void RenderLayersPreview(HashSet<Guid> memberGuids)
     private void RenderLayersPreview(HashSet<Guid> memberGuids)
@@ -116,7 +139,8 @@ internal class MemberPreviewUpdater
                 {
                 {
                     if (!keyFramesGuids.Contains(childFrame.Id))
                     if (!keyFramesGuids.Contains(childFrame.Id))
                     {
                     {
-                        if (!memberGuids.Contains(childFrame.LayerGuid) || !IsInFrame(childFrame))
+                        if (!memberGuids.Contains(childFrame.LayerGuid) || !IsInFrame(childFrame) ||
+                            !groupHandler.IsVisible)
                             continue;
                             continue;
                     }
                     }
 
 
@@ -134,7 +158,7 @@ internal class MemberPreviewUpdater
     private bool IsInFrame(ICelHandler cel)
     private bool IsInFrame(ICelHandler cel)
     {
     {
         return cel.StartFrameBindable <= doc.AnimationHandler.ActiveFrameBindable &&
         return cel.StartFrameBindable <= doc.AnimationHandler.ActiveFrameBindable &&
-               cel.StartFrameBindable + cel.DurationBindable >= doc.AnimationHandler.ActiveFrameBindable;
+               cel.StartFrameBindable + cel.DurationBindable > doc.AnimationHandler.ActiveFrameBindable;
     }
     }
 
 
     private void RenderFramePreview(ICelHandler cel)
     private void RenderFramePreview(ICelHandler cel)
@@ -265,16 +289,16 @@ internal class MemberPreviewUpdater
         nodeVm.TraverseForwards(next =>
         nodeVm.TraverseForwards(next =>
         {
         {
             if (next is not INodeHandler nextVm)
             if (next is not INodeHandler nextVm)
-                return true;
+                return Traverse.Further;
 
 
             var nextNode = allNodes.FirstOrDefault(x => x.Id == next.Id);
             var nextNode = allNodes.FirstOrDefault(x => x.Id == next.Id);
 
 
             if (nextNode is null || actualRepaintedNodes.Contains(next.Id))
             if (nextNode is null || actualRepaintedNodes.Contains(next.Id))
-                return true;
+                return Traverse.Further;
 
 
             RequestRepaintNode(nextNode, nextVm);
             RequestRepaintNode(nextNode, nextVm);
             actualRepaintedNodes.Add(next.Id);
             actualRepaintedNodes.Add(next.Id);
-            return true;
+            return Traverse.Further;
         });
         });
     }
     }
 
 
@@ -287,6 +311,7 @@ internal class MemberPreviewUpdater
                 nodeVm.ResultPainter = new PreviewPainter(doc.Renderer, renderable,
                 nodeVm.ResultPainter = new PreviewPainter(doc.Renderer, renderable,
                     doc.AnimationHandler.ActiveFrameTime,
                     doc.AnimationHandler.ActiveFrameTime,
                     doc.SizeBindable, internals.Tracker.Document.ProcessingColorSpace);
                     doc.SizeBindable, internals.Tracker.Document.ProcessingColorSpace);
+                nodeVm.ResultPainter.AllowPartialResolutions = false;
                 nodeVm.ResultPainter.Repaint();
                 nodeVm.ResultPainter.Repaint();
             }
             }
             else
             else

+ 40 - 3
src/PixiEditor/Models/Rendering/PreviewPainter.cs

@@ -23,6 +23,8 @@ public class PreviewPainter : IDisposable
     public VecI DocumentSize { get; set; }
     public VecI DocumentSize { get; set; }
     public DocumentRenderer Renderer { get; set; }
     public DocumentRenderer Renderer { get; set; }
 
 
+    public bool AllowPartialResolutions { get; set; } = true;
+
     public bool CanRender => canRender;
     public bool CanRender => canRender;
 
 
     public event Action<bool>? CanRenderChanged;
     public event Action<bool>? CanRenderChanged;
@@ -136,7 +138,8 @@ public class PreviewPainter : IDisposable
     {
     {
         var dirtyArray = dirtyTextures.ToArray();
         var dirtyArray = dirtyTextures.ToArray();
         bool couldRender = canRender;
         bool couldRender = canRender;
-        canRender = PreviewRenderable?.GetPreviewBounds(FrameTime.Frame, ElementToRenderName) != null;
+        canRender = PreviewRenderable?.GetPreviewBounds(FrameTime.Frame, ElementToRenderName) != null &&
+                    painterInstances.Count > 0;
         if (couldRender != canRender)
         if (couldRender != canRender)
         {
         {
             CanRenderChanged?.Invoke(canRender);
             CanRenderChanged?.Invoke(canRender);
@@ -167,11 +170,18 @@ public class PreviewPainter : IDisposable
             renderTexture.DrawingSurface.Canvas.Save();
             renderTexture.DrawingSurface.Canvas.Save();
 
 
             Matrix3X3? matrix = painterInstance.RequestMatrix?.Invoke();
             Matrix3X3? matrix = painterInstance.RequestMatrix?.Invoke();
+            VecI bounds = painterInstance.RequestRenderBounds?.Invoke() ?? VecI.Zero;
+
+            ChunkResolution finalResolution = FindResolution(bounds);
+            SamplingOptions samplingOptions = FindSamplingOptions(matrix);
 
 
             renderTexture.DrawingSurface.Canvas.SetMatrix(matrix ?? Matrix3X3.Identity);
             renderTexture.DrawingSurface.Canvas.SetMatrix(matrix ?? Matrix3X3.Identity);
+            renderTexture.DrawingSurface.Canvas.Scale((float)finalResolution.InvertedMultiplier());
 
 
-            RenderContext context = new(renderTexture.DrawingSurface, FrameTime, ChunkResolution.Full, DocumentSize, DocumentSize,
-                ProcessingColorSpace);
+            RenderContext context = new(renderTexture.DrawingSurface, FrameTime, finalResolution,
+                DocumentSize,
+                DocumentSize,
+                ProcessingColorSpace, samplingOptions);
 
 
             dirtyTextures.Remove(texture);
             dirtyTextures.Remove(texture);
             Renderer.RenderNodePreview(PreviewRenderable, renderTexture.DrawingSurface, context, ElementToRenderName)
             Renderer.RenderNodePreview(PreviewRenderable, renderTexture.DrawingSurface, context, ElementToRenderName)
@@ -230,6 +240,31 @@ public class PreviewPainter : IDisposable
         }
         }
     }
     }
 
 
+    private ChunkResolution FindResolution(VecI bounds)
+    {
+        if (bounds.X <= 0 || bounds.Y <= 0 || !AllowPartialResolutions)
+        {
+            return ChunkResolution.Full;
+        }
+
+        double density = DocumentSize.X / (double)bounds.X;
+        if (density > 8.01)
+            return ChunkResolution.Eighth;
+        if (density > 4.01)
+            return ChunkResolution.Quarter;
+        if (density > 2.01)
+            return ChunkResolution.Half;
+        return ChunkResolution.Full;
+    }
+
+    private SamplingOptions FindSamplingOptions(Matrix3X3? matrix)
+    {
+        Matrix3X3 mtx = matrix ?? Matrix3X3.Identity;
+        return mtx.ScaleX < 1f || mtx.ScaleY < 1f
+            ? SamplingOptions.Bilinear
+            : SamplingOptions.Default;
+    }
+
     public void Dispose()
     public void Dispose()
     {
     {
         foreach (var texture in renderTextures)
         foreach (var texture in renderTextures)
@@ -242,6 +277,8 @@ public class PreviewPainter : IDisposable
 public class PainterInstance
 public class PainterInstance
 {
 {
     public int RequestId { get; set; }
     public int RequestId { get; set; }
+    public Func<VecI> RequestRenderBounds;
+
     public Func<Matrix3X3?>? RequestMatrix;
     public Func<Matrix3X3?>? RequestMatrix;
     public Action RequestRepaint;
     public Action RequestRepaint;
 }
 }

+ 35 - 13
src/PixiEditor/Models/Rendering/SceneRenderer.cs

@@ -4,6 +4,7 @@ using Drawie.Backend.Core.Numerics;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.ChangeableDocument.Rendering;
 using PixiEditor.ChangeableDocument.Rendering;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces;
+using Drawie.Backend.Core.Surfaces.PaintImpl;
 using Drawie.Numerics;
 using Drawie.Numerics;
 using PixiEditor.ChangeableDocument.Changeables.Animations;
 using PixiEditor.ChangeableDocument.Changeables.Animations;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
@@ -34,11 +35,12 @@ internal class SceneRenderer : IDisposable
         DocumentViewModel = documentViewModel;
         DocumentViewModel = documentViewModel;
     }
     }
 
 
-    public void RenderScene(DrawingSurface target, ChunkResolution resolution, string? targetOutput = null)
+    public void RenderScene(DrawingSurface target, ChunkResolution resolution, SamplingOptions samplingOptions,
+        string? targetOutput = null)
     {
     {
         if (Document.Renderer.IsBusy || DocumentViewModel.Busy ||
         if (Document.Renderer.IsBusy || DocumentViewModel.Busy ||
             target.DeviceClipBounds.Size.ShortestAxis <= 0) return;
             target.DeviceClipBounds.Size.ShortestAxis <= 0) return;
-        RenderOnionSkin(target, resolution, targetOutput);
+        RenderOnionSkin(target, resolution, samplingOptions, targetOutput);
 
 
         string adjustedTargetOutput = targetOutput ?? "";
         string adjustedTargetOutput = targetOutput ?? "";
 
 
@@ -55,7 +57,7 @@ internal class SceneRenderer : IDisposable
                 cachedTextures[adjustedTargetOutput]?.Dispose();
                 cachedTextures[adjustedTargetOutput]?.Dispose();
             }
             }
 
 
-            var rendered = RenderGraph(target, resolution, targetOutput, finalGraph);
+            var rendered = RenderGraph(target, resolution, samplingOptions, targetOutput, finalGraph);
             cachedTextures[adjustedTargetOutput] = rendered;
             cachedTextures[adjustedTargetOutput] = rendered;
             return;
             return;
         }
         }
@@ -64,11 +66,21 @@ internal class SceneRenderer : IDisposable
         Matrix3X3 matrixDiff = SolveMatrixDiff(target, cachedTexture);
         Matrix3X3 matrixDiff = SolveMatrixDiff(target, cachedTexture);
         int saved = target.Canvas.Save();
         int saved = target.Canvas.Save();
         target.Canvas.SetMatrix(matrixDiff);
         target.Canvas.SetMatrix(matrixDiff);
-        target.Canvas.DrawSurface(cachedTexture.DrawingSurface, 0, 0);
+        if (samplingOptions == SamplingOptions.Default)
+        {
+            target.Canvas.DrawSurface(cachedTexture.DrawingSurface, 0, 0);
+        }
+        else
+        {
+            using var img = cachedTexture.DrawingSurface.Snapshot();
+            target.Canvas.DrawImage(img, 0, 0, samplingOptions);
+        }
+
         target.Canvas.RestoreToCount(saved);
         target.Canvas.RestoreToCount(saved);
     }
     }
 
 
-    private Texture RenderGraph(DrawingSurface target, ChunkResolution resolution, string? targetOutput,
+    private Texture RenderGraph(DrawingSurface target, ChunkResolution resolution, SamplingOptions samplingOptions,
+        string? targetOutput,
         IReadOnlyNodeGraph finalGraph)
         IReadOnlyNodeGraph finalGraph)
     {
     {
         DrawingSurface renderTarget = target;
         DrawingSurface renderTarget = target;
@@ -104,13 +116,22 @@ internal class SceneRenderer : IDisposable
         }
         }
 
 
         RenderContext context = new(renderTarget, DocumentViewModel.AnimationHandler.ActiveFrameTime,
         RenderContext context = new(renderTarget, DocumentViewModel.AnimationHandler.ActiveFrameTime,
-            resolution, finalSize, Document.Size, Document.ProcessingColorSpace);
+            resolution, finalSize, Document.Size, Document.ProcessingColorSpace, samplingOptions);
         context.TargetOutput = targetOutput;
         context.TargetOutput = targetOutput;
         finalGraph.Execute(context);
         finalGraph.Execute(context);
 
 
         if (renderTexture != null)
         if (renderTexture != null)
         {
         {
-            target.Canvas.DrawSurface(renderTexture.DrawingSurface, 0, 0);
+            if (samplingOptions == SamplingOptions.Default)
+            {
+                target.Canvas.DrawSurface(renderTexture.DrawingSurface, 0, 0);
+            }
+            else
+            {
+                using var snapshot = renderTexture.DrawingSurface.Snapshot();
+                target.Canvas.DrawImage(snapshot, 0, 0, samplingOptions);
+            }
+
             target.Canvas.RestoreToCount(restoreCanvasTo);
             target.Canvas.RestoreToCount(restoreCanvasTo);
         }
         }
 
 
@@ -168,7 +189,9 @@ internal class SceneRenderer : IDisposable
         }
         }
 
 
         bool renderInDocumentSize = RenderInOutputSize(finalGraph);
         bool renderInDocumentSize = RenderInOutputSize(finalGraph);
-        VecI compareSize = renderInDocumentSize ? (VecI)(Document.Size * resolution.Multiplier()) : target.DeviceClipBounds.Size;
+        VecI compareSize = renderInDocumentSize
+            ? (VecI)(Document.Size * resolution.Multiplier())
+            : target.DeviceClipBounds.Size;
 
 
         if (cachedTexture.DrawingSurface.DeviceClipBounds.Size != compareSize)
         if (cachedTexture.DrawingSurface.DeviceClipBounds.Size != compareSize)
         {
         {
@@ -243,7 +266,7 @@ internal class SceneRenderer : IDisposable
         return highDpiRenderNodePresent;
         return highDpiRenderNodePresent;
     }
     }
 
 
-    private void RenderOnionSkin(DrawingSurface target, ChunkResolution resolution, string? targetOutput)
+    private void RenderOnionSkin(DrawingSurface target, ChunkResolution resolution, SamplingOptions sampling, string? targetOutput)
     {
     {
         var animationData = Document.AnimationData;
         var animationData = Document.AnimationData;
         if (!DocumentViewModel.AnimationHandler.OnionSkinningEnabledBindable)
         if (!DocumentViewModel.AnimationHandler.OnionSkinningEnabledBindable)
@@ -268,9 +291,9 @@ internal class SceneRenderer : IDisposable
 
 
             double finalOpacity = onionOpacity * alphaFalloffMultiplier * (animationData.OnionFrames - i + 1);
             double finalOpacity = onionOpacity * alphaFalloffMultiplier * (animationData.OnionFrames - i + 1);
 
 
+
             RenderContext onionContext = new(target, frame, resolution, renderOutputSize, Document.Size,
             RenderContext onionContext = new(target, frame, resolution, renderOutputSize, Document.Size,
-                Document.ProcessingColorSpace,
-                finalOpacity);
+                Document.ProcessingColorSpace, sampling, finalOpacity);
             onionContext.TargetOutput = targetOutput;
             onionContext.TargetOutput = targetOutput;
             finalGraph.Execute(onionContext);
             finalGraph.Execute(onionContext);
         }
         }
@@ -286,8 +309,7 @@ internal class SceneRenderer : IDisposable
 
 
             double finalOpacity = onionOpacity * alphaFalloffMultiplier * (animationData.OnionFrames - i + 1);
             double finalOpacity = onionOpacity * alphaFalloffMultiplier * (animationData.OnionFrames - i + 1);
             RenderContext onionContext = new(target, frame, resolution, renderOutputSize, Document.Size,
             RenderContext onionContext = new(target, frame, resolution, renderOutputSize, Document.Size,
-                Document.ProcessingColorSpace,
-                finalOpacity);
+                Document.ProcessingColorSpace, sampling, finalOpacity);
             onionContext.TargetOutput = targetOutput;
             onionContext.TargetOutput = targetOutput;
             finalGraph.Execute(onionContext);
             finalGraph.Execute(onionContext);
         }
         }

+ 144 - 0
src/PixiEditor/Models/Structures/ObservableHashSet.cs

@@ -0,0 +1,144 @@
+using System.Collections;
+using System.Collections.Immutable;
+using System.Collections.Specialized;
+using System.Runtime.Serialization;
+
+namespace PixiEditor.Models.Structures;
+
+public class ObservableHashSet<T> : ISet<T>, IReadOnlySet<T>, IDeserializationCallback, ISerializable, INotifyCollectionChanged
+{
+    private readonly HashSet<T> setImplementation;
+
+    public ObservableHashSet()
+    {
+        setImplementation = new HashSet<T>();
+    }
+
+    public ObservableHashSet(IEnumerable<T> collection)
+    {
+        setImplementation = new HashSet<T>(collection);
+    }
+    
+    public bool Add(T item)
+    {
+        var isAdded = setImplementation.Add(item);
+
+        if (isAdded)
+        {
+            CallCollectionChanged(NotifyCollectionChangedAction.Add, item);
+        }
+        
+        return isAdded;
+    }
+
+    void ICollection<T>.Add(T item) => Add(item);
+
+    public void Clear()
+    {
+        setImplementation.Clear();
+        CallCollectionChanged(NotifyCollectionChangedAction.Reset, Array.Empty<T>(), setImplementation.ToList());
+    }
+
+    public bool Remove(T item)
+    {
+        var isRemoved = setImplementation.Remove(item);
+
+        if (isRemoved)
+        {
+            CallCollectionChanged(NotifyCollectionChangedAction.Remove, item);
+        }
+        
+        return isRemoved;
+    }
+
+    /// <summary>
+    /// Not implemented
+    /// </summary>
+    /// <exception cref="NotSupportedException">This method is not implemented.</exception>
+    public void ExceptWith(IEnumerable<T> other)
+    {
+        throw new NotSupportedException();
+    }
+
+    /// <summary>
+    /// Not implemented
+    /// </summary>
+    /// <exception cref="NotSupportedException">This method is not implemented.</exception>
+    public void IntersectWith(IEnumerable<T> other)
+    {
+        throw new NotSupportedException();
+    }
+
+    /// <summary>
+    /// Not implemented
+    /// </summary>
+    /// <exception cref="NotSupportedException">This method is not implemented.</exception>
+    public void SymmetricExceptWith(IEnumerable<T> other)
+    {
+        throw new NotSupportedException();
+    }
+
+    public void UnionWith(IEnumerable<T> other)
+    {
+        var allOther = other.ToImmutableHashSet();
+        var addedOnly = allOther.Except(setImplementation);
+        
+        setImplementation.UnionWith(allOther);
+        CallCollectionChanged(
+            NotifyCollectionChangedAction.Reset, 
+            addedOnly.ToList());
+    }
+
+    public void ReplaceBy(IEnumerable<T> other)
+    {
+        var otherOriginal = other.ToHashSet();
+        var original = setImplementation.ToImmutableHashSet();
+        var removed = original.Except(otherOriginal);
+
+        setImplementation.Clear();
+        setImplementation.UnionWith(otherOriginal);
+        CallCollectionChanged(NotifyCollectionChangedAction.Replace, otherOriginal.ToList(), removed.ToList());
+    }
+
+    public bool IsProperSubsetOf(IEnumerable<T> other) => setImplementation.IsProperSubsetOf(other);
+
+    public bool IsProperSupersetOf(IEnumerable<T> other) => setImplementation.IsProperSupersetOf(other);
+
+    public bool IsSubsetOf(IEnumerable<T> other) => setImplementation.IsSubsetOf(other);
+
+    public bool IsSupersetOf(IEnumerable<T> other) => setImplementation.IsSupersetOf(other);
+
+    public bool Overlaps(IEnumerable<T> other) => setImplementation.Overlaps(other);
+
+    public bool SetEquals(IEnumerable<T> other) => setImplementation.SetEquals(other);
+
+    public bool Contains(T item) => setImplementation.Contains(item);
+
+    public void CopyTo(T[] array, int arrayIndex) => setImplementation.CopyTo(array, arrayIndex);
+
+    public IEnumerator<T> GetEnumerator() => setImplementation.GetEnumerator();
+
+    IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)setImplementation).GetEnumerator();
+
+    public int Count => setImplementation.Count;
+
+    bool ICollection<T>.IsReadOnly => ((ISet<T>)setImplementation).IsReadOnly;
+
+    void IDeserializationCallback.OnDeserialization(object? sender) => setImplementation.OnDeserialization(sender);
+
+    void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) => setImplementation.GetObjectData(info, context);
+
+    private void CallCollectionChanged(NotifyCollectionChangedAction action) =>
+        CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(action));
+    
+    private void CallCollectionChanged(NotifyCollectionChangedAction action, IList added, IList removed) =>
+        CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(action, added, removed));
+
+    private void CallCollectionChanged(NotifyCollectionChangedAction action, IList changed) =>
+        CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(action, changed));
+
+    private void CallCollectionChanged(NotifyCollectionChangedAction action, T item) =>
+        CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(action, item));
+
+    public event NotifyCollectionChangedEventHandler? CollectionChanged;
+}

+ 5 - 1
src/PixiEditor/Models/Tools/BrightnessMode.cs

@@ -1,7 +1,11 @@
-namespace PixiEditor.Models.Tools;
+using System.ComponentModel;
+
+namespace PixiEditor.Models.Tools;
 
 
 public enum BrightnessMode
 public enum BrightnessMode
 {
 {
+    [Description("BRIGHTNESS_MODE_DEFAULT")]
     Default,
     Default,
+    [Description("BRIGHTNESS_MODE_REPEAT")]
     Repeat
     Repeat
 }
 }

+ 2 - 2
src/PixiEditor/Properties/AssemblyInfo.cs

@@ -43,5 +43,5 @@ using System.Runtime.InteropServices;
 // You can specify all the values or you can default the Build and Revision Numbers
 // You can specify all the values or you can default the Build and Revision Numbers
 // by using the '*' as shown below:
 // by using the '*' as shown below:
 // [assembly: AssemblyVersion("1.0.*")]
 // [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("2.0.1.9")]
-[assembly: AssemblyFileVersion("2.0.1.9")]
+[assembly: AssemblyVersion("2.0.1.11")]
+[assembly: AssemblyFileVersion("2.0.1.11")]

+ 4 - 7
src/PixiEditor/Styles/Templates/NodeFrameView.axaml

@@ -7,13 +7,10 @@
         <Setter Property="Template">
         <Setter Property="Template">
             <Setter.Value>
             <Setter.Value>
                 <ControlTemplate>
                 <ControlTemplate>
-                    <Grid Width="{Binding Size.X, RelativeSource={RelativeSource FindAncestor, AncestorType=nodes:NodeFrameView}}">
-                        <Rectangle Fill="{TemplateBinding Background}"
-                                   Stroke="{TemplateBinding BorderBrush}"
-                                   StrokeThickness="2" RadiusX="10" RadiusY="10"
-                                   Width="{Binding Size.X, RelativeSource={RelativeSource FindAncestor, AncestorType=nodes:NodeFrameView}}"
-                                   Height="{Binding Size.Y, RelativeSource={RelativeSource FindAncestor, AncestorType=nodes:NodeFrameView}}" />
-                    </Grid>
+                    <Path Data="{Binding Geometry, RelativeSource={RelativeSource FindAncestor, AncestorType=nodes:NodeFrameView}}"
+                          Fill="{TemplateBinding Background}"
+                          Stroke="{TemplateBinding BorderBrush}"
+                          StrokeThickness="2" />
                 </ControlTemplate>
                 </ControlTemplate>
             </Setter.Value>
             </Setter.Value>
         </Setter>
         </Setter>

+ 4 - 10
src/PixiEditor/Styles/Templates/NodeGraphView.axaml

@@ -59,7 +59,8 @@
                                         RelativeSource={RelativeSource FindAncestor, AncestorType=nodes:NodeGraphView}}"
                                         RelativeSource={RelativeSource FindAncestor, AncestorType=nodes:NodeGraphView}}"
                                     SocketDropCommand="{Binding SocketDropCommand,
                                     SocketDropCommand="{Binding SocketDropCommand,
                                         RelativeSource={RelativeSource FindAncestor, AncestorType=nodes:NodeGraphView}}"
                                         RelativeSource={RelativeSource FindAncestor, AncestorType=nodes:NodeGraphView}}"
-                                    ResultPreview="{Binding ResultPainter}" />
+                                    ResultPreview="{Binding ResultPainter}"
+                                    Bounds="{Binding UiSize, Mode=OneWayToSource}" />
                             </DataTemplate>
                             </DataTemplate>
                         </ItemsControl.ItemTemplate>
                         </ItemsControl.ItemTemplate>
                         <ItemsControl.ItemContainerTheme>
                         <ItemsControl.ItemContainerTheme>
@@ -109,9 +110,8 @@
                         <ItemsControl.ItemTemplate>
                         <ItemsControl.ItemTemplate>
                             <DataTemplate>
                             <DataTemplate>
                                 <nodes:NodeFrameView
                                 <nodes:NodeFrameView
-                                    TopLeft="{Binding TopLeft}"
-                                    BottomRight="{Binding BottomRight}"
-                                    Size="{Binding Size}">
+                                    Geometry="{Binding Geometry}"
+                                    ClipToBounds="False">
                                     <nodes:NodeFrameView.Background>
                                     <nodes:NodeFrameView.Background>
                                         <MultiBinding Converter="{converters:UnsetSkipMultiConverter}">
                                         <MultiBinding Converter="{converters:UnsetSkipMultiConverter}">
                                             <Binding Path="InternalName"
                                             <Binding Path="InternalName"
@@ -131,12 +131,6 @@
                                 </nodes:NodeFrameView>
                                 </nodes:NodeFrameView>
                             </DataTemplate>
                             </DataTemplate>
                         </ItemsControl.ItemTemplate>
                         </ItemsControl.ItemTemplate>
-                        <ItemsControl.ItemContainerTheme>
-                            <ControlTheme TargetType="ContentPresenter">
-                                <Setter Property="Canvas.Left" Value="{Binding TopLeft.X}" />
-                                <Setter Property="Canvas.Top" Value="{Binding TopLeft.Y}" />
-                            </ControlTheme>
-                        </ItemsControl.ItemContainerTheme>
                     </ItemsControl>
                     </ItemsControl>
                 </Grid>
                 </Grid>
             </ControlTemplate>
             </ControlTemplate>

+ 15 - 1
src/PixiEditor/Styles/Templates/NodePicker.axaml

@@ -4,7 +4,8 @@
                     xmlns:visuals="clr-namespace:PixiEditor.Views.Visuals"
                     xmlns:visuals="clr-namespace:PixiEditor.Views.Visuals"
                     xmlns:ui="clr-namespace:PixiEditor.UI.Common.Localization;assembly=PixiEditor.UI.Common"
                     xmlns:ui="clr-namespace:PixiEditor.UI.Common.Localization;assembly=PixiEditor.UI.Common"
                     xmlns:input="clr-namespace:PixiEditor.Views.Input"
                     xmlns:input="clr-namespace:PixiEditor.Views.Input"
-                    xmlns:nodes1="clr-namespace:PixiEditor.ViewModels.Nodes">
+                    xmlns:nodes1="clr-namespace:PixiEditor.ViewModels.Nodes"
+                    xmlns:converters="clr-namespace:PixiEditor.Helpers.Converters">
     <ControlTheme TargetType="nodes:NodePicker" x:Key="{x:Type nodes:NodePicker}">
     <ControlTheme TargetType="nodes:NodePicker" x:Key="{x:Type nodes:NodePicker}">
         <Setter Property="Template">
         <Setter Property="Template">
             <ControlTemplate>
             <ControlTemplate>
@@ -55,6 +56,19 @@
                                                         CommandParameter="{Binding}"
                                                         CommandParameter="{Binding}"
                                                         HorizontalContentAlignment="Left"
                                                         HorizontalContentAlignment="Left"
                                                         IsVisible="{Binding !Hidden}">
                                                         IsVisible="{Binding !Hidden}">
+                                                        <Classes.KeyboardSelected>
+                                                            <MultiBinding Converter="{converters:AreEqualConverter}">
+                                                                <Binding />
+                                                                <Binding Path="SelectedNode" RelativeSource="{RelativeSource FindAncestor, AncestorType=nodes:NodePicker}" Mode="OneWay"/>
+                                                            </MultiBinding>
+                                                        </Classes.KeyboardSelected>
+                                                        
+                                                        <Button.Styles>
+                                                            <Style Selector="Button.KeyboardSelected">
+                                                                <Setter Property="Background" Value="{DynamicResource ThemeControlHighlightBrush}" />
+                                                            </Style>
+                                                        </Button.Styles>
+                                                        
                                                         <TextBlock Margin="10 0 0 0">
                                                         <TextBlock Margin="10 0 0 0">
                                                             <Run Classes="pixi-icon"
                                                             <Run Classes="pixi-icon"
                                                                  BaselineAlignment="Center"
                                                                  BaselineAlignment="Center"

+ 15 - 0
src/PixiEditor/ViewModels/Document/DocumentViewModel.cs

@@ -35,6 +35,7 @@ using PixiEditor.Models.Tools;
 using Drawie.Numerics;
 using Drawie.Numerics;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Workspace;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Workspace;
 using PixiEditor.Models.IO;
 using PixiEditor.Models.IO;
+using PixiEditor.Models.Position;
 using PixiEditor.Parser;
 using PixiEditor.Parser;
 using PixiEditor.Parser.Skia;
 using PixiEditor.Parser.Skia;
 using PixiEditor.UI.Common.Localization;
 using PixiEditor.UI.Common.Localization;
@@ -184,6 +185,16 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
 
 
     public IStructureMemberHandler? SelectedStructureMember { get; private set; } = null;
     public IStructureMemberHandler? SelectedStructureMember { get; private set; } = null;
 
 
+    private PreviewPainter miniPreviewPainter;
+
+    public PreviewPainter MiniPreviewPainter
+    {
+        get => miniPreviewPainter;
+        set
+        {
+            SetProperty(ref miniPreviewPainter, value);
+        }
+    }
 
 
     private PreviewPainter previewSurface;
     private PreviewPainter previewSurface;
 
 
@@ -374,11 +385,15 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
             viewModel.MarkAsSaved();
             viewModel.MarkAsSaved();
         }));
         }));
 
 
+        acc.AddActions(new SetActiveFrame_PassthroughAction(1), new RefreshPreviews_PassthroughAction());
+
         foreach (var factory in allFactories)
         foreach (var factory in allFactories)
         {
         {
             factory.ResourceLocator = null;
             factory.ResourceLocator = null;
         }
         }
 
 
+        viewModel.NodeGraph.FinalizeCreation();
+
         return viewModel;
         return viewModel;
 
 
 
 

+ 81 - 0
src/PixiEditor/ViewModels/Document/NodeGraphViewModel.cs

@@ -19,6 +19,8 @@ namespace PixiEditor.ViewModels.Document;
 
 
 internal class NodeGraphViewModel : ViewModelBase, INodeGraphHandler, IDisposable
 internal class NodeGraphViewModel : ViewModelBase, INodeGraphHandler, IDisposable
 {
 {
+    private bool isFullyCreated;
+    
     public DocumentViewModel DocumentViewModel { get; }
     public DocumentViewModel DocumentViewModel { get; }
     public ObservableCollection<INodeHandler> AllNodes { get; } = new();
     public ObservableCollection<INodeHandler> AllNodes { get; } = new();
     public ObservableCollection<NodeConnectionViewModel> Connections { get; } = new();
     public ObservableCollection<NodeConnectionViewModel> Connections { get; } = new();
@@ -108,6 +110,9 @@ internal class NodeGraphViewModel : ViewModelBase, INodeGraphHandler, IDisposabl
         connection.OutputProperty.ConnectedInputs.Add(connection.InputProperty);
         connection.OutputProperty.ConnectedInputs.Add(connection.InputProperty);
 
 
         Connections.Add(connection);
         Connections.Add(connection);
+        
+        UpdatesFramesPartOf(connection.InputNode);
+        UpdatesFramesPartOf(connection.OutputNode);
 
 
         StructureTree.Update(this);
         StructureTree.Update(this);
     }
     }
@@ -123,6 +128,9 @@ internal class NodeGraphViewModel : ViewModelBase, INodeGraphHandler, IDisposabl
             Connections.Remove(connection);
             Connections.Remove(connection);
         }
         }
 
 
+        UpdatesFramesPartOf(connection.InputNode);
+        UpdatesFramesPartOf(connection.OutputNode);
+        
         var node = AllNodes.FirstOrDefault(x => x.Id == nodeId);
         var node = AllNodes.FirstOrDefault(x => x.Id == nodeId);
         if (node != null)
         if (node != null)
         {
         {
@@ -136,6 +144,79 @@ internal class NodeGraphViewModel : ViewModelBase, INodeGraphHandler, IDisposabl
         StructureTree.Update(this);
         StructureTree.Update(this);
     }
     }
 
 
+    public void UpdatesFramesPartOf(INodeHandler node)
+    {
+        if (!isFullyCreated)
+            return;
+
+        var lastKnownFramesPartOf = node.Frames.OfType<NodeZoneViewModel>().ToHashSet();
+        var startLookup = Frames.OfType<NodeZoneViewModel>().ToDictionary(x => x.Start);
+        var currentlyPartOf = new HashSet<NodeZoneViewModel>();
+        
+        node.TraverseBackwards(x =>
+        {
+            if (x is IPairNodeEndViewModel)
+                return Traverse.NoFurther;
+
+            if (x is not IPairNodeStartViewModel)
+                return Traverse.Further;
+
+            var zone = startLookup[x];
+            currentlyPartOf.Add(zone);
+
+            return Traverse.Further;
+        });
+
+        foreach (var frame in currentlyPartOf)
+        {
+            frame.Nodes.Add(node);
+            node.Frames.Add(frame);
+        }
+
+        lastKnownFramesPartOf.ExceptWith(currentlyPartOf);
+        foreach (var removedFrom in lastKnownFramesPartOf)
+        {
+            removedFrom.Nodes.Remove(node);
+            node.Frames.Remove(removedFrom);
+        }
+    }
+
+    public void FinalizeCreation()
+    {
+        if (isFullyCreated)
+            return;
+        
+        isFullyCreated = true;
+        
+        foreach (var nodeZoneViewModel in Frames.OfType<NodeZoneViewModel>())
+        {
+            UpdateNodesPartOf(nodeZoneViewModel);
+        }
+    }
+
+    private static void UpdateNodesPartOf(NodeZoneViewModel zone)
+    {
+        var currentlyPartOf = new HashSet<INodeHandler>([zone.Start, zone.End]);
+
+        foreach (var node in zone.Start
+                     .Outputs
+                     .SelectMany(x => x.ConnectedInputs)
+                     .Select(x => x.Node))
+        {
+            node.TraverseForwards((x) =>
+            {
+                if (x is IPairNodeEndViewModel)
+                    return Traverse.NoFurther;
+
+                currentlyPartOf.Add(x);
+
+                return Traverse.Further;
+            });
+        }
+
+        zone.Nodes.ReplaceBy(currentlyPartOf);
+    }
+
     public void RemoveConnections(Guid nodeId)
     public void RemoveConnections(Guid nodeId)
     {
     {
         var connections = Connections
         var connections = Connections

+ 21 - 1
src/PixiEditor/ViewModels/Document/Nodes/FilterNodes/ApplyFilterNodeViewModel.cs

@@ -1,3 +1,4 @@
+using System.Collections.Specialized;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.FilterNodes;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.FilterNodes;
 using PixiEditor.UI.Common.Fonts;
 using PixiEditor.UI.Common.Fonts;
 using PixiEditor.ViewModels.Nodes;
 using PixiEditor.ViewModels.Nodes;
@@ -5,4 +6,23 @@ using PixiEditor.ViewModels.Nodes;
 namespace PixiEditor.ViewModels.Document.Nodes.FilterNodes;
 namespace PixiEditor.ViewModels.Document.Nodes.FilterNodes;
 
 
 [NodeViewModel("APPLY_FILTER_NODE", "FILTERS", PixiPerfectIcons.Magic)]
 [NodeViewModel("APPLY_FILTER_NODE", "FILTERS", PixiPerfectIcons.Magic)]
-internal class ApplyFilterNodeViewModel : NodeViewModel<ApplyFilterNode>;
+internal class ApplyFilterNodeViewModel : NodeViewModel<ApplyFilterNode>
+{
+    private NodePropertyViewModel MaskInput { get; set; }
+    
+    private NodePropertyViewModel MaskInvertInput { get; set; }
+
+    public override void OnInitialized()
+    {
+        MaskInput = FindInputProperty("Mask");
+        MaskInvertInput = FindInputProperty("InvertMask");
+        
+        UpdateInvertVisible();
+        MaskInput.ConnectedOutputChanged += (_, _) => UpdateInvertVisible();
+    }
+
+    private void UpdateInvertVisible()
+    {
+        MaskInvertInput.IsVisible = MaskInput.ConnectedOutput != null;
+    }
+}

+ 1 - 1
src/PixiEditor/ViewModels/Document/Nodes/ModifyImageLeftNodeViewModel.cs

@@ -5,4 +5,4 @@ using PixiEditor.ViewModels.Nodes;
 namespace PixiEditor.ViewModels.Document.Nodes;
 namespace PixiEditor.ViewModels.Document.Nodes;
 
 
 [NodeViewModel("MODIFY_IMAGE_LEFT_NODE", "IMAGE", PixiPerfectIcons.PutImage)]
 [NodeViewModel("MODIFY_IMAGE_LEFT_NODE", "IMAGE", PixiPerfectIcons.PutImage)]
-internal class ModifyImageLeftNodeViewModel : NodeViewModel<ModifyImageLeftNode>;
+internal class ModifyImageLeftNodeViewModel : NodeViewModel<ModifyImageLeftNode>, IPairNodeStartViewModel;

+ 1 - 1
src/PixiEditor/ViewModels/Document/Nodes/ModifyImageRightNodeViewModel.cs

@@ -4,4 +4,4 @@ using PixiEditor.ViewModels.Nodes;
 namespace PixiEditor.ViewModels.Document.Nodes;
 namespace PixiEditor.ViewModels.Document.Nodes;
 
 
 [NodeViewModel("MODIFY_IMAGE_RIGHT_NODE", "IMAGE", null)]
 [NodeViewModel("MODIFY_IMAGE_RIGHT_NODE", "IMAGE", null)]
-internal class ModifyImageRightNodeViewModel : NodeViewModel<ModifyImageRightNode>;
+internal class ModifyImageRightNodeViewModel : NodeViewModel<ModifyImageRightNode>, IPairNodeEndViewModel;

+ 29 - 1
src/PixiEditor/ViewModels/Document/Nodes/NoiseNodeViewModel.cs

@@ -1,8 +1,36 @@
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.UI.Common.Fonts;
 using PixiEditor.UI.Common.Fonts;
 using PixiEditor.ViewModels.Nodes;
 using PixiEditor.ViewModels.Nodes;
+using PixiEditor.ViewModels.Nodes.Properties;
 
 
 namespace PixiEditor.ViewModels.Document.Nodes;
 namespace PixiEditor.ViewModels.Document.Nodes;
 
 
 [NodeViewModel("NOISE_NODE", "IMAGE", PixiPerfectIcons.Noise)]
 [NodeViewModel("NOISE_NODE", "IMAGE", PixiPerfectIcons.Noise)]
-internal class NoiseNodeViewModel : NodeViewModel<NoiseNode>;
+internal class NoiseNodeViewModel : NodeViewModel<NoiseNode>
+{
+    private GenericEnumPropertyViewModel Type { get; set; }
+    private GenericEnumPropertyViewModel VoronoiFeature { get; set; }
+    private NodePropertyViewModel Randomness { get; set; }
+    private NodePropertyViewModel AngleOffset { get; set; }
+
+    public override void OnInitialized()
+    {
+        Type = FindInputProperty("NoiseType") as GenericEnumPropertyViewModel;
+        VoronoiFeature = FindInputProperty("VoronoiFeature") as GenericEnumPropertyViewModel;
+        Randomness = FindInputProperty("Randomness");
+        AngleOffset = FindInputProperty("AngleOffset");
+
+        Type.ValueChanged += (_, _) => UpdateInputVisibility();
+        UpdateInputVisibility();
+    }
+
+    private void UpdateInputVisibility()
+    {
+        if (Type.Value is not NoiseType type)
+            return;
+        
+        Randomness.IsVisible = type == NoiseType.Voronoi;
+        VoronoiFeature.IsVisible = type == NoiseType.Voronoi;
+        AngleOffset.IsVisible = type == NoiseType.Voronoi;
+    }
+}

+ 9 - 0
src/PixiEditor/ViewModels/Document/Nodes/Shapes/LineNodeViewModel.cs

@@ -0,0 +1,9 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes;
+using PixiEditor.ViewModels.Nodes;
+
+namespace PixiEditor.ViewModels.Document.Nodes.Shapes;
+
+[NodeViewModel("LINE_NODE", "SHAPE", PixiPerfectIcons.Line)]
+internal class LineNodeViewModel : NodeViewModel<LineNode>
+{
+}

+ 9 - 0
src/PixiEditor/ViewModels/Document/Nodes/Shapes/RectangleNodeViewModel.cs

@@ -0,0 +1,9 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes;
+using PixiEditor.ViewModels.Nodes;
+
+namespace PixiEditor.ViewModels.Document.Nodes.Shapes;
+
+[NodeViewModel("RECTANGLE_NODE", "SHAPE", PixiPerfectIcons.Square)]
+internal class RectangleNodeViewModel : NodeViewModel<RectangleNode>
+{
+}

+ 2 - 2
src/PixiEditor/ViewModels/Document/Nodes/StructureMemberViewModel.cs

@@ -60,10 +60,10 @@ internal abstract class StructureMemberViewModel<T> : NodeViewModel<T>, IStructu
                 if (node is IFolderHandler parent && input is { PropertyName: FolderNode.ContentInternalName })
                 if (node is IFolderHandler parent && input is { PropertyName: FolderNode.ContentInternalName })
                 {
                 {
                     visible = parent.IsVisibleBindable;
                     visible = parent.IsVisibleBindable;
-                    return visible;
+                    return visible ? Traverse.Further : Traverse.Exit;
                 }
                 }
 
 
-                return true;
+                return Traverse.Further;
             });
             });
 
 
             return visible;
             return visible;

+ 2 - 1
src/PixiEditor/ViewModels/Document/StructureTree.cs

@@ -1,5 +1,6 @@
 using System.Collections.ObjectModel;
 using System.Collections.ObjectModel;
 using PixiEditor.Models.Handlers;
 using PixiEditor.Models.Handlers;
+using PixiEditor.ViewModels.Nodes;
 
 
 namespace PixiEditor.ViewModels.Document;
 namespace PixiEditor.ViewModels.Document;
 
 
@@ -60,7 +61,7 @@ internal class StructureTree
 
 
             _memberMap.TryAdd(node, lastRoot);
             _memberMap.TryAdd(node, lastRoot);
 
 
-            return true;
+            return Traverse.Further;
         });
         });
 
 
         List<IStructureMemberHandler> toRemove = new();
         List<IStructureMemberHandler> toRemove = new();

+ 6 - 0
src/PixiEditor/ViewModels/Nodes/IPairNodeEndViewModel.cs

@@ -0,0 +1,6 @@
+namespace PixiEditor.ViewModels.Nodes;
+
+public interface IPairNodeEndViewModel
+{
+    
+}

+ 6 - 0
src/PixiEditor/ViewModels/Nodes/IPairNodeStartViewModel.cs

@@ -0,0 +1,6 @@
+namespace PixiEditor.ViewModels.Nodes;
+
+public interface IPairNodeStartViewModel
+{
+    
+}

+ 2 - 28
src/PixiEditor/ViewModels/Nodes/NodeFrameViewModel.cs

@@ -1,9 +1,4 @@
-using System.Collections.ObjectModel;
-using System.Collections.Specialized;
-using System.ComponentModel;
-using CommunityToolkit.Mvvm.ComponentModel;
-using PixiEditor.Models.Handlers;
-using Drawie.Numerics;
+using PixiEditor.Models.Handlers;
 
 
 namespace PixiEditor.ViewModels.Nodes;
 namespace PixiEditor.ViewModels.Nodes;
 
 
@@ -16,27 +11,6 @@ internal sealed class NodeFrameViewModel : NodeFrameViewModelBase
 
 
     protected override void CalculateBounds()
     protected override void CalculateBounds()
     {
     {
-        
-        // TODO: Use the GetBounds like in NodeZoneViewModel
-        if (Nodes.Count == 0)
-        {
-            if (TopLeft == BottomRight)
-            {
-                BottomRight = TopLeft + new VecD(100, 100);
-            }
-            
-            return;
-        }
-        
-        var minX = Nodes.Min(n => n.PositionBindable.X) - 30;
-        var minY = Nodes.Min(n => n.PositionBindable.Y) - 45;
-        
-        var maxX = Nodes.Max(n => n.PositionBindable.X) + 130;
-        var maxY = Nodes.Max(n => n.PositionBindable.Y) + 130;
-
-        TopLeft = new VecD(minX, minY);
-        BottomRight = new VecD(maxX, maxY);
-
-        Size = BottomRight - TopLeft;
+        throw new NotImplementedException();
     }
     }
 }
 }

+ 23 - 21
src/PixiEditor/ViewModels/Nodes/NodeFrameViewModelBase.cs

@@ -1,20 +1,23 @@
 using System.Collections.ObjectModel;
 using System.Collections.ObjectModel;
 using System.Collections.Specialized;
 using System.Collections.Specialized;
 using System.ComponentModel;
 using System.ComponentModel;
+using Avalonia.Media;
 using CommunityToolkit.Mvvm.ComponentModel;
 using CommunityToolkit.Mvvm.ComponentModel;
 using PixiEditor.Models.Handlers;
 using PixiEditor.Models.Handlers;
 using Drawie.Numerics;
 using Drawie.Numerics;
+using PixiEditor.Models.Structures;
 
 
 namespace PixiEditor.ViewModels.Nodes;
 namespace PixiEditor.ViewModels.Nodes;
 
 
 public abstract class NodeFrameViewModelBase : ObservableObject
 public abstract class NodeFrameViewModelBase : ObservableObject
 {
 {
     private Guid id;
     private Guid id;
+    private StreamGeometry geometry;
     private VecD topLeft;
     private VecD topLeft;
     private VecD bottomRight;
     private VecD bottomRight;
     private VecD size;
     private VecD size;
     
     
-    public ObservableCollection<INodeHandler> Nodes { get; }
+    public ObservableHashSet<INodeHandler> Nodes { get; }
 
 
     public string InternalName { get; init; }
     public string InternalName { get; init; }
     
     
@@ -24,28 +27,16 @@ public abstract class NodeFrameViewModelBase : ObservableObject
         set => SetProperty(ref id, value);
         set => SetProperty(ref id, value);
     }
     }
     
     
-    public VecD TopLeft
+    public StreamGeometry Geometry
     {
     {
-        get => topLeft;
-        set => SetProperty(ref topLeft, value);
-    }
-
-    public VecD BottomRight
-    {
-        get => bottomRight;
-        set => SetProperty(ref bottomRight, value);
-    }
-
-    public VecD Size
-    {
-        get => size;
-        set => SetProperty(ref size, value);
+        get => geometry;
+        set => SetProperty(ref geometry, value);
     }
     }
 
 
     public NodeFrameViewModelBase(Guid id, IEnumerable<INodeHandler> nodes)
     public NodeFrameViewModelBase(Guid id, IEnumerable<INodeHandler> nodes)
     {
     {
         Id = id;
         Id = id;
-        Nodes = new ObservableCollection<INodeHandler>(nodes);
+        Nodes = new(nodes);
 
 
         Nodes.CollectionChanged += OnCollectionChanged;
         Nodes.CollectionChanged += OnCollectionChanged;
         AddHandlers(Nodes);
         AddHandlers(Nodes);
@@ -54,13 +45,22 @@ public abstract class NodeFrameViewModelBase : ObservableObject
     private void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
     private void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
     {
     {
         var action = e.Action;
         var action = e.Action;
-        if (action != NotifyCollectionChangedAction.Add && action != NotifyCollectionChangedAction.Remove && action != NotifyCollectionChangedAction.Replace && action != NotifyCollectionChangedAction.Reset)
+        if (action is
+            not NotifyCollectionChangedAction.Add and
+            not NotifyCollectionChangedAction.Remove and
+            not NotifyCollectionChangedAction.Replace and
+            not NotifyCollectionChangedAction.Reset)
         {
         {
             return;
             return;
         }
         }
+
+        CalculateBounds();
+        
+        if (e.NewItems != null)
+            AddHandlers(e.NewItems.Cast<INodeHandler>());
         
         
-        AddHandlers((IEnumerable<NodeViewModel>)e.NewItems);
-        RemoveHandlers((IEnumerable<NodeViewModel>)e.OldItems);
+        if (e.OldItems != null)
+            RemoveHandlers(e.OldItems.Cast<INodeHandler>());
     }
     }
 
 
     private void AddHandlers(IEnumerable<INodeHandler> nodes)
     private void AddHandlers(IEnumerable<INodeHandler> nodes)
@@ -81,7 +81,9 @@ public abstract class NodeFrameViewModelBase : ObservableObject
 
 
     private void NodePropertyChanged(object? sender, PropertyChangedEventArgs e)
     private void NodePropertyChanged(object? sender, PropertyChangedEventArgs e)
     {
     {
-        if (e.PropertyName != nameof(INodeHandler.PositionBindable))
+        if (e.PropertyName is
+            not nameof(INodeHandler.PositionBindable) and
+            not nameof(INodeHandler.UiSize))
         {
         {
             return;
             return;
         }
         }

+ 2 - 0
src/PixiEditor/ViewModels/Nodes/NodePropertyViewModel.cs

@@ -27,6 +27,7 @@ internal abstract class NodePropertyViewModel : ViewModelBase, INodePropertyHand
     private INodePropertyHandler? connectedOutput;
     private INodePropertyHandler? connectedOutput;
 
 
     public event NodePropertyValueChanged? ValueChanged;
     public event NodePropertyValueChanged? ValueChanged;
+    public event EventHandler? ConnectedOutputChanged;
 
 
     public string DisplayName
     public string DisplayName
     {
     {
@@ -115,6 +116,7 @@ internal abstract class NodePropertyViewModel : ViewModelBase, INodePropertyHand
             if (SetProperty(ref connectedOutput, value))
             if (SetProperty(ref connectedOutput, value))
             {
             {
                 OnPropertyChanged(nameof(ShowInputField));
                 OnPropertyChanged(nameof(ShowInputField));
+                ConnectedOutputChanged?.Invoke(this, EventArgs.Empty);
             }
             }
         }
         }
     }
     }

+ 99 - 24
src/PixiEditor/ViewModels/Nodes/NodeViewModel.cs

@@ -1,6 +1,7 @@
 using System.Collections.ObjectModel;
 using System.Collections.ObjectModel;
 using System.Collections.Specialized;
 using System.Collections.Specialized;
 using System.ComponentModel;
 using System.ComponentModel;
+using System.Diagnostics.CodeAnalysis;
 using System.Reflection;
 using System.Reflection;
 using Avalonia;
 using Avalonia;
 using Avalonia.Media;
 using Avalonia.Media;
@@ -25,6 +26,7 @@ namespace PixiEditor.ViewModels.Nodes;
 internal abstract class NodeViewModel : ObservableObject, INodeHandler
 internal abstract class NodeViewModel : ObservableObject, INodeHandler
 {
 {
     private LocalizedString displayName;
     private LocalizedString displayName;
+    private Rect size;
     private IBrush? categoryBrush;
     private IBrush? categoryBrush;
     private string? nodeNameBindable;
     private string? nodeNameBindable;
     private VecD position;
     private VecD position;
@@ -111,6 +113,12 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
         }
         }
     }
     }
 
 
+    public Rect UiSize
+    {
+        get => size;
+        set => SetProperty(ref size, value);
+    }
+
     public ObservableRangeCollection<INodePropertyHandler> Inputs
     public ObservableRangeCollection<INodePropertyHandler> Inputs
     {
     {
         get => inputs;
         get => inputs;
@@ -218,7 +226,11 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
 
 
     public string Icon => icon ??= GetType().GetCustomAttribute<NodeViewModelAttribute>().Icon;
     public string Icon => icon ??= GetType().GetCustomAttribute<NodeViewModelAttribute>().Icon;
 
 
-    public void TraverseBackwards(Func<INodeHandler, bool> func)
+    [DoesNotReturn]
+    private void ThrowInvalidTraverseResult(Traverse traverse) =>
+        throw new IndexOutOfRangeException($"Invalid Traverse Option '{traverse}'");
+
+    public void TraverseBackwards(Func<INodeHandler, Traverse> func)
     {
     {
         var visited = new HashSet<INodeHandler>();
         var visited = new HashSet<INodeHandler>();
         var queueNodes = new Queue<INodeHandler>();
         var queueNodes = new Queue<INodeHandler>();
@@ -233,9 +245,18 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
                 continue;
                 continue;
             }
             }
 
 
-            if (!func(node))
+            var result = func(node);
+            switch (result)
             {
             {
-                return;
+                case Traverse.NoFurther:
+                    continue;
+                case Traverse.Exit:
+                    return;
+                case Traverse.Further:
+                    break;
+                default:
+                    ThrowInvalidTraverseResult(result);
+                    break;
             }
             }
 
 
             foreach (var inputProperty in node.Inputs)
             foreach (var inputProperty in node.Inputs)
@@ -248,7 +269,7 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
         }
         }
     }
     }
 
 
-    public void TraverseBackwards(Func<INodeHandler, INodeHandler, bool> func)
+    public void TraverseBackwards(Func<INodeHandler, INodeHandler, Traverse> func)
     {
     {
         var visited = new HashSet<INodeHandler>();
         var visited = new HashSet<INodeHandler>();
         var queueNodes = new Queue<(INodeHandler, INodeHandler)>();
         var queueNodes = new Queue<(INodeHandler, INodeHandler)>();
@@ -263,9 +284,18 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
                 continue;
                 continue;
             }
             }
 
 
-            if (!func(node.Item1, node.Item2))
+            var result = func(node.Item1, node.Item2);
+            switch (result)
             {
             {
-                return;
+                case Traverse.NoFurther:
+                    continue;
+                case Traverse.Exit:
+                    return;
+                case Traverse.Further:
+                    break;
+                default:
+                    ThrowInvalidTraverseResult(result);
+                    break;
             }
             }
 
 
             foreach (var inputProperty in node.Item1.Inputs)
             foreach (var inputProperty in node.Item1.Inputs)
@@ -278,7 +308,7 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
         }
         }
     }
     }
 
 
-    public void TraverseBackwards(Func<INodeHandler, INodeHandler, INodePropertyHandler, bool> func)
+    public void TraverseBackwards(Func<INodeHandler, INodeHandler, INodePropertyHandler, Traverse> func)
     {
     {
         var visited = new HashSet<INodeHandler>();
         var visited = new HashSet<INodeHandler>();
         var queueNodes = new Queue<(INodeHandler, INodeHandler, INodePropertyHandler)>();
         var queueNodes = new Queue<(INodeHandler, INodeHandler, INodePropertyHandler)>();
@@ -292,10 +322,18 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
             {
             {
                 continue;
                 continue;
             }
             }
-
-            if (!func(node.Item1, node.Item2, node.Item3))
+            var result = func(node.Item1, node.Item2, node.Item3);
+            switch (result)
             {
             {
-                return;
+                case Traverse.NoFurther:
+                    continue;
+                case Traverse.Exit:
+                    return;
+                case Traverse.Further:
+                    break;
+                default:
+                    ThrowInvalidTraverseResult(result);
+                    break;
             }
             }
 
 
             foreach (var inputProperty in node.Item1.Inputs)
             foreach (var inputProperty in node.Item1.Inputs)
@@ -308,7 +346,7 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
         }
         }
     }
     }
 
 
-    public void TraverseForwards(Func<INodeHandler, bool> func)
+    public void TraverseForwards(Func<INodeHandler, Traverse> func)
     {
     {
         var visited = new HashSet<INodeHandler>();
         var visited = new HashSet<INodeHandler>();
         var queueNodes = new Queue<INodeHandler>();
         var queueNodes = new Queue<INodeHandler>();
@@ -322,10 +360,19 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
             {
             {
                 continue;
                 continue;
             }
             }
-
-            if (!func(node))
+            
+            var result = func(node);
+            switch (result)
             {
             {
-                return;
+                case Traverse.NoFurther:
+                    continue;
+                case Traverse.Exit:
+                    return;
+                case Traverse.Further:
+                    break;
+                default:
+                    ThrowInvalidTraverseResult(result);
+                    break;
             }
             }
 
 
             foreach (var outputProperty in node.Outputs)
             foreach (var outputProperty in node.Outputs)
@@ -338,7 +385,7 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
         }
         }
     }
     }
 
 
-    public void TraverseForwards(Func<INodeHandler, INodeHandler, bool> func)
+    public void TraverseForwards(Func<INodeHandler, INodeHandler, Traverse> func)
     {
     {
         var visited = new HashSet<INodeHandler>();
         var visited = new HashSet<INodeHandler>();
         var queueNodes = new Queue<(INodeHandler, INodeHandler)>();
         var queueNodes = new Queue<(INodeHandler, INodeHandler)>();
@@ -352,10 +399,19 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
             {
             {
                 continue;
                 continue;
             }
             }
-
-            if (!func(node.Item1, node.Item2))
+            
+            var result = func(node.Item1, node.Item2);
+            switch (result)
             {
             {
-                return;
+                case Traverse.NoFurther:
+                    continue;
+                case Traverse.Exit:
+                    return;
+                case Traverse.Further:
+                    break;
+                default:
+                    ThrowInvalidTraverseResult(result);
+                    break;
             }
             }
 
 
             foreach (var outputProperty in node.Item1.Outputs)
             foreach (var outputProperty in node.Item1.Outputs)
@@ -368,7 +424,7 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
         }
         }
     }
     }
 
 
-    public void TraverseForwards(Func<INodeHandler, INodeHandler, INodePropertyHandler, bool> func)
+    public void TraverseForwards(Func<INodeHandler, INodeHandler, INodePropertyHandler, Traverse> func)
     {
     {
         var visited = new HashSet<INodeHandler>();
         var visited = new HashSet<INodeHandler>();
         var queueNodes = new Queue<(INodeHandler, INodeHandler, INodePropertyHandler)>();
         var queueNodes = new Queue<(INodeHandler, INodeHandler, INodePropertyHandler)>();
@@ -383,9 +439,18 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
                 continue;
                 continue;
             }
             }
 
 
-            if (!func(node.Item1, node.Item2, node.Item3))
+            var result = func(node.Item1, node.Item2, node.Item3);
+            switch (result)
             {
             {
-                return;
+                case Traverse.NoFurther:
+                    continue;
+                case Traverse.Exit:
+                    return;
+                case Traverse.Further:
+                    break;
+                default:
+                    ThrowInvalidTraverseResult(result);
+                    break;
             }
             }
 
 
             foreach (var outputProperty in node.Item1.Outputs)
             foreach (var outputProperty in node.Item1.Outputs)
@@ -399,7 +464,7 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
     }
     }
 
 
     public void TraverseForwards(
     public void TraverseForwards(
-        Func<INodeHandler, INodeHandler, INodePropertyHandler, INodePropertyHandler, bool> func)
+        Func<INodeHandler, INodeHandler, INodePropertyHandler, INodePropertyHandler, Traverse> func)
     {
     {
         var visited = new HashSet<INodeHandler>();
         var visited = new HashSet<INodeHandler>();
         var queueNodes = new Queue<(INodeHandler, INodeHandler, INodePropertyHandler, INodePropertyHandler)>();
         var queueNodes = new Queue<(INodeHandler, INodeHandler, INodePropertyHandler, INodePropertyHandler)>();
@@ -414,9 +479,18 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
                 continue;
                 continue;
             }
             }
 
 
-            if (!func(node.Item1, node.Item2, node.Item3, node.Item4))
+            var result = func(node.Item1, node.Item2, node.Item3, node.Item4);
+            switch (result)
             {
             {
-                return;
+                case Traverse.NoFurther:
+                    continue;
+                case Traverse.Exit:
+                    return;
+                case Traverse.Further:
+                    break;
+                default:
+                    ThrowInvalidTraverseResult(result);
+                    break;
             }
             }
 
 
             foreach (var outputProperty in node.Item1.Outputs)
             foreach (var outputProperty in node.Item1.Outputs)
@@ -429,6 +503,7 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
         }
         }
     }
     }
 
 
+    public HashSet<NodeFrameViewModelBase> Frames { get; } = [];
 
 
     public virtual void Dispose()
     public virtual void Dispose()
     {
     {

+ 222 - 35
src/PixiEditor/ViewModels/Nodes/NodeZoneViewModel.cs

@@ -1,20 +1,26 @@
-using PixiEditor.Models.Handlers;
+using System.Buffers;
+using System.Runtime.InteropServices;
+using Avalonia;
+using Avalonia.Media;
+using PixiEditor.Models.Handlers;
 using Drawie.Numerics;
 using Drawie.Numerics;
 
 
 namespace PixiEditor.ViewModels.Nodes;
 namespace PixiEditor.ViewModels.Nodes;
 
 
 public sealed class NodeZoneViewModel : NodeFrameViewModelBase
 public sealed class NodeZoneViewModel : NodeFrameViewModelBase
 {
 {
-    private INodeHandler start;
-    private INodeHandler end;
+    public INodeHandler Start { get; }
     
     
-    public NodeZoneViewModel(Guid id, string internalName, INodeHandler start, INodeHandler end) : base(id, [start, end])
+    public INodeHandler End { get; }
+
+    public NodeZoneViewModel(Guid id, string internalName, INodeHandler start, INodeHandler end) : base(id,
+        [start, end])
     {
     {
         InternalName = internalName;
         InternalName = internalName;
-        
-        this.start = start.Metadata.IsPairNodeStart ? start : end;
-        this.end = start.Metadata.IsPairNodeStart ? end : start;
-        
+
+        this.Start = start.Metadata.IsPairNodeStart ? start : end;
+        this.End = start.Metadata.IsPairNodeStart ? end : start;
+
         CalculateBounds();
         CalculateBounds();
     }
     }
 
 
@@ -22,53 +28,234 @@ public sealed class NodeZoneViewModel : NodeFrameViewModelBase
     {
     {
         if (Nodes.Count == 0)
         if (Nodes.Count == 0)
         {
         {
-            if (TopLeft == BottomRight)
+            return;
+        }
+
+        var points = GetBoundPoints();
+
+        Geometry = BuildRoundedHullGeometry(points, 25);
+    }
+
+    private static StreamGeometry BuildRoundedHullGeometry(List<VecD> points, double cornerRadius)
+    {
+        const double startBoostDeg = 100;
+        const double maxBoost = 2.5;
+
+        var span = CollectionsMarshal.AsSpan(points);
+
+        var pool = ArrayPool<VecD>.Shared;
+        var hullBuf = pool.Rent(Math.Max(3, span.Length));
+
+        try
+        {
+            var hullCount = ConvexHull(span, hullBuf.AsSpan());
+            var hull = hullBuf.AsSpan(0, hullCount);
+
+            var geometry = new StreamGeometry();
+            if (hull.IsEmpty) return geometry;
+
+            using var ctx = geometry.Open();
+            
+            if (hull.Length <= 2 || cornerRadius <= 0)
             {
             {
-                BottomRight = TopLeft + new VecD(100, 100);
+                ctx.BeginFigure(new Point(hull[0].X, hull[0].Y), isFilled: true);
+                for (var i = 1; i < hull.Length; i++)
+                    ctx.LineTo(new Point(hull[i].X, hull[i].Y));
+                ctx.EndFigure(isClosed: true);
+                return geometry;
             }
             }
-            
-            return;
+
+            var n = hull.Length;
+
+            var enter = n <= 256 ? stackalloc VecD[n] : pool.Rent(n).AsSpan(0, n);
+            var exit = n <= 256 ? stackalloc VecD[n] : pool.Rent(n).AsSpan(0, n);
+            var rented = n > 256;
+
+            try
+            {
+                for (var i = 0; i < n; i++)
+                {
+                    var prev = hull[(i - 1 + n) % n];
+                    var current = hull[i];
+                    var next = hull[(i + 1) % n];
+
+                    var directionIn = (current - prev).Normalize();
+                    var directionOut = (next - current).Normalize();
+                    var lenIn = (prev - current).Length;
+                    var lenOut = (current - next).Length;
+
+                    var a = (prev - current).Normalize();
+                    var b = (next - current).Normalize();
+                    var dot = Math.Clamp(a.X * b.X + a.Y * b.Y, -1, 1);
+                    var theta = Math.Acos(dot); // radians
+
+                    // Boost wide angles a bit (same curve, fewer ops)
+                    var thetaDeg = theta * (180.0 / Math.PI);
+                    var tNorm = Math.Clamp((thetaDeg - startBoostDeg) / (180.0 - startBoostDeg), 0, 1);
+                    var s = tNorm * tNorm * (3 - 2 * tNorm); // smoothstep
+                    var radiusHere = cornerRadius * (1 + (maxBoost - 1) * s);
+
+                    var t = (theta > 1e-6) ? radiusHere / Math.Tan(theta / 2.0) : 0;
+                    var tMax = Math.Min(lenIn, lenOut) * 0.5;
+                    t = Math.Min(t, tMax);
+
+                    if (t <= 1e-6)
+                    {
+                        enter[i] = current;
+                        exit[i] = current;
+                    }
+                    else
+                    {
+                        enter[i] = current + directionIn * -t;
+                        exit[i] = current + directionOut * t;
+                    }
+                }
+
+                ctx.BeginFigure(new Point(enter[0].X, enter[0].Y), isFilled: true);
+
+                for (var i = 0; i < n; i++)
+                {
+                    ctx.QuadraticBezierTo(
+                        new Point(hull[i].X, hull[i].Y),
+                        new Point(exit[i].X, exit[i].Y));
+
+                    var nextEnter = enter[(i + 1) % n];
+                    ctx.LineTo(new Point(nextEnter.X, nextEnter.Y));
+                }
+
+                ctx.EndFigure(isClosed: true);
+            }
+            finally
+            {
+                if (rented)
+                {
+                    pool.Return(
+                        MemoryMarshal.CreateReadOnlySpan(ref MemoryMarshal.GetReference(enter), n).ToArray());
+
+                    pool.Return(MemoryMarshal.CreateReadOnlySpan(ref MemoryMarshal.GetReference(exit), n)
+                        .ToArray());
+                }
+            }
+
+            return geometry;
+        }
+        finally
+        {
+            pool.Return(hullBuf);
         }
         }
+    }
+
+    private static int ConvexHull(ReadOnlySpan<VecD> input, Span<VecD> hull)
+    {
+        var n = input.Length;
+        if (n <= 1)
+        {
+            if (n == 1) hull[0] = input[0];
+            return n;
+        }
+
+        var pool = ArrayPool<VecD>.Shared;
+        var pts = pool.Rent(n);
+
+        try
+        {
+            input.CopyTo(pts);
+            Array.Sort(pts, 0, n, VecDComparer.Instance);
+
+            var m = 0;
+            for (var i = 0; i < n; i++)
+            {
+                if (m == 0 || !pts[i].Equals(pts[m - 1]))
+                    pts[m++] = pts[i];
+            }
+
+            if (m <= 1)
+            {
+                if (m == 1) hull[0] = pts[0];
+                return m;
+            }
+
+            var k = 0;
+
+            for (var i = 0; i < m; i++)
+            {
+                while (k >= 2 && (hull[k - 1] - hull[k - 2]).Cross(pts[i] - hull[k - 2]) <= 0)
+                    k--;
+                hull[k++] = pts[i];
+            }
+
+            var t = k + 1;
+            for (var i = m - 2; i >= 0; i--)
+            {
+                while (k >= t && (hull[k - 1] - hull[k - 2]).Cross(pts[i] - hull[k - 2]) <= 0)
+                    k--;
+                hull[k++] = pts[i];
+            }
 
 
-        var bounds = GetBounds();
-        
-        var minX = bounds.Min(n => n.X);
-        var minY = bounds.Min(n => n.Y);
-        
-        var maxX = bounds.Max(n => n.Right);
-        var maxY = bounds.Max(n => n.Bottom);
+            return k - 1;
+        }
+        finally
+        {
+            pool.Return(pts);
+        }
+    }
 
 
-        TopLeft = new VecD(minX, minY);
-        BottomRight = new VecD(maxX, maxY);
+    private sealed class VecDComparer : IComparer<VecD>
+    {
+        public static readonly VecDComparer Instance = new();
 
 
-        Size = BottomRight - TopLeft;
+        public int Compare(VecD a, VecD b)
+        {
+            var cx = a.X.CompareTo(b.X);
+            return cx != 0 ? cx : a.Y.CompareTo(b.Y);
+        }
     }
     }
 
 
-    private List<RectD> GetBounds()
+    private List<VecD> GetBoundPoints()
     {
     {
-        var list = new List<RectD>();
+        var list = new List<VecD>(Nodes.Count * 4);
+
+        const int defaultXOffset = 30;
+        const int defaultYOffset = 45;
 
 
-        const int defaultXOffset = -30;
-        const int defaultYOffset = -45;
-        
-        // TODO: Use the actual node height
         foreach (var node in Nodes)
         foreach (var node in Nodes)
         {
         {
-            if (node == start)
+            var pos = node.PositionBindable;
+            var size = new VecD(node.UiSize.Size.Width, node.UiSize.Size.Height);
+
+            if (node == Start)
             {
             {
-                list.Add(new RectD(node.PositionBindable + new VecD(100, defaultYOffset), new VecD(100, 400)));
+                var twoThirdsX = size.X * (2.0 / 3.0);
+
+                list.Add(pos + new VecD(twoThirdsX, -defaultYOffset));
+                list.Add(pos + new VecD(twoThirdsX, defaultYOffset + size.Y));
+
+                list.Add(pos + new VecD(size.X + defaultXOffset, -defaultYOffset));
+                list.Add(pos + new VecD(size.X + defaultXOffset, defaultYOffset + size.Y));
                 continue;
                 continue;
             }
             }
 
 
-            if (node == end)
+            if (node == End)
             {
             {
-                list.Add(new RectD(node.PositionBindable + new VecD(defaultXOffset, defaultYOffset), new VecD(100, 400)));
+                var oneThirdX = size.X / 3.0;
+
+                list.Add(pos + new VecD(oneThirdX, -defaultYOffset));
+                list.Add(pos + new VecD(oneThirdX, defaultYOffset + size.Y));
+
+                list.Add(pos + new VecD(-defaultXOffset, -defaultYOffset));
+                list.Add(pos + new VecD(-defaultXOffset, defaultYOffset + size.Y));
                 continue;
                 continue;
             }
             }
-            
-            list.Add(new RectD(node.PositionBindable + new VecD(defaultXOffset, defaultYOffset), new VecD(200, 400)));
+
+            var right = defaultXOffset + size.X;
+            var bottom = defaultYOffset + size.Y;
+
+            list.Add(pos + new VecD(-defaultXOffset, -defaultYOffset));
+            list.Add(pos + new VecD(right, -defaultYOffset));
+            list.Add(pos + new VecD(-defaultXOffset, bottom));
+            list.Add(pos + new VecD(right, bottom));
         }
         }
-        
+
         return list;
         return list;
     }
     }
 }
 }

+ 19 - 0
src/PixiEditor/ViewModels/Nodes/Traverse.cs

@@ -0,0 +1,19 @@
+namespace PixiEditor.ViewModels.Nodes;
+
+public enum Traverse
+{
+    /// <summary>
+    /// Go further in this direction, meaning any further child connections will not be enqueued.
+    /// </summary>
+    Further,
+    
+    /// <summary>
+    /// Don't go further in this direction, meaning all further child connections will be enqueued.
+    /// </summary>
+    NoFurther,
+    
+    /// <summary>
+    /// Completely stop traversing in any direction, meaning this will drop all enqueued child connections.
+    /// </summary>
+    Exit
+}

+ 4 - 0
src/PixiEditor/ViewModels/PixiObservableObject.cs

@@ -1,5 +1,6 @@
 using System.ComponentModel;
 using System.ComponentModel;
 using CommunityToolkit.Mvvm.ComponentModel;
 using CommunityToolkit.Mvvm.ComponentModel;
+using PixiEditor.Extensions.CommonApi.UserPreferences.Settings;
 using PixiEditor.Models.Commands;
 using PixiEditor.Models.Commands;
 
 
 namespace PixiEditor.ViewModels;
 namespace PixiEditor.ViewModels;
@@ -11,4 +12,7 @@ public class PixiObservableObject : ObservableObject
         base.OnPropertyChanged(e);
         base.OnPropertyChanged(e);
         CommandController.Current.NotifyPropertyChanged(e.PropertyName);
         CommandController.Current.NotifyPropertyChanged(e.PropertyName);
     }
     }
+
+    protected void SubscribeSettingsValueChanged<T>(Setting<T> settingStore, string propertyName) =>
+        settingStore.ValueChanged += (_, _) => OnPropertyChanged(propertyName);
 }
 }

+ 16 - 3
src/PixiEditor/ViewModels/SettingsWindowViewModel.cs

@@ -170,11 +170,23 @@ internal partial class SettingsWindowViewModel : ViewModelBase
         {
         {
             Patterns = fileTypes.SelectMany(a => a.Patterns).ToList(),
             Patterns = fileTypes.SelectMany(a => a.Patterns).ToList(),
         });
         });
-        
+
+        IStorageFolder? suggestedLocation = null;
+        try
+        {
+            suggestedLocation =
+                await MainWindow.Current!.StorageProvider.TryGetWellKnownFolderAsync(WellKnownFolder.Documents);
+        }
+        catch (Exception)
+        {
+            // If we can't get the documents folder, we will just use the default location
+            // This is not a critical error, so we can ignore it
+        }
+
         IReadOnlyList<IStorageFile> files = await MainWindow.Current!.StorageProvider.OpenFilePickerAsync(new()
         IReadOnlyList<IStorageFile> files = await MainWindow.Current!.StorageProvider.OpenFilePickerAsync(new()
         {
         {
             AllowMultiple = false,
             AllowMultiple = false,
-            SuggestedStartLocation = await MainWindow.Current!.StorageProvider.TryGetWellKnownFolderAsync(WellKnownFolder.Documents),
+            SuggestedStartLocation = suggestedLocation,
             FileTypeFilter = fileTypes,
             FileTypeFilter = fileTypes,
         });
         });
         
         
@@ -272,7 +284,8 @@ internal partial class SettingsWindowViewModel : ViewModelBase
             new("KEY_BINDINGS"),
             new("KEY_BINDINGS"),
             new SettingsPage("UPDATES"),
             new SettingsPage("UPDATES"),
             new("EXPORT"),
             new("EXPORT"),
-            new SettingsPage("SCENE")
+            new SettingsPage("SCENE"),
+            new("PERFORMANCE")
         };
         };
 
 
         ILocalizationProvider.Current.OnLanguageChanged += OnLanguageChanged;
         ILocalizationProvider.Current.OnLanguageChanged += OnLanguageChanged;

+ 17 - 0
src/PixiEditor/ViewModels/SubViewModels/ToolsViewModel.cs

@@ -63,6 +63,19 @@ internal class ToolsViewModel : SubViewModel<ViewModelMain>, IToolsHandler
         }
         }
     }
     }
 
 
+    public bool SelectionTintingEnabled
+    {
+        get => PixiEditorSettings.Tools.SelectionTintingEnabled.Value;
+        set
+        {
+            if (SelectionTintingEnabled == value)
+                return;
+
+            PixiEditorSettings.Tools.SelectionTintingEnabled.Value = value;
+            OnPropertyChanged();
+        }
+    }
+
     private Cursor? toolCursor;
     private Cursor? toolCursor;
 
 
     public Cursor? ToolCursor
     public Cursor? ToolCursor
@@ -118,6 +131,7 @@ internal class ToolsViewModel : SubViewModel<ViewModelMain>, IToolsHandler
     {
     {
         owner.DocumentManagerSubViewModel.ActiveDocumentChanged += ActiveDocumentChanged;
         owner.DocumentManagerSubViewModel.ActiveDocumentChanged += ActiveDocumentChanged;
         PixiEditorSettings.Tools.PrimaryToolset.ValueChanged += PrimaryToolsetOnValueChanged;
         PixiEditorSettings.Tools.PrimaryToolset.ValueChanged += PrimaryToolsetOnValueChanged;
+        SubscribeSettingsValueChanged(PixiEditorSettings.Tools.SelectionTintingEnabled, nameof(SelectionTintingEnabled));
     }
     }
 
 
     private void PrimaryToolsetOnValueChanged(Setting<string> setting, string? newPrimaryToolset)
     private void PrimaryToolsetOnValueChanged(Setting<string> setting, string? newPrimaryToolset)
@@ -174,6 +188,9 @@ internal class ToolsViewModel : SubViewModel<ViewModelMain>, IToolsHandler
         OnPropertyChanged(nameof(NonSelectedToolSets));
         OnPropertyChanged(nameof(NonSelectedToolSets));
     }
     }
 
 
+    [Command.Basic("PixiEditor.Tools.ToggleSelectionTinting", "TOGGLE_TINTING_SELECTION", "TOGGLE_TINTING_SELECTION_DESCRIPTIVE", AnalyticsTrack = true)]
+    public void ToggleTintSelection() => SelectionTintingEnabled = !SelectionTintingEnabled;
+
     public void SetupToolsTooltipShortcuts()
     public void SetupToolsTooltipShortcuts()
     {
     {
         foreach (IToolHandler tool in allTools)
         foreach (IToolHandler tool in allTools)

+ 19 - 0
src/PixiEditor/ViewModels/SubViewModels/ViewOptionsViewModel.cs

@@ -1,6 +1,9 @@
 using Avalonia.Input;
 using Avalonia.Input;
+using PixiEditor.Extensions.CommonApi.UserPreferences.Settings.PixiEditor;
 using PixiEditor.Models.Commands.Attributes.Commands;
 using PixiEditor.Models.Commands.Attributes.Commands;
+using PixiEditor.Models.Preferences;
 using PixiEditor.UI.Common.Fonts;
 using PixiEditor.UI.Common.Fonts;
+using PixiEditor.ViewModels.UserPreferences.Settings;
 
 
 namespace PixiEditor.ViewModels.SubViewModels;
 namespace PixiEditor.ViewModels.SubViewModels;
 #nullable enable
 #nullable enable
@@ -36,9 +39,25 @@ internal class ViewOptionsViewModel : SubViewModel<ViewModelMain>
         }
         }
     }
     }
 
 
+    private int maxBilinearSampleSize = 4096;
+    public int MaxBilinearSampleSize
+    {
+        get => maxBilinearSampleSize;
+        set
+        {
+            SetProperty(ref maxBilinearSampleSize, value);
+        }
+    }
+
     public ViewOptionsViewModel(ViewModelMain owner)
     public ViewOptionsViewModel(ViewModelMain owner)
         : base(owner)
         : base(owner)
     {
     {
+        MaxBilinearSampleSize = PixiEditorSettings.Performance.MaxBilinearSampleSize.Value;
+
+        PixiEditorSettings.Performance.MaxBilinearSampleSize.ValueChanged += (s, e) =>
+        {
+            MaxBilinearSampleSize = PixiEditorSettings.Performance.MaxBilinearSampleSize.Value;
+        };
     }
     }
 
 
     [Command.Basic("PixiEditor.View.ToggleGrid", "TOGGLE_GRIDLINES", "TOGGLE_GRIDLINES", Key = Key.OemTilde,
     [Command.Basic("PixiEditor.View.ToggleGrid", "TOGGLE_GRIDLINES", "TOGGLE_GRIDLINES", Key = Key.OemTilde,

+ 4 - 3
src/PixiEditor/ViewModels/SubViewModels/ViewportWindowViewModel.cs

@@ -175,7 +175,8 @@ internal class ViewportWindowViewModel : SubViewModel<WindowViewModel>, IDockabl
         PixiEditorSettings.Scene.PrimaryBackgroundColor.ValueChanged += UpdateBackgroundBitmap;
         PixiEditorSettings.Scene.PrimaryBackgroundColor.ValueChanged += UpdateBackgroundBitmap;
         PixiEditorSettings.Scene.SecondaryBackgroundColor.ValueChanged += UpdateBackgroundBitmap;
         PixiEditorSettings.Scene.SecondaryBackgroundColor.ValueChanged += UpdateBackgroundBitmap;
 
 
-        previewPainterControl = new PreviewPainterControl(Document.PreviewPainter,
+        previewPainterControl = new PreviewPainterControl(
+            Document.MiniPreviewPainter,
             Document.AnimationDataViewModel.ActiveFrameTime.Frame);
             Document.AnimationDataViewModel.ActiveFrameTime.Frame);
         TabCustomizationSettings.Icon = previewPainterControl;
         TabCustomizationSettings.Icon = previewPainterControl;
     }
     }
@@ -187,9 +188,9 @@ internal class ViewportWindowViewModel : SubViewModel<WindowViewModel>, IDockabl
         {
         {
             OnPropertyChanged(nameof(Title));
             OnPropertyChanged(nameof(Title));
         }
         }
-        else if (e.PropertyName == nameof(DocumentViewModel.PreviewPainter))
+        else if (e.PropertyName == nameof(DocumentViewModel.MiniPreviewPainter))
         {
         {
-            previewPainterControl.PreviewPainter = Document.PreviewPainter;
+            previewPainterControl.PreviewPainter = Document.MiniPreviewPainter;
             previewPainterControl.FrameToRender = Document.AnimationDataViewModel.ActiveFrameTime.Frame;
             previewPainterControl.FrameToRender = Document.AnimationDataViewModel.ActiveFrameTime.Frame;
         }
         }
         else if (e.PropertyName == nameof(DocumentViewModel.AllChangesSaved))
         else if (e.PropertyName == nameof(DocumentViewModel.AllChangesSaved))

+ 28 - 0
src/PixiEditor/ViewModels/SubViewModels/WindowViewModel.cs

@@ -2,6 +2,7 @@
 using System.Drawing;
 using System.Drawing;
 using System.Linq;
 using System.Linq;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
+using Avalonia.Controls;
 using Avalonia.Input;
 using Avalonia.Input;
 using CommunityToolkit.Mvvm.Input;
 using CommunityToolkit.Mvvm.Input;
 using Drawie.Numerics;
 using Drawie.Numerics;
@@ -303,4 +304,31 @@ internal class WindowViewModel : SubViewModel<ViewModelMain>, IWindowHandler
         popup.Show();
         popup.Show();
         return popup;
         return popup;
     }
     }
+
+    /// <summary>
+    /// Used to save the WindowState before toggling to FullScreen-Mode.
+    /// </summary>
+    private WindowState LastWindowState { get; set; }
+
+    /// <summary>
+    /// Method used to toggle to FullScreen-Mode.
+    /// </summary>
+    [Commands_Command.Basic("PixiEditor.Window.ToggleFullscreen", "TOGGLE_FULLSCREEN", "TOGGLE_FULLSCREEN_DESCRIPTIVE",
+        Key = Key.F11,
+        Icon = PixiPerfectIcons.Fullscreen,
+        AnalyticsTrack = true)]
+    public void ToggleFullscreen()
+    {
+        var window = Owner.AttachedWindow;
+
+        if (window is null) return;
+
+        if (window.WindowState != WindowState.FullScreen) LastWindowState = window.WindowState;
+
+        window.WindowState = window.WindowState switch
+        {
+            WindowState.FullScreen => LastWindowState,
+            _ => WindowState.FullScreen
+        };
+    }
 }
 }

+ 21 - 0
src/PixiEditor/ViewModels/UserPreferences/Settings/PerformanceSettings.cs

@@ -0,0 +1,21 @@
+using PixiEditor.Extensions.CommonApi.UserPreferences;
+
+namespace PixiEditor.ViewModels.UserPreferences.Settings;
+
+internal class PerformanceSettings : SettingsGroup
+{
+    private bool disablePreviews = GetPreference(PreferencesConstants.DisablePreviews, PreferencesConstants.DisablePreviewsDefault);
+    private int maxBilinearSize = GetPreference(PreferencesConstants.MaxBilinearSampleSize, PreferencesConstants.MaxBilinearSampleSizeDefault);
+
+    public bool DisablePreviews
+    {
+        get => disablePreviews;
+        set => RaiseAndUpdatePreference(ref disablePreviews, value, PreferencesConstants.DisablePreviews);
+    }
+
+    public int MaxBilinearSize
+    {
+        get => maxBilinearSize;
+        set => RaiseAndUpdatePreference(ref maxBilinearSize, value, PreferencesConstants.MaxBilinearSampleSize);
+    }
+}

+ 9 - 0
src/PixiEditor/ViewModels/UserPreferences/Settings/SceneSettings.cs

@@ -3,6 +3,7 @@ using Avalonia.Media;
 using CommunityToolkit.Mvvm.Input;
 using CommunityToolkit.Mvvm.Input;
 using Drawie.Numerics;
 using Drawie.Numerics;
 using PixiEditor.Extensions.CommonApi.UserPreferences;
 using PixiEditor.Extensions.CommonApi.UserPreferences;
+using PixiEditor.Extensions.CommonApi.UserPreferences.Settings.PixiEditor;
 using PixiEditor.Helpers.Extensions;
 using PixiEditor.Helpers.Extensions;
 
 
 namespace PixiEditor.ViewModels.UserPreferences.Settings;
 namespace PixiEditor.ViewModels.UserPreferences.Settings;
@@ -44,6 +45,12 @@ internal class SceneSettings : SettingsGroup
         set => RaiseAndUpdatePreference(ref _secondaryBackgroundColorHex, value, PreferencesConstants.SecondaryBackgroundColor);
         set => RaiseAndUpdatePreference(ref _secondaryBackgroundColorHex, value, PreferencesConstants.SecondaryBackgroundColor);
     }
     }
 
 
+    public bool SelectionTintingEnabled
+    {
+        get => PixiEditorSettings.Tools.SelectionTintingEnabled.Value;
+        set => RaiseAndUpdatePreference(PixiEditorSettings.Tools.SelectionTintingEnabled, value);
+    }
+
     public Color PrimaryBackgroundColor
     public Color PrimaryBackgroundColor
     {
     {
         get => Color.Parse(PrimaryBackgroundColorHex);
         get => Color.Parse(PrimaryBackgroundColorHex);
@@ -65,5 +72,7 @@ internal class SceneSettings : SettingsGroup
             PrimaryBackgroundColorHex = PreferencesConstants.PrimaryBackgroundColorDefault;
             PrimaryBackgroundColorHex = PreferencesConstants.PrimaryBackgroundColorDefault;
             SecondaryBackgroundColorHex = PreferencesConstants.SecondaryBackgroundColorDefault;
             SecondaryBackgroundColorHex = PreferencesConstants.SecondaryBackgroundColorDefault;
         });
         });
+        
+        SubscribeValueChanged(PixiEditorSettings.Tools.SelectionTintingEnabled, nameof(SelectionTintingEnabled));
     }
     }
 }
 }

+ 16 - 1
src/PixiEditor/ViewModels/UserPreferences/SettingsGroup.cs

@@ -1,10 +1,11 @@
 using System.Runtime.CompilerServices;
 using System.Runtime.CompilerServices;
 using CommunityToolkit.Mvvm.ComponentModel;
 using CommunityToolkit.Mvvm.ComponentModel;
 using PixiEditor.Extensions.CommonApi.UserPreferences;
 using PixiEditor.Extensions.CommonApi.UserPreferences;
+using PixiEditor.Extensions.CommonApi.UserPreferences.Settings;
 
 
 namespace PixiEditor.ViewModels.UserPreferences;
 namespace PixiEditor.ViewModels.UserPreferences;
 
 
-internal class SettingsGroup : ObservableObject
+internal class SettingsGroup : PixiObservableObject
 {
 {
     protected static T GetPreference<T>(string name)
     protected static T GetPreference<T>(string name)
     {
     {
@@ -27,4 +28,18 @@ internal class SettingsGroup : ObservableObject
         SetProperty(ref backingStore, value, propertyName: name);
         SetProperty(ref backingStore, value, propertyName: name);
         IPreferences.Current.UpdatePreference(name, value);
         IPreferences.Current.UpdatePreference(name, value);
     }
     }
+    
+    protected void RaiseAndUpdatePreference<T>(Setting<T> settingStore, T value, [CallerMemberName] string name = "")
+    {
+        if (EqualityComparer<T>.Default.Equals(settingStore.Value, value))
+            return;
+
+        settingStore.Value = value;
+        OnPropertyChanged(name);
+    }
+
+    protected void SubscribeValueChanged<T>(Setting<T> settingStore, string propertyName)
+    {
+        settingStore.ValueChanged += (_, _) => OnPropertyChanged(propertyName);
+    }
 }
 }

+ 2 - 0
src/PixiEditor/ViewModels/UserPreferences/SettingsViewModel.cs

@@ -17,6 +17,8 @@ internal class SettingsViewModel : SubViewModel<SettingsWindowViewModel>
 
 
     public SceneSettings Scene { get; set; } = new();
     public SceneSettings Scene { get; set; } = new();
 
 
+    public PerformanceSettings Performance { get; set; } = new();
+
     public SettingsViewModel(SettingsWindowViewModel owner)
     public SettingsViewModel(SettingsWindowViewModel owner)
         : base(owner)
         : base(owner)
     {
     {

+ 1 - 0
src/PixiEditor/Views/Dock/DocumentTemplate.axaml

@@ -45,6 +45,7 @@
         CustomBackgroundScaleY="{Binding CustomBackgroundScaleY, Mode=OneWay}"
         CustomBackgroundScaleY="{Binding CustomBackgroundScaleY, Mode=OneWay}"
         BackgroundBitmap="{Binding BackgroundBitmap, Mode=OneWay}"
         BackgroundBitmap="{Binding BackgroundBitmap, Mode=OneWay}"
         HudVisible="{Binding HudVisible}"
         HudVisible="{Binding HudVisible}"
+        MaxBilinearSamplingSize="{Binding ViewportSubViewModel.MaxBilinearSampleSize, Source={viewModels1:MainVM}}"
         Document="{Binding Document}">
         Document="{Binding Document}">
     </viewportControls:Viewport>
     </viewportControls:Viewport>
 </UserControl>
 </UserControl>

+ 5 - 9
src/PixiEditor/Views/Main/CommandSearch/CommandSearchControl.axaml.cs

@@ -184,11 +184,11 @@ internal partial class CommandSearchControl : UserControl, INotifyPropertyChange
         }
         }
         else if (e.Key is Key.Down or Key.PageDown)
         else if (e.Key is Key.Down or Key.PageDown)
         {
         {
-            MoveSelection(1);
+            MoveSelection(NextToDirection.Forwards);
         }
         }
         else if (e.Key is Key.Up or Key.PageUp)
         else if (e.Key is Key.Up or Key.PageUp)
         {
         {
-            MoveSelection(-1);
+            MoveSelection(NextToDirection.Backwards);
         }
         }
         else if (e.Key == Key.Escape ||
         else if (e.Key == Key.Escape ||
                  CommandController.Current.Commands["PixiEditor.Search.Toggle"].Shortcut
                  CommandController.Current.Commands["PixiEditor.Search.Toggle"].Shortcut
@@ -266,22 +266,18 @@ internal partial class CommandSearchControl : UserControl, INotifyPropertyChange
         }
         }
     }
     }
 
 
-    private void MoveSelection(int delta)
+    private void MoveSelection(NextToDirection direction)
     {
     {
-        if (delta == 0)
-            return;
         if (SelectedResult is null)
         if (SelectedResult is null)
         {
         {
             SelectedResult = Results.FirstOrDefault(x => x.CanExecute);
             SelectedResult = Results.FirstOrDefault(x => x.CanExecute);
             return;
             return;
         }
         }
 
 
-        int newIndex = Results.IndexOf(SelectedResult) + delta;
+        var newIndex = Results.IndexOf(SelectedResult) + (int)direction;
         newIndex = (newIndex % Results.Count + Results.Count) % Results.Count;
         newIndex = (newIndex % Results.Count + Results.Count) % Results.Count;
 
 
-        SelectedResult = delta > 0
-            ? Results.IndexOrNext(x => x.CanExecute, newIndex)
-            : Results.IndexOrPrevious(x => x.CanExecute, newIndex);
+        SelectedResult = Results.IndexOrNextInDirection(x => x.CanExecute, newIndex, direction);
 
 
         newIndex = Results.IndexOf(SelectedResult);
         newIndex = Results.IndexOf(SelectedResult);
         itemscontrol.ContainerFromIndex(newIndex)?.BringIntoView();
         itemscontrol.ContainerFromIndex(newIndex)?.BringIntoView();

+ 1 - 0
src/PixiEditor/Views/Main/ViewportControls/Viewport.axaml

@@ -257,6 +257,7 @@
             CustomBackgroundScaleX="{Binding CustomBackgroundScaleX, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}, Mode=OneWay}"
             CustomBackgroundScaleX="{Binding CustomBackgroundScaleX, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}, Mode=OneWay}"
             CustomBackgroundScaleY="{Binding CustomBackgroundScaleY, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}, Mode=OneWay}"
             CustomBackgroundScaleY="{Binding CustomBackgroundScaleY, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}, Mode=OneWay}"
             BackgroundBitmap="{Binding BackgroundBitmap, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}, Mode=OneWay}"
             BackgroundBitmap="{Binding BackgroundBitmap, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}, Mode=OneWay}"
+            MaxBilinearSamplingSize="{Binding MaxBilinearSamplingSize, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}, Mode=OneWay}"
             PointerPressed="Scene_OnContextMenuOpening"
             PointerPressed="Scene_OnContextMenuOpening"
             ui1:RenderOptionsBindable.BitmapInterpolationMode="{Binding Scale, Converter={converters:ScaleToBitmapScalingModeConverter}, RelativeSource={RelativeSource Self}}">
             ui1:RenderOptionsBindable.BitmapInterpolationMode="{Binding Scale, Converter={converters:ScaleToBitmapScalingModeConverter}, RelativeSource={RelativeSource Self}}">
             <rendering:Scene.ContextFlyout>
             <rendering:Scene.ContextFlyout>

+ 8 - 0
src/PixiEditor/Views/Main/ViewportControls/Viewport.axaml.cs

@@ -382,6 +382,8 @@ internal partial class Viewport : UserControl, INotifyPropertyChanged
 
 
     private MouseUpdateController? mouseUpdateController;
     private MouseUpdateController? mouseUpdateController;
     private ViewportOverlays builtInOverlays = new();
     private ViewportOverlays builtInOverlays = new();
+    public static readonly StyledProperty<int> MaxBilinearSamplingSizeProperty
+        = AvaloniaProperty.Register<Viewport, int>("MaxBilinearSamplingSize", 4096);
 
 
     public static readonly StyledProperty<bool> SnappingEnabledProperty =
     public static readonly StyledProperty<bool> SnappingEnabledProperty =
         AvaloniaProperty.Register<Viewport, bool>("SnappingEnabled");
         AvaloniaProperty.Register<Viewport, bool>("SnappingEnabled");
@@ -445,6 +447,12 @@ internal partial class Viewport : UserControl, INotifyPropertyChanged
         set { SetValue(AvailableRenderOutputsProperty, value); }
         set { SetValue(AvailableRenderOutputsProperty, value); }
     }
     }
 
 
+    public int MaxBilinearSamplingSize
+    {
+        get { return (int)GetValue(MaxBilinearSamplingSizeProperty); }
+        set { SetValue(MaxBilinearSamplingSizeProperty, value); }
+    }
+
     private void ForceRefreshFinalImage()
     private void ForceRefreshFinalImage()
     {
     {
         Scene.InvalidateVisual();
         Scene.InvalidateVisual();

Some files were not shown because too many files changed in this diff