ソースを参照

Duplicate with ctrl wip

flabbet 7 ヶ月 前
コミット
149b833afd

+ 2 - 2
src/PixiEditor.ChangeableDocument/Changes/Structure/DuplicateLayer_Change.cs

@@ -14,16 +14,16 @@ internal class DuplicateLayer_Change : Change
     private ConnectionsData? connectionsData;
 
     [GenerateMakeChangeAction]
-    public DuplicateLayer_Change(Guid layerGuid)
+    public DuplicateLayer_Change(Guid layerGuid, Guid newGuid)
     {
         this.layerGuid = layerGuid;
+        this.duplicateGuid = newGuid;
     }
 
     public override bool InitializeAndValidate(Document target)
     {
         if (!target.TryFindMember<LayerNode>(layerGuid, out LayerNode? layer))
             return false;
-        duplicateGuid = Guid.NewGuid();
         
         connectionsData = NodeOperations.CreateConnectionsData(layer);
         

+ 8 - 0
src/PixiEditor/Models/DocumentModels/ChangeExecutionController.cs

@@ -230,6 +230,14 @@ internal class ChangeExecutionController
         }
     }
 
+    public void TransformStoppedInlet()
+    {
+        if(currentSession is ITransformStoppedEvent transformStoppedEvent)
+        {
+            transformStoppedEvent.OnTransformStopped();
+        }
+    }
+
     public void MembersSelectedInlet(List<Guid> memberGuids)
     {
         currentSession?.OnMembersSelected(memberGuids);

+ 5 - 4
src/PixiEditor/Models/DocumentModels/Public/DocumentOperationsModule.cs

@@ -210,7 +210,7 @@ internal class DocumentOperationsModule : IDocumentOperations
         bool isFolder = Document.StructureHelper.Find(guidValue) is IFolderHandler;
         if (!isFolder)
         {
-            Internals.ActionAccumulator.AddFinishedActions(new DuplicateLayer_Action(guidValue));
+            Internals.ActionAccumulator.AddFinishedActions(new DuplicateLayer_Action(guidValue, Guid.NewGuid()));
         }
         else
         {
@@ -881,7 +881,8 @@ internal class DocumentOperationsModule : IDocumentOperations
 
         Internals.ChangeController.TryStopActiveExecutor();
 
-        Internals.ActionAccumulator.AddFinishedActions(new KeyFrameLength_Action(celId, startFrame, duration), new EndKeyFrameLength_Action());
+        Internals.ActionAccumulator.AddFinishedActions(new KeyFrameLength_Action(celId, startFrame, duration),
+            new EndKeyFrameLength_Action());
     }
 
     public void DeleteNodes(Guid[] nodes)
@@ -890,7 +891,7 @@ internal class DocumentOperationsModule : IDocumentOperations
             return;
 
         Internals.ChangeController.TryStopActiveExecutor();
-        
+
         List<IAction> actions = new();
 
         for (var i = 0; i < nodes.Length; i++)
@@ -899,7 +900,7 @@ internal class DocumentOperationsModule : IDocumentOperations
             if (Document.StructureHelper.TryFindNode(node, out INodeHandler nodeHandler) &&
                 nodeHandler.InternalName == OutputNode.UniqueName)
                 return;
-            
+
             actions.Add(new DeleteNode_Action(node));
         }
 

+ 14 - 1
src/PixiEditor/Models/DocumentModels/Public/DocumentStructureModule.cs

@@ -146,7 +146,7 @@ internal class DocumentStructureModule
         return layers;
     }
 
-    public List<IStructureMemberHandler> GetAllMembers()
+    public List<IStructureMemberHandler> TraverseAllMembers()
     {
         List<IStructureMemberHandler> members = new List<IStructureMemberHandler>();
 
@@ -160,6 +160,19 @@ internal class DocumentStructureModule
         return members;
     }
 
+    public List<IStructureMemberHandler> GetAllMembers()
+    {
+        List<IStructureMemberHandler> members = new List<IStructureMemberHandler>();
+
+        foreach (INodeHandler node in doc.NodeGraphHandler.AllNodes)
+        {
+            if (node is IStructureMemberHandler member)
+                members.Add(member);
+        }
+
+        return members;
+    }
+
     private void FillPath(INodeHandler node, List<INodeHandler> toFill)
     {
         node.TraverseForwards(newNode =>

+ 6 - 0
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/Features/ITransformStoppedEvent.cs

@@ -0,0 +1,6 @@
+namespace PixiEditor.Models.DocumentModels.UpdateableChangeExecutors.Features;
+
+public interface ITransformStoppedEvent : IExecutorFeature
+{
+    public void OnTransformStopped();
+}

+ 110 - 3
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/TransformSelectedExecutor.cs

@@ -4,18 +4,22 @@ 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.Models.Controllers.InputDevice;
+using PixiEditor.Models.DocumentPassthroughActions;
 using PixiEditor.ViewModels.Document.Nodes;
 
 namespace PixiEditor.Models.DocumentModels.UpdateableChangeExecutors;
 #nullable enable
-internal class TransformSelectedExecutor : UpdateableChangeExecutor, ITransformableExecutor, IMidChangeUndoableExecutor
+internal class TransformSelectedExecutor : UpdateableChangeExecutor, ITransformableExecutor, IMidChangeUndoableExecutor,
+    ITransformStoppedEvent
 {
     private Dictionary<Guid, ShapeCorners> memberCorners = new();
     private IMoveToolHandler? tool;
@@ -26,8 +30,10 @@ internal class TransformSelectedExecutor : UpdateableChangeExecutor, ITransforma
 
     private List<Guid> selectedMembers = new();
 
+    private ShapeCorners cornersOnStartDuplicate;
     private ShapeCorners lastCorners = new();
     private bool movedOnce;
+    private bool duplicateOnStop = false;
 
     public TransformSelectedExecutor(bool toolLinked)
     {
@@ -42,7 +48,6 @@ internal class TransformSelectedExecutor : UpdateableChangeExecutor, ITransforma
 
         tool.TransformingSelectedArea = true;
         List<IStructureMemberHandler> members = new();
-
         var guids = document.ExtractSelectedLayers(false);
         members = guids.Select(g => document.StructureHelper.Find(g)).ToList();
 
@@ -50,6 +55,7 @@ internal class TransformSelectedExecutor : UpdateableChangeExecutor, ITransforma
             return ExecutionState.Error;
 
         document.TransformHandler.PassthroughPointerPressed += OnLeftMouseButtonDown;
+
         return SelectMembers(members);
     }
 
@@ -122,6 +128,7 @@ internal class TransformSelectedExecutor : UpdateableChangeExecutor, ITransforma
 
         movedOnce = false;
         isInProgress = true;
+
         return ExecutionState.Success;
     }
 
@@ -164,6 +171,11 @@ internal class TransformSelectedExecutor : UpdateableChangeExecutor, ITransforma
         }
     }
 
+    public void OnTransformStopped()
+    {
+        DuplicateIfRequired();
+    }
+
     private void Deselect(List<ILayerHandler> topMostWithinClick)
     {
         var topMost = topMostWithinClick.FirstOrDefault();
@@ -234,11 +246,23 @@ internal class TransformSelectedExecutor : UpdateableChangeExecutor, ITransforma
         if (!isInProgress)
             return;
 
+        if (tool.DuplicateOnMove)
+        {
+            if (!duplicateOnStop)
+            {
+                cornersOnStartDuplicate = corners;
+                duplicateOnStop = true;
+            }
+
+            return;
+        }
+
         if (!movedOnce)
         {
             internals!.ActionAccumulator.AddActions(
                 new TransformSelected_Action(lastCorners, tool.KeepOriginalImage, memberCorners, false,
                     document.AnimationHandler.ActiveFrameBindable));
+
             movedOnce = true;
         }
 
@@ -247,6 +271,78 @@ internal class TransformSelectedExecutor : UpdateableChangeExecutor, ITransforma
                 document!.AnimationHandler.ActiveFrameBindable));
     }
 
+    private void DuplicateSelected()
+    {
+        List<IAction> actions = new();
+
+        List<Guid> newGuids = new();
+
+        internals.ActionAccumulator.StartChangeBlock();
+
+        VectorPath? original = document.SelectionPathBindable != null
+            ? new VectorPath(document.SelectionPathBindable)
+            : null;
+        if (original != null)
+        {
+            var selection = document.SelectionPathBindable;
+            var inverse = new VectorPath();
+            inverse.AddRect(new RectD(new(0, 0), document.SizeBindable));
+
+            actions.Add(new SetSelection_Action(inverse.Op(selection, VectorPathOp.Difference)));
+        }
+
+        for (var i = 0; i < selectedMembers.Count; i++)
+        {
+            var member = selectedMembers[i];
+            Guid newGuid = Guid.NewGuid();
+            newGuids.Add(newGuid);
+            actions.Add(new DuplicateLayer_Action(member, newGuid));
+            if (document.SelectionPathBindable is { IsEmpty: false })
+            {
+                actions.Add(new ClearSelectedArea_Action(newGuid, false,
+                    document.AnimationHandler.ActiveFrameBindable));
+            }
+        }
+
+        if (original != null)
+        {
+            actions.Add(new SetSelection_Action(original));
+        }
+
+        internals!.ActionAccumulator.AddFinishedActions(actions.ToArray());
+
+        actions.Clear();
+
+        actions.Add(new ClearSoftSelectedMembers_PassthroughAction());
+        foreach (var newGuid in newGuids)
+        {
+            actions.Add(new AddSoftSelectedMember_PassthroughAction(newGuid));
+        }
+
+        internals!.ActionAccumulator.AddFinishedActions(actions.ToArray());
+
+        actions.Clear();
+
+        Dictionary<Guid, ShapeCorners> newMemberCorners = new();
+        for (var i = 0; i < selectedMembers.Count; i++)
+        {
+            var member = selectedMembers[i];
+            newMemberCorners.Add(newGuids[i], memberCorners[member]);
+        }
+
+        actions.Add(new TransformSelected_Action(cornersOnStartDuplicate, false, newMemberCorners,
+            false, document!.AnimationHandler.ActiveFrameBindable));
+        actions.Add(new TransformSelected_Action(lastCorners, false, memberCorners, false,
+            document!.AnimationHandler.ActiveFrameBindable));
+        actions.Add(new EndTransformSelected_Action());
+
+        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);
@@ -293,6 +389,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 +420,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;
     }
 }

+ 4 - 1
src/PixiEditor/Models/Handlers/Tools/IMoveToolHandler.cs

@@ -1,7 +1,10 @@
-namespace PixiEditor.Models.Handlers.Tools;
+using Drawie.Numerics;
+
+namespace PixiEditor.Models.Handlers.Tools;
 
 internal interface IMoveToolHandler : IToolHandler
 {
     public bool KeepOriginalImage { get; }
     public bool TransformingSelectedArea { get; set; }
+    public bool DuplicateOnMove { get; set; }
 }

+ 3 - 2
src/PixiEditor/ViewModels/Document/DocumentViewModel.cs

@@ -236,7 +236,8 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
         NodeGraph = new NodeGraphViewModel(this, Internals);
 
         TransformViewModel = new(this);
-        TransformViewModel.TransformMoved += (_, args) => Internals.ChangeController.TransformMovedInlet(args);
+        TransformViewModel.TransformMoved += (args) => Internals.ChangeController.TransformMovedInlet(args);
+        TransformViewModel.TransformStopped += () => Internals.ChangeController.TransformStoppedInlet();
 
         PathOverlayViewModel = new(this, Internals);
         PathOverlayViewModel.PathChanged += path =>
@@ -679,7 +680,7 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
             if (scope == DocumentScope.AllLayers)
             {
                 using Surface tmpSurface = new Surface(SizeBindable);
-                HashSet<Guid> layers = StructureHelper.GetAllMembers().Select(x => x.Id).ToHashSet();
+                HashSet<Guid> layers = StructureHelper.TraverseAllMembers().Select(x => x.Id).ToHashSet();
                 Renderer.RenderLayers(tmpSurface.DrawingSurface, layers, frameTime.Frame, ChunkResolution.Full,
                     SizeBindable);
 

+ 4 - 2
src/PixiEditor/ViewModels/Document/TransformOverlays/DocumentTransformViewModel.cs

@@ -135,7 +135,7 @@ internal class DocumentTransformViewModel : ObservableObject, ITransformHandler
         set
         {
             SetProperty(ref corners, value);
-            TransformMoved?.Invoke(this, value);
+            TransformMoved?.Invoke(value);
         }
     }
 
@@ -191,7 +191,8 @@ internal class DocumentTransformViewModel : ObservableObject, ITransformHandler
         }
     }
 
-    public event EventHandler<ShapeCorners>? TransformMoved;
+    public event Action<ShapeCorners>? TransformMoved;
+    public event Action TransformStopped; 
 
     private DocumentTransformMode activeTransformMode = DocumentTransformMode.Scale_Rotate_NoShear_NoPerspective;
 
@@ -200,6 +201,7 @@ internal class DocumentTransformViewModel : ObservableObject, ITransformHandler
         this.document = document;
         ActionCompletedCommand = new RelayCommand(() =>
         {
+            TransformStopped?.Invoke();
             AddToUndoCommand?.Execute(Corners);
 
             if (undoStack is null)

+ 17 - 0
src/PixiEditor/ViewModels/Tools/ToolViewModel.cs

@@ -299,6 +299,23 @@ internal abstract class ToolViewModel : ObservableObject, IToolHandler
         }
     }
 
+    protected void SetValue<T>(T value, [CallerMemberName] string name = null)
+    {
+        var setting = Toolbar.GetSetting(name);
+        if(setting is null)
+        {
+            throw new InvalidOperationException($"Setting {name} not found in toolbar {Toolbar.GetType().Name}");
+        }
+
+        if (setting.GetSettingType() != typeof(T))
+        {
+            throw new InvalidCastException($"Setting {name} is not of type {typeof(T).Name}");
+        }
+        
+        setting.Value = value;
+    }
+    
+
     private bool IsExposeSetting(KeyValuePair<string, object> settingConfig, out bool expose)
     {
         bool isExpose = settingConfig.Key.StartsWith("Expose", StringComparison.InvariantCultureIgnoreCase);

+ 13 - 5
src/PixiEditor/ViewModels/Tools/Tools/MoveToolViewModel.cs

@@ -42,6 +42,13 @@ internal class MoveToolViewModel : ToolViewModel, IMoveToolHandler
         }
     }
 
+    [Settings.Bool("_duplicate_on_move", ExposedByDefault = false)]
+    public bool DuplicateOnMove
+    {
+        get => GetValue<bool>();
+        set => SetValue(value);
+    }
+
     public override BrushShape BrushShape => BrushShape.Hidden;
     public override Type[]? SupportedLayerTypes { get; } = null;
     public override Type LayerTypeToCreateOnEmptyUse { get; } = null;
@@ -57,6 +64,7 @@ internal class MoveToolViewModel : ToolViewModel, IMoveToolHandler
         }
     }
 
+
     public override void UseTool(VecD pos)
     {
         ViewModelMain.Current.DocumentManagerSubViewModel.ActiveDocument?.Operations.TransformSelectedArea(true);
@@ -64,7 +72,7 @@ internal class MoveToolViewModel : ToolViewModel, IMoveToolHandler
 
     public override void ModifierKeyChanged(bool ctrlIsDown, bool shiftIsDown, bool altIsDown)
     {
-        
+        DuplicateOnMove = ctrlIsDown;
     }
 
     protected override void OnSelected(bool restoring)
@@ -73,7 +81,7 @@ internal class MoveToolViewModel : ToolViewModel, IMoveToolHandler
         {
             return;
         }
-        
+
         ViewModelMain.Current.DocumentManagerSubViewModel.ActiveDocument?.Operations.TransformSelectedArea(true);
     }
 
@@ -91,7 +99,7 @@ internal class MoveToolViewModel : ToolViewModel, IMoveToolHandler
     {
         if (IsActive)
         {
-           OnToolSelected(false);
+            OnToolSelected(false);
         }
     }
 
@@ -107,7 +115,7 @@ internal class MoveToolViewModel : ToolViewModel, IMoveToolHandler
     {
         UpdateSelection();
     }
-    
+
     public override void OnActiveFrameChanged(int newFrame)
     {
         UpdateSelection();
@@ -126,7 +134,7 @@ internal class MoveToolViewModel : ToolViewModel, IMoveToolHandler
         {
             return;
         }
-        
+
         activeDocument.TransformViewModel.ShowTransformControls = KeepOriginalImage;
     }
 }