Bladeren bron

Merge branch 'general-changes'

Equbuxu 3 jaren geleden
bovenliggende
commit
2e99c02e8a
31 gewijzigde bestanden met toevoegingen van 349 en 74 verwijderingen
  1. 20 1
      src/ChunkyImageLib/Chunk.cs
  2. 12 2
      src/ChunkyImageLib/ChunkPool.cs
  3. 12 0
      src/ChunkyImageLib/DataHolders/ChunkResolution.cs
  4. 7 0
      src/ChunkyImageLib/DataHolders/ChunkResolutionEx.cs
  5. 103 3
      src/PixiEditor.ChangeableDocument/Changeables/Document.cs
  6. 12 3
      src/PixiEditor.ChangeableDocument/Changeables/Folder.cs
  7. 61 3
      src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyDocument.cs
  8. 3 0
      src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyFolder.cs
  9. 6 0
      src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyLayer.cs
  10. 3 0
      src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlySelection.cs
  11. 23 2
      src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyStructureMember.cs
  12. 7 0
      src/PixiEditor.ChangeableDocument/Changeables/Layer.cs
  13. 0 1
      src/PixiEditor.ChangeableDocument/Changeables/StructureMember.cs
  14. 25 0
      src/PixiEditor.ChangeableDocument/Changes/Change.cs
  15. 3 4
      src/PixiEditor.ChangeableDocument/Changes/Drawing/ApplyLayerMask_Change.cs
  16. 6 6
      src/PixiEditor.ChangeableDocument/Changes/Drawing/CombineStructureMembersOnto_Change.cs
  17. 19 13
      src/PixiEditor.ChangeableDocument/Changes/Drawing/DrawingChangeHelper.cs
  18. 6 7
      src/PixiEditor.ChangeableDocument/Changes/Drawing/ShiftLayer_UpdateableChange.cs
  19. 2 2
      src/PixiEditor.ChangeableDocument/Changes/Properties/CreateStructureMemberMask_Change.cs
  20. 2 2
      src/PixiEditor.ChangeableDocument/Changes/Properties/DeleteStructureMemberMask_Change.cs
  21. 4 4
      src/PixiEditor.ChangeableDocument/Changes/Properties/LayerLockTransparency_Change.cs
  22. 1 2
      src/PixiEditor.ChangeableDocument/Changes/Properties/StructureMemberBlendMode_Change.cs
  23. 1 2
      src/PixiEditor.ChangeableDocument/Changes/Properties/StructureMemberClipToMemberBelow_Change.cs
  24. 1 2
      src/PixiEditor.ChangeableDocument/Changes/Properties/StructureMemberIsVisible_Change.cs
  25. 2 4
      src/PixiEditor.ChangeableDocument/Changes/Properties/StructureMemberMaskIsVisible_Change.cs
  26. 1 2
      src/PixiEditor.ChangeableDocument/Changes/Properties/StructureMemberName_Change.cs
  27. 1 2
      src/PixiEditor.ChangeableDocument/Changes/Properties/StructureMemberOpacity_UpdateableChange.cs
  28. 3 3
      src/PixiEditor.ChangeableDocument/Changes/Structure/CreateStructureMember_Change.cs
  29. 1 1
      src/PixiEditor.ChangeableDocument/Changes/Structure/DeleteStructureMember_Change.cs
  30. 1 1
      src/PixiEditor.ChangeableDocument/Changes/Structure/MoveStructureMember_Change.cs
  31. 1 2
      src/PixiEditorPrototype/Models/Rendering/AffectedChunkGatherer.cs

+ 20 - 1
src/ChunkyImageLib/Chunk.cs

@@ -12,10 +12,18 @@ public class Chunk : IDisposable
     /// </summary>
     public static int ChunkCounter => chunkCounter;
 
-
     private bool returned = false;
+    /// <summary>
+    /// The surface of the chunk
+    /// </summary>
     public Surface Surface { get; }
+    /// <summary>
+    /// The size of the chunk
+    /// </summary>
     public VecI PixelSize { get; }
+    /// <summary>
+    /// The resolution of the chunk
+    /// </summary>
     public ChunkResolution Resolution { get; }
     private Chunk(ChunkResolution resolution)
     {
@@ -26,6 +34,9 @@ public class Chunk : IDisposable
         Surface = new Surface(PixelSize);
     }
 
+    /// <summary>
+    /// Tries to take a chunk with the <paramref name="resolution"/> from the pool, or creates a new one
+    /// </summary>
     public static Chunk Create(ChunkResolution resolution = ChunkResolution.Full)
     {
         var chunk = ChunkPool.Instance.Get(resolution) ?? new Chunk(resolution);
@@ -34,11 +45,19 @@ public class Chunk : IDisposable
         return chunk;
     }
 
+    /// <summary>
+    /// Draw's on the <see cref="Surface"/> of the chunk
+    /// </summary>
+    /// <param name="pos">The destination for the <paramref name="surface"/></param>
+    /// <param name="paint">The paint to use while drawing</param>
     public void DrawOnSurface(SKSurface surface, VecI pos, SKPaint? paint = null)
     {
         surface.Canvas.DrawSurface(Surface.SkiaSurface, pos.X, pos.Y, paint);
     }
 
+    /// <summary>
+    /// Returns the chunk back to the pool
+    /// </summary>
     public void Dispose()
     {
         if (returned)

+ 12 - 2
src/ChunkyImageLib/ChunkPool.cs

@@ -10,6 +10,9 @@ internal class ChunkPool
 
     private static object lockObj = new();
     private static ChunkPool? instance;
+    /// <summary>
+    /// The instance of the <see cref="ChunkPool"/>
+    /// </summary>
     public static ChunkPool Instance
     {
         get
@@ -18,8 +21,7 @@ internal class ChunkPool
             {
                 lock (lockObj)
                 {
-                    if (instance is null)
-                        instance = new ChunkPool();
+                    instance ??= new ChunkPool();
                 }
             }
             return instance;
@@ -30,6 +32,11 @@ internal class ChunkPool
     private readonly ConcurrentBag<Chunk> halfChunks = new();
     private readonly ConcurrentBag<Chunk> quarterChunks = new();
     private readonly ConcurrentBag<Chunk> eighthChunks = new();
+    
+    /// <summary>
+    /// Tries to take a chunk from the pool, returns null if there's no Chunk available
+    /// </summary>
+    /// <param name="resolution">The resolution for the chunk</param>
     internal Chunk? Get(ChunkResolution resolution) => GetBag(resolution).TryTake(out Chunk? item) ? item : null;
 
     private ConcurrentBag<Chunk> GetBag(ChunkResolution resolution)
@@ -44,6 +51,9 @@ internal class ChunkPool
         };
     }
 
+    /// <summary>
+    /// Returns a chunk back to the pool
+    /// </summary>
     internal void Push(Chunk chunk)
     {
         var chunks = GetBag(chunk.Resolution);

+ 12 - 0
src/ChunkyImageLib/DataHolders/ChunkResolution.cs

@@ -3,8 +3,20 @@
 [Flags]
 public enum ChunkResolution
 {
+    /// <summary>
+    /// The full resolution of the chunk
+    /// </summary>
     Full = 1,
+    /// <summary>
+    /// Half of the chunks resolution
+    /// </summary>
     Half = 2,
+    /// <summary>
+    /// A quarter of the chunks resolution
+    /// </summary>
     Quarter = 4,
+    /// <summary>
+    /// An eighth of the chunks resolution
+    /// </summary>
     Eighth = 8
 }

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

@@ -2,6 +2,9 @@
 
 public static class ChunkResolutionEx
 {
+    /// <summary>
+    /// Returns the multiplier of the <paramref name="resolution"/>.
+    /// </summary>
     public static double Multiplier(this ChunkResolution resolution)
     {
         return resolution switch
@@ -14,6 +17,10 @@ public static class ChunkResolutionEx
         };
     }
 
+    /// <summary>
+    /// Returns the <see cref="ChunkPool.FullChunkSize"/> for the <paramref name="resolution"/>
+    /// </summary>
+    /// <seealso cref="ChunkPool"/>
     public static int PixelSize(this ChunkResolution resolution)
     {
         return resolution switch

+ 103 - 3
src/PixiEditor.ChangeableDocument/Changeables/Document.cs

@@ -1,4 +1,5 @@
-using PixiEditor.ChangeableDocument.Changeables.Interfaces;
+using System.Diagnostics.CodeAnalysis;
+using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 
 namespace PixiEditor.ChangeableDocument.Changeables;
 
@@ -7,10 +8,14 @@ internal class Document : IChangeable, IReadOnlyDocument, IDisposable
     IReadOnlyFolder IReadOnlyDocument.StructureRoot => StructureRoot;
     IReadOnlySelection IReadOnlyDocument.Selection => Selection;
     IReadOnlyStructureMember? IReadOnlyDocument.FindMember(Guid guid) => FindMember(guid);
+    bool IReadOnlyDocument.TryFindMember(Guid guid, [NotNullWhen(true)] out IReadOnlyStructureMember? member) => TryFindMember(guid, out member);
     IReadOnlyList<IReadOnlyStructureMember> IReadOnlyDocument.FindMemberPath(Guid guid) => FindMemberPath(guid);
     IReadOnlyStructureMember IReadOnlyDocument.FindMemberOrThrow(Guid guid) => FindMemberOrThrow(guid);
     (IReadOnlyStructureMember, IReadOnlyFolder) IReadOnlyDocument.FindChildAndParentOrThrow(Guid guid) => FindChildAndParentOrThrow(guid);
 
+    /// <summary>
+    /// The default size for a new document
+    /// </summary>
     public static VecI DefaultSize { get; } = new VecI(64, 64);
     internal Folder StructureRoot { get; } = new() { GuidValue = Guid.Empty };
     internal Selection Selection { get; } = new();
@@ -25,8 +30,11 @@ internal class Document : IChangeable, IReadOnlyDocument, IDisposable
         StructureRoot.Dispose();
         Selection.Dispose();
     }
-
+    
     public void ForEveryReadonlyMember(Action<IReadOnlyStructureMember> action) => ForEveryReadonlyMember(StructureRoot, action);
+    /// <summary>
+    /// Performs the specified action on each member of the document
+    /// </summary>
     public void ForEveryMember(Action<StructureMember> action) => ForEveryMember(StructureRoot, action);
 
     private void ForEveryReadonlyMember(IReadOnlyFolder folder, Action<IReadOnlyStructureMember> action)
@@ -49,14 +57,97 @@ internal class Document : IChangeable, IReadOnlyDocument, IDisposable
         }
     }
 
-    public StructureMember FindMemberOrThrow(Guid guid) => FindMember(guid) ?? throw new ArgumentException("Could not find member with guid " + guid.ToString());
+    /// <summary>
+    /// Checks if a member with the <paramref name="guid"/> exists
+    /// </summary>
+    /// <param name="guid">The <see cref="StructureMember.GuidValue"/> of the member</param>
+    /// <returns>True if the member can be found, otherwise false</returns>
+    public bool HasMember(Guid guid)
+    {
+        var list = FindMemberPath(guid);
+        return list.Count > 0;
+    }
+
+    /// <summary>
+    /// Checks if a member with the <paramref name="guid"/> exists and is of type <typeparamref name="T"/>
+    /// </summary>
+    /// <param name="guid">The <see cref="StructureMember.GuidValue"/> of the member</param>
+    /// <returns>True if the member can be found and is of type <typeparamref name="T"/>, otherwise false</returns>
+    public bool HasMember<T>(Guid guid) where T : StructureMember
+    {
+        var list = FindMemberPath(guid);
+        return list.Count > 0 && list[0] is T;
+    }
+    
+    /// <summary>
+    /// Finds the member with the <paramref name="guid"/> or throws a ArgumentException if not found
+    /// </summary>
+    /// <param name="guid">The <see cref="StructureMember.GuidValue"/> of the member</param>
+    /// <exception cref="ArgumentException">Thrown if the member could not be found</exception>
+    public StructureMember FindMemberOrThrow(Guid guid) => FindMember(guid) ?? throw new ArgumentException($"Could not find member with guid '{guid}'");
+
+    /// <summary>
+    /// Finds the member of type <typeparamref name="T"/> with the <paramref name="guid"/> or throws an exception
+    /// </summary>
+    /// <param name="guid">The <see cref="StructureMember.GuidValue"/> of the member</param>
+    /// <exception cref="ArgumentException">Thrown if the member could not be found</exception>
+    /// <exception cref="InvalidCastException">Thrown if the member is not of type <typeparamref name="T"/></exception>
+    public T FindMemberOrThrow<T>(Guid guid) where T : StructureMember => (T)FindMember(guid)!;
 
+    /// <summary>
+    /// Finds the member with the <paramref name="guid"/> or returns null if not found
+    /// </summary>
+    /// <param name="guid">The <see cref="StructureMember.GuidValue"/> of the member</param>
     public StructureMember? FindMember(Guid guid)
     {
         var list = FindMemberPath(guid);
         return list.Count > 0 ? list[0] : null;
     }
 
+    /// <summary>
+    /// Tries finding the member with the <paramref name="guid"/> and returns true if it was found
+    /// </summary>
+    /// <param name="guid">The <see cref="StructureMember.GuidValue"/> of the <paramref name="member"/></param>
+    /// <param name="member">The member</param>
+    /// <returns>True if the member could be found, otherwise false</returns>
+    public bool TryFindMember(Guid guid, [NotNullWhen(true)] out StructureMember? member)
+    {
+        var list = FindMemberPath(guid);
+        if (list.Count == 0)
+        {
+            member = null;
+            return false;
+        }
+
+        member = list[0];
+        return true;
+    }
+
+    /// <summary>
+    /// Tries finding the member with the <paramref name="guid"/> of type <typeparamref name="T"/> and returns true if it was found
+    /// </summary>
+    /// <param name="guid">The <see cref="StructureMember.GuidValue"/> of the <paramref name="member"/></param>
+    /// <param name="member">The member</param>
+    /// <typeparam name="T">The type of the <see cref="StructureMember"/></typeparam>
+    /// <returns>True if the member could be found and is of type <typeparamref name="T"/>, otherwise false</returns>
+    public bool TryFindMember<T>(Guid guid, [NotNullWhen(true)] out T? member) where T : IReadOnlyStructureMember
+    {
+        if (!TryFindMember(guid, out var structureMember) || structureMember is not T cast)
+        {
+            member = default;
+            return false;
+        }
+
+        member = cast;
+        return false;
+    }
+
+    /// <summary>
+    /// Finds a member with the <paramref name="childGuid"/>  and its parent, throws a ArgumentException if they can't be found
+    /// </summary>
+    /// <param name="childGuid">The <see cref="StructureMember.GuidValue"/> of the member</param>
+    /// <returns>A value tuple consisting of child (<see cref="ValueTuple{T, T}.Item1"/>) and parent (<see cref="ValueTuple{T, T}.Item2"/>)</returns>
+    /// <exception cref="ArgumentException">Thrown if the member and parent could not be found</exception>
     public (StructureMember, Folder) FindChildAndParentOrThrow(Guid childGuid)
     {
         var path = FindMemberPath(childGuid);
@@ -65,6 +156,11 @@ internal class Document : IChangeable, IReadOnlyDocument, IDisposable
         return (path[0], (Folder)path[1]);
     }
 
+    /// <summary>
+    /// Finds a member with the <paramref name="childGuid"/> and its parent
+    /// </summary>
+    /// <param name="childGuid">The <see cref="StructureMember.GuidValue"/> of the member</param>
+    /// <returns>A value tuple consisting of child (<see cref="ValueTuple{T, T}.Item1"/>) and parent (<see cref="ValueTuple{T, T}.Item2"/>)<para>Child and parent can be null if not found!</para></returns>
     public (StructureMember?, Folder?) FindChildAndParent(Guid childGuid)
     {
         var path = FindMemberPath(childGuid);
@@ -76,6 +172,10 @@ internal class Document : IChangeable, IReadOnlyDocument, IDisposable
         };
     }
 
+    /// <summary>
+    /// Finds the path to the member with <paramref name="guid"/>, the first element will be the member
+    /// </summary>
+    /// <param name="guid">The <see cref="StructureMember.GuidValue"/> of the member</param>
     public List<StructureMember> FindMemberPath(Guid guid)
     {
         var list = new List<StructureMember>();

+ 12 - 3
src/PixiEditor.ChangeableDocument/Changeables/Folder.cs

@@ -6,9 +6,15 @@ namespace PixiEditor.ChangeableDocument.Changeables;
 internal class Folder : StructureMember, IReadOnlyFolder
 {
     // Don't forget to update CreateFolder_ChangeInfo, DocumentUpdater.ProcessCreateStructureMember, and Folder.Clone when adding new properties
+    /// <summary>
+    /// The children of the folder
+    /// </summary>
     public ImmutableList<StructureMember> Children { get; set; } = ImmutableList<StructureMember>.Empty;
     IReadOnlyList<IReadOnlyStructureMember> IReadOnlyFolder.Children => Children;
 
+    /// <summary>
+    /// Creates a clone of the folder, its mask and all of its children
+    /// </summary>
     internal override Folder Clone()
     {
         var builder = ImmutableList<StructureMember>.Empty.ToBuilder();
@@ -18,7 +24,7 @@ internal class Folder : StructureMember, IReadOnlyFolder
             builder.Add(child.Clone());
         }
 
-        return new Folder()
+        return new Folder
         {
             GuidValue = GuidValue,
             IsVisible = IsVisible,
@@ -32,11 +38,14 @@ internal class Folder : StructureMember, IReadOnlyFolder
         };
     }
 
+    /// <summary>
+    /// Disposes all children and the mask
+    /// </summary>
     public override void Dispose()
     {
-        for (var i = 0; i < Children.Count; i++)
+        foreach (var child in Children)
         {
-            Children[i].Dispose();
+            child.Dispose();
         }
         Mask?.Dispose();
     }

+ 61 - 3
src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyDocument.cs

@@ -1,17 +1,75 @@
-namespace PixiEditor.ChangeableDocument.Changeables.Interfaces;
+using System.Diagnostics.CodeAnalysis;
+
+namespace PixiEditor.ChangeableDocument.Changeables.Interfaces;
 
 public interface IReadOnlyDocument
-{
+{    /// <summary>
+    /// The root folder of the document
+    /// </summary>
     IReadOnlyFolder StructureRoot { get; }
+    /// <summary>
+    /// The selection of the document
+    /// </summary>
     IReadOnlySelection Selection { get; }
+    /// <summary>
+    /// The size of the document
+    /// </summary>
     VecI Size { get; }
+    /// <summary>
+    /// Is the horizontal symmetry axis enabled (Mirrors top and bottom)
+    /// </summary>
     bool HorizontalSymmetryAxisEnabled { get; }
+    /// <summary>
+    /// Is the vertical symmetry axis enabled (Mirrors left and right)
+    /// </summary>
     bool VerticalSymmetryAxisEnabled { get; }
+    /// <summary>
+    /// The position of the horizontal symmetry axis (Mirrors top and bottom)
+    /// </summary>
     int HorizontalSymmetryAxisY { get; }
+    /// <summary>
+    /// The position of the vertical symmetry axis (Mirrors left and right)
+    /// </summary>
     int VerticalSymmetryAxisX { get; }
+    /// <summary>
+    /// Performs the specified action on each readonly member of the document
+    /// </summary>
     void ForEveryReadonlyMember(Action<IReadOnlyStructureMember> action);
+    /// <summary>
+    /// Finds the member with the <paramref name="guid"/> or returns null if not found
+    /// </summary>
+    /// <param name="guid">The <see cref="IReadOnlyStructureMember.GuidValue"/> of the member</param>
     IReadOnlyStructureMember? FindMember(Guid guid);
+    /// <summary>
+    /// Tries finding the member with the <paramref name="guid"/> of type <typeparamref name="T"/> and returns true if it was found
+    /// </summary>
+    /// <param name="guid">The <see cref="StructureMember.GuidValue"/> of the <paramref name="member"/></param>
+    /// <param name="member">The member</param>
+    /// <returns>True if the member could be found, otherwise false</returns>
+    bool TryFindMember<T>(Guid guid, [NotNullWhen(true)] out T? member) where T : IReadOnlyStructureMember;
+    /// <summary>
+    /// Tries finding the member with the <paramref name="guid"/> and returns true if it was found
+    /// </summary>
+    /// <param name="guid">The <see cref="StructureMember.GuidValue"/> of the <paramref name="member"/></param>
+    /// <param name="member">The member</param>
+    /// <returns>True if the member could be found, otherwise false</returns>
+    bool TryFindMember(Guid guid, [NotNullWhen(true)] out IReadOnlyStructureMember? member);
+    /// <summary>
+    /// Finds the member with the <paramref name="guid"/> or throws a ArgumentException if not found
+    /// </summary>
+    /// <param name="guid">The <see cref="StructureMember.GuidValue"/> of the member</param>
+    /// <exception cref="ArgumentException">Thrown if the member could not be found</exception>
     IReadOnlyStructureMember FindMemberOrThrow(Guid guid);
-    (IReadOnlyStructureMember, IReadOnlyFolder) FindChildAndParentOrThrow(Guid guid);
+    /// <summary>
+    /// Finds a member with the <paramref name="childGuid"/>  and its parent, throws a ArgumentException if they can't be found
+    /// </summary>
+    /// <param name="childGuid">The <see cref="IReadOnlyStructureMember.GuidValue"/> of the member</param>
+    /// <returns>A value tuple consisting of child (<see cref="ValueTuple{T, T}.Item1"/>) and parent (<see cref="ValueTuple{T, T}.Item2"/>)</returns>
+    /// <exception cref="ArgumentException">Thrown if the member and parent could not be found</exception>
+    (IReadOnlyStructureMember, IReadOnlyFolder) FindChildAndParentOrThrow(Guid childGuid);
+    /// <summary>
+    /// Finds the path to the member with <paramref name="guid"/>, the first element will be the member
+    /// </summary>
+    /// <param name="guid">The <see cref="IReadOnlyStructureMember.GuidValue"/> of the member</param>
     IReadOnlyList<IReadOnlyStructureMember> FindMemberPath(Guid guid);
 }

+ 3 - 0
src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyFolder.cs

@@ -2,5 +2,8 @@
 
 public interface IReadOnlyFolder : IReadOnlyStructureMember
 {
+    /// <summary>
+    /// The children of the folder
+    /// </summary>
     IReadOnlyList<IReadOnlyStructureMember> Children { get; }
 }

+ 6 - 0
src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyLayer.cs

@@ -2,6 +2,12 @@
 
 public interface IReadOnlyLayer : IReadOnlyStructureMember
 {
+    /// <summary>
+    /// The chunky image of the layer
+    /// </summary>
     IReadOnlyChunkyImage LayerImage { get; }
+    /// <summary>
+    /// Locks the transparency of the layer
+    /// </summary>
     bool LockTransparency { get; }
 }

+ 3 - 0
src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlySelection.cs

@@ -4,5 +4,8 @@ namespace PixiEditor.ChangeableDocument.Changeables.Interfaces;
 
 public interface IReadOnlySelection
 {
+    /// <summary>
+    /// The path of the selection
+    /// </summary>
     public SKPath SelectionPath { get; }
 }

+ 23 - 2
src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyStructureMember.cs

@@ -4,12 +4,33 @@ namespace PixiEditor.ChangeableDocument.Changeables.Interfaces;
 
 public interface IReadOnlyStructureMember
 {
+    /// <summary>
+    /// Is the member visible. Defaults to true
+    /// </summary>
     bool IsVisible { get; }
-    bool MaskIsVisible { get; }
+    /// <summary>
+    /// The opacity of the member (Ranging from 0f to 1f)
+    /// </summary>
+    float Opacity { get; }
     bool ClipToMemberBelow { get; }
+    /// <summary>
+    /// The name of the member. Defaults to "Unnamed"
+    /// </summary>
     string Name { get; }
+    /// <summary>
+    /// The guid of the member
+    /// </summary>
     Guid GuidValue { get; }
-    float Opacity { get; }
+    /// <summary>
+    /// The blend mode of the member
+    /// </summary>
     BlendMode BlendMode { get; }
+    /// <summary>
+    /// Is the mask of the member visible. Defaults to true
+    /// </summary>
+    bool MaskIsVisible { get; }
+    /// <summary>
+    /// The mask of the member
+    /// </summary>
     IReadOnlyChunkyImage? Mask { get; }
 }

+ 7 - 0
src/PixiEditor.ChangeableDocument/Changeables/Layer.cs

@@ -19,12 +19,19 @@ internal class Layer : StructureMember, IReadOnlyLayer
         LayerImage = image;
     }
 
+    /// <summary>
+    /// Disposes the layer's image and mask
+    /// </summary>
     public override void Dispose()
     {
         LayerImage.Dispose();
         Mask?.Dispose();
     }
 
+    /// <summary>
+    /// Creates a clone of the layer, its image and its mask
+    /// </summary>
+    /// <returns></returns>
     internal override Layer Clone()
     {
         return new Layer(LayerImage.CloneFromCommitted())

+ 0 - 1
src/PixiEditor.ChangeableDocument/Changeables/StructureMember.cs

@@ -16,7 +16,6 @@ internal abstract class StructureMember : IChangeable, IReadOnlyStructureMember,
     public ChunkyImage? Mask { get; set; } = null;
     public bool MaskIsVisible { get; set; } = true;
     IReadOnlyChunkyImage? IReadOnlyStructureMember.Mask => Mask;
-
     internal abstract StructureMember Clone();
     public abstract void Dispose();
 }

+ 25 - 0
src/PixiEditor.ChangeableDocument/Changes/Change.cs

@@ -2,9 +2,34 @@
 
 internal abstract class Change : IDisposable
 {
+    /// <summary>
+    /// Checks if this change can be combined with the <paramref name="other"/> change. Returns false if not overridden
+    /// </summary>
+    /// <param name="other"></param>
+    /// <returns></returns>
     public virtual bool IsMergeableWith(Change other) => false;
+
+    /// <summary>
+    /// Initializes and validates the changes parameter. Called before Apply and ApplyTemporarily
+    /// </summary>
+    /// <param name="target">The document the change will be applied on</param>
+    /// <returns><see cref="Success"/> if all parameters are valid, otherwise <see cref="Error"/></returns>
     public abstract OneOf<Success, Error> InitializeAndValidate(Document target);
+
+    /// <summary>
+    /// Applies the change to the <paramref name="target"/>
+    /// </summary>
+    /// <param name="target">The document to apply the change to</param>
+    /// <param name="firstApply">True if this is the first time Apply was called</param>
+    /// <param name="ignoreInUndo">Should this change be undoable using <see cref="Revert"/></param>
+    /// <returns>Either nothing, a single change info, or a collection of change infos</returns>
     public abstract OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply, out bool ignoreInUndo);
+
+    /// <summary>
+    /// Reverts the <paramref name="target"/> to the state before <see cref="Apply"/> was called. Not executed if ignoreInUndo was set in <see cref="Apply"/>
+    /// </summary>
+    /// <returns>Either nothing, a single change info, or a collection of change infos</returns>
     public abstract OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target);
+
     public virtual void Dispose() { }
 };

+ 3 - 4
src/PixiEditor.ChangeableDocument/Changes/Drawing/ApplyLayerMask_Change.cs

@@ -15,8 +15,7 @@ internal class ApplyLayerMask_Change : Change
 
     public override OneOf<Success, Error> InitializeAndValidate(Document target)
     {
-        var member = target.FindMember(layerGuid);
-        if (member is not Layer layer || layer.Mask is null)
+        if (!target.TryFindMember<Layer>(layerGuid, out var layer) || layer.Mask is null)
             return new Error();
 
         savedLayer = new CommittedChunkStorage(layer.LayerImage, layer.LayerImage.FindCommittedChunks());
@@ -26,7 +25,7 @@ internal class ApplyLayerMask_Change : Change
 
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply, out bool ignoreInUndo)
     {
-        var layer = (Layer)target.FindMemberOrThrow(layerGuid);
+        var layer = target.FindMemberOrThrow<Layer>(layerGuid);
         if (layer.Mask is null)
             throw new InvalidOperationException("Cannot apply layer mask, no mask");
 
@@ -55,7 +54,7 @@ internal class ApplyLayerMask_Change : Change
 
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
     {
-        var layer = (Layer)target.FindMemberOrThrow(layerGuid);
+        var layer = target.FindMemberOrThrow<Layer>(layerGuid);
         if (layer.Mask is not null)
             throw new InvalidOperationException("Cannot restore layer mask, it already has one");
         if (savedLayer is null || savedMask is null)

+ 6 - 6
src/PixiEditor.ChangeableDocument/Changes/Drawing/CombineStructureMembersOnto_Change.cs

@@ -20,13 +20,13 @@ internal class CombineStructureMembersOnto_Change : Change
 
     public override OneOf<Success, Error> InitializeAndValidate(Document target)
     {
-        if (target.FindMember(targetLayer) is null || membersToMerge.Count == 0)
+        if (!target.HasMember(targetLayer) || membersToMerge.Count == 0)
             return new Error();
         foreach (Guid guid in membersToMerge)
         {
-            var member = target.FindMember(guid);
-            if (member is null)
+            if (!target.TryFindMember(guid, out var member))
                 return new Error();
+            
             if (member is Layer layer)
                 layersToCombine.Add(layer.GuidValue);
             else if (member is Folder innerFolder)
@@ -48,12 +48,12 @@ internal class CombineStructureMembersOnto_Change : Change
 
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply, out bool ignoreInUndo)
     {
-        Layer toDrawOn = (Layer)target.FindMemberOrThrow(targetLayer);
+        var toDrawOn = target.FindMemberOrThrow<Layer>(targetLayer);
 
         var chunksToCombine = new HashSet<VecI>();
         foreach (var guid in layersToCombine)
         {
-            var layer = (Layer)target.FindMemberOrThrow(guid);
+            var layer = target.FindMemberOrThrow<Layer>(guid);
             chunksToCombine.UnionWith(layer.LayerImage.FindAllChunks());
         }
 
@@ -77,7 +77,7 @@ internal class CombineStructureMembersOnto_Change : Change
 
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
     {
-        Layer toDrawOn = (Layer)target.FindMemberOrThrow(targetLayer);
+        var toDrawOn = target.FindMemberOrThrow<Layer>(targetLayer);
         var affectedChunks = DrawingChangeHelper.ApplyStoredChunksDisposeAndSetToNull(toDrawOn.LayerImage, ref originalChunks);
         return new LayerImageChunks_ChangeInfo(targetLayer, affectedChunks);
     }

+ 19 - 13
src/PixiEditor.ChangeableDocument/Changes/Drawing/DrawingChangeHelper.cs

@@ -22,16 +22,19 @@ internal static class DrawingChangeHelper
     public static ChunkyImage GetTargetImageOrThrow(Document target, Guid memberGuid, bool drawOnMask)
     {
         var member = target.FindMemberOrThrow(memberGuid);
+        
         if (drawOnMask)
         {
             if (member.Mask is null)
                 throw new InvalidOperationException("Trying to draw on a mask that doesn't exist");
             return member.Mask;
         }
-        else if (member is Folder)
+        
+        if (member is Folder)
         {
             throw new InvalidOperationException("Trying to draw on a folder");
         }
+        
         return ((Layer)member).LayerImage;
     }
 
@@ -41,7 +44,7 @@ internal static class DrawingChangeHelper
             targetImage.SetClippingPath(target.Selection.SelectionPath);
 
         var targetMember = target.FindMemberOrThrow(targetMemberGuid);
-        if (targetMember is Layer layer && layer.LockTransparency && !drawOnMask)
+        if (targetMember is Layer { LockTransparency: true } && !drawOnMask)
             targetImage.EnableLockTransparency();
 
         if (target.HorizontalSymmetryAxisEnabled)
@@ -52,22 +55,25 @@ internal static class DrawingChangeHelper
 
     public static bool IsValidForDrawing(Document target, Guid memberGuid, bool drawOnMask)
     {
-        var member = target.FindMember(memberGuid);
-        if (member is null)
-            return false;
-        if (drawOnMask && member.Mask is null)
-            return false;
-        if (!drawOnMask && member is Folder)
+        if (!target.TryFindMember(memberGuid, out var member))
+        {
             return false;
-        return true;
-    }
+        }
 
-    public static OneOf<None, IChangeInfo, List<IChangeInfo>> CreateChunkChangeInfo(Guid memberGuid, HashSet<VecI> affectedChunks, bool drawOnMask)
-    {
         return drawOnMask switch
+        {
+            // If it should draw on the mask, the mask can't be null
+            true when member.Mask is null => false,
+            // If it should not draw on the mask, the member can't be a folder
+            false when member is Folder => false,
+            _ => true
+        };
+    }
+
+    public static OneOf<None, IChangeInfo, List<IChangeInfo>> CreateChunkChangeInfo(Guid memberGuid, HashSet<VecI> affectedChunks, bool drawOnMask) =>
+        drawOnMask switch
         {
             false => new LayerImageChunks_ChangeInfo(memberGuid, affectedChunks),
             true => new MaskChunks_ChangeInfo(memberGuid, affectedChunks),
         };
-    }
 }

+ 6 - 7
src/PixiEditor.ChangeableDocument/Changes/Drawing/ShiftLayer_UpdateableChange.cs

@@ -14,10 +14,9 @@ internal class ShiftLayer_UpdateableChange : UpdateableChange
 
     public override OneOf<Success, Error> InitializeAndValidate(Document target)
     {
-        var member = target.FindMember(layerGuid);
-        if (member is not Layer)
-            return new Error();
-        return new Success();
+        if (target.HasMember<Layer>(layerGuid))
+            return new Success();
+        return new Error();
     }
 
     [UpdateChangeMethod]
@@ -28,7 +27,7 @@ internal class ShiftLayer_UpdateableChange : UpdateableChange
 
     private HashSet<VecI> DrawShiftedLayer(Document target)
     {
-        var targetImage = ((Layer)target.FindMemberOrThrow(layerGuid)).LayerImage;
+        var targetImage = target.FindMemberOrThrow<Layer>(layerGuid).LayerImage;
         var prevChunks = targetImage.FindAffectedChunks();
         targetImage.CancelChanges();
         targetImage.EnqueueClear();
@@ -41,7 +40,7 @@ internal class ShiftLayer_UpdateableChange : UpdateableChange
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply, out bool ignoreInUndo)
     {
         var chunks = DrawShiftedLayer(target);
-        var image = ((Layer)target.FindMemberOrThrow(layerGuid)).LayerImage;
+        var image = target.FindMemberOrThrow<Layer>(layerGuid).LayerImage;
 
         if (originalLayerChunks is not null)
             throw new InvalidOperationException("saved chunks is not null even though we are trying to save them again");
@@ -60,7 +59,7 @@ internal class ShiftLayer_UpdateableChange : UpdateableChange
 
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
     {
-        var image = ((Layer)target.FindMemberOrThrow(layerGuid)).LayerImage;
+        var image = target.FindMemberOrThrow<Layer>(layerGuid).LayerImage;
         var affected = DrawingChangeHelper.ApplyStoredChunksDisposeAndSetToNull(image, ref originalLayerChunks);
         return new LayerImageChunks_ChangeInfo(layerGuid, affected);
     }

+ 2 - 2
src/PixiEditor.ChangeableDocument/Changes/Properties/CreateStructureMemberMask_Change.cs

@@ -14,9 +14,9 @@ internal class CreateStructureMemberMask_Change : Change
 
     public override OneOf<Success, Error> InitializeAndValidate(Document target)
     {
-        var member = target.FindMember(targetMember);
-        if (member is null || member.Mask is not null)
+        if (!target.TryFindMember(targetMember, out var member) || member.Mask is not null)
             return new Error();
+        
         return new Success();
     }
 

+ 2 - 2
src/PixiEditor.ChangeableDocument/Changes/Properties/DeleteStructureMemberMask_Change.cs

@@ -15,9 +15,9 @@ internal class DeleteStructureMemberMask_Change : Change
 
     public override OneOf<Success, Error> InitializeAndValidate(Document target)
     {
-        var member = target.FindMember(memberGuid);
-        if (member is null || member.Mask is null)
+        if (!target.TryFindMember(memberGuid, out var member) || member.Mask is null)
             return new Error();
+        
         storedMask = member.Mask.CloneFromCommitted();
         return new Success();
     }

+ 4 - 4
src/PixiEditor.ChangeableDocument/Changes/Properties/LayerLockTransparency_Change.cs

@@ -16,9 +16,9 @@ internal class LayerLockTransparency_Change : Change
 
     public override OneOf<Success, Error> InitializeAndValidate(Document target)
     {
-        var member = target.FindMember(layerGuid);
-        if (member is not Layer layer)
+        if (!target.TryFindMember<Layer>(layerGuid, out var layer))
             return new Error();
+
         originalValue = layer.LockTransparency;
         if (originalValue == newValue)
             return new Error();
@@ -27,14 +27,14 @@ internal class LayerLockTransparency_Change : Change
 
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply, out bool ignoreInUndo)
     {
-        ((Layer)target.FindMemberOrThrow(layerGuid)).LockTransparency = newValue;
+        (target.FindMemberOrThrow<Layer>(layerGuid)).LockTransparency = newValue;
         ignoreInUndo = false;
         return new LayerLockTransparency_ChangeInfo(layerGuid, newValue);
     }
 
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
     {
-        ((Layer)target.FindMemberOrThrow(layerGuid)).LockTransparency = originalValue;
+        (target.FindMemberOrThrow<Layer>(layerGuid)).LockTransparency = originalValue;
         return new LayerLockTransparency_ChangeInfo(layerGuid, originalValue);
     }
 

+ 1 - 2
src/PixiEditor.ChangeableDocument/Changes/Properties/StructureMemberBlendMode_Change.cs

@@ -17,8 +17,7 @@ internal class StructureMemberBlendMode_Change : Change
 
     public override OneOf<Success, Error> InitializeAndValidate(Document target)
     {
-        var member = target.FindMember(targetGuid);
-        if (member is null || member.BlendMode == newBlendMode)
+        if (!target.TryFindMember(targetGuid, out var member) || member.BlendMode == newBlendMode)
             return new Error();
         originalBlendMode = member.BlendMode;
         return new Success();

+ 1 - 2
src/PixiEditor.ChangeableDocument/Changes/Properties/StructureMemberClipToMemberBelow_Change.cs

@@ -16,8 +16,7 @@ internal class StructureMemberClipToMemberBelow_Change : Change
 
     public override OneOf<Success, Error> InitializeAndValidate(Document target)
     {
-        var member = target.FindMember(memberGuid);
-        if (member is null || member.ClipToMemberBelow == newValue)
+        if (!target.TryFindMember(memberGuid, out var member) || member.ClipToMemberBelow == newValue)
             return new Error();
         originalValue = member.ClipToMemberBelow;
         return new Success();

+ 1 - 2
src/PixiEditor.ChangeableDocument/Changes/Properties/StructureMemberIsVisible_Change.cs

@@ -16,8 +16,7 @@ internal class StructureMemberIsVisible_Change : Change
 
     public override OneOf<Success, Error> InitializeAndValidate(Document target)
     {
-        var member = target.FindMember(targetMember);
-        if (member is null || member.IsVisible == newIsVisible)
+        if (!target.TryFindMember(targetMember, out var member) || member.IsVisible == newIsVisible)
             return new Error();
         originalIsVisible = member.IsVisible;
         return new Success();

+ 2 - 4
src/PixiEditor.ChangeableDocument/Changes/Properties/StructureMemberMaskIsVisible_Change.cs

@@ -16,11 +16,9 @@ internal class StructureMemberMaskIsVisible_Change : Change
 
     public override OneOf<Success, Error> InitializeAndValidate(Document target)
     {
-        var member = target.FindMember(memberGuid);
-        if (member is null)
-            return new Error();
-        if (member.MaskIsVisible == newMaskIsVisible)
+        if (!target.TryFindMember(memberGuid, out var member) || member.MaskIsVisible == newMaskIsVisible)
             return new Error();
+        
         originalMaskIsVisible = member.MaskIsVisible;
         return new Success();
     }

+ 1 - 2
src/PixiEditor.ChangeableDocument/Changes/Properties/StructureMemberName_Change.cs

@@ -17,8 +17,7 @@ internal class StructureMemberName_Change : Change
 
     public override OneOf<Success, Error> InitializeAndValidate(Document target)
     {
-        var member = target.FindMember(targetMember);
-        if (member is null || member.Name == newName)
+        if (!target.TryFindMember(targetMember, out var member) || member.Name == newName)
             return new Error();
         originalName = member.Name;
         return new Success();

+ 1 - 2
src/PixiEditor.ChangeableDocument/Changes/Properties/StructureMemberOpacity_UpdateableChange.cs

@@ -24,8 +24,7 @@ internal class StructureMemberOpacity_UpdateableChange : UpdateableChange
 
     public override OneOf<Success, Error> InitializeAndValidate(Document document)
     {
-        var member = document.FindMember(memberGuid);
-        if (member is null)
+        if (!document.TryFindMember(memberGuid, out var member))
             return new Error();
         originalOpacity = member.Opacity;
         return new Success();

+ 3 - 3
src/PixiEditor.ChangeableDocument/Changes/Structure/CreateStructureMember_Change.cs

@@ -22,14 +22,14 @@ internal class CreateStructureMember_Change : Change
 
     public override OneOf<Success, Error> InitializeAndValidate(Document target)
     {
-        if (target.FindMember(parentFolderGuid) is null)
+        if (!target.HasMember(parentFolderGuid))
             return new Error();
         return new Success();
     }
 
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document document, bool firstApply, out bool ignoreInUndo)
     {
-        var folder = (Folder)document.FindMemberOrThrow(parentFolderGuid);
+        var folder = document.FindMemberOrThrow<Folder>(parentFolderGuid);
 
         StructureMember member = type switch
         {
@@ -51,7 +51,7 @@ internal class CreateStructureMember_Change : Change
 
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document document)
     {
-        Folder folder = (Folder)document.FindMemberOrThrow(parentFolderGuid);
+        Folder folder = document.FindMemberOrThrow<Folder>(parentFolderGuid);
         StructureMember child = document.FindMemberOrThrow(newMemberGuid);
         child.Dispose();
         folder.Children = folder.Children.RemoveAt(folder.Children.FindIndex(member => member.GuidValue == newMemberGuid));

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changes/Structure/DeleteStructureMember_Change.cs

@@ -38,7 +38,7 @@ internal class DeleteStructureMember_Change : Change
 
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document doc)
     {
-        var parent = (Folder)doc.FindMemberOrThrow(parentGuid);
+        var parent = doc.FindMemberOrThrow<Folder>(parentGuid);
 
         var copy = savedCopy!.Clone();
         parent.Children = parent.Children.Insert(originalIndex, copy);

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changes/Structure/MoveStructureMember_Change.cs

@@ -33,7 +33,7 @@ internal class MoveStructureMember_Change : Change
 
     private static void Move(Document document, Guid memberGuid, Guid targetFolderGuid, int targetIndex)
     {
-        var targetFolder = (Folder)document.FindMemberOrThrow(targetFolderGuid);
+        var targetFolder = document.FindMemberOrThrow<Folder>(targetFolderGuid);
         var (member, curFolder) = document.FindChildAndParentOrThrow(memberGuid);
 
         curFolder.Children = curFolder.Children.Remove(member);

+ 1 - 2
src/PixiEditorPrototype/Models/Rendering/AffectedChunkGatherer.cs

@@ -128,8 +128,7 @@ internal class AffectedChunkGatherer
 
     private void AddAllToMaskPreview(Guid memberGuid)
     {
-        var member = tracker.Document.FindMember(memberGuid);
-        if (member is null)
+        if (!tracker.Document.TryFindMember(memberGuid, out var member))
             return;
         if (member.Mask is not null)
         {