Browse Source

Not quite working pixel perfect pen

Equbuxu 3 years ago
parent
commit
1cec2ef5c0

+ 50 - 21
src/ChunkyImageLib/ChunkyImage.cs

@@ -6,6 +6,7 @@ using OneOf.Types;
 using SkiaSharp;
 using SkiaSharp;
 
 
 [assembly: InternalsVisibleTo("ChunkyImageLibTest")]
 [assembly: InternalsVisibleTo("ChunkyImageLibTest")]
+
 namespace ChunkyImageLib;
 namespace ChunkyImageLib;
 
 
 /// <summary>
 /// <summary>
@@ -46,8 +47,9 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
         public int QueueProgress { get; set; }
         public int QueueProgress { get; set; }
         public bool IsDeleted { get; set; }
         public bool IsDeleted { get; set; }
     }
     }
+
     private bool disposed = false;
     private bool disposed = false;
-    private object lockObject = new();
+    private readonly object lockObject = new();
     private int commitCounter = 0;
     private int commitCounter = 0;
 
 
     public const int FullChunkSize = ChunkPool.FullChunkSize;
     public const int FullChunkSize = ChunkPool.FullChunkSize;
@@ -56,10 +58,11 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
     private static SKPaint ReplacingPaint { get; } = new SKPaint() { BlendMode = SKBlendMode.Src };
     private static SKPaint ReplacingPaint { get; } = new SKPaint() { BlendMode = SKBlendMode.Src };
     private static SKPaint SmoothReplacingPaint { get; } = new SKPaint() { BlendMode = SKBlendMode.Src, FilterQuality = SKFilterQuality.Medium };
     private static SKPaint SmoothReplacingPaint { get; } = new SKPaint() { BlendMode = SKBlendMode.Src, FilterQuality = SKFilterQuality.Medium };
     private static SKPaint AddingPaint { get; } = new SKPaint() { BlendMode = SKBlendMode.Plus };
     private static SKPaint AddingPaint { get; } = new SKPaint() { BlendMode = SKBlendMode.Plus };
-    private SKPaint blendModePaint = new SKPaint() { BlendMode = SKBlendMode.Src };
+    private readonly SKPaint blendModePaint = new SKPaint() { BlendMode = SKBlendMode.Src };
 
 
     public VecI CommittedSize { get; private set; }
     public VecI CommittedSize { get; private set; }
     public VecI LatestSize { get; private set; }
     public VecI LatestSize { get; private set; }
+
     public int QueueLength
     public int QueueLength
     {
     {
         get
         get
@@ -69,17 +72,17 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
         }
         }
     }
     }
 
 
-    private List<(IOperation operation, HashSet<VecI> affectedChunks)> queuedOperations = new();
-    private List<ChunkyImage> activeClips = new();
+    private readonly List<(IOperation operation, HashSet<VecI> affectedChunks)> queuedOperations = new();
+    private readonly List<ChunkyImage> activeClips = new();
     private SKBlendMode blendMode = SKBlendMode.Src;
     private SKBlendMode blendMode = SKBlendMode.Src;
     private bool lockTransparency = false;
     private bool lockTransparency = false;
     private SKPath? clippingPath;
     private SKPath? clippingPath;
     private int? horizontalSymmetryAxis = null;
     private int? horizontalSymmetryAxis = null;
     private int? verticalSymmetryAxis = null;
     private int? verticalSymmetryAxis = null;
 
 
-    private Dictionary<ChunkResolution, Dictionary<VecI, Chunk>> committedChunks;
-    private Dictionary<ChunkResolution, Dictionary<VecI, Chunk>> latestChunks;
-    private Dictionary<ChunkResolution, Dictionary<VecI, LatestChunkData>> latestChunksData;
+    private readonly Dictionary<ChunkResolution, Dictionary<VecI, Chunk>> committedChunks;
+    private readonly Dictionary<ChunkResolution, Dictionary<VecI, Chunk>> latestChunks;
+    private readonly Dictionary<ChunkResolution, Dictionary<VecI, LatestChunkData>> latestChunksData;
 
 
     public ChunkyImage(VecI size)
     public ChunkyImage(VecI size)
     {
     {
@@ -87,24 +90,15 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
         LatestSize = size;
         LatestSize = size;
         committedChunks = new()
         committedChunks = new()
         {
         {
-            [ChunkResolution.Full] = new(),
-            [ChunkResolution.Half] = new(),
-            [ChunkResolution.Quarter] = new(),
-            [ChunkResolution.Eighth] = new(),
+            [ChunkResolution.Full] = new(), [ChunkResolution.Half] = new(), [ChunkResolution.Quarter] = new(), [ChunkResolution.Eighth] = new(),
         };
         };
         latestChunks = new()
         latestChunks = new()
         {
         {
-            [ChunkResolution.Full] = new(),
-            [ChunkResolution.Half] = new(),
-            [ChunkResolution.Quarter] = new(),
-            [ChunkResolution.Eighth] = new(),
+            [ChunkResolution.Full] = new(), [ChunkResolution.Half] = new(), [ChunkResolution.Quarter] = new(), [ChunkResolution.Eighth] = new(),
         };
         };
         latestChunksData = new()
         latestChunksData = new()
         {
         {
-            [ChunkResolution.Full] = new(),
-            [ChunkResolution.Half] = new(),
-            [ChunkResolution.Quarter] = new(),
-            [ChunkResolution.Eighth] = new(),
+            [ChunkResolution.Full] = new(), [ChunkResolution.Half] = new(), [ChunkResolution.Quarter] = new(), [ChunkResolution.Eighth] = new(),
         };
         };
     }
     }
 
 
@@ -122,6 +116,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
                     continue;
                     continue;
                 output.EnqueueDrawImage(chunk * FullChunkSize, image.Surface);
                 output.EnqueueDrawImage(chunk * FullChunkSize, image.Surface);
             }
             }
+
             output.CommitChanges();
             output.CommitChanges();
             return output;
             return output;
         }
         }
@@ -167,6 +162,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
                     latestChunk.AsT2.DrawOnSurface(surface, pos, paint);
                     latestChunk.AsT2.DrawOnSurface(surface, pos, paint);
                     return true;
                     return true;
                 }
                 }
+
                 return false;
                 return false;
             }
             }
 
 
@@ -178,7 +174,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
             if (lockTransparency)
             if (lockTransparency)
                 OperationHelper.ClampAlpha(tempChunk.Surface.SkiaSurface, committedChunk.Surface.SkiaSurface);
                 OperationHelper.ClampAlpha(tempChunk.Surface.SkiaSurface, committedChunk.Surface.SkiaSurface);
             tempChunk.DrawOnSurface(surface, pos, paint);
             tempChunk.DrawOnSurface(surface, pos, paint);
-            
+
             return true;
             return true;
         }
         }
     }
     }
@@ -196,6 +192,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
                 if (operation.affectedChunks.Contains(chunkPos))
                 if (operation.affectedChunks.Contains(chunkPos))
                     return true;
                     return true;
             }
             }
+
             return false;
             return false;
         }
         }
     }
     }
@@ -253,6 +250,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
 
 
     private Chunk? MaybeGetLatestChunk(VecI pos, ChunkResolution resolution)
     private Chunk? MaybeGetLatestChunk(VecI pos, ChunkResolution resolution)
         => latestChunks[resolution].TryGetValue(pos, out Chunk? value) ? value : null;
         => latestChunks[resolution].TryGetValue(pos, out Chunk? value) ? value : null;
+
     private Chunk? MaybeGetCommittedChunk(VecI pos, ChunkResolution resolution)
     private Chunk? MaybeGetCommittedChunk(VecI pos, ChunkResolution resolution)
         => committedChunks[resolution].TryGetValue(pos, out Chunk? value) ? value : null;
         => committedChunks[resolution].TryGetValue(pos, out Chunk? value) ? value : null;
 
 
@@ -396,7 +394,6 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
         }
         }
     }
     }
 
 
-
     public void EnqueueDrawSkiaLine(VecI from, VecI to, SKStrokeCap strokeCap, float strokeWidth, SKColor color, SKBlendMode blendMode)
     public void EnqueueDrawSkiaLine(VecI from, VecI to, SKStrokeCap strokeCap, float strokeWidth, SKColor color, SKBlendMode blendMode)
     {
     {
         lock (lockObject)
         lock (lockObject)
@@ -407,6 +404,26 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
         }
         }
     }
     }
 
 
+    public void EnqueueDrawPixels(IEnumerable<VecI> pixels, SKColor color, SKBlendMode blendMode)
+    {
+        lock (lockObject)
+        {
+            ThrowIfDisposed();
+            PixelsOperation operation = new(pixels, color, blendMode);
+            EnqueueOperation(operation);
+        }
+    }
+
+    public void EnqueueDrawPixel(VecI pos, SKColor color, SKBlendMode blendMode)
+    {
+        lock (lockObject)
+        {
+            ThrowIfDisposed();
+            PixelOperation operation = new(pos, color, blendMode);
+            EnqueueOperation(operation);
+        }
+    }
+
     public void EnqueueDrawChunkyImage(VecI pos, ChunkyImage image, bool flipHor = false, bool flipVer = false)
     public void EnqueueDrawChunkyImage(VecI pos, ChunkyImage image, bool flipHor = false, bool flipVer = false)
     {
     {
         lock (lockObject)
         lock (lockObject)
@@ -501,6 +518,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
                     chunk.Dispose();
                     chunk.Dispose();
                 }
                 }
             }
             }
+
             LatestSize = CommittedSize;
             LatestSize = CommittedSize;
             foreach (var (res, chunks) in latestChunks)
             foreach (var (res, chunks) in latestChunks)
             {
             {
@@ -520,6 +538,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
             {
             {
                 MaybeCreateAndProcessQueueForChunk(chunk, ChunkResolution.Full);
                 MaybeCreateAndProcessQueueForChunk(chunk, ChunkResolution.Full);
             }
             }
+
             foreach (var (operation, _) in queuedOperations)
             foreach (var (operation, _) in queuedOperations)
             {
             {
                 operation.Dispose();
                 operation.Dispose();
@@ -614,6 +633,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
                     {
                     {
                         maybeCommitted.Surface.SkiaSurface.Canvas.DrawSurface(chunk.Surface.SkiaSurface, 0, 0, blendModePaint);
                         maybeCommitted.Surface.SkiaSurface.Canvas.DrawSurface(chunk.Surface.SkiaSurface, 0, 0, blendModePaint);
                     }
                     }
+
                     chunk.Dispose();
                     chunk.Dispose();
                 }
                 }
             }
             }
@@ -658,6 +678,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
             {
             {
                 allChunks.UnionWith(opChunks);
                 allChunks.UnionWith(opChunks);
             }
             }
+
             return allChunks;
             return allChunks;
         }
         }
     }
     }
@@ -685,6 +706,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
                 var (_, opChunks) = queuedOperations[i];
                 var (_, opChunks) = queuedOperations[i];
                 chunks.UnionWith(opChunks);
                 chunks.UnionWith(opChunks);
             }
             }
+
             return chunks;
             return chunks;
         }
         }
     }
     }
@@ -743,6 +765,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
         {
         {
             return new EmptyChunk();
             return new EmptyChunk();
         }
         }
+
         if (activeClips.Count == 0)
         if (activeClips.Count == 0)
         {
         {
             return new FilledChunk();
             return new FilledChunk();
@@ -763,6 +786,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
                 return new EmptyChunk();
                 return new EmptyChunk();
             }
             }
         }
         }
+
         return intersection;
         return intersection;
     }
     }
 
 
@@ -814,6 +838,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
         {
         {
             return IsOutsideBounds(chunkPos, resizeOperation.Size);
             return IsOutsideBounds(chunkPos, resizeOperation.Size);
         }
         }
+
         return chunkData.IsDeleted;
         return chunkData.IsDeleted;
     }
     }
 
 
@@ -878,6 +903,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
                 chunk.Dispose();
                 chunk.Dispose();
             }
             }
         }
         }
+
         foreach (var pos in toRemove)
         foreach (var pos in toRemove)
         {
         {
             committedChunks[ChunkResolution.Full].Remove(pos);
             committedChunks[ChunkResolution.Full].Remove(pos);
@@ -1000,6 +1026,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
                 chunk.Dispose();
                 chunk.Dispose();
             }
             }
         }
         }
+
         foreach (var (_, chunks) in latestChunks)
         foreach (var (_, chunks) in latestChunks)
         {
         {
             foreach (var (_, chunk) in chunks)
             foreach (var (_, chunk) in chunks)
@@ -1007,8 +1034,10 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
                 chunk.Dispose();
                 chunk.Dispose();
             }
             }
         }
         }
+
         disposed = true;
         disposed = true;
     }
     }
+
     ~ChunkyImage()
     ~ChunkyImage()
     {
     {
         DisposeAll();
         DisposeAll();

+ 54 - 0
src/ChunkyImageLib/Operations/PixelOperation.cs

@@ -0,0 +1,54 @@
+using ChunkyImageLib.DataHolders;
+using SkiaSharp;
+
+namespace ChunkyImageLib.Operations;
+
+internal class PixelOperation : IDrawOperation
+{
+    public bool IgnoreEmptyChunks => false;
+    private readonly VecI pixel;
+    private readonly SKColor color;
+    private readonly SKBlendMode blendMode;
+    private readonly SKPaint paint;
+
+    public PixelOperation(VecI pixel, SKColor color, SKBlendMode blendMode)
+    {
+        this.pixel = pixel;
+        this.color = color;
+        this.blendMode = blendMode;
+        paint = new SKPaint() { BlendMode = blendMode };
+    }
+
+    public void DrawOnChunk(Chunk chunk, VecI chunkPos)
+    {
+        // a hacky way to make the lines look slightly better on non full res chunks
+        paint.Color = new SKColor(color.Red, color.Green, color.Blue, (byte)(color.Alpha * chunk.Resolution.Multiplier()));
+
+        SKSurface surf = chunk.Surface.SkiaSurface;
+        surf.Canvas.Save();
+        surf.Canvas.Scale((float)chunk.Resolution.Multiplier());
+        surf.Canvas.Translate(-chunkPos * ChunkyImage.FullChunkSize);
+        surf.Canvas.DrawPoint(pixel, paint);
+        surf.Canvas.Restore();
+    }
+
+    public HashSet<VecI> FindAffectedChunks()
+    {
+        return new HashSet<VecI>() { OperationHelper.GetChunkPos(pixel, ChunkyImage.FullChunkSize) };
+    }
+
+    public IDrawOperation AsMirrored(int? verAxisX, int? horAxisY)
+    {
+        RectI pixelRect = new RectI(pixel, new VecI(1, 1));
+        if (verAxisX is not null)
+            pixelRect = pixelRect.ReflectX((int)verAxisX);
+        if (horAxisY is not null)
+            pixelRect = pixelRect.ReflectY((int)horAxisY);
+        return new PixelOperation(pixelRect.Pos, color, blendMode);
+    }
+
+    public void Dispose()
+    {
+        paint.Dispose();
+    }
+}

+ 53 - 0
src/ChunkyImageLib/Operations/PixelsOperation.cs

@@ -0,0 +1,53 @@
+using ChunkyImageLib.DataHolders;
+using SkiaSharp;
+
+namespace ChunkyImageLib.Operations;
+
+internal class PixelsOperation : IDrawOperation
+{
+    public bool IgnoreEmptyChunks => false;
+    private readonly SKPoint[] pixels;
+    private readonly SKColor color;
+    private readonly SKBlendMode blendMode;
+    private readonly SKPaint paint;
+
+    public PixelsOperation(IEnumerable<VecI> pixels, SKColor color, SKBlendMode blendMode)
+    {
+        this.pixels = pixels.Select(pixel => (SKPoint)pixel).ToArray();
+        this.color = color;
+        this.blendMode = blendMode;
+        paint = new SKPaint() { BlendMode = blendMode };
+    }
+
+    public void DrawOnChunk(Chunk chunk, VecI chunkPos)
+    {
+        // a hacky way to make the lines look slightly better on non full res chunks
+        paint.Color = new SKColor(color.Red, color.Green, color.Blue, (byte)(color.Alpha * chunk.Resolution.Multiplier()));
+
+        SKSurface surf = chunk.Surface.SkiaSurface;
+        surf.Canvas.Save();
+        surf.Canvas.Scale((float)chunk.Resolution.Multiplier());
+        surf.Canvas.Translate(-chunkPos * ChunkyImage.FullChunkSize);
+        surf.Canvas.DrawPoints(SKPointMode.Points, pixels, paint);
+        surf.Canvas.Restore();
+    }
+
+    public HashSet<VecI> FindAffectedChunks()
+    {
+        return pixels.Select(static pixel => OperationHelper.GetChunkPos((VecI)pixel, ChunkyImage.FullChunkSize)).ToHashSet();
+    }
+
+    public IDrawOperation AsMirrored(int? verAxisX, int? horAxisY)
+    {
+        var arr = pixels.Select(pixel => new VecI(
+            verAxisX is not null ? 2 * (int)verAxisX - (int)pixel.X - 1 : (int)pixel.X,
+            horAxisY is not null ? 2 * (int)horAxisY - (int)pixel.Y - 1 : (int)pixel.Y
+        ));
+        return new PixelsOperation(arr, color, blendMode);
+    }
+
+    public void Dispose()
+    {
+        paint.Dispose();
+    }
+}

+ 103 - 0
src/PixiEditor.ChangeableDocument/Changes/Drawing/PixelPerfectPen_UpdateableChange.cs

@@ -0,0 +1,103 @@
+using SkiaSharp;
+
+namespace PixiEditor.ChangeableDocument.Changes.Drawing;
+
+internal class PixelPerfectPen_UpdateableChange : UpdateableChange
+{
+    private readonly SKColor color;
+    private readonly bool drawOnMask;
+    private readonly Guid memberGuid;
+    private readonly List<VecI> points = new();
+    private CommittedChunkStorage? chunkStorage;
+
+    [GenerateUpdateableChangeActions]
+    public PixelPerfectPen_UpdateableChange(Guid memberGuid, VecI pos, SKColor color, bool drawOnMask)
+    {
+        this.memberGuid = memberGuid;
+        this.color = color;
+        this.drawOnMask = drawOnMask;
+        points.Add(pos);
+    }
+
+    [UpdateChangeMethod]
+    public void Update(VecI pos)
+    {
+        if (points[^1] != pos)
+            points.Add(pos);
+    }
+
+    public override OneOf<Success, Error> InitializeAndValidate(Document target)
+    {
+        if (!DrawingChangeHelper.IsValidForDrawing(target, memberGuid, drawOnMask))
+            return new Error();
+        var image = DrawingChangeHelper.GetTargetImageOrThrow(target, memberGuid, drawOnMask);
+        image.SetBlendMode(SKBlendMode.SrcOver);
+        DrawingChangeHelper.ApplyClipsSymmetriesEtc(target, image, memberGuid, drawOnMask);
+        return new Success();
+    }
+
+    private bool IsAngle(int lastPixelIndex)
+    {
+        if (lastPixelIndex < 3)
+            return false;
+        VecI first = points[lastPixelIndex - 2];
+        VecI second = points[lastPixelIndex - 1];
+        VecI third = points[lastPixelIndex];
+        return first.X != third.X && first.Y != third.Y && (second - first).TaxicabLength == 1 && (second - third).TaxicabLength == 1;
+    }
+
+    private void DoDrawingIteration(ChunkyImage image, int pointsCount)
+    {
+        if (pointsCount == 1)
+        {
+            image.EnqueueDrawPixel(points[0], color, SKBlendMode.Src);
+            return;
+        }
+
+        image.EnqueueDrawBresenhamLine(points[pointsCount - 2], points[pointsCount - 1], color, SKBlendMode.Src);
+        if (pointsCount == 3 && IsAngle(pointsCount - 1) ||
+            pointsCount >= 4 && IsAngle(pointsCount - 1) && !IsAngle(pointsCount - 2))
+        {
+            image.EnqueueDrawPixel(points[pointsCount - 2], SKColors.Transparent, SKBlendMode.Src);
+        }
+    }
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> ApplyTemporarily(Document target)
+    {
+        ChunkyImage image = DrawingChangeHelper.GetTargetImageOrThrow(target, memberGuid, drawOnMask);
+
+        int changeCount = image.QueueLength;
+        DoDrawingIteration(image, points.Count);
+        HashSet<VecI> affChunks = image.FindAffectedChunks(changeCount);
+        return DrawingChangeHelper.CreateChunkChangeInfo(memberGuid, affChunks, drawOnMask);
+    }
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, out bool ignoreInUndo)
+    {
+        if (chunkStorage is not null)
+            throw new InvalidOperationException("Trying to save chunks while saved one already exist");
+
+        ignoreInUndo = false;
+        var image = DrawingChangeHelper.GetTargetImageOrThrow(target, memberGuid, drawOnMask);
+        if (image.QueueLength == 0)
+        {
+            image.SetBlendMode(SKBlendMode.SrcOver);
+            DrawingChangeHelper.ApplyClipsSymmetriesEtc(target, image, memberGuid, drawOnMask);
+            for (int i = 1; i <= points.Count; i++)
+            {
+                DoDrawingIteration(image, i);
+            }
+        }
+
+        var affChunks = image.FindAffectedChunks();
+        chunkStorage = new CommittedChunkStorage(image, affChunks);
+        image.CommitChanges();
+        return DrawingChangeHelper.CreateChunkChangeInfo(memberGuid, affChunks, drawOnMask);
+    }
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
+    {
+        var chunks = DrawingChangeHelper.ApplyStoredChunksDisposeAndSetToNull(target, memberGuid, drawOnMask, ref chunkStorage);
+        return DrawingChangeHelper.CreateChunkChangeInfo(memberGuid, chunks, drawOnMask);
+    }
+}

+ 5 - 0
src/PixiEditorPrototype.sln.DotSettings

@@ -6,5 +6,10 @@
 	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantDefaultMemberInitializer/@EntryIndexedValue">SUGGESTION</s:String>
 	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantDefaultMemberInitializer/@EntryIndexedValue">SUGGESTION</s:String>
 	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantExtendsListEntry/@EntryIndexedValue">SUGGESTION</s:String>
 	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantExtendsListEntry/@EntryIndexedValue">SUGGESTION</s:String>
 	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=Xaml_002ERedundantNamespaceAlias/@EntryIndexedValue">SUGGESTION</s:String>
 	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=Xaml_002ERedundantNamespaceAlias/@EntryIndexedValue">SUGGESTION</s:String>
+	<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/BLANK_LINES_AFTER_BLOCK_STATEMENTS/@EntryValue">0</s:Int64>
 	<s:String x:Key="/Default/CodeStyle/Naming/XamlNaming/UserRules/=XAML_005FFIELD/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"&gt;&lt;ExtraRule Prefix="" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
 	<s:String x:Key="/Default/CodeStyle/Naming/XamlNaming/UserRules/=XAML_005FFIELD/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"&gt;&lt;ExtraRule Prefix="" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
+	<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=2CAB0A567F30704CA99AA3EC249E3153/Text/@EntryValue">$HEADER$namespace $NAMESPACE$
+{
+  internal class $CLASS$ {$END$}
+}</s:String>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=Zoombox/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=Zoombox/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

+ 1 - 0
src/PixiEditorPrototype/Models/Tool.cs

@@ -6,6 +6,7 @@ internal enum Tool
     Ellipse,
     Ellipse,
     PathBasedPen,
     PathBasedPen,
     LineBasedPen,
     LineBasedPen,
+    PixelPerfectPen,
     Eraser,
     Eraser,
     Select,
     Select,
     Lasso,
     Lasso,

+ 41 - 7
src/PixiEditorPrototype/ViewModels/DocumentViewModel.cs

@@ -43,6 +43,7 @@ internal class DocumentViewModel : INotifyPropertyChanged
     public RelayCommand? TransformSelectedAreaCommand { get; }
     public RelayCommand? TransformSelectedAreaCommand { get; }
 
 
     private VecI size = new VecI(64, 64);
     private VecI size = new VecI(64, 64);
+
     public void SetSize(VecI size)
     public void SetSize(VecI size)
     {
     {
         this.size = size;
         this.size = size;
@@ -50,39 +51,48 @@ internal class DocumentViewModel : INotifyPropertyChanged
         RaisePropertyChanged(nameof(Width));
         RaisePropertyChanged(nameof(Width));
         RaisePropertyChanged(nameof(Height));
         RaisePropertyChanged(nameof(Height));
     }
     }
+
     public VecI SizeBindable => size;
     public VecI SizeBindable => size;
 
 
     private SKPath selectionPath = new SKPath();
     private SKPath selectionPath = new SKPath();
+
     public void SetSelectionPath(SKPath selectionPath)
     public void SetSelectionPath(SKPath selectionPath)
     {
     {
         (var toDispose, this.selectionPath) = (this.selectionPath, selectionPath);
         (var toDispose, this.selectionPath) = (this.selectionPath, selectionPath);
         toDispose.Dispose();
         toDispose.Dispose();
         RaisePropertyChanged(nameof(SelectionPathBindable));
         RaisePropertyChanged(nameof(SelectionPathBindable));
     }
     }
+
     public SKPath SelectionPathBindable => selectionPath;
     public SKPath SelectionPathBindable => selectionPath;
 
 
     private int horizontalSymmetryAxisY;
     private int horizontalSymmetryAxisY;
+
     public void SetHorizontalSymmetryAxisY(int horizontalSymmetryAxisY)
     public void SetHorizontalSymmetryAxisY(int horizontalSymmetryAxisY)
     {
     {
         this.horizontalSymmetryAxisY = horizontalSymmetryAxisY;
         this.horizontalSymmetryAxisY = horizontalSymmetryAxisY;
         RaisePropertyChanged(nameof(HorizontalSymmetryAxisYBindable));
         RaisePropertyChanged(nameof(HorizontalSymmetryAxisYBindable));
     }
     }
+
     public int HorizontalSymmetryAxisYBindable => horizontalSymmetryAxisY;
     public int HorizontalSymmetryAxisYBindable => horizontalSymmetryAxisY;
 
 
     private int verticalSymmetryAxisX;
     private int verticalSymmetryAxisX;
+
     public void SetVerticalSymmetryAxisX(int verticalSymmetryAxisX)
     public void SetVerticalSymmetryAxisX(int verticalSymmetryAxisX)
     {
     {
         this.verticalSymmetryAxisX = verticalSymmetryAxisX;
         this.verticalSymmetryAxisX = verticalSymmetryAxisX;
         RaisePropertyChanged(nameof(VerticalSymmetryAxisXBindable));
         RaisePropertyChanged(nameof(VerticalSymmetryAxisXBindable));
     }
     }
+
     public int VerticalSymmetryAxisXBindable => verticalSymmetryAxisX;
     public int VerticalSymmetryAxisXBindable => verticalSymmetryAxisX;
 
 
     private bool horizontalSymmetryAxisEnabled;
     private bool horizontalSymmetryAxisEnabled;
+
     public void SetHorizontalSymmetryAxisEnabled(bool horizontalSymmetryAxisEnabled)
     public void SetHorizontalSymmetryAxisEnabled(bool horizontalSymmetryAxisEnabled)
     {
     {
         this.horizontalSymmetryAxisEnabled = horizontalSymmetryAxisEnabled;
         this.horizontalSymmetryAxisEnabled = horizontalSymmetryAxisEnabled;
         RaisePropertyChanged(nameof(HorizontalSymmetryAxisEnabledBindable));
         RaisePropertyChanged(nameof(HorizontalSymmetryAxisEnabledBindable));
     }
     }
+
     public bool HorizontalSymmetryAxisEnabledBindable
     public bool HorizontalSymmetryAxisEnabledBindable
     {
     {
         get => horizontalSymmetryAxisEnabled;
         get => horizontalSymmetryAxisEnabled;
@@ -90,11 +100,13 @@ internal class DocumentViewModel : INotifyPropertyChanged
     }
     }
 
 
     private bool verticalSymmetryAxisEnabled;
     private bool verticalSymmetryAxisEnabled;
+
     public void SetVerticalSymmetryAxisEnabled(bool verticalSymmetryAxisEnabled)
     public void SetVerticalSymmetryAxisEnabled(bool verticalSymmetryAxisEnabled)
     {
     {
         this.verticalSymmetryAxisEnabled = verticalSymmetryAxisEnabled;
         this.verticalSymmetryAxisEnabled = verticalSymmetryAxisEnabled;
         RaisePropertyChanged(nameof(VerticalSymmetryAxisEnabledBindable));
         RaisePropertyChanged(nameof(VerticalSymmetryAxisEnabledBindable));
     }
     }
+
     public bool VerticalSymmetryAxisEnabledBindable
     public bool VerticalSymmetryAxisEnabledBindable
     {
     {
         get => verticalSymmetryAxisEnabled;
         get => verticalSymmetryAxisEnabled;
@@ -102,6 +114,7 @@ internal class DocumentViewModel : INotifyPropertyChanged
     }
     }
 
 
     private string name = string.Empty;
     private string name = string.Empty;
+
     public string Name
     public string Name
     {
     {
         get => name;
         get => name;
@@ -120,10 +133,7 @@ internal class DocumentViewModel : INotifyPropertyChanged
 
 
     public Dictionary<ChunkResolution, WriteableBitmap> Bitmaps { get; set; } = new()
     public Dictionary<ChunkResolution, WriteableBitmap> Bitmaps { get; set; } = new()
     {
     {
-        [ChunkResolution.Full] = new WriteableBitmap(64, 64, 96, 96, PixelFormats.Pbgra32, null),
-        [ChunkResolution.Half] = new WriteableBitmap(32, 32, 96, 96, PixelFormats.Pbgra32, null),
-        [ChunkResolution.Quarter] = new WriteableBitmap(16, 16, 96, 96, PixelFormats.Pbgra32, null),
-        [ChunkResolution.Eighth] = new WriteableBitmap(8, 8, 96, 96, PixelFormats.Pbgra32, null),
+        [ChunkResolution.Full] = new WriteableBitmap(64, 64, 96, 96, PixelFormats.Pbgra32, null), [ChunkResolution.Half] = new WriteableBitmap(32, 32, 96, 96, PixelFormats.Pbgra32, null), [ChunkResolution.Quarter] = new WriteableBitmap(16, 16, 96, 96, PixelFormats.Pbgra32, null), [ChunkResolution.Eighth] = new WriteableBitmap(8, 8, 96, 96, PixelFormats.Pbgra32, null),
     };
     };
 
 
     public Dictionary<ChunkResolution, SKSurface> Surfaces { get; set; } = new();
     public Dictionary<ChunkResolution, SKSurface> Surfaces { get; set; } = new();
@@ -135,7 +145,7 @@ internal class DocumentViewModel : INotifyPropertyChanged
 
 
     private DocumentHelpers Helpers { get; }
     private DocumentHelpers Helpers { get; }
 
 
-    private ViewModelMain owner;
+    private readonly ViewModelMain owner;
 
 
 
 
     private bool updateableChangeActive = false;
     private bool updateableChangeActive = false;
@@ -146,6 +156,7 @@ internal class DocumentViewModel : INotifyPropertyChanged
     private bool drawingEllipse = false;
     private bool drawingEllipse = false;
     private bool drawingPathBasedPen = false;
     private bool drawingPathBasedPen = false;
     private bool drawingLineBasedPen = false;
     private bool drawingLineBasedPen = false;
+    private bool drawingPixelPerfectPen = false;
     private bool transformingRectangle = false;
     private bool transformingRectangle = false;
     private bool transformingEllipse = false;
     private bool transformingEllipse = false;
     private bool shiftingLayer = false;
     private bool shiftingLayer = false;
@@ -221,7 +232,7 @@ internal class DocumentViewModel : INotifyPropertyChanged
                 new StructureMemberName_Action(guid, layer.Name),
                 new StructureMemberName_Action(guid, layer.Name),
                 new PasteImage_Action(surface, new(new RectD(new VecD(layer.OffsetX, layer.OffsetY), new(layer.Width, layer.Height))), guid, true, false),
                 new PasteImage_Action(surface, new(new RectD(new VecD(layer.OffsetX, layer.OffsetY), new(layer.Width, layer.Height))), guid, true, false),
                 new EndPasteImage_Action()
                 new EndPasteImage_Action()
-                );
+            );
             if (layer.Opacity != 1)
             if (layer.Opacity != 1)
                 acc.AddFinishedActions(
                 acc.AddFinishedActions(
                     new StructureMemberOpacity_Action(guid, layer.Opacity),
                     new StructureMemberOpacity_Action(guid, layer.Opacity),
@@ -229,6 +240,7 @@ internal class DocumentViewModel : INotifyPropertyChanged
             if (!layer.IsVisible)
             if (!layer.IsVisible)
                 acc.AddFinishedActions(new StructureMemberIsVisible_Action(layer.IsVisible, guid));
                 acc.AddFinishedActions(new StructureMemberIsVisible_Action(layer.IsVisible, guid));
         }
         }
+
         acc.AddActions(new DeleteRecordedChanges_Action());
         acc.AddActions(new DeleteRecordedChanges_Action());
         return document;
         return document;
     }
     }
@@ -253,6 +265,7 @@ internal class DocumentViewModel : INotifyPropertyChanged
             return false;
             return false;
         return true;
         return true;
     }
     }
+
     private void TransformSelectedArea(object? obj)
     private void TransformSelectedArea(object? obj)
     {
     {
         if (updateableChangeActive || FindFirstSelectedMember() is not LayerViewModel layer || SelectionPathBindable.IsEmpty)
         if (updateableChangeActive || FindFirstSelectedMember() is not LayerViewModel layer || SelectionPathBindable.IsEmpty)
@@ -280,6 +293,7 @@ internal class DocumentViewModel : INotifyPropertyChanged
         {
         {
             Helpers.ActionAccumulator.AddActions(new ClearSelectedArea_Action(layer.GuidValue, false));
             Helpers.ActionAccumulator.AddActions(new ClearSelectedArea_Action(layer.GuidValue, false));
         }
         }
+
         Helpers.ActionAccumulator.AddActions(new ClearSelection_Action());
         Helpers.ActionAccumulator.AddActions(new ClearSelection_Action());
 
 
         // initiate transform using paste image logic
         // initiate transform using paste image logic
@@ -368,6 +382,25 @@ internal class DocumentViewModel : INotifyPropertyChanged
         Helpers.ActionAccumulator.AddFinishedActions(new EndLineBasedPen_Action());
         Helpers.ActionAccumulator.AddFinishedActions(new EndLineBasedPen_Action());
     }
     }
 
 
+    public void StartUpdatePixelPerfectPen(VecI pos, SKColor color)
+    {
+        if (!CanStartUpdate())
+            return;
+        updateableChangeActive = true;
+        drawingPixelPerfectPen = true;
+        var member = FindFirstSelectedMember();
+        Helpers.ActionAccumulator.AddActions(new PixelPerfectPen_Action(member!.GuidValue, pos, color, member.ShouldDrawOnMask));
+    }
+
+    public void EndUPixelPerfectPen()
+    {
+        if (!drawingPixelPerfectPen)
+            return;
+        drawingPixelPerfectPen = false;
+        updateableChangeActive = false;
+        Helpers.ActionAccumulator.AddFinishedActions(new EndPixelPerfectPen_Action());
+    }
+
     public void StartUpdateEllipse(RectI location, SKColor strokeColor, SKColor fillColor, int strokeWidth)
     public void StartUpdateEllipse(RectI location, SKColor strokeColor, SKColor fillColor, int strokeWidth)
     {
     {
         if (!CanStartUpdate())
         if (!CanStartUpdate())
@@ -466,7 +499,7 @@ internal class DocumentViewModel : INotifyPropertyChanged
         Helpers.ActionAccumulator.AddFinishedActions(new EndSelectLasso_Action());
         Helpers.ActionAccumulator.AddFinishedActions(new EndSelectLasso_Action());
     }
     }
 
 
-    public void ApplyTransform(object? param)
+    private void ApplyTransform(object? param)
     {
     {
         if (!transformingRectangle && !pastingImage && !transformingSelectionPath && !transformingEllipse)
         if (!transformingRectangle && !pastingImage && !transformingSelectionPath && !transformingEllipse)
             return;
             return;
@@ -497,6 +530,7 @@ internal class DocumentViewModel : INotifyPropertyChanged
             TransformViewModel.HideTransform();
             TransformViewModel.HideTransform();
             Helpers.ActionAccumulator.AddFinishedActions(new EndTransformSelectionPath_Action());
             Helpers.ActionAccumulator.AddFinishedActions(new EndTransformSelectionPath_Action());
         }
         }
+
         updateableChangeActive = false;
         updateableChangeActive = false;
     }
     }
 
 

+ 34 - 26
src/PixiEditorPrototype/ViewModels/ViewModelMain.cs

@@ -63,7 +63,9 @@ internal class ViewModelMain : INotifyPropertyChanged
             PropertyChanged?.Invoke(this, new(nameof(ZoomboxMode)));
             PropertyChanged?.Invoke(this, new(nameof(ZoomboxMode)));
         }
         }
     }
     }
+
     private int activeDocumentIndex;
     private int activeDocumentIndex;
+
     public int ActiveDocumentIndex
     public int ActiveDocumentIndex
     {
     {
         get => activeDocumentIndex;
         get => activeDocumentIndex;
@@ -123,11 +125,7 @@ internal class ViewModelMain : INotifyPropertyChanged
         var args = (MouseButtonEventArgs)(param!);
         var args = (MouseButtonEventArgs)(param!);
         var source = (System.Windows.Controls.Image)args.Source;
         var source = (System.Windows.Controls.Image)args.Source;
         var pos = args.GetPosition(source);
         var pos = args.GetPosition(source);
-        mouseDownCanvasPos = new()
-        {
-            X = pos.X / source.Width * ActiveDocument.Bitmaps[ChunkResolution.Full].PixelWidth,
-            Y = pos.Y / source.Height * ActiveDocument.Bitmaps[ChunkResolution.Full].PixelHeight
-        };
+        mouseDownCanvasPos = new() { X = pos.X / source.Width * ActiveDocument.Bitmaps[ChunkResolution.Full].PixelWidth, Y = pos.Y / source.Height * ActiveDocument.Bitmaps[ChunkResolution.Full].PixelHeight };
         toolOnMouseDown = activeTool;
         toolOnMouseDown = activeTool;
         ProcessToolMouseDown(mouseDownCanvasPos);
         ProcessToolMouseDown(mouseDownCanvasPos);
     }
     }
@@ -146,6 +144,10 @@ internal class ViewModelMain : INotifyPropertyChanged
         {
         {
             ActiveDocument!.StartUpdateLineBasedPen((VecI)pos, new SKColor(SelectedColor.R, SelectedColor.G, SelectedColor.B, SelectedColor.A));
             ActiveDocument!.StartUpdateLineBasedPen((VecI)pos, new SKColor(SelectedColor.R, SelectedColor.G, SelectedColor.B, SelectedColor.A));
         }
         }
+        else if (toolOnMouseDown == Tool.PixelPerfectPen)
+        {
+            ActiveDocument!.StartUpdatePixelPerfectPen((VecI)pos, new SKColor(SelectedColor.R, SelectedColor.G, SelectedColor.B, SelectedColor.A));
+        }
         else if (toolOnMouseDown == Tool.Eraser)
         else if (toolOnMouseDown == Tool.Eraser)
         {
         {
             ActiveDocument!.StartUpdateLineBasedPen((VecI)pos, SKColors.Transparent, true);
             ActiveDocument!.StartUpdateLineBasedPen((VecI)pos, SKColors.Transparent, true);
@@ -171,30 +173,30 @@ internal class ViewModelMain : INotifyPropertyChanged
         switch (toolOnMouseDown)
         switch (toolOnMouseDown)
         {
         {
             case Tool.Rectangle:
             case Tool.Rectangle:
-                {
-                    var rect = RectI.FromTwoPixels((VecI)mouseDownCanvasPos, (VecI)canvasPos);
-                    ActiveDocument!.StartUpdateRectangle(new ShapeData(
-                                rect.Center,
-                                rect.Size,
-                                0,
-                                (int)StrokeWidth,
-                                new SKColor(SelectedColor.R, SelectedColor.G, SelectedColor.B, SelectedColor.A),
-                                new SKColor(0, 0, 255, 128)));
-                    break;
-                }
+            {
+                var rect = RectI.FromTwoPixels((VecI)mouseDownCanvasPos, (VecI)canvasPos);
+                ActiveDocument!.StartUpdateRectangle(new ShapeData(
+                    rect.Center,
+                    rect.Size,
+                    0,
+                    (int)StrokeWidth,
+                    new SKColor(SelectedColor.R, SelectedColor.G, SelectedColor.B, SelectedColor.A),
+                    new SKColor(0, 0, 255, 128)));
+                break;
+            }
             case Tool.Ellipse:
             case Tool.Ellipse:
-                {
-                    ActiveDocument!.StartUpdateEllipse(
-                        RectI.FromTwoPixels((VecI)mouseDownCanvasPos, (VecI)canvasPos),
-                        new SKColor(SelectedColor.R, SelectedColor.G, SelectedColor.B, SelectedColor.A),
-                        new SKColor(0, 0, 255, 128),
-                        (int)StrokeWidth);
-                    break;
-                }
+            {
+                ActiveDocument!.StartUpdateEllipse(
+                    RectI.FromTwoPixels((VecI)mouseDownCanvasPos, (VecI)canvasPos),
+                    new SKColor(SelectedColor.R, SelectedColor.G, SelectedColor.B, SelectedColor.A),
+                    new SKColor(0, 0, 255, 128),
+                    (int)StrokeWidth);
+                break;
+            }
             case Tool.Select:
             case Tool.Select:
                 ActiveDocument!.StartUpdateRectSelection(
                 ActiveDocument!.StartUpdateRectSelection(
-                            RectI.FromTwoPixels((VecI)mouseDownCanvasPos, (VecI)canvasPos),
-                            selectionMode);
+                    RectI.FromTwoPixels((VecI)mouseDownCanvasPos, (VecI)canvasPos),
+                    selectionMode);
                 break;
                 break;
             case Tool.ShiftLayer:
             case Tool.ShiftLayer:
                 ActiveDocument!.StartUpdateShiftLayer((VecI)canvasPos - (VecI)mouseDownCanvasPos);
                 ActiveDocument!.StartUpdateShiftLayer((VecI)canvasPos - (VecI)mouseDownCanvasPos);
@@ -208,6 +210,9 @@ internal class ViewModelMain : INotifyPropertyChanged
             case Tool.LineBasedPen:
             case Tool.LineBasedPen:
                 ActiveDocument!.StartUpdateLineBasedPen((VecI)canvasPos, new SKColor(SelectedColor.R, SelectedColor.G, SelectedColor.B, SelectedColor.A));
                 ActiveDocument!.StartUpdateLineBasedPen((VecI)canvasPos, new SKColor(SelectedColor.R, SelectedColor.G, SelectedColor.B, SelectedColor.A));
                 break;
                 break;
+            case Tool.PixelPerfectPen:
+                ActiveDocument!.StartUpdatePixelPerfectPen((VecI)canvasPos, new SKColor(SelectedColor.R, SelectedColor.G, SelectedColor.B, SelectedColor.A));
+                break;
             case Tool.Eraser:
             case Tool.Eraser:
                 ActiveDocument!.StartUpdateLineBasedPen((VecI)canvasPos, SKColors.Transparent, true);
                 ActiveDocument!.StartUpdateLineBasedPen((VecI)canvasPos, SKColors.Transparent, true);
                 break;
                 break;
@@ -252,6 +257,9 @@ internal class ViewModelMain : INotifyPropertyChanged
             case Tool.PathBasedPen:
             case Tool.PathBasedPen:
                 ActiveDocument!.EndPathBasedPen();
                 ActiveDocument!.EndPathBasedPen();
                 break;
                 break;
+            case Tool.PixelPerfectPen:
+                ActiveDocument!.EndUPixelPerfectPen();
+                break;
             case Tool.LineBasedPen:
             case Tool.LineBasedPen:
             case Tool.Eraser:
             case Tool.Eraser:
                 ActiveDocument!.EndLineBasedPen();
                 ActiveDocument!.EndLineBasedPen();

+ 601 - 178
src/PixiEditorPrototype/Views/MainWindow.xaml

@@ -1,170 +1,391 @@
-<Window x:Class="PixiEditorPrototype.Views.MainWindow" x:ClassModifier="internal"
-        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
-        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
-        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
-        xmlns:vm="clr-namespace:PixiEditorPrototype.ViewModels"
-        xmlns:conv="clr-namespace:PixiEditorPrototype.Converters" 
-        xmlns:beh="clr-namespace:PixiEditorPrototype.Behaviors"
-        xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
-        xmlns:pe="clr-namespace:PixiEditorPrototype"
-        xmlns:controls="clr-namespace:PixiEditorPrototype.CustomControls"
-        xmlns:to="clr-namespace:PixiEditorPrototype.CustomControls.TransformOverlay"
-        xmlns:vp="clr-namespace:PixiEditorPrototype.UserControls.Viewport"
-        xmlns:zoombox="clr-namespace:PixiEditor.Zoombox;assembly=PixiEditor.Zoombox"
-        xmlns:models="clr-namespace:PixiEditorPrototype.Models"
-        xmlns:chen="clr-namespace:PixiEditor.ChangeableDocument.Enums;assembly=PixiEditor.ChangeableDocument"
-        xmlns:colorpicker="clr-namespace:ColorPicker;assembly=ColorPicker"
-        mc:Ignorable="d"
-        x:Name="window"
-        Title="MainWindow" Height="800" Width="1500">
+<Window
+    x:Class="PixiEditorPrototype.Views.MainWindow"
+    x:ClassModifier="internal"
+    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+    xmlns:vm="clr-namespace:PixiEditorPrototype.ViewModels"
+    xmlns:conv="clr-namespace:PixiEditorPrototype.Converters"
+    xmlns:beh="clr-namespace:PixiEditorPrototype.Behaviors"
+    xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
+    xmlns:pe="clr-namespace:PixiEditorPrototype"
+    xmlns:controls="clr-namespace:PixiEditorPrototype.CustomControls"
+    xmlns:to="clr-namespace:PixiEditorPrototype.CustomControls.TransformOverlay"
+    xmlns:vp="clr-namespace:PixiEditorPrototype.UserControls.Viewport"
+    xmlns:zoombox="clr-namespace:PixiEditor.Zoombox;assembly=PixiEditor.Zoombox"
+    xmlns:models="clr-namespace:PixiEditorPrototype.Models"
+    xmlns:chen="clr-namespace:PixiEditor.ChangeableDocument.Enums;assembly=PixiEditor.ChangeableDocument"
+    xmlns:colorpicker="clr-namespace:ColorPicker;assembly=ColorPicker"
+    mc:Ignorable="d"
+    x:Name="window"
+    Title="MainWindow"
+    Height="800"
+    Width="1500">
     <Window.DataContext>
     <Window.DataContext>
-        <vm:ViewModelMain/>
+        <vm:ViewModelMain />
     </Window.DataContext>
     </Window.DataContext>
     <Window.Resources>
     <Window.Resources>
-        <conv:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
-        <conv:BlendModeToStringConverter x:Key="BlendModeToStringConverter"/>
+        <conv:BoolToVisibilityConverter
+            x:Key="BoolToVisibilityConverter" />
+        <conv:BlendModeToStringConverter
+            x:Key="BlendModeToStringConverter" />
     </Window.Resources>
     </Window.Resources>
-    <DockPanel Background="Gray">
-        <Border BorderThickness="1" Background="White" BorderBrush="Black" Width="280" DockPanel.Dock="Right" Margin="5">
+    <DockPanel
+        Background="Gray">
+        <Border
+            BorderThickness="1"
+            Background="White"
+            BorderBrush="Black"
+            Width="280"
+            DockPanel.Dock="Right"
+            Margin="5">
             <DockPanel>
             <DockPanel>
-                <StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Margin="0,5,0,0">
-                    <Button Margin="5,0" Command="{Binding ActiveDocument.CreateNewLayerCommand}" Width="80">New Layer</Button>
-                    <Button Margin="5,0" Command="{Binding ActiveDocument.CreateNewFolderCommand}" Width="80">New Folder</Button>
-                    <Button Margin="5,0" Command="{Binding ActiveDocument.DeleteStructureMemberCommand}" Width="80">Delete</Button>
+                <StackPanel
+                    DockPanel.Dock="Top"
+                    Orientation="Horizontal"
+                    Margin="0,5,0,0">
+                    <Button
+                        Margin="5,0"
+                        Command="{Binding ActiveDocument.CreateNewLayerCommand}"
+                        Width="80">
+                        New Layer
+                    </Button>
+                    <Button
+                        Margin="5,0"
+                        Command="{Binding ActiveDocument.CreateNewFolderCommand}"
+                        Width="80">
+                        New Folder
+                    </Button>
+                    <Button
+                        Margin="5,0"
+                        Command="{Binding ActiveDocument.DeleteStructureMemberCommand}"
+                        Width="80">
+                        Delete
+                    </Button>
                 </StackPanel>
                 </StackPanel>
-                <StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Margin="0,5,0,0">
-                    <Button Margin="5,0" Command="{Binding ActiveDocument.CreateMaskCommand}" Width="80">Create Mask</Button>
-                    <Button Margin="5,0" Command="{Binding ActiveDocument.DeleteMaskCommand}" Width="80">Delete Mask</Button>
-                    <Button Margin="5,0" Command="{Binding ActiveDocument.ApplyMaskCommand}" Width="80">Apply Mask</Button>
+                <StackPanel
+                    DockPanel.Dock="Top"
+                    Orientation="Horizontal"
+                    Margin="0,5,0,0">
+                    <Button
+                        Margin="5,0"
+                        Command="{Binding ActiveDocument.CreateMaskCommand}"
+                        Width="80">
+                        Create Mask
+                    </Button>
+                    <Button
+                        Margin="5,0"
+                        Command="{Binding ActiveDocument.DeleteMaskCommand}"
+                        Width="80">
+                        Delete Mask
+                    </Button>
+                    <Button
+                        Margin="5,0"
+                        Command="{Binding ActiveDocument.ApplyMaskCommand}"
+                        Width="80">
+                        Apply Mask
+                    </Button>
                 </StackPanel>
                 </StackPanel>
-                <StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Margin="0,5,0,0">
-                    <controls:BlendModeComboBox Margin="5,0" Width="80" SelectedBlendMode="{Binding ActiveDocument.SelectedStructureMember.BlendModeBindable, Mode=TwoWay}"/>
+                <StackPanel
+                    DockPanel.Dock="Top"
+                    Orientation="Horizontal"
+                    Margin="0,5,0,0">
+                    <controls:BlendModeComboBox
+                        Margin="5,0"
+                        Width="80"
+                        SelectedBlendMode="{Binding ActiveDocument.SelectedStructureMember.BlendModeBindable, Mode=TwoWay}" />
                 </StackPanel>
                 </StackPanel>
-                <StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Margin="0,5,0,0">
-                    <Button Margin="5,0" Command="{Binding ActiveDocument.ToggleLockTransparencyCommand}" Width="80">Lock Alpha</Button>
-                    <Button Margin="5,0" IsEnabled="False" Width="80">Lock</Button>
-                    <Button Margin="5,0" Command="{Binding ActiveDocument.ClipToMemberBelowCommand}" Width="80">Clip to below</Button>
+                <StackPanel
+                    DockPanel.Dock="Top"
+                    Orientation="Horizontal"
+                    Margin="0,5,0,0">
+                    <Button
+                        Margin="5,0"
+                        Command="{Binding ActiveDocument.ToggleLockTransparencyCommand}"
+                        Width="80">
+                        Lock Alpha
+                    </Button>
+                    <Button
+                        Margin="5,0"
+                        IsEnabled="False"
+                        Width="80">
+                        Lock
+                    </Button>
+                    <Button
+                        Margin="5,0"
+                        Command="{Binding ActiveDocument.ClipToMemberBelowCommand}"
+                        Width="80">
+                        Clip to below
+                    </Button>
                 </StackPanel>
                 </StackPanel>
-                <DockPanel DockPanel.Dock="Top" HorizontalAlignment="Stretch" Margin="0,5,0,5">
-                    <Button Width="80" Margin="5,0" Command="{Binding ActiveDocument.CombineCommand}">Merge</Button>
-                    <TextBlock Text="{Binding ActiveDocument.SelectedStructureMember.OpacityBindable, StringFormat=N2}" 
-                           Margin="5,0" DockPanel.Dock="Right" VerticalAlignment="Center" TextAlignment="Center" d:Text="1.00" Width="30"/>
-                    <Slider Minimum="0" Maximum="1" SmallChange="0.01" LargeChange="0.1" IsSnapToTickEnabled="True" TickFrequency="0.01" x:Name="opacitySlider"
-                            VerticalAlignment="Center" HorizontalAlignment="Stretch"
-                            Value="{Binding ActiveDocument.SelectedStructureMember.OpacityBindable, Mode=OneWay}">
+                <DockPanel
+                    DockPanel.Dock="Top"
+                    HorizontalAlignment="Stretch"
+                    Margin="0,5,0,5">
+                    <Button
+                        Width="80"
+                        Margin="5,0"
+                        Command="{Binding ActiveDocument.CombineCommand}">
+                        Merge
+                    </Button>
+                    <TextBlock
+                        Text="{Binding ActiveDocument.SelectedStructureMember.OpacityBindable, StringFormat=N2}"
+                        Margin="5,0"
+                        DockPanel.Dock="Right"
+                        VerticalAlignment="Center"
+                        TextAlignment="Center"
+                        d:Text="1.00"
+                        Width="30" />
+                    <Slider
+                        Minimum="0"
+                        Maximum="1"
+                        SmallChange="0.01"
+                        LargeChange="0.1"
+                        IsSnapToTickEnabled="True"
+                        TickFrequency="0.01"
+                        x:Name="opacitySlider"
+                        VerticalAlignment="Center"
+                        HorizontalAlignment="Stretch"
+                        Value="{Binding ActiveDocument.SelectedStructureMember.OpacityBindable, Mode=OneWay}">
                         <i:Interaction.Behaviors>
                         <i:Interaction.Behaviors>
                             <beh:SliderUpdateBehavior
                             <beh:SliderUpdateBehavior
                                 DragValueChanged="{Binding ActiveDocument.SelectedStructureMember.UpdateOpacityCommand}"
                                 DragValueChanged="{Binding ActiveDocument.SelectedStructureMember.UpdateOpacityCommand}"
                                 DragEnded="{Binding ActiveDocument.SelectedStructureMember.EndOpacityUpdateCommand}"
                                 DragEnded="{Binding ActiveDocument.SelectedStructureMember.EndOpacityUpdateCommand}"
-                                ValueFromSlider="{Binding ElementName=opacitySlider, Path=Value}"/>
+                                ValueFromSlider="{Binding ElementName=opacitySlider, Path=Value}" />
                         </i:Interaction.Behaviors>
                         </i:Interaction.Behaviors>
                     </Slider>
                     </Slider>
                 </DockPanel>
                 </DockPanel>
-                <TreeView ItemsSource="{Binding ActiveDocument.StructureRoot.Children}">
+                <TreeView
+                    ItemsSource="{Binding ActiveDocument.StructureRoot.Children}">
                     <TreeView.ItemsPanel>
                     <TreeView.ItemsPanel>
                         <ItemsPanelTemplate>
                         <ItemsPanelTemplate>
-                            <pe:ReversedOrderStackPanel/>
+                            <pe:ReversedOrderStackPanel />
                         </ItemsPanelTemplate>
                         </ItemsPanelTemplate>
                     </TreeView.ItemsPanel>
                     </TreeView.ItemsPanel>
                     <TreeView.ItemContainerStyle>
                     <TreeView.ItemContainerStyle>
-                        <Style TargetType="TreeViewItem">
-                            <Setter Property="ItemsPanel">
+                        <Style
+                            TargetType="TreeViewItem">
+                            <Setter
+                                Property="ItemsPanel">
                                 <Setter.Value>
                                 <Setter.Value>
                                     <ItemsPanelTemplate>
                                     <ItemsPanelTemplate>
-                                        <pe:ReversedOrderStackPanel/>
+                                        <pe:ReversedOrderStackPanel />
                                     </ItemsPanelTemplate>
                                     </ItemsPanelTemplate>
                                 </Setter.Value>
                                 </Setter.Value>
                             </Setter>
                             </Setter>
                         </Style>
                         </Style>
                     </TreeView.ItemContainerStyle>
                     </TreeView.ItemContainerStyle>
                     <TreeView.Resources>
                     <TreeView.Resources>
-                        <HierarchicalDataTemplate DataType="{x:Type vm:FolderViewModel}" ItemsSource="{Binding Children}">
-                            <StackPanel Orientation="Horizontal" MinWidth="200" Background="Wheat">
-                                <StackPanel Orientation="Vertical" VerticalAlignment="Center">
-                                    <CheckBox VerticalAlignment="Center" HorizontalAlignment="Center" IsChecked="{Binding IsVisibleBindable}"/>
-                                    <CheckBox VerticalAlignment="Center" HorizontalAlignment="Center" IsChecked="{Binding MaskIsVisibleBindable}"
-                                              Visibility="{Binding HasMaskBindable, Converter={StaticResource BoolToVisibilityConverter}}"
-                                              Background="LightBlue"/>
+                        <HierarchicalDataTemplate
+                            DataType="{x:Type vm:FolderViewModel}"
+                            ItemsSource="{Binding Children}">
+                            <StackPanel
+                                Orientation="Horizontal"
+                                MinWidth="200"
+                                Background="Wheat">
+                                <StackPanel
+                                    Orientation="Vertical"
+                                    VerticalAlignment="Center">
+                                    <CheckBox
+                                        VerticalAlignment="Center"
+                                        HorizontalAlignment="Center"
+                                        IsChecked="{Binding IsVisibleBindable}" />
+                                    <CheckBox
+                                        VerticalAlignment="Center"
+                                        HorizontalAlignment="Center"
+                                        IsChecked="{Binding MaskIsVisibleBindable}"
+                                        Visibility="{Binding HasMaskBindable, Converter={StaticResource BoolToVisibilityConverter}}"
+                                        Background="LightBlue" />
                                 </StackPanel>
                                 </StackPanel>
-                                <Rectangle 
-                                    Fill="DarkRed" Width="8" Margin="3,0" 
-                                    Visibility="{Binding ClipToMemberBelowEnabledBindable, Converter={StaticResource BoolToVisibilityConverter}}"/>
+                                <Rectangle
+                                    Fill="DarkRed"
+                                    Width="8"
+                                    Margin="3,0"
+                                    Visibility="{Binding ClipToMemberBelowEnabledBindable, Converter={StaticResource BoolToVisibilityConverter}}" />
                                 <StackPanel>
                                 <StackPanel>
-                                    <Button Width="12" Command="{Binding MoveUpCommand}">^</Button>
-                                    <Button Width="12" Command="{Binding MoveDownCommand}">v</Button>
+                                    <Button
+                                        Width="12"
+                                        Command="{Binding MoveUpCommand}">
+                                        ^
+                                    </Button>
+                                    <Button
+                                        Width="12"
+                                        Command="{Binding MoveDownCommand}">
+                                        v
+                                    </Button>
                                 </StackPanel>
                                 </StackPanel>
-                                <Border 
-                                    BorderBrush="Black" BorderThickness="1" MaxWidth="30" MaxHeight="30" 
-                                    HorizontalAlignment="Center" VerticalAlignment="Center" Margin="3,0,0,0">
-                                    <Image Source="{Binding PreviewBitmap}"></Image>
+                                <Border
+                                    BorderBrush="Black"
+                                    BorderThickness="1"
+                                    MaxWidth="30"
+                                    MaxHeight="30"
+                                    HorizontalAlignment="Center"
+                                    VerticalAlignment="Center"
+                                    Margin="3,0,0,0">
+                                    <Image
+                                        Source="{Binding PreviewBitmap}">
+                                    </Image>
                                 </Border>
                                 </Border>
-                                <Border 
+                                <Border
                                     Visibility="{Binding HasMaskBindable, Converter={StaticResource BoolToVisibilityConverter}}"
                                     Visibility="{Binding HasMaskBindable, Converter={StaticResource BoolToVisibilityConverter}}"
-                                    BorderBrush="Black" BorderThickness="1" Background="White" MaxWidth="30" MaxHeight="30" 
-                                    HorizontalAlignment="Center" VerticalAlignment="Center" Margin="3,0,0,0">
-                                    <Image Source="{Binding MaskPreviewBitmap}"></Image>
+                                    BorderBrush="Black"
+                                    BorderThickness="1"
+                                    Background="White"
+                                    MaxWidth="30"
+                                    MaxHeight="30"
+                                    HorizontalAlignment="Center"
+                                    VerticalAlignment="Center"
+                                    Margin="3,0,0,0">
+                                    <Image
+                                        Source="{Binding MaskPreviewBitmap}">
+                                    </Image>
                                 </Border>
                                 </Border>
-                                <StackPanel VerticalAlignment="Center">
-                                    <DockPanel Margin="3, 0, 0, 0">
-                                        <TextBlock Text="{Binding OpacityBindable}" Width="25"/>
-                                        <TextBlock Text="{Binding BlendModeBindable, Converter={StaticResource BlendModeToStringConverter}}"/>
+                                <StackPanel
+                                    VerticalAlignment="Center">
+                                    <DockPanel
+                                        Margin="3, 0, 0, 0">
+                                        <TextBlock
+                                            Text="{Binding OpacityBindable}"
+                                            Width="25" />
+                                        <TextBlock
+                                            Text="{Binding BlendModeBindable, Converter={StaticResource BlendModeToStringConverter}}" />
                                     </DockPanel>
                                     </DockPanel>
-                                    <TextBox HorizontalAlignment="Left" Width="65" Text="{Binding NameBindable}" Margin="3, 0, 0, 0" Height="20"/>
+                                    <TextBox
+                                        HorizontalAlignment="Left"
+                                        Width="65"
+                                        Text="{Binding NameBindable}"
+                                        Margin="3, 0, 0, 0"
+                                        Height="20" />
                                 </StackPanel>
                                 </StackPanel>
-                                <StackPanel VerticalAlignment="Center" Margin="3, 0, 0, 0">
-                                    <CheckBox VerticalAlignment="Center"
-                                          Visibility="{Binding HasMaskBindable, Converter={StaticResource BoolToVisibilityConverter}}"
-                                          IsChecked="{Binding ShouldDrawOnMask}">Edit Mask</CheckBox>
-                                    <CheckBox VerticalAlignment="Center" IsChecked="{Binding IsSelected}">Select</CheckBox>
+                                <StackPanel
+                                    VerticalAlignment="Center"
+                                    Margin="3, 0, 0, 0">
+                                    <CheckBox
+                                        VerticalAlignment="Center"
+                                        Visibility="{Binding HasMaskBindable, Converter={StaticResource BoolToVisibilityConverter}}"
+                                        IsChecked="{Binding ShouldDrawOnMask}">
+                                        Edit Mask
+                                    </CheckBox>
+                                    <CheckBox
+                                        VerticalAlignment="Center"
+                                        IsChecked="{Binding IsSelected}">
+                                        Select
+                                    </CheckBox>
                                 </StackPanel>
                                 </StackPanel>
-                                <StackPanel Margin="3">
-                                    <TextBlock Visibility="Collapsed">🔒</TextBlock>
+                                <StackPanel
+                                    Margin="3">
+                                    <TextBlock
+                                        Visibility="Collapsed">
+                                        🔒
+                                    </TextBlock>
                                 </StackPanel>
                                 </StackPanel>
                             </StackPanel>
                             </StackPanel>
                         </HierarchicalDataTemplate>
                         </HierarchicalDataTemplate>
-                        <DataTemplate DataType="{x:Type vm:LayerViewModel}">
-                            <StackPanel Orientation="Horizontal">
-                                <StackPanel Orientation="Vertical" VerticalAlignment="Center">
-                                    <CheckBox VerticalAlignment="Center" HorizontalAlignment="Center" IsChecked="{Binding IsVisibleBindable}"/>
-                                    <CheckBox VerticalAlignment="Center" HorizontalAlignment="Center" IsChecked="{Binding MaskIsVisibleBindable}"
-                                              Visibility="{Binding HasMaskBindable, Converter={StaticResource BoolToVisibilityConverter}}"
-                                              Background="LightBlue"/>
+                        <DataTemplate
+                            DataType="{x:Type vm:LayerViewModel}">
+                            <StackPanel
+                                Orientation="Horizontal">
+                                <StackPanel
+                                    Orientation="Vertical"
+                                    VerticalAlignment="Center">
+                                    <CheckBox
+                                        VerticalAlignment="Center"
+                                        HorizontalAlignment="Center"
+                                        IsChecked="{Binding IsVisibleBindable}" />
+                                    <CheckBox
+                                        VerticalAlignment="Center"
+                                        HorizontalAlignment="Center"
+                                        IsChecked="{Binding MaskIsVisibleBindable}"
+                                        Visibility="{Binding HasMaskBindable, Converter={StaticResource BoolToVisibilityConverter}}"
+                                        Background="LightBlue" />
                                 </StackPanel>
                                 </StackPanel>
-                                <Rectangle 
-                                    Fill="DarkRed" Width="8" Margin="3,0" 
-                                    Visibility="{Binding ClipToMemberBelowEnabledBindable, Converter={StaticResource BoolToVisibilityConverter}}"/>
+                                <Rectangle
+                                    Fill="DarkRed"
+                                    Width="8"
+                                    Margin="3,0"
+                                    Visibility="{Binding ClipToMemberBelowEnabledBindable, Converter={StaticResource BoolToVisibilityConverter}}" />
                                 <StackPanel>
                                 <StackPanel>
-                                    <Button Width="12" Command="{Binding MoveUpCommand}">^</Button>
-                                    <Button Width="12" Command="{Binding MoveDownCommand}">v</Button>
+                                    <Button
+                                        Width="12"
+                                        Command="{Binding MoveUpCommand}">
+                                        ^
+                                    </Button>
+                                    <Button
+                                        Width="12"
+                                        Command="{Binding MoveDownCommand}">
+                                        v
+                                    </Button>
                                 </StackPanel>
                                 </StackPanel>
-                                <Border 
-                                    BorderBrush="Black" BorderThickness="1" Background="White" MaxWidth="30" MaxHeight="30" 
-                                    HorizontalAlignment="Center" VerticalAlignment="Center" Margin="3,0,0,0">
-                                    <Image Source="{Binding PreviewBitmap}"></Image>
+                                <Border
+                                    BorderBrush="Black"
+                                    BorderThickness="1"
+                                    Background="White"
+                                    MaxWidth="30"
+                                    MaxHeight="30"
+                                    HorizontalAlignment="Center"
+                                    VerticalAlignment="Center"
+                                    Margin="3,0,0,0">
+                                    <Image
+                                        Source="{Binding PreviewBitmap}">
+                                    </Image>
                                 </Border>
                                 </Border>
-                                <Border 
+                                <Border
                                     Visibility="{Binding HasMaskBindable, Converter={StaticResource BoolToVisibilityConverter}}"
                                     Visibility="{Binding HasMaskBindable, Converter={StaticResource BoolToVisibilityConverter}}"
-                                    BorderBrush="Black" BorderThickness="1" Background="White" MaxWidth="30" MaxHeight="30" 
-                                    HorizontalAlignment="Center" VerticalAlignment="Center" Margin="3,0,0,0">
-                                    <Image Source="{Binding MaskPreviewBitmap}"></Image>
+                                    BorderBrush="Black"
+                                    BorderThickness="1"
+                                    Background="White"
+                                    MaxWidth="30"
+                                    MaxHeight="30"
+                                    HorizontalAlignment="Center"
+                                    VerticalAlignment="Center"
+                                    Margin="3,0,0,0">
+                                    <Image
+                                        Source="{Binding MaskPreviewBitmap}">
+                                    </Image>
                                 </Border>
                                 </Border>
-                                <StackPanel VerticalAlignment="Center">
-                                    <DockPanel Margin="3, 0, 0, 0">
-                                        <TextBlock Text="{Binding OpacityBindable}" Width="25"/>
-                                        <TextBlock Text="{Binding BlendModeBindable, Converter={StaticResource BlendModeToStringConverter}}"/>
+                                <StackPanel
+                                    VerticalAlignment="Center">
+                                    <DockPanel
+                                        Margin="3, 0, 0, 0">
+                                        <TextBlock
+                                            Text="{Binding OpacityBindable}"
+                                            Width="25" />
+                                        <TextBlock
+                                            Text="{Binding BlendModeBindable, Converter={StaticResource BlendModeToStringConverter}}" />
                                     </DockPanel>
                                     </DockPanel>
-                                    <TextBox HorizontalAlignment="Left" Width="65" Text="{Binding NameBindable}" Margin="3, 0, 0, 0" Height="20"/>
+                                    <TextBox
+                                        HorizontalAlignment="Left"
+                                        Width="65"
+                                        Text="{Binding NameBindable}"
+                                        Margin="3, 0, 0, 0"
+                                        Height="20" />
                                 </StackPanel>
                                 </StackPanel>
-                                <StackPanel VerticalAlignment="Center" Margin="3, 0, 0, 0">
-                                    <CheckBox VerticalAlignment="Center"
-                                          Visibility="{Binding HasMaskBindable, Converter={StaticResource BoolToVisibilityConverter}}"
-                                          IsChecked="{Binding ShouldDrawOnMask}">Edit Mask</CheckBox>
-                                    <CheckBox VerticalAlignment="Center" IsChecked="{Binding IsSelected}">Select</CheckBox>
+                                <StackPanel
+                                    VerticalAlignment="Center"
+                                    Margin="3, 0, 0, 0">
+                                    <CheckBox
+                                        VerticalAlignment="Center"
+                                        Visibility="{Binding HasMaskBindable, Converter={StaticResource BoolToVisibilityConverter}}"
+                                        IsChecked="{Binding ShouldDrawOnMask}">
+                                        Edit Mask
+                                    </CheckBox>
+                                    <CheckBox
+                                        VerticalAlignment="Center"
+                                        IsChecked="{Binding IsSelected}">
+                                        Select
+                                    </CheckBox>
                                 </StackPanel>
                                 </StackPanel>
-                                <StackPanel Margin="3">
-                                    <TextBlock Visibility="{Binding LockTransparencyBindable, Converter={StaticResource BoolToVisibilityConverter}}">🙾</TextBlock>
-                                    <TextBlock Visibility="Collapsed">🔒</TextBlock>
+                                <StackPanel
+                                    Margin="3">
+                                    <TextBlock
+                                        Visibility="{Binding LockTransparencyBindable, Converter={StaticResource BoolToVisibilityConverter}}">
+                                        🙾
+                                    </TextBlock>
+                                    <TextBlock
+                                        Visibility="Collapsed">
+                                        🔒
+                                    </TextBlock>
                                 </StackPanel>
                                 </StackPanel>
                             </StackPanel>
                             </StackPanel>
                         </DataTemplate>
                         </DataTemplate>
@@ -172,81 +393,281 @@
                 </TreeView>
                 </TreeView>
             </DockPanel>
             </DockPanel>
         </Border>
         </Border>
-        <Border BorderThickness="1" Background="White" BorderBrush="Black" DockPanel.Dock="Top" Margin="5">
+        <Border
+            BorderThickness="1"
+            Background="White"
+            BorderBrush="Black"
+            DockPanel.Dock="Top"
+            Margin="5">
             <DockPanel>
             <DockPanel>
-                <StackPanel Orientation="Horizontal" Background="White">
-                    <Button Width="50" Margin="5" Command="{Binding LoadDocumentCommand}">Open</Button>
-                    <Button Width="50" Margin="5" Command="{Binding ActiveDocument.UndoCommand}">Undo</Button>
-                    <Button Width="50" Margin="5" Command="{Binding ActiveDocument.RedoCommand}">Redo</Button>
-                    <Button Width="100" Margin="5" Command="{Binding ActiveDocument.ClearSelectionCommand}">Clear selection</Button>
-                    <Button Width="110" Margin="5" Command="{Binding ActiveDocument.TransformSelectionPathCommand}">Transform sel. path</Button>
-                    <Button Width="110" Margin="5" Command="{Binding ActiveDocument.TransformSelectedAreaCommand}">Transform sel. area</Button>
-                    <ComboBox Width="70" Height="20" Margin="5" SelectedIndex="0" x:Name="selectionModeComboBox">
-                        <ComboBoxItem Tag="{x:Static chen:SelectionMode.New}">New</ComboBoxItem>
-                        <ComboBoxItem Tag="{x:Static chen:SelectionMode.Add}">Add</ComboBoxItem>
-                        <ComboBoxItem Tag="{x:Static chen:SelectionMode.Subtract}">Subtract</ComboBoxItem>
-                        <ComboBoxItem Tag="{x:Static chen:SelectionMode.Intersect}">Intersect</ComboBoxItem>
+                <StackPanel
+                    Orientation="Horizontal"
+                    Background="White">
+                    <Button
+                        Width="50"
+                        Margin="5"
+                        Command="{Binding LoadDocumentCommand}">
+                        Open
+                    </Button>
+                    <Button
+                        Width="50"
+                        Margin="5"
+                        Command="{Binding ActiveDocument.UndoCommand}">
+                        Undo
+                    </Button>
+                    <Button
+                        Width="50"
+                        Margin="5"
+                        Command="{Binding ActiveDocument.RedoCommand}">
+                        Redo
+                    </Button>
+                    <Button
+                        Width="100"
+                        Margin="5"
+                        Command="{Binding ActiveDocument.ClearSelectionCommand}">
+                        Clear selection
+                    </Button>
+                    <Button
+                        Width="110"
+                        Margin="5"
+                        Command="{Binding ActiveDocument.TransformSelectionPathCommand}">
+                        Transform sel. path
+                    </Button>
+                    <Button
+                        Width="110"
+                        Margin="5"
+                        Command="{Binding ActiveDocument.TransformSelectedAreaCommand}">
+                        Transform sel. area
+                    </Button>
+                    <ComboBox
+                        Width="70"
+                        Height="20"
+                        Margin="5"
+                        SelectedIndex="0"
+                        x:Name="selectionModeComboBox">
+                        <ComboBoxItem
+                            Tag="{x:Static chen:SelectionMode.New}">
+                            New
+                        </ComboBoxItem>
+                        <ComboBoxItem
+                            Tag="{x:Static chen:SelectionMode.Add}">
+                            Add
+                        </ComboBoxItem>
+                        <ComboBoxItem
+                            Tag="{x:Static chen:SelectionMode.Subtract}">
+                            Subtract
+                        </ComboBoxItem>
+                        <ComboBoxItem
+                            Tag="{x:Static chen:SelectionMode.Intersect}">
+                            Intersect
+                        </ComboBoxItem>
                         <i:Interaction.Triggers>
                         <i:Interaction.Triggers>
-                            <i:EventTrigger EventName="SelectionChanged">
-                                <i:InvokeCommandAction Command="{Binding SetSelectionModeCommand}" CommandParameter="{Binding SelectedItem.Tag, ElementName=selectionModeComboBox}"/>
+                            <i:EventTrigger
+                                EventName="SelectionChanged">
+                                <i:InvokeCommandAction
+                                    Command="{Binding SetSelectionModeCommand}"
+                                    CommandParameter="{Binding SelectedItem.Tag, ElementName=selectionModeComboBox}" />
                             </i:EventTrigger>
                             </i:EventTrigger>
                         </i:Interaction.Triggers>
                         </i:Interaction.Triggers>
                     </ComboBox>
                     </ComboBox>
-                    <Button Width="120" Margin="5" Command="{Binding ActiveDocument.ClearHistoryCommand}">Clear undo history</Button>
-                    <Button Width="100" Margin="5" Command="{Binding ActiveDocument.PasteImageCommand}">Paste Image</Button>
-                    <Button Width="100" Margin="5" Command="{Binding ActiveDocument.ApplyTransformCommand}">Apply Transform</Button>
+                    <Button
+                        Width="120"
+                        Margin="5"
+                        Command="{Binding ActiveDocument.ClearHistoryCommand}">
+                        Clear undo history
+                    </Button>
+                    <Button
+                        Width="100"
+                        Margin="5"
+                        Command="{Binding ActiveDocument.PasteImageCommand}">
+                        Paste Image
+                    </Button>
+                    <Button
+                        Width="100"
+                        Margin="5"
+                        Command="{Binding ActiveDocument.ApplyTransformCommand}">
+                        Apply Transform
+                    </Button>
                 </StackPanel>
                 </StackPanel>
-                <StackPanel DockPanel.Dock="Right" Orientation="Horizontal" HorizontalAlignment="Right">
+                <StackPanel
+                    DockPanel.Dock="Right"
+                    Orientation="Horizontal"
+                    HorizontalAlignment="Right">
                     <Label>Pen size:</Label>
                     <Label>Pen size:</Label>
-                    <TextBox Width="30" Margin="5" Text="{Binding StrokeWidth}"/>
-                    <TextBox Width="30" Margin="5" Text="{Binding ActiveDocument.ResizeWidth}"/>
-                    <TextBox Width="30" Margin="5" Text="{Binding ActiveDocument.ResizeHeight}"/>
-                    <Button Width="50" Margin="5" Command="{Binding ActiveDocument.ResizeCanvasCommand}">Resize</Button>
+                    <TextBox
+                        Width="30"
+                        Margin="5"
+                        Text="{Binding StrokeWidth}" />
+                    <TextBox
+                        Width="30"
+                        Margin="5"
+                        Text="{Binding ActiveDocument.ResizeWidth}" />
+                    <TextBox
+                        Width="30"
+                        Margin="5"
+                        Text="{Binding ActiveDocument.ResizeHeight}" />
+                    <Button
+                        Width="50"
+                        Margin="5"
+                        Command="{Binding ActiveDocument.ResizeCanvasCommand}">
+                        Resize
+                    </Button>
                 </StackPanel>
                 </StackPanel>
             </DockPanel>
             </DockPanel>
         </Border>
         </Border>
-        <Border BorderThickness="1" Background="White" BorderBrush="Black" DockPanel.Dock="Left" Margin="5">
-            <StackPanel Orientation="Vertical" Background="White">
-                <Button Width="70" Margin="5" Command="{Binding ChangeActiveToolCommand}" CommandParameter="{x:Static models:Tool.Rectangle}">Rect</Button>
-                <Button Width="70" Margin="5" Command="{Binding ChangeActiveToolCommand}" CommandParameter="{x:Static models:Tool.Ellipse}">Ellipse</Button>
-                <Button Width="70" Margin="5" Command="{Binding ChangeActiveToolCommand}" CommandParameter="{x:Static models:Tool.PathBasedPen}">Path Pen</Button>
-                <Button Width="70" Margin="5" Command="{Binding ChangeActiveToolCommand}" CommandParameter="{x:Static models:Tool.LineBasedPen}">Line Pen</Button>
-                <Button Width="70" Margin="5" Command="{Binding ChangeActiveToolCommand}" CommandParameter="{x:Static models:Tool.Eraser}">Eraser</Button>
-                <Button Width="70" Margin="5" Command="{Binding ChangeActiveToolCommand}" CommandParameter="{x:Static models:Tool.Select}">Select</Button>
-                <Button Width="70" Margin="5" Command="{Binding ChangeActiveToolCommand}" CommandParameter="{x:Static models:Tool.Lasso}">Lasso</Button>
-                <Button Width="70" Margin="5" Command="{Binding ChangeActiveToolCommand}" CommandParameter="{x:Static models:Tool.ShiftLayer}">Shift Layer</Button>
-                <Button Width="70" Margin="5" Command="{Binding ChangeActiveToolCommand}" CommandParameter="{x:Static models:Tool.FloodFill}">Fill</Button>
-                <colorpicker:PortableColorPicker Margin="5" SelectedColor="{Binding SelectedColor, Mode=TwoWay}" Width="30" Height="30"/>
-                <RadioButton GroupName="zoomboxMode" Margin="5,0" IsChecked="{Binding NormalZoombox, Mode=OneWayToSource}">Normal</RadioButton>
-                <RadioButton GroupName="zoomboxMode" Margin="5,0" IsChecked="{Binding MoveZoombox, Mode=OneWayToSource}">Move</RadioButton>
-                <RadioButton GroupName="zoomboxMode" Margin="5,0" IsChecked="{Binding RotateZoombox, Mode=OneWayToSource}">Rotate</RadioButton>
-                <CheckBox x:Name="flipXCheckbox" Margin="5, 0">Flip X</CheckBox>
-                <CheckBox x:Name="flipYCheckbox" Margin="5, 0">Flip Y</CheckBox>
-                <CheckBox 
-                    x:Name="keepOriginalImageCheckbox" Margin="5, 0"
-                    IsChecked="{Binding KeepOriginalImageOnTransform}">Keep area</CheckBox>
-                <CheckBox 
-                    x:Name="horizontalSymmetryCheckbox" Margin="5,0" 
-                    IsChecked="{Binding ActiveDocument.HorizontalSymmetryAxisEnabledBindable}">Hor Sym</CheckBox>
-                <CheckBox 
-                    x:Name="verticalSymmetryCheckbox" Margin="5,0" 
-                    IsChecked="{Binding ActiveDocument.VerticalSymmetryAxisEnabledBindable}">Ver Sym</CheckBox>
+        <Border
+            BorderThickness="1"
+            Background="White"
+            BorderBrush="Black"
+            DockPanel.Dock="Left"
+            Margin="5">
+            <StackPanel
+                Orientation="Vertical"
+                Background="White">
+                <Button
+                    Width="70"
+                    Margin="5"
+                    Command="{Binding ChangeActiveToolCommand}"
+                    CommandParameter="{x:Static models:Tool.Rectangle}">
+                    Rect
+                </Button>
+                <Button
+                    Width="70"
+                    Margin="5"
+                    Command="{Binding ChangeActiveToolCommand}"
+                    CommandParameter="{x:Static models:Tool.Ellipse}">
+                    Ellipse
+                </Button>
+                <Button
+                    Width="70"
+                    Margin="5"
+                    Command="{Binding ChangeActiveToolCommand}"
+                    CommandParameter="{x:Static models:Tool.PathBasedPen}">
+                    Path Pen
+                </Button>
+                <Button
+                    Width="70"
+                    Margin="5"
+                    Command="{Binding ChangeActiveToolCommand}"
+                    CommandParameter="{x:Static models:Tool.LineBasedPen}">
+                    Line Pen
+                </Button>
+                <Button
+                    Width="70"
+                    Margin="5"
+                    Command="{Binding ChangeActiveToolCommand}"
+                    CommandParameter="{x:Static models:Tool.PixelPerfectPen}">
+                    P Perf Pen
+                </Button>
+                <Button
+                    Width="70"
+                    Margin="5"
+                    Command="{Binding ChangeActiveToolCommand}"
+                    CommandParameter="{x:Static models:Tool.Eraser}">
+                    Eraser
+                </Button>
+                <Button
+                    Width="70"
+                    Margin="5"
+                    Command="{Binding ChangeActiveToolCommand}"
+                    CommandParameter="{x:Static models:Tool.Select}">
+                    Select
+                </Button>
+                <Button
+                    Width="70"
+                    Margin="5"
+                    Command="{Binding ChangeActiveToolCommand}"
+                    CommandParameter="{x:Static models:Tool.Lasso}">
+                    Lasso
+                </Button>
+                <Button
+                    Width="70"
+                    Margin="5"
+                    Command="{Binding ChangeActiveToolCommand}"
+                    CommandParameter="{x:Static models:Tool.ShiftLayer}">
+                    Shift Layer
+                </Button>
+                <Button
+                    Width="70"
+                    Margin="5"
+                    Command="{Binding ChangeActiveToolCommand}"
+                    CommandParameter="{x:Static models:Tool.FloodFill}">
+                    Fill
+                </Button>
+                <colorpicker:PortableColorPicker
+                    Margin="5"
+                    SelectedColor="{Binding SelectedColor, Mode=TwoWay}"
+                    Width="30"
+                    Height="30" />
+                <RadioButton
+                    GroupName="zoomboxMode"
+                    Margin="5,0"
+                    IsChecked="{Binding NormalZoombox, Mode=OneWayToSource}">
+                    Normal
+                </RadioButton>
+                <RadioButton
+                    GroupName="zoomboxMode"
+                    Margin="5,0"
+                    IsChecked="{Binding MoveZoombox, Mode=OneWayToSource}">
+                    Move
+                </RadioButton>
+                <RadioButton
+                    GroupName="zoomboxMode"
+                    Margin="5,0"
+                    IsChecked="{Binding RotateZoombox, Mode=OneWayToSource}">
+                    Rotate
+                </RadioButton>
+                <CheckBox
+                    x:Name="flipXCheckbox"
+                    Margin="5, 0">
+                    Flip X
+                </CheckBox>
+                <CheckBox
+                    x:Name="flipYCheckbox"
+                    Margin="5, 0">
+                    Flip Y
+                </CheckBox>
+                <CheckBox
+                    x:Name="keepOriginalImageCheckbox"
+                    Margin="5, 0"
+                    IsChecked="{Binding KeepOriginalImageOnTransform}">
+                    Keep area
+                </CheckBox>
+                <CheckBox
+                    x:Name="horizontalSymmetryCheckbox"
+                    Margin="5,0"
+                    IsChecked="{Binding ActiveDocument.HorizontalSymmetryAxisEnabledBindable}">
+                    Hor Sym
+                </CheckBox>
+                <CheckBox
+                    x:Name="verticalSymmetryCheckbox"
+                    Margin="5,0"
+                    IsChecked="{Binding ActiveDocument.VerticalSymmetryAxisEnabledBindable}">
+                    Ver Sym
+                </CheckBox>
             </StackPanel>
             </StackPanel>
         </Border>
         </Border>
-        <TabControl ItemsSource="{Binding Documents}" SelectedIndex="{Binding ActiveDocumentIndex}">
+        <TabControl
+            ItemsSource="{Binding Documents}"
+            SelectedIndex="{Binding ActiveDocumentIndex}">
             <TabControl.ItemTemplate>
             <TabControl.ItemTemplate>
                 <DataTemplate>
                 <DataTemplate>
-                    <TextBlock Text="{Binding Name}"/>
+                    <TextBlock
+                        Text="{Binding Name}" />
                 </DataTemplate>
                 </DataTemplate>
             </TabControl.ItemTemplate>
             </TabControl.ItemTemplate>
             <TabControl.ContentTemplate>
             <TabControl.ContentTemplate>
                 <DataTemplate>
                 <DataTemplate>
-                    <Grid Background="Gray">
+                    <Grid
+                        Background="Gray">
                         <Grid.ColumnDefinitions>
                         <Grid.ColumnDefinitions>
-                            <ColumnDefinition/>
-                            <ColumnDefinition/>
+                            <ColumnDefinition />
+                            <ColumnDefinition />
                         </Grid.ColumnDefinitions>
                         </Grid.ColumnDefinitions>
-                        <Border BorderThickness="1" BorderBrush="Black" Margin="5">
+                        <Border
+                            BorderThickness="1"
+                            BorderBrush="Black"
+                            Margin="5">
                             <vp:Viewport
                             <vp:Viewport
                                 Document="{Binding}"
                                 Document="{Binding}"
                                 FlipX="{Binding ElementName=flipXCheckbox, Path=IsChecked}"
                                 FlipX="{Binding ElementName=flipXCheckbox, Path=IsChecked}"
@@ -255,10 +676,13 @@
                                 MouseDownCommand="{Binding ElementName=window, Path=DataContext.MouseDownCommand}"
                                 MouseDownCommand="{Binding ElementName=window, Path=DataContext.MouseDownCommand}"
                                 MouseMoveCommand="{Binding ElementName=window, Path=DataContext.MouseMoveCommand}"
                                 MouseMoveCommand="{Binding ElementName=window, Path=DataContext.MouseMoveCommand}"
                                 MouseUpCommand="{Binding ElementName=window, Path=DataContext.MouseUpCommand}"
                                 MouseUpCommand="{Binding ElementName=window, Path=DataContext.MouseUpCommand}"
-                                Tag="First"
-                            />
+                                Tag="First" />
                         </Border>
                         </Border>
-                        <Border BorderThickness="1" BorderBrush="Black" Margin="5" Grid.Column="1">
+                        <Border
+                            BorderThickness="1"
+                            BorderBrush="Black"
+                            Margin="5"
+                            Grid.Column="1">
                             <vp:Viewport
                             <vp:Viewport
                                 Document="{Binding}"
                                 Document="{Binding}"
                                 FlipX="{Binding ElementName=flipXCheckbox, Path=IsChecked}"
                                 FlipX="{Binding ElementName=flipXCheckbox, Path=IsChecked}"
@@ -267,12 +691,11 @@
                                 MouseDownCommand="{Binding ElementName=window, Path=DataContext.MouseDownCommand}"
                                 MouseDownCommand="{Binding ElementName=window, Path=DataContext.MouseDownCommand}"
                                 MouseMoveCommand="{Binding ElementName=window, Path=DataContext.MouseMoveCommand}"
                                 MouseMoveCommand="{Binding ElementName=window, Path=DataContext.MouseMoveCommand}"
                                 MouseUpCommand="{Binding ElementName=window, Path=DataContext.MouseUpCommand}"
                                 MouseUpCommand="{Binding ElementName=window, Path=DataContext.MouseUpCommand}"
-                                Tag="Second"
-                            />
+                                Tag="Second" />
                         </Border>
                         </Border>
                     </Grid>
                     </Grid>
                 </DataTemplate>
                 </DataTemplate>
             </TabControl.ContentTemplate>
             </TabControl.ContentTemplate>
         </TabControl>
         </TabControl>
     </DockPanel>
     </DockPanel>
-</Window>
+</Window>