Forráskód Böngészése

Added undo to vector path overlay

flabbet 9 hónapja
szülő
commit
41682511b2

+ 1 - 1
src/Drawie

@@ -1 +1 @@
-Subproject commit c28dcaa0cc2b31c4ccbb239e739de46f455282d4
+Subproject commit b3636b77962f9bb7c6263391a51033fde1329742

+ 7 - 3
src/PixiEditor/Models/DocumentModels/Public/DocumentToolsModule.cs

@@ -58,13 +58,19 @@ internal class DocumentToolsModule
         bool force = Internals.ChangeController.GetCurrentExecutorType() == ExecutorType.ToolLinked;
         Internals.ChangeController.TryStartExecutor<VectorRectangleToolExecutor>(force);
     }
-    
+
     public void UseVectorLineTool()
     {
         bool force = Internals.ChangeController.GetCurrentExecutorType() == ExecutorType.ToolLinked;
         Internals.ChangeController.TryStartExecutor<VectorLineToolExecutor>(force);
     }
 
+    public void UseVectorPathTool()
+    {
+        bool force = Internals.ChangeController.GetCurrentExecutorType() == ExecutorType.ToolLinked;
+        Internals.ChangeController.TryStartExecutor<VectorPathToolExecutor>(force);
+    }
+
     public void UseSelectTool() => Internals.ChangeController.TryStartExecutor<SelectToolExecutor>();
 
     public void UseBrightnessTool() => Internals.ChangeController.TryStartExecutor<BrightnessToolExecutor>();
@@ -74,6 +80,4 @@ internal class DocumentToolsModule
     public void UseLassoTool() => Internals.ChangeController.TryStartExecutor<LassoToolExecutor>();
 
     public void UseMagicWandTool() => Internals.ChangeController.TryStartExecutor<MagicWandToolExecutor>();
-
-    public void UseVectorPathTool() => Internals.ChangeController.TryStartExecutor<VectorPathToolExecutor>();
 }

+ 1 - 1
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/Features/IPathExecutor.cs

@@ -2,7 +2,7 @@
 
 namespace PixiEditor.Models.DocumentModels.UpdateableChangeExecutors.Features;
 
-public interface IPathExecutor
+public interface IPathExecutor : IExecutorFeature
 {
     public void OnPathChanged(VectorPath path);
 }

+ 24 - 2
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/VectorPathToolExecutor.cs

@@ -15,7 +15,7 @@ using Colors = Drawie.Backend.Core.ColorsImpl.Colors;
 
 namespace PixiEditor.Models.DocumentModels.UpdateableChangeExecutors;
 
-internal class VectorPathToolExecutor : UpdateableChangeExecutor, IPathExecutor
+internal class VectorPathToolExecutor : UpdateableChangeExecutor, IPathExecutor, IMidChangeUndoableExecutor
 {
     private IStructureMemberHandler member;
     private VectorPath startingPath;
@@ -23,6 +23,9 @@ internal class VectorPathToolExecutor : UpdateableChangeExecutor, IPathExecutor
     private IBasicShapeToolbar toolbar;
 
     public override ExecutorType Type => ExecutorType.ToolLinked;
+    
+    public bool CanUndo => document.PathOverlayHandler.HasUndo;
+    public bool CanRedo => document.PathOverlayHandler.HasRedo;
 
     public override ExecutionState Start()
     {
@@ -73,7 +76,7 @@ internal class VectorPathToolExecutor : UpdateableChangeExecutor, IPathExecutor
     {
         startingPath.LineTo((VecF)args.PositionOnCanvas);
         PathVectorData vectorData = ConstructShapeData();
-
+        
         internals.ActionAccumulator.AddActions(new SetShapeGeometry_Action(member.Id, vectorData));
     }
 
@@ -116,4 +119,23 @@ internal class VectorPathToolExecutor : UpdateableChangeExecutor, IPathExecutor
             internals.ActionAccumulator.AddActions(new SetShapeGeometry_Action(member.Id, ConstructShapeData()));
         }
     }
+
+    public bool IsFeatureEnabled(IExecutorFeature feature)
+    {
+        return feature switch
+        {
+            IPathExecutor _ => true,
+            _ => false
+        };
+    }
+
+    public void OnMidChangeUndo()
+    {
+        document.PathOverlayHandler.Undo();
+    }
+
+    public void OnMidChangeRedo()
+    {
+        document.PathOverlayHandler.Redo();
+    }
 }

+ 4 - 0
src/PixiEditor/Models/Handlers/IPathOverlayHandler.cs

@@ -8,4 +8,8 @@ public interface IPathOverlayHandler : IHandler
     public void Hide();
     public event Action<VectorPath> PathChanged;
     public bool IsActive { get; }
+    public bool HasUndo { get; }
+    public bool HasRedo { get; }
+    public void Undo();
+    public void Redo();
 }

+ 17 - 1
src/PixiEditor/ViewModels/Document/TransformOverlays/PathOverlayUndoStack.cs

@@ -2,7 +2,7 @@
 
 namespace PixiEditor.ViewModels.Document.TransformOverlays;
 
-internal class PathOverlayUndoStack<TState> where TState : class
+internal class PathOverlayUndoStack<TState> : IDisposable where TState : class
 {
     private struct StackItem<TState>
     {
@@ -20,7 +20,10 @@ internal class PathOverlayUndoStack<TState> where TState : class
 
     public void AddState(TState state)
     {
+        foreach (var item in redoStack)
+            (item.State as IDisposable)?.Dispose();
         redoStack.Clear();
+        
         if (current is not null)
             undoStack.Push(current.Value);
 
@@ -62,4 +65,17 @@ internal class PathOverlayUndoStack<TState> where TState : class
         undoStack.Push(current.Value);
         current = redoStack.Pop();
     }
+
+    public void Dispose()
+    {
+        foreach (var item in undoStack)
+            (item.State as IDisposable)?.Dispose();
+        foreach (var item in redoStack)
+            (item.State as IDisposable)?.Dispose();
+        (current?.State as IDisposable)?.Dispose();
+
+        undoStack.Clear();
+        redoStack.Clear();
+        current = null;
+    }
 }

+ 63 - 5
src/PixiEditor/ViewModels/Document/TransformOverlays/PathOverlayViewModel.cs

@@ -1,4 +1,6 @@
-using CommunityToolkit.Mvvm.ComponentModel;
+using System.Windows.Input;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
 using Drawie.Backend.Core.Vector;
 using PixiEditor.Models.DocumentModels;
 using PixiEditor.Models.Handlers;
@@ -10,7 +12,6 @@ internal class PathOverlayViewModel : ObservableObject, IPathOverlayHandler
     private DocumentViewModel documentViewModel;
     private DocumentInternalParts internals;
 
-
     private PathOverlayUndoStack<VectorPath>? undoStack = null;
 
     private VectorPath path;
@@ -20,8 +21,19 @@ internal class PathOverlayViewModel : ObservableObject, IPathOverlayHandler
         get => path;
         set
         {
+            var old = path;
             if (SetProperty(ref path, value))
             {
+                if (old != null)
+                {
+                    old.Changed -= PathDataChanged;
+                }
+
+                if (value != null)
+                {
+                    value.Changed += PathDataChanged;
+                }
+
                 PathChanged?.Invoke(value);
             }
         }
@@ -29,22 +41,68 @@ internal class PathOverlayViewModel : ObservableObject, IPathOverlayHandler
 
     public event Action<VectorPath>? PathChanged;
     public bool IsActive { get; set; }
+    public bool HasUndo => undoStack.UndoCount > 0;
+    public bool HasRedo => undoStack.RedoCount > 0;
+
+    public RelayCommand<VectorPath> AddToUndoCommand { get; }
+
+    private bool suppressUndo = false;
 
     public PathOverlayViewModel(DocumentViewModel documentViewModel, DocumentInternalParts internals)
     {
         this.documentViewModel = documentViewModel;
         this.internals = internals;
+
+        AddToUndoCommand = new RelayCommand<VectorPath>(AddToUndo);
+        undoStack = new PathOverlayUndoStack<VectorPath>();
     }
 
-    public void Show(VectorPath path)
+    public void Show(VectorPath newPath)
     {
-        Path = path;
+        if (IsActive)
+        {
+            return;
+        }
+
+        undoStack?.Dispose();
+        undoStack = new PathOverlayUndoStack<VectorPath>();
+        undoStack.AddState(new VectorPath(newPath));
+        Path = newPath;
         IsActive = true;
     }
-    
+
     public void Hide()
     {
         IsActive = false;
         Path = null;
     }
+
+    public void Undo()
+    {
+        suppressUndo = true;
+        Path = new VectorPath(undoStack?.Undo());
+        suppressUndo = false;
+    }
+
+    public void Redo()
+    {
+        suppressUndo = true;
+        Path = new VectorPath(undoStack?.Redo());
+        suppressUndo = false;
+    }
+
+    private void AddToUndo(VectorPath toAdd)
+    {
+        if (suppressUndo)
+        {
+            return;
+        }
+
+        undoStack?.AddState(new VectorPath(path));
+    }
+
+    private void PathDataChanged(VectorPath path)
+    {
+        AddToUndo(path);
+    }
 }

+ 12 - 2
src/PixiEditor/ViewModels/Tools/Tools/VectorPathToolViewModel.cs

@@ -14,6 +14,8 @@ internal class VectorPathToolViewModel : ShapeTool, IVectorPathToolHandler
     public override Type LayerTypeToCreateOnEmptyUse { get; } = typeof(VectorLayerNode);
     public override LocalizedString Tooltip => new LocalizedString("PATH_TOOL_TOOLTIP", Shortcut);
 
+    public override bool StopsLinkedToolOnUse => false; 
+
     public VectorPathToolViewModel()
     {
         var fillSetting = Toolbar.GetSetting(nameof(BasicShapeToolbar.Fill));
@@ -25,9 +27,17 @@ internal class VectorPathToolViewModel : ShapeTool, IVectorPathToolHandler
 
     public override void UseTool(VecD pos)
     {
-        ViewModelMain.Current?.DocumentManagerSubViewModel.ActiveDocument?.Tools.UseVectorPathTool();
-    }
+        var doc =
+            ViewModelMain.Current?.DocumentManagerSubViewModel.ActiveDocument;
+        
+        if (doc is null) return;
 
+        if (!doc.PathOverlayViewModel.IsActive)
+        {
+            doc?.Tools.UseVectorPathTool();
+        }
+    }
+    
     public override void OnSelected(bool restoring)
     {
         if (restoring) return;

+ 6 - 0
src/PixiEditor/Views/Main/ViewportControls/ViewportOverlays.cs

@@ -346,8 +346,14 @@ internal class ViewportOverlays
         {
             Source = Viewport, Path = "Document.PathOverlayViewModel.Path", Mode = BindingMode.TwoWay
         };
+        
+        Binding addToUndoCommandBinding = new()
+        {
+            Source = Viewport, Path = "Document.PathOverlayViewModel.AddToUndoCommand", Mode = BindingMode.OneWay
+        };
 
         vectorPathOverlay.Bind(VectorPathOverlay.PathProperty, pathBinding);
+        vectorPathOverlay.Bind(VectorPathOverlay.AddToUndoCommandProperty, addToUndoCommandBinding);
     }
 
     private void BindSnappingOverlay()

+ 4 - 3
src/PixiEditor/Views/Overlays/Handles/Handle.cs

@@ -147,13 +147,14 @@ public abstract class Handle : IHandle
         if (isPressed)
         {
             isPressed = false;
-            OnRelease?.Invoke(this);
-            args.Pointer.Capture(null);
-            
             if (!moved)
             {
                 OnTap?.Invoke(this);
             }
+            
+            OnRelease?.Invoke(this);
+            args.Pointer.Capture(null);
+            
         }
     }
 }

+ 20 - 4
src/PixiEditor/Views/Overlays/PathOverlay/VectorPathOverlay.cs

@@ -1,4 +1,5 @@
-using Avalonia;
+using System.Windows.Input;
+using Avalonia;
 using Drawie.Backend.Core.ColorsImpl;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
 using Drawie.Backend.Core.Vector;
@@ -21,6 +22,15 @@ public class VectorPathOverlay : Overlay
         set => SetValue(PathProperty, value);
     }
 
+    public static readonly StyledProperty<ICommand> AddToUndoCommandProperty = AvaloniaProperty.Register<VectorPathOverlay, ICommand>(
+        nameof(AddToUndoCommand));
+
+    public ICommand AddToUndoCommand
+    {
+        get => GetValue(AddToUndoCommandProperty);
+        set => SetValue(AddToUndoCommandProperty, value);
+    }
+
     private DashedStroke dashedStroke = new DashedStroke();
 
     private List<AnchorHandle> pointsHandles = new List<AnchorHandle>();
@@ -73,6 +83,7 @@ public class VectorPathOverlay : Overlay
                 {
                     var handle = new AnchorHandle(this);
                     handle.OnDrag += HandleOnOnDrag;
+                    handle.OnRelease += OnHandleRelease;
                     handle.OnTap += OnHandleTap;
                     pointsHandles.Add(handle);
                     AddHandle(pointsHandles[i]);
@@ -84,9 +95,9 @@ public class VectorPathOverlay : Overlay
     private void OnHandleTap(Handle handle)
     {
         VectorPath newPath = new VectorPath(Path);
-        
-        if(IsLastHandle(handle)) return;
-        
+
+        if (IsLastHandle(handle)) return;
+
         if (IsFirstHandle(handle))
         {
             newPath.Close();
@@ -134,4 +145,9 @@ public class VectorPathOverlay : Overlay
 
         Path = newPath;
     }
+    
+    private void OnHandleRelease(Handle source)
+    {
+        AddToUndoCommand.Execute(Path);
+    }
 }