Jelajahi Sumber

Transform overlay undo changes for shapes

flabbet 8 bulan lalu
induk
melakukan
270a1c3408

+ 41 - 17
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/DrawableShapeToolExecutor.cs

@@ -1,8 +1,6 @@
 using ChunkyImageLib.DataHolders;
 using PixiEditor.Helpers.Extensions;
 using PixiEditor.ChangeableDocument.Actions;
-using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
-using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes.Data;
 using Drawie.Backend.Core.ColorsImpl;
 using PixiEditor.Models.Handlers;
 using PixiEditor.Models.Handlers.Toolbars;
@@ -10,7 +8,8 @@ using PixiEditor.Models.Handlers.Tools;
 using PixiEditor.Models.Tools;
 using Drawie.Numerics;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
-using PixiEditor.Models.DocumentModels.UpdateableChangeExecutors.Features;
+using PixiEditor.ViewModels.Document.TransformOverlays;
+using PixiEditor.Views.Overlays.TransformOverlay;
 
 namespace PixiEditor.Models.DocumentModels.UpdateableChangeExecutors;
 
@@ -35,10 +34,13 @@ internal abstract class DrawableShapeToolExecutor<T> : SimpleShapeToolExecutor w
     protected IFillableShapeToolbar toolbar;
     private IColorsHandler? colorsVM;
     private bool ignoreNextColorChange = false;
+    
+    protected abstract bool UseGlobalUndo { get; }
+    protected abstract bool ShowApplyButton { get; }
 
-    public override bool CanUndo => document.TransformHandler.HasUndo;
-    public override bool CanRedo => document.TransformHandler.HasRedo;
-
+    public override bool CanUndo => !UseGlobalUndo && document.TransformHandler.HasUndo;
+    public override bool CanRedo => !UseGlobalUndo && document.TransformHandler.HasRedo;
+    
     public override ExecutionState Start()
     {
         if (base.Start() == ExecutionState.Error)
@@ -55,7 +57,7 @@ internal abstract class DrawableShapeToolExecutor<T> : SimpleShapeToolExecutor w
             return ExecutionState.Error;
         if (!drawOnMask && member is not ILayerHandler)
             return ExecutionState.Error;
-
+        
         if (ActiveMode == ShapeToolMode.Drawing)
         {
             if (toolbar.SyncWithPrimaryColor)
@@ -68,7 +70,7 @@ internal abstract class DrawableShapeToolExecutor<T> : SimpleShapeToolExecutor w
             lastRect = new RectD(startDrawingPos, VecD.Zero);
 
             document!.TransformHandler.ShowTransform(TransformMode, false, new ShapeCorners((RectD)lastRect.Inflate(1)),
-                false);
+                false, UseGlobalUndo ? AddToUndo : null);
             document.TransformHandler.ShowHandles = false;
             document.TransformHandler.IsSizeBoxEnabled = true;
             document.TransformHandler.CanAlignToPixels = AlignToPixels;
@@ -91,6 +93,11 @@ internal abstract class DrawableShapeToolExecutor<T> : SimpleShapeToolExecutor w
             toolbar.ToolSize = shapeData.StrokeWidth;
             toolbar.Fill = shapeData.FillColor != Colors.Transparent;
             initialCorners = shapeData.TransformationCorners;
+            
+            ShapeCorners corners = vectorLayerHandler.TransformationCorners;
+            document.TransformHandler.ShowTransform(
+                DocumentTransformMode.Scale_Rotate_Shear_NoPerspective, false, corners, false, UseGlobalUndo ? AddToUndo : null);
+            document.TransformHandler.CanAlignToPixels = false;
 
             ActiveMode = ShapeToolMode.Transform;
         }
@@ -118,8 +125,8 @@ internal abstract class DrawableShapeToolExecutor<T> : SimpleShapeToolExecutor w
         VecI pos2 = (VecI)(((VecD)curPos).ProjectOntoLine(startPos, startPos + new VecD(1, -1)) -
                            new VecD(0.25).Multiply((curPos - startPos).Signs())).Round();
         if ((pos1 - curPos).LengthSquared > (pos2 - curPos).LengthSquared)
-            return (VecI)pos2;
-        return (VecI)pos1;
+            return pos2;
+        return pos1;
     }
 
     public static VecD GetSquaredPosition(VecD startPos, VecD curPos)
@@ -138,13 +145,19 @@ internal abstract class DrawableShapeToolExecutor<T> : SimpleShapeToolExecutor w
         if (ActiveMode != ShapeToolMode.Transform)
             return;
 
+        var shapeData = ShapeDataFromCorners(corners);
+        IAction drawAction = TransformMovedAction(shapeData, corners);
+
+        internals!.ActionAccumulator.AddActions(drawAction);
+    }
+
+    private ShapeData ShapeDataFromCorners(ShapeCorners corners)
+    {
         var rect = RectD.FromCenterAndSize(corners.RectCenter, corners.RectSize);
         ShapeData shapeData = new ShapeData(rect.Center, rect.Size, corners.RectRotation, (float)StrokeWidth,
             StrokeColor,
             FillColor) { AntiAliasing = toolbar.AntiAliasing };
-        IAction drawAction = TransformMovedAction(shapeData, corners);
-
-        internals!.ActionAccumulator.AddActions(drawAction);
+        return shapeData;
     }
 
     public override void OnTransformApplied()
@@ -190,14 +203,16 @@ internal abstract class DrawableShapeToolExecutor<T> : SimpleShapeToolExecutor w
     {
         if (ActiveMode != ShapeToolMode.Transform)
             return;
-        document!.TransformHandler.Undo();
+
+        document.TransformHandler.Undo();
     }
 
     public override void OnMidChangeRedo()
     {
         if (ActiveMode != ShapeToolMode.Transform)
             return;
-        document!.TransformHandler.Redo();
+
+        document.TransformHandler.Redo();
     }
 
     protected override void PrecisePositionChangeDrawingMode(VecD pos)
@@ -240,7 +255,7 @@ internal abstract class DrawableShapeToolExecutor<T> : SimpleShapeToolExecutor w
 
         startDrawingPos = startPos;
 
-        document!.TransformHandler.ShowTransform(TransformMode, false, new ShapeCorners((RectD)lastRect), false);
+        document!.TransformHandler.ShowTransform(TransformMode, false, new ShapeCorners((RectD)lastRect), false, UseGlobalUndo ? AddToUndo : null);
         document.TransformHandler.CanAlignToPixels = AlignToPixels;
         document!.TransformHandler.Corners = new ShapeCorners((RectD)lastRect);
     }
@@ -318,6 +333,7 @@ internal abstract class DrawableShapeToolExecutor<T> : SimpleShapeToolExecutor w
         if (CanEditShape(layer))
         {
             internals!.ActionAccumulator.AddActions(SettingsChangedAction());
+            // TODO add to undo
         }
     }
 
@@ -354,7 +370,7 @@ internal abstract class DrawableShapeToolExecutor<T> : SimpleShapeToolExecutor w
         if (mode == ShapeToolMode.Transform)
         {
             document.TransformHandler.HideTransform();
-            document!.TransformHandler.ShowTransform(TransformMode, false, initialCorners, true);
+            document!.TransformHandler.ShowTransform(TransformMode, false, initialCorners, ShowApplyButton, UseGlobalUndo ? AddToUndo : null);
             document.TransformHandler.CanAlignToPixels = AlignToPixels;
         }
     }
@@ -369,4 +385,12 @@ internal abstract class DrawableShapeToolExecutor<T> : SimpleShapeToolExecutor w
     {
         document!.TransformHandler.HideTransform();
     }
+    
+    private void AddToUndo(ShapeCorners corners)
+    {
+        if (UseGlobalUndo)
+        {
+            internals!.ActionAccumulator.AddFinishedActions(EndDrawAction(), TransformMovedAction(ShapeDataFromCorners(corners), corners), EndDrawAction());
+        }
+    }
 }

+ 2 - 0
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/RasterEllipseToolExecutor.cs

@@ -29,6 +29,8 @@ internal class RasterEllipseToolExecutor : DrawableShapeToolExecutor<IRasterElli
 
     public override ExecutorType Type => ExecutorType.ToolLinked;
     protected override DocumentTransformMode TransformMode => DocumentTransformMode.Scale_Rotate_NoShear_NoPerspective;
+    protected override bool UseGlobalUndo => false;
+    protected override bool ShowApplyButton => true;
     protected override void DrawShape(VecD currentPos, double rotationRad, bool firstDraw) => DrawEllipseOrCircle(currentPos, rotationRad, firstDraw);
     protected override IAction SettingsChangedAction()
     {

+ 3 - 0
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/RasterRectangleToolExecutor.cs

@@ -35,6 +35,9 @@ internal class RasterRectangleToolExecutor : DrawableShapeToolExecutor<IRasterRe
             document!.AnimationHandler.ActiveFrameBindable));
     }
 
+    protected override bool UseGlobalUndo => false;
+    protected override bool ShowApplyButton => true;
+
     protected override void DrawShape(VecD currentPos, double rotationRad, bool first) =>
         DrawRectangle(currentPos, rotationRad, first);
 

+ 15 - 4
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/VectorEllipseToolExecutor.cs

@@ -8,6 +8,7 @@ using PixiEditor.Models.Handlers.Tools;
 using PixiEditor.Models.Tools;
 using Drawie.Numerics;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+using PixiEditor.Models.DocumentModels.UpdateableChangeExecutors.Features;
 using PixiEditor.Models.Handlers;
 
 namespace PixiEditor.Models.DocumentModels.UpdateableChangeExecutors;
@@ -41,11 +42,14 @@ internal class VectorEllipseToolExecutor : DrawableShapeToolExecutor<IVectorElli
         IVectorLayerHandler vectorLayer = layer as IVectorLayerHandler;
         if (vectorLayer is null)
             return false;
-        
+
         var shapeData = vectorLayer.GetShapeData(document.AnimationHandler.ActiveFrameTime);
         return shapeData is EllipseVectorData;
     }
 
+    protected override bool UseGlobalUndo => true;
+    protected override bool ShowApplyButton => false;
+
     protected override void DrawShape(VecD curPos, double rotationRad, bool firstDraw)
     {
         RectD rect;
@@ -90,9 +94,10 @@ internal class VectorEllipseToolExecutor : DrawableShapeToolExecutor<IVectorElli
         {
             firstCenter = corners.RectCenter;
             firstRadius = corners.RectSize / 2f;
-            
-            if(corners.RectRotation != 0)
-                matrix = Matrix3X3.CreateRotation((float)corners.RectRotation, (float)firstCenter.X, (float)firstCenter.Y);
+
+            if (corners.RectRotation != 0)
+                matrix = Matrix3X3.CreateRotation((float)corners.RectRotation, (float)firstCenter.X,
+                    (float)firstCenter.Y);
         }
         else
         {
@@ -119,4 +124,10 @@ internal class VectorEllipseToolExecutor : DrawableShapeToolExecutor<IVectorElli
     {
         return new EndSetShapeGeometry_Action();
     }
+
+    public override bool IsFeatureEnabled(IExecutorFeature feature)
+    {
+        if(feature is IMidChangeUndoableExecutor) return false;
+        return base.IsFeatureEnabled(feature);
+    }
 }

+ 10 - 0
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/VectorRectangleToolExecutor.cs

@@ -8,6 +8,7 @@ using PixiEditor.Models.Handlers.Tools;
 using PixiEditor.Models.Tools;
 using Drawie.Numerics;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+using PixiEditor.Models.DocumentModels.UpdateableChangeExecutors.Features;
 using PixiEditor.Models.Handlers;
 
 namespace PixiEditor.Models.DocumentModels.UpdateableChangeExecutors;
@@ -45,6 +46,9 @@ internal class VectorRectangleToolExecutor : DrawableShapeToolExecutor<IVectorRe
         return shapeData is RectangleVectorData;
     }
 
+    protected override bool UseGlobalUndo => true;
+    protected override bool ShowApplyButton => false;
+
     protected override void DrawShape(VecD curPos, double rotationRad, bool firstDraw)
     {
         RectD rect;
@@ -127,4 +131,10 @@ internal class VectorRectangleToolExecutor : DrawableShapeToolExecutor<IVectorRe
     {
         return new EndSetShapeGeometry_Action();
     }
+
+    public override bool IsFeatureEnabled(IExecutorFeature feature)
+    {
+        if (feature is IMidChangeUndoableExecutor) return false;
+        return base.IsFeatureEnabled(feature);
+    }
 }

+ 2 - 1
src/PixiEditor/Models/Handlers/ITransformHandler.cs

@@ -3,13 +3,14 @@ using Drawie.Backend.Core.Numerics;
 using PixiEditor.Models.DocumentModels;
 using Drawie.Numerics;
 using PixiEditor.Models.Controllers.InputDevice;
+using PixiEditor.Views.Overlays.TransformOverlay;
 
 namespace PixiEditor.Models.Handlers;
 
 internal interface ITransformHandler : IHandler
 {
     public void KeyModifiersInlet(bool argsIsShiftDown, bool argsIsCtrlDown, bool argsIsAltDown);
-    public void ShowTransform(DocumentTransformMode transformMode, bool coverWholeScreen, ShapeCorners shapeCorners, bool showApplyButton);
+    public void ShowTransform(DocumentTransformMode transformMode, bool coverWholeScreen, ShapeCorners shapeCorners, bool showApplyButton, Action<ShapeCorners>? customAddToUndo = null);
     public void HideTransform();
     public bool Undo();
     public bool Redo();

+ 30 - 25
src/PixiEditor/ViewModels/Document/TransformOverlays/DocumentTransformViewModel.cs

@@ -101,8 +101,9 @@ internal class DocumentTransformViewModel : ObservableObject, ITransformHandler
         get => showTransformControls;
         set => SetProperty(ref showTransformControls, value);
     }
-    
+
     private bool canAlignToPixels = true;
+
     public bool CanAlignToPixels
     {
         get => canAlignToPixels;
@@ -147,14 +148,6 @@ internal class DocumentTransformViewModel : ObservableObject, ITransformHandler
         set => SetProperty(ref isSizeBoxEnabled, value);
     }
 
-    private bool enableSnapping = true;
-
-    public bool EnableSnapping
-    {
-        get => enableSnapping;
-        set => SetProperty(ref enableSnapping, value);
-    }
-
 
     private ExecutionTrigger<ShapeCorners> requestedCornersExecutor;
 
@@ -172,6 +165,14 @@ internal class DocumentTransformViewModel : ObservableObject, ITransformHandler
         set => SetProperty(ref actionCompletedCommand, value);
     }
 
+    private ICommand? addToUndoCommand = null;
+
+    public ICommand? AddToUndoCommand
+    {
+        get => addToUndoCommand;
+        set => SetProperty(ref addToUndoCommand, value);
+    }
+
     private RelayCommand<MouseOnCanvasEventArgs>? passThroughPointerPressedCommand;
 
     public RelayCommand<MouseOnCanvasEventArgs> PassThroughPointerPressedCommand
@@ -192,6 +193,8 @@ internal class DocumentTransformViewModel : ObservableObject, ITransformHandler
         this.document = document;
         ActionCompletedCommand = new RelayCommand(() =>
         {
+            AddToUndoCommand?.Execute(Corners);
+
             if (undoStack is null)
                 return;
 
@@ -230,12 +233,13 @@ internal class DocumentTransformViewModel : ObservableObject, ITransformHandler
 
     public bool Nudge(VecD distance)
     {
-        if (undoStack is null)
-            return false;
-
         InternalState = InternalState with { Origin = InternalState.Origin + distance };
         Corners = Corners.AsTranslated(distance);
-        undoStack.AddState((Corners, InternalState), TransformOverlayStateType.Nudge);
+        
+        AddToUndoCommand?.Execute(Corners);
+
+        undoStack?.AddState((Corners, InternalState), TransformOverlayStateType.Nudge);
+
         return true;
     }
 
@@ -244,20 +248,16 @@ internal class DocumentTransformViewModel : ObservableObject, ITransformHandler
 
     public void HideTransform()
     {
-        if (undoStack is null)
-            return;
         undoStack = null;
-
         TransformActive = false;
         ShowTransformControls = false;
     }
 
     public void ShowTransform(DocumentTransformMode mode, bool coverWholeScreen, ShapeCorners initPos,
-        bool showApplyButton)
+        bool showApplyButton, Action<ShapeCorners>? customAddToUndo = null)
     {
-        if (undoStack is not null || initPos.IsPartiallyDegenerate)
+        if (initPos.IsPartiallyDegenerate)
             return;
-        undoStack = new();
 
         activeTransformMode = mode;
         CornerFreedom = TransformCornerFreedom.Scale;
@@ -272,7 +272,17 @@ internal class DocumentTransformViewModel : ObservableObject, ITransformHandler
         ShowHandles = true;
 
         RequestCornersExecutor?.Execute(this, initPos);
-        undoStack.AddState((Corners, InternalState), TransformOverlayStateType.Initial);
+
+        if (customAddToUndo is not null)
+        {
+            AddToUndoCommand = new RelayCommand<ShapeCorners>(customAddToUndo);
+            undoStack = null;
+        }
+        else
+        {
+            undoStack = new TransformOverlayUndoStack<(ShapeCorners, TransformState)>();
+            undoStack.AddState((Corners, InternalState), TransformOverlayStateType.Initial);
+        }
     }
 
     public void KeyModifiersInlet(bool isShiftDown, bool isCtrlDown, bool isAltDown)
@@ -291,11 +301,6 @@ internal class DocumentTransformViewModel : ObservableObject, ITransformHandler
             requestedCornerFreedom = TransformCornerFreedom.Free;
             requestedSideFreedom = TransformSideFreedom.Free;
         }
-        /*else if (isAltDown)
-        {
-        TODO: Add shear to the transform overlay
-            requestedSideFreedom = TransformSideFreedom.Shear;
-        }*/
         else
         {
             requestedCornerFreedom = TransformCornerFreedom.Scale;

+ 1 - 0
src/PixiEditor/ViewModels/SubViewModels/UndoViewModel.cs

@@ -24,6 +24,7 @@ internal class UndoViewModel : SubViewModel<ViewModelMain>
         var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
         if (doc is null || (!doc.IsChangeFeatureActive<IMidChangeUndoableExecutor>() && !doc.HasSavedRedo))
             return;
+        
         doc.Operations.Redo();
         doc.Operations.InvokeCustomAction(
             () =>

+ 12 - 1
src/PixiEditor/ViewModels/Tools/ShapeTool.cs

@@ -15,6 +15,8 @@ internal abstract class ShapeTool : ToolViewModel, IShapeToolHandler
     public override bool IsErasable => true;
     public bool DrawEven { get; protected set; }
     public bool DrawFromCenter { get; protected set; }
+    
+    protected bool isActivated;
 
     public ShapeTool()
     {
@@ -22,12 +24,21 @@ internal abstract class ShapeTool : ToolViewModel, IShapeToolHandler
         Toolbar = new FillableShapeToolbar();
     }
 
+    public override void OnSelected(bool restoring)
+    {
+        base.OnSelected(restoring);
+        if (!restoring)
+        {
+            isActivated = true;
+        }
+    }
+
     public override void OnDeselecting(bool transient)
     {
         if (!transient)
         {
             ViewModelMain.Current.DocumentManagerSubViewModel.ActiveDocument?.Operations.TryStopToolLinkedExecutor();
+            isActivated = false;
         }
     }
-
 }

+ 16 - 10
src/PixiEditor/ViewModels/Tools/Tools/VectorEllipseToolViewModel.cs

@@ -35,6 +35,7 @@ internal class VectorEllipseToolViewModel : ShapeTool, IVectorEllipseToolHandler
     public override Type LayerTypeToCreateOnEmptyUse { get; } = typeof(VectorLayerNode);
 
     public string? DefaultNewLayerName { get; } = new LocalizedString(NewLayerKey);
+    
 
     public override void UseTool(VecD pos)
     {
@@ -61,19 +62,24 @@ internal class VectorEllipseToolViewModel : ShapeTool, IVectorEllipseToolHandler
     {
         if (restoring) return;
 
-        var document = ViewModelMain.Current?.DocumentManagerSubViewModel.ActiveDocument;
-        var layer = document.SelectedStructureMember;
-        if (layer is IVectorLayerHandler vectorLayer &&
-            vectorLayer.GetShapeData(document.AnimationDataViewModel.ActiveFrameTime) is IReadOnlyEllipseData)
-        {
-            ShapeCorners corners = vectorLayer.TransformationCorners;
-            document.TransformViewModel.ShowTransform(
-                DocumentTransformMode.Scale_Rotate_Shear_NoPerspective, false, corners, false);
+        ViewModelMain.Current?.DocumentManagerSubViewModel.ActiveDocument?.Tools.UseVectorEllipseTool();
+        isActivated = true;
+    }
 
-            document.TransformViewModel.CanAlignToPixels = false;
+    public override void OnPostUndo()
+    {
+        if (isActivated)
+        {
+            OnSelected(false);
         }
+    }
 
-        ViewModelMain.Current?.DocumentManagerSubViewModel.ActiveDocument?.Tools.UseVectorEllipseTool();
+    public override void OnPostRedo()
+    {
+        if (isActivated)
+        {
+            OnSelected(false);
+        }
     }
 
     protected override void OnSelectedLayersChanged(IStructureMemberHandler[] layers)

+ 15 - 11
src/PixiEditor/ViewModels/Tools/Tools/VectorRectangleToolViewModel.cs

@@ -59,20 +59,24 @@ internal class VectorRectangleToolViewModel : ShapeTool, IVectorRectangleToolHan
     {
         if (restoring) return;
 
-        var document = ViewModelMain.Current?.DocumentManagerSubViewModel.ActiveDocument;
-        var layer = document.SelectedStructureMember;
-        if (layer is IVectorLayerHandler vectorLayer &&
-            vectorLayer.GetShapeData(document.AnimationDataViewModel.ActiveFrameTime) is IReadOnlyRectangleData)
+        ViewModelMain.Current?.DocumentManagerSubViewModel.ActiveDocument?.Tools.UseVectorRectangleTool();
+        isActivated = true;
+    }
+
+    public override void OnPostUndo()
+    {
+        if (isActivated)
         {
-            ShapeCorners corners = vectorLayer.TransformationCorners;
-            var transformVm = ViewModelMain.Current?.DocumentManagerSubViewModel.ActiveDocument.TransformViewModel;
-            transformVm.ShowTransform(
-                DocumentTransformMode.Scale_Rotate_Shear_NoPerspective, false, corners, false);
-            
-            transformVm.CanAlignToPixels = false;
+            OnSelected(false);
         }
+    }
 
-        ViewModelMain.Current?.DocumentManagerSubViewModel.ActiveDocument?.Tools.UseVectorRectangleTool();
+    public override void OnPostRedo()
+    {
+        if (isActivated)
+        {
+            OnSelected(false);
+        }
     }
 
     protected override void OnSelectedLayersChanged(IStructureMemberHandler[] layers)

+ 0 - 2
src/PixiEditor/Views/Overlays/TransformOverlay/TransformOverlay.cs

@@ -179,8 +179,6 @@ internal class TransformOverlay : Overlay
         RequestCornersExecutorProperty.Changed.Subscribe(OnCornersExecutorChanged);
     }
 
-    private const int anchorSizeMultiplierForRotation = 15;
-
     private bool isMoving = false;
     private VecD mousePosOnStartMove = new();
     private VecD originOnStartMove = new();