Equbuxu преди 3 години
родител
ревизия
d5c913b934

+ 6 - 0
src/PixiEditor.ChangeableDocument/Actions/Undo/ChangeBoundary_Action.cs

@@ -0,0 +1,6 @@
+namespace PixiEditor.ChangeableDocument.Actions.Undo
+{
+    public record class ChangeBoundary_Action : IAction
+    {
+    }
+}

+ 0 - 12
src/PixiEditor.ChangeableDocument/Actions/Undo/MergeLatestChanges_Action.cs

@@ -1,12 +0,0 @@
-namespace PixiEditor.ChangeableDocument.Actions.Undo
-{
-    public record class MergeLatestChanges_Action : IAction
-    {
-        public MergeLatestChanges_Action(int count)
-        {
-            Count = count;
-        }
-
-        public int Count { get; }
-    }
-}

+ 41 - 41
src/PixiEditor.ChangeableDocument/DocumentChangeTracker.cs

@@ -13,6 +13,7 @@ namespace ChangeableDocument
         public IReadOnlyDocument Document => document;
 
         private UpdateableChange? activeChange = null;
+        private List<Change>? activePacket = null;
 
         private Stack<List<Change>> undoStack = new();
         private Stack<List<Change>> redoStack = new();
@@ -24,8 +25,9 @@ namespace ChangeableDocument
 
         private void AddToUndo(Change change)
         {
-            List<Change> targetPacket = GetOrCreatePacket(change);
-            targetPacket.Add(change);
+            if (activePacket is null)
+                activePacket = new();
+            activePacket.Add(change);
 
             foreach (var changesToDispose in redoStack)
                 foreach (var changeToDispose in changesToDispose)
@@ -33,19 +35,43 @@ namespace ChangeableDocument
             redoStack.Clear();
         }
 
-        private List<Change> GetOrCreatePacket(Change change)
+        private void CompletePacket()
         {
-            if (undoStack.Count != 0 && change.IsMergeableWith(undoStack.Peek()[^1]))
-                return undoStack.Peek();
-            var newPacket = new List<Change>();
-            undoStack.Push(newPacket);
-            return newPacket;
+            if (activePacket is null)
+                return;
+
+            // maybe merge with previous
+            if (activePacket.Count == 1 &&
+                undoStack.Count > 0 &&
+                IsHomologous(undoStack.Peek()) &&
+                undoStack.Peek()[^1].IsMergeableWith(activePacket[0]))
+            {
+                undoStack.Peek().Add(activePacket[0]);
+            }
+            else
+            {
+                undoStack.Push(activePacket);
+            }
+
+            activePacket = null;
+        }
+
+        private bool IsHomologous(List<Change> changes)
+        {
+            for (int i = 1; i < changes.Count; i++)
+            {
+                if (!changes[i].IsMergeableWith(changes[i - 1]))
+                    return false;
+            }
+            return true;
         }
 
         private List<IChangeInfo?> Undo()
         {
             if (undoStack.Count == 0)
                 return new List<IChangeInfo?>();
+            if (activePacket is not null || activeChange is not null)
+                throw new InvalidOperationException("Cannot undo while there is an active updateable change or an unfinished undo packet");
             List<IChangeInfo?> changeInfos = new();
             List<Change> changePacket = undoStack.Pop();
 
@@ -60,6 +86,8 @@ namespace ChangeableDocument
         {
             if (redoStack.Count == 0)
                 return new List<IChangeInfo?>();
+            if (activePacket is not null || activeChange is not null)
+                throw new InvalidOperationException("Cannot redo while there is an active updateable change or an unfinished undo packet");
             List<IChangeInfo?> changeInfos = new();
             List<Change> changePacket = redoStack.Pop();
 
@@ -70,38 +98,10 @@ namespace ChangeableDocument
             return changeInfos;
         }
 
-        private List<Change> PopLatestChanges(int count)
-        {
-            if (redoStack.Count > 0)
-                throw new InvalidOperationException("There are changes in the redo stack");
-            List<Change> popped = new();
-            while (count > 0)
-            {
-                if (undoStack.Count == 0)
-                    return popped;
-                var packet = undoStack.Peek();
-                var change = packet[^1];
-                packet.RemoveAt(packet.Count - 1);
-                popped.Add(change);
-                if (packet.Count == 0)
-                    undoStack.Pop();
-                count--;
-            }
-            popped.Reverse();
-            return popped;
-        }
-
-        private void MergeLatestChanges(int count)
-        {
-            if (redoStack.Count > 0)
-                throw new InvalidOperationException("There are changes in the redo stack");
-            var packet = PopLatestChanges(count);
-            if (packet.Count > 0)
-                undoStack.Push(packet);
-        }
-
         private void DeleteAllChanges()
         {
+            if (activeChange is not null || activePacket is not null)
+                throw new InvalidOperationException("Cannot delete all changes while there is an active updateable change or an unfinished undo packet");
             foreach (var changesToDispose in redoStack)
                 foreach (var changeToDispose in changesToDispose)
                     changeToDispose.Dispose();
@@ -142,7 +142,7 @@ namespace ChangeableDocument
             if (activeChange is null)
                 throw new InvalidOperationException("Can't end a change: no changes are active");
             if (!act.IsChangeTypeMatching(activeChange))
-                throw new InvalidOperationException($"Trying to end a change via action of type {act.GetType()} while a change of type {activeChange.GetType()} is active");
+                throw new InvalidOperationException($"Trying to end a change with an action of type {act.GetType()} while a change of type {activeChange.GetType()} is active");
 
             var info = activeChange.Apply(document, out bool ignoreInUndo);
             if (!ignoreInUndo)
@@ -175,8 +175,8 @@ namespace ChangeableDocument
                     case Redo_Action act:
                         changeInfos.AddRange(Redo());
                         break;
-                    case MergeLatestChanges_Action act:
-                        MergeLatestChanges(act.Count);
+                    case ChangeBoundary_Action:
+                        CompletePacket();
                         break;
                     case DeleteRecordedChanges_Action:
                         DeleteAllChanges();

+ 25 - 3
src/PixiEditorPrototype/Models/ActionAccumulator.cs

@@ -1,5 +1,6 @@
 using ChunkyImageLib.DataHolders;
 using PixiEditor.ChangeableDocument.Actions;
+using PixiEditor.ChangeableDocument.Actions.Undo;
 using PixiEditor.ChangeableDocument.ChangeInfos;
 using PixiEditorPrototype.Models.Rendering;
 using PixiEditorPrototype.Models.Rendering.RenderInfos;
@@ -7,6 +8,7 @@ using PixiEditorPrototype.ViewModels;
 using SkiaSharp;
 using System;
 using System.Collections.Generic;
+using System.Linq;
 using System.Windows.Media.Imaging;
 
 namespace PixiEditorPrototype.Models
@@ -29,9 +31,16 @@ namespace PixiEditorPrototype.Models
             renderer = new(helpers);
         }
 
-        public void AddAction(IAction action)
+        public void AddFinishedActions(params IAction[] actions)
         {
-            queuedActions.Add(action);
+            queuedActions.AddRange(actions);
+            queuedActions.Add(new ChangeBoundary_Action());
+            TryExecuteAccumulatedActions();
+        }
+
+        public void AddActions(params IAction[] actions)
+        {
+            queuedActions.AddRange(actions);
             TryExecuteAccumulatedActions();
         }
 
@@ -46,7 +55,10 @@ namespace PixiEditorPrototype.Models
                 var toExecute = queuedActions;
                 queuedActions = new List<IAction>();
 
-                var result = await helpers.Tracker.ProcessActions(toExecute);
+                List<IChangeInfo?> result = AreAllPassthrough(toExecute) ?
+                    toExecute.Select(a => (IChangeInfo?)a).ToList() :
+                    await helpers.Tracker.ProcessActions(toExecute);
+
                 foreach (IChangeInfo? info in result)
                 {
                     helpers.Updater.ApplyChangeFromChangeInfo(info);
@@ -68,6 +80,16 @@ namespace PixiEditorPrototype.Models
             executing = false;
         }
 
+        private bool AreAllPassthrough(List<IAction> actions)
+        {
+            foreach (var action in actions)
+            {
+                if (action is not IChangeInfo)
+                    return false;
+            }
+            return true;
+        }
+
         private (WriteableBitmap, SKSurface) GetCorrespondingBitmap(ChunkResolution res)
         {
             var result = res switch

+ 6 - 6
src/PixiEditorPrototype/Models/DocumentStructureHelper.cs

@@ -21,13 +21,13 @@ namespace PixiEditorPrototype.Models
             if (doc.SelectedStructureMember is null)
             {
                 //put member on top
-                helpers.ActionAccumulator.AddAction(new CreateStructureMember_Action(doc.StructureRoot.GuidValue, Guid.NewGuid(), doc.StructureRoot.Children.Count, type));
+                helpers.ActionAccumulator.AddFinishedActions(new CreateStructureMember_Action(doc.StructureRoot.GuidValue, Guid.NewGuid(), doc.StructureRoot.Children.Count, type));
                 return;
             }
             if (doc.SelectedStructureMember is FolderViewModel folder)
             {
                 //put member inside folder on top
-                helpers.ActionAccumulator.AddAction(new CreateStructureMember_Action(folder.GuidValue, Guid.NewGuid(), folder.Children.Count, type));
+                helpers.ActionAccumulator.AddFinishedActions(new CreateStructureMember_Action(folder.GuidValue, Guid.NewGuid(), folder.Children.Count, type));
                 return;
             }
             if (doc.SelectedStructureMember is LayerViewModel layer)
@@ -37,7 +37,7 @@ namespace PixiEditorPrototype.Models
                 if (path.Count < 2)
                     throw new InvalidOperationException("Couldn't find a path to the selected member");
                 var parent = (FolderViewModel)path[1];
-                helpers.ActionAccumulator.AddAction(new CreateStructureMember_Action(parent.GuidValue, Guid.NewGuid(), parent.Children.IndexOf(layer) + 1, type));
+                helpers.ActionAccumulator.AddFinishedActions(new CreateStructureMember_Action(parent.GuidValue, Guid.NewGuid(), parent.Children.IndexOf(layer) + 1, type));
                 return;
             }
             throw new ArgumentException("Unknown member type: " + type.ToString());
@@ -100,19 +100,19 @@ namespace PixiEditorPrototype.Models
                 int curIndex = doc.StructureRoot.Children.IndexOf(path[0]);
                 if (curIndex == 0 && toSmallerIndex || curIndex == doc.StructureRoot.Children.Count - 1 && !toSmallerIndex)
                     return;
-                helpers.ActionAccumulator.AddAction(new MoveStructureMember_Action(guid, doc.StructureRoot.GuidValue, toSmallerIndex ? curIndex - 1 : curIndex + 1));
+                helpers.ActionAccumulator.AddFinishedActions(new MoveStructureMember_Action(guid, doc.StructureRoot.GuidValue, toSmallerIndex ? curIndex - 1 : curIndex + 1));
                 return;
             }
             var folder = (FolderViewModel)path[1];
             int index = folder.Children.IndexOf(path[0]);
             if (toSmallerIndex && index > 0 || !toSmallerIndex && index < folder.Children.Count - 1)
             {
-                helpers.ActionAccumulator.AddAction(new MoveStructureMember_Action(guid, path[1].GuidValue, toSmallerIndex ? index - 1 : index + 1));
+                helpers.ActionAccumulator.AddFinishedActions(new MoveStructureMember_Action(guid, path[1].GuidValue, toSmallerIndex ? index - 1 : index + 1));
             }
             else
             {
                 int parentIndex = ((FolderViewModel)path[2]).Children.IndexOf(folder);
-                helpers.ActionAccumulator.AddAction(new MoveStructureMember_Action(guid, path[2].GuidValue, toSmallerIndex ? parentIndex : parentIndex + 1));
+                helpers.ActionAccumulator.AddFinishedActions(new MoveStructureMember_Action(guid, path[2].GuidValue, toSmallerIndex ? parentIndex : parentIndex + 1));
             }
         }
     }

+ 20 - 19
src/PixiEditorPrototype/ViewModels/DocumentViewModel.cs

@@ -151,7 +151,7 @@ namespace PixiEditorPrototype.ViewModels
             if (SelectedStructureMember is not LayerViewModel && !drawOnMask)
                 return;
             startedRectangle = true;
-            Helpers.ActionAccumulator.AddAction(new DrawRectangle_Action(SelectedStructureMember.GuidValue, data, drawOnMask));
+            Helpers.ActionAccumulator.AddActions(new DrawRectangle_Action(SelectedStructureMember.GuidValue, data, drawOnMask));
         }
 
         public void EndRectangle()
@@ -159,16 +159,16 @@ namespace PixiEditorPrototype.ViewModels
             if (!startedRectangle)
                 return;
             startedRectangle = false;
-            Helpers.ActionAccumulator.AddAction(new EndDrawRectangle_Action());
+            Helpers.ActionAccumulator.AddFinishedActions(new EndDrawRectangle_Action());
         }
 
         bool startedSelection = false;
         public void StartUpdateSelection(Vector2i pos, Vector2i size)
         {
             if (!startedSelection)
-                Helpers.ActionAccumulator.AddAction(new ClearSelection_Action());
+                Helpers.ActionAccumulator.AddActions(new ClearSelection_Action());
             startedSelection = true;
-            Helpers.ActionAccumulator.AddAction(new SelectRectangle_Action(pos, size));
+            Helpers.ActionAccumulator.AddActions(new SelectRectangle_Action(pos, size));
         }
 
         public void EndSelection()
@@ -176,7 +176,7 @@ namespace PixiEditorPrototype.ViewModels
             if (!startedSelection)
                 return;
             startedSelection = false;
-            Helpers.ActionAccumulator.AddAction(new EndSelectRectangle_Action());
+            Helpers.ActionAccumulator.AddFinishedActions(new EndSelectRectangle_Action());
         }
 
         public void ForceRefreshView()
@@ -186,28 +186,28 @@ namespace PixiEditorPrototype.ViewModels
 
         private void ClearSelection(object? param)
         {
-            Helpers.ActionAccumulator.AddAction(new ClearSelection_Action());
+            Helpers.ActionAccumulator.AddFinishedActions(new ClearSelection_Action());
         }
 
         private void DeleteStructureMember(object? param)
         {
             if (SelectedStructureMember is not null)
-                Helpers.ActionAccumulator.AddAction(new DeleteStructureMember_Action(SelectedStructureMember.GuidValue));
+                Helpers.ActionAccumulator.AddFinishedActions(new DeleteStructureMember_Action(SelectedStructureMember.GuidValue));
         }
 
         private void Undo(object? param)
         {
-            Helpers.ActionAccumulator.AddAction(new Undo_Action());
+            Helpers.ActionAccumulator.AddActions(new Undo_Action());
         }
 
         private void Redo(object? param)
         {
-            Helpers.ActionAccumulator.AddAction(new Redo_Action());
+            Helpers.ActionAccumulator.AddActions(new Redo_Action());
         }
 
         private void ResizeCanvas(object? param)
         {
-            Helpers.ActionAccumulator.AddAction(new ResizeCanvas_Action(new(ResizeWidth, ResizeHeight)));
+            Helpers.ActionAccumulator.AddFinishedActions(new ResizeCanvas_Action(new(ResizeWidth, ResizeHeight)));
         }
 
         private void ChangeSelectedItem(object? param)
@@ -219,14 +219,14 @@ namespace PixiEditorPrototype.ViewModels
         {
             if (SelectedStructureMember is null || SelectedStructureMember.HasMask)
                 return;
-            Helpers.ActionAccumulator.AddAction(new CreateStructureMemberMask_Action(SelectedStructureMember.GuidValue));
+            Helpers.ActionAccumulator.AddFinishedActions(new CreateStructureMemberMask_Action(SelectedStructureMember.GuidValue));
         }
 
         private void DeleteMask(object? param)
         {
             if (SelectedStructureMember is null || !SelectedStructureMember.HasMask)
                 return;
-            Helpers.ActionAccumulator.AddAction(new DeleteStructureMemberMask_Action(SelectedStructureMember.GuidValue));
+            Helpers.ActionAccumulator.AddFinishedActions(new DeleteStructureMemberMask_Action(SelectedStructureMember.GuidValue));
         }
 
         private void Combine(object? param)
@@ -243,12 +243,13 @@ namespace PixiEditorPrototype.ViewModels
             Guid newGuid = Guid.NewGuid();
 
             //make a new layer, put combined image onto it, delete layers that were merged
-            Helpers.ActionAccumulator.AddAction(new CreateStructureMember_Action(parent.GuidValue, newGuid, index, StructureMemberType.Layer));
-            Helpers.ActionAccumulator.AddAction(new SetStructureMemberName_Action(child.Name + "-comb", newGuid));
-            Helpers.ActionAccumulator.AddAction(new CombineStructureMembersOnto_Action(newGuid, selected.ToHashSet()));
+            Helpers.ActionAccumulator.AddActions(
+                new CreateStructureMember_Action(parent.GuidValue, newGuid, index, StructureMemberType.Layer),
+                new SetStructureMemberName_Action(child.Name + "-comb", newGuid),
+                new CombineStructureMembersOnto_Action(newGuid, selected.ToHashSet()));
             foreach (var member in selected)
-                Helpers.ActionAccumulator.AddAction(new DeleteStructureMember_Action(member));
-            Helpers.ActionAccumulator.AddAction(new MergeLatestChanges_Action(3 + selected.Count));
+                Helpers.ActionAccumulator.AddActions(new DeleteStructureMember_Action(member));
+            Helpers.ActionAccumulator.AddActions(new ChangeBoundary_Action());
         }
 
         private void MoveViewport(object? param)
@@ -256,12 +257,12 @@ namespace PixiEditorPrototype.ViewModels
             if (param is null)
                 throw new ArgumentNullException(nameof(param));
             var args = (ViewportRoutedEventArgs)param;
-            Helpers.ActionAccumulator.AddAction(new MoveViewport_PassthroughAction(args.Center, args.Size / 2, args.Angle, args.RealSize / 2));
+            Helpers.ActionAccumulator.AddActions(new MoveViewport_PassthroughAction(args.Center, args.Size / 2, args.Angle, args.RealSize / 2));
         }
 
         private void ClearHistory(object? param)
         {
-            Helpers.ActionAccumulator.AddAction(new DeleteRecordedChanges_Action());
+            Helpers.ActionAccumulator.AddActions(new DeleteRecordedChanges_Action());
         }
 
         private void AddSelectedMembers(FolderViewModel folder, List<Guid> collection)

+ 4 - 4
src/PixiEditorPrototype/ViewModels/StructureMemberViewModel.cs

@@ -16,13 +16,13 @@ namespace PixiEditorPrototype.ViewModels
         public string Name
         {
             get => member.Name;
-            set => Helpers.ActionAccumulator.AddAction(new SetStructureMemberName_Action(value, member.GuidValue));
+            set => Helpers.ActionAccumulator.AddFinishedActions(new SetStructureMemberName_Action(value, member.GuidValue));
         }
 
         public bool IsVisible
         {
             get => member.IsVisible;
-            set => Helpers.ActionAccumulator.AddAction(new SetStructureMemberVisibility_Action(value, member.GuidValue));
+            set => Helpers.ActionAccumulator.AddFinishedActions(new SetStructureMemberVisibility_Action(value, member.GuidValue));
         }
 
         public bool IsSelected { get; set; }
@@ -68,14 +68,14 @@ namespace PixiEditorPrototype.ViewModels
 
         private void EndOpacityUpdate(object? opacity)
         {
-            Helpers.ActionAccumulator.AddAction(new EndOpacityChange_Action());
+            Helpers.ActionAccumulator.AddFinishedActions(new EndOpacityChange_Action());
         }
 
         private void UpdateOpacity(object? opacity)
         {
             if (opacity is not double value)
                 throw new ArgumentException("The passed value isn't a double");
-            Helpers.ActionAccumulator.AddAction(new OpacityChange_Action(GuidValue, (float)value));
+            Helpers.ActionAccumulator.AddActions(new OpacityChange_Action(GuidValue, (float)value));
         }
     }
 }