|
@@ -1,21 +1,29 @@
|
|
|
using System.Collections.Generic;
|
|
|
+using System.Collections.Immutable;
|
|
|
using System.Linq;
|
|
|
using Avalonia.Input;
|
|
|
using ChunkyImageLib.DataHolders;
|
|
|
using PixiEditor.ChangeableDocument.Actions.Generated;
|
|
|
using Drawie.Backend.Core.Numerics;
|
|
|
+using Drawie.Backend.Core.Vector;
|
|
|
using PixiEditor.Models.DocumentModels.Public;
|
|
|
using PixiEditor.Models.DocumentModels.UpdateableChangeExecutors.Features;
|
|
|
using PixiEditor.Models.Handlers;
|
|
|
using PixiEditor.Models.Handlers.Tools;
|
|
|
using PixiEditor.Models.Tools;
|
|
|
using Drawie.Numerics;
|
|
|
+using PixiEditor.ChangeableDocument.Actions;
|
|
|
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
|
|
|
using PixiEditor.Models.Controllers.InputDevice;
|
|
|
+using PixiEditor.Models.DocumentPassthroughActions;
|
|
|
+using PixiEditor.ViewModels;
|
|
|
using PixiEditor.ViewModels.Document.Nodes;
|
|
|
|
|
|
namespace PixiEditor.Models.DocumentModels.UpdateableChangeExecutors;
|
|
|
#nullable enable
|
|
|
-internal class TransformSelectedExecutor : UpdateableChangeExecutor, ITransformableExecutor, IMidChangeUndoableExecutor
|
|
|
+internal class TransformSelectedExecutor : UpdateableChangeExecutor, ITransformableExecutor, ITransformDraggedEvent,
|
|
|
+ IMidChangeUndoableExecutor,
|
|
|
+ ITransformStoppedEvent
|
|
|
{
|
|
|
private Dictionary<Guid, ShapeCorners> memberCorners = new();
|
|
|
private IMoveToolHandler? tool;
|
|
@@ -25,9 +33,12 @@ internal class TransformSelectedExecutor : UpdateableChangeExecutor, ITransforma
|
|
|
public override bool BlocksOtherActions => false;
|
|
|
|
|
|
private List<Guid> selectedMembers = new();
|
|
|
+ private List<Guid> originalSelectedMembers = new();
|
|
|
|
|
|
+ private ShapeCorners cornersOnStartDuplicate;
|
|
|
private ShapeCorners lastCorners = new();
|
|
|
private bool movedOnce;
|
|
|
+ private bool duplicateOnStop = false;
|
|
|
|
|
|
public TransformSelectedExecutor(bool toolLinked)
|
|
|
{
|
|
@@ -42,7 +53,7 @@ internal class TransformSelectedExecutor : UpdateableChangeExecutor, ITransforma
|
|
|
|
|
|
tool.TransformingSelectedArea = true;
|
|
|
List<IStructureMemberHandler> members = new();
|
|
|
-
|
|
|
+ originalSelectedMembers = document.SelectedMembers.ToList();
|
|
|
var guids = document.ExtractSelectedLayers(false);
|
|
|
members = guids.Select(g => document.StructureHelper.Find(g)).ToList();
|
|
|
|
|
@@ -50,6 +61,7 @@ internal class TransformSelectedExecutor : UpdateableChangeExecutor, ITransforma
|
|
|
return ExecutionState.Error;
|
|
|
|
|
|
document.TransformHandler.PassthroughPointerPressed += OnLeftMouseButtonDown;
|
|
|
+
|
|
|
return SelectMembers(members);
|
|
|
}
|
|
|
|
|
@@ -115,6 +127,8 @@ internal class TransformSelectedExecutor : UpdateableChangeExecutor, ITransforma
|
|
|
selectedMembers = members.Select(m => m.Id).ToList();
|
|
|
|
|
|
lastCorners = masterCorners;
|
|
|
+
|
|
|
+
|
|
|
document.TransformHandler.ShowTransform(mode, true, masterCorners,
|
|
|
Type == ExecutorType.Regular || tool.KeepOriginalImage);
|
|
|
|
|
@@ -122,12 +136,22 @@ internal class TransformSelectedExecutor : UpdateableChangeExecutor, ITransforma
|
|
|
|
|
|
movedOnce = false;
|
|
|
isInProgress = true;
|
|
|
+
|
|
|
return ExecutionState.Success;
|
|
|
}
|
|
|
|
|
|
public override void OnLeftMouseButtonDown(MouseOnCanvasEventArgs args)
|
|
|
{
|
|
|
args.Handled = true;
|
|
|
+
|
|
|
+ if (args.ClickCount >= 2)
|
|
|
+ {
|
|
|
+ if (SwitchToLayerTool())
|
|
|
+ {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
var allLayers = document.StructureHelper.GetAllLayers();
|
|
|
var topMostWithinClick = allLayers.Where(x =>
|
|
|
x is { IsVisibleBindable: true, TightBounds: not null } &&
|
|
@@ -164,6 +188,23 @@ internal class TransformSelectedExecutor : UpdateableChangeExecutor, ITransforma
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ private bool SwitchToLayerTool()
|
|
|
+ {
|
|
|
+ if (document.SelectedStructureMember is ILayerHandler layerHandler && layerHandler.QuickEditTool != null)
|
|
|
+ {
|
|
|
+ ViewModelMain.Current.ToolsSubViewModel.SetActiveTool(layerHandler.QuickEditTool, false);
|
|
|
+ ViewModelMain.Current.ToolsSubViewModel.QuickToolSwitchInlet();
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void OnTransformStopped()
|
|
|
+ {
|
|
|
+ DuplicateIfRequired();
|
|
|
+ }
|
|
|
+
|
|
|
private void Deselect(List<ILayerHandler> topMostWithinClick)
|
|
|
{
|
|
|
var topMost = topMostWithinClick.FirstOrDefault();
|
|
@@ -212,6 +253,7 @@ internal class TransformSelectedExecutor : UpdateableChangeExecutor, ITransforma
|
|
|
if (isInProgress)
|
|
|
{
|
|
|
internals.ActionAccumulator.AddActions(new EndTransformSelected_Action());
|
|
|
+ internals!.ActionAccumulator.AddActions(new EndPreviewShiftLayers_Action());
|
|
|
document!.TransformHandler.HideTransform();
|
|
|
AddSnappingForMembers(selectedMembers);
|
|
|
|
|
@@ -223,22 +265,48 @@ internal class TransformSelectedExecutor : UpdateableChangeExecutor, ITransforma
|
|
|
|
|
|
public bool IsTransforming => isInProgress;
|
|
|
|
|
|
- public void OnTransformMoved(ShapeCorners corners)
|
|
|
+ public void OnTransformChanged(ShapeCorners corners)
|
|
|
{
|
|
|
DoTransform(corners);
|
|
|
lastCorners = corners;
|
|
|
}
|
|
|
|
|
|
+ public void OnTransformDragged(VecD from, VecD to)
|
|
|
+ {
|
|
|
+ if (!isInProgress)
|
|
|
+ return;
|
|
|
+
|
|
|
+ if (tool.DuplicateOnMove)
|
|
|
+ {
|
|
|
+ if (!duplicateOnStop)
|
|
|
+ {
|
|
|
+ cornersOnStartDuplicate = lastCorners;
|
|
|
+ duplicateOnStop = true;
|
|
|
+ internals.ActionAccumulator.AddFinishedActions(new EndTransformSelected_Action());
|
|
|
+ }
|
|
|
+
|
|
|
+ VecD delta = new VecD(
|
|
|
+ to.X - from.X,
|
|
|
+ to.Y - from.Y);
|
|
|
+
|
|
|
+ internals.ActionAccumulator.AddActions(new PreviewShiftLayers_Action(selectedMembers, delta,
|
|
|
+ document!.AnimationHandler.ActiveFrameBindable));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
private void DoTransform(ShapeCorners corners)
|
|
|
{
|
|
|
if (!isInProgress)
|
|
|
return;
|
|
|
|
|
|
+ if (duplicateOnStop) return;
|
|
|
+
|
|
|
if (!movedOnce)
|
|
|
{
|
|
|
internals!.ActionAccumulator.AddActions(
|
|
|
new TransformSelected_Action(lastCorners, tool.KeepOriginalImage, memberCorners, false,
|
|
|
document.AnimationHandler.ActiveFrameBindable));
|
|
|
+
|
|
|
movedOnce = true;
|
|
|
}
|
|
|
|
|
@@ -247,6 +315,101 @@ internal class TransformSelectedExecutor : UpdateableChangeExecutor, ITransforma
|
|
|
document!.AnimationHandler.ActiveFrameBindable));
|
|
|
}
|
|
|
|
|
|
+ private void DuplicateSelected()
|
|
|
+ {
|
|
|
+ List<IAction> actions = new();
|
|
|
+
|
|
|
+ List<Guid> newLayerGuids = new();
|
|
|
+ List<Guid> newGuidsOfOriginal = new();
|
|
|
+
|
|
|
+ internals.ActionAccumulator.StartChangeBlock();
|
|
|
+
|
|
|
+ actions.Add(new EndPreviewShiftLayers_Action());
|
|
|
+
|
|
|
+ VectorPath? original = document.SelectionPathBindable != null
|
|
|
+ ? new VectorPath(document.SelectionPathBindable)
|
|
|
+ : null;
|
|
|
+
|
|
|
+ VectorPath? clearArea = null;
|
|
|
+ if (original != null)
|
|
|
+ {
|
|
|
+ var selection = document.SelectionPathBindable;
|
|
|
+ var inverse = new VectorPath();
|
|
|
+ inverse.AddRect(new RectD(new(0, 0), document.SizeBindable));
|
|
|
+
|
|
|
+ clearArea = inverse.Op(selection, VectorPathOp.Difference);
|
|
|
+ }
|
|
|
+
|
|
|
+ for (var i = 0; i < originalSelectedMembers.Count; i++)
|
|
|
+ {
|
|
|
+ var member = originalSelectedMembers[i];
|
|
|
+ Guid newGuid = Guid.NewGuid();
|
|
|
+ if (document.StructureHelper.Find(member) is not FolderNodeViewModel folder)
|
|
|
+ {
|
|
|
+ newLayerGuids.Add(newGuid);
|
|
|
+ actions.Add(new DuplicateLayer_Action(member, newGuid));
|
|
|
+ if (document.SelectionPathBindable is { IsEmpty: false })
|
|
|
+ {
|
|
|
+ actions.Add(new ClearSelectedArea_Action(newGuid, clearArea, false,
|
|
|
+ document.AnimationHandler.ActiveFrameBindable));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ int childCount = folder.CountChildrenRecursive();
|
|
|
+ Guid[] newGuidsArray = new Guid[childCount];
|
|
|
+ for (var j = 0; j < childCount; j++)
|
|
|
+ {
|
|
|
+ newGuidsArray[j] = Guid.NewGuid();
|
|
|
+ }
|
|
|
+
|
|
|
+ actions.Add(new DuplicateFolder_Action(member, newGuid, newGuidsArray.ToImmutableList()));
|
|
|
+
|
|
|
+ for (int j = 0; j < childCount; j++)
|
|
|
+ {
|
|
|
+ if (document.SelectionPathBindable is { IsEmpty: false })
|
|
|
+ {
|
|
|
+ actions.Add(new ClearSelectedArea_Action(newGuidsArray[j], clearArea, false,
|
|
|
+ document.AnimationHandler.ActiveFrameBindable));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ newLayerGuids.AddRange(newGuidsArray);
|
|
|
+ }
|
|
|
+
|
|
|
+ newGuidsOfOriginal.Add(newGuid);
|
|
|
+ }
|
|
|
+
|
|
|
+ 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);
|
|
|
+
|
|
|
+ actions.Add(new ShiftLayer_Action(newLayerGuids, delta, document!.AnimationHandler.ActiveFrameBindable));
|
|
|
+
|
|
|
+ internals!.ActionAccumulator.AddFinishedActions(actions.ToArray());
|
|
|
+
|
|
|
+ actions.Clear();
|
|
|
+
|
|
|
+ actions.Add(new ClearSoftSelectedMembers_PassthroughAction());
|
|
|
+ foreach (var newGuid in newGuidsOfOriginal)
|
|
|
+ {
|
|
|
+ actions.Add(new AddSoftSelectedMember_PassthroughAction(newGuid));
|
|
|
+ }
|
|
|
+
|
|
|
+ actions.Add(new SetSelectedMember_PassthroughAction(newGuidsOfOriginal.Last()));
|
|
|
+
|
|
|
+ internals!.ActionAccumulator.AddFinishedActions(actions.ToArray());
|
|
|
+
|
|
|
+
|
|
|
+ internals.ActionAccumulator.EndChangeBlock();
|
|
|
+
|
|
|
+ tool!.DuplicateOnMove = false;
|
|
|
+ }
|
|
|
+
|
|
|
public void OnLineOverlayMoved(VecD start, VecD end) { }
|
|
|
|
|
|
public void OnSelectedObjectNudged(VecI distance) => document!.TransformHandler.Nudge(distance);
|
|
@@ -264,6 +427,7 @@ internal class TransformSelectedExecutor : UpdateableChangeExecutor, ITransforma
|
|
|
tool.TransformingSelectedArea = false;
|
|
|
}
|
|
|
|
|
|
+ internals!.ActionAccumulator.AddActions(new EndPreviewShiftLayers_Action());
|
|
|
internals!.ActionAccumulator.AddActions(new EndTransformSelected_Action());
|
|
|
internals!.ActionAccumulator.AddFinishedActions();
|
|
|
document!.TransformHandler.HideTransform();
|
|
@@ -286,6 +450,7 @@ internal class TransformSelectedExecutor : UpdateableChangeExecutor, ITransforma
|
|
|
tool.TransformingSelectedArea = false;
|
|
|
}
|
|
|
|
|
|
+ internals!.ActionAccumulator.AddActions(new EndPreviewShiftLayers_Action());
|
|
|
internals!.ActionAccumulator.AddActions(new EndTransformSelected_Action());
|
|
|
internals!.ActionAccumulator.AddFinishedActions();
|
|
|
document!.TransformHandler.HideTransform();
|
|
@@ -293,6 +458,16 @@ internal class TransformSelectedExecutor : UpdateableChangeExecutor, ITransforma
|
|
|
|
|
|
isInProgress = false;
|
|
|
document.TransformHandler.PassthroughPointerPressed -= OnLeftMouseButtonDown;
|
|
|
+ DuplicateIfRequired();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void DuplicateIfRequired()
|
|
|
+ {
|
|
|
+ if (duplicateOnStop)
|
|
|
+ {
|
|
|
+ DuplicateSelected();
|
|
|
+ duplicateOnStop = false;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
private void AddSnappingForMembers(List<Guid> memberGuids)
|
|
@@ -314,6 +489,7 @@ internal class TransformSelectedExecutor : UpdateableChangeExecutor, ITransforma
|
|
|
|
|
|
public bool IsFeatureEnabled(IExecutorFeature feature)
|
|
|
{
|
|
|
- return feature is ITransformableExecutor && IsTransforming;
|
|
|
+ return feature is ITransformableExecutor && IsTransforming || feature is IMidChangeUndoableExecutor ||
|
|
|
+ feature is ITransformStoppedEvent || feature is ITransformDraggedEvent;
|
|
|
}
|
|
|
}
|