Browse Source

Symmetry full implementation

Equbuxu 3 years ago
parent
commit
c1cbaecdf7
29 changed files with 335 additions and 158 deletions
  1. 79 7
      src/ChunkyImageLib/ChunkyImage.cs
  2. 15 0
      src/ChunkyImageLib/DataHolders/ShapeCorners.cs
  3. 7 0
      src/ChunkyImageLib/DataHolders/ShapeData.cs
  4. 16 1
      src/ChunkyImageLib/DataHolders/Vector2d.cs
  5. 16 0
      src/ChunkyImageLib/DataHolders/Vector2i.cs
  6. 11 0
      src/ChunkyImageLib/Operations/ClearRegionOperation.cs
  7. 1 0
      src/ChunkyImageLib/Operations/IDrawOperation.cs
  8. 15 1
      src/ChunkyImageLib/Operations/ImageOperation.cs
  9. 19 8
      src/ChunkyImageLib/Operations/RectangleOperation.cs
  10. 4 4
      src/PixiEditor.ChangeableDocument/Actions/Root/SetSymmetryAxisState_Action.cs
  11. 2 2
      src/PixiEditor.ChangeableDocument/Actions/Root/SymmetryPosition/EndSetSymmetryAxisPosition_Action.cs
  12. 5 5
      src/PixiEditor.ChangeableDocument/Actions/Root/SymmetryPosition/SetSymmetryAxisPosition_Action.cs
  13. 7 0
      src/PixiEditor.ChangeableDocument/ChangeInfos/Root/SymmetryAxisPosition_ChangeInfo.cs
  14. 7 0
      src/PixiEditor.ChangeableDocument/ChangeInfos/Root/SymmetryAxisState_ChangeInfo.cs
  15. 0 7
      src/PixiEditor.ChangeableDocument/ChangeInfos/Root/SymmetryPosition_ChangeInfo.cs
  16. 0 7
      src/PixiEditor.ChangeableDocument/ChangeInfos/Root/SymmetryState_ChangeInfo.cs
  17. 4 4
      src/PixiEditor.ChangeableDocument/Changeables/Document.cs
  18. 4 4
      src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyDocument.cs
  19. 4 0
      src/PixiEditor.ChangeableDocument/Changes/Drawing/DrawRectangle_UpdateableChange.cs
  20. 9 0
      src/PixiEditor.ChangeableDocument/Changes/Root/ResizeCanvas_Change.cs
  21. 13 13
      src/PixiEditor.ChangeableDocument/Changes/Root/SymmetryAxisPosition_UpdateableChange.cs
  22. 12 12
      src/PixiEditor.ChangeableDocument/Changes/Root/SymmetryAxisState_Change.cs
  23. 1 1
      src/PixiEditor.ChangeableDocument/Enums/SymmetryAxisDirection.cs
  24. 1 1
      src/PixiEditorPrototype/CustomControls/SymmetryOverlay/SymmetryAxisDragInfo.cs
  25. 52 52
      src/PixiEditorPrototype/CustomControls/SymmetryOverlay/SymmetryOverlay.cs
  26. 14 12
      src/PixiEditorPrototype/Models/DocumentUpdater.cs
  27. 4 4
      src/PixiEditorPrototype/UserControls/Viewport/Viewport.xaml
  28. 11 11
      src/PixiEditorPrototype/ViewModels/DocumentViewModel.cs
  29. 2 2
      src/PixiEditorPrototype/Views/MainWindow.xaml

+ 79 - 7
src/ChunkyImageLib/ChunkyImage.cs

@@ -64,6 +64,8 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
     private List<ChunkyImage> activeClips = new();
     private SKBlendMode blendMode = SKBlendMode.Src;
     private bool lockTransparency = false;
+    private int? horizontalSymmetryAxis = null;
+    private int? verticalSymmetryAxis = null;
 
     private Dictionary<ChunkResolution, Dictionary<Vector2i, Chunk>> committedChunks;
     private Dictionary<ChunkResolution, Dictionary<Vector2i, Chunk>> latestChunks;
@@ -214,8 +216,10 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
         return null;
     }
 
-    private Chunk? MaybeGetLatestChunk(Vector2i pos, ChunkResolution resolution) => latestChunks[resolution].TryGetValue(pos, out Chunk? value) ? value : null;
-    private Chunk? MaybeGetCommittedChunk(Vector2i pos, ChunkResolution resolution) => committedChunks[resolution].TryGetValue(pos, out Chunk? value) ? value : null;
+    private Chunk? MaybeGetLatestChunk(Vector2i pos, ChunkResolution resolution)
+        => latestChunks[resolution].TryGetValue(pos, out Chunk? value) ? value : null;
+    private Chunk? MaybeGetCommittedChunk(Vector2i pos, ChunkResolution resolution)
+        => committedChunks[resolution].TryGetValue(pos, out Chunk? value) ? value : null;
 
     public void AddRasterClip(ChunkyImage clippingMask)
     {
@@ -240,6 +244,26 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
         }
     }
 
+    public void SetHorizontalAxisOfSymmetry(int position)
+    {
+        lock (lockObject)
+        {
+            if (queuedOperations.Count > 0)
+                throw new InvalidOperationException("This function can only be executed when there are no queued operations");
+            horizontalSymmetryAxis = position;
+        }
+    }
+
+    public void SetVerticalAxisOfSymmetry(int position)
+    {
+        lock (lockObject)
+        {
+            if (queuedOperations.Count > 0)
+                throw new InvalidOperationException("This function can only be executed when there are no queued operations");
+            verticalSymmetryAxis = position;
+        }
+    }
+
     public void EnableLockTransparency()
     {
         lock (lockObject)
@@ -311,12 +335,26 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
 
     private void EnqueueOperation(IDrawOperation operation)
     {
-        var chunks = operation.FindAffectedChunks();
-        chunks.RemoveWhere(pos => IsOutsideBounds(pos, LatestSize));
-        if (operation.IgnoreEmptyChunks)
-            chunks.IntersectWith(FindAllChunks());
-        EnqueueOperation(operation, chunks);
+        List<IDrawOperation> operations = new(4) { operation };
+
+        if (horizontalSymmetryAxis is not null && verticalSymmetryAxis is not null)
+            operations.Add(operation.AsMirrored(verticalSymmetryAxis, horizontalSymmetryAxis));
+        if (horizontalSymmetryAxis is not null)
+            operations.Add(operation.AsMirrored(null, horizontalSymmetryAxis));
+        if (verticalSymmetryAxis is not null)
+            operations.Add(operation.AsMirrored(verticalSymmetryAxis, null));
+
+        foreach (var op in operations)
+        {
+            var chunks = op.FindAffectedChunks();
+            chunks.RemoveWhere(pos => IsOutsideBounds(pos, LatestSize));
+            if (operation.IgnoreEmptyChunks)
+                chunks.IntersectWith(FindAllChunks());
+            chunks.UnionWith(op.FindAffectedChunks());
+            EnqueueOperation(op, chunks);
+        }
     }
+
     private void EnqueueOperation(IOperation operation, HashSet<Vector2i> chunks)
     {
         queuedOperations.Add((operation, chunks));
@@ -335,6 +373,8 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
             activeClips.Clear();
             blendMode = SKBlendMode.Src;
             lockTransparency = false;
+            horizontalSymmetryAxis = null;
+            verticalSymmetryAxis = null;
 
             //clear latest chunks
             foreach (var (_, chunksOfRes) in latestChunks)
@@ -373,6 +413,8 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
             activeClips.Clear();
             blendMode = SKBlendMode.Src;
             lockTransparency = false;
+            horizontalSymmetryAxis = null;
+            verticalSymmetryAxis = null;
 
             commitCounter++;
             if (commitCounter % 30 == 0)
@@ -642,6 +684,36 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
         }
     }
 
+    private void DrawOperationWithSymmetry(IDrawOperation operation, Chunk chunk, Vector2i chunkPos)
+    {
+        operation.DrawOnChunk(chunk, chunkPos);
+        if (horizontalSymmetryAxis is not null)
+        {
+            chunk.Surface.SkiaSurface.Canvas.Save();
+            float y = (float)horizontalSymmetryAxis * (float)chunk.Resolution.Multiplier() - (chunkPos.Y * chunk.Resolution.PixelSize());
+            chunk.Surface.SkiaSurface.Canvas.Scale(1, -1, 0, y);
+            operation.DrawOnChunk(chunk, chunkPos);
+            chunk.Surface.SkiaSurface.Canvas.Restore();
+        }
+        if (verticalSymmetryAxis is not null)
+        {
+            chunk.Surface.SkiaSurface.Canvas.Save();
+            float x = (float)verticalSymmetryAxis * (float)chunk.Resolution.Multiplier() - (chunkPos.X * chunk.Resolution.PixelSize());
+            chunk.Surface.SkiaSurface.Canvas.Scale(-1, 1, x, 0);
+            operation.DrawOnChunk(chunk, chunkPos);
+            chunk.Surface.SkiaSurface.Canvas.Restore();
+        }
+        if (horizontalSymmetryAxis is not null && verticalSymmetryAxis is not null)
+        {
+            float x = (float)verticalSymmetryAxis * (float)chunk.Resolution.Multiplier() - (chunkPos.X * chunk.Resolution.PixelSize());
+            float y = (float)horizontalSymmetryAxis * (float)chunk.Resolution.Multiplier() - (chunkPos.Y * chunk.Resolution.PixelSize());
+            chunk.Surface.SkiaSurface.Canvas.Save();
+            chunk.Surface.SkiaSurface.Canvas.Scale(-1, -1, x, y);
+            operation.DrawOnChunk(chunk, chunkPos);
+            chunk.Surface.SkiaSurface.Canvas.Restore();
+        }
+    }
+
     /// <returns>
     /// True if the chunk was fully cleared (and should be deleted).
     /// </returns>

+ 15 - 0
src/ChunkyImageLib/DataHolders/ShapeCorners.cs

@@ -73,4 +73,19 @@ public struct ShapeCorners
 
         return crossTop == crossRight && crossTop == crossLeft && crossTop == crossBottom;
     }
+
+    public ShapeCorners AsMirroredAcrossHorAxis(int horAxisY) => this with
+    {
+        BottomLeft = BottomLeft.ReflectY(horAxisY),
+        BottomRight = BottomRight.ReflectY(horAxisY),
+        TopLeft = TopLeft.ReflectY(horAxisY),
+        TopRight = TopRight.ReflectY(horAxisY),
+    };
+    public ShapeCorners AsMirroredAcrossVerAxis(int verAxisX) => this with
+    {
+        BottomLeft = BottomLeft.ReflectX(verAxisX),
+        BottomRight = BottomRight.ReflectX(verAxisX),
+        TopLeft = TopLeft.ReflectX(verAxisX),
+        TopRight = TopRight.ReflectX(verAxisX),
+    };
 }

+ 7 - 0
src/ChunkyImageLib/DataHolders/ShapeData.cs

@@ -18,7 +18,14 @@ public record struct ShapeData
     public SKColor FillColor { get; }
     public SKBlendMode BlendMode { get; }
     public Vector2d Center { get; }
+    /// <summary>Can be negative to show flipping </summary>
     public Vector2d Size { get; }
     public double Angle { get; }
     public int StrokeWidth { get; }
+
+    public ShapeData AsMirroredAcrossHorAxis(int horAxisY)
+        => new ShapeData(Center.ReflectY(horAxisY), new(Size.X, -Size.Y), -Angle, StrokeWidth, StrokeColor, FillColor, BlendMode);
+    public ShapeData AsMirroredAcrossVerAxis(int verAxisX)
+        => new ShapeData(Center.ReflectX(verAxisX), new(-Size.X, Size.Y), -Angle, StrokeWidth, StrokeColor, FillColor, BlendMode);
+
 }

+ 16 - 1
src/ChunkyImageLib/DataHolders/Vector2d.cs

@@ -11,6 +11,8 @@ public struct Vector2d
     public double Length => Math.Sqrt(LengthSquared);
     public double LengthSquared => X * X + Y * Y;
     public double Angle => Y < 0 ? -AngleTo(new Vector2d(1, 0)) : AngleTo(new Vector2d(1, 0));
+    public double LongestAxis => (Math.Abs(X) < Math.Abs(Y)) ? Y : X;
+    public double ShortestAxis => (Math.Abs(X) < Math.Abs(Y)) ? X : Y;
 
     public Vector2d(double x, double y)
     {
@@ -62,7 +64,20 @@ public struct Vector2d
         Vector2d point = this - pos1;
         return (line * point) * line + pos1;
     }
-
+    /// <summary>
+    /// Reflects the vector across a vertical line with the specified position
+    /// </summary>
+    public Vector2d ReflectX(double lineX)
+    {
+        return new(2 * lineX - X, Y);
+    }
+    /// <summary>
+    /// Reflects the vector along a horizontal line with the specified position
+    /// </summary>
+    public Vector2d ReflectY(double lineY)
+    {
+        return new(X, 2 * lineY - Y);
+    }
     public Vector2d ReflectAcrossLine(Vector2d pos1, Vector2d pos2)
     {
         var onLine = ProjectOntoLine(pos1, pos2);

+ 16 - 0
src/ChunkyImageLib/DataHolders/Vector2i.cs

@@ -10,6 +10,8 @@ public struct Vector2i
     public int TaxicabLength => Math.Abs(X) + Math.Abs(Y);
     public double Length => Math.Sqrt(LengthSquared);
     public int LengthSquared => X * X + Y * Y;
+    public int LongestAxis => (Math.Abs(X) < Math.Abs(Y)) ? Y : X;
+    public int ShortestAxis => (Math.Abs(X) < Math.Abs(Y)) ? X : Y;
 
     public Vector2i(int x, int y)
     {
@@ -25,6 +27,20 @@ public struct Vector2i
     {
         return new Vector2i(X * other.X, Y * other.Y);
     }
+    /// <summary>
+    /// Reflects the vector across a vertical line with the specified position
+    /// </summary>
+    public Vector2i ReflectX(int lineX)
+    {
+        return new(2 * lineX - X, Y);
+    }
+    /// <summary>
+    /// Reflects the vector along a horizontal line with the specified position
+    /// </summary>
+    public Vector2i ReflectY(int lineY)
+    {
+        return new(X, 2 * lineY - Y);
+    }
     public static Vector2i operator +(Vector2i a, Vector2i b)
     {
         return new Vector2i(a.X + b.X, a.Y + b.Y);

+ 11 - 0
src/ChunkyImageLib/Operations/ClearRegionOperation.cs

@@ -32,4 +32,15 @@ internal class ClearRegionOperation : IDrawOperation
         return OperationHelper.FindChunksFullyInsideRectangle(pos, size, ChunkPool.FullChunkSize);
     }
     public void Dispose() { }
+
+    public IDrawOperation AsMirrored(int? verAxisX, int? horAxisY)
+    {
+        if (verAxisX is not null && horAxisY is not null)
+            return new ClearRegionOperation((pos + size).ReflectX((int)verAxisX).ReflectY((int)horAxisY), size);
+        if (verAxisX is not null)
+            return new ClearRegionOperation(new Vector2i(pos.X + size.X, pos.Y).ReflectX((int)verAxisX), size);
+        if (horAxisY is not null)
+            return new ClearRegionOperation(new Vector2i(pos.X, pos.Y + size.Y).ReflectY((int)horAxisY), size);
+        return new ClearRegionOperation(pos, size);
+    }
 }

+ 1 - 0
src/ChunkyImageLib/Operations/IDrawOperation.cs

@@ -7,4 +7,5 @@ internal interface IDrawOperation : IOperation
     bool IgnoreEmptyChunks { get; }
     void DrawOnChunk(Chunk chunk, Vector2i chunkPos);
     HashSet<Vector2i> FindAffectedChunks();
+    IDrawOperation AsMirrored(int? verAxisX, int? horAxisY);
 }

+ 15 - 1
src/ChunkyImageLib/Operations/ImageOperation.cs

@@ -3,7 +3,7 @@ using SkiaSharp;
 
 namespace ChunkyImageLib.Operations;
 
-internal record class ImageOperation : IDrawOperation
+internal class ImageOperation : IDrawOperation
 {
     private SKMatrix transformMatrix;
     private ShapeCorners corners;
@@ -71,4 +71,18 @@ internal record class ImageOperation : IDrawOperation
         if (imageWasCopied)
             toPaint.Dispose();
     }
+
+    public IDrawOperation AsMirrored(int? verAxisX, int? horAxisY)
+    {
+        if (verAxisX is not null && horAxisY is not null)
+            return new ImageOperation
+                (corners.AsMirroredAcrossVerAxis((int)verAxisX).AsMirroredAcrossHorAxis((int)horAxisY), toPaint, imageWasCopied);
+        else if (verAxisX is not null)
+            return new ImageOperation
+                (corners.AsMirroredAcrossVerAxis((int)verAxisX), toPaint, imageWasCopied);
+        else if (horAxisY is not null)
+            return new ImageOperation
+                (corners.AsMirroredAcrossHorAxis((int)horAxisY), toPaint, imageWasCopied);
+        return new ImageOperation(corners, toPaint, imageWasCopied);
+    }
 }

+ 19 - 8
src/ChunkyImageLib/Operations/RectangleOperation.cs

@@ -3,7 +3,7 @@ using SkiaSharp;
 
 namespace ChunkyImageLib.Operations;
 
-internal record class RectangleOperation : IDrawOperation
+internal class RectangleOperation : IDrawOperation
 {
     public RectangleOperation(ShapeData rect)
     {
@@ -20,10 +20,10 @@ internal record class RectangleOperation : IDrawOperation
         // use a clipping rectangle with 2x stroke width to make sure stroke doesn't stick outside rect bounds
         skiaSurf.Canvas.Save();
 
-        var convertedPos = OperationHelper.ConvertForResolution(-Data.Size / 2, chunk.Resolution);
+        var convertedPos = OperationHelper.ConvertForResolution(-Data.Size.Abs() / 2, chunk.Resolution);
         var convertedCenter = OperationHelper.ConvertForResolution(Data.Center, chunk.Resolution) - chunkPos.Multiply(chunk.PixelSize);
 
-        var convertedSize = OperationHelper.ConvertForResolution(Data.Size, chunk.Resolution);
+        var convertedSize = OperationHelper.ConvertForResolution(Data.Size.Abs(), chunk.Resolution);
         int convertedStroke = (int)Math.Round(chunk.Resolution.Multiplier() * Data.StrokeWidth);
 
         var rect = SKRect.Create((SKPoint)convertedPos, (SKSize)convertedSize);
@@ -56,20 +56,31 @@ internal record class RectangleOperation : IDrawOperation
 
     public HashSet<Vector2i> FindAffectedChunks()
     {
-        if (Data.Size.X < 1 || Data.Size.Y < 1 || Data.StrokeColor.Alpha == 0 && Data.FillColor.Alpha == 0)
+        if (Math.Abs(Data.Size.X) < 1 || Math.Abs(Data.Size.Y) < 1 || Data.StrokeColor.Alpha == 0 && Data.FillColor.Alpha == 0)
             return new();
-        if (Data.FillColor.Alpha != 0 || Data.Size.X == 1 || Data.Size.Y == 1)
-            return OperationHelper.FindChunksTouchingRectangle(Data.Center, Data.Size, Data.Angle, ChunkPool.FullChunkSize);
+        if (Data.FillColor.Alpha != 0 || Math.Abs(Data.Size.X) == 1 || Math.Abs(Data.Size.Y) == 1)
+            return OperationHelper.FindChunksTouchingRectangle(Data.Center, Data.Size.Abs(), Data.Angle, ChunkPool.FullChunkSize);
 
-        var chunks = OperationHelper.FindChunksTouchingRectangle(Data.Center, Data.Size, Data.Angle, ChunkPool.FullChunkSize);
+        var chunks = OperationHelper.FindChunksTouchingRectangle(Data.Center, Data.Size.Abs(), Data.Angle, ChunkPool.FullChunkSize);
         chunks.ExceptWith(
             OperationHelper.FindChunksFullyInsideRectangle(
                 Data.Center,
-                Data.Size - new Vector2d(Data.StrokeWidth * 2, Data.StrokeWidth * 2),
+                Data.Size.Abs() - new Vector2d(Data.StrokeWidth * 2, Data.StrokeWidth * 2),
                 Data.Angle,
                 ChunkPool.FullChunkSize));
         return chunks;
     }
 
     public void Dispose() { }
+
+    public IDrawOperation AsMirrored(int? verAxisX, int? horAxisY)
+    {
+        if (verAxisX is not null && horAxisY is not null)
+            return new RectangleOperation(Data.AsMirroredAcrossHorAxis((int)horAxisY).AsMirroredAcrossVerAxis((int)verAxisX));
+        else if (verAxisX is not null)
+            return new RectangleOperation(Data.AsMirroredAcrossVerAxis((int)verAxisX));
+        else if (horAxisY is not null)
+            return new RectangleOperation(Data.AsMirroredAcrossHorAxis((int)horAxisY));
+        return new RectangleOperation(Data);
+    }
 }

+ 4 - 4
src/PixiEditor.ChangeableDocument/Actions/Root/SetSymmetryState_Action.cs → src/PixiEditor.ChangeableDocument/Actions/Root/SetSymmetryAxisState_Action.cs

@@ -3,19 +3,19 @@ using PixiEditor.ChangeableDocument.Changes.Root;
 using PixiEditor.ChangeableDocument.Enums;
 
 namespace PixiEditor.ChangeableDocument.Actions.Root;
-public record class SetSymmetryState_Action : IMakeChangeAction
+public record class SetSymmetryAxisState_Action : IMakeChangeAction
 {
-    public SetSymmetryState_Action(SymmetryDirection direction, bool state)
+    public SetSymmetryAxisState_Action(SymmetryAxisDirection direction, bool state)
     {
         Direction = direction;
         State = state;
     }
 
-    public SymmetryDirection Direction { get; }
+    public SymmetryAxisDirection Direction { get; }
     public bool State { get; }
 
     Change IMakeChangeAction.CreateCorrespondingChange()
     {
-        return new SymmetryState_Change(Direction, State);
+        return new SymmetryAxisState_Change(Direction, State);
     }
 }

+ 2 - 2
src/PixiEditor.ChangeableDocument/Actions/Root/SymmetryPosition/EndSetSymmetryPosition_Action.cs → src/PixiEditor.ChangeableDocument/Actions/Root/SymmetryPosition/EndSetSymmetryAxisPosition_Action.cs

@@ -2,10 +2,10 @@
 using PixiEditor.ChangeableDocument.Changes.Root;
 
 namespace PixiEditor.ChangeableDocument.Actions.Root.SymmetryPosition;
-public class EndSetSymmetryPosition_Action : IEndChangeAction
+public class EndSetSymmetryAxisPosition_Action : IEndChangeAction
 {
     bool IEndChangeAction.IsChangeTypeMatching(Change change)
     {
-        return change is SymmetryPosition_UpdateableChange;
+        return change is SymmetryAxisPosition_UpdateableChange;
     }
 }

+ 5 - 5
src/PixiEditor.ChangeableDocument/Actions/Root/SymmetryPosition/SetSymmetryPosition_Action.cs → src/PixiEditor.ChangeableDocument/Actions/Root/SymmetryPosition/SetSymmetryAxisPosition_Action.cs

@@ -3,24 +3,24 @@ using PixiEditor.ChangeableDocument.Changes.Root;
 using PixiEditor.ChangeableDocument.Enums;
 
 namespace PixiEditor.ChangeableDocument.Actions.Root.SymmetryPosition;
-public class SetSymmetryPosition_Action : IStartOrUpdateChangeAction
+public class SetSymmetryAxisPosition_Action : IStartOrUpdateChangeAction
 {
-    public SetSymmetryPosition_Action(SymmetryDirection direction, int position)
+    public SetSymmetryAxisPosition_Action(SymmetryAxisDirection direction, int position)
     {
         Direction = direction;
         Position = position;
     }
 
-    public SymmetryDirection Direction { get; }
+    public SymmetryAxisDirection Direction { get; }
     public int Position { get; }
 
     UpdateableChange IStartOrUpdateChangeAction.CreateCorrespondingChange()
     {
-        return new SymmetryPosition_UpdateableChange(Direction, Position);
+        return new SymmetryAxisPosition_UpdateableChange(Direction, Position);
     }
 
     void IStartOrUpdateChangeAction.UpdateCorrespodingChange(UpdateableChange change)
     {
-        ((SymmetryPosition_UpdateableChange)change).Update(Position);
+        ((SymmetryAxisPosition_UpdateableChange)change).Update(Position);
     }
 }

+ 7 - 0
src/PixiEditor.ChangeableDocument/ChangeInfos/Root/SymmetryAxisPosition_ChangeInfo.cs

@@ -0,0 +1,7 @@
+using PixiEditor.ChangeableDocument.Enums;
+
+namespace PixiEditor.ChangeableDocument.ChangeInfos.Root;
+public record class SymmetryAxisPosition_ChangeInfo : IChangeInfo
+{
+    public SymmetryAxisDirection Direction { get; init; }
+}

+ 7 - 0
src/PixiEditor.ChangeableDocument/ChangeInfos/Root/SymmetryAxisState_ChangeInfo.cs

@@ -0,0 +1,7 @@
+using PixiEditor.ChangeableDocument.Enums;
+
+namespace PixiEditor.ChangeableDocument.ChangeInfos.Root;
+public record class SymmetryAxisState_ChangeInfo : IChangeInfo
+{
+    public SymmetryAxisDirection Direction { get; init; }
+}

+ 0 - 7
src/PixiEditor.ChangeableDocument/ChangeInfos/Root/SymmetryPosition_ChangeInfo.cs

@@ -1,7 +0,0 @@
-using PixiEditor.ChangeableDocument.Enums;
-
-namespace PixiEditor.ChangeableDocument.ChangeInfos.Root;
-public record class SymmetryPosition_ChangeInfo : IChangeInfo
-{
-    public SymmetryDirection Direction { get; init; }
-}

+ 0 - 7
src/PixiEditor.ChangeableDocument/ChangeInfos/Root/SymmetryState_ChangeInfo.cs

@@ -1,7 +0,0 @@
-using PixiEditor.ChangeableDocument.Enums;
-
-namespace PixiEditor.ChangeableDocument.ChangeInfos.Root;
-public record class SymmetryState_ChangeInfo : IChangeInfo
-{
-    public SymmetryDirection Direction { get; init; }
-}

+ 4 - 4
src/PixiEditor.ChangeableDocument/Changeables/Document.cs

@@ -16,10 +16,10 @@ internal class Document : IChangeable, IReadOnlyDocument, IDisposable
     internal Folder StructureRoot { get; } = new() { GuidValue = Guid.Empty };
     internal Selection Selection { get; } = new();
     public Vector2i Size { get; set; } = DefaultSize;
-    public bool HorizontalSymmetryEnabled { get; set; }
-    public bool VerticalSymmetryEnabled { get; set; }
-    public int HorizontalSymmetryPosition { get; set; }
-    public int VerticalSymmetryPosition { get; set; }
+    public bool HorizontalSymmetryAxisEnabled { get; set; }
+    public bool VerticalSymmetryAxisEnabled { get; set; }
+    public int HorizontalSymmetryAxisY { get; set; }
+    public int VerticalSymmetryAxisX { get; set; }
 
     public void Dispose()
     {

+ 4 - 4
src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyDocument.cs

@@ -7,10 +7,10 @@ public interface IReadOnlyDocument
     IReadOnlyFolder ReadOnlyStructureRoot { get; }
     IReadOnlySelection ReadOnlySelection { get; }
     Vector2i Size { get; }
-    bool HorizontalSymmetryEnabled { get; }
-    bool VerticalSymmetryEnabled { get; }
-    int HorizontalSymmetryPosition { get; }
-    int VerticalSymmetryPosition { get; }
+    bool HorizontalSymmetryAxisEnabled { get; }
+    bool VerticalSymmetryAxisEnabled { get; }
+    int HorizontalSymmetryAxisY { get; }
+    int VerticalSymmetryAxisX { get; }
     IReadOnlyStructureMember? FindMember(Guid guid);
     IReadOnlyStructureMember FindMemberOrThrow(Guid guid);
     (IReadOnlyStructureMember, IReadOnlyFolder) FindChildAndParentOrThrow(Guid guid);

+ 4 - 0
src/PixiEditor.ChangeableDocument/Changes/Drawing/DrawRectangle_UpdateableChange.cs

@@ -32,6 +32,10 @@ internal class DrawRectangle_UpdateableChange : UpdateableChange
         var targetMember = target.FindMemberOrThrow(memberGuid);
         if (targetMember is Layer layer && layer.LockTransparency)
             targetImage.EnableLockTransparency();
+        if (target.HorizontalSymmetryAxisEnabled)
+            targetImage.SetHorizontalAxisOfSymmetry(target.HorizontalSymmetryAxisY);
+        if (target.VerticalSymmetryAxisEnabled)
+            targetImage.SetVerticalAxisOfSymmetry(target.VerticalSymmetryAxisX);
 
         targetImage.EnqueueDrawRectangle(rect);
 

+ 9 - 0
src/PixiEditor.ChangeableDocument/Changes/Root/ResizeCanvas_Change.cs

@@ -9,6 +9,8 @@ namespace PixiEditor.ChangeableDocument.Changes.Root;
 internal class ResizeCanvas_Change : Change
 {
     private Vector2i originalSize;
+    private int originalHorAxisY;
+    private int originalVerAxisX;
     private Dictionary<Guid, CommittedChunkStorage> deletedChunks = new();
     private Dictionary<Guid, CommittedChunkStorage> deletedMaskChunks = new();
     private CommittedChunkStorage? selectionChunkStorage;
@@ -20,6 +22,8 @@ internal class ResizeCanvas_Change : Change
     public override void Initialize(Document target)
     {
         originalSize = target.Size;
+        originalHorAxisY = target.HorizontalSymmetryAxisY;
+        originalVerAxisX = target.VerticalSymmetryAxisX;
     }
 
     private void ForEachLayer(Folder folder, Action<Layer> action)
@@ -44,6 +48,8 @@ internal class ResizeCanvas_Change : Change
         }
 
         target.Size = newSize;
+        target.VerticalSymmetryAxisX = Math.Clamp(originalVerAxisX, 0, target.Size.X);
+        target.HorizontalSymmetryAxisY = Math.Clamp(originalHorAxisY, 0, target.Size.Y);
 
         ForEachLayer(target.StructureRoot, (layer) =>
         {
@@ -93,6 +99,9 @@ internal class ResizeCanvas_Change : Change
         selectionChunkStorage.Dispose();
         selectionChunkStorage = null;
 
+        target.HorizontalSymmetryAxisY = originalHorAxisY;
+        target.VerticalSymmetryAxisX = originalVerAxisX;
+
         foreach (var stored in deletedChunks)
             stored.Value.Dispose();
         deletedChunks = new();

+ 13 - 13
src/PixiEditor.ChangeableDocument/Changes/Root/SymmetryPosition_UpdateableChange.cs → src/PixiEditor.ChangeableDocument/Changes/Root/SymmetryAxisPosition_UpdateableChange.cs

@@ -4,13 +4,13 @@ using PixiEditor.ChangeableDocument.ChangeInfos.Root;
 using PixiEditor.ChangeableDocument.Enums;
 
 namespace PixiEditor.ChangeableDocument.Changes.Root;
-internal class SymmetryPosition_UpdateableChange : UpdateableChange
+internal class SymmetryAxisPosition_UpdateableChange : UpdateableChange
 {
-    private readonly SymmetryDirection direction;
+    private readonly SymmetryAxisDirection direction;
     private int newPos;
     private int originalPos;
 
-    public SymmetryPosition_UpdateableChange(SymmetryDirection direction, int pos)
+    public SymmetryAxisPosition_UpdateableChange(SymmetryAxisDirection direction, int pos)
     {
         this.direction = direction;
         newPos = pos;
@@ -25,18 +25,18 @@ internal class SymmetryPosition_UpdateableChange : UpdateableChange
     {
         originalPos = direction switch
         {
-            SymmetryDirection.Horizontal => target.HorizontalSymmetryPosition,
-            SymmetryDirection.Vertical => target.VerticalSymmetryPosition,
+            SymmetryAxisDirection.Horizontal => target.HorizontalSymmetryAxisY,
+            SymmetryAxisDirection.Vertical => target.VerticalSymmetryAxisX,
             _ => throw new NotImplementedException(),
         };
     }
 
     private void SetPosition(Document target, int position)
     {
-        if (direction == SymmetryDirection.Horizontal)
-            target.HorizontalSymmetryPosition = position;
-        else if (direction == SymmetryDirection.Vertical)
-            target.VerticalSymmetryPosition = position;
+        if (direction == SymmetryAxisDirection.Horizontal)
+            target.HorizontalSymmetryAxisY = position;
+        else if (direction == SymmetryAxisDirection.Vertical)
+            target.VerticalSymmetryAxisX = position;
         else
             throw new NotImplementedException();
     }
@@ -45,13 +45,13 @@ internal class SymmetryPosition_UpdateableChange : UpdateableChange
     {
         ignoreInUndo = originalPos == newPos;
         SetPosition(target, newPos);
-        return new SymmetryPosition_ChangeInfo() { Direction = direction };
+        return new SymmetryAxisPosition_ChangeInfo() { Direction = direction };
     }
 
     public override IChangeInfo? ApplyTemporarily(Document target)
     {
         SetPosition(target, newPos);
-        return new SymmetryPosition_ChangeInfo() { Direction = direction };
+        return new SymmetryAxisPosition_ChangeInfo() { Direction = direction };
     }
 
     public override IChangeInfo? Revert(Document target)
@@ -59,11 +59,11 @@ internal class SymmetryPosition_UpdateableChange : UpdateableChange
         if (originalPos == newPos)
             return null;
         SetPosition(target, originalPos);
-        return new SymmetryPosition_ChangeInfo() { Direction = direction };
+        return new SymmetryAxisPosition_ChangeInfo() { Direction = direction };
     }
 
     public override bool IsMergeableWith(Change other)
     {
-        return other is SymmetryPosition_UpdateableChange;
+        return other is SymmetryAxisPosition_UpdateableChange;
     }
 }

+ 12 - 12
src/PixiEditor.ChangeableDocument/Changes/Root/SymmetryState_Change.cs → src/PixiEditor.ChangeableDocument/Changes/Root/SymmetryAxisState_Change.cs

@@ -4,13 +4,13 @@ using PixiEditor.ChangeableDocument.ChangeInfos.Root;
 using PixiEditor.ChangeableDocument.Enums;
 
 namespace PixiEditor.ChangeableDocument.Changes.Root;
-internal class SymmetryState_Change : Change
+internal class SymmetryAxisState_Change : Change
 {
-    private readonly SymmetryDirection direction;
+    private readonly SymmetryAxisDirection direction;
     private readonly bool newEnabled;
     private bool originalEnabled;
 
-    public SymmetryState_Change(SymmetryDirection direction, bool enabled)
+    public SymmetryAxisState_Change(SymmetryAxisDirection direction, bool enabled)
     {
         this.direction = direction;
         this.newEnabled = enabled;
@@ -20,18 +20,18 @@ internal class SymmetryState_Change : Change
     {
         originalEnabled = direction switch
         {
-            SymmetryDirection.Horizontal => target.HorizontalSymmetryEnabled,
-            SymmetryDirection.Vertical => target.VerticalSymmetryEnabled,
+            SymmetryAxisDirection.Horizontal => target.HorizontalSymmetryAxisEnabled,
+            SymmetryAxisDirection.Vertical => target.VerticalSymmetryAxisEnabled,
             _ => throw new NotImplementedException(),
         };
     }
 
     private void SetState(Document target, bool state)
     {
-        if (direction == SymmetryDirection.Horizontal)
-            target.HorizontalSymmetryEnabled = state;
-        else if (direction == SymmetryDirection.Vertical)
-            target.VerticalSymmetryEnabled = state;
+        if (direction == SymmetryAxisDirection.Horizontal)
+            target.HorizontalSymmetryAxisEnabled = state;
+        else if (direction == SymmetryAxisDirection.Vertical)
+            target.VerticalSymmetryAxisEnabled = state;
         else
             throw new NotImplementedException();
     }
@@ -45,7 +45,7 @@ internal class SymmetryState_Change : Change
         }
         SetState(target, newEnabled);
         ignoreInUndo = false;
-        return new SymmetryState_ChangeInfo() { Direction = direction };
+        return new SymmetryAxisState_ChangeInfo() { Direction = direction };
     }
 
     public override IChangeInfo? Revert(Document target)
@@ -53,11 +53,11 @@ internal class SymmetryState_Change : Change
         if (originalEnabled == newEnabled)
             return null;
         SetState(target, originalEnabled);
-        return new SymmetryState_ChangeInfo() { Direction = direction };
+        return new SymmetryAxisState_ChangeInfo() { Direction = direction };
     }
 
     public override bool IsMergeableWith(Change other)
     {
-        return other is SymmetryState_Change;
+        return other is SymmetryAxisState_Change;
     }
 }

+ 1 - 1
src/PixiEditor.ChangeableDocument/Enums/SymmetryDirection.cs → src/PixiEditor.ChangeableDocument/Enums/SymmetryAxisDirection.cs

@@ -1,5 +1,5 @@
 namespace PixiEditor.ChangeableDocument.Enums;
-public enum SymmetryDirection
+public enum SymmetryAxisDirection
 {
     Horizontal, Vertical
 }

+ 1 - 1
src/PixiEditorPrototype/CustomControls/SymmetryOverlay/SymmetryDragInfo.cs → src/PixiEditorPrototype/CustomControls/SymmetryOverlay/SymmetryAxisDragInfo.cs

@@ -1,4 +1,4 @@
 using PixiEditor.ChangeableDocument.Enums;
 
 namespace PixiEditorPrototype.CustomControls.SymmetryOverlay;
-internal record class SymmetryDragInfo(SymmetryDirection Direction, int NewPosition);
+internal record class SymmetryAxisDragInfo(SymmetryAxisDirection Direction, int NewPosition);

+ 52 - 52
src/PixiEditorPrototype/CustomControls/SymmetryOverlay/SymmetryOverlay.cs

@@ -10,44 +10,44 @@ namespace PixiEditorPrototype.CustomControls.SymmetryOverlay;
 
 internal class SymmetryOverlay : Control
 {
-    public static readonly DependencyProperty HorizontalPositionProperty =
-        DependencyProperty.Register(nameof(HorizontalPosition), typeof(int), typeof(SymmetryOverlay),
+    public static readonly DependencyProperty HorizontalAxisYProperty =
+        DependencyProperty.Register(nameof(HorizontalAxisY), typeof(int), typeof(SymmetryOverlay),
             new(0, OnPositionUpdate));
 
-    public int HorizontalPosition
+    public int HorizontalAxisY
     {
-        get => (int)GetValue(HorizontalPositionProperty);
-        set => SetValue(HorizontalPositionProperty, value);
+        get => (int)GetValue(HorizontalAxisYProperty);
+        set => SetValue(HorizontalAxisYProperty, value);
     }
 
-    public static readonly DependencyProperty VerticalPositionProperty =
-        DependencyProperty.Register(nameof(VerticalPosition), typeof(int), typeof(SymmetryOverlay),
+    public static readonly DependencyProperty VerticalAxisXProperty =
+        DependencyProperty.Register(nameof(VerticalAxisX), typeof(int), typeof(SymmetryOverlay),
             new(0, OnPositionUpdate));
 
-    public int VerticalPosition
+    public int VerticalAxisX
     {
-        get => (int)GetValue(VerticalPositionProperty);
-        set => SetValue(VerticalPositionProperty, value);
+        get => (int)GetValue(VerticalAxisXProperty);
+        set => SetValue(VerticalAxisXProperty, value);
     }
 
-    public static readonly DependencyProperty HorizontalVisibleProperty =
-        DependencyProperty.Register(nameof(HorizontalVisible), typeof(bool), typeof(SymmetryOverlay),
+    public static readonly DependencyProperty HorizontalAxisVisibleProperty =
+        DependencyProperty.Register(nameof(HorizontalAxisVisible), typeof(bool), typeof(SymmetryOverlay),
             new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsRender));
 
-    public bool HorizontalVisible
+    public bool HorizontalAxisVisible
     {
-        get => (bool)GetValue(HorizontalVisibleProperty);
-        set => SetValue(HorizontalVisibleProperty, value);
+        get => (bool)GetValue(HorizontalAxisVisibleProperty);
+        set => SetValue(HorizontalAxisVisibleProperty, value);
     }
 
-    public static readonly DependencyProperty VerticalVisibleProperty =
-        DependencyProperty.Register(nameof(VerticalVisible), typeof(bool), typeof(SymmetryOverlay),
+    public static readonly DependencyProperty VerticalAxisVisibleProperty =
+        DependencyProperty.Register(nameof(VerticalAxisVisible), typeof(bool), typeof(SymmetryOverlay),
             new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsRender));
 
-    public bool VerticalVisible
+    public bool VerticalAxisVisible
     {
-        get => (bool)GetValue(VerticalVisibleProperty);
-        set => SetValue(VerticalVisibleProperty, value);
+        get => (bool)GetValue(VerticalAxisVisibleProperty);
+        set => SetValue(VerticalAxisVisibleProperty, value);
     }
 
     public static readonly DependencyProperty ZoomboxScaleProperty =
@@ -87,39 +87,39 @@ internal class SymmetryOverlay : Control
     };
 
     private Pen borderPen = new Pen(Brushes.Black, 1.0);
-    private int horizontalPosition;
-    private int verticalPosition;
+    private int horizontalAxisY;
+    private int verticalAxisX;
 
     protected override void OnRender(DrawingContext drawingContext)
     {
         base.OnRender(drawingContext);
-        if (!HorizontalVisible && !VerticalVisible)
+        if (!HorizontalAxisVisible && !VerticalAxisVisible)
             return;
 
         borderPen.Thickness = 1.0 / ZoomboxScale;
         handleGeometry.Transform = new ScaleTransform(HandleSize / ZoomboxScale, HandleSize / ZoomboxScale);
 
-        if (HorizontalVisible)
+        if (HorizontalAxisVisible)
         {
-            drawingContext.PushTransform(new TranslateTransform(0, horizontalPosition));
+            drawingContext.PushTransform(new TranslateTransform(0, horizontalAxisY));
             drawingContext.DrawGeometry(Brushes.White, borderPen, handleGeometry);
             drawingContext.PushTransform(new RotateTransform(180, ActualWidth / 2, 0));
             drawingContext.DrawGeometry(Brushes.White, borderPen, handleGeometry);
             drawingContext.Pop();
             drawingContext.Pop();
-            drawingContext.DrawLine(borderPen, new(0, horizontalPosition), new(ActualWidth, horizontalPosition));
+            drawingContext.DrawLine(borderPen, new(0, horizontalAxisY), new(ActualWidth, horizontalAxisY));
         }
-        if (VerticalVisible)
+        if (VerticalAxisVisible)
         {
             drawingContext.PushTransform(new RotateTransform(90));
-            drawingContext.PushTransform(new TranslateTransform(0, -verticalPosition));
+            drawingContext.PushTransform(new TranslateTransform(0, -verticalAxisX));
             drawingContext.DrawGeometry(Brushes.White, borderPen, handleGeometry);
             drawingContext.PushTransform(new RotateTransform(180, ActualHeight / 2, 0));
             drawingContext.DrawGeometry(Brushes.White, borderPen, handleGeometry);
             drawingContext.Pop();
             drawingContext.Pop();
             drawingContext.Pop();
-            drawingContext.DrawLine(borderPen, new(verticalPosition, 0), new(verticalPosition, ActualHeight));
+            drawingContext.DrawLine(borderPen, new(verticalAxisX, 0), new(verticalAxisX, ActualHeight));
         }
     }
 
@@ -132,24 +132,24 @@ internal class SymmetryOverlay : Control
         return new PointHitTestResult(this, hitTestParameters.HitPoint);
     }
 
-    private SymmetryDirection? IsTouchingHandle(Vector2d position)
+    private SymmetryAxisDirection? IsTouchingHandle(Vector2d position)
     {
         double radius = HandleSize / ZoomboxScale / 2;
-        Vector2d left = new(-radius, horizontalPosition);
-        Vector2d right = new(ActualWidth + radius, horizontalPosition);
-        Vector2d up = new(verticalPosition, -radius);
-        Vector2d down = new(verticalPosition, ActualHeight + radius);
-
-        if (HorizontalVisible && ((left - position).Length < radius || (right - position).Length < radius))
-            return SymmetryDirection.Horizontal;
-        if (VerticalVisible && ((up - position).Length < radius || (down - position).Length < radius))
-            return SymmetryDirection.Vertical;
+        Vector2d left = new(-radius, horizontalAxisY);
+        Vector2d right = new(ActualWidth + radius, horizontalAxisY);
+        Vector2d up = new(verticalAxisX, -radius);
+        Vector2d down = new(verticalAxisX, ActualHeight + radius);
+
+        if (HorizontalAxisVisible && (Math.Abs((left - position).LongestAxis) < radius || Math.Abs((right - position).LongestAxis) < radius))
+            return SymmetryAxisDirection.Horizontal;
+        if (VerticalAxisVisible && (Math.Abs((up - position).LongestAxis) < radius || Math.Abs((down - position).LongestAxis) < radius))
+            return SymmetryAxisDirection.Vertical;
         return null;
     }
 
     private Vector2d ToVector2d(Point pos) => new Vector2d(pos.X, pos.Y);
 
-    public SymmetryDirection? capturedDirection;
+    public SymmetryAxisDirection? capturedDirection;
 
     protected override void OnMouseDown(MouseButtonEventArgs e)
     {
@@ -164,13 +164,13 @@ internal class SymmetryOverlay : Control
         e.Handled = true;
     }
 
-    private void CallSymmetryDragCommand(SymmetryDirection direction, int position)
+    private void CallSymmetryDragCommand(SymmetryAxisDirection direction, int position)
     {
-        SymmetryDragInfo dragInfo = new(direction, position);
+        SymmetryAxisDragInfo dragInfo = new(direction, position);
         if (DragCommand is not null && DragCommand.CanExecute(dragInfo))
             DragCommand.Execute(dragInfo);
     }
-    private void CallSymmetryDragEndCommand(SymmetryDirection direction)
+    private void CallSymmetryDragEndCommand(SymmetryAxisDirection direction)
     {
         if (DragEndCommand is not null && DragEndCommand.CanExecute(direction))
             DragEndCommand.Execute(direction);
@@ -184,7 +184,7 @@ internal class SymmetryOverlay : Control
             return;
         ReleaseMouseCapture();
 
-        CallSymmetryDragEndCommand((SymmetryDirection)capturedDirection);
+        CallSymmetryDragEndCommand((SymmetryAxisDirection)capturedDirection);
 
         capturedDirection = null;
         e.Handled = true;
@@ -197,15 +197,15 @@ internal class SymmetryOverlay : Control
         if (capturedDirection is null)
             return;
         var pos = ToVector2d(e.GetPosition(this));
-        if (capturedDirection == SymmetryDirection.Horizontal)
+        if (capturedDirection == SymmetryAxisDirection.Horizontal)
         {
-            horizontalPosition = (int)Math.Round(Math.Clamp(pos.Y, 0, ActualHeight));
-            CallSymmetryDragCommand((SymmetryDirection)capturedDirection, horizontalPosition);
+            horizontalAxisY = (int)Math.Round(Math.Clamp(pos.Y, 0, ActualHeight));
+            CallSymmetryDragCommand((SymmetryAxisDirection)capturedDirection, horizontalAxisY);
         }
-        else if (capturedDirection == SymmetryDirection.Vertical)
+        else if (capturedDirection == SymmetryAxisDirection.Vertical)
         {
-            verticalPosition = (int)Math.Round(Math.Clamp(pos.X, 0, ActualWidth));
-            CallSymmetryDragCommand((SymmetryDirection)capturedDirection, verticalPosition);
+            verticalAxisX = (int)Math.Round(Math.Clamp(pos.X, 0, ActualWidth));
+            CallSymmetryDragCommand((SymmetryAxisDirection)capturedDirection, verticalAxisX);
         }
         e.Handled = true;
     }
@@ -213,8 +213,8 @@ internal class SymmetryOverlay : Control
     private static void OnPositionUpdate(DependencyObject obj, DependencyPropertyChangedEventArgs args)
     {
         var self = (SymmetryOverlay)obj;
-        self.horizontalPosition = self.HorizontalPosition;
-        self.verticalPosition = self.VerticalPosition;
+        self.horizontalAxisY = self.HorizontalAxisY;
+        self.verticalAxisX = self.VerticalAxisX;
         self.InvalidateVisual();
     }
 }

+ 14 - 12
src/PixiEditorPrototype/Models/DocumentUpdater.cs

@@ -71,29 +71,29 @@ internal class DocumentUpdater
             case Selection_ChangeInfo info:
                 ProcessSelection(info);
                 break;
-            case SymmetryState_ChangeInfo info:
+            case SymmetryAxisState_ChangeInfo info:
                 ProcessSymmetryState(info);
                 break;
-            case SymmetryPosition_ChangeInfo info:
+            case SymmetryAxisPosition_ChangeInfo info:
                 ProcessSymmetryPosition(info);
                 break;
         }
     }
 
-    private void ProcessSymmetryPosition(SymmetryPosition_ChangeInfo info)
+    private void ProcessSymmetryPosition(SymmetryAxisPosition_ChangeInfo info)
     {
-        if (info.Direction == SymmetryDirection.Horizontal)
-            doc.RaisePropertyChanged(nameof(doc.HorizontalSymmetryPosition));
-        else if (info.Direction == SymmetryDirection.Vertical)
-            doc.RaisePropertyChanged(nameof(doc.VerticalSymmetryPosition));
+        if (info.Direction == SymmetryAxisDirection.Horizontal)
+            doc.RaisePropertyChanged(nameof(doc.HorizontalSymmetryAxisY));
+        else if (info.Direction == SymmetryAxisDirection.Vertical)
+            doc.RaisePropertyChanged(nameof(doc.VerticalSymmetryAxisX));
     }
 
-    private void ProcessSymmetryState(SymmetryState_ChangeInfo info)
+    private void ProcessSymmetryState(SymmetryAxisState_ChangeInfo info)
     {
-        if (info.Direction == SymmetryDirection.Horizontal)
-            doc.RaisePropertyChanged(nameof(doc.HorizontalSymmetryEnabled));
-        else if (info.Direction == SymmetryDirection.Vertical)
-            doc.RaisePropertyChanged(nameof(doc.VerticalSymmetryEnabled));
+        if (info.Direction == SymmetryAxisDirection.Horizontal)
+            doc.RaisePropertyChanged(nameof(doc.HorizontalSymmetryAxisEnabled));
+        else if (info.Direction == SymmetryAxisDirection.Vertical)
+            doc.RaisePropertyChanged(nameof(doc.VerticalSymmetryAxisEnabled));
     }
 
     private void ProcessSelection(Selection_ChangeInfo info)
@@ -144,6 +144,8 @@ internal class DocumentUpdater
         doc.RaisePropertyChanged(nameof(doc.Bitmaps));
         doc.RaisePropertyChanged(nameof(doc.Width));
         doc.RaisePropertyChanged(nameof(doc.Height));
+        doc.RaisePropertyChanged(nameof(doc.HorizontalSymmetryAxisY));
+        doc.RaisePropertyChanged(nameof(doc.VerticalSymmetryAxisX));
     }
 
     private WriteableBitmap CreateBitmap(Vector2i size)

+ 4 - 4
src/PixiEditorPrototype/UserControls/Viewport/Viewport.xaml

@@ -50,10 +50,10 @@
                     </Image>
                     <sym:SymmetryOverlay 
                         ZoomboxScale="{Binding Zoombox.Scale}"
-                        HorizontalVisible="{Binding Document.HorizontalSymmetryEnabled}"
-                        VerticalVisible="{Binding Document.VerticalSymmetryEnabled}"
-                        HorizontalPosition="{Binding Document.HorizontalSymmetryPosition, Mode=OneWay}"
-                        VerticalPosition="{Binding Document.VerticalSymmetryPosition, Mode=OneWay}"
+                        HorizontalAxisVisible="{Binding Document.HorizontalSymmetryAxisEnabled}"
+                        VerticalAxisVisible="{Binding Document.VerticalSymmetryAxisEnabled}"
+                        HorizontalAxisY="{Binding Document.HorizontalSymmetryAxisY, Mode=OneWay}"
+                        VerticalAxisX="{Binding Document.VerticalSymmetryAxisX, Mode=OneWay}"
                         DragCommand="{Binding Document.DragSymmetryCommand}"
                         DragEndCommand="{Binding Document.EndDragSymmetryCommand}"
                         />

+ 11 - 11
src/PixiEditorPrototype/ViewModels/DocumentViewModel.cs

@@ -76,17 +76,17 @@ internal class DocumentViewModel : INotifyPropertyChanged
     public int Height => Helpers.Tracker.Document.Size.Y;
     public SKPath SelectionPath => Helpers.Tracker.Document.ReadOnlySelection.ReadOnlySelectionPath;
     public Guid GuidValue { get; } = Guid.NewGuid();
-    public int HorizontalSymmetryPosition => Helpers.Tracker.Document.HorizontalSymmetryPosition;
-    public int VerticalSymmetryPosition => Helpers.Tracker.Document.VerticalSymmetryPosition;
-    public bool HorizontalSymmetryEnabled
+    public int HorizontalSymmetryAxisY => Helpers.Tracker.Document.HorizontalSymmetryAxisY;
+    public int VerticalSymmetryAxisX => Helpers.Tracker.Document.VerticalSymmetryAxisX;
+    public bool HorizontalSymmetryAxisEnabled
     {
-        get => Helpers.Tracker.Document.HorizontalSymmetryEnabled;
-        set => Helpers.ActionAccumulator.AddFinishedActions(new SetSymmetryState_Action(SymmetryDirection.Horizontal, value));
+        get => Helpers.Tracker.Document.HorizontalSymmetryAxisEnabled;
+        set => Helpers.ActionAccumulator.AddFinishedActions(new SetSymmetryAxisState_Action(SymmetryAxisDirection.Horizontal, value));
     }
-    public bool VerticalSymmetryEnabled
+    public bool VerticalSymmetryAxisEnabled
     {
-        get => Helpers.Tracker.Document.VerticalSymmetryEnabled;
-        set => Helpers.ActionAccumulator.AddFinishedActions(new SetSymmetryState_Action(SymmetryDirection.Vertical, value));
+        get => Helpers.Tracker.Document.VerticalSymmetryAxisEnabled;
+        set => Helpers.ActionAccumulator.AddFinishedActions(new SetSymmetryAxisState_Action(SymmetryAxisDirection.Vertical, value));
     }
 
     public Dictionary<ChunkResolution, SKSurface> Surfaces { get; set; } = new();
@@ -156,15 +156,15 @@ internal class DocumentViewModel : INotifyPropertyChanged
     {
         if (obj is null)
             return;
-        var info = (SymmetryDragInfo)obj;
-        Helpers.ActionAccumulator.AddActions(new SetSymmetryPosition_Action(info.Direction, info.NewPosition));
+        var info = (SymmetryAxisDragInfo)obj;
+        Helpers.ActionAccumulator.AddActions(new SetSymmetryAxisPosition_Action(info.Direction, info.NewPosition));
     }
 
     private void EndDragSymmetry(object? obj)
     {
         if (obj is null)
             return;
-        Helpers.ActionAccumulator.AddFinishedActions(new EndSetSymmetryPosition_Action());
+        Helpers.ActionAccumulator.AddFinishedActions(new EndSetSymmetryAxisPosition_Action());
     }
 
     public void StartUpdateRectangle(ShapeData data)

+ 2 - 2
src/PixiEditorPrototype/Views/MainWindow.xaml

@@ -167,10 +167,10 @@
                 <CheckBox x:Name="flipYCheckbox" Margin="5, 0">Flip Y</CheckBox>
                 <CheckBox 
                     x:Name="horizontalSymmetryCheckbox" Margin="5,0" 
-                    IsChecked="{Binding ActiveDocument.HorizontalSymmetryEnabled}">Hor Sym</CheckBox>
+                    IsChecked="{Binding ActiveDocument.HorizontalSymmetryAxisEnabled}">Hor Sym</CheckBox>
                 <CheckBox 
                     x:Name="verticalSymmetryCheckbox" Margin="5,0" 
-                    IsChecked="{Binding ActiveDocument.VerticalSymmetryEnabled}">Ver Sym</CheckBox>
+                    IsChecked="{Binding ActiveDocument.VerticalSymmetryAxisEnabled}">Ver Sym</CheckBox>
             </StackPanel>
         </Border>
         <Grid>