Browse Source

Merge pull request #647 from PixiEditor/select-layer-move-tool

Select layer move tool
Krzysztof Krysiński 9 months ago
parent
commit
4e84da1e3a
30 changed files with 222 additions and 87 deletions
  1. 15 10
      src/PixiEditor/Models/Controllers/InputDevice/MouseInputFilter.cs
  2. 3 1
      src/PixiEditor/Models/Controllers/InputDevice/MouseOnCanvasEventArgs.cs
  3. 5 4
      src/PixiEditor/Models/DocumentModels/ChangeExecutionController.cs
  4. 3 2
      src/PixiEditor/Models/DocumentModels/Public/DocumentEventsModule.cs
  5. 1 1
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/BrightnessToolExecutor.cs
  6. 1 1
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/ColorPickerToolExecutor.cs
  7. 3 3
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/ComplexShapeToolExecutor.cs
  8. 1 1
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/EraserToolExecutor.cs
  9. 1 1
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/FloodFillToolExecutor.cs
  10. 1 1
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/LassoToolExecutor.cs
  11. 3 3
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/LineExecutor.cs
  12. 2 1
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/MagicWandToolExecutor.cs
  13. 1 1
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/PenToolExecutor.cs
  14. 1 1
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/SelectToolExecutor.cs
  15. 1 1
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/ShiftLayerExecutor.cs
  16. 3 2
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/SimpleShapeToolExecutor.cs
  17. 106 9
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/TransformSelectedExecutor.cs
  18. 3 2
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/UpdateableChangeExecutor.cs
  19. 1 1
      src/PixiEditor/Models/Handlers/INodeHandler.cs
  20. 3 0
      src/PixiEditor/Models/Handlers/ITransformHandler.cs
  21. 1 1
      src/PixiEditor/Styles/Templates/NodeGraphView.axaml
  22. 13 0
      src/PixiEditor/ViewModels/Document/TransformOverlays/DocumentTransformViewModel.cs
  23. 1 1
      src/PixiEditor/ViewModels/Nodes/NodeViewModel.cs
  24. 5 3
      src/PixiEditor/ViewModels/SubViewModels/IoViewModel.cs
  25. 1 1
      src/PixiEditor/ViewModels/SubViewModels/NodeGraphManagerViewModel.cs
  26. 9 2
      src/PixiEditor/ViewModels/Tools/Tools/MoveToolViewModel.cs
  27. 3 3
      src/PixiEditor/Views/Main/ViewportControls/Viewport.axaml.cs
  28. 8 0
      src/PixiEditor/Views/Main/ViewportControls/ViewportOverlays.cs
  29. 3 3
      src/PixiEditor/Views/Nodes/NodeGraphView.cs
  30. 20 27
      src/PixiEditor/Views/Overlays/TransformOverlay/TransformOverlay.cs

+ 15 - 10
src/PixiEditor/Models/Controllers/InputDevice/MouseInputFilter.cs

@@ -10,7 +10,7 @@ internal class MouseInputFilter
 {
     public EventHandler<MouseOnCanvasEventArgs> OnMouseDown;
     public EventHandler<VecD> OnMouseMove;
-    public EventHandler<MouseButton> OnMouseUp;
+    public EventHandler<MouseOnCanvasEventArgs> OnMouseUp;
 
 
     private Dictionary<MouseButton, bool> buttonStates = new()
@@ -36,23 +36,28 @@ internal class MouseInputFilter
 
     public void MouseMoveInlet(object args) => OnMouseMove?.Invoke(this, ((MouseOnCanvasEventArgs)args).PositionOnCanvas);
 
-    public void MouseUpInlet(object args) => MouseUpInlet(((MouseOnCanvasEventArgs)args).Button);
+    public void MouseUpInlet(object args) => MouseUpInlet(((MouseOnCanvasEventArgs)args));
     public void MouseUpInlet(object? sender, Point p, MouseButton button) => MouseUpInlet(button);
-    public void MouseUpInlet(MouseButton button)
+    public void MouseUpInlet(MouseOnCanvasEventArgs args)
     {
-        if (button is MouseButton.XButton1 or MouseButton.XButton2 or MouseButton.None)
+        if (args.Button is MouseButton.XButton1 or MouseButton.XButton2 or MouseButton.None)
             return;
-        if (!buttonStates[button])
+        if (!buttonStates[args.Button])
             return;
-        buttonStates[button] = false;
+        buttonStates[args.Button] = false;
 
-        OnMouseUp?.Invoke(this, button);
+        OnMouseUp?.Invoke(this, args);
     }
 
     public void DeactivatedInlet(object? sender, EventArgs e)
     {
-        MouseUpInlet(MouseButton.Left);
-        MouseUpInlet(MouseButton.Middle);
-        MouseUpInlet(MouseButton.Right);
+        MouseOnCanvasEventArgs argsLeft = new(MouseButton.Left, VecD.Zero, KeyModifiers.None);
+        MouseUpInlet(argsLeft);
+        
+        MouseOnCanvasEventArgs argsMiddle = new(MouseButton.Middle, VecD.Zero, KeyModifiers.None);
+        MouseUpInlet(argsMiddle);
+        
+        MouseOnCanvasEventArgs argsRight = new(MouseButton.Right, VecD.Zero, KeyModifiers.None);
+        MouseUpInlet(argsRight);
     }
 }

+ 3 - 1
src/PixiEditor/Models/Controllers/InputDevice/MouseOnCanvasEventArgs.cs

@@ -5,12 +5,14 @@ using Drawie.Numerics;
 namespace PixiEditor.Models.Controllers.InputDevice;
 internal class MouseOnCanvasEventArgs : EventArgs
 {
-    public MouseOnCanvasEventArgs(MouseButton button, VecD positionOnCanvas)
+    public MouseOnCanvasEventArgs(MouseButton button, VecD positionOnCanvas, KeyModifiers keyModifiers)
     {
         Button = button;
         PositionOnCanvas = positionOnCanvas;
+        KeyModifiers = keyModifiers;
     }
 
     public MouseButton Button { get; }
     public VecD PositionOnCanvas { get; }
+    public KeyModifiers KeyModifiers { get; }
 }

+ 5 - 4
src/PixiEditor/Models/DocumentModels/ChangeExecutionController.cs

@@ -8,6 +8,7 @@ using PixiEditor.Models.DocumentModels.UpdateableChangeExecutors.Features;
 using PixiEditor.Models.Handlers;
 using PixiEditor.Models.Tools;
 using Drawie.Numerics;
+using PixiEditor.Models.Controllers.InputDevice;
 using PixiEditor.Views.Overlays.SymmetryOverlay;
 
 namespace PixiEditor.Models.DocumentModels;
@@ -196,7 +197,7 @@ internal class ChangeExecutionController
 
     public void SymmetryDragEndedInlet(SymmetryAxisDirection dir) => currentSession?.OnSymmetryDragEnded(dir);
 
-    public void LeftMouseButtonDownInlet(VecD canvasPos)
+    public void LeftMouseButtonDownInlet(MouseOnCanvasEventArgs args)
     {
         //update internal state
         LeftMousePressed = true;
@@ -207,16 +208,16 @@ internal class ChangeExecutionController
         }
 
         //call session event
-        currentSession?.OnLeftMouseButtonDown(canvasPos);
+        currentSession?.OnLeftMouseButtonDown(args);
     }
 
-    public void LeftMouseButtonUpInlet()
+    public void LeftMouseButtonUpInlet(VecD argsPositionOnCanvas)
     {
         //update internal state
         LeftMousePressed = false;
 
         //call session events
-        currentSession?.OnLeftMouseButtonUp();
+        currentSession?.OnLeftMouseButtonUp(argsPositionOnCanvas);
     }
 
     public void TransformMovedInlet(ShapeCorners corners)

+ 3 - 2
src/PixiEditor/Models/DocumentModels/Public/DocumentEventsModule.cs

@@ -5,6 +5,7 @@ using Drawie.Backend.Core.Numerics;
 using PixiEditor.Models.Events;
 using PixiEditor.Models.Handlers;
 using Drawie.Numerics;
+using PixiEditor.Models.Controllers.InputDevice;
 using PixiEditor.Views.Overlays.SymmetryOverlay;
 
 namespace PixiEditor.Models.DocumentModels.Public;
@@ -33,13 +34,13 @@ internal class DocumentEventsModule
         DocumentsHandler.TransformHandler.KeyModifiersInlet(args.IsShiftDown, args.IsCtrlDown, args.IsAltDown);
     }
 
-    public void OnCanvasLeftMouseButtonDown(VecD pos) => Internals.ChangeController.LeftMouseButtonDownInlet(pos);
+    public void OnCanvasLeftMouseButtonDown(MouseOnCanvasEventArgs args) => Internals.ChangeController.LeftMouseButtonDownInlet(args);
     public void OnCanvasMouseMove(VecD newPos)
     {
         DocumentsHandler.CoordinatesString = $"X: {(int)newPos.X} Y: {(int)newPos.Y}";
         Internals.ChangeController.MouseMoveInlet(newPos);
     }
-    public void OnCanvasLeftMouseButtonUp() => Internals.ChangeController.LeftMouseButtonUpInlet();
+    public void OnCanvasLeftMouseButtonUp(VecD argsPositionOnCanvas) => Internals.ChangeController.LeftMouseButtonUpInlet(argsPositionOnCanvas);
     public void OnOpacitySliderDragStarted() => Internals.ChangeController.OpacitySliderDragStartedInlet();
     public void OnOpacitySliderDragged(float newValue) => Internals.ChangeController.OpacitySliderDraggedInlet(newValue);
     public void OnOpacitySliderDragEnded() => Internals.ChangeController.OpacitySliderDragEndedInlet();

+ 1 - 1
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/BrightnessToolExecutor.cs

@@ -42,7 +42,7 @@ internal class BrightnessToolExecutor : UpdateableChangeExecutor
         internals!.ActionAccumulator.AddActions(action);
     }
 
-    public override void OnLeftMouseButtonUp()
+    public override void OnLeftMouseButtonUp(VecD argsPositionOnCanvas)
     {
         internals!.ActionAccumulator.AddFinishedActions(new EndChangeBrightness_Action());
         onEnded?.Invoke(this);

+ 1 - 1
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/ColorPickerToolExecutor.cs

@@ -42,7 +42,7 @@ internal class ColorPickerToolExecutor : UpdateableChangeExecutor
         colorsViewModel.PrimaryColor = document.PickColor(pos, scope, includeReference, includeCanvas, document.AnimationHandler.ActiveFrameBindable, document.ReferenceLayerHandler.IsTopMost);
     }
 
-    public override void OnLeftMouseButtonUp()
+    public override void OnLeftMouseButtonUp(VecD argsPositionOnCanvas)
     {
         onEnded?.Invoke(this);
     }

+ 3 - 3
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/ComplexShapeToolExecutor.cs

@@ -256,7 +256,7 @@ internal abstract class ComplexShapeToolExecutor<T> : SimpleShapeToolExecutor wh
         internals!.ActionAccumulator.AddActions(SettingsChangedAction());
     }
 
-    public override void OnLeftMouseButtonUp()
+    public override void OnLeftMouseButtonUp(VecD argsPositionOnCanvas)
     {
         if (ActiveMode != ShapeToolMode.Transform)
         {
@@ -265,13 +265,13 @@ internal abstract class ComplexShapeToolExecutor<T> : SimpleShapeToolExecutor wh
                 internals!.ActionAccumulator.AddFinishedActions(EndDrawAction());
                 AddMemberToSnapping();
 
-                base.OnLeftMouseButtonUp();
+                base.OnLeftMouseButtonUp(argsPositionOnCanvas);
                 onEnded?.Invoke(this);
                 return;
             }
         }
 
-        base.OnLeftMouseButtonUp();
+        base.OnLeftMouseButtonUp(argsPositionOnCanvas);
     }
 
     protected override void StartMode(ShapeToolMode mode)

+ 1 - 1
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/EraserToolExecutor.cs

@@ -60,7 +60,7 @@ internal class EraserToolExecutor : UpdateableChangeExecutor
         internals!.ActionAccumulator.AddActions(action);
     }
 
-    public override void OnLeftMouseButtonUp()
+    public override void OnLeftMouseButtonUp(VecD argsPositionOnCanvas)
     {
         internals!.ActionAccumulator.AddFinishedActions(new EndLineBasedPen_Action());
         onEnded?.Invoke(this);

+ 1 - 1
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/FloodFillToolExecutor.cs

@@ -50,7 +50,7 @@ internal class FloodFillToolExecutor : UpdateableChangeExecutor
         internals!.ActionAccumulator.AddActions(new FloodFill_Action(memberGuid, pos, color, considerAllLayers, tolerance, drawOnMask, document!.AnimationHandler.ActiveFrameBindable));
     }
 
-    public override void OnLeftMouseButtonUp()
+    public override void OnLeftMouseButtonUp(VecD argsPositionOnCanvas)
     {
         internals!.ActionAccumulator.AddActions(new ChangeBoundary_Action());
         onEnded!(this);

+ 1 - 1
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/LassoToolExecutor.cs

@@ -25,7 +25,7 @@ internal sealed class LassoToolExecutor : UpdateableChangeExecutor
 
     public override void OnPixelPositionChange(VecI pos) => AddStartAction(pos);
 
-    public override void OnLeftMouseButtonUp()
+    public override void OnLeftMouseButtonUp(VecD argsPositionOnCanvas)
     {
         internals!.ActionAccumulator.AddFinishedActions(new EndSelectLasso_Action());
         onEnded!(this);

+ 3 - 3
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/LineExecutor.cs

@@ -115,18 +115,18 @@ internal abstract class LineExecutor<T> : SimpleShapeToolExecutor where T : ILin
         internals!.ActionAccumulator.AddActions(drawLineAction);
     }
 
-    public override void OnLeftMouseButtonUp()
+    public override void OnLeftMouseButtonUp(VecD argsPositionOnCanvas)
     {
         if (!startedDrawing)
         {
-            base.OnLeftMouseButtonUp();
+            base.OnLeftMouseButtonUp(argsPositionOnCanvas);
             onEnded!(this);
             return;
         }
 
         document!.LineToolOverlayHandler.Hide();
         document!.LineToolOverlayHandler.Show(startDrawingPos, curPos, true);
-        base.OnLeftMouseButtonUp();
+        base.OnLeftMouseButtonUp(argsPositionOnCanvas);
     }
 
     public override void OnLineOverlayMoved(VecD start, VecD end)

+ 2 - 1
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/MagicWandToolExecutor.cs

@@ -1,5 +1,6 @@
 using System.Collections.Generic;
 using System.Linq;
+using Drawie.Numerics;
 using PixiEditor.ChangeableDocument.Actions.Generated;
 using PixiEditor.ChangeableDocument.Actions.Undo;
 using PixiEditor.ChangeableDocument.Enums;
@@ -37,7 +38,7 @@ internal class MagicWandToolExecutor : UpdateableChangeExecutor
         return ExecutionState.Success;
     }
 
-    public override void OnLeftMouseButtonUp()
+    public override void OnLeftMouseButtonUp(VecD argsPositionOnCanvas)
     {
         internals!.ActionAccumulator.AddActions(new ChangeBoundary_Action());
         onEnded!(this);

+ 1 - 1
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/PenToolExecutor.cs

@@ -66,7 +66,7 @@ internal class PenToolExecutor : UpdateableChangeExecutor
         internals!.ActionAccumulator.AddActions(action);
     }
 
-    public override void OnLeftMouseButtonUp()
+    public override void OnLeftMouseButtonUp(VecD argsPositionOnCanvas)
     {
         IAction? action = pixelPerfect switch
         {

+ 1 - 1
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/SelectToolExecutor.cs

@@ -56,7 +56,7 @@ internal class SelectToolExecutor : UpdateableChangeExecutor
         internals!.ActionAccumulator.AddActions(action);
     }
 
-    public override void OnLeftMouseButtonUp()
+    public override void OnLeftMouseButtonUp(VecD argsPositionOnCanvas)
     {
         IAction action = CreateEndAction(selectShape);
         internals!.ActionAccumulator.AddFinishedActions(action);

+ 1 - 1
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/ShiftLayerExecutor.cs

@@ -65,7 +65,7 @@ internal class ShiftLayerExecutor : UpdateableChangeExecutor
         internals!.ActionAccumulator.AddActions(action);
     }
 
-    public override void OnLeftMouseButtonUp()
+    public override void OnLeftMouseButtonUp(VecD argsPositionOnCanvas)
     {
         internals!.ActionAccumulator.AddFinishedActions(new EndShiftLayer_Action());
         onEnded?.Invoke(this);

+ 3 - 2
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/SimpleShapeToolExecutor.cs

@@ -3,6 +3,7 @@ using PixiEditor.Models.DocumentModels.UpdateableChangeExecutors.Features;
 using PixiEditor.Models.Handlers;
 using PixiEditor.Models.Tools;
 using Drawie.Numerics;
+using PixiEditor.Models.Controllers.InputDevice;
 
 namespace PixiEditor.Models.DocumentModels.UpdateableChangeExecutors;
 
@@ -107,7 +108,7 @@ internal abstract class SimpleShapeToolExecutor : UpdateableChangeExecutor,
         document!.LineToolOverlayHandler.Hide();
     }
 
-    public override void OnLeftMouseButtonDown(VecD pos)
+    public override void OnLeftMouseButtonDown(MouseOnCanvasEventArgs args)
     {
         if (ActiveMode == ShapeToolMode.Preview)
         {
@@ -131,7 +132,7 @@ internal abstract class SimpleShapeToolExecutor : UpdateableChangeExecutor,
         }
     }
 
-    public override void OnLeftMouseButtonUp()
+    public override void OnLeftMouseButtonUp(VecD argsPositionOnCanvas)
     {
         HighlightSnapping(null, null);
         ActiveMode = ShapeToolMode.Transform;

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

@@ -1,5 +1,6 @@
 using System.Collections.Generic;
 using System.Linq;
+using Avalonia.Input;
 using ChunkyImageLib.DataHolders;
 using PixiEditor.ChangeableDocument.Actions.Generated;
 using Drawie.Backend.Core.Numerics;
@@ -10,6 +11,7 @@ using PixiEditor.Models.Handlers;
 using PixiEditor.Models.Handlers.Tools;
 using PixiEditor.Models.Tools;
 using Drawie.Numerics;
+using PixiEditor.Models.Controllers.InputDevice;
 using PixiEditor.ViewModels.Document.Nodes;
 
 namespace PixiEditor.Models.DocumentModels.UpdateableChangeExecutors;
@@ -21,10 +23,12 @@ internal class TransformSelectedExecutor : UpdateableChangeExecutor, ITransforma
     private bool isInProgress;
     public override ExecutorType Type { get; }
 
-    public override bool BlocksOtherActions => false; 
-    
+    public override bool BlocksOtherActions => false;
+
     private List<Guid> selectedMembers = new();
 
+    private ShapeCorners lastCorners = new();
+
     public TransformSelectedExecutor(bool toolLinked)
     {
         Type = toolLinked ? ExecutorType.ToolLinked : ExecutorType.Regular;
@@ -46,6 +50,7 @@ internal class TransformSelectedExecutor : UpdateableChangeExecutor, ITransforma
         if (!members.Any())
             return ExecutionState.Error;
 
+        document.TransformHandler.PassthroughPointerPressed += OnLeftMouseButtonDown;
         return SelectMembers(members);
     }
 
@@ -81,15 +86,18 @@ internal class TransformSelectedExecutor : UpdateableChangeExecutor, ITransforma
         DocumentTransformMode mode = allRaster
             ? DocumentTransformMode.Scale_Rotate_Shear_Perspective
             : DocumentTransformMode.Scale_Rotate_Shear_NoPerspective;
-        
+
         foreach (var structureMemberHandler in members)
         {
             document.SnappingHandler.Remove(structureMemberHandler.Id.ToString());
         }
-        
+
         selectedMembers = members.Select(m => m.Id).ToList();
-        
-        document.TransformHandler.ShowTransform(mode, true, masterCorners, Type == ExecutorType.Regular);
+
+        lastCorners = masterCorners;
+        document.TransformHandler.ShowTransform(mode, true, masterCorners,
+            Type == ExecutorType.Regular || tool.KeepOriginalImage);
+
         internals!.ActionAccumulator.AddActions(
             new TransformSelected_Action(masterCorners, tool.KeepOriginalImage, memberCorners, false,
                 document.AnimationHandler.ActiveFrameBindable));
@@ -98,6 +106,87 @@ internal class TransformSelectedExecutor : UpdateableChangeExecutor, ITransforma
         return ExecutionState.Success;
     }
 
+    public override void OnLeftMouseButtonDown(MouseOnCanvasEventArgs args)
+    {
+        var allLayers = document.StructureHelper.GetAllLayers();
+        var topMostWithinClick = allLayers.Where(x =>
+                x is { IsVisibleBindable: true, TightBounds: not null } &&
+                x.TightBounds.Value.ContainsInclusive(args.PositionOnCanvas))
+            .OrderByDescending(x => allLayers.IndexOf(x));
+
+        var nonSelected = topMostWithinClick.Where(x => x != document.SelectedStructureMember
+                                                        && !document.SoftSelectedStructureMembers.Contains(x))
+            .ToArray();
+        
+        bool isHoldingShift = args.KeyModifiers.HasFlag(KeyModifiers.Shift);
+
+        if (nonSelected.Any())
+        {
+            var topMost = nonSelected.First();
+
+            if (!isHoldingShift)
+            {
+                document.Operations.ClearSoftSelectedMembers();
+                document.Operations.SetSelectedMember(topMost.Id);
+            }
+            else
+            {
+                document.Operations.AddSoftSelectedMember(topMost.Id);
+            }
+        }
+        else if (isHoldingShift)
+        {
+            var topMostList = topMostWithinClick.ToList();
+            if (document.SoftSelectedStructureMembers.Count > 0)
+            {
+                Deselect(topMostList);
+            }
+        }
+    }
+
+    private void Deselect(List<ILayerHandler> topMostWithinClick)
+    {
+        var topMost = topMostWithinClick.FirstOrDefault();
+        if (topMost is not null)
+        {
+            bool deselectingWasMain = document.SelectedStructureMember.Id == topMost.Id;
+            if (deselectingWasMain)
+            {
+                Guid? nextMain = document.SoftSelectedStructureMembers.FirstOrDefault().Id;
+                List<Guid> softSelected = document.SoftSelectedStructureMembers
+                    .Select(x => x.Id).Where(x => x != nextMain.Value).ToList();
+                    
+                document.Operations.ClearSoftSelectedMembers();
+                document.Operations.SetSelectedMember(nextMain.Value);
+                    
+                foreach (var guid in softSelected)
+                {
+                    document.Operations.AddSoftSelectedMember(guid);
+                }
+            }
+            else
+            {
+                List<Guid> softSelected = document.SoftSelectedStructureMembers
+                    .Select(x => x.Id).Where(x => x != topMost.Id).ToList();
+                    
+                document.Operations.ClearSoftSelectedMembers();
+                    
+                foreach (var guid in softSelected)
+                {
+                    document.Operations.AddSoftSelectedMember(guid);
+                }
+            }
+        }
+    }
+
+    public override void OnSettingsChanged(string name, object value)
+    {
+        if (name == nameof(IMoveToolHandler.KeepOriginalImage))
+        {
+            DoTransform(lastCorners);
+        }
+    }
+
     public override void OnMembersSelected(List<Guid> memberGuids)
     {
         if (isInProgress)
@@ -105,7 +194,7 @@ internal class TransformSelectedExecutor : UpdateableChangeExecutor, ITransforma
             internals.ActionAccumulator.AddActions(new EndTransformSelected_Action());
             document!.TransformHandler.HideTransform();
             AddSnappingForMembers(selectedMembers);
-            
+
             selectedMembers.Clear();
             memberCorners.Clear();
             isInProgress = false;
@@ -122,6 +211,12 @@ internal class TransformSelectedExecutor : UpdateableChangeExecutor, ITransforma
     public bool IsTransforming => isInProgress;
 
     public void OnTransformMoved(ShapeCorners corners)
+    {
+        DoTransform(corners);
+        lastCorners = corners;
+    }
+
+    private void DoTransform(ShapeCorners corners)
     {
         if (!isInProgress)
             return;
@@ -138,7 +233,7 @@ internal class TransformSelectedExecutor : UpdateableChangeExecutor, ITransforma
     public void OnMidChangeUndo() => document!.TransformHandler.Undo();
 
     public void OnMidChangeRedo() => document!.TransformHandler.Redo();
-    public bool CanUndo => document!.TransformHandler.HasUndo; 
+    public bool CanUndo => document!.TransformHandler.HasUndo;
     public bool CanRedo => document!.TransformHandler.HasRedo;
 
     public void OnTransformApplied()
@@ -160,6 +255,7 @@ internal class TransformSelectedExecutor : UpdateableChangeExecutor, ITransforma
         }
 
         isInProgress = false;
+        document.TransformHandler.PassthroughPointerPressed -= OnLeftMouseButtonDown;
     }
 
     public override void ForceStop()
@@ -175,8 +271,9 @@ internal class TransformSelectedExecutor : UpdateableChangeExecutor, ITransforma
         AddSnappingForMembers(memberCorners.Keys.ToList());
 
         isInProgress = false;
+        document.TransformHandler.PassthroughPointerPressed -= OnLeftMouseButtonDown;
     }
-    
+
     private void AddSnappingForMembers(List<Guid> memberGuids)
     {
         foreach (Guid memberGuid in memberGuids)

+ 3 - 2
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/UpdateableChangeExecutor.cs

@@ -7,6 +7,7 @@ using Drawie.Backend.Core.Numerics;
 using PixiEditor.Models.Handlers;
 using PixiEditor.Models.Tools;
 using Drawie.Numerics;
+using PixiEditor.Models.Controllers.InputDevice;
 using PixiEditor.Views.Overlays.SymmetryOverlay;
 
 namespace PixiEditor.Models.DocumentModels.UpdateableChangeExecutors;
@@ -48,8 +49,8 @@ internal abstract class UpdateableChangeExecutor
     public abstract void ForceStop();
     public virtual void OnPixelPositionChange(VecI pos) { }
     public virtual void OnPrecisePositionChange(VecD pos) { }
-    public virtual void OnLeftMouseButtonDown(VecD pos) { }
-    public virtual void OnLeftMouseButtonUp() { }
+    public virtual void OnLeftMouseButtonDown(MouseOnCanvasEventArgs args) { }
+    public virtual void OnLeftMouseButtonUp(VecD pos) { }
     public virtual void OnOpacitySliderDragStarted() { }
     public virtual void OnOpacitySliderDragged(float newValue) { }
     public virtual void OnOpacitySliderDragEnded() { }

+ 1 - 1
src/PixiEditor/Models/Handlers/INodeHandler.cs

@@ -22,7 +22,7 @@ public interface INodeHandler : INotifyPropertyChanged
     public ObservableRangeCollection<INodePropertyHandler> Outputs { get; }
     public PreviewPainter? ResultPainter { get; set; }
     public VecD PositionBindable { get; set; }
-    public bool IsSelected { get; set; }
+    public bool IsNodeSelected { get; set; }
     public void TraverseBackwards(Func<INodeHandler, bool> func);
     public void TraverseBackwards(Func<INodeHandler, INodeHandler, bool> func);
     public void TraverseBackwards(Func<INodeHandler, INodeHandler, INodePropertyHandler, bool> func);

+ 3 - 0
src/PixiEditor/Models/Handlers/ITransformHandler.cs

@@ -2,6 +2,7 @@
 using Drawie.Backend.Core.Numerics;
 using PixiEditor.Models.DocumentModels;
 using Drawie.Numerics;
+using PixiEditor.Models.Controllers.InputDevice;
 
 namespace PixiEditor.Models.Handlers;
 
@@ -15,6 +16,8 @@ internal interface ITransformHandler : IHandler
     public bool Nudge(VecD distance);
     public bool HasUndo { get; }
     public bool HasRedo { get; }
+    public bool ShowTransformControls { get; set; }
+    public event Action<MouseOnCanvasEventArgs> PassthroughPointerPressed;
     public ShapeCorners Corners { get; set; }
     public bool ShowHandles { get; set; }
     public bool IsSizeBoxEnabled { get; set; }

+ 1 - 1
src/PixiEditor/Styles/Templates/NodeGraphView.axaml

@@ -45,7 +45,7 @@
                                         BorderBrush="{Binding InternalName, Converter={converters:NodeInternalNameToStyleConverter}, ConverterParameter='BorderBrush'}"
                                         BorderThickness="2"
                                         Outputs="{Binding Outputs}"
-                                        IsSelected="{Binding IsSelected}"
+                                        IsSelected="{Binding IsNodeSelected}"
                                         SelectNodeCommand="{Binding SelectNodeCommand,
                                     RelativeSource={RelativeSource FindAncestor, AncestorType=nodes:NodeGraphView}}"
                                         StartDragCommand="{Binding StartDraggingCommand, RelativeSource={RelativeSource FindAncestor, AncestorType=nodes:NodeGraphView}}"

+ 13 - 0
src/PixiEditor/ViewModels/Document/TransformOverlays/DocumentTransformViewModel.cs

@@ -10,6 +10,7 @@ using PixiEditor.Helpers.UI;
 using PixiEditor.Models.DocumentModels;
 using PixiEditor.Models.Handlers;
 using Drawie.Numerics;
+using PixiEditor.Models.Controllers.InputDevice;
 using PixiEditor.Views.Overlays.TransformOverlay;
 
 namespace PixiEditor.ViewModels.Document.TransformOverlays;
@@ -85,6 +86,8 @@ internal class DocumentTransformViewModel : ObservableObject, ITransformHandler
         set => SetProperty(ref showTransformControls, value);
     }
 
+    public event Action<MouseOnCanvasEventArgs>? PassthroughPointerPressed;
+
     private bool coverWholeScreen;
     public bool CoverWholeScreen
     {
@@ -126,6 +129,7 @@ internal class DocumentTransformViewModel : ObservableObject, ITransformHandler
         set => SetProperty(ref enableSnapping, value);
     }
     
+    
     private ExecutionTrigger<ShapeCorners> requestedCornersExecutor;
     public ExecutionTrigger<ShapeCorners> RequestCornersExecutor
     {
@@ -140,6 +144,15 @@ internal class DocumentTransformViewModel : ObservableObject, ITransformHandler
         set => SetProperty(ref actionCompletedCommand, value);
     }
 
+    private RelayCommand<MouseOnCanvasEventArgs>? passThroughPointerPressedCommand; 
+    public RelayCommand<MouseOnCanvasEventArgs> PassThroughPointerPressedCommand
+    {
+        get
+        {
+            return passThroughPointerPressedCommand ??= new RelayCommand<MouseOnCanvasEventArgs>(x => PassthroughPointerPressed?.Invoke(x));
+        }
+    }
+
     public event EventHandler<ShapeCorners>? TransformMoved;
 
     private DocumentTransformMode activeTransformMode = DocumentTransformMode.Scale_Rotate_NoShear_NoPerspective;

+ 1 - 1
src/PixiEditor/ViewModels/Nodes/NodeViewModel.cs

@@ -117,7 +117,7 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
         set => SetProperty(ref resultPainter, value);
     }
     
-    public bool IsSelected
+    public bool IsNodeSelected
     {
         get => isSelected;
         set => SetProperty(ref isSelected, value);

+ 5 - 3
src/PixiEditor/ViewModels/SubViewModels/IoViewModel.cs

@@ -193,7 +193,7 @@ internal class IoViewModel : SubViewModel<ViewModelMain>
             return;
 
         drawingWithRight = args.Button == MouseButton.Right;
-        activeDocument.EventInlet.OnCanvasLeftMouseButtonDown(args.PositionOnCanvas);
+        activeDocument.EventInlet.OnCanvasLeftMouseButtonDown(args);
         Owner.ToolsSubViewModel.UseToolEventInlet(args.PositionOnCanvas, args.Button);
 
         Analytics.SendUseTool(Owner.ToolsSubViewModel.ActiveTool, args.PositionOnCanvas, activeDocument.SizeBindable);
@@ -267,8 +267,9 @@ internal class IoViewModel : SubViewModel<ViewModelMain>
         activeDocument.EventInlet.OnCanvasMouseMove(pos);
     }
 
-    private void OnMouseUp(object? sender, MouseButton button)
+    private void OnMouseUp(object? sender, MouseOnCanvasEventArgs args)
     {
+        var button = args.Button;
         bool toLeftRightClick = drawingWithRight == null ||
                                 (button == MouseButton.Left && drawingWithRight.Value) ||
                                 (button == MouseButton.Right && !drawingWithRight.Value);
@@ -284,7 +285,8 @@ internal class IoViewModel : SubViewModel<ViewModelMain>
 
         if (button == MouseButton.Left || rightCanUp)
         {
-            Owner.DocumentManagerSubViewModel.ActiveDocument.EventInlet.OnCanvasLeftMouseButtonUp();
+            Owner.DocumentManagerSubViewModel.ActiveDocument.EventInlet
+                .OnCanvasLeftMouseButtonUp(args.PositionOnCanvas);
         }
 
         drawingWithRight = null;

+ 1 - 1
src/PixiEditor/ViewModels/SubViewModels/NodeGraphManagerViewModel.cs

@@ -19,7 +19,7 @@ internal class NodeGraphManagerViewModel : SubViewModel<ViewModelMain>
     public void DeleteSelectedNodes()
     {
         var nodes = Owner.DocumentManagerSubViewModel.ActiveDocument?.NodeGraph.AllNodes
-            .Where(x => x.IsSelected).ToList();
+            .Where(x => x.IsNodeSelected).ToList();
         
         if (nodes == null || nodes.Count == 0)
             return;

+ 9 - 2
src/PixiEditor/ViewModels/Tools/Tools/MoveToolViewModel.cs

@@ -35,7 +35,7 @@ internal class MoveToolViewModel : ToolViewModel, IMoveToolHandler
 
     public override LocalizedString Tooltip => new LocalizedString("MOVE_TOOL_TOOLTIP", Shortcut);
 
-    [Settings.Bool("KEEP_ORIGINAL_IMAGE_SETTING")]
+    [Settings.Bool("KEEP_ORIGINAL_IMAGE_SETTING", Notify = nameof(KeepOriginalChanged))]
     public bool KeepOriginalImage => GetValue<bool>();
 
     public override BrushShape BrushShape => BrushShape.Hidden;
@@ -55,7 +55,8 @@ internal class MoveToolViewModel : ToolViewModel, IMoveToolHandler
 
     public override void UseTool(VecD pos)
     {
-        ViewModelMain.Current.DocumentManagerSubViewModel.ActiveDocument?.Tools.UseShiftLayerTool();
+        //ViewModelMain.Current.DocumentManagerSubViewModel.ActiveDocument?.Tools.UseShiftLayerTool();
+        ViewModelMain.Current.DocumentManagerSubViewModel.ActiveDocument?.Operations.TransformSelectedArea(true);
     }
 
     public override void ModifierKeyChanged(bool ctrlIsDown, bool shiftIsDown, bool altIsDown)
@@ -107,4 +108,10 @@ internal class MoveToolViewModel : ToolViewModel, IMoveToolHandler
         OnDeselecting(false);
         OnSelected(false);
     }
+    
+    public void KeepOriginalChanged()
+    {
+        var activeDocument = ViewModelMain.Current.DocumentManagerSubViewModel.ActiveDocument;
+        activeDocument.TransformViewModel.ShowTransformControls = KeepOriginalImage;
+    }
 }

+ 3 - 3
src/PixiEditor/Views/Main/ViewportControls/Viewport.axaml.cs

@@ -407,7 +407,7 @@ internal partial class Viewport : UserControl, INotifyPropertyChanged
 
         var pos = e.GetPosition(Scene);
         VecD scenePos = Scene.ToZoomboxSpace(new VecD(pos.X, pos.Y));
-        MouseOnCanvasEventArgs? parameter = new MouseOnCanvasEventArgs(mouseButton, scenePos);
+        MouseOnCanvasEventArgs? parameter = new MouseOnCanvasEventArgs(mouseButton, scenePos, e.KeyModifiers);
 
         if (MouseDownCommand.CanExecute(parameter))
             MouseDownCommand.Execute(parameter);
@@ -422,7 +422,7 @@ internal partial class Viewport : UserControl, INotifyPropertyChanged
 
         MouseButton mouseButton = e.GetMouseButton(this);
 
-        MouseOnCanvasEventArgs parameter = new(mouseButton, conv);
+        MouseOnCanvasEventArgs parameter = new(mouseButton, conv, e.KeyModifiers);
 
         if (MouseMoveCommand.CanExecute(parameter))
             MouseMoveCommand.Execute(parameter);
@@ -435,7 +435,7 @@ internal partial class Viewport : UserControl, INotifyPropertyChanged
 
         Point pos = e.GetPosition(Scene);
         VecD conv = Scene.ToZoomboxSpace(new VecD(pos.X, pos.Y));
-        MouseOnCanvasEventArgs parameter = new(e.InitialPressMouseButton, conv);
+        MouseOnCanvasEventArgs parameter = new(e.InitialPressMouseButton, conv, e.KeyModifiers);
         if (MouseUpCommand.CanExecute(parameter))
             MouseUpCommand.Execute(parameter);
     }

+ 8 - 0
src/PixiEditor/Views/Main/ViewportControls/ViewportOverlays.cs

@@ -297,6 +297,13 @@ internal class ViewportOverlays
         {
             Source = Viewport, Path = "Document.TransformViewModel.InternalState", Mode = BindingMode.TwoWay
         };
+
+        Binding passThroughPointerPressedBinding = new()
+        {
+            Source = Viewport,
+            Path = "Document.TransformViewModel.PassThroughPointerPressedCommand",
+            Mode = BindingMode.OneWay
+        };
         
         Binding showHandlesBinding = new()
         {
@@ -321,6 +328,7 @@ internal class ViewportOverlays
         transformOverlay.Bind(TransformOverlay.CoverWholeScreenProperty, coverWholeScreenBinding);
         transformOverlay.Bind(TransformOverlay.SnapToAnglesProperty, snapToAnglesBinding);
         transformOverlay.Bind(TransformOverlay.InternalStateProperty, internalStateBinding);
+        transformOverlay.Bind(TransformOverlay.PassthroughPointerPressedCommandProperty, passThroughPointerPressedBinding);
         transformOverlay.Bind(TransformOverlay.ZoomboxAngleProperty, zoomboxAngleBinding);
         transformOverlay.Bind(TransformOverlay.ShowHandlesProperty, showHandlesBinding);
         transformOverlay.Bind(TransformOverlay.IsSizeBoxEnabledProperty, isSizeBoxEnabledBinding);

+ 3 - 3
src/PixiEditor/Views/Nodes/NodeGraphView.cs

@@ -150,7 +150,7 @@ internal class NodeGraphView : Zoombox.Zoombox
     }
 
     public List<INodeHandler> SelectedNodes => NodeGraph != null
-        ? NodeGraph.AllNodes.Where(x => x.IsSelected).ToList()
+        ? NodeGraph.AllNodes.Where(x => x.IsNodeSelected).ToList()
         : new List<INodeHandler>();
 
     protected override Type StyleKeyOverride => typeof(NodeGraphView);
@@ -478,14 +478,14 @@ internal class NodeGraphView : Zoombox.Zoombox
             ClearSelection();
         }
 
-        viewModel.IsSelected = true;
+        viewModel.IsNodeSelected = true;
     }
 
     private void ClearSelection()
     {
         foreach (var node in SelectedNodes)
         {
-            node.IsSelected = false;
+            node.IsNodeSelected = false;
         }
     }
 }

+ 20 - 27
src/PixiEditor/Views/Overlays/TransformOverlay/TransformOverlay.cs

@@ -78,6 +78,15 @@ internal class TransformOverlay : Overlay
         AvaloniaProperty.Register<TransformOverlay, TransformState>(nameof(InternalState),
             defaultValue: default(TransformState));
 
+    public static readonly StyledProperty<ICommand> PassthroughPointerPressedCommandProperty = AvaloniaProperty.Register<TransformOverlay, ICommand>(
+        nameof(PassthroughPointerPressedCommand));
+
+    public ICommand PassthroughPointerPressedCommand
+    {
+        get => GetValue(PassthroughPointerPressedCommandProperty);
+        set => SetValue(PassthroughPointerPressedCommandProperty, value);
+    }
+
     public TransformState InternalState
     {
         get => GetValue(InternalStateProperty);
@@ -250,7 +259,8 @@ internal class TransformOverlay : Overlay
     private VecD lastPointerPos;
     private InfoBox infoBox;
     private VecD lastSize;
-
+    private bool actuallyMoved = false;
+    
     public TransformOverlay()
     {
         topLeftHandle = new AnchorHandle(this);
@@ -317,35 +327,10 @@ internal class TransformOverlay : Overlay
             UpdateRotationCursor(lastPointerPos);
     }
 
-    private void DrawMouseInputArea(Canvas context, VecD size)
-    {
-        if (CoverWholeScreen)
-        {
-            // TODO: Is it needed? Seems like it makes a hit area for avalonia
-            //context.DrawRect(new RectD(new VecD(-size.X * 50, -size.Y * 50), new VecD(size.X * 101, size.Y * 101)));
-            return;
-        }
-
-        StreamGeometry geometry = new();
-        using (StreamGeometryContext ctx = geometry.Open())
-        {
-            ctx.BeginFigure(TransformHelper.ToPoint(Corners.TopLeft), true);
-            ctx.LineTo(TransformHelper.ToPoint(Corners.TopRight));
-            ctx.LineTo(TransformHelper.ToPoint(Corners.BottomRight));
-            ctx.LineTo(TransformHelper.ToPoint(Corners.BottomLeft));
-            ctx.EndFigure(true);
-        }
-
-        // TODO: Is it needed? Seems like it makes a hit area for avalonia
-        //context.DrawGeometry(Brushes.Transparent, null, geometry);
-    }
-
     private void DrawOverlay
         (Canvas context, VecD size, ShapeCorners corners, VecD origin, float zoomboxScale)
     {
         lastSize = size;
-        // draw transparent background to enable mouse input
-        DrawMouseInputArea(context, size);
 
         handlePen.StrokeWidth = 1 / zoomboxScale;
         blackDashedPen.StrokeWidth = 1 / zoomboxScale;
@@ -509,7 +494,7 @@ internal class TransformOverlay : Overlay
         {
             return;
         }
-
+        
         args.Pointer.Capture(this);
         args.Handled = true;
     }
@@ -525,6 +510,7 @@ internal class TransformOverlay : Overlay
         {
             HandleTransform(pos);
             finalCursor = new Cursor(StandardCursorType.DragMove);
+            actuallyMoved = true;
         }
 
         if (capturedAnchor is not null)
@@ -570,6 +556,12 @@ internal class TransformOverlay : Overlay
         if (e.InitialPressMouseButton != MouseButton.Left)
             return;
 
+        if (!isRotating && !actuallyMoved)
+        {
+            MouseOnCanvasEventArgs args = new(MouseButton.Left, e.Point, e.Modifiers);
+            PassthroughPointerPressedCommand?.Execute(args);
+        }
+
         if (isRotating)
         {
             isRotating = false;
@@ -610,6 +602,7 @@ internal class TransformOverlay : Overlay
         mousePosOnStartMove = position;
         originOnStartMove = InternalState.Origin;
         cornersOnStartMove = Corners;
+        actuallyMoved = false;
     }
 
     private void HandleTransform(VecD pos)