Browse Source

Auto-merge similar changes

Equbuxu 3 years ago
parent
commit
37c48dd2d6
32 changed files with 145 additions and 119 deletions
  1. 1 1
      src/ChangeableDocument/Actions/Document/ResizeCanvas_Action.cs
  2. 1 1
      src/ChangeableDocument/Actions/Drawing/CombineStructureMembersOnto_Action.cs
  3. 2 2
      src/ChangeableDocument/Actions/Drawing/Rectangle/DrawRectangle_Action.cs
  4. 1 1
      src/ChangeableDocument/Actions/Drawing/Rectangle/EndDrawRectangle_Action.cs
  5. 1 1
      src/ChangeableDocument/Actions/Drawing/Selection/ClearSelection_Action.cs
  6. 1 1
      src/ChangeableDocument/Actions/Drawing/Selection/EndSelectRectangle_Action.cs
  7. 2 2
      src/ChangeableDocument/Actions/Drawing/Selection/SelectRectangle_Action.cs
  8. 1 1
      src/ChangeableDocument/Actions/IEndChangeAction.cs
  9. 1 1
      src/ChangeableDocument/Actions/IMakeChangeAction.cs
  10. 2 2
      src/ChangeableDocument/Actions/IStartOrUpdateChangeAction.cs
  11. 1 1
      src/ChangeableDocument/Actions/Properties/EndOpacityChange_Action.cs
  12. 2 2
      src/ChangeableDocument/Actions/Properties/OpacityChange_Action.cs
  13. 1 1
      src/ChangeableDocument/Actions/Properties/SetStructureMemberName_Action.cs
  14. 2 2
      src/ChangeableDocument/Actions/Properties/SetStructureMemberVisibility_Action.cs
  15. 1 1
      src/ChangeableDocument/Actions/Structure/CreateStructureMember_Action.cs
  16. 1 1
      src/ChangeableDocument/Actions/Structure/DeleteStructureMember_Action.cs
  17. 1 1
      src/ChangeableDocument/Actions/Structure/MoveStructureMember_Action.cs
  18. 14 0
      src/ChangeableDocument/Changes/Change.cs
  19. 4 6
      src/ChangeableDocument/Changes/CreateStructureMember_Change.cs
  20. 5 5
      src/ChangeableDocument/Changes/DeleteStructureMember_Change.cs
  21. 5 5
      src/ChangeableDocument/Changes/Drawing/ClearSelection_Change.cs
  22. 5 5
      src/ChangeableDocument/Changes/Drawing/CombineStructureMembersOnto_Change.cs
  23. 6 6
      src/ChangeableDocument/Changes/Drawing/DrawRectangle_UpdateableChange.cs
  24. 6 6
      src/ChangeableDocument/Changes/Drawing/SelectRectangle_UpdateableChange.cs
  25. 0 12
      src/ChangeableDocument/Changes/IChange.cs
  26. 4 6
      src/ChangeableDocument/Changes/MoveStructureMember_Change.cs
  27. 5 5
      src/ChangeableDocument/Changes/ResizeCanvas_Change.cs
  28. 5 7
      src/ChangeableDocument/Changes/StructureMemberIsVisible_Change.cs
  29. 10 5
      src/ChangeableDocument/Changes/StructureMemberName_Change.cs
  30. 11 6
      src/ChangeableDocument/Changes/StructureMemberOpacity_UpdateableChange.cs
  31. 2 2
      src/ChangeableDocument/Changes/UpdateableChange.cs
  32. 41 21
      src/ChangeableDocument/DocumentChangeTracker.cs

+ 1 - 1
src/ChangeableDocument/Actions/Document/ResizeCanvas_Action.cs

@@ -10,7 +10,7 @@ namespace ChangeableDocument.Actions.Document
         {
             Size = size;
         }
-        IChange IMakeChangeAction.CreateCorrespondingChange()
+        Change IMakeChangeAction.CreateCorrespondingChange()
         {
             return new ResizeCanvas_Change(Size);
         }

+ 1 - 1
src/ChangeableDocument/Actions/Drawing/CombineStructureMembersOnto_Action.cs

@@ -13,7 +13,7 @@ namespace ChangeableDocument.Actions.Drawing
 
         public Guid TargetLayer { get; }
         public HashSet<Guid> MembersToCombine { get; }
-        IChange IMakeChangeAction.CreateCorrespondingChange()
+        Change IMakeChangeAction.CreateCorrespondingChange()
         {
             return new CombineStructureMembersOnto_Change(MembersToCombine, TargetLayer);
         }

+ 2 - 2
src/ChangeableDocument/Actions/Drawing/Rectangle/DrawRectangle_Action.cs

@@ -15,12 +15,12 @@ namespace ChangeableDocument.Actions.Drawing.Rectangle
         public Guid LayerGuid { get; }
         public ShapeData Rectangle { get; }
 
-        void IStartOrUpdateChangeAction.UpdateCorrespodingChange(IUpdateableChange change)
+        void IStartOrUpdateChangeAction.UpdateCorrespodingChange(UpdateableChange change)
         {
             ((DrawRectangle_UpdateableChange)change).Update(Rectangle);
         }
 
-        IUpdateableChange IStartOrUpdateChangeAction.CreateCorrespondingChange()
+        UpdateableChange IStartOrUpdateChangeAction.CreateCorrespondingChange()
         {
             return new DrawRectangle_UpdateableChange(LayerGuid, Rectangle);
         }

+ 1 - 1
src/ChangeableDocument/Actions/Drawing/Rectangle/EndDrawRectangle_Action.cs

@@ -5,7 +5,7 @@ namespace ChangeableDocument.Actions.Drawing.Rectangle
 {
     public record class EndDrawRectangle_Action : IEndChangeAction
     {
-        bool IEndChangeAction.IsChangeTypeMatching(IChange change)
+        bool IEndChangeAction.IsChangeTypeMatching(Change change)
         {
             return change is DrawRectangle_UpdateableChange;
         }

+ 1 - 1
src/ChangeableDocument/Actions/Drawing/Selection/ClearSelection_Action.cs

@@ -5,7 +5,7 @@ namespace ChangeableDocument.Actions.Drawing.Selection
 {
     public record class ClearSelection_Action : IMakeChangeAction
     {
-        IChange IMakeChangeAction.CreateCorrespondingChange()
+        Change IMakeChangeAction.CreateCorrespondingChange()
         {
             return new ClearSelection_Change();
         }

+ 1 - 1
src/ChangeableDocument/Actions/Drawing/Selection/EndSelectRectangle_Action.cs

@@ -5,7 +5,7 @@ namespace ChangeableDocument.Actions.Drawing.Selection
 {
     public record class EndSelectRectangle_Action : IEndChangeAction
     {
-        bool IEndChangeAction.IsChangeTypeMatching(IChange change)
+        bool IEndChangeAction.IsChangeTypeMatching(Change change)
         {
             return change is SelectRectangle_UpdateableChange;
         }

+ 2 - 2
src/ChangeableDocument/Actions/Drawing/Selection/SelectRectangle_Action.cs

@@ -14,12 +14,12 @@ namespace ChangeableDocument.Actions.Drawing.Selection
             Size = size;
         }
 
-        IUpdateableChange IStartOrUpdateChangeAction.CreateCorrespondingChange()
+        UpdateableChange IStartOrUpdateChangeAction.CreateCorrespondingChange()
         {
             return new SelectRectangle_UpdateableChange(Pos, Size);
         }
 
-        void IStartOrUpdateChangeAction.UpdateCorrespodingChange(IUpdateableChange change)
+        void IStartOrUpdateChangeAction.UpdateCorrespodingChange(UpdateableChange change)
         {
             ((SelectRectangle_UpdateableChange)change).Update(Pos, Size);
         }

+ 1 - 1
src/ChangeableDocument/Actions/IEndChangeAction.cs

@@ -4,6 +4,6 @@ namespace ChangeableDocument.Actions
 {
     internal interface IEndChangeAction : IAction
     {
-        bool IsChangeTypeMatching(IChange change);
+        bool IsChangeTypeMatching(Change change);
     }
 }

+ 1 - 1
src/ChangeableDocument/Actions/IMakeChangeAction.cs

@@ -4,6 +4,6 @@ namespace ChangeableDocument.Actions
 {
     internal interface IMakeChangeAction : IAction
     {
-        IChange CreateCorrespondingChange();
+        Change CreateCorrespondingChange();
     }
 }

+ 2 - 2
src/ChangeableDocument/Actions/IStartOrUpdateChangeAction.cs

@@ -4,7 +4,7 @@ namespace ChangeableDocument.Actions
 {
     internal interface IStartOrUpdateChangeAction : IAction
     {
-        void UpdateCorrespodingChange(IUpdateableChange change);
-        IUpdateableChange CreateCorrespondingChange();
+        void UpdateCorrespodingChange(UpdateableChange change);
+        UpdateableChange CreateCorrespondingChange();
     }
 }

+ 1 - 1
src/ChangeableDocument/Actions/Properties/EndOpacityChange_Action.cs

@@ -4,6 +4,6 @@ namespace ChangeableDocument.Actions.Properties
 {
     public record class EndOpacityChange_Action : IEndChangeAction
     {
-        bool IEndChangeAction.IsChangeTypeMatching(IChange change) => change is StructureMemberOpacity_UpdateableChange;
+        bool IEndChangeAction.IsChangeTypeMatching(Change change) => change is StructureMemberOpacity_UpdateableChange;
     }
 }

+ 2 - 2
src/ChangeableDocument/Actions/Properties/OpacityChange_Action.cs

@@ -13,12 +13,12 @@ namespace ChangeableDocument.Actions.Properties
         public Guid MemberGuid { get; }
         public float Opacity { get; }
 
-        IUpdateableChange IStartOrUpdateChangeAction.CreateCorrespondingChange()
+        UpdateableChange IStartOrUpdateChangeAction.CreateCorrespondingChange()
         {
             return new StructureMemberOpacity_UpdateableChange(MemberGuid, Opacity);
         }
 
-        void IStartOrUpdateChangeAction.UpdateCorrespodingChange(IUpdateableChange change)
+        void IStartOrUpdateChangeAction.UpdateCorrespodingChange(UpdateableChange change)
         {
             ((StructureMemberOpacity_UpdateableChange)change).Update(Opacity);
         }

+ 1 - 1
src/ChangeableDocument/Actions/Properties/SetStructureMemberName_Action.cs

@@ -13,7 +13,7 @@ public record class SetStructureMemberName_Action : IMakeChangeAction
     public string Name { get; init; }
     public Guid GuidValue { get; init; }
 
-    IChange IMakeChangeAction.CreateCorrespondingChange()
+    Change IMakeChangeAction.CreateCorrespondingChange()
     {
         return new StructureMemberName_Change(GuidValue, Name);
     }

+ 2 - 2
src/ChangeableDocument/Actions/Properties/SetStructureMemberVisibility_Action.cs

@@ -13,8 +13,8 @@ public record class SetStructureMemberVisibility_Action : IMakeChangeAction
     public bool isVisible { get; init; }
     public Guid GuidValue { get; init; }
 
-    IChange IMakeChangeAction.CreateCorrespondingChange()
+    Change IMakeChangeAction.CreateCorrespondingChange()
     {
-        return new StructureMemberVisibility_Change(GuidValue, isVisible);
+        return new StructureMemberIsVisible_Change(GuidValue, isVisible);
     }
 }

+ 1 - 1
src/ChangeableDocument/Actions/Structure/CreateStructureMember_Action.cs

@@ -15,7 +15,7 @@ public record class CreateStructureMember_Action : IMakeChangeAction
     public int Index { get; init; }
     public StructureMemberType Type { get; init; }
 
-    IChange IMakeChangeAction.CreateCorrespondingChange()
+    Change IMakeChangeAction.CreateCorrespondingChange()
     {
         return new CreateStructureMember_Change(ParentGuid, Index, Type);
     }

+ 1 - 1
src/ChangeableDocument/Actions/Structure/DeleteStructureMember_Action.cs

@@ -11,7 +11,7 @@ public record class DeleteStructureMember_Action : IMakeChangeAction
 
     public Guid GuidValue { get; }
 
-    IChange IMakeChangeAction.CreateCorrespondingChange()
+    Change IMakeChangeAction.CreateCorrespondingChange()
     {
         return new DeleteStructureMember_Change(GuidValue);
     }

+ 1 - 1
src/ChangeableDocument/Actions/Structure/MoveStructureMember_Action.cs

@@ -15,7 +15,7 @@ public record class MoveStructureMember_Action : IMakeChangeAction
     public Guid TargetFolder { get; init; }
     public int Index { get; init; }
 
-    IChange IMakeChangeAction.CreateCorrespondingChange()
+    Change IMakeChangeAction.CreateCorrespondingChange()
     {
         return new MoveStructureMember_Change(Member, TargetFolder, Index);
     }

+ 14 - 0
src/ChangeableDocument/Changes/Change.cs

@@ -0,0 +1,14 @@
+using ChangeableDocument.Changeables;
+using ChangeableDocument.ChangeInfos;
+
+namespace ChangeableDocument.Changes
+{
+    internal abstract class Change : IDisposable
+    {
+        public virtual bool IsMergeableWith(Change other) => false;
+        public abstract void Initialize(Document target);
+        public abstract IChangeInfo? Apply(Document target, out bool ignoreInUndo);
+        public abstract IChangeInfo? Revert(Document target);
+        public virtual void Dispose() { }
+    };
+}

+ 4 - 6
src/ChangeableDocument/Changes/CreateStructureMember_Change.cs

@@ -3,7 +3,7 @@ using ChangeableDocument.ChangeInfos;
 
 namespace ChangeableDocument.Changes
 {
-    internal class CreateStructureMember_Change : IChange
+    internal class CreateStructureMember_Change : Change
     {
         private Guid newMemberGuid;
 
@@ -18,12 +18,12 @@ namespace ChangeableDocument.Changes
             this.type = type;
         }
 
-        public void Initialize(Document target)
+        public override void Initialize(Document target)
         {
             newMemberGuid = Guid.NewGuid();
         }
 
-        public IChangeInfo Apply(Document document, out bool ignoreInUndo)
+        public override IChangeInfo Apply(Document document, out bool ignoreInUndo)
         {
             var folder = (Folder)document.FindMemberOrThrow(parentFolderGuid);
 
@@ -40,7 +40,7 @@ namespace ChangeableDocument.Changes
             return new CreateStructureMember_ChangeInfo() { GuidValue = newMemberGuid };
         }
 
-        public IChangeInfo Revert(Document document)
+        public override IChangeInfo Revert(Document document)
         {
             var folder = (Folder)document.FindMemberOrThrow(parentFolderGuid);
             var child = document.FindMemberOrThrow(newMemberGuid);
@@ -49,7 +49,5 @@ namespace ChangeableDocument.Changes
 
             return new DeleteStructureMember_ChangeInfo() { GuidValue = newMemberGuid };
         }
-
-        public void Dispose() { }
     }
 }

+ 5 - 5
src/ChangeableDocument/Changes/DeleteStructureMember_Change.cs

@@ -3,7 +3,7 @@ using ChangeableDocument.ChangeInfos;
 
 namespace ChangeableDocument.Changes
 {
-    internal class DeleteStructureMember_Change : IChange
+    internal class DeleteStructureMember_Change : Change
     {
         private Guid memberGuid;
         private Guid parentGuid;
@@ -14,7 +14,7 @@ namespace ChangeableDocument.Changes
             this.memberGuid = memberGuid;
         }
 
-        public void Initialize(Document document)
+        public override void Initialize(Document document)
         {
             var (member, parent) = document.FindChildAndParentOrThrow(memberGuid);
 
@@ -23,7 +23,7 @@ namespace ChangeableDocument.Changes
             savedCopy = member.Clone();
         }
 
-        public IChangeInfo Apply(Document document, out bool ignoreInUndo)
+        public override IChangeInfo Apply(Document document, out bool ignoreInUndo)
         {
             var (member, parent) = document.FindChildAndParentOrThrow(memberGuid);
             parent.Children.Remove(member);
@@ -32,7 +32,7 @@ namespace ChangeableDocument.Changes
             return new DeleteStructureMember_ChangeInfo() { GuidValue = memberGuid };
         }
 
-        public IChangeInfo Revert(Document doc)
+        public override IChangeInfo Revert(Document doc)
         {
             var parent = (Folder)doc.FindMemberOrThrow(parentGuid);
 
@@ -40,7 +40,7 @@ namespace ChangeableDocument.Changes
             return new CreateStructureMember_ChangeInfo() { GuidValue = memberGuid };
         }
 
-        public void Dispose()
+        public override void Dispose()
         {
             savedCopy!.Dispose();
         }

+ 5 - 5
src/ChangeableDocument/Changes/Drawing/ClearSelection_Change.cs

@@ -5,18 +5,18 @@ using ChunkyImageLib.DataHolders;
 
 namespace ChangeableDocument.Changes.Drawing
 {
-    internal class ClearSelection_Change : IChange
+    internal class ClearSelection_Change : Change
     {
         private bool originalIsEmpty;
         private CommittedChunkStorage? savedSelection;
-        public void Initialize(Document target)
+        public override void Initialize(Document target)
         {
             originalIsEmpty = target.Selection.IsEmptyAndInactive;
             if (!originalIsEmpty)
                 savedSelection = new(target.Selection.SelectionImage, target.Selection.SelectionImage.FindAllChunks());
         }
 
-        public IChangeInfo? Apply(Document target, out bool ignoreInUndo)
+        public override IChangeInfo? Apply(Document target, out bool ignoreInUndo)
         {
             if (originalIsEmpty)
             {
@@ -34,7 +34,7 @@ namespace ChangeableDocument.Changes.Drawing
             return new Selection_ChangeInfo() { Chunks = affChunks };
         }
 
-        public IChangeInfo? Revert(Document target)
+        public override IChangeInfo? Revert(Document target)
         {
             if (originalIsEmpty)
                 return new Selection_ChangeInfo() { Chunks = new() };
@@ -50,7 +50,7 @@ namespace ChangeableDocument.Changes.Drawing
             return new Selection_ChangeInfo() { Chunks = affChunks };
         }
 
-        public void Dispose()
+        public override void Dispose()
         {
             savedSelection?.Dispose();
         }

+ 5 - 5
src/ChangeableDocument/Changes/Drawing/CombineStructureMembersOnto_Change.cs

@@ -6,7 +6,7 @@ using ChunkyImageLib.DataHolders;
 
 namespace ChangeableDocument.Changes.Drawing
 {
-    internal class CombineStructureMembersOnto_Change : IChange
+    internal class CombineStructureMembersOnto_Change : Change
     {
         private HashSet<Guid> membersToMerge;
 
@@ -21,7 +21,7 @@ namespace ChangeableDocument.Changes.Drawing
             this.targetLayer = targetLayer;
         }
 
-        public void Initialize(Document target)
+        public override void Initialize(Document target)
         {
             foreach (Guid guid in membersToMerge)
             {
@@ -44,7 +44,7 @@ namespace ChangeableDocument.Changes.Drawing
             }
         }
 
-        public IChangeInfo? Apply(Document target, out bool ignoreInUndo)
+        public override IChangeInfo? Apply(Document target, out bool ignoreInUndo)
         {
             Layer toDrawOn = (Layer)target.FindMemberOrThrow(targetLayer);
 
@@ -73,7 +73,7 @@ namespace ChangeableDocument.Changes.Drawing
             };
         }
 
-        public IChangeInfo? Revert(Document target)
+        public override IChangeInfo? Revert(Document target)
         {
             Layer toDrawOn = (Layer)target.FindMemberOrThrow(targetLayer);
             if (originalChunks == null)
@@ -93,7 +93,7 @@ namespace ChangeableDocument.Changes.Drawing
             };
         }
 
-        public void Dispose()
+        public override void Dispose()
         {
             originalChunks?.Dispose();
         }

+ 6 - 6
src/ChangeableDocument/Changes/Drawing/DrawRectangle_UpdateableChange.cs

@@ -5,7 +5,7 @@ using ChunkyImageLib.DataHolders;
 
 namespace ChangeableDocument.Changes.Drawing
 {
-    internal class DrawRectangle_UpdateableChange : IUpdateableChange
+    internal class DrawRectangle_UpdateableChange : UpdateableChange
     {
         private Guid layerGuid;
         private ShapeData rect;
@@ -16,14 +16,14 @@ namespace ChangeableDocument.Changes.Drawing
             this.rect = rectangle;
         }
 
-        public void Initialize(Document target) { }
+        public override void Initialize(Document target) { }
 
         public void Update(ShapeData updatedRectangle)
         {
             rect = updatedRectangle;
         }
 
-        public IChangeInfo? ApplyTemporarily(Document target)
+        public override IChangeInfo? ApplyTemporarily(Document target)
         {
             Layer layer = (Layer)target.FindMemberOrThrow(layerGuid);
             var oldChunks = layer.LayerImage.FindAffectedChunks();
@@ -40,7 +40,7 @@ namespace ChangeableDocument.Changes.Drawing
             };
         }
 
-        public IChangeInfo? Apply(Document target, out bool ignoreInUndo)
+        public override IChangeInfo? Apply(Document target, out bool ignoreInUndo)
         {
             Layer layer = (Layer)target.FindMemberOrThrow(layerGuid);
             var changes = ApplyTemporarily(target);
@@ -51,7 +51,7 @@ namespace ChangeableDocument.Changes.Drawing
             return changes;
         }
 
-        public IChangeInfo? Revert(Document target)
+        public override IChangeInfo? Revert(Document target)
         {
             if (storedChunks == null)
                 throw new Exception("No stored chunks to revert to");
@@ -68,7 +68,7 @@ namespace ChangeableDocument.Changes.Drawing
             return changes;
         }
 
-        public void Dispose()
+        public override void Dispose()
         {
             storedChunks?.Dispose();
         }

+ 6 - 6
src/ChangeableDocument/Changes/Drawing/SelectRectangle_UpdateableChange.cs

@@ -6,7 +6,7 @@ using SkiaSharp;
 
 namespace ChangeableDocument.Changes.Drawing
 {
-    internal class SelectRectangle_UpdateableChange : IUpdateableChange
+    internal class SelectRectangle_UpdateableChange : UpdateableChange
     {
         private bool originalIsEmpty;
         private Vector2i pos;
@@ -16,7 +16,7 @@ namespace ChangeableDocument.Changes.Drawing
         {
             Update(pos, size);
         }
-        public void Initialize(Document target)
+        public override void Initialize(Document target)
         {
             originalIsEmpty = target.Selection.IsEmptyAndInactive;
         }
@@ -27,7 +27,7 @@ namespace ChangeableDocument.Changes.Drawing
             this.size = size;
         }
 
-        public IChangeInfo? ApplyTemporarily(Document target)
+        public override IChangeInfo? ApplyTemporarily(Document target)
         {
             var oldChunks = target.Selection.SelectionImage.FindAffectedChunks();
             target.Selection.SelectionImage.CancelChanges();
@@ -38,7 +38,7 @@ namespace ChangeableDocument.Changes.Drawing
             return new Selection_ChangeInfo() { Chunks = oldChunks };
         }
 
-        public IChangeInfo? Apply(Document target, out bool ignoreInUndo)
+        public override IChangeInfo? Apply(Document target, out bool ignoreInUndo)
         {
             var changes = ApplyTemporarily(target);
             originalSelectionState = new CommittedChunkStorage(target.Selection.SelectionImage, ((Selection_ChangeInfo)changes!).Chunks!);
@@ -48,7 +48,7 @@ namespace ChangeableDocument.Changes.Drawing
             return changes;
         }
 
-        public IChangeInfo? Revert(Document target)
+        public override IChangeInfo? Revert(Document target)
         {
             if (originalSelectionState == null)
                 throw new Exception("No stored chunks to revert to");
@@ -62,7 +62,7 @@ namespace ChangeableDocument.Changes.Drawing
             return changes;
         }
 
-        public void Dispose()
+        public override void Dispose()
         {
             originalSelectionState?.Dispose();
         }

+ 0 - 12
src/ChangeableDocument/Changes/IChange.cs

@@ -1,12 +0,0 @@
-using ChangeableDocument.Changeables;
-using ChangeableDocument.ChangeInfos;
-
-namespace ChangeableDocument.Changes
-{
-    internal interface IChange : IDisposable
-    {
-        void Initialize(Document target);
-        IChangeInfo? Apply(Document target, out bool ignoreInUndo);
-        IChangeInfo? Revert(Document target);
-    };
-}

+ 4 - 6
src/ChangeableDocument/Changes/MoveStructureMember_Change.cs

@@ -3,7 +3,7 @@ using ChangeableDocument.ChangeInfos;
 
 namespace ChangeableDocument.Changes
 {
-    internal class MoveStructureMember_Change : IChange
+    internal class MoveStructureMember_Change : Change
     {
         private Guid memberGuid;
 
@@ -20,7 +20,7 @@ namespace ChangeableDocument.Changes
             this.targetFolderIndex = targetFolderIndex;
         }
 
-        public void Initialize(Document document)
+        public override void Initialize(Document document)
         {
             var (member, curFolder) = document.FindChildAndParentOrThrow(memberGuid);
             originalFolderGuid = curFolder.GuidValue;
@@ -36,19 +36,17 @@ namespace ChangeableDocument.Changes
             targetFolder.Children.Insert(targetIndex, member);
         }
 
-        public IChangeInfo? Apply(Document target, out bool ignoreInUndo)
+        public override IChangeInfo? Apply(Document target, out bool ignoreInUndo)
         {
             Move(target, memberGuid, targetFolderGuid, targetFolderIndex);
             ignoreInUndo = false;
             return new MoveStructureMember_ChangeInfo() { GuidValue = memberGuid };
         }
 
-        public IChangeInfo? Revert(Document target)
+        public override IChangeInfo? Revert(Document target)
         {
             Move(target, memberGuid, originalFolderGuid, originalFolderIndex);
             return new MoveStructureMember_ChangeInfo() { GuidValue = memberGuid };
         }
-
-        public void Dispose() { }
     }
 }

+ 5 - 5
src/ChangeableDocument/Changes/ResizeCanvas_Change.cs

@@ -5,7 +5,7 @@ using ChunkyImageLib.DataHolders;
 
 namespace ChangeableDocument.Changes
 {
-    internal class ResizeCanvas_Change : IChange
+    internal class ResizeCanvas_Change : Change
     {
         private Vector2i originalSize;
         private Dictionary<Guid, CommittedChunkStorage> deletedChunks = new();
@@ -15,7 +15,7 @@ namespace ChangeableDocument.Changes
         {
             newSize = size;
         }
-        public void Initialize(Document target)
+        public override void Initialize(Document target)
         {
             originalSize = target.Size;
         }
@@ -33,7 +33,7 @@ namespace ChangeableDocument.Changes
             }
         }
 
-        public IChangeInfo? Apply(Document target, out bool ignoreInUndo)
+        public override IChangeInfo? Apply(Document target, out bool ignoreInUndo)
         {
             if (originalSize == newSize)
             {
@@ -58,7 +58,7 @@ namespace ChangeableDocument.Changes
             return new Size_ChangeInfo();
         }
 
-        public IChangeInfo? Revert(Document target)
+        public override IChangeInfo? Revert(Document target)
         {
             if (originalSize == newSize)
                 return null;
@@ -84,7 +84,7 @@ namespace ChangeableDocument.Changes
             return new Size_ChangeInfo();
         }
 
-        public void Dispose()
+        public override void Dispose()
         {
             foreach (var layer in deletedChunks)
                 layer.Value.Dispose();

+ 5 - 7
src/ChangeableDocument/Changes/StructureMemberVisibility_Change.cs → src/ChangeableDocument/Changes/StructureMemberIsVisible_Change.cs

@@ -3,24 +3,24 @@ using ChangeableDocument.ChangeInfos;
 
 namespace ChangeableDocument.Changes
 {
-    internal class StructureMemberVisibility_Change : IChange
+    internal class StructureMemberIsVisible_Change : Change
     {
         private bool? originalIsVisible;
         private bool newIsVisible;
         private Guid targetMember;
-        public StructureMemberVisibility_Change(Guid targetMember, bool newIsVisible)
+        public StructureMemberIsVisible_Change(Guid targetMember, bool newIsVisible)
         {
             this.targetMember = targetMember;
             this.newIsVisible = newIsVisible;
         }
 
-        public void Initialize(Document target)
+        public override void Initialize(Document target)
         {
             var member = target.FindMemberOrThrow(targetMember);
             originalIsVisible = member.IsVisible;
         }
 
-        public IChangeInfo? Apply(Document target, out bool ignoreInUndo)
+        public override IChangeInfo? Apply(Document target, out bool ignoreInUndo)
         {
             // don't record layer/folder visibility changes - it's just more convenient this way
             ignoreInUndo = true;
@@ -31,14 +31,12 @@ namespace ChangeableDocument.Changes
             return new StructureMemberIsVisible_ChangeInfo() { GuidValue = targetMember };
         }
 
-        public IChangeInfo? Revert(Document target)
+        public override IChangeInfo? Revert(Document target)
         {
             if (originalIsVisible == null)
                 throw new Exception("No name to revert to");
             target.FindMemberOrThrow(targetMember).IsVisible = originalIsVisible.Value;
             return new StructureMemberIsVisible_ChangeInfo() { GuidValue = targetMember };
         }
-
-        public void Dispose() { }
     }
 }

+ 10 - 5
src/ChangeableDocument/Changes/StructureMemberName_Change.cs

@@ -3,7 +3,7 @@ using ChangeableDocument.ChangeInfos;
 
 namespace ChangeableDocument.Changes
 {
-    internal class StructureMemberName_Change : IChange
+    internal class StructureMemberName_Change : Change
     {
         private string? originalName;
         private string newName;
@@ -14,13 +14,13 @@ namespace ChangeableDocument.Changes
             this.newName = newName;
         }
 
-        public void Initialize(Document target)
+        public override void Initialize(Document target)
         {
             var member = target.FindMemberOrThrow(targetMember);
             originalName = member.Name;
         }
 
-        public IChangeInfo? Apply(Document target, out bool ignoreInUndo)
+        public override IChangeInfo? Apply(Document target, out bool ignoreInUndo)
         {
             if (originalName == newName)
             {
@@ -33,7 +33,7 @@ namespace ChangeableDocument.Changes
             return new StructureMemberName_ChangeInfo() { GuidValue = targetMember };
         }
 
-        public IChangeInfo? Revert(Document target)
+        public override IChangeInfo? Revert(Document target)
         {
             if (originalName == null)
                 throw new Exception("No name to revert to");
@@ -41,6 +41,11 @@ namespace ChangeableDocument.Changes
             return new StructureMemberName_ChangeInfo() { GuidValue = targetMember };
         }
 
-        public void Dispose() { }
+        public override bool IsMergeableWith(Change other)
+        {
+            if (other is not StructureMemberName_Change same)
+                return false;
+            return same.targetMember == targetMember;
+        }
     }
 }

+ 11 - 6
src/ChangeableDocument/Changes/StructureMemberOpacity_UpdateableChange.cs

@@ -3,7 +3,7 @@ using ChangeableDocument.ChangeInfos;
 
 namespace ChangeableDocument.Changes
 {
-    internal class StructureMemberOpacity_UpdateableChange : IUpdateableChange
+    internal class StructureMemberOpacity_UpdateableChange : UpdateableChange
     {
         private Guid memberGuid;
 
@@ -21,15 +21,15 @@ namespace ChangeableDocument.Changes
             newOpacity = updatedOpacity;
         }
 
-        public void Initialize(Document document)
+        public override void Initialize(Document document)
         {
             var member = document.FindMemberOrThrow(memberGuid);
             originalOpacity = member.Opacity;
         }
 
-        public IChangeInfo? ApplyTemporarily(Document target) => Apply(target, out _);
+        public override IChangeInfo? ApplyTemporarily(Document target) => Apply(target, out _);
 
-        public IChangeInfo? Apply(Document document, out bool ignoreInUndo)
+        public override IChangeInfo? Apply(Document document, out bool ignoreInUndo)
         {
             if (originalOpacity == newOpacity)
             {
@@ -44,7 +44,7 @@ namespace ChangeableDocument.Changes
             return new StructureMemberOpacity_ChangeInfo() { GuidValue = memberGuid };
         }
 
-        public IChangeInfo? Revert(Document document)
+        public override IChangeInfo? Revert(Document document)
         {
             if (originalOpacity == newOpacity)
                 return null;
@@ -55,6 +55,11 @@ namespace ChangeableDocument.Changes
             return new StructureMemberOpacity_ChangeInfo() { GuidValue = memberGuid };
         }
 
-        public void Dispose() { }
+        public override bool IsMergeableWith(Change other)
+        {
+            if (other is not StructureMemberOpacity_UpdateableChange same)
+                return false;
+            return same.memberGuid == memberGuid;
+        }
     }
 }

+ 2 - 2
src/ChangeableDocument/Changes/IUpdateableChange.cs → src/ChangeableDocument/Changes/UpdateableChange.cs

@@ -3,8 +3,8 @@ using ChangeableDocument.ChangeInfos;
 
 namespace ChangeableDocument.Changes
 {
-    internal interface IUpdateableChange : IChange
+    internal abstract class UpdateableChange : Change
     {
-        IChangeInfo? ApplyTemporarily(Document target);
+        public abstract IChangeInfo? ApplyTemporarily(Document target);
     }
 }

+ 41 - 21
src/ChangeableDocument/DocumentChangeTracker.cs

@@ -12,42 +12,62 @@ namespace ChangeableDocument
         private Document document;
         public IReadOnlyDocument Document => document;
 
-        private IUpdateableChange? activeChange = null;
+        private UpdateableChange? activeChange = null;
 
-        private Stack<IChange> undoStack = new();
-        private Stack<IChange> redoStack = new();
+        private Stack<List<Change>> undoStack = new();
+        private Stack<List<Change>> redoStack = new();
 
         public DocumentChangeTracker()
         {
             document = new Document();
         }
 
-        private void AddToUndo(IChange change)
+        private void AddToUndo(Change change)
         {
-            undoStack.Push(change);
-            foreach (var changeToDispose in redoStack)
-                changeToDispose.Dispose();
+            List<Change> targetPacket = GetOrCreatePacket(change);
+            targetPacket.Add(change);
+
+            foreach (var changesToDispose in redoStack)
+                foreach (var changeToDispose in changesToDispose)
+                    changeToDispose.Dispose();
             redoStack.Clear();
         }
 
-        private IChangeInfo? Undo()
+        private List<Change> GetOrCreatePacket(Change change)
+        {
+            if (undoStack.Count != 0 && change.IsMergeableWith(undoStack.Peek()[^1]))
+                return undoStack.Peek();
+            var newPacket = new List<Change>();
+            undoStack.Push(newPacket);
+            return newPacket;
+        }
+
+        private List<IChangeInfo?> Undo()
         {
             if (undoStack.Count == 0)
-                return null;
-            IChange change = undoStack.Pop();
-            var info = change.Revert(document);
-            redoStack.Push(change);
-            return info;
+                return new List<IChangeInfo?>();
+            List<IChangeInfo?> changeInfos = new();
+            List<Change> changePacket = undoStack.Pop();
+
+            for (int i = changePacket.Count - 1; i >= 0; i--)
+                changeInfos.Add(changePacket[i].Revert(document));
+
+            redoStack.Push(changePacket);
+            return changeInfos;
         }
 
-        private IChangeInfo? Redo()
+        private List<IChangeInfo?> Redo()
         {
             if (redoStack.Count == 0)
-                return null;
-            IChange change = redoStack.Pop();
-            var info = change.Apply(document, out bool _);
-            undoStack.Push(change);
-            return info;
+                return new List<IChangeInfo?>();
+            List<IChangeInfo?> changeInfos = new();
+            List<Change> changePacket = redoStack.Pop();
+
+            for (int i = 0; i < changePacket.Count; i++)
+                changeInfos.Add(changePacket[i].Apply(document, out _));
+
+            undoStack.Push(changePacket);
+            return changeInfos;
         }
 
         private IChangeInfo? ProcessMakeChangeAction(IMakeChangeAction act)
@@ -108,10 +128,10 @@ namespace ChangeableDocument
                         changeInfos.Add(ProcessEndChangeAction(act));
                         break;
                     case Undo_Action act:
-                        changeInfos.Add(Undo());
+                        changeInfos.AddRange(Undo());
                         break;
                     case Redo_Action act:
-                        changeInfos.Add(Redo());
+                        changeInfos.AddRange(Redo());
                         break;
                     default:
                         throw new Exception("Unknown action type");