Browse Source

Reworked undo for vector shapes

Krzysztof Krysiński 4 months ago
parent
commit
9c1bd77150
21 changed files with 433 additions and 52 deletions
  1. 1 1
      src/Drawie
  2. 30 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/EllipseVectorData.cs
  3. 29 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/LineVectorData.cs
  4. 30 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/PathVectorData.cs
  5. 30 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/PointsVectorData.cs
  6. 30 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/RectangleVectorData.cs
  7. 26 2
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/ShapeVectorData.cs
  8. 58 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/TextVectorData.cs
  9. 43 4
      src/PixiEditor.ChangeableDocument/Changes/Vectors/SetShapeGeometry_UpdateableChange.cs
  10. 38 16
      src/PixiEditor.ChangeableDocument/DocumentChangeTracker.cs
  11. 28 3
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/DrawableShapeToolExecutor.cs
  12. 3 3
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/LineExecutor.cs
  13. 1 1
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/RasterEllipseToolExecutor.cs
  14. 1 1
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/RasterLineToolExecutor.cs
  15. 1 1
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/RasterRectangleToolExecutor.cs
  16. 15 4
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/VectorEllipseToolExecutor.cs
  17. 21 4
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/VectorLineToolExecutor.cs
  18. 13 3
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/VectorPathToolExecutor.cs
  19. 16 4
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/VectorRectangleToolExecutor.cs
  20. 18 4
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/VectorTextToolExecutor.cs
  21. 1 1
      src/PixiEditor/Views/Overlays/TextOverlay/TextOverlay.cs

+ 1 - 1
src/Drawie

@@ -1 +1 @@
-Subproject commit 4037b56fc0accf094d10fd445f7ff93ed23131f5
+Subproject commit 02c8bda10d6536f1d8a613ef877526756a789132

+ 30 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/EllipseVectorData.cs

@@ -98,4 +98,34 @@ public class EllipseVectorData : ShapeVectorData, IReadOnlyEllipseData
 
         return path;
     }
+
+    protected bool Equals(EllipseVectorData other)
+    {
+        return base.Equals(other) && Radius.Equals(other.Radius) && Center.Equals(other.Center);
+    }
+
+    public override bool Equals(object? obj)
+    {
+        if (obj is null)
+        {
+            return false;
+        }
+
+        if (ReferenceEquals(this, obj))
+        {
+            return true;
+        }
+
+        if (obj.GetType() != GetType())
+        {
+            return false;
+        }
+
+        return Equals((EllipseVectorData)obj);
+    }
+
+    public override int GetHashCode()
+    {
+        return HashCode.Combine(base.GetHashCode(), Radius, Center);
+    }
 }

+ 29 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/LineVectorData.cs

@@ -121,4 +121,33 @@ public class LineVectorData : ShapeVectorData, IReadOnlyLineData
         return path;
     }
 
+    protected bool Equals(LineVectorData other)
+    {
+        return base.Equals(other) && Start.Equals(other.Start) && End.Equals(other.End);
+    }
+
+    public override bool Equals(object? obj)
+    {
+        if (obj is null)
+        {
+            return false;
+        }
+
+        if (ReferenceEquals(this, obj))
+        {
+            return true;
+        }
+
+        if (obj.GetType() != GetType())
+        {
+            return false;
+        }
+
+        return Equals((LineVectorData)obj);
+    }
+
+    public override int GetHashCode()
+    {
+        return HashCode.Combine(base.GetHashCode(), Start, End);
+    }
 }

+ 30 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/PathVectorData.cs

@@ -115,4 +115,34 @@ public class PathVectorData : ShapeVectorData, IReadOnlyPathData
 
         return newPath;
     }
+
+    protected bool Equals(PathVectorData other)
+    {
+        return base.Equals(other) && Path.Equals(other.Path) && StrokeLineCap == other.StrokeLineCap && StrokeLineJoin == other.StrokeLineJoin;
+    }
+
+    public override bool Equals(object? obj)
+    {
+        if (obj is null)
+        {
+            return false;
+        }
+
+        if (ReferenceEquals(this, obj))
+        {
+            return true;
+        }
+
+        if (obj.GetType() != GetType())
+        {
+            return false;
+        }
+
+        return Equals((PathVectorData)obj);
+    }
+
+    public override int GetHashCode()
+    {
+        return HashCode.Combine(base.GetHashCode(), Path, (int)StrokeLineCap, (int)StrokeLineJoin);
+    }
 }

+ 30 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/PointsVectorData.cs

@@ -92,4 +92,34 @@ public class PointsVectorData : ShapeVectorData
 
         return path;
     }
+
+    protected bool Equals(PointsVectorData other)
+    {
+        return base.Equals(other) && (Points.Equals(other.Points) || Points.SequenceEqual(other.Points));
+    }
+
+    public override bool Equals(object? obj)
+    {
+        if (obj is null)
+        {
+            return false;
+        }
+
+        if (ReferenceEquals(this, obj))
+        {
+            return true;
+        }
+
+        if (obj.GetType() != GetType())
+        {
+            return false;
+        }
+
+        return Equals((PointsVectorData)obj);
+    }
+
+    public override int GetHashCode()
+    {
+        return HashCode.Combine(base.GetHashCode(), Points);
+    }
 }

+ 30 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/RectangleVectorData.cs

@@ -106,4 +106,34 @@ public class RectangleVectorData : ShapeVectorData, IReadOnlyRectangleData
 
         return path;
     }
+
+    protected bool Equals(RectangleVectorData other)
+    {
+        return base.Equals(other) && Center.Equals(other.Center) && Size.Equals(other.Size);
+    }
+
+    public override bool Equals(object? obj)
+    {
+        if (obj is null)
+        {
+            return false;
+        }
+
+        if (ReferenceEquals(this, obj))
+        {
+            return true;
+        }
+
+        if (obj.GetType() != GetType())
+        {
+            return false;
+        }
+
+        return Equals((RectangleVectorData)obj);
+    }
+
+    public override int GetHashCode()
+    {
+        return HashCode.Combine(base.GetHashCode(), Center, Size);
+    }
 }

+ 26 - 2
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/ShapeVectorData.cs

@@ -15,7 +15,6 @@ public abstract class ShapeVectorData : ICacheable, ICloneable, IReadOnlyShapeVe
     private float strokeWidth = 0;
 
     public Matrix3X3 TransformationMatrix { get; set; } = Matrix3X3.Identity;
-
     public Paintable Stroke { get; set; } = Colors.White;
     public Paintable FillPaintable { get; set; } = Colors.White;
 
@@ -78,8 +77,33 @@ public abstract class ShapeVectorData : ICacheable, ICloneable, IReadOnlyShapeVe
 
     public override int GetHashCode()
     {
-        return GetCacheHash();
+        return HashCode.Combine(TransformationMatrix, Stroke, FillPaintable, Fill);
     }
 
     public abstract VectorPath ToPath(bool transformed = false);
+
+    protected bool Equals(ShapeVectorData other)
+    {
+        return TransformationMatrix.Equals(other.TransformationMatrix) && Stroke.Equals(other.Stroke) && FillPaintable.Equals(other.FillPaintable) && Fill == other.Fill && StrokeWidth.Equals(other.StrokeWidth);
+    }
+
+    public override bool Equals(object? obj)
+    {
+        if (obj is null)
+        {
+            return false;
+        }
+
+        if (ReferenceEquals(this, obj))
+        {
+            return true;
+        }
+
+        if (obj.GetType() != GetType())
+        {
+            return false;
+        }
+
+        return Equals((ShapeVectorData)obj);
+    }
 }

+ 58 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/TextVectorData.cs

@@ -12,6 +12,8 @@ namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes.Data;
 
 public class TextVectorData : ShapeVectorData, IReadOnlyTextData, IScalable
 {
+    private bool bold;
+    private bool italic;
     private string text;
     private Font font = Font.CreateDefault();
     private double? spacing = null;
@@ -55,6 +57,28 @@ public class TextVectorData : ShapeVectorData, IReadOnlyTextData, IScalable
         }
     }
 
+    public bool Bold
+    {
+        get => bold;
+        set
+        {
+            bold = value;
+            Font.Bold = value;
+            lastBounds = richText.MeasureBounds(Font);
+        }
+    }
+
+    public bool Italic
+    {
+        get => italic;
+        set
+        {
+            italic = value;
+            Font.Italic = value;
+            lastBounds = richText.MeasureBounds(Font);
+        }
+    }
+
     private void FontChanged()
     {
         if (richText == null)
@@ -235,6 +259,9 @@ public class TextVectorData : ShapeVectorData, IReadOnlyTextData, IScalable
         hash.Add(AntiAlias);
         hash.Add(MissingFontFamily);
         hash.Add(MissingFontText);
+        hash.Add(MaxWidth);
+        hash.Add(Bold);
+        hash.Add(Italic);
 
         return hash.ToHashCode();
     }
@@ -257,4 +284,35 @@ public class TextVectorData : ShapeVectorData, IReadOnlyTextData, IScalable
 
         lastBounds = richText.MeasureBounds(Font);
     }
+
+    protected bool Equals(TextVectorData other)
+    {
+        return base.Equals(other) && Position.Equals(other.Position) && MaxWidth.Equals(other.MaxWidth) && AntiAlias == other.AntiAlias && Nullable.Equals(MissingFontFamily, other.MissingFontFamily) && MissingFontText == other.MissingFontText
+            && Text == other.Text && Font.Equals(other.Font) && Spacing.Equals(other.Spacing) && Path == other.Path && Bold == other.Bold && Italic == other.Italic;
+    }
+
+    public override bool Equals(object? obj)
+    {
+        if (obj is null)
+        {
+            return false;
+        }
+
+        if (ReferenceEquals(this, obj))
+        {
+            return true;
+        }
+
+        if (obj.GetType() != GetType())
+        {
+            return false;
+        }
+
+        return Equals((TextVectorData)obj);
+    }
+
+    public override int GetHashCode()
+    {
+        return HashCode.Combine(base.GetHashCode(), Position, MaxWidth, AntiAlias, MissingFontFamily, MissingFontText, Font, HashCode.Combine(Text, Spacing, Path));
+    }
 }

+ 43 - 4
src/PixiEditor.ChangeableDocument/Changes/Vectors/SetShapeGeometry_UpdateableChange.cs

@@ -10,23 +10,29 @@ internal class SetShapeGeometry_UpdateableChange : InterruptableUpdateableChange
 {
     public Guid TargetId { get; set; }
     public ShapeVectorData Data { get; set; }
+    public VectorShapeChangeType ChangeType { get; set; }
 
     private ShapeVectorData? originalData;
 
     private AffectedArea lastAffectedArea;
 
     [GenerateUpdateableChangeActions]
-    public SetShapeGeometry_UpdateableChange(Guid targetId, ShapeVectorData data)
+    public SetShapeGeometry_UpdateableChange(Guid targetId, ShapeVectorData data, VectorShapeChangeType changeType)
     {
         TargetId = targetId;
         Data = data;
+        ChangeType = changeType;
     }
 
     public override bool InitializeAndValidate(Document target)
     {
         if (target.TryFindNode<VectorLayerNode>(TargetId, out var node))
         {
-            // TODO: Add is identical check
+            if (IsIdentical(node.ShapeData, Data))
+            {
+                return false;
+            }
+
             originalData = (ShapeVectorData?)node.ShapeData?.Clone();
             return true;
         }
@@ -45,7 +51,7 @@ internal class SetShapeGeometry_UpdateableChange : InterruptableUpdateableChange
         var node = target.FindNode<VectorLayerNode>(TargetId);
 
         node.ShapeData = Data;
-        
+
         RectD aabb = node.ShapeData.TransformedAABB.RoundOutwards();
         aabb = aabb with { Size = new VecD(Math.Max(1, aabb.Size.X), Math.Max(1, aabb.Size.Y)) };
 
@@ -99,13 +105,46 @@ internal class SetShapeGeometry_UpdateableChange : InterruptableUpdateableChange
         return new VectorShape_ChangeInfo(node.Id, affected);
     }
 
+    private bool IsIdentical(ShapeVectorData? a, ShapeVectorData? b)
+    {
+        if (a is null && b is null)
+        {
+            return true;
+        }
+
+        if (a is null || b is null)
+        {
+            return false;
+        }
+
+        if (a.GetType() != b.GetType())
+        {
+            return false;
+        }
+
+        return a.Equals(b);
+    }
+
     public override bool IsMergeableWith(Change other)
     {
         if (other is SetShapeGeometry_UpdateableChange change)
         {
-            return change.TargetId == TargetId && change.Data is not TextVectorData; // text should not be merged into one change
+            return change.TargetId == TargetId &&
+                   !ChangeType.HasFlag(VectorShapeChangeType.GeometryData) && ChangeType.HasFlag(change.ChangeType) &&
+                   change.Data is not TextVectorData; // text should not be merged into one change
         }
 
         return false;
     }
 }
+
+[Flags]
+public enum VectorShapeChangeType
+{
+    GeometryData = 1,
+    Stroke = 2,
+    Fill = 4,
+    TransformationMatrix = 8,
+    OtherVisuals = 16,
+    All = ~0
+}

+ 38 - 16
src/PixiEditor.ChangeableDocument/DocumentChangeTracker.cs

@@ -135,17 +135,28 @@ public class DocumentChangeTracker : IDisposable
         }
 
         List<IChangeInfo> changeInfos = new();
-        var changePacket = undoStack.Pop();
+        bool handledUserChange = false;
 
-        for (int i = changePacket.changes.Count - 1; i >= 0; i--)
+        (ActionSource source, List<Change> changes) changePacket;
+        do
         {
-            changePacket.changes[i].Revert(document).Switch(
-                (None _) => { },
-                (IChangeInfo info) => changeInfos.Add(info),
-                (List<IChangeInfo> infos) => changeInfos.AddRange(infos));
-        }
+            changePacket = undoStack.Pop();
+            for (int i = changePacket.changes.Count - 1; i >= 0; i--)
+            {
+                changePacket.changes[i].Revert(document).Switch(
+                    (None _) => { },
+                    (IChangeInfo info) => changeInfos.Add(info),
+                    (List<IChangeInfo> infos) => changeInfos.AddRange(infos));
+            }
+
+            if (changePacket.source == ActionSource.User)
+                handledUserChange = true;
+
+            redoStack.Push(changePacket);
+        } while (undoStack.Count > 0 &&
+                 ((changePacket.source == ActionSource.Automated && !handledUserChange)
+                  || undoStack.Peek().source == ActionSource.Automated));
 
-        redoStack.Push(changePacket);
         return changeInfos;
     }
 
@@ -161,17 +172,28 @@ public class DocumentChangeTracker : IDisposable
         }
 
         List<IChangeInfo> changeInfos = new();
-        var changePacket = redoStack.Pop();
+        (ActionSource source, List<Change> changes) changePacket;
+        bool handledUserChange = false;
 
-        for (int i = 0; i < changePacket.changes.Count; i++)
+        do
         {
-            changePacket.changes[i].Apply(document, false, out _).Switch(
-                (None _) => { },
-                (IChangeInfo info) => changeInfos.Add(info),
-                (List<IChangeInfo> infos) => changeInfos.AddRange(infos));
-        }
+            changePacket = redoStack.Pop();
+            for (int i = 0; i < changePacket.changes.Count; i++)
+            {
+                changePacket.changes[i].Apply(document, false, out _).Switch(
+                    (None _) => { },
+                    (IChangeInfo info) => changeInfos.Add(info),
+                    (List<IChangeInfo> infos) => changeInfos.AddRange(infos));
+            }
+
+            if (changePacket.source == ActionSource.User)
+                handledUserChange = true;
+
+            undoStack.Push(changePacket);
+        } while (redoStack.Count > 0
+                 && ((changePacket.source == ActionSource.Automated && !handledUserChange)
+                     || redoStack.Peek().source == ActionSource.Automated));
 
-        undoStack.Push(changePacket);
         return changeInfos;
     }
 

+ 28 - 3
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/DrawableShapeToolExecutor.cs

@@ -10,7 +10,10 @@ using PixiEditor.Models.Handlers.Toolbars;
 using PixiEditor.Models.Handlers.Tools;
 using PixiEditor.Models.Tools;
 using Drawie.Numerics;
+using PixiEditor.ChangeableDocument;
+using PixiEditor.ChangeableDocument.Actions.Generated;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+using PixiEditor.ChangeableDocument.Changes.Vectors;
 using PixiEditor.ViewModels.Document.TransformOverlays;
 using PixiEditor.Views.Overlays.TransformOverlay;
 using Color = Drawie.Backend.Core.ColorsImpl.Color;
@@ -44,6 +47,8 @@ internal abstract class DrawableShapeToolExecutor<T> : SimpleShapeToolExecutor w
     private IColorsHandler? colorsVM;
     private bool ignoreNextColorChange = false;
 
+    private bool preventSettingsChange = false;
+
     protected abstract bool UseGlobalUndo { get; }
     protected abstract bool ShowApplyButton { get; }
 
@@ -119,7 +124,7 @@ internal abstract class DrawableShapeToolExecutor<T> : SimpleShapeToolExecutor w
     }
 
     protected abstract void DrawShape(VecD currentPos, double rotationRad, bool firstDraw);
-    protected abstract IAction SettingsChangedAction();
+    protected abstract IAction SettingsChangedAction(string name, object value);
     protected abstract IAction TransformMovedAction(ShapeData data, ShapeCorners corners);
     protected virtual bool InitShapeData(IReadOnlyShapeVectorData data) { return true; }
     protected abstract bool CanEditShape(IStructureMemberHandler layer);
@@ -205,8 +210,23 @@ internal abstract class DrawableShapeToolExecutor<T> : SimpleShapeToolExecutor w
 
         ignoreNextColorChange = ActiveMode == ShapeToolMode.Drawing;
 
+        preventSettingsChange = true;
         toolbar.StrokeBrush = new SolidColorBrush(color.ToColor());
         toolbar.FillBrush = new SolidColorBrush(color.ToColor());
+        preventSettingsChange = false;
+
+        var layer = document.StructureHelper.Find(memberId);
+        if (layer is null)
+            return;
+
+        if (CanEditShape(layer))
+        {
+            internals!.ActionAccumulator.AddFinishedActions(
+                EndDrawAction(),
+                SettingsChangedAction("FillAndStroke", color),
+                EndDrawAction());
+            // TODO add to undo
+        }
     }
 
     public override void OnSelectedObjectNudged(VecI distance)
@@ -344,13 +364,16 @@ internal abstract class DrawableShapeToolExecutor<T> : SimpleShapeToolExecutor w
 
     public override void OnSettingsChanged(string name, object value)
     {
+        if (preventSettingsChange) return;
+
         var layer = document.StructureHelper.Find(memberId);
         if (layer is null)
             return;
 
         if (CanEditShape(layer))
         {
-            internals!.ActionAccumulator.AddActions(SettingsChangedAction());
+            internals!.ActionAccumulator.AddFinishedActions(EndDrawAction(), SettingsChangedAction(name, value),
+                EndDrawAction());
             // TODO add to undo
         }
     }
@@ -374,7 +397,9 @@ internal abstract class DrawableShapeToolExecutor<T> : SimpleShapeToolExecutor w
                         var member = document!.StructureHelper.Find(memberId);
                         if (member is not null)
                         {
-                            document.Operations.DeleteStructureMember(memberId);
+                            internals.ActionAccumulator.AddActions(ActionSource.Automated,
+                                new DeleteStructureMember_Action(memberId));
+                            //internals.ActionAccumulator.AddFinishedActions();
                             document.TransformHandler.HideTransform();
                         }
                     }

+ 3 - 3
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/LineExecutor.cs

@@ -112,7 +112,7 @@ internal abstract class LineExecutor<T> : SimpleShapeToolExecutor where T : ILin
     protected abstract bool InitShapeData(IReadOnlyLineData? data);
     protected abstract IAction DrawLine(VecD pos);
     protected abstract IAction TransformOverlayMoved(VecD start, VecD end);
-    protected abstract IAction[] SettingsChange();
+    protected abstract IAction[] SettingsChange(string name, object value);
     protected abstract IAction EndDraw();
 
     protected override void PrecisePositionChangeDrawingMode(VecD pos)
@@ -210,7 +210,7 @@ internal abstract class LineExecutor<T> : SimpleShapeToolExecutor where T : ILin
 
         ignoreNextColorChange = ActiveMode == ShapeToolMode.Drawing;
         toolbar!.StrokeBrush = new SolidColorBrush(color.ToColor());
-        var colorChangedAction = SettingsChange();
+        var colorChangedAction = SettingsChange(nameof(IShapeToolbar.StrokeBrush), color);
         internals!.ActionAccumulator.AddActions(colorChangedAction);
     }
 
@@ -225,7 +225,7 @@ internal abstract class LineExecutor<T> : SimpleShapeToolExecutor where T : ILin
 
     public override void OnSettingsChanged(string name, object value)
     {
-        var colorChangedActions = SettingsChange();
+        var colorChangedActions = SettingsChange(name, value);
         if (ActiveMode == ShapeToolMode.Transform)
         {
             internals!.ActionAccumulator.AddFinishedActions(colorChangedActions);

+ 1 - 1
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/RasterEllipseToolExecutor.cs

@@ -32,7 +32,7 @@ internal class RasterEllipseToolExecutor : DrawableShapeToolExecutor<IRasterElli
     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()
+    protected override IAction SettingsChangedAction(string name, object value)
     {
         return new DrawRasterEllipse_Action(memberId, (RectI)lastRect, lastRadians, StrokePaintable, FillPaintable, (float)StrokeWidth, toolbar.AntiAliasing, drawOnMask, document!.AnimationHandler.ActiveFrameBindable);
     }

+ 1 - 1
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/RasterLineToolExecutor.cs

@@ -33,7 +33,7 @@ internal class RasterLineToolExecutor : LineExecutor<ILineToolHandler>
             (float)StrokeWidth, StrokePaintable, StrokeCap.Butt, toolbar.AntiAliasing, drawOnMask, document!.AnimationHandler.ActiveFrameBindable);
     }
 
-    protected override IAction[] SettingsChange()
+    protected override IAction[] SettingsChange(string name, object value)
     {
         VecD dir = GetSignedDirection(startDrawingPos, curPos);
         VecD oppositeDir = new VecD(-dir.X, -dir.Y);

+ 1 - 1
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/RasterRectangleToolExecutor.cs

@@ -41,7 +41,7 @@ internal class RasterRectangleToolExecutor : DrawableShapeToolExecutor<IRasterRe
     protected override void DrawShape(VecD currentPos, double rotationRad, bool first) =>
         DrawRectangle(currentPos, rotationRad, first);
 
-    protected override IAction SettingsChangedAction()
+    protected override IAction SettingsChangedAction(string name, object value)
     {
         lastData = new ShapeData(lastData.Center, lastData.Size, lastRadians, (float)StrokeWidth, StrokePaintable, FillPaintable)
         {

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

@@ -9,8 +9,10 @@ using PixiEditor.Models.Tools;
 using Drawie.Numerics;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces.Shapes;
+using PixiEditor.ChangeableDocument.Changes.Vectors;
 using PixiEditor.Models.DocumentModels.UpdateableChangeExecutors.Features;
 using PixiEditor.Models.Handlers;
+using PixiEditor.Models.Handlers.Toolbars;
 
 namespace PixiEditor.Models.DocumentModels.UpdateableChangeExecutors;
 
@@ -76,11 +78,20 @@ internal class VectorEllipseToolExecutor : DrawableShapeToolExecutor<IVectorElli
 
         lastRect = rect;
 
-        internals!.ActionAccumulator.AddActions(new SetShapeGeometry_Action(memberId, data));
+        internals!.ActionAccumulator.AddActions(new SetShapeGeometry_Action(memberId, data, VectorShapeChangeType.GeometryData));
     }
 
-    protected override IAction SettingsChangedAction()
+    protected override IAction SettingsChangedAction(string name, object value)
     {
+        VectorShapeChangeType changeType = name switch
+        {
+            nameof(IShapeToolbar.StrokeBrush) => VectorShapeChangeType.Stroke,
+            nameof(IShapeToolbar.ToolSize) => VectorShapeChangeType.Stroke,
+            nameof(IFillableShapeToolbar.FillBrush) => VectorShapeChangeType.Fill,
+            nameof(IShapeToolbar.AntiAliasing) => VectorShapeChangeType.OtherVisuals,
+            "FillAndStroke" => VectorShapeChangeType.Fill | VectorShapeChangeType.Stroke,
+            _ => VectorShapeChangeType.All
+        };
         return new SetShapeGeometry_Action(memberId,
             new EllipseVectorData(firstCenter, firstRadius)
             {
@@ -88,7 +99,7 @@ internal class VectorEllipseToolExecutor : DrawableShapeToolExecutor<IVectorElli
                 FillPaintable = FillPaintable,
                 StrokeWidth = (float)StrokeWidth,
                 TransformationMatrix = lastMatrix
-            });
+            }, changeType);
     }
 
     protected override IAction TransformMovedAction(ShapeData data, ShapeCorners corners)
@@ -124,7 +135,7 @@ internal class VectorEllipseToolExecutor : DrawableShapeToolExecutor<IVectorElli
         lastRect = rect;
         lastMatrix = matrix;
 
-        return new SetShapeGeometry_Action(memberId, ellipseData);
+        return new SetShapeGeometry_Action(memberId, ellipseData, VectorShapeChangeType.GeometryData);
     }
 
     protected override IAction EndDrawAction()

+ 21 - 4
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/VectorLineToolExecutor.cs

@@ -5,8 +5,10 @@ using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces.Shapes;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes.Data;
 using PixiEditor.Models.Handlers.Tools;
 using Drawie.Numerics;
+using PixiEditor.ChangeableDocument.Changes.Vectors;
 using PixiEditor.Models.DocumentModels.UpdateableChangeExecutors.Features;
 using PixiEditor.Models.Handlers;
+using PixiEditor.Models.Handlers.Toolbars;
 
 namespace PixiEditor.Models.DocumentModels.UpdateableChangeExecutors;
 
@@ -38,7 +40,7 @@ internal class VectorLineToolExecutor : LineExecutor<IVectorLineToolHandler>
         startPoint = startDrawingPos;
         endPoint = pos;
 
-        return new SetShapeGeometry_Action(memberId, data);
+        return new SetShapeGeometry_Action(memberId, data, VectorShapeChangeType.GeometryData);
     }
 
     protected override IAction TransformOverlayMoved(VecD start, VecD end)
@@ -48,7 +50,22 @@ internal class VectorLineToolExecutor : LineExecutor<IVectorLineToolHandler>
         startPoint = start;
         endPoint = end;
 
-        return new SetShapeGeometry_Action(memberId, data);
+        return new SetShapeGeometry_Action(memberId, data, VectorShapeChangeType.GeometryData);
+    }
+
+    protected IAction SettingsChanged(string name, object value)
+    {
+        var data = ConstructLineData(startPoint, endPoint);
+
+        VectorShapeChangeType changeType = name switch
+        {
+            nameof(IShapeToolbar.StrokeBrush) => VectorShapeChangeType.Stroke,
+            nameof(IShapeToolbar.ToolSize) => VectorShapeChangeType.Stroke,
+            nameof(IShapeToolbar.AntiAliasing) => VectorShapeChangeType.OtherVisuals,
+            _ => VectorShapeChangeType.All
+        };
+
+        return new SetShapeGeometry_Action(memberId, data, changeType);
     }
 
     public override void OnLeftMouseButtonUp(VecD argsPositionOnCanvas)
@@ -74,9 +91,9 @@ internal class VectorLineToolExecutor : LineExecutor<IVectorLineToolHandler>
         }
     }
 
-    protected override IAction[] SettingsChange()
+    protected override IAction[] SettingsChange(string name, object value)
     {
-        return [TransformOverlayMoved(startPoint, endPoint), new EndSetShapeGeometry_Action()];
+        return [SettingsChanged(name, value), new EndSetShapeGeometry_Action()];
     }
 
     protected override IAction EndDraw()

+ 13 - 3
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/VectorPathToolExecutor.cs

@@ -12,6 +12,7 @@ using PixiEditor.ChangeableDocument.Actions.Generated;
 using PixiEditor.ChangeableDocument.Changeables.Animations;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces.Shapes;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.ChangeableDocument.Changes.Vectors;
 using PixiEditor.Helpers.Extensions;
 using PixiEditor.Models.Controllers.InputDevice;
 using PixiEditor.Models.DocumentModels.Public;
@@ -176,8 +177,17 @@ internal class VectorPathToolExecutor : UpdateableChangeExecutor, IPathExecutorF
     {
         if (document.PathOverlayHandler.IsActive)
         {
+            VectorShapeChangeType changeType = name switch
+            {
+                nameof(IFillableShapeToolbar.Fill) => VectorShapeChangeType.Fill,
+                nameof(IShapeToolbar.StrokeBrush) => VectorShapeChangeType.Stroke,
+                nameof(IShapeToolbar.ToolSize) => VectorShapeChangeType.Stroke,
+                nameof(IShapeToolbar.AntiAliasing) => VectorShapeChangeType.OtherVisuals,
+                _ => VectorShapeChangeType.All
+            };
+
             internals.ActionAccumulator.AddFinishedActions(
-                new SetShapeGeometry_Action(member.Id, ConstructShapeData(startingPath)),
+                new SetShapeGeometry_Action(member.Id, ConstructShapeData(startingPath), changeType),
                 new EndSetShapeGeometry_Action());
         }
     }
@@ -195,7 +205,7 @@ internal class VectorPathToolExecutor : UpdateableChangeExecutor, IPathExecutorF
     private void AddToUndo(VectorPath path)
     {
         internals.ActionAccumulator.AddFinishedActions(new EndSetShapeGeometry_Action(),
-            new SetShapeGeometry_Action(member.Id, ConstructShapeData(path)), new EndSetShapeGeometry_Action());
+            new SetShapeGeometry_Action(member.Id, ConstructShapeData(path), VectorShapeChangeType.GeometryData), new EndSetShapeGeometry_Action());
     }
 
     private PathVectorData ConstructShapeData(VectorPath? path)
@@ -230,7 +240,7 @@ internal class VectorPathToolExecutor : UpdateableChangeExecutor, IPathExecutorF
         {
             startingPath = path;
             internals.ActionAccumulator.AddActions(new SetShapeGeometry_Action(member.Id,
-                ConstructShapeData(startingPath)));
+                ConstructShapeData(startingPath), VectorShapeChangeType.GeometryData));
         }
     }
 

+ 16 - 4
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/VectorRectangleToolExecutor.cs

@@ -9,8 +9,10 @@ using PixiEditor.Models.Tools;
 using Drawie.Numerics;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces.Shapes;
+using PixiEditor.ChangeableDocument.Changes.Vectors;
 using PixiEditor.Models.DocumentModels.UpdateableChangeExecutors.Features;
 using PixiEditor.Models.Handlers;
+using PixiEditor.Models.Handlers.Toolbars;
 
 namespace PixiEditor.Models.DocumentModels.UpdateableChangeExecutors;
 
@@ -80,11 +82,21 @@ internal class VectorRectangleToolExecutor : DrawableShapeToolExecutor<IVectorRe
 
         lastRect = rect;
 
-        internals!.ActionAccumulator.AddActions(new SetShapeGeometry_Action(memberId, data));
+        internals!.ActionAccumulator.AddActions(new SetShapeGeometry_Action(memberId, data, VectorShapeChangeType.GeometryData));
     }
 
-    protected override IAction SettingsChangedAction()
+    protected override IAction SettingsChangedAction(string name, object value)
     {
+        VectorShapeChangeType changeType = name switch
+        {
+            nameof(IFillableShapeToolbar.Fill) => VectorShapeChangeType.Fill,
+            nameof(IFillableShapeToolbar.FillBrush) => VectorShapeChangeType.Fill,
+            nameof(IShapeToolbar.StrokeBrush) => VectorShapeChangeType.Stroke,
+            nameof(IShapeToolbar.ToolSize) => VectorShapeChangeType.Stroke,
+            nameof(IShapeToolbar.AntiAliasing) => VectorShapeChangeType.OtherVisuals,
+            "FillAndStroke" => VectorShapeChangeType.Fill | VectorShapeChangeType.Stroke,
+            _ => VectorShapeChangeType.All
+        };
         return new SetShapeGeometry_Action(memberId,
             new RectangleVectorData(firstCenter, firstSize)
             {
@@ -92,7 +104,7 @@ internal class VectorRectangleToolExecutor : DrawableShapeToolExecutor<IVectorRe
                 FillPaintable = FillPaintable,
                 StrokeWidth = (float)StrokeWidth,
                 TransformationMatrix = lastMatrix
-            });
+            }, changeType);
     }
 
     protected override IAction TransformMovedAction(ShapeData data, ShapeCorners corners)
@@ -132,7 +144,7 @@ internal class VectorRectangleToolExecutor : DrawableShapeToolExecutor<IVectorRe
 
         lastMatrix = matrix;
 
-        return new SetShapeGeometry_Action(memberId, newData);
+        return new SetShapeGeometry_Action(memberId, newData, VectorShapeChangeType.GeometryData);
     }
 
     protected override IAction EndDrawAction()

+ 18 - 4
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/VectorTextToolExecutor.cs

@@ -8,6 +8,7 @@ using Drawie.Numerics;
 using PixiEditor.ChangeableDocument.Actions.Generated;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes.Data;
+using PixiEditor.ChangeableDocument.Changes.Vectors;
 using PixiEditor.Helpers.Extensions;
 using PixiEditor.Models.Controllers.InputDevice;
 using PixiEditor.Models.DocumentModels.UpdateableChangeExecutors.Features;
@@ -158,7 +159,7 @@ internal class VectorTextToolExecutor : UpdateableChangeExecutor, ITextOverlayEv
     {
         var constructedText = ConstructTextData(text);
         internals.ActionAccumulator.AddFinishedActions(
-            new SetShapeGeometry_Action(selectedMember.Id, constructedText),
+            new SetShapeGeometry_Action(selectedMember.Id, constructedText, VectorShapeChangeType.GeometryData),
             new EndSetShapeGeometry_Action(),
             new SetLowDpiRendering_Action(selectedMember.Id, toolbar.ForceLowDpiRendering));
         lastText = text;
@@ -191,9 +192,20 @@ internal class VectorTextToolExecutor : UpdateableChangeExecutor, ITextOverlayEv
             cachedFont.Italic = toolbar.Italic;
         }
 
+        VectorShapeChangeType changeType = name switch {
+            nameof(ITextToolbar.Fill) => VectorShapeChangeType.Fill,
+            nameof(ITextToolbar.FillBrush) => VectorShapeChangeType.Fill,
+            nameof(ITextToolbar.StrokeBrush) => VectorShapeChangeType.Stroke,
+            nameof(ITextToolbar.ToolSize) => VectorShapeChangeType.GeometryData,
+            nameof(ITextToolbar.Spacing) => VectorShapeChangeType.GeometryData,
+            nameof(ITextToolbar.AntiAliasing) => VectorShapeChangeType.OtherVisuals,
+            nameof(ITextToolbar.ForceLowDpiRendering) => VectorShapeChangeType.OtherVisuals,
+            _ => VectorShapeChangeType.OtherVisuals
+        };
+
         var constructedText = ConstructTextData(lastText);
         internals.ActionAccumulator.AddActions(
-            new SetShapeGeometry_Action(selectedMember.Id, constructedText),
+            new SetShapeGeometry_Action(selectedMember.Id, constructedText, changeType),
             new SetLowDpiRendering_Action(selectedMember.Id, toolbar.ForceLowDpiRendering));
 
         document.TextOverlayHandler.Font = constructedText.Font;
@@ -233,10 +245,10 @@ internal class VectorTextToolExecutor : UpdateableChangeExecutor, ITextOverlayEv
 
         var constructedText = ConstructTextData(lastText);
         internals.ActionAccumulator.AddFinishedActions(
-            new SetShapeGeometry_Action(selectedMember.Id, constructedText),
+            new SetShapeGeometry_Action(selectedMember.Id, constructedText, VectorShapeChangeType.GeometryData),
             new EndSetShapeGeometry_Action(),
             new SetLowDpiRendering_Action(selectedMember.Id, toolbar.ForceLowDpiRendering),
-            new SetShapeGeometry_Action(firstValidLayer.Id, newShape),
+            new SetShapeGeometry_Action(firstValidLayer.Id, newShape, VectorShapeChangeType.GeometryData),
             new EndSetShapeGeometry_Action());
     }
 
@@ -266,6 +278,8 @@ internal class VectorTextToolExecutor : UpdateableChangeExecutor, ITextOverlayEv
             Stroke = toolbar.StrokeBrush.ToPaintable(),
             TransformationMatrix = lastMatrix,
             Font = cachedFont,
+            Bold = toolbar.Bold,
+            Italic = toolbar.Italic,
             Spacing = toolbar.Spacing,
             AntiAlias = toolbar.AntiAliasing,
             Path = onPath,

+ 1 - 1
src/PixiEditor/Views/Overlays/TextOverlay/TextOverlay.cs

@@ -639,7 +639,7 @@ internal class TextOverlay : Overlay
 
     private void UpdateGlyphs()
     {
-        if (Font == null) return;
+        if (Font == null || Font.IsDisposed) return;
 
         richText = new(Text);
         richText.Spacing = Spacing;