Browse Source

Create tight transform for move tool on select

Krzysztof Krysiński 1 year ago
parent
commit
a6e2cce996

+ 7 - 2
src/PixiEditor.AvaloniaUI/Models/DocumentModels/Public/DocumentOperationsModule.cs

@@ -35,12 +35,17 @@ internal class DocumentOperationsModule : IDocumentOperations
     /// <summary>
     /// <summary>
     /// Creates a new selection with the size of the document
     /// Creates a new selection with the size of the document
     /// </summary>
     /// </summary>
-    public void SelectAll()
+    public void SelectAll() => Select(new RectI(VecI.Zero, Document.SizeBindable), SelectionMode.Add);
+
+    /// <summary>
+    /// Creates a new selection with the size of the document
+    /// </summary>
+    public void Select(RectI rect, SelectionMode mode = SelectionMode.New)
     {
     {
         if (Internals.ChangeController.IsChangeActive)
         if (Internals.ChangeController.IsChangeActive)
             return;
             return;
         Internals.ActionAccumulator.AddFinishedActions(
         Internals.ActionAccumulator.AddFinishedActions(
-            new SelectRectangle_Action(new RectI(VecI.Zero, Document.SizeBindable), SelectionMode.Add),
+            new SelectRectangle_Action(rect, mode),
             new EndSelectRectangle_Action());
             new EndSelectRectangle_Action());
     }
     }
 
 

+ 2 - 0
src/PixiEditor.AvaloniaUI/Models/Handlers/IStructureMemberHandler.cs

@@ -1,6 +1,7 @@
 using Avalonia.Media.Imaging;
 using Avalonia.Media.Imaging;
 using ChunkyImageLib;
 using ChunkyImageLib;
 using PixiEditor.AvaloniaUI.Models.Layers;
 using PixiEditor.AvaloniaUI.Models.Layers;
+using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.DrawingApi.Core.Surface;
 using PixiEditor.DrawingApi.Core.Surface;
 using BlendMode = PixiEditor.ChangeableDocument.Enums.BlendMode;
 using BlendMode = PixiEditor.ChangeableDocument.Enums.BlendMode;
 
 
@@ -18,6 +19,7 @@ internal interface IStructureMemberHandler : IHandler
     public float OpacityBindable { get; set; }
     public float OpacityBindable { get; set; }
     public IDocument Document { get; }
     public IDocument Document { get; }
     public bool IsVisibleBindable { get; set; }
     public bool IsVisibleBindable { get; set; }
+    public RectI? TightBounds { get; }
     public void SetMaskIsVisible(bool infoIsVisible);
     public void SetMaskIsVisible(bool infoIsVisible);
     public void SetClipToMemberBelowEnabled(bool infoClipToMemberBelow);
     public void SetClipToMemberBelowEnabled(bool infoClipToMemberBelow);
     public void SetBlendMode(BlendMode infoBlendMode);
     public void SetBlendMode(BlendMode infoBlendMode);

+ 2 - 2
src/PixiEditor.AvaloniaUI/ViewModels/Dock/LayoutManager.cs

@@ -51,7 +51,7 @@ internal class LayoutManager
                     Id = "DocumentArea",
                     Id = "DocumentArea",
                     FallbackContent = new CreateDocumentFallbackView()
                     FallbackContent = new CreateDocumentFallbackView()
                 },
                 },
-                FirstSize = 0.75,
+                FirstSize = 0.8,
                 SplitDirection = DockingDirection.Right,
                 SplitDirection = DockingDirection.Right,
                 Second = new DockableTree
                 Second = new DockableTree
                 {
                 {
@@ -68,7 +68,7 @@ internal class LayoutManager
                                 DockContext.CreateDockable(paletteViewerDockViewModel)
                                 DockContext.CreateDockable(paletteViewerDockViewModel)
                             ]
                             ]
                         },
                         },
-                        FirstSize = 0.5,
+                        FirstSize = 0.6,
                         SplitDirection = DockingDirection.Bottom,
                         SplitDirection = DockingDirection.Bottom,
                         Second = new DockableArea
                         Second = new DockableArea
                         {
                         {

+ 5 - 0
src/PixiEditor.AvaloniaUI/ViewModels/Document/DocumentViewModel.cs

@@ -582,6 +582,11 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
         return layerGuids;
         return layerGuids;
     }
     }
 
 
+    /// <summary>
+    ///     Gets all selected layers extracted from selected folders.
+    /// </summary>
+    /// <param name="includeFoldersWithMask">Should folders with mask be included</param>
+    /// <returns>A list of GUIDs of selected layers</returns>
     public List<Guid> ExtractSelectedLayers(bool includeFoldersWithMask = false)
     public List<Guid> ExtractSelectedLayers(bool includeFoldersWithMask = false)
     {
     {
         var result = new List<Guid>();
         var result = new List<Guid>();

+ 2 - 0
src/PixiEditor.AvaloniaUI/ViewModels/Document/StructureMemberViewModel.cs

@@ -51,6 +51,8 @@ internal abstract class StructureMemberViewModel : ObservableObject, IStructureM
     }
     }
 
 
     private bool maskIsVisible;
     private bool maskIsVisible;
+    public RectI? TightBounds => Internals.Tracker.Document.FindMember(GuidValue)?.GetTightBounds();
+
     public void SetMaskIsVisible(bool maskIsVisible)
     public void SetMaskIsVisible(bool maskIsVisible)
     {
     {
         this.maskIsVisible = maskIsVisible;
         this.maskIsVisible = maskIsVisible;

+ 58 - 1
src/PixiEditor.AvaloniaUI/ViewModels/Tools/Tools/MoveToolViewModel.cs

@@ -1,9 +1,13 @@
 using Avalonia.Input;
 using Avalonia.Input;
+using ChunkyImageLib.DataHolders;
 using PixiEditor.AvaloniaUI.Models.Commands.Attributes.Commands;
 using PixiEditor.AvaloniaUI.Models.Commands.Attributes.Commands;
+using PixiEditor.AvaloniaUI.Models.DocumentModels;
+using PixiEditor.AvaloniaUI.Models.Handlers;
 using PixiEditor.AvaloniaUI.Models.Handlers.Tools;
 using PixiEditor.AvaloniaUI.Models.Handlers.Tools;
 using PixiEditor.AvaloniaUI.ViewModels.Tools.ToolSettings.Toolbars;
 using PixiEditor.AvaloniaUI.ViewModels.Tools.ToolSettings.Toolbars;
 using PixiEditor.AvaloniaUI.Views.Overlays.BrushShapeOverlay;
 using PixiEditor.AvaloniaUI.Views.Overlays.BrushShapeOverlay;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.DrawingApi.Core.Surface.Vector;
 using PixiEditor.Extensions.Common.Localization;
 using PixiEditor.Extensions.Common.Localization;
 
 
 namespace PixiEditor.AvaloniaUI.ViewModels.Tools.Tools;
 namespace PixiEditor.AvaloniaUI.ViewModels.Tools.Tools;
@@ -18,6 +22,8 @@ internal class MoveToolViewModel : ToolViewModel, IMoveToolHandler
     private bool transformingSelectedArea = false;
     private bool transformingSelectedArea = false;
     public bool MoveAllLayers { get; set; }
     public bool MoveAllLayers { get; set; }
 
 
+    private bool removeSelection = false;
+
     public MoveToolViewModel()
     public MoveToolViewModel()
     {
     {
         ActionDisplay = defaultActionDisplay;
         ActionDisplay = defaultActionDisplay;
@@ -54,7 +60,7 @@ internal class MoveToolViewModel : ToolViewModel, IMoveToolHandler
         {
         {
             return;
             return;
         }
         }
-        
+
         if (ctrlIsDown)
         if (ctrlIsDown)
         {
         {
             ActionDisplay = new LocalizedString("MOVE_TOOL_ACTION_DISPLAY_CTRL");
             ActionDisplay = new LocalizedString("MOVE_TOOL_ACTION_DISPLAY_CTRL");
@@ -69,6 +75,57 @@ internal class MoveToolViewModel : ToolViewModel, IMoveToolHandler
 
 
     public override void OnSelected()
     public override void OnSelected()
     {
     {
+        RectI? bounds = GetSelectedLayersBounds();
+        bool? isEmpty = ViewModelMain.Current.DocumentManagerSubViewModel.ActiveDocument?.SelectionPathBindable
+            ?.IsEmpty;
+        if ((!isEmpty.HasValue || isEmpty.Value) && bounds.HasValue)
+        {
+            ViewModelMain.Current.DocumentManagerSubViewModel.ActiveDocument.Operations.Select(bounds.Value);
+            VectorPath path = new VectorPath();
+            path.AddRect(bounds.Value);
+            ViewModelMain.Current.DocumentManagerSubViewModel.ActiveDocument.UpdateSelectionPath(path);
+            removeSelection = true;
+        }
+
         ViewModelMain.Current.DocumentManagerSubViewModel.ActiveDocument?.Operations.TransformSelectedArea(true);
         ViewModelMain.Current.DocumentManagerSubViewModel.ActiveDocument?.Operations.TransformSelectedArea(true);
     }
     }
+
+    public override void OnDeselecting()
+    {
+        ViewModelMain.Current.DocumentManagerSubViewModel.ActiveDocument?.Operations.TryStopToolLinkedExecutor();
+
+        if (removeSelection)
+        {
+            ViewModelMain.Current.DocumentManagerSubViewModel.ActiveDocument?.Operations.ClearSelection();
+            removeSelection = false;
+        }
+    }
+
+    private static RectI? GetSelectedLayersBounds()
+    {
+        var layers = ViewModelMain.Current.DocumentManagerSubViewModel.ActiveDocument?.ExtractSelectedLayers();
+        RectI? bounds = null;
+        if (layers != null)
+        {
+            foreach (var layer in layers)
+            {
+                var foundLayer =
+                    ViewModelMain.Current.DocumentManagerSubViewModel.ActiveDocument.StructureHelper.Find(layer);
+                RectI? layerBounds = foundLayer?.TightBounds;
+                if (layerBounds != null)
+                {
+                    if (bounds == null)
+                    {
+                        bounds = layerBounds;
+                    }
+                    else
+                    {
+                        bounds = bounds.Value.Union(layerBounds.Value);
+                    }
+                }
+            }
+        }
+
+        return bounds;
+    }
 }
 }

+ 30 - 0
src/PixiEditor.ChangeableDocument/Changeables/Folder.cs

@@ -1,5 +1,6 @@
 using System.Collections.Immutable;
 using System.Collections.Immutable;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
+using PixiEditor.DrawingApi.Core.Numerics;
 
 
 namespace PixiEditor.ChangeableDocument.Changeables;
 namespace PixiEditor.ChangeableDocument.Changeables;
 
 
@@ -12,6 +13,35 @@ internal class Folder : StructureMember, IReadOnlyFolder
     public ImmutableList<StructureMember> Children { get; set; } = ImmutableList<StructureMember>.Empty;
     public ImmutableList<StructureMember> Children { get; set; } = ImmutableList<StructureMember>.Empty;
     IReadOnlyList<IReadOnlyStructureMember> IReadOnlyFolder.Children => Children;
     IReadOnlyList<IReadOnlyStructureMember> IReadOnlyFolder.Children => Children;
 
 
+    public override RectI? GetTightBounds()
+    {
+        if (Children.Count == 0)
+        {
+            return null;
+        }
+
+        var bounds = Children[0].GetTightBounds();
+        for (var i = 1; i < Children.Count; i++)
+        {
+            var childBounds = Children[i].GetTightBounds();
+            if (childBounds == null)
+            {
+                continue;
+            }
+
+            if (bounds == null)
+            {
+                bounds = childBounds;
+            }
+            else
+            {
+                bounds = bounds.Value.Union(childBounds.Value);
+            }
+        }
+
+        return bounds;
+    }
+
     /// <summary>
     /// <summary>
     /// Creates a clone of the folder, its mask and all of its children
     /// Creates a clone of the folder, its mask and all of its children
     /// </summary>
     /// </summary>

+ 0 - 1
src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyLayer.cs

@@ -5,5 +5,4 @@ namespace PixiEditor.ChangeableDocument.Changeables.Interfaces;
 public interface IReadOnlyLayer : IReadOnlyStructureMember
 public interface IReadOnlyLayer : IReadOnlyStructureMember
 {
 {
     public ChunkyImage Rasterize();
     public ChunkyImage Rasterize();
-    public RectI? GetTightBounds();
 }
 }

+ 13 - 0
src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyStructureMember.cs

@@ -1,4 +1,5 @@
 using PixiEditor.ChangeableDocument.Enums;
 using PixiEditor.ChangeableDocument.Enums;
+using PixiEditor.DrawingApi.Core.Numerics;
 
 
 namespace PixiEditor.ChangeableDocument.Changeables.Interfaces;
 namespace PixiEditor.ChangeableDocument.Changeables.Interfaces;
 
 
@@ -8,29 +9,41 @@ public interface IReadOnlyStructureMember
     /// Is the member visible. Defaults to true
     /// Is the member visible. Defaults to true
     /// </summary>
     /// </summary>
     bool IsVisible { get; }
     bool IsVisible { get; }
+
     /// <summary>
     /// <summary>
     /// The opacity of the member (Ranging from 0f to 1f)
     /// The opacity of the member (Ranging from 0f to 1f)
     /// </summary>
     /// </summary>
     float Opacity { get; }
     float Opacity { get; }
     bool ClipToMemberBelow { get; }
     bool ClipToMemberBelow { get; }
+
     /// <summary>
     /// <summary>
     /// The name of the member. Defaults to "Unnamed"
     /// The name of the member. Defaults to "Unnamed"
     /// </summary>
     /// </summary>
     string Name { get; }
     string Name { get; }
+
     /// <summary>
     /// <summary>
     /// The guid of the member
     /// The guid of the member
     /// </summary>
     /// </summary>
     Guid GuidValue { get; }
     Guid GuidValue { get; }
+
     /// <summary>
     /// <summary>
     /// The blend mode of the member
     /// The blend mode of the member
     /// </summary>
     /// </summary>
     BlendMode BlendMode { get; }
     BlendMode BlendMode { get; }
+
     /// <summary>
     /// <summary>
     /// Is the mask of the member visible. Defaults to true
     /// Is the mask of the member visible. Defaults to true
     /// </summary>
     /// </summary>
     bool MaskIsVisible { get; }
     bool MaskIsVisible { get; }
+
     /// <summary>
     /// <summary>
     /// The mask of the member
     /// The mask of the member
     /// </summary>
     /// </summary>
     IReadOnlyChunkyImage? Mask { get; }
     IReadOnlyChunkyImage? Mask { get; }
+
+    /// <summary>
+    ///     The tight bounds of the member. The bounds are the smallest rectangle that contains all the pixels of the member.
+    /// </summary>
+    /// <returns>The tight bounds of the member</returns>
+    public RectI? GetTightBounds();
 }
 }

+ 0 - 1
src/PixiEditor.ChangeableDocument/Changeables/Layer.cs

@@ -6,5 +6,4 @@ namespace PixiEditor.ChangeableDocument.Changeables;
 internal abstract class Layer : StructureMember, IReadOnlyLayer
 internal abstract class Layer : StructureMember, IReadOnlyLayer
 {
 {
     public abstract ChunkyImage Rasterize();
     public abstract ChunkyImage Rasterize();
-    public abstract RectI? GetTightBounds();
 }
 }

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changeables/RasterLayer.cs

@@ -36,7 +36,7 @@ internal class RasterLayer : Layer, IReadOnlyRasterLayer
 
 
     public override RectI? GetTightBounds()
     public override RectI? GetTightBounds()
     {
     {
-        return LayerImage.FindChunkAlignedMostUpToDateBounds();
+        return LayerImage.FindTightCommittedBounds();
     }
     }
 
 
     /// <summary>
     /// <summary>

+ 3 - 0
src/PixiEditor.ChangeableDocument/Changeables/StructureMember.cs

@@ -1,5 +1,6 @@
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.ChangeableDocument.Enums;
 using PixiEditor.ChangeableDocument.Enums;
+using PixiEditor.DrawingApi.Core.Numerics;
 
 
 namespace PixiEditor.ChangeableDocument.Changeables;
 namespace PixiEditor.ChangeableDocument.Changeables;
 
 
@@ -14,6 +15,8 @@ internal abstract class StructureMember : IChangeable, IReadOnlyStructureMember,
     public BlendMode BlendMode { get; set; } = BlendMode.Normal;
     public BlendMode BlendMode { get; set; } = BlendMode.Normal;
     public Guid GuidValue { get; set; }
     public Guid GuidValue { get; set; }
     public ChunkyImage? Mask { get; set; } = null;
     public ChunkyImage? Mask { get; set; } = null;
+    public abstract RectI? GetTightBounds();
+
     public bool MaskIsVisible { get; set; } = true;
     public bool MaskIsVisible { get; set; } = true;
     IReadOnlyChunkyImage? IReadOnlyStructureMember.Mask => Mask;
     IReadOnlyChunkyImage? IReadOnlyStructureMember.Mask => Mask;
     internal abstract StructureMember Clone();
     internal abstract StructureMember Clone();