瀏覽代碼

Fixed previews for duplicating

flabbet 7 月之前
父節點
當前提交
e3de0f94a7

+ 1 - 1
src/Drawie

@@ -1 +1 @@
-Subproject commit bbc26f8cbc06e8cbe0cdbe1c11cb1b5372a7e484
+Subproject commit 50007fb51e74bde8cfbb1a30f8d040ef0804e43e

+ 0 - 394
src/PixiEditor.ChangeableDocument/Changes/Drawing/PreviewShiftLayers_Change.cs

@@ -1,394 +0,0 @@
-using ChunkyImageLib.Operations;
-using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
-using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
-using PixiEditor.ChangeableDocument.ChangeInfos.Objects;
-using PixiEditor.ChangeableDocument.Changes.Selection;
-using Drawie.Backend.Core;
-using Drawie.Backend.Core.Numerics;
-using Drawie.Backend.Core.Surfaces;
-using Drawie.Backend.Core.Surfaces.PaintImpl;
-using Drawie.Backend.Core.Vector;
-using Drawie.Numerics;
-using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes.Data;
-using PixiEditor.ChangeableDocument.ChangeInfos.Vectors;
-
-namespace PixiEditor.ChangeableDocument.Changes.Drawing;
-
-internal class PreviewShiftLayers_Change : InterruptableUpdateableChange
-{
-    private readonly bool drawOnMask;
-    private ShapeCorners masterCorners;
-
-    private List<MemberTransformationData> memberData;
-
-    private VectorPath? originalPath;
-    private VecD selectionAwareSize;
-    private VecD tightBoundsSize;
-    private RectD cornersToSelectionOffset;
-    private VecD originalCornersSize;
-
-    private bool isTransformingSelection;
-    private bool hasEnqueudImages = false;
-    private int frame;
-    private bool appliedOnce;
-    private AffectedArea lastAffectedArea;
-
-    private static Paint RegularPaint { get; } = new() { BlendMode = BlendMode.SrcOver };
-
-    [GenerateUpdateableChangeActions]
-    public PreviewShiftLayers_Change(
-        ShapeCorners masterCorners,
-        Dictionary<Guid, ShapeCorners> memberCorners,
-        int frame)
-    {
-        memberData = new();
-        foreach (var corners in memberCorners)
-        {
-            memberData.Add(new MemberTransformationData(corners.Key) { MemberCorners = corners.Value });
-        }
-
-        this.masterCorners = masterCorners;
-        this.frame = frame;
-    }
-
-    public override bool InitializeAndValidate(Document target)
-    {
-        if (memberData.Count == 0)
-            return false;
-
-        originalCornersSize = masterCorners.RectSize;
-        RectD tightBoundsWithSelection = default;
-        bool hasSelection = target.Selection.SelectionPath is { IsEmpty: false };
-
-        if (hasSelection)
-        {
-            originalPath = new VectorPath(target.Selection.SelectionPath) { FillType = PathFillType.EvenOdd };
-            tightBoundsWithSelection = originalPath.TightBounds;
-            selectionAwareSize = tightBoundsWithSelection.Size;
-            isTransformingSelection = true;
-
-            tightBoundsSize = tightBoundsWithSelection.Size;
-            cornersToSelectionOffset = new RectD(masterCorners.TopLeft - tightBoundsWithSelection.TopLeft,
-                tightBoundsSize - masterCorners.RectSize);
-        }
-
-        StructureNode firstLayer = target.FindMemberOrThrow(memberData[0].MemberId);
-        RectD tightBounds = firstLayer.GetTightBounds(frame) ?? default;
-
-        if (memberData.Count == 1 && firstLayer is VectorLayerNode vectorLayer)
-        {
-            tightBounds = vectorLayer.ShapeData?.GeometryAABB ?? default;
-        }
-
-        for (var i = 1; i < memberData.Count; i++)
-        {
-            StructureNode layer = target.FindMemberOrThrow(memberData[i].MemberId);
-
-            var layerTightBounds = layer.GetTightBounds(frame);
-
-            if (tightBounds == default)
-            {
-                tightBounds = layerTightBounds.GetValueOrDefault();
-            }
-
-            if (layerTightBounds is not null)
-            {
-                tightBounds = tightBounds.Union(layerTightBounds.Value);
-            }
-        }
-
-        if (tightBounds == default)
-            return false;
-
-        tightBoundsSize = tightBounds.Size;
-
-        foreach (var member in memberData)
-        {
-            StructureNode layer = target.FindMemberOrThrow(member.MemberId);
-
-            if (layer is IReadOnlyImageNode)
-            {
-                var targetBounds = tightBoundsWithSelection != default ? tightBoundsWithSelection : tightBounds;
-                SetImageMember(target, member, targetBounds, layer);
-            }
-            else if (layer is IReadOnlyVectorNode vectorNode)
-            {
-                SetVectorMember(member, vectorNode, tightBounds);
-            }
-        }
-
-        return true;
-    }
-
-    private void SetVectorMember(MemberTransformationData member,
-        IReadOnlyVectorNode vectorNode, RectD tightBounds)
-    {
-        member.OriginalBounds = tightBounds;
-        VecD posRelativeToMaster = member.OriginalBounds.Value.TopLeft - masterCorners.TopLeft;
-
-        member.OriginalMatrix = vectorNode.ShapeData.TransformationMatrix;
-        member.OriginalPos = (VecI)posRelativeToMaster;
-        member.OriginalShapeData = vectorNode.ShapeData as ShapeVectorData;
-        member.OriginalPath = vectorNode.ShapeData?.ToPath();
-        member.OriginalPath.Transform(vectorNode.ShapeData.TransformationMatrix);
-    }
-
-    private void SetImageMember(Document target, MemberTransformationData member, RectD originalTightBounds,
-        StructureNode layer)
-    {
-        ChunkyImage image =
-            DrawingChangeHelper.GetTargetImageOrThrow(target, member.MemberId, drawOnMask, frame);
-        VectorPath? pathToExtract = originalPath;
-        RectD targetBounds = originalTightBounds;
-
-        if (pathToExtract == null)
-        {
-            RectD tightBounds = layer.GetTightBounds(frame).GetValueOrDefault();
-            pathToExtract = new VectorPath();
-            pathToExtract.AddRect(tightBounds.RoundOutwards());
-        }
-
-        member.OriginalPath = pathToExtract;
-        member.OriginalBounds = targetBounds;
-        var extracted = ExtractArea(image, pathToExtract, member.RoundedOriginalBounds.Value);
-        if (extracted.IsT0)
-            return;
-
-        member.AddImage(extracted.AsT1.image, extracted.AsT1.extractedRect.Pos);
-    }
-
-    [UpdateChangeMethod]
-    public void Update(ShapeCorners masterCorners)
-    {
-        this.masterCorners = masterCorners;
-
-        var globalMatrixWithSelection = OperationHelper.CreateMatrixFromPoints(masterCorners, originalCornersSize);
-        var tightBoundsGlobalMatrix = OperationHelper.CreateMatrixFromPoints(masterCorners, tightBoundsSize);
-
-        foreach (var member in memberData)
-        {
-            Matrix3X3 localMatrix = tightBoundsGlobalMatrix;
-
-            if (member.IsImage)
-            {
-                localMatrix =
-                    Matrix3X3.CreateTranslation(
-                            (float)-cornersToSelectionOffset.TopLeft.X, (float)-cornersToSelectionOffset.TopLeft.Y)
-                        .PostConcat(
-                            Matrix3X3.CreateTranslation(
-                                (float)member.OriginalPos.Value.X - (float)member.OriginalBounds.Value.Left,
-                                (float)member.OriginalPos.Value.Y - (float)member.OriginalBounds.Value.Top));
-
-                localMatrix = localMatrix.PostConcat(selectionAwareSize.Length > 0
-                    ? globalMatrixWithSelection
-                    : tightBoundsGlobalMatrix);
-            }
-            else if (member.OriginalMatrix is not null)
-            {
-                if (memberData.Count > 1)
-                {
-                    localMatrix = member.OriginalMatrix.Value;
-                    localMatrix = localMatrix.PostConcat(Matrix3X3.CreateTranslation(
-                            (float)member.OriginalPos.Value.X - (float)member.OriginalBounds.Value.Left,
-                            (float)member.OriginalPos.Value.Y - (float)member.OriginalBounds.Value.Top))
-                        .PostConcat(tightBoundsGlobalMatrix);
-                }
-                else
-                {
-                    localMatrix = Matrix3X3.CreateTranslation(
-                        (float)-member.OriginalBounds.Value.X,
-                        (float)-member.OriginalBounds.Value.Y);
-                    localMatrix = localMatrix.PostConcat(tightBoundsGlobalMatrix);
-                }
-            }
-
-            member.LocalMatrix = localMatrix;
-        }
-    }
-
-    public OneOf<None, (Surface image, RectI extractedRect)> ExtractArea(ChunkyImage image, VectorPath path,
-        RectI pathBounds)
-    {
-        // get rid of transparent areas on edges
-        var memberImageBounds = image.FindChunkAlignedMostUpToDateBounds();
-        if (memberImageBounds is null)
-            return new None();
-        pathBounds = pathBounds.Intersect(memberImageBounds.Value);
-        pathBounds = pathBounds.Intersect(new RectI(VecI.Zero, image.LatestSize));
-        if (pathBounds.IsZeroOrNegativeArea)
-            return new None();
-
-        // shift the clip to account for the image being smaller than the selection
-        VectorPath clipPath = new VectorPath(path) { FillType = PathFillType.EvenOdd };
-        clipPath.Transform(Matrix3X3.CreateTranslation(-pathBounds.X, -pathBounds.Y));
-
-        // draw
-        Surface output = new(pathBounds.Size);
-        output.DrawingSurface.Canvas.Save();
-        output.DrawingSurface.Canvas.ClipPath(clipPath);
-        image.DrawMostUpToDateRegionOn(pathBounds, ChunkResolution.Full, output.DrawingSurface, VecI.Zero);
-        output.DrawingSurface.Canvas.Restore();
-
-        return (output, pathBounds);
-    }
-
-    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply,
-        out bool ignoreInUndo)
-    {
-        List<IChangeInfo> infos = new();
-        
-        foreach (var data in memberData)
-        {
-            if (data.IsImage)
-            {
-                ChunkyImage targetImage =
-                    DrawingChangeHelper.GetTargetImageOrThrow(target, data.MemberId, drawOnMask, frame);
-                targetImage.CancelChanges();
-            }
-            else if (data.OriginalPath != null)
-            {
-                var memberNode = target.FindMemberOrThrow(data.MemberId);
-                if (memberNode is VectorLayerNode vectorNode)
-                {
-                    (vectorNode.ShapeData as PathVectorData)?.Path.Dispose();
-                    vectorNode.ShapeData = data.OriginalShapeData;
-                    infos.Add(new VectorShape_ChangeInfo(data.MemberId, GetTranslationAffectedArea()));
-                }
-            }
-        }
-
-
-        if (isTransformingSelection)
-        {
-            VectorPath? newPath = originalPath == null ? null : new VectorPath(originalPath);
-            target.Selection.SelectionPath = newPath;
-            infos.Add(new Selection_ChangeInfo(newPath));
-        }
-
-        hasEnqueudImages = false;
-        ignoreInUndo = true;
-
-        return infos;
-    }
-
-    public override OneOf<None, IChangeInfo, List<IChangeInfo>> ApplyTemporarily(Document target)
-    {
-        List<IChangeInfo> infos = new();
-
-        foreach (var member in memberData)
-        {
-            if (member.IsImage)
-            {
-                ChunkyImage targetImage =
-                    DrawingChangeHelper.GetTargetImageOrThrow(target, member.MemberId, drawOnMask, frame);
-
-                infos.Add(DrawingChangeHelper.CreateAreaChangeInfo(member.MemberId,
-                        DrawImage(member, targetImage), drawOnMask)
-                    .AsT1);
-            }
-            else if (member.OriginalPath != null)
-            {
-                VectorPath newPath = new VectorPath(member.OriginalPath);
-
-                VecD translation = VecD.Zero;
-                    //member.OriginalBounds.Value.TopLeft;
-                
-                var finalMatrix = member.LocalMatrix
-                    .Concat(member.OriginalShapeData.TransformationMatrix.Invert())
-                    .PostConcat(Matrix3X3.CreateTranslation(-(float)translation.X, -(float)translation.Y));
-                    
-                newPath.AddPath(member.OriginalPath, finalMatrix, AddPathMode.Append);
-
-                var memberNode = target.FindMemberOrThrow(member.MemberId);
-                if (memberNode is VectorLayerNode vectorNode)
-                {
-                    StrokeCap cap = vectorNode.ShapeData is PathVectorData pathData
-                        ? pathData.StrokeLineCap
-                        : StrokeCap.Butt;
-                    StrokeJoin join = vectorNode.ShapeData is PathVectorData pathData1
-                        ? pathData1.StrokeLineJoin
-                        : StrokeJoin.Miter;
-                    vectorNode.ShapeData = new PathVectorData(newPath)
-                    {
-                        Fill = vectorNode.ShapeData.Fill,
-                        FillColor = vectorNode.ShapeData.FillColor,
-                        StrokeWidth = vectorNode.ShapeData.StrokeWidth,
-                        StrokeColor = vectorNode.ShapeData.StrokeColor,
-                        Path = newPath,
-                        StrokeLineCap = cap,
-                        StrokeLineJoin = join
-                    };
-                }
-                else
-                {
-                    continue;
-                }
-
-                AffectedArea translationAffectedArea = GetTranslationAffectedArea();
-                var tmp = new AffectedArea(translationAffectedArea);
-                if (lastAffectedArea.Chunks != null)
-                {
-                    translationAffectedArea.UnionWith(lastAffectedArea);
-                }
-
-                lastAffectedArea = tmp;
-                infos.Add(new VectorShape_ChangeInfo(member.MemberId, translationAffectedArea));
-            }
-        }
-
-        return infos;
-    }
-
-    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
-    {
-        return new None();
-    }
-
-    public override void Dispose()
-    {
-        if (hasEnqueudImages)
-            throw new InvalidOperationException(
-                "Attempted to dispose the change while it's internally stored image is still used enqueued in some ChunkyImage. Most likely someone tried to dispose a change after ApplyTemporarily was called but before the subsequent call to Apply. Don't do that.");
-
-        foreach (var member in memberData)
-        {
-            member.Dispose();
-        }
-    }
-
-    private AffectedArea GetTranslationAffectedArea()
-    {
-        RectI oldBounds = (RectI)masterCorners.AABBBounds.RoundOutwards();
-
-        HashSet<VecI> chunks = new();
-        VecI topLeftChunk = new VecI((int)oldBounds.Left / ChunkyImage.FullChunkSize,
-            (int)oldBounds.Top / ChunkyImage.FullChunkSize);
-        VecI bottomRightChunk = new VecI((int)oldBounds.Right / ChunkyImage.FullChunkSize,
-            (int)oldBounds.Bottom / ChunkyImage.FullChunkSize);
-
-        for (int x = topLeftChunk.X; x <= bottomRightChunk.X; x++)
-        {
-            for (int y = topLeftChunk.Y; y <= bottomRightChunk.Y; y++)
-            {
-                chunks.Add(new VecI(x, y));
-            }
-        }
-
-        var final = new AffectedArea(chunks);
-        return final;
-    }
-
-    private AffectedArea DrawImage(MemberTransformationData data, ChunkyImage memberImage)
-    {
-        var prevAffArea = memberImage.FindAffectedArea();
-
-        memberImage.CancelChanges();
-
-        memberImage.EnqueueDrawImage(data.LocalMatrix, data.Image, RegularPaint, false);
-        hasEnqueudImages = true;
-
-        var affectedArea = memberImage.FindAffectedArea();
-        affectedArea.UnionWith(prevAffArea);
-        return affectedArea;
-    }
-}

+ 153 - 0
src/PixiEditor.ChangeableDocument/Changes/Drawing/PreviewShiftLayers_UpdateableChange.cs

@@ -0,0 +1,153 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using Drawie.Backend.Core.Numerics;
+using Drawie.Backend.Core.Surfaces.PaintImpl;
+using Drawie.Backend.Core.Vector;
+using Drawie.Numerics;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes.Data;
+using PixiEditor.ChangeableDocument.ChangeInfos.Vectors;
+
+namespace PixiEditor.ChangeableDocument.Changes.Drawing;
+
+internal class PreviewShiftLayers_UpdateableChange : InterruptableUpdateableChange
+{
+    private List<Guid> layerGuids;
+    private VecD delta;
+    private Dictionary<Guid, ShapeVectorData> originalShapes;
+
+    private int frame;
+
+    [GenerateUpdateableChangeActions]
+    public PreviewShiftLayers_UpdateableChange(List<Guid> layerGuids, VecD delta, int frame)
+    {
+        this.delta = delta;
+        this.layerGuids = layerGuids;
+        this.frame = frame;
+    }
+
+    public override bool InitializeAndValidate(Document target)
+    {
+        if (layerGuids.Count == 0)
+        {
+            return false;
+        }
+
+        layerGuids = target.ExtractLayers(layerGuids);
+
+        foreach (var layer in layerGuids)
+        {
+            if (!target.HasMember(layer)) return false;
+        }
+
+        originalShapes = new Dictionary<Guid, ShapeVectorData>();
+
+        foreach (var layerGuid in layerGuids)
+        {
+            var layer = target.FindMemberOrThrow<LayerNode>(layerGuid);
+
+            if (layer is VectorLayerNode transformableObject)
+            {
+                originalShapes[layerGuid] = transformableObject.ShapeData;
+                transformableObject.ShapeData = null;
+            }
+        }
+
+        return true;
+    }
+
+    [UpdateChangeMethod]
+    public void Update(VecD delta)
+    {
+        this.delta = delta;
+    }
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> ApplyTemporarily(Document target)
+    {
+        List<IChangeInfo> changes = new List<IChangeInfo>();
+        foreach (var layerGuid in layerGuids)
+        {
+            var layer = target.FindMemberOrThrow<LayerNode>(layerGuid);
+
+            if (layer is ImageLayerNode)
+            {
+                var area = ShiftLayerHelper.DrawShiftedLayer(target, layerGuid, true, (VecI)delta, frame);
+                changes.Add(new LayerImageArea_ChangeInfo(layerGuid, area));
+            }
+            else if (layer is VectorLayerNode vectorLayer)
+            {
+                StrokeJoin join = StrokeJoin.Miter;
+                StrokeCap cap = StrokeCap.Butt;
+                
+                (vectorLayer.ShapeData as PathVectorData)?.Path.Dispose();
+
+                var originalShape = originalShapes[layerGuid];
+
+                var path = originalShape.ToPath();
+
+                if (originalShape is PathVectorData shape)
+                {
+                    join = shape.StrokeLineJoin;
+                    cap = shape.StrokeLineCap;
+                }
+
+                VecD mappedDelta = originalShape.TransformationMatrix.Invert().MapVector((float)delta.X, (float)delta.Y);
+                
+                var finalMatrix = Matrix3X3.CreateTranslation((float)mappedDelta.X, (float)mappedDelta.Y);
+
+                path.AddPath(path, finalMatrix, AddPathMode.Append);
+
+                var newShapeData = new PathVectorData(path)
+                {
+                    StrokeWidth = originalShape.StrokeWidth,
+                    StrokeColor = originalShape.StrokeColor,
+                    FillColor = originalShape.FillColor,
+                    Fill = originalShape.Fill,
+                    TransformationMatrix = originalShape.TransformationMatrix,
+                    StrokeLineJoin = join,
+                    StrokeLineCap = cap
+                };
+                
+                vectorLayer.ShapeData = newShapeData;
+                changes.Add(new VectorShape_ChangeInfo(layerGuid, ShiftLayer_UpdateableChange.AffectedAreaFromBounds(target, layerGuid, frame)));
+            }
+        }
+
+        return changes;
+    }
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply,
+        out bool ignoreInUndo)
+    {
+        ignoreInUndo = true;
+        return RevertPreview(target);
+    }
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
+    {
+        return RevertPreview(target);
+    }
+
+    private OneOf<None, IChangeInfo, List<IChangeInfo>> RevertPreview(Document target)
+    {
+        List<IChangeInfo> changes = new List<IChangeInfo>();
+        foreach (var layerGuid in layerGuids)
+        {
+            var layer = target.FindMemberOrThrow<LayerNode>(layerGuid);
+
+            if (layer is ImageLayerNode imgLayer)
+            {
+                var image = imgLayer.GetLayerImageAtFrame(frame);
+                var affected = image.FindAffectedArea();
+                image.CancelChanges();
+                changes.Add(new LayerImageArea_ChangeInfo(layerGuid, affected));
+            }
+            else if (layer is VectorLayerNode transformableObject)
+            {
+                (transformableObject.ShapeData as PathVectorData)?.Path.Dispose();
+                transformableObject.ShapeData = originalShapes[layerGuid];
+            }
+        }
+
+        return changes;
+    }
+}

+ 39 - 0
src/PixiEditor.ChangeableDocument/Changes/Drawing/ShiftLayer_UpdateableChange.cs

@@ -3,6 +3,7 @@ using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Numerics;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+using PixiEditor.ChangeableDocument.ChangeInfos.Vectors;
 
 namespace PixiEditor.ChangeableDocument.Changes.Drawing;
 
@@ -63,8 +64,10 @@ internal class ShiftLayer_UpdateableChange : Change
             else if (layer is ITransformableObject transformableObject)
             {
                 originalTransformations[layerGuid] = transformableObject.TransformationMatrix;
+                AffectedArea affected = AffectedAreaFromBounds(target, layerGuid, frame);
                 transformableObject.TransformationMatrix = transformableObject.TransformationMatrix.PostConcat(
                 Matrix3X3.CreateTranslation((float)delta.X, (float)delta.Y));
+                changes.Add(new VectorShape_ChangeInfo(layerGuid, affected));
             }
         }
 
@@ -102,4 +105,40 @@ internal class ShiftLayer_UpdateableChange : Change
             value?.Dispose();
         }
     }
+    
+    internal static AffectedArea AffectedAreaFromBounds(Document target, Guid layerGuid, int frame)
+    {
+        HashSet<VecI> chunks = new HashSet<VecI>();
+        
+        var layer = target.FindMemberOrThrow<LayerNode>(layerGuid);
+        if (layer is not VectorLayerNode vectorLayer)
+        {
+            return new AffectedArea();
+        }
+
+        RectD? bounds = vectorLayer.GetTightBounds(frame);
+        if (bounds is null)
+        {
+            return new AffectedArea();
+        }
+        
+        RectD boundsValue = bounds.Value;
+
+        int chunkSize = ChunkyImage.FullChunkSize;
+        
+        VecI start = new VecI((int)boundsValue.X / chunkSize, (int)boundsValue.Y / chunkSize);
+        VecI end = new VecI((int)(boundsValue.X + boundsValue.Width) / chunkSize, (int)(boundsValue.Y + boundsValue.Height) / chunkSize);
+        
+        HashSet<VecI> affectedChunks = new HashSet<VecI>();
+        
+        for (int x = start.X; x <= end.X; x++)
+        {
+            for (int y = start.Y; y <= end.Y; y++)
+            {
+                affectedChunks.Add(new VecI(x, y));
+            }
+        }
+        
+        return new AffectedArea(affectedChunks);
+    }
 }

+ 10 - 9
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/TransformSelectedExecutor.cs

@@ -230,7 +230,7 @@ internal class TransformSelectedExecutor : UpdateableChangeExecutor, ITransforma
         if (isInProgress)
         {
             internals.ActionAccumulator.AddActions(new EndTransformSelected_Action());
-            internals!.ActionAccumulator.AddActions(new EndPreviewTransformSelected_Action());
+            internals!.ActionAccumulator.AddActions(new EndPreviewShiftLayers_Action());
             document!.TransformHandler.HideTransform();
             AddSnappingForMembers(selectedMembers);
 
@@ -260,12 +260,13 @@ internal class TransformSelectedExecutor : UpdateableChangeExecutor, ITransforma
                 cornersOnStartDuplicate = lastCorners;
                 duplicateOnStop = true;
                 internals.ActionAccumulator.AddFinishedActions(new EndTransformSelected_Action());
-
-                internals.ActionAccumulator.AddActions(new PreviewTransformSelected_Action(new ShapeCorners(lastCorners.AABBBounds), memberCorners,
-                    document!.AnimationHandler.ActiveFrameBindable));
             }
 
-            internals.ActionAccumulator.AddActions(new PreviewTransformSelected_Action(new ShapeCorners(corners.AABBBounds), memberCorners,
+            VecD delta = new VecD(
+                corners.AABBBounds.TopLeft.X - cornersOnStartDuplicate.AABBBounds.TopLeft.X,
+                corners.AABBBounds.TopLeft.Y - cornersOnStartDuplicate.AABBBounds.TopLeft.Y);
+
+            internals.ActionAccumulator.AddActions(new PreviewShiftLayers_Action(selectedMembers, delta,
                 document!.AnimationHandler.ActiveFrameBindable));
             return;
         }
@@ -293,7 +294,7 @@ internal class TransformSelectedExecutor : UpdateableChangeExecutor, ITransforma
 
         internals.ActionAccumulator.StartChangeBlock();
 
-        actions.Add(new EndPreviewTransformSelected_Action());
+        actions.Add(new EndPreviewShiftLayers_Action());
 
         VectorPath? original = document.SelectionPathBindable != null
             ? new VectorPath(document.SelectionPathBindable)
@@ -352,7 +353,7 @@ internal class TransformSelectedExecutor : UpdateableChangeExecutor, ITransforma
         internals!.ActionAccumulator.AddFinishedActions(actions.ToArray());
 
         actions.Clear();
-        
+
         VecD delta = new VecD(
             lastCorners.AABBBounds.TopLeft.X - cornersOnStartDuplicate.AABBBounds.TopLeft.X,
             lastCorners.AABBBounds.TopLeft.Y - cornersOnStartDuplicate.AABBBounds.TopLeft.Y);
@@ -396,7 +397,7 @@ internal class TransformSelectedExecutor : UpdateableChangeExecutor, ITransforma
             tool.TransformingSelectedArea = false;
         }
 
-        internals!.ActionAccumulator.AddActions(new EndPreviewTransformSelected_Action());
+        internals!.ActionAccumulator.AddActions(new EndPreviewShiftLayers_Action());
         internals!.ActionAccumulator.AddActions(new EndTransformSelected_Action());
         internals!.ActionAccumulator.AddFinishedActions();
         document!.TransformHandler.HideTransform();
@@ -419,7 +420,7 @@ internal class TransformSelectedExecutor : UpdateableChangeExecutor, ITransforma
             tool.TransformingSelectedArea = false;
         }
 
-        internals!.ActionAccumulator.AddActions(new EndPreviewTransformSelected_Action());
+        internals!.ActionAccumulator.AddActions(new EndPreviewShiftLayers_Action());
         internals!.ActionAccumulator.AddActions(new EndTransformSelected_Action());
         internals!.ActionAccumulator.AddFinishedActions();
         document!.TransformHandler.HideTransform();