Browse Source

Added multi-layer dragging

Krzysztof Krysiński 4 months ago
parent
commit
75a2e64ca5

+ 1 - 0
src/PixiEditor/Models/Handlers/IDocument.cs

@@ -64,6 +64,7 @@ internal interface IDocument : IHandler
         bool includeCanvas, int frame, bool isTopMost, string? customOutput);
 
     public HashSet<Guid> ExtractSelectedLayers(bool includeFoldersWithMask = false);
+    public List<Guid> GetSelectedMembersInOrder(bool includeNested = false);
     public void UpdateSavedState();
 
     internal void InternalRaiseLayersChanged(LayersChangedEventArgs e);

+ 2 - 0
src/PixiEditor/Models/Handlers/IDocumentOperations.cs

@@ -1,4 +1,5 @@
 using PixiEditor.ChangeableDocument;
+using PixiEditor.Models.DocumentModels.Public;
 using PixiEditor.Models.Layers;
 
 namespace PixiEditor.Models.Handlers;
@@ -13,4 +14,5 @@ internal interface IDocumentOperations
     public void ClearSoftSelectedMembers();
     public Guid? CreateStructureMember(Type type, ActionSource source, string? name = null);
     public void InvokeCustomAction(Action action, bool stopActiveExecutor = true);
+    public ChangeBlock StartChangeBlock();
 }

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

@@ -437,7 +437,8 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
             mappedNodeIds.Add(id, guid);
             Guid pairGuid = Guid.Empty;
 
-            if (serializedNode.PairId != null && mappedNodeIds.TryGetValue(serializedNode.PairId.Value, out Guid pairId))
+            if (serializedNode.PairId != null &&
+                mappedNodeIds.TryGetValue(serializedNode.PairId.Value, out Guid pairId))
             {
                 pairGuid = pairId;
             }
@@ -778,7 +779,9 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
             // via a passthrough action to avoid all the try catches
             if (scope == DocumentScope.Canvas)
             {
-                using Surface tmpSurface = new Surface(SizeBindable); // new Surface is on purpose, Surface.ForDisplay doesn't work here
+                using Surface
+                    tmpSurface =
+                        new Surface(SizeBindable); // new Surface is on purpose, Surface.ForDisplay doesn't work here
                 Renderer.RenderDocument(tmpSurface.DrawingSurface, frameTime, SizeBindable, customOutput);
 
                 return tmpSurface.GetSrgbPixel(pos);
@@ -924,6 +927,31 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
         return layerGuids;
     }
 
+
+    public List<Guid> GetSelectedMembersInOrder(bool includeNested = false)
+    {
+        var selectedMembers = GetSelectedMembers();
+        List<Guid> orderedMembers = new List<Guid>();
+        var allMembers = StructureHelper.TraverseAllMembers();
+
+        for (var index = 0; index < allMembers.Count; index++)
+        {
+            var member = allMembers[index];
+            if (selectedMembers.Contains(member.Id))
+            {
+                if (!includeNested)
+                {
+                    var parents = StructureHelper.GetParents(member.Id);
+                    if(parents.Any(x => selectedMembers.Contains(x.Id)))
+                        continue;
+                }
+                orderedMembers.Add(member.Id);
+            }
+        }
+
+        return orderedMembers;
+    }
+
     /// <summary>
     ///     Gets all selected layers extracted from selected folders.
     /// </summary>

+ 30 - 12
src/PixiEditor/Views/Layers/FolderControl.axaml.cs

@@ -12,7 +12,6 @@ namespace PixiEditor.Views.Layers;
 #nullable enable
 internal partial class FolderControl : UserControl
 {
-
     public static readonly StyledProperty<FolderNodeViewModel> FolderProperty =
         AvaloniaProperty.Register<FolderControl, FolderNodeViewModel>(nameof(Folder));
 
@@ -22,8 +21,6 @@ internal partial class FolderControl : UserControl
         set => SetValue(FolderProperty, value);
     }
 
-    public static string? FolderControlDataName = typeof(FolderControl).FullName;
-
     public static readonly StyledProperty<LayersManager> ManagerProperty =
         AvaloniaProperty.Register<FolderControl, LayersManager>(nameof(Manager));
 
@@ -35,7 +32,7 @@ internal partial class FolderControl : UserControl
 
     private readonly IBrush? highlightColor;
 
-    
+
     private MouseUpdateController? mouseUpdateController;
 
     public FolderControl()
@@ -48,22 +45,22 @@ internal partial class FolderControl : UserControl
 
         Loaded += OnLoaded;
         Unloaded += OnUnloaded;
-        
+
         AddHandler(DragDrop.DragEnterEvent, FolderControl_DragEnter);
         AddHandler(DragDrop.DragLeaveEvent, FolderControl_DragLeave);
-        
+
         TopDropGrid.AddHandler(DragDrop.DragEnterEvent, Grid_DragEnter);
         TopDropGrid.AddHandler(DragDrop.DragLeaveEvent, Grid_DragLeave);
         TopDropGrid.AddHandler(DragDrop.DropEvent, Grid_Drop_Top);
-        
+
         BottomDropGrid.AddHandler(DragDrop.DragEnterEvent, Grid_DragEnter);
         BottomDropGrid.AddHandler(DragDrop.DragLeaveEvent, Grid_DragLeave);
         BottomDropGrid.AddHandler(DragDrop.DropEvent, Grid_Drop_Bottom);
-        
+
         middleDropGrid.AddHandler(DragDrop.DragEnterEvent, Grid_CenterEnter);
         middleDropGrid.AddHandler(DragDrop.DragLeaveEvent, Grid_CenterLeave);
         middleDropGrid.AddHandler(DragDrop.DropEvent, Grid_Drop_Center);
-        
+
         DisableDropPanels();
     }
 
@@ -109,10 +106,31 @@ internal partial class FolderControl : UserControl
     private bool HandleDrop(IDataObject dataObj, StructureMemberPlacement placement)
     {
         DisableDropPanels();
-        Guid? droppedMemberGuid = LayerControl.ExtractMemberGuid(dataObj);
-        if (droppedMemberGuid is null)
+        Guid[]? droppedGuids = LayerControl.ExtractMemberGuids(dataObj);
+        if (droppedGuids is null)
             return false;
-        Folder.Document.Operations.MoveStructureMember((Guid)droppedMemberGuid, Folder.Id, placement);
+
+        var document = Folder.Document;
+        if (placement is StructureMemberPlacement.Below or StructureMemberPlacement.BelowOutsideFolder or StructureMemberPlacement.Inside)
+        {
+            droppedGuids = droppedGuids.Reverse().ToArray();
+        }
+
+        using var block = document.Operations.StartChangeBlock();
+        Guid lastMovedMember = Folder.Id;
+
+        foreach (Guid memberGuid in droppedGuids)
+        {
+            document.Operations.MoveStructureMember(memberGuid, lastMovedMember,
+                placement);
+            lastMovedMember = memberGuid;
+            if (placement == StructureMemberPlacement.Inside)
+            {
+                placement = StructureMemberPlacement.Below;
+            }
+
+            block.ExecuteQueuedActions();
+        }
 
         return true;
     }

+ 36 - 15
src/PixiEditor/Views/Layers/LayerControl.axaml.cs

@@ -16,8 +16,6 @@ namespace PixiEditor.Views.Layers;
 #nullable enable
 internal partial class LayerControl : UserControl
 {
-    public static string? LayerControlDataName = typeof(LayerControl).FullName;
-
     public static readonly StyledProperty<ILayerHandler> LayerProperty =
         AvaloniaProperty.Register<LayerControl, ILayerHandler>(nameof(Layer));
 
@@ -87,7 +85,7 @@ internal partial class LayerControl : UserControl
         {
             highlightColor = value as IBrush;
         }
-     
+
         TopGrid.AddHandler(DragDrop.DragEnterEvent, Grid_DragEnter);
         TopGrid.AddHandler(DragDrop.DragLeaveEvent, Grid_DragLeave);
         TopGrid.AddHandler(DragDrop.DropEvent, Grid_Drop_Top);
@@ -98,9 +96,9 @@ internal partial class LayerControl : UserControl
         thirdDropGrid.AddHandler(DragDrop.DragLeaveEvent, Grid_DragLeave);
         thirdDropGrid.AddHandler(DragDrop.DropEvent, Grid_Drop_Bottom);
     }
-    
+
     private void LayerControl_Unloaded(object? sender, RoutedEventArgs e)
-    { 
+    {
         mouseUpdateController?.Dispose();
     }
 
@@ -138,14 +136,20 @@ internal partial class LayerControl : UserControl
             RemoveDragEffect(item);
     }
 
-    public static Guid? ExtractMemberGuid(IDataObject droppedMemberDataObject)
+    public static Guid[]? ExtractMemberGuids(IDataObject droppedMemberDataObject)
     {
-        object droppedLayer = droppedMemberDataObject.Get(LayerControlDataName);
-        object droppedFolder = droppedMemberDataObject.Get(FolderControl.FolderControlDataName);
-        if (droppedLayer is LayerControl layer)
-            return layer.Layer.Id;
-        else if (droppedFolder is FolderControl folder)
-            return folder.Folder.Id;
+        object droppedLayer = droppedMemberDataObject.Get(LayersManager.LayersDataName);
+        if (droppedLayer is null)
+            return null;
+
+        if (droppedLayer is Guid droppedLayerGuid)
+            return new[] { droppedLayerGuid };
+
+        if (droppedLayer is Guid[] droppedLayerGuids)
+        {
+            return droppedLayerGuids;
+        }
+
         return null;
     }
 
@@ -153,10 +157,27 @@ internal partial class LayerControl : UserControl
     {
         if (placement == StructureMemberPlacement.Inside)
             return false;
-        Guid? droppedMemberGuid = ExtractMemberGuid(dataObj);
-        if (droppedMemberGuid is null)
+        Guid[]? droppedMemberGuids = ExtractMemberGuids(dataObj);
+        if (droppedMemberGuids is null)
             return false;
-        Layer.Document.Operations.MoveStructureMember((Guid)droppedMemberGuid, Layer.Id, placement);
+        if (Layer is null)
+            return false;
+
+        if(placement is StructureMemberPlacement.Below or StructureMemberPlacement.BelowOutsideFolder)
+        {
+            droppedMemberGuids = droppedMemberGuids.Reverse().ToArray();
+        }
+
+        var document = Layer.Document;
+
+        using var block = Layer.Document.Operations.StartChangeBlock();
+        Guid lastMovedMember = Layer.Id;
+        foreach (Guid memberGuid in droppedMemberGuids)
+        {
+            document.Operations.MoveStructureMember(memberGuid, lastMovedMember, placement);
+            lastMovedMember = memberGuid;
+            block.ExecuteQueuedActions();
+        }
 
         return true;
     }

+ 41 - 11
src/PixiEditor/Views/Layers/LayersManager.axaml.cs

@@ -20,6 +20,7 @@ namespace PixiEditor.Views.Layers;
 #nullable enable
 internal partial class LayersManager : UserControl
 {
+    public const string LayersDataName = "PixiEditor.LayersData";
     public DocumentViewModel ActiveDocument => DataContext is LayersDockViewModel vm ? vm.ActiveDocument : null;
     private readonly IBrush? highlightColor;
 
@@ -31,7 +32,7 @@ internal partial class LayersManager : UserControl
         {
             highlightColor = value as IBrush;
         }
-      
+
         dropBorder.AddHandler(DragDrop.DragEnterEvent, Grid_DragEnter);
         dropBorder.AddHandler(DragDrop.DragLeaveEvent, Grid_DragLeave);
         dropBorder.AddHandler(DragDrop.DropEvent, Grid_Drop);
@@ -67,7 +68,8 @@ internal partial class LayersManager : UserControl
         if (e.Source is LayerControl container && isLeftPressed && Equals(e.Pointer.Captured, container))
         {
             DataObject data = new();
-            data.Set(LayerControl.LayerControlDataName, container);
+            Guid[] selectedGuids = container.Layer.Document.GetSelectedMembersInOrder().ToArray();
+            data.Set(LayersDataName, selectedGuids);
             Dispatcher.UIThread.InvokeAsync(() => DragDrop.DoDragDrop(e, data, DragDropEffects.Move));
         }
     }
@@ -77,6 +79,16 @@ internal partial class LayersManager : UserControl
         if (sender is not LayerControl)
             return;
 
+        if (e is { Source: LayerControl layerControl, InitialPressMouseButton: MouseButton.Left } &&
+            !e.KeyModifiers.HasFlag(KeyModifiers.Control) && !e.KeyModifiers.HasFlag(KeyModifiers.Shift))
+        {
+            if (layerControl.Layer is not null)
+            {
+                layerControl.Layer.Document.Operations.SetSelectedMember(layerControl.Layer.Id);
+                layerControl.Layer.Document.Operations.ClearSoftSelectedMembers();
+            }
+        }
+
         e.Pointer.Capture(null);
     }
 
@@ -108,7 +120,8 @@ internal partial class LayersManager : UserControl
             isLeftPressed && Equals(e.Pointer.Captured, container))
         {
             DataObject data = new();
-            data.Set(FolderControl.FolderControlDataName, container);
+            Guid[] selectedGuids = container.Folder.Document.GetSelectedMembersInOrder().ToArray();
+            data.Set(LayersDataName, selectedGuids);
             Dispatcher.UIThread.InvokeAsync(() => DragDrop.DoDragDrop(e, data, DragDropEffects.Move));
         }
     }
@@ -118,6 +131,16 @@ internal partial class LayersManager : UserControl
         if (sender is not FolderControl folderControl)
             return;
 
+        if (e is { Source: FolderControl layerControl, InitialPressMouseButton: MouseButton.Left } &&
+            !e.KeyModifiers.HasFlag(KeyModifiers.Control) && !e.KeyModifiers.HasFlag(KeyModifiers.Shift))
+        {
+            if (layerControl.Folder is not null)
+            {
+                layerControl.Folder.Document.Operations.SetSelectedMember(layerControl.Folder.Id);
+                layerControl.Folder.Document.Operations.ClearSoftSelectedMembers();
+            }
+        }
+
         e.Pointer.Capture(null);
     }
 
@@ -141,12 +164,19 @@ internal partial class LayersManager : UserControl
         }
 
         dropBorder.BorderBrush = Brushes.Transparent;
-        Guid? droppedGuid = LayerControl.ExtractMemberGuid(e.Data);
-
-        if (droppedGuid is not null && ActiveDocument is not null)
+        Guid[]? droppedGuids = LayerControl.ExtractMemberGuids(e.Data);
+        if (droppedGuids != null)
         {
-            ActiveDocument.Operations.MoveStructureMember((Guid)droppedGuid,
-                ActiveDocument.NodeGraph.StructureTree.Members[^1].Id, StructureMemberPlacement.Below);
+            using var block = ActiveDocument.Operations.StartChangeBlock();
+            Guid lastMovedMember = ActiveDocument.NodeGraph.StructureTree.Members[^1].Id;
+
+            foreach (Guid memberGuid in droppedGuids)
+            {
+                ActiveDocument.Operations.MoveStructureMember(memberGuid, lastMovedMember,
+                    StructureMemberPlacement.Below);
+                lastMovedMember = memberGuid;
+            }
+
             e.Handled = true;
         }
 
@@ -163,7 +193,7 @@ internal partial class LayersManager : UserControl
             return;
         }
 
-        var member = LayerControl.ExtractMemberGuid(e.Data);
+        var member = LayerControl.ExtractMemberGuids(e.Data);
 
         if (member == null)
         {
@@ -221,7 +251,7 @@ internal partial class LayersManager : UserControl
                         member.Document.Operations.AddSoftSelectedMember(member.Id);
                 });
         }
-        else
+        else if (!ActiveDocument.SelectedMembers.Contains(memberVM.Id))
         {
             ActiveDocument.Operations.SetSelectedMember(memberVM.Id);
             ActiveDocument.Operations.ClearSoftSelectedMembers();
@@ -233,7 +263,7 @@ internal partial class LayersManager : UserControl
     {
         if (matches == 2)
             return 2;
-        
+
         var reversed = root.Reverse();
         foreach (var child in reversed)
         {