Browse Source

Merge branch 'master' into pixiauth

Krzysztof Krysiński 4 months ago
parent
commit
1dab6af370
41 changed files with 638 additions and 186 deletions
  1. 1 1
      src/ColorPicker
  2. 1 1
      src/Directory.Build.props
  3. 1 1
      src/Drawie
  4. 1 1
      src/PixiDocks
  5. 11 16
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/FilterNodes/ApplyFilterNode.cs
  6. 11 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ImageLayerNode.cs
  7. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Node.cs
  8. 44 7
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ShaderNode.cs
  9. 61 32
      src/PixiEditor.ChangeableDocument/Changes/Root/RotateImage_Change.cs
  10. 15 0
      src/PixiEditor.Extensions.CommonApi/UserPreferences/PreferencesConstants.cs
  11. 11 0
      src/PixiEditor.Extensions.CommonApi/UserPreferences/Settings/PixiEditor/PixiEditorSettings.cs
  12. BIN
      src/PixiEditor.Extensions.Sdk/build/PixiEditor.Api.CGlueMSBuild.dll
  13. BIN
      src/PixiEditor.Extensions.Sdk/build/PixiEditor.Extensions.MSPackageBuilder.dll
  14. 6 1
      src/PixiEditor/Data/Configs/ToolSetsConfig.json
  15. 9 2
      src/PixiEditor/Data/Localization/Languages/en.json
  16. 14 24
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/VectorPathToolExecutor.cs
  17. 2 2
      src/PixiEditor/Properties/AssemblyInfo.cs
  18. 1 1
      src/PixiEditor/ViewModels/Document/DocumentViewModel.cs
  19. 2 0
      src/PixiEditor/ViewModels/SettingsWindowViewModel.cs
  20. 2 2
      src/PixiEditor/ViewModels/SubViewModels/AnimationsViewModel.cs
  21. 1 1
      src/PixiEditor/ViewModels/SubViewModels/ToolsViewModel.cs
  22. 118 6
      src/PixiEditor/ViewModels/SubViewModels/ViewportWindowViewModel.cs
  23. 69 0
      src/PixiEditor/ViewModels/UserPreferences/Settings/SceneSettings.cs
  24. 2 0
      src/PixiEditor/ViewModels/UserPreferences/SettingsViewModel.cs
  25. 4 0
      src/PixiEditor/Views/Dock/DocumentTemplate.axaml
  26. 5 5
      src/PixiEditor/Views/Input/SizeInput.axaml.cs
  27. 1 0
      src/PixiEditor/Views/Layers/LayerControl.axaml
  28. 7 1
      src/PixiEditor/Views/Layers/LayerControl.axaml.cs
  29. 6 0
      src/PixiEditor/Views/Layers/LayersManager.axaml
  30. 1 0
      src/PixiEditor/Views/Main/Tools/ToolPickerButton.axaml
  31. 12 7
      src/PixiEditor/Views/Main/ViewportControls/Viewport.axaml
  32. 36 0
      src/PixiEditor/Views/Main/ViewportControls/Viewport.axaml.cs
  33. 1 1
      src/PixiEditor/Views/Overlays/Handles/AnchorHandle.cs
  34. 2 0
      src/PixiEditor/Views/Overlays/Handles/ControlPointHandle.cs
  35. 19 3
      src/PixiEditor/Views/Overlays/PathOverlay/EditableVectorPath.cs
  36. 6 4
      src/PixiEditor/Views/Overlays/PathOverlay/SubShape.cs
  37. 12 4
      src/PixiEditor/Views/Overlays/PathOverlay/VectorPathOverlay.cs
  38. 0 1
      src/PixiEditor/Views/Palettes/PaletteViewer.axaml
  39. 49 30
      src/PixiEditor/Views/Rendering/Scene.cs
  40. 92 30
      src/PixiEditor/Views/Windows/Settings/SettingsWindow.axaml
  41. 1 1
      tests/Directory.Build.props

+ 1 - 1
src/ColorPicker

@@ -1 +1 @@
-Subproject commit ef170d8a8ace0e58889e6586c1539b6a19ac55aa
+Subproject commit 14fd539e1e72dc03d7bb04e14b1d5cbaf1202306

+ 1 - 1
src/Directory.Build.props

@@ -1,7 +1,7 @@
 <Project>
     <PropertyGroup>
         <CodeAnalysisRuleSet>../Custom.ruleset</CodeAnalysisRuleSet>
-		    <AvaloniaVersion>11.2.6</AvaloniaVersion>
+		    <AvaloniaVersion>11.2.7</AvaloniaVersion>
     </PropertyGroup>
     <ItemGroup>
         <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />

+ 1 - 1
src/Drawie

@@ -1 +1 @@
-Subproject commit e0dc4185b84cfd9f8fa5bb5234e8cbf9e0bb851e
+Subproject commit 72824cf4ae68a88c69002f6b2e0e81283ff7905a

+ 1 - 1
src/PixiDocks

@@ -1 +1 @@
-Subproject commit 1420290bb4485a34cede1a5018bb0dc3bf9b8b1f
+Subproject commit c4bfc826ebe5c44b72930c27dab0d7661850e58e

+ 11 - 16
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/FilterNodes/ApplyFilterNode.cs

@@ -35,18 +35,23 @@ public class ApplyFilterNode : RenderNode, IRenderInput
 
         if (!context.ProcessingColorSpace.IsSrgb)
         {
-            var target = Texture.ForProcessing(surface, ColorSpace.CreateSrgb());
+            var intermediate = Texture.ForProcessing(surface, context.ProcessingColorSpace);
 
             int saved = surface.Canvas.Save();
             surface.Canvas.SetMatrix(Matrix3X3.Identity);
 
-            target.DrawingSurface.Canvas.SaveLayer(_paint);
-            Background.Value?.Paint(context, target.DrawingSurface);
-            target.DrawingSurface.Canvas.Restore();
+            Background.Value?.Paint(context, intermediate.DrawingSurface);
 
-            surface.Canvas.DrawSurface(target.DrawingSurface, 0, 0);
+            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();
+
+            surface.Canvas.DrawSurface(srgbSurface.DrawingSurface, 0, 0);
             surface.Canvas.RestoreToCount(saved);
-            target.Dispose();
+            intermediate.Dispose();
+            srgbSurface.Dispose();
         }
         else
         {
@@ -61,15 +66,5 @@ public class ApplyFilterNode : RenderNode, IRenderInput
         return PreviewUtils.FindPreviewBounds(Background.Connection, frame, elementToRenderName);
     }
 
-    public override bool RenderPreview(DrawingSurface renderOn, RenderContext context,
-        string elementToRenderName)
-    {
-        int layer = renderOn.Canvas.SaveLayer(_paint);
-        Background.Value?.Paint(context, renderOn);
-        renderOn.Canvas.RestoreToCount(layer);
-
-        return true;
-    }
-
     public override Node CreateCopy() => new ApplyFilterNode();
 }

+ 11 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ImageLayerNode.cs

@@ -302,6 +302,17 @@ public class ImageLayerNode : LayerNode, IReadOnlyImageNode
         }
     }
 
+    public void ForEveryFrame(Action<ChunkyImage, Guid> action)
+    {
+        foreach (var frame in keyFrames)
+        {
+            if (frame.Data is ChunkyImage imageFrame)
+            {
+                action(imageFrame, frame.KeyFrameGuid);
+            }
+        }
+    }
+
     public ChunkyImage GetLayerImageAtFrame(int frame)
     {
         return GetFrameWithImage(frame).Data as ChunkyImage;

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

@@ -64,7 +64,7 @@ public abstract class Node : IReadOnlyNode, IDisposable
     {
         if (_isDisposed) throw new ObjectDisposedException("Node was disposed before execution.");
 
-        if (ExecuteOnlyOnCacheChange && !CacheChanged(context))
+        if (!context.FullRerender && ExecuteOnlyOnCacheChange && !CacheChanged(context))
         {
             return;
         }

+ 44 - 7
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ShaderNode.cs

@@ -1,5 +1,6 @@
 using Drawie.Backend.Core;
 using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Shaders;
 using Drawie.Backend.Core.Shaders.Generation;
 using Drawie.Backend.Core.Surfaces;
@@ -87,7 +88,7 @@ public class ShaderNode : RenderNode, IRenderInput, ICustomShaderNode
         Uniforms uniforms;
         uniforms = new Uniforms();
 
-        uniforms.Add("iResolution", new Uniform("iResolution", context.DocumentSize));
+        uniforms.Add("iResolution", new Uniform("iResolution", (VecD)context.DocumentSize));
         uniforms.Add("iNormalizedTime", new Uniform("iNormalizedTime", (float)context.FrameTime.NormalizedTime));
         uniforms.Add("iFrame", new Uniform("iFrame", context.FrameTime.Frame));
 
@@ -222,6 +223,22 @@ public class ShaderNode : RenderNode, IRenderInput, ICustomShaderNode
                 {
                     input = CreateInput<Vec3D>(uniform.Name, uniform.Name, new Vec3D(0, 0, 0));
                 }
+                else if (uniform.DataType == UniformValueType.Vector4)
+                {
+                    input = CreateInput<Vec4D>(uniform.Name, uniform.Name, new Vec4D(0, 0, 0, 0));
+                }
+                else if (uniform.DataType == UniformValueType.Int)
+                {
+                    input = CreateInput<int>(uniform.Name, uniform.Name, 0);
+                }
+                else if (uniform.DataType == UniformValueType.Vector2Int)
+                {
+                    input = CreateInput<VecI>(uniform.Name, uniform.Name, new VecI(0, 0));
+                }
+                else if (uniform.DataType == UniformValueType.Matrix3X3)
+                {
+                    input = CreateInput<Matrix3X3>(uniform.Name, uniform.Name, Matrix3X3.Identity);
+                }
                 else
                 {
                     continue;
@@ -259,9 +276,12 @@ public class ShaderNode : RenderNode, IRenderInput, ICustomShaderNode
                 {
                     uniforms.Add(input.Key, new Uniform(input.Key, (float)doubleValue));
                 }
-                else if (value is int intValue)
+            }
+            else if (input.Value.valueType == UniformValueType.Int)
+            {
+                if (value is int intValue)
                 {
-                    uniforms.Add(input.Key, new Uniform(input.Key, (float)intValue));
+                    uniforms.Add(input.Key, new Uniform(input.Key, intValue));
                 }
             }
             else if (input.Value.valueType == UniformValueType.Vector2)
@@ -270,10 +290,6 @@ public class ShaderNode : RenderNode, IRenderInput, ICustomShaderNode
                 {
                     uniforms.Add(input.Key, new Uniform(input.Key, vector));
                 }
-                else if (value is VecI vecI)
-                {
-                    uniforms.Add(input.Key, new Uniform(input.Key, new VecD(vecI.X, vecI.Y)));
-                }
             }
             else if (input.Value.valueType == UniformValueType.Vector3)
             {
@@ -289,6 +305,27 @@ public class ShaderNode : RenderNode, IRenderInput, ICustomShaderNode
                     uniforms.Add(input.Key, new Uniform(input.Key, vector));
                 }
             }
+            else if (input.Value.valueType == UniformValueType.Vector2Int)
+            {
+                if (value is VecI vector)
+                {
+                    uniforms.Add(input.Key, new Uniform(input.Key, vector));
+                }
+            }
+            else if (input.Value.valueType is UniformValueType.Vector3Int or UniformValueType.Vector4Int)
+            {
+                if (value is int[] vector)
+                {
+                    uniforms.Add(input.Key, new Uniform(input.Key, vector));
+                }
+            }
+            else if (input.Value.valueType == UniformValueType.Matrix3X3)
+            {
+                if (value is Matrix3X3 matrix)
+                {
+                    uniforms.Add(input.Key, new Uniform(input.Key, matrix));
+                }
+            }
             else if (input.Value.valueType == UniformValueType.Color)
             {
                 if (value is Color color)

+ 61 - 32
src/PixiEditor.ChangeableDocument/Changes/Root/RotateImage_Change.cs

@@ -52,8 +52,8 @@ internal sealed class RotateImage_Change : Change
                     }
                 }
             }
-            
-            if(frame != null && (bounds == null || bounds.Value.IsZeroArea)) return false;
+
+            if (frame != null && (bounds == null || bounds.Value.IsZeroArea)) return false;
         }
 
         originalSize = target.Size;
@@ -83,7 +83,7 @@ internal sealed class RotateImage_Change : Change
                 bounds = preciseBounds.Value;
             }
         }
-        
+
         if (bounds.IsZeroArea)
         {
             return;
@@ -167,10 +167,8 @@ internal sealed class RotateImage_Change : Change
                     }
                     else
                     {
-                        layer.ForEveryFrame(img =>
-                        {
-                            Resize(img, layer.Id, deletedChunks, changes);
-                        });
+                        var img = layer.GetLayerImageAtFrame(0);
+                        Resize(img, layer.Id, deletedChunks, changes);
                     }
                 }
                 else if (member is ITransformableObject transformableObject)
@@ -216,9 +214,9 @@ internal sealed class RotateImage_Change : Change
                 }
                 else
                 {
-                    layer.ForEveryFrame(img =>
+                    layer.ForEveryFrame((img, id) =>
                     {
-                        Resize(img, layer.Id, deletedChunks, null);
+                        Resize(img, id, deletedChunks, null);
                     });
                 }
             }
@@ -245,7 +243,32 @@ internal sealed class RotateImage_Change : Change
     private OneOf<None, IChangeInfo, List<IChangeInfo>> RevertRotateWholeImage(Document target)
     {
         target.Size = originalSize;
-        RevertRotateMembers(target);
+        List<IChangeInfo> revertChanges = new List<IChangeInfo>();
+        target.ForEveryMember((member) =>
+        {
+            if (membersToRotate.Count > 0 && !membersToRotate.Contains(member.Id)) return;
+            if (member is ImageLayerNode layer)
+            {
+                if (frame != null)
+                {
+                    RevertFrame(layer, frame.Value, revertChanges);
+                }
+                else
+                {
+                    layer.ForEveryFrame((img, id) =>
+                    {
+                        RevertFrame(img, id, revertChanges);
+                    });
+                }
+            }
+
+            if (member.EmbeddedMask is null)
+                return;
+
+            RevertMask(member, revertChanges);
+        });
+
+        DisposeDeletedChunks();
 
         target.HorizontalSymmetryAxisY = originalHorAxisY;
         target.VerticalSymmetryAxisX = originalVerAxisX;
@@ -261,38 +284,44 @@ internal sealed class RotateImage_Change : Change
             if (membersToRotate.Count > 0 && !membersToRotate.Contains(member.Id)) return;
             if (member is ImageLayerNode layer)
             {
-                if (frame != null)
-                {
-                    var layerImage = layer.GetLayerImageAtFrame(frame.Value);
-                    layerImage.EnqueueResize(originalSize);
-                    deletedChunks[layer.Id].ApplyChunksToImage(layerImage);
-                    revertChanges.Add(new LayerImageArea_ChangeInfo(layer.Id, layerImage.FindAffectedArea()));
-                    layerImage.CommitChanges();
-                }
-                else
-                {
-                    layer.ForEveryFrame(img =>
-                    {
-                        img.EnqueueResize(originalSize);
-                        deletedChunks[layer.Id].ApplyChunksToImage(img);
-                        revertChanges.Add(new LayerImageArea_ChangeInfo(layer.Id, img.FindAffectedArea()));
-                        img.CommitChanges();
-                    });
-                }
+                RevertFrame(layer, frame ?? 0, revertChanges);
             }
 
             if (member.EmbeddedMask is null)
                 return;
-            member.EmbeddedMask.EnqueueResize(originalSize);
-            deletedMaskChunks[member.Id].ApplyChunksToImage(member.EmbeddedMask);
-            revertChanges.Add(new LayerImageArea_ChangeInfo(member.Id, member.EmbeddedMask.FindAffectedArea()));
-            member.EmbeddedMask.CommitChanges();
+            RevertMask(member, revertChanges);
         });
 
         DisposeDeletedChunks();
         return revertChanges;
     }
 
+
+    private void RevertFrame(ImageLayerNode layer, int targetFrame, List<IChangeInfo> revertChanges)
+    {
+        RevertFrame(layer.GetLayerImageAtFrame(targetFrame), layer.Id, revertChanges);
+    }
+
+    private void RevertFrame(ChunkyImage img, Guid id, List<IChangeInfo> revertChanges)
+    {
+        img.EnqueueResize(originalSize);
+        if (deletedChunks.ContainsKey(id))
+        {
+            deletedChunks[id].ApplyChunksToImage(img);
+        }
+
+        revertChanges.Add(new LayerImageArea_ChangeInfo(id, img.FindAffectedArea()));
+        img.CommitChanges();
+    }
+
+    private void RevertMask(StructureNode member, List<IChangeInfo> revertChanges)
+    {
+        member.EmbeddedMask.EnqueueResize(originalSize);
+        deletedMaskChunks[member.Id].ApplyChunksToImage(member.EmbeddedMask);
+        revertChanges.Add(new LayerImageArea_ChangeInfo(member.Id, member.EmbeddedMask.FindAffectedArea()));
+        member.EmbeddedMask.CommitChanges();
+    }
+
     private void DisposeDeletedChunks()
     {
         foreach (var stored in deletedChunks)

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

@@ -33,6 +33,21 @@ public static class PreferencesConstants
 
     public const string AnalyticsEnabled = "AnalyticsEnabled";
     public const bool AnalyticsEnabledDefault = true;
+
     public const string PrimaryToolset = "PrimaryToolset";
     public const string PrimaryToolsetDefault = "PAINT_TOOLSET";
+
+    public const string AutoScaleBackground = "AutoScaleBackground";
+    public const bool AutoScaleBackgroundDefault = true;
+
+    public const string CustomBackgroundScaleX = "CustomBackgroundScaleX";
+    public const string CustomBackgroundScaleY = "CustomBackgroundScaleY";
+    public const double CustomBackgroundScaleDefault = 16;
+
+    public const string PrimaryBackgroundColorDefault = "#616161";
+    public const string PrimaryBackgroundColor = "PrimaryBackgroundColor";
+
+    public const string SecondaryBackgroundColorDefault = "#353535";
+    public const string SecondaryBackgroundColor = "SecondaryBackgroundColor";
+
 }

+ 11 - 0
src/PixiEditor.Extensions.CommonApi/UserPreferences/Settings/PixiEditor/PixiEditorSettings.cs

@@ -71,4 +71,15 @@ public static class PixiEditorSettings
     {
         public static SyncedSetting<bool> AnalyticsEnabled { get; } = SyncedSetting.NonOwned(PixiEditor, true);
     }
+
+    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<string> PrimaryBackgroundColor { get; } = SyncedSetting.NonOwned(PixiEditor, PreferencesConstants.PrimaryBackgroundColorDefault, PreferencesConstants.PrimaryBackgroundColor);
+        public static SyncedSetting<string> SecondaryBackgroundColor { get; } = SyncedSetting.NonOwned(PixiEditor, PreferencesConstants.SecondaryBackgroundColorDefault, PreferencesConstants.SecondaryBackgroundColor);
+
+    }
 }

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


BIN
src/PixiEditor.Extensions.Sdk/build/PixiEditor.Extensions.MSPackageBuilder.dll


+ 6 - 1
src/PixiEditor/Data/Configs/ToolSetsConfig.json

@@ -17,7 +17,12 @@
       "Select",
       "MagicWand",
       "Lasso",
-      "FloodFill",
+      {
+        "ToolName": "FloodFill",
+        "Settings": {
+          "Tolerance": 0
+        }
+      },
       "RasterLine",
       "RasterEllipse",
       "RasterRectangle",

+ 9 - 2
src/PixiEditor/Data/Localization/Languages/en.json

@@ -783,7 +783,7 @@
   "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 on a path to insert a point.",
+  "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",
@@ -1045,5 +1045,12 @@
   "LOGIN_LINK_INFO": "We'll email you a secure link to log in. No password needed.",
   "LOGIN_WINDOW_TITLE": "Founder's Account",
   "CONNECTION_TIMEOUT": "Connection timed out. Please try again.",
-  "OPEN_LOGIN_WINDOW": "Open login window"
+  "OPEN_LOGIN_WINDOW": "Open login window",
+  "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"
 }

+ 14 - 24
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/VectorPathToolExecutor.cs

@@ -137,26 +137,26 @@ internal class VectorPathToolExecutor : UpdateableChangeExecutor, IPathExecutorF
 
     public override void OnLeftMouseButtonDown(MouseOnCanvasEventArgs args)
     {
-        bool allClosed = WholePathClosed();
-        if (!isValidPathLayer || allClosed)
+        if (args.KeyModifiers.HasFlag(KeyModifiers.Shift) || NeedsNewLayer(member, document.AnimationHandler.ActiveFrameTime))
         {
-            if (NeedsNewLayer(document.SelectedStructureMember, document.AnimationHandler.ActiveFrameTime))
-            {
-                Guid? created =
-                    document.Operations.CreateStructureMember(typeof(VectorLayerNode), ActionSource.Automated);
+            Guid? created =
+                document.Operations.CreateStructureMember(typeof(VectorLayerNode), ActionSource.Automated);
 
-                if (created is null) return;
+            if (created is null) return;
 
-                document.Operations.SetSelectedMember(created.Value);
-            }
+            document.Operations.SetSelectedMember(created.Value);
         }
     }
 
-    private bool WholePathClosed()
+    private bool NeedsNewLayer(IStructureMemberHandler? member, KeyFrameTime frameTime)
     {
-        EditableVectorPath editablePath = new EditableVectorPath(startingPath);
+        var shapeData = (member as IVectorLayerHandler).GetShapeData(frameTime);
+        if (shapeData is null)
+        {
+            return false;
+        }
 
-        return editablePath.SubShapes.Count > 0 && editablePath.SubShapes.All(x => x.IsClosed);
+        return shapeData is not IReadOnlyPathData pathData;
     }
 
     public override void OnLeftMouseButtonUp(VecD pos)
@@ -205,7 +205,8 @@ internal class VectorPathToolExecutor : UpdateableChangeExecutor, IPathExecutorF
     private void AddToUndo(VectorPath path)
     {
         internals.ActionAccumulator.AddFinishedActions(new EndSetShapeGeometry_Action(),
-            new SetShapeGeometry_Action(member.Id, ConstructShapeData(path), VectorShapeChangeType.GeometryData), new EndSetShapeGeometry_Action());
+            new SetShapeGeometry_Action(member.Id, ConstructShapeData(path), VectorShapeChangeType.GeometryData),
+            new EndSetShapeGeometry_Action());
     }
 
     private PathVectorData ConstructShapeData(VectorPath? path)
@@ -259,17 +260,6 @@ internal class VectorPathToolExecutor : UpdateableChangeExecutor, IPathExecutorF
         document.SnappingHandler.SnappingController.HighlightedPoint = null;
     }
 
-    private bool NeedsNewLayer(IStructureMemberHandler? member, KeyFrameTime frameTime)
-    {
-        var shapeData = (member as IVectorLayerHandler).GetShapeData(frameTime);
-        if (shapeData is null)
-        {
-            return false;
-        }
-
-        return shapeData is not IReadOnlyPathData pathData || pathData.Path.IsClosed;
-    }
-
     private void ApplySettings(PathVectorData pathData)
     {
         toolbar.ToolSize = pathData.StrokeWidth;

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

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

+ 1 - 1
src/PixiEditor/ViewModels/Document/DocumentViewModel.cs

@@ -611,7 +611,7 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
             Surface finalSurface = null;
             DrawingBackendApi.Current.RenderingDispatcher.Invoke(() =>
             {
-                finalSurface = new Surface(renderSize);
+                finalSurface = Surface.ForDisplay(renderSize);
                 finalSurface.DrawingSurface.Canvas.Save();
                 VecD scaling = new VecD(renderSize.X / (double)SizeBindable.X, renderSize.Y / (double)SizeBindable.Y);
 

+ 2 - 0
src/PixiEditor/ViewModels/SettingsWindowViewModel.cs

@@ -270,7 +270,9 @@ internal partial class SettingsWindowViewModel : ViewModelBase
             new("GENERAL"),
             new("DISCORD"),
             new("KEY_BINDINGS"),
+            new SettingsPage("UPDATES"),
             new("EXPORT"),
+            new SettingsPage("SCENE")
         };
 
         ILocalizationProvider.Current.OnLanguageChanged += OnLanguageChanged;

+ 2 - 2
src/PixiEditor/ViewModels/SubViewModels/AnimationsViewModel.cs

@@ -165,9 +165,9 @@ internal class AnimationsViewModel : SubViewModel<ViewModelMain>
     public void DeleteCels()
     {
         var activeDocument = Owner.DocumentManagerSubViewModel.ActiveDocument;
-        var selected = activeDocument.AnimationDataViewModel.AllCels.Where(x => x.IsSelected).ToArray();
+        var selected = activeDocument?.AnimationDataViewModel?.AllCels.Where(x => x is { IsSelected: true }).ToArray();
 
-        if (activeDocument is null || selected.Length == 0)
+        if (activeDocument is null || selected == null || selected.Length == 0)
             return;
 
         List<Guid> celIds = selected.Select(x => x.Id).ToList();

+ 1 - 1
src/PixiEditor/ViewModels/SubViewModels/ToolsViewModel.cs

@@ -344,7 +344,7 @@ internal class ToolsViewModel : SubViewModel<ViewModelMain>, IToolsHandler
         CanExecute = "PixiEditor.Tools.CanChangeToolSize", Key = Key.OemOpenBrackets, AnalyticsTrack = true)]
     public void ChangeToolSize(double increment)
     {
-        if (ActiveTool?.Toolbar is not IToolSizeToolbar toolbar)
+        if (ActiveTool?.Toolbar is not IToolSizeToolbar toolbar || !CanChangeToolSize())
             return;
         double newSize = toolbar.ToolSize + increment;
         if (newSize > 0)

+ 118 - 6
src/PixiEditor/ViewModels/SubViewModels/ViewportWindowViewModel.cs

@@ -1,10 +1,16 @@
 using System.ComponentModel;
 using Avalonia.Threading;
+using Drawie.Backend.Core;
+using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.Surfaces;
+using Drawie.Backend.Core.Surfaces.PaintImpl;
 using PixiDocks.Core.Docking;
 using PixiDocks.Core.Docking.Events;
 using PixiEditor.Helpers.UI;
 using PixiEditor.Models.DocumentModels;
 using Drawie.Numerics;
+using PixiEditor.Extensions.CommonApi.UserPreferences.Settings;
+using PixiEditor.Extensions.CommonApi.UserPreferences.Settings.PixiEditor;
 using PixiEditor.Models.Handlers;
 using PixiEditor.ViewModels.Dock;
 using PixiEditor.ViewModels.Document;
@@ -91,6 +97,50 @@ internal class ViewportWindowViewModel : SubViewModel<WindowViewModel>, IDockabl
         }
     }
 
+    private bool autoScaleBackground = true;
+    public bool AutoScaleBackground
+    {
+        get => autoScaleBackground;
+        set
+        {
+            autoScaleBackground = value;
+            OnPropertyChanged(nameof(AutoScaleBackground));
+        }
+    }
+
+    private double customBackgroundScaleX = 16;
+    public double CustomBackgroundScaleX
+    {
+        get => customBackgroundScaleX;
+        set
+        {
+            customBackgroundScaleX = value;
+            OnPropertyChanged(nameof(CustomBackgroundScaleX));
+        }
+    }
+
+    private double customBackgroundScaleY = 16;
+    public double CustomBackgroundScaleY
+    {
+        get => customBackgroundScaleY;
+        set
+        {
+            customBackgroundScaleY = value;
+            OnPropertyChanged(nameof(CustomBackgroundScaleY));
+        }
+    }
+
+    private Bitmap backgroundBitmap;
+    public Bitmap BackgroundBitmap
+    {
+        get => backgroundBitmap;
+        set
+        {
+            backgroundBitmap = value;
+            OnPropertyChanged(nameof(BackgroundBitmap));
+        }
+    }
+
     private PreviewPainterControl previewPainterControl;
 
     public void IndexChanged()
@@ -106,6 +156,20 @@ internal class ViewportWindowViewModel : SubViewModel<WindowViewModel>, IDockabl
         Document = document;
         Document.SizeChanged += DocumentOnSizeChanged;
         Document.PropertyChanged += DocumentOnPropertyChanged;
+
+        AutoScaleBackground = PixiEditorSettings.Scene.AutoScaleBackground.Value;
+        CustomBackgroundScaleX = PixiEditorSettings.Scene.CustomBackgroundScaleX.Value;
+        CustomBackgroundScaleY = PixiEditorSettings.Scene.CustomBackgroundScaleY.Value;
+        BackgroundBitmap = BitmapFromColors(
+            PixiEditorSettings.Scene.PrimaryBackgroundColor.Value,
+            PixiEditorSettings.Scene.SecondaryBackgroundColor.Value);
+
+        PixiEditorSettings.Scene.AutoScaleBackground.ValueChanged += UpdateAutoScaleBackground;
+        PixiEditorSettings.Scene.CustomBackgroundScaleX.ValueChanged += UpdateCustomBackgroundScaleX;
+        PixiEditorSettings.Scene.CustomBackgroundScaleY.ValueChanged += UpdateCustomBackgroundScaleY;
+        PixiEditorSettings.Scene.PrimaryBackgroundColor.ValueChanged += UpdateBackgroundBitmap;
+        PixiEditorSettings.Scene.SecondaryBackgroundColor.ValueChanged += UpdateBackgroundBitmap;
+
         previewPainterControl = new PreviewPainterControl(Document.PreviewPainter,
             Document.AnimationDataViewModel.ActiveFrameTime.Frame);
         TabCustomizationSettings.Icon = previewPainterControl;
@@ -132,12 +196,6 @@ internal class ViewportWindowViewModel : SubViewModel<WindowViewModel>, IDockabl
         }
     }
 
-    ~ViewportWindowViewModel()
-    {
-        Document.SizeChanged -= DocumentOnSizeChanged;
-        Document.PropertyChanged -= DocumentOnPropertyChanged;
-    }
-
     private void DocumentOnSizeChanged(object? sender, DocumentSizeChangedEventArgs e)
     {
         previewPainterControl.QueueNextFrame();
@@ -154,6 +212,17 @@ internal class ViewportWindowViewModel : SubViewModel<WindowViewModel>, IDockabl
                 {
                     _closeRequested =
                         await Owner.OnViewportWindowCloseButtonPressed(this);
+                    if (_closeRequested)
+                    {
+                        Document.SizeChanged -= DocumentOnSizeChanged;
+                        Document.PropertyChanged -= DocumentOnPropertyChanged;
+
+                        PixiEditorSettings.Scene.AutoScaleBackground.ValueChanged -= UpdateAutoScaleBackground;
+                        PixiEditorSettings.Scene.CustomBackgroundScaleX.ValueChanged -= UpdateCustomBackgroundScaleX;
+                        PixiEditorSettings.Scene.CustomBackgroundScaleY.ValueChanged -= UpdateCustomBackgroundScaleY;
+                        PixiEditorSettings.Scene.PrimaryBackgroundColor.ValueChanged -= UpdateBackgroundBitmap;
+                        PixiEditorSettings.Scene.SecondaryBackgroundColor.ValueChanged -= UpdateBackgroundBitmap;
+                    }
                 });
             });
         }
@@ -161,6 +230,48 @@ internal class ViewportWindowViewModel : SubViewModel<WindowViewModel>, IDockabl
         return _closeRequested;
     }
 
+    private void UpdateAutoScaleBackground(Setting<bool> setting, bool newValue)
+    {
+        AutoScaleBackground = newValue;
+    }
+
+    private void UpdateCustomBackgroundScaleX(Setting<double> setting, double newValue)
+    {
+        CustomBackgroundScaleX = newValue;
+    }
+
+    private void UpdateCustomBackgroundScaleY(Setting<double> setting, double newValue)
+    {
+        CustomBackgroundScaleY = newValue;
+    }
+
+    private void UpdateBackgroundBitmap(Setting<string> setting, string newValue)
+    {
+        BackgroundBitmap?.Dispose();
+        BackgroundBitmap = BitmapFromColors(
+            PixiEditorSettings.Scene.PrimaryBackgroundColor.Value,
+            PixiEditorSettings.Scene.SecondaryBackgroundColor.Value);
+    }
+
+    private static Bitmap BitmapFromColors(string primaryHex, string secondaryHex)
+    {
+        Color primary = Color.FromHex(primaryHex);
+        Color secondary = Color.FromHex(secondaryHex);
+
+        Surface surface = Surface.ForDisplay(new VecI(2, 2));
+        surface.DrawingSurface.Canvas.Clear(primary);
+        using Paint secondaryPaint = new Paint
+        {
+            Color = secondary,
+            Style = PaintStyle.Fill
+        };
+        surface.DrawingSurface.Canvas.DrawRect(1, 0, 1, 1, secondaryPaint);
+        surface.DrawingSurface.Canvas.DrawRect(0, 1, 1, 1, secondaryPaint);
+
+        using var snapshot = surface.DrawingSurface.Snapshot();
+        return Bitmap.FromImage(snapshot);
+    }
+
     private static SavedState GetSaveState(DocumentViewModel document)
     {
         if (document.AllChangesSaved)
@@ -186,4 +297,5 @@ internal class ViewportWindowViewModel : SubViewModel<WindowViewModel>, IDockabl
     {
         Owner.Owner.ShortcutController.ClearContext(GetType());
     }
+
 }

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

@@ -0,0 +1,69 @@
+using System.Windows.Input;
+using Avalonia.Media;
+using CommunityToolkit.Mvvm.Input;
+using Drawie.Numerics;
+using PixiEditor.Extensions.CommonApi.UserPreferences;
+using PixiEditor.Helpers.Extensions;
+
+namespace PixiEditor.ViewModels.UserPreferences.Settings;
+
+internal class SceneSettings : SettingsGroup
+{
+    private bool autoScaleBackground = GetPreference(PreferencesConstants.AutoScaleBackground, PreferencesConstants.AutoScaleBackgroundDefault);
+    public bool AutoScaleBackground
+    {
+        get => autoScaleBackground;
+        set => RaiseAndUpdatePreference(ref autoScaleBackground, value);
+    }
+
+    private double customBackgroundScaleX = GetPreference(PreferencesConstants.CustomBackgroundScaleX, PreferencesConstants.CustomBackgroundScaleDefault);
+    public double CustomBackgroundScaleX
+    {
+        get => customBackgroundScaleX;
+        set => RaiseAndUpdatePreference(ref customBackgroundScaleX, value);
+    }
+
+    private double customBackgroundScaleY = GetPreference(PreferencesConstants.CustomBackgroundScaleY, PreferencesConstants.CustomBackgroundScaleDefault);
+    public double CustomBackgroundScaleY
+    {
+        get => customBackgroundScaleY;
+        set => RaiseAndUpdatePreference(ref customBackgroundScaleY, value);
+    }
+
+    private string _primaryBackgroundColorHex = GetPreference(PreferencesConstants.PrimaryBackgroundColor, PreferencesConstants.PrimaryBackgroundColorDefault);
+    public string PrimaryBackgroundColorHex
+    {
+        get => _primaryBackgroundColorHex;
+        set => RaiseAndUpdatePreference(ref _primaryBackgroundColorHex, value, PreferencesConstants.PrimaryBackgroundColor);
+    }
+
+    private string _secondaryBackgroundColorHex = GetPreference(PreferencesConstants.SecondaryBackgroundColor, PreferencesConstants.SecondaryBackgroundColorDefault);
+    public string SecondaryBackgroundColorHex
+    {
+        get => _secondaryBackgroundColorHex;
+        set => RaiseAndUpdatePreference(ref _secondaryBackgroundColorHex, value, PreferencesConstants.SecondaryBackgroundColor);
+    }
+
+    public Color PrimaryBackgroundColor
+    {
+        get => Color.Parse(PrimaryBackgroundColorHex);
+        set => PrimaryBackgroundColorHex = value.ToColor().ToRgbHex();
+    }
+
+    public Color SecondaryBackgroundColor
+    {
+        get => Color.Parse(SecondaryBackgroundColorHex);
+        set => SecondaryBackgroundColorHex = value.ToColor().ToRgbHex();
+    }
+
+    public ICommand ResetBackgroundCommand { get; }
+
+    public SceneSettings()
+    {
+        ResetBackgroundCommand = new RelayCommand(() =>
+        {
+            PrimaryBackgroundColorHex = PreferencesConstants.PrimaryBackgroundColorDefault;
+            SecondaryBackgroundColorHex = PreferencesConstants.SecondaryBackgroundColorDefault;
+        });
+    }
+}

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

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

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

@@ -40,6 +40,10 @@
         SnappingEnabled="{Binding ViewportSubViewModel.SnappingEnabled, Source={viewModels1:MainVM}, Mode=TwoWay}"
         AvailableRenderOutputs="{Binding ActiveDocument.NodeGraph.AvailableRenderOutputs, Source={viewModels1:MainVM DocumentManagerSVM}}"
         ViewportRenderOutput="{Binding RenderOutputName, Mode=TwoWay}"
+        AutoBackgroundScale="{Binding AutoScaleBackground, Mode=OneWay}"
+        CustomBackgroundScaleX="{Binding CustomBackgroundScaleX, Mode=OneWay}"
+        CustomBackgroundScaleY="{Binding CustomBackgroundScaleY, Mode=OneWay}"
+        BackgroundBitmap="{Binding BackgroundBitmap, Mode=OneWay}"
         HudVisible="{Binding HudVisible}"
         Document="{Binding Document}">
     </viewportControls:Viewport>

+ 5 - 5
src/PixiEditor/Views/Input/SizeInput.axaml.cs

@@ -13,11 +13,11 @@ internal partial class SizeInput : UserControl
     public static readonly StyledProperty<double> SizeProperty =
         AvaloniaProperty.Register<SizeInput, double>(nameof(Size), defaultValue: 1);
 
-    public static readonly StyledProperty<int> MinSizeProperty = AvaloniaProperty.Register<SizeInput, int>(
+    public static readonly StyledProperty<double> MinSizeProperty = AvaloniaProperty.Register<SizeInput, double>(
         nameof(MinSize), defaultValue: 1);
 
-    public static readonly StyledProperty<int> MaxSizeProperty =
-        AvaloniaProperty.Register<SizeInput, int>(nameof(MaxSize), defaultValue: int.MaxValue);
+    public static readonly StyledProperty<double> MaxSizeProperty =
+        AvaloniaProperty.Register<SizeInput, double>(nameof(MaxSize), defaultValue: double.MaxValue);
 
     public static readonly StyledProperty<bool> BehaveLikeSmallEmbeddedFieldProperty =
         AvaloniaProperty.Register<SizeInput, bool>(nameof(BehaveLikeSmallEmbeddedField), defaultValue: true);
@@ -58,13 +58,13 @@ internal partial class SizeInput : UserControl
         set => SetValue(SizeProperty, value);
     }
 
-    public int MinSize
+    public double MinSize
     {
         get => GetValue(MinSizeProperty);
         set => SetValue(MinSizeProperty, value);
     }
 
-    public int MaxSize
+    public double MaxSize
     {
         get => (int)GetValue(MaxSizeProperty);
         set => SetValue(MaxSizeProperty, value);

+ 1 - 0
src/PixiEditor/Views/Layers/LayerControl.axaml

@@ -42,6 +42,7 @@
                 <RowDefinition Height="10" />
                 <RowDefinition Height="26" />
             </Grid.RowDefinitions>
+            <Panel Grid.ColumnSpan="2" Grid.RowSpan="2" Name="BackgroundGrid" Background="Transparent" DragDrop.AllowDrop="True"/>
             <Grid DragDrop.AllowDrop="True"
                   Name="TopGrid"
                   Grid.Row="0" Grid.ColumnSpan="3" Background="Transparent" />

+ 7 - 1
src/PixiEditor/Views/Layers/LayerControl.axaml.cs

@@ -89,6 +89,7 @@ internal partial class LayerControl : UserControl
         TopGrid.AddHandler(DragDrop.DragEnterEvent, Grid_DragEnter);
         TopGrid.AddHandler(DragDrop.DragLeaveEvent, Grid_DragLeave);
         TopGrid.AddHandler(DragDrop.DropEvent, Grid_Drop_Top);
+        BackgroundGrid.AddHandler(DragDrop.DropEvent, BackgroundGrid_Drop);
         dropBelowGrid.AddHandler(DragDrop.DragEnterEvent, Grid_DragEnter);
         dropBelowGrid.AddHandler(DragDrop.DragLeaveEvent, Grid_DragLeave);
         dropBelowGrid.AddHandler(DragDrop.DropEvent, Grid_Drop_Below);
@@ -136,6 +137,11 @@ internal partial class LayerControl : UserControl
             RemoveDragEffect(item);
     }
 
+    private void BackgroundGrid_Drop(object sender, DragEventArgs e)
+    {
+        e.Handled = true;
+    }
+
     public static Guid[]? ExtractMemberGuids(IDataObject droppedMemberDataObject)
     {
         object droppedLayer = droppedMemberDataObject.Get(LayersManager.LayersDataName);
@@ -163,7 +169,7 @@ internal partial class LayerControl : UserControl
         if (Layer is null)
             return false;
 
-        if(placement is StructureMemberPlacement.Below or StructureMemberPlacement.BelowOutsideFolder)
+        if (placement is StructureMemberPlacement.Below or StructureMemberPlacement.BelowOutsideFolder)
         {
             droppedMemberGuids = droppedMemberGuids.Reverse().ToArray();
         }

+ 6 - 0
src/PixiEditor/Views/Layers/LayersManager.axaml

@@ -35,6 +35,7 @@
                     Height="24" Width="24" Cursor="Hand" uiExt:Translator.TooltipKey="NEW_LAYER"
                     HorizontalAlignment="Stretch" Margin="0,0,5,0"
                     Classes="pixi-icon"
+                    Focusable="False"
                     Content="{DynamicResource icon-file-plus}"
                     FlowDirection="LeftToRight"/>
                 <Button 
@@ -43,6 +44,7 @@
                     DockPanel.Dock="Left"
                     HorizontalAlignment="Stretch"  Margin="0,0,5,0"
                     Classes="pixi-icon"
+                    Focusable="False"
                     Content="{DynamicResource icon-folder-plus}"
                     FlowDirection="LeftToRight"/>
                 <Button 
@@ -51,6 +53,7 @@
                     HorizontalAlignment="Stretch" Margin="0,0,5,0"
                     DockPanel.Dock="Left"
                     Classes="pixi-icon"
+                    Focusable="False"
                     Content="{DynamicResource icon-trash}"
                     FlowDirection="LeftToRight"/>
                 <Button 
@@ -58,6 +61,7 @@
                     DockPanel.Dock="Right"
                     HorizontalAlignment="Stretch" Margin="5,0,0,0"
                     Classes="pixi-icon"
+                    Focusable="False"
                     Content="{DynamicResource icon-merge}"
                     FlowDirection="LeftToRight"/>
                 <Button 
@@ -65,6 +69,7 @@
                     DockPanel.Dock="Right"
                     HorizontalAlignment="Stretch" Margin="5,0,0,0"
                     Classes="pixi-icon"
+                    Focusable="False"
                     Content="{DynamicResource icon-create-mask}"
                     Command="{xaml:Command PixiEditor.Layer.CreateMask}"
                     FlowDirection="LeftToRight"/>
@@ -73,6 +78,7 @@
                     DockPanel.Dock="Right"
                     HorizontalAlignment="Stretch" Margin="5,0,0,0"
                     Classes="pixi-icon"
+                    Focusable="False"
                     Content="{DynamicResource icon-alpha-lock}"
                     Command="{xaml:Command PixiEditor.Layer.ToggleLockTransparency}"
                     FlowDirection="LeftToRight"/>

+ 1 - 0
src/PixiEditor/Views/Main/Tools/ToolPickerButton.axaml

@@ -16,6 +16,7 @@
     </Design.DataContext>
     <Button Command="{xaml:Command PixiEditor.Tools.SelectTool, UseProvided=true}"
             CommandParameter="{Binding}"
+            Focusable="False"
             Width="44" Height="34"
             ui:Translator.TooltipLocalizedString="{Binding Tooltip}"
             Background="{DynamicResource ThemeBackgroundBrush1}">

+ 12 - 7
src/PixiEditor/Views/Main/ViewportControls/Viewport.axaml

@@ -43,9 +43,10 @@
                                         PassEventArgsToCommand="True"/>
             </EventTriggerBehavior>-->
         </Interaction.Behaviors>
-        <overlays:TogglableFlyout IsVisible="{Binding HudVisible, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}}" 
-                                  Margin="5" Icon="{DynamicResource icon-tool}"
-                                  ZIndex="2" HorizontalAlignment="Right" VerticalAlignment="Top">
+        <overlays:TogglableFlyout
+            IsVisible="{Binding HudVisible, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}}"
+            Margin="5" Icon="{DynamicResource icon-tool}"
+            ZIndex="2" HorizontalAlignment="Right" VerticalAlignment="Top">
             <overlays:TogglableFlyout.Child>
                 <Border Padding="5"
                         CornerRadius="{DynamicResource ControlCornerRadius}"
@@ -112,7 +113,7 @@
                                           IsChecked="{Binding FlipX, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}, Mode=TwoWay}"
                                           Content="{DynamicResource icon-y-flip}"
                                           Cursor="Hand" />
-                            <ToggleButton  Width="32" Height="32"
+                            <ToggleButton Width="32" Height="32"
                                           ui:Translator.TooltipKey="FLIP_VIEWPORT_VERTICALLY"
                                           Classes="OverlayToggleButton pixi-icon"
                                           IsChecked="{Binding FlipY, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}, Mode=TwoWay}"
@@ -135,7 +136,7 @@
                         </StackPanel>
                         <Separator />
                         <TextBlock HorizontalAlignment="Center"
-                                   ui:Translator.Key="GRIDLINES_SIZE" Margin="0 0 0 10"/>
+                                   ui:Translator.Key="GRIDLINES_SIZE" Margin="0 0 0 10" />
                         <input:NumberInput Min="1"
                                            Max="1024"
                                            Value="{Binding GridLinesXSize, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}, Mode=TwoWay}" />
@@ -160,7 +161,8 @@
                 </Border>
             </overlays:TogglableFlyout.Child>
         </overlays:TogglableFlyout>
-        <Grid IsVisible="{Binding HudVisible, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}}"
+        <Grid
+            IsVisible="{Binding HudVisible, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}}"
             ZIndex="100" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="10">
             <Grid.RowDefinitions>
                 <RowDefinition MinHeight="40" MaxHeight="120" />
@@ -206,7 +208,10 @@
             FadeOut="{Binding Source={viewModels:ToolVM ColorPickerToolViewModel}, Path=PickOnlyFromReferenceLayer, Mode=OneWay}"
             DefaultCursor="{Binding Source={viewModels:MainVM}, Path=ToolsSubViewModel.ToolCursor, Mode=OneWay}"
             RenderOutput="{Binding ViewportRenderOutput, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}, Mode=OneWay}"
-            CheckerImagePath="/Images/CheckerTile.png"
+            AutoBackgroundScale="{Binding AutoBackgroundScale, 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}"
+            BackgroundBitmap="{Binding BackgroundBitmap, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}, Mode=OneWay}"
             PointerPressed="Scene_OnContextMenuOpening"
             ui:RenderOptionsBindable.BitmapInterpolationMode="{Binding Scale, Converter={converters:ScaleToBitmapScalingModeConverter}, RelativeSource={RelativeSource Self}}">
             <rendering:Scene.ContextFlyout>

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

@@ -13,6 +13,7 @@ using PixiEditor.ViewModels;
 using PixiEditor.Views.Visuals;
 using Drawie.Backend.Core;
 using Drawie.Backend.Core.Numerics;
+using Drawie.Backend.Core.Surfaces;
 using PixiEditor.Helpers.Behaviours;
 using PixiEditor.Helpers.UI;
 using PixiEditor.Models.Controllers.InputDevice;
@@ -114,6 +115,41 @@ internal partial class Viewport : UserControl, INotifyPropertyChanged
     public static readonly StyledProperty<bool> HighResPreviewProperty =
         AvaloniaProperty.Register<Viewport, bool>(nameof(HighResPreview), true);
 
+    public static readonly StyledProperty<bool> AutoBackgroundScaleProperty = AvaloniaProperty.Register<Viewport, bool>(
+        nameof(AutoBackgroundScale), true);
+
+    public static readonly StyledProperty<double> CustomBackgroundScaleXProperty = AvaloniaProperty.Register<Viewport, double>(
+        nameof(CustomBackgroundScaleX));
+
+    public static readonly StyledProperty<double> CustomBackgroundScaleYProperty = AvaloniaProperty.Register<Viewport, double>(
+        nameof(CustomBackgroundScaleY));
+
+    public static readonly StyledProperty<Bitmap> BackgroundBitmapProperty = AvaloniaProperty.Register<Viewport, Bitmap>(
+        nameof(BackgroundBitmap));
+
+    public Bitmap BackgroundBitmap
+    {
+        get => GetValue(BackgroundBitmapProperty);
+        set => SetValue(BackgroundBitmapProperty, value);
+    }
+
+    public double CustomBackgroundScaleY
+    {
+        get => GetValue(CustomBackgroundScaleYProperty);
+        set => SetValue(CustomBackgroundScaleYProperty, value);
+    }
+    public double CustomBackgroundScaleX
+    {
+        get => GetValue(CustomBackgroundScaleXProperty);
+        set => SetValue(CustomBackgroundScaleXProperty, value);
+    }
+
+    public bool AutoBackgroundScale
+    {
+        get => GetValue(AutoBackgroundScaleProperty);
+        set => SetValue(AutoBackgroundScaleProperty, value);
+    }
+
     public SnappingViewModel SnappingViewModel
     {
         get => GetValue(SnappingViewModelProperty);

+ 1 - 1
src/PixiEditor/Views/Overlays/Handles/AnchorHandle.cs

@@ -14,7 +14,7 @@ public class AnchorHandle : RectangleHandle
     private Paint selectedPaint;
     
     public bool IsSelected { get; set; } = false;
-    public override VecD HitSizeMargin { get; set; } = new VecD(5, 5);
+    public override VecD HitSizeMargin { get; set; } = new VecD(10, 10);
 
     public AnchorHandle(Overlay owner) : base(owner)
     {

+ 2 - 0
src/PixiEditor/Views/Overlays/Handles/ControlPointHandle.cs

@@ -9,6 +9,8 @@ public class ControlPointHandle : Handle
 {
     public Handle ConnectedTo { get; set; }
 
+    public override VecD HitSizeMargin { get; set; } = new VecD(10);
+
     public ControlPointHandle(IOverlay owner) : base(owner)
     {
         Size = new VecD(GetResource<double>("AnchorHandleSize"));

+ 19 - 3
src/PixiEditor/Views/Overlays/PathOverlay/EditableVectorPath.cs

@@ -65,7 +65,7 @@ public class EditableVectorPath
         {
             newPath = new VectorPath();
         }
-        
+
         newPath.FillType = FillType;
 
         foreach (var subShape in subShapes)
@@ -307,7 +307,7 @@ public class EditableVectorPath
         return closest;
     }
 
-    public void AddPointAt(VecD point)
+    public int? AddPointAt(VecD point)
     {
         SubShape targetSubShape = null;
         Verb verb = null;
@@ -321,9 +321,25 @@ public class EditableVectorPath
             }
         }
 
-        targetSubShape?.InsertPointAt((VecF)point, verb);
+        if (targetSubShape != null)
+        {
+            int localIndex = targetSubShape.InsertPointAt((VecF)point, verb);
+            int globalIndex = GetGlobalIndex(targetSubShape, localIndex);
+            return globalIndex;
+        }
+
+        return null;
     }
 
+    /*
+    public void NewSubShape(VecD point)
+    {
+        VecF pointF = (VecF)point;
+        ShapePoint newPoint = new ShapePoint(pointF, 0, new Verb(PathVerb.Move, pointF, pointF, null, null, 0));
+        var newSubShape = new SubShape(new List<ShapePoint>() { newPoint }, false);
+        subShapes.Add(newSubShape);
+    }*/
+
     public void RemoveSubShape(SubShape subShapeContainingIndex)
     {
         if (subShapes.Contains(subShapeContainingIndex))

+ 6 - 4
src/PixiEditor/Views/Overlays/PathOverlay/SubShape.cs

@@ -52,7 +52,7 @@ public class SubShape
             {
                 previousPoint.Verb.To = nextPoint.Position;
             }
-            
+
             for (int j = i + 1; j < points.Count; j++)
             {
                 points[j].Index--;
@@ -70,8 +70,8 @@ public class SubShape
         }
 
         points.RemoveAt(i);
-        
-        if(points.Count < 3)
+
+        if (points.Count < 3)
         {
             IsClosed = false;
         }
@@ -128,7 +128,7 @@ public class SubShape
         }
     }
 
-    public void InsertPointAt(VecF point, Verb pointVerb)
+    public int InsertPointAt(VecF point, Verb pointVerb)
     {
         int indexOfVerb = this.points.FirstOrDefault(x => x.Verb == pointVerb)?.Index ?? -1;
         if (indexOfVerb == -1)
@@ -187,6 +187,8 @@ public class SubShape
         {
             this.points[i].Index++;
         }
+
+        return indexOfVerb + 1;
     }
 
     public VecD? GetClosestPointOnPath(VecD point, float maxDistanceInPixels)

+ 12 - 4
src/PixiEditor/Views/Overlays/PathOverlay/VectorPathOverlay.cs

@@ -515,7 +515,7 @@ public class VectorPathOverlay : Overlay
             return;
         }
 
-        if (args.Modifiers == KeyModifiers.Shift && IsOverPath(args.Point, out VecD closestPoint))
+        if (IsOverPath(args.Point, out VecD closestPoint))
         {
             AddPointAt(closestPoint);
             AddToUndoCommand.Execute(Path);
@@ -530,7 +530,7 @@ public class VectorPathOverlay : Overlay
 
     protected override void OnOverlayPointerMoved(OverlayPointerArgs args)
     {
-        if (args.Modifiers == KeyModifiers.Shift && IsOverPath(args.Point, out VecD closestPoint))
+        if (IsOverPath(args.Point, out VecD closestPoint))
         {
             insertPreviewHandle.Position = closestPoint;
             canInsert = true;
@@ -554,7 +554,13 @@ public class VectorPathOverlay : Overlay
 
         if (subShape.IsClosed)
         {
-            return false;
+            var path = editableVectorPath.ToVectorPath();
+            VectorPath newShape = new VectorPath();
+            newShape.MoveTo((VecF)point);
+            path.AddPath(newShape, AddPathMode.Append);
+            Path = path;
+            SelectAnchor(anchorHandles.Last());
+            return true;
         }
 
         if (Path.IsEmpty)
@@ -575,8 +581,9 @@ public class VectorPathOverlay : Overlay
 
     private void AddPointAt(VecD point)
     {
-        editableVectorPath.AddPointAt(point);
+        int? insertedAt = editableVectorPath.AddPointAt(point);
         Path = editableVectorPath.ToVectorPath();
+        SelectAnchor(insertedAt is > 0 && insertedAt.Value < anchorHandles.Count ? anchorHandles[insertedAt.Value] : anchorHandles.Last());
     }
 
     private bool IsOverPath(VecD point, out VecD closestPoint)
@@ -593,6 +600,7 @@ public class VectorPathOverlay : Overlay
         {
             SnappingController.RemoveAll($"editingPath[{anchorHandles.IndexOf(anchorHandle)}]");
             CaptureHandle(source);
+            canInsert = false;
             args.Handled = true;
         }
     }

+ 0 - 1
src/PixiEditor/Views/Palettes/PaletteViewer.axaml

@@ -122,7 +122,6 @@
                                                           DragDrop.AllowDrop="True" Color="{Binding}"
                                                           Height="24" Width="24" CornerRadius="0"
                                                           DropCommand="{Binding ElementName=paletteControl, Path=DropColorCommand}">
-                                >
                                 <Interaction.Behaviors>
                                     <EventTriggerBehavior EventName="PointerReleased">
                                         <InvokeCommandAction

+ 49 - 30
src/PixiEditor/Views/Rendering/Scene.cs

@@ -51,9 +51,6 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
         AvaloniaProperty.Register<Scene, ObservableCollection<Overlay>>(
             nameof(AllOverlays));
 
-    public static readonly StyledProperty<string> CheckerImagePathProperty = AvaloniaProperty.Register<Scene, string>(
-        nameof(CheckerImagePath));
-
     public static readonly StyledProperty<Cursor> DefaultCursorProperty = AvaloniaProperty.Register<Scene, Cursor>(
         nameof(DefaultCursor));
 
@@ -65,6 +62,42 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
         AvaloniaProperty.Register<Scene, SceneRenderer>(
             nameof(SceneRenderer));
 
+    public static readonly StyledProperty<bool> AutoBackgroundScaleProperty = AvaloniaProperty.Register<Scene, bool>(
+        nameof(AutoBackgroundScale), true);
+
+    public bool AutoBackgroundScale
+    {
+        get => GetValue(AutoBackgroundScaleProperty);
+        set => SetValue(AutoBackgroundScaleProperty, value);
+    }
+
+    public static readonly StyledProperty<double> CustomBackgroundScaleXProperty = AvaloniaProperty.Register<Scene, double>(
+        nameof(CustomBackgroundScaleX));
+
+    public double CustomBackgroundScaleX
+    {
+        get => GetValue(CustomBackgroundScaleXProperty);
+        set => SetValue(CustomBackgroundScaleXProperty, value);
+    }
+
+    public static readonly StyledProperty<double> CustomBackgroundScaleYProperty = AvaloniaProperty.Register<Scene, double>(
+        nameof(CustomBackgroundScaleY));
+
+    public double CustomBackgroundScaleY
+    {
+        get => GetValue(CustomBackgroundScaleYProperty);
+        set => SetValue(CustomBackgroundScaleYProperty, value);
+    }
+
+    public static readonly StyledProperty<Bitmap> BackgroundBitmapProperty = AvaloniaProperty.Register<Scene, Bitmap>(
+        nameof(BackgroundBitmap));
+
+    public Bitmap BackgroundBitmap
+    {
+        get => GetValue(BackgroundBitmapProperty);
+        set => SetValue(BackgroundBitmapProperty, value);
+    }
+
     public SceneRenderer SceneRenderer
     {
         get => GetValue(SceneRendererProperty);
@@ -77,12 +110,6 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
         set => SetValue(DefaultCursorProperty, value);
     }
 
-    public string CheckerImagePath
-    {
-        get => GetValue(CheckerImagePathProperty);
-        set => SetValue(CheckerImagePathProperty, value);
-    }
-
     public ObservableCollection<Overlay> AllOverlays
     {
         get => GetValue(AllOverlaysProperty);
@@ -113,9 +140,6 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
         set { SetValue(RenderOutputProperty, value); }
     }
 
-
-    private Bitmap? checkerBitmap;
-
     private Overlay? capturedOverlay;
 
     private List<Overlay> mouseOverOverlays = new();
@@ -150,16 +174,20 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
     {
         AffectsRender<Scene>(BoundsProperty, WidthProperty, HeightProperty, ScaleProperty, AngleRadiansProperty,
             FlipXProperty,
-            FlipYProperty, DocumentProperty, AllOverlaysProperty, ContentDimensionsProperty);
+            FlipYProperty, DocumentProperty, AllOverlaysProperty, ContentDimensionsProperty,
+            AutoBackgroundScaleProperty, CustomBackgroundScaleXProperty, CustomBackgroundScaleYProperty);
 
         FadeOutProperty.Changed.AddClassHandler<Scene>(FadeOutChanged);
-        CheckerImagePathProperty.Changed.AddClassHandler<Scene>(CheckerImagePathChanged);
         AllOverlaysProperty.Changed.AddClassHandler<Scene>(ActiveOverlaysChanged);
         DefaultCursorProperty.Changed.AddClassHandler<Scene>(DefaultCursorChanged);
         ChannelsProperty.Changed.AddClassHandler<Scene>(Refresh);
         DocumentProperty.Changed.AddClassHandler<Scene>(DocumentChanged);
         FlipXProperty.Changed.AddClassHandler<Scene>(Refresh);
         FlipYProperty.Changed.AddClassHandler<Scene>(Refresh);
+        AutoBackgroundScaleProperty.Changed.AddClassHandler<Scene>(Refresh);
+        CustomBackgroundScaleXProperty.Changed.AddClassHandler<Scene>(Refresh);
+        CustomBackgroundScaleYProperty.Changed.AddClassHandler<Scene>(Refresh);
+        BackgroundBitmapProperty.Changed.AddClassHandler<Scene>(Refresh);
     }
 
     private static void Refresh(Scene scene, AvaloniaPropertyChangedEventArgs args)
@@ -276,18 +304,21 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
 
     private void DrawCheckerboard(DrawingSurface surface, RectD dirtyBounds)
     {
-        if (checkerBitmap == null) return;
+        if (BackgroundBitmap == null) return;
 
         RectD operationSurfaceRectToRender = new RectD(0, 0, dirtyBounds.Width, dirtyBounds.Height);
-        float checkerScale = (float)ZoomToViewportConverter.ZoomToViewport(16, Scale) * 0.5f;
+        VecD checkerScale = AutoBackgroundScale
+            ? new VecD(ZoomToViewportConverter.ZoomToViewport(16, Scale) * 0.5f)
+            : new VecD(CustomBackgroundScaleX, CustomBackgroundScaleY);
+        checkerScale = new VecD(Math.Max(0.5, checkerScale.X), Math.Max(0.5, checkerScale.Y));
         checkerPaint?.Shader?.Dispose();
         checkerPaint?.Dispose();
         checkerPaint = new Paint
         {
             Shader = Shader.CreateBitmap(
-                checkerBitmap,
+                BackgroundBitmap,
                 TileMode.Repeat, TileMode.Repeat,
-                Matrix3X3.CreateScale(checkerScale, checkerScale)),
+                Matrix3X3.CreateScale((float)checkerScale.X, (float)checkerScale.Y)),
             FilterQuality = FilterQuality.None
         };
 
@@ -763,18 +794,6 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
         }
     }
 
-    private static void CheckerImagePathChanged(Scene scene, AvaloniaPropertyChangedEventArgs e)
-    {
-        if (e.NewValue is string path)
-        {
-            scene.checkerBitmap = ImagePathToBitmapConverter.LoadDrawingApiBitmapFromRelativePath(path);
-        }
-        else
-        {
-            scene.checkerBitmap = null;
-        }
-    }
-
     private static void DocumentChanged(Scene scene, AvaloniaPropertyChangedEventArgs e)
     {
         if (e.OldValue is DocumentViewModel oldDocumentViewModel)

+ 92 - 30
src/PixiEditor/Views/Windows/Settings/SettingsWindow.axaml

@@ -17,6 +17,7 @@
     xmlns:dialogs="clr-namespace:PixiEditor.Views.Dialogs"
     xmlns:settings="clr-namespace:PixiEditor.Views.Windows.Settings"
     xmlns:localization="clr-namespace:PixiEditor.Extensions.Common.Localization;assembly=PixiEditor.Extensions"
+    xmlns:colorPicker="clr-namespace:ColorPicker;assembly=ColorPicker.AvaloniaUI"
     mc:Ignorable="d"
     x:Class="PixiEditor.Views.Windows.Settings.SettingsWindow"
     Name="window"
@@ -192,36 +193,6 @@
                                       IsChecked="{Binding SettingsSubViewModel.Tools.EnableSharedToolbar}"
                                       ui:Translator.Key="ENABLE_SHARED_TOOLBAR" />
 
-                            <TextBlock ui:Translator.Key="AUTOMATIC_UPDATES" Classes="h5" />
-
-                            <CheckBox
-                                VerticalAlignment="Center"
-                                IsEnabled="{Binding Path=ShowUpdateTab}"
-                                IsChecked="{Binding SettingsSubViewModel.Update.CheckUpdatesOnStartup}"
-                                ui:Translator.Key="CHECK_FOR_UPDATES"
-                                Classes="leftOffset" />
-
-                            <StackPanel Orientation="Horizontal" Classes="leftOffset">
-                                <Label Target="updateStreamComboBox" ui:Translator.Key="UPDATE_STREAM"
-                                       VerticalAlignment="Center" />
-                                <StackPanel Orientation="Horizontal" VerticalAlignment="Center"
-                                            HorizontalAlignment="Left">
-                                    <ComboBox Width="110"
-                                              Name="updateStreamComboBox"
-                                              VerticalAlignment="Center"
-                                              IsEnabled="{Binding Path=ShowUpdateTab}"
-                                              ItemsSource="{Binding SettingsSubViewModel.Update.UpdateChannels}"
-                                              SelectedValue="{Binding SettingsSubViewModel.Update.UpdateChannelName}" />
-                                    <Image Cursor="Help"
-                                           Source="/Images/Commands/PixiEditor/Links/OpenDocumentation.png"
-                                           VerticalAlignment="Center"
-                                           ToolTip.ShowDelay="0"
-                                           IsVisible="{Binding !ShowUpdateTab}"
-                                           ui:Translator.TooltipKey="UPDATE_CHANNEL_HELP_TOOLTIP" />
-                                    <!-- ToolTipService.InitialShowDelay="0"-->
-                                </StackPanel>
-                            </StackPanel>
-
                             <TextBlock ui:Translator.Key="DEBUG" Classes="h5" />
                             <CheckBox Classes="leftOffset"
                                       IsChecked="{Binding SettingsSubViewModel.General.IsDebugModeEnabled}"
@@ -329,6 +300,49 @@
                             </Binding>
                         </ScrollViewer.IsVisible>
                         <!--Background="{StaticResource AccentColor}"-->
+                        <controls:FixedSizeStackPanel Orientation="Vertical" ChildSize="32"
+                                                      VerticalChildrenAlignment="Center" Margin="12">
+                            <TextBlock ui:Translator.Key="AUTOMATIC_UPDATES" Classes="h5" />
+
+                            <CheckBox
+                                VerticalAlignment="Center"
+                                IsEnabled="{Binding Path=ShowUpdateTab}"
+                                IsChecked="{Binding SettingsSubViewModel.Update.CheckUpdatesOnStartup}"
+                                ui:Translator.Key="CHECK_FOR_UPDATES"
+                                Classes="leftOffset" />
+
+                            <StackPanel Orientation="Horizontal" Classes="leftOffset">
+                                <Label Target="updateStreamComboBox" ui:Translator.Key="UPDATE_STREAM"
+                                       VerticalAlignment="Center" />
+                                <StackPanel Orientation="Horizontal" VerticalAlignment="Center"
+                                            HorizontalAlignment="Left">
+                                    <ComboBox Width="110"
+                                              Name="updateStreamComboBox"
+                                              VerticalAlignment="Center"
+                                              IsEnabled="{Binding Path=ShowUpdateTab}"
+                                              ItemsSource="{Binding SettingsSubViewModel.Update.UpdateChannels}"
+                                              SelectedValue="{Binding SettingsSubViewModel.Update.UpdateChannelName}" />
+                                    <Image Cursor="Help"
+                                           Source="/Images/Commands/PixiEditor/Links/OpenDocumentation.png"
+                                           VerticalAlignment="Center"
+                                           ToolTip.ShowDelay="0"
+                                           IsVisible="{Binding !ShowUpdateTab}"
+                                           ui:Translator.TooltipKey="UPDATE_CHANNEL_HELP_TOOLTIP" />
+                                    <!-- ToolTipService.InitialShowDelay="0"-->
+                                </StackPanel>
+                            </StackPanel>
+                        </controls:FixedSizeStackPanel>
+                    </ScrollViewer>
+
+                    <ScrollViewer>
+                        <ScrollViewer.IsVisible>
+                            <Binding Path="CurrentPage" Converter="{converters:IsEqualConverter}">
+                                <Binding.ConverterParameter>
+                                    <sys:Int32>4</sys:Int32>
+                                </Binding.ConverterParameter>
+                            </Binding>
+                        </ScrollViewer.IsVisible>
+                        <!--Background="{StaticResource AccentColor}"-->
                         <controls:FixedSizeStackPanel Orientation="Vertical" ChildSize="32"
                                                       VerticalChildrenAlignment="Center" Margin="12">
 
@@ -337,6 +351,54 @@
                                       IsChecked="{Binding SettingsSubViewModel.File.OpenDirectoryOnExport}" />
                         </controls:FixedSizeStackPanel>
                     </ScrollViewer>
+                    <ScrollViewer>
+                        <ScrollViewer.IsVisible>
+                            <Binding Path="CurrentPage" Converter="{converters:IsEqualConverter}">
+                                <Binding.ConverterParameter>
+                                    <sys:Int32>5</sys:Int32>
+                                </Binding.ConverterParameter>
+                            </Binding>
+                        </ScrollViewer.IsVisible>
+                        <!--Background="{StaticResource AccentColor}"-->
+                        <controls:FixedSizeStackPanel Orientation="Vertical" ChildSize="32"
+                                                      VerticalChildrenAlignment="Center" Margin="12">
+
+                            <TextBlock ui:Translator.Key="BACKGROUND" Classes="h4" />
+
+                            <CheckBox Classes="leftOffset" Width="200" HorizontalAlignment="Left"
+                                      ui:Translator.Key="AUTO_SCALE_BACKGROUND"
+                                      IsChecked="{Binding SettingsSubViewModel.Scene.AutoScaleBackground}" />
+
+                            <TextBlock ui:Translator.Key="CUSTOM_BACKGROUND_SCALE" Classes="h5" />
+                            <StackPanel Spacing="5" Orientation="Horizontal" Classes="leftOffset">
+                                <Label Content="X" />
+                                <input:SizeInput MinSize="0.5" Decimals="1"
+                                                 Size="{Binding SettingsSubViewModel.Scene.CustomBackgroundScaleX, Mode=TwoWay}"
+                                                 HorizontalAlignment="Left" />
+                                <Label Content="Y" />
+                                <input:SizeInput MinSize="0.5" Decimals="1"
+                                                 Size="{Binding SettingsSubViewModel.Scene.CustomBackgroundScaleY, Mode=TwoWay}"
+                                                 HorizontalAlignment="Left" />
+                            </StackPanel>
+
+                            <StackPanel Orientation="Horizontal" Spacing="5">
+                                <TextBlock ui:Translator.Key="PRIMARY_BG_COLOR" />
+                                <colorPicker:PortableColorPicker Width="40" Height="20" EnableGradientsTab="False"
+                                                                 SelectedColor="{Binding SettingsSubViewModel.Scene.PrimaryBackgroundColor, Mode=TwoWay}" />
+                            </StackPanel>
+                            <StackPanel Orientation="Horizontal" Spacing="5">
+                                <TextBlock ui:Translator.Key="SECONDARY_BG_COLOR" />
+                                <colorPicker:PortableColorPicker Width="40" Height="20" EnableGradientsTab="False"
+                                                                 SelectedColor="{Binding SettingsSubViewModel.Scene.SecondaryBackgroundColor, Mode=TwoWay}" />
+                            </StackPanel>
+
+                            <Button
+                                Command="{Binding SettingsSubViewModel.Scene.ResetBackgroundCommand}"
+                                d:Content="Reset"
+                                Background="{DynamicResource ThemeAccentBrush}"
+                                ui:Translator.Key="RESET" />
+                        </controls:FixedSizeStackPanel>
+                    </ScrollViewer>
                 </Grid>
             </Border>
         </DockPanel>

+ 1 - 1
tests/Directory.Build.props

@@ -1,7 +1,7 @@
 <Project>
     <PropertyGroup>
         <CodeAnalysisRuleSet>../Custom.ruleset</CodeAnalysisRuleSet>
-		<AvaloniaVersion>11.2.6</AvaloniaVersion>
+		<AvaloniaVersion>11.2.7</AvaloniaVersion>
     </PropertyGroup>
     <ItemGroup>
         <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />