Browse Source

Porting TransformOverlay to new handles API wip

Krzysztof Krysiński 1 year ago
parent
commit
1ac9edbad7

+ 5 - 1
src/PixiEditor.AvaloniaUI/Styles/PixiEditor.Handles.axaml

@@ -1,5 +1,6 @@
 <Styles xmlns="https://github.com/avaloniaui"
 <Styles xmlns="https://github.com/avaloniaui"
-                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+                    xmlns:system="clr-namespace:System;assembly=System.Runtime">
     <Styles.Resources>
     <Styles.Resources>
         <ResourceDictionary>
         <ResourceDictionary>
             <Path x:Key="MoveHandle"
             <Path x:Key="MoveHandle"
@@ -10,6 +11,9 @@
             <Path x:Key="RotateHandle" Data="M -1.26 -0.455 Q 0 0.175 1.26 -0.455 L 1.12 -0.735 L 2.1 -0.7 L 1.54 0.105 L 1.4 -0.175 Q 0 0.525 -1.4 -0.175 L -1.54 0.105 L -2.1 -0.7 L -1.12 -0.735 Z"/>
             <Path x:Key="RotateHandle" Data="M -1.26 -0.455 Q 0 0.175 1.26 -0.455 L 1.12 -0.735 L 2.1 -0.7 L 1.54 0.105 L 1.4 -0.175 Q 0 0.525 -1.4 -0.175 L -1.54 0.105 L -2.1 -0.7 L -1.12 -0.735 Z"/>
             <SolidColorBrush x:Key="HandleBrush" Color="{DynamicResource GlyphColor}"/>
             <SolidColorBrush x:Key="HandleBrush" Color="{DynamicResource GlyphColor}"/>
             <SolidColorBrush x:Key="HandleBackgroundBrush" Color="{DynamicResource GlyphBackground}"/>
             <SolidColorBrush x:Key="HandleBackgroundBrush" Color="{DynamicResource GlyphBackground}"/>
+
+            <system:Double x:Key="HandleSize">24</system:Double>
+            <system:Double x:Key="AnchorHandleSize">14</system:Double>
         </ResourceDictionary>
         </ResourceDictionary>
     </Styles.Resources>
     </Styles.Resources>
 
 

+ 12 - 0
src/PixiEditor.AvaloniaUI/Views/Overlays/Handles/AnchorHandle.cs

@@ -0,0 +1,12 @@
+using Avalonia.Controls;
+using PixiEditor.DrawingApi.Core.Numerics;
+
+namespace PixiEditor.AvaloniaUI.Views.Overlays.Handles;
+
+public class AnchorHandle : RectangleHandle
+{
+    public AnchorHandle(Control owner, VecD position) : base(owner, position)
+    {
+        Size = new VecD(GetResource<double>("AnchorHandleSize"));
+    }
+}

+ 33 - 15
src/PixiEditor.AvaloniaUI/Views/Overlays/Handles/Handle.cs

@@ -10,7 +10,7 @@ using PixiEditor.Extensions.UI.Overlays;
 
 
 namespace PixiEditor.AvaloniaUI.Views.Overlays.Handles;
 namespace PixiEditor.AvaloniaUI.Views.Overlays.Handles;
 
 
-public delegate void HandleDrag(VecD newPosition);
+public delegate void HandleEvent(Handle source, VecD position);
 public abstract class Handle : IHandle
 public abstract class Handle : IHandle
 {
 {
     public IBrush HandleBrush { get; set; } = GetBrush("HandleBackgroundBrush");
     public IBrush HandleBrush { get; set; } = GetBrush("HandleBackgroundBrush");
@@ -21,20 +21,22 @@ public abstract class Handle : IHandle
     public VecD Size { get; set; }
     public VecD Size { get; set; }
     public RectD HandleRect => new(Position, Size);
     public RectD HandleRect => new(Position, Size);
 
 
-    public event Action OnPress;
-    public event HandleDrag OnDrag;
-    public event Action OnRelease;
-    public event Action OnHover;
-    public event Action OnExit;
+    public event HandleEvent OnPress;
+    public event HandleEvent OnDrag;
+    public event Action<Handle> OnRelease;
+    public event Action<Handle> OnHover;
+    public event Action<Handle> OnExit;
+
+    public Cursor? Cursor { get; set; }
 
 
     private bool isPressed;
     private bool isPressed;
     private bool isHovered;
     private bool isHovered;
 
 
-    public Handle(Control owner, VecD position, VecD size)
+    public Handle(Control owner, VecD position)
     {
     {
         Owner = owner;
         Owner = owner;
         Position = position;
         Position = position;
-        Size = size;
+        Size = Application.Current.TryGetResource("HandleSize", out object size) ? new VecD((double)size) : new VecD(24);
 
 
         Owner.PointerPressed += OnPointerPressed;
         Owner.PointerPressed += OnPointerPressed;
         Owner.PointerMoved += OnPointerMoved;
         Owner.PointerMoved += OnPointerMoved;
@@ -45,12 +47,22 @@ public abstract class Handle : IHandle
 
 
     public virtual void OnPressed(PointerPressedEventArgs args) { }
     public virtual void OnPressed(PointerPressedEventArgs args) { }
 
 
-    protected virtual bool IsWithinHandle(VecD handlePos, VecD pos, double zoomboxScale)
+    public virtual bool IsWithinHandle(VecD handlePos, VecD pos, double zoomboxScale)
     {
     {
         return TransformHelper.IsWithinHandle(handlePos, pos, zoomboxScale, Size);
         return TransformHelper.IsWithinHandle(handlePos, pos, zoomboxScale, Size);
     }
     }
 
 
-    protected static Geometry GetHandleGeometry(string handleName)
+    public static T GetResource<T>(string key)
+    {
+        if (Application.Current.Styles.TryGetResource(key, null, out object resource))
+        {
+            return (T)resource;
+        }
+
+        return default!;
+    }
+
+    public static Geometry GetHandleGeometry(string handleName)
     {
     {
         if (Application.Current.Styles.TryGetResource(handleName, null, out object shape))
         if (Application.Current.Styles.TryGetResource(handleName, null, out object shape))
         {
         {
@@ -84,7 +96,7 @@ public abstract class Handle : IHandle
         {
         {
             e.Handled = true;
             e.Handled = true;
             OnPressed(e);
             OnPressed(e);
-            OnPress?.Invoke();
+            OnPress?.Invoke(this, pos);
             isPressed = true;
             isPressed = true;
             e.Pointer.Capture(Owner);
             e.Pointer.Capture(Owner);
         }
         }
@@ -100,12 +112,18 @@ public abstract class Handle : IHandle
         if (!isHovered && isWithinHandle)
         if (!isHovered && isWithinHandle)
         {
         {
             isHovered = true;
             isHovered = true;
-            OnHover?.Invoke();
+            if (Owner.Cursor != null)
+            {
+                Owner.Cursor = Cursor;
+            }
+
+            OnHover?.Invoke(this);
         }
         }
         else if (isHovered && isWithinHandle)
         else if (isHovered && isWithinHandle)
         {
         {
             isHovered = false;
             isHovered = false;
-            OnExit?.Invoke();
+            Owner.Cursor = null;
+            OnExit?.Invoke(this);
         }
         }
 
 
         if (!isPressed)
         if (!isPressed)
@@ -113,7 +131,7 @@ public abstract class Handle : IHandle
             return;
             return;
         }
         }
 
 
-        OnDrag?.Invoke(pos);
+        OnDrag?.Invoke(this, pos);
     }
     }
 
 
     private void OnPointerReleased(object? sender, PointerReleasedEventArgs e)
     private void OnPointerReleased(object? sender, PointerReleasedEventArgs e)
@@ -126,7 +144,7 @@ public abstract class Handle : IHandle
         if (isPressed)
         if (isPressed)
         {
         {
             isPressed = false;
             isPressed = false;
-            OnRelease?.Invoke();
+            OnRelease?.Invoke(this);
             e.Pointer.Capture(null);
             e.Pointer.Capture(null);
         }
         }
     }
     }

+ 1 - 2
src/PixiEditor.AvaloniaUI/Views/Overlays/Handles/RectangleHandle.cs

@@ -2,13 +2,12 @@
 using Avalonia.Media;
 using Avalonia.Media;
 using PixiEditor.AvaloniaUI.Views.Overlays.TransformOverlay;
 using PixiEditor.AvaloniaUI.Views.Overlays.TransformOverlay;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.Extensions.UI.Overlays;
 
 
 namespace PixiEditor.AvaloniaUI.Views.Overlays.Handles;
 namespace PixiEditor.AvaloniaUI.Views.Overlays.Handles;
 
 
 public class RectangleHandle : Handle
 public class RectangleHandle : Handle
 {
 {
-    public RectangleHandle(Control owner, VecD position, VecD size) : base(owner, position, size)
+    public RectangleHandle(Control owner, VecD position) : base(owner, position)
     {
     {
     }
     }
 
 

+ 3 - 2
src/PixiEditor.AvaloniaUI/Views/Overlays/Handles/TransformHandle.cs

@@ -1,9 +1,9 @@
 using Avalonia;
 using Avalonia;
 using Avalonia.Controls;
 using Avalonia.Controls;
+using Avalonia.Input;
 using Avalonia.Media;
 using Avalonia.Media;
 using PixiEditor.AvaloniaUI.Views.Overlays.TransformOverlay;
 using PixiEditor.AvaloniaUI.Views.Overlays.TransformOverlay;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.Extensions.UI.Overlays;
 
 
 namespace PixiEditor.AvaloniaUI.Views.Overlays.Handles;
 namespace PixiEditor.AvaloniaUI.Views.Overlays.Handles;
 
 
@@ -13,8 +13,9 @@ public class TransformHandle : Handle
 
 
     private Geometry handleGeometry = GetHandleGeometry("MoveHandle");
     private Geometry handleGeometry = GetHandleGeometry("MoveHandle");
 
 
-    public TransformHandle(Control owner, VecD position, VecD size) : base(owner, position, size)
+    public TransformHandle(Control owner, VecD position) : base(owner, position)
     {
     {
+        Cursor = new Cursor(StandardCursorType.SizeAll);
     }
     }
 
 
     public override void Draw(DrawingContext context)
     public override void Draw(DrawingContext context)

+ 13 - 23
src/PixiEditor.AvaloniaUI/Views/Overlays/LineToolOverlay/LineToolOverlay.cs

@@ -14,15 +14,6 @@ using PixiEditor.Views.UserControls.Overlays.LineToolOverlay;
 namespace PixiEditor.AvaloniaUI.Views.Overlays.LineToolOverlay;
 namespace PixiEditor.AvaloniaUI.Views.Overlays.LineToolOverlay;
 internal class LineToolOverlay : Overlay
 internal class LineToolOverlay : Overlay
 {
 {
-    public static readonly StyledProperty<double> ZoomboxScaleProperty =
-        AvaloniaProperty.Register<LineToolOverlay, double>(nameof(ZoomboxScale), defaultValue: 1.0);
-
-    public double ZoomboxScale
-    {
-        get => GetValue(ZoomboxScaleProperty);
-        set => SetValue(ZoomboxScaleProperty, value);
-    }
-
     public static readonly StyledProperty<VecD> LineStartProperty =
     public static readonly StyledProperty<VecD> LineStartProperty =
         AvaloniaProperty.Register<LineToolOverlay, VecD>(nameof(LineStart), defaultValue: VecD.Zero);
         AvaloniaProperty.Register<LineToolOverlay, VecD>(nameof(LineStart), defaultValue: VecD.Zero);
 
 
@@ -75,19 +66,20 @@ internal class LineToolOverlay : Overlay
     {
     {
         Cursor = new Cursor(StandardCursorType.Arrow);
         Cursor = new Cursor(StandardCursorType.Arrow);
 
 
-        VecD anchorSize = new(14, 14);
-
-        startHandle = new RectangleHandle(this, LineStart, anchorSize);
+        startHandle = new AnchorHandle(this, LineStart);
         startHandle.HandlePen = blackPen;
         startHandle.HandlePen = blackPen;
         startHandle.OnDrag += StartHandleOnDrag;
         startHandle.OnDrag += StartHandleOnDrag;
+        AddHandle(startHandle);
 
 
-        endHandle = new RectangleHandle(this, LineEnd, anchorSize);
+        endHandle = new AnchorHandle(this, LineEnd);
         startHandle.HandlePen = blackPen;
         startHandle.HandlePen = blackPen;
         endHandle.OnDrag += EndHandleOnDrag;
         endHandle.OnDrag += EndHandleOnDrag;
+        AddHandle(endHandle);
 
 
-        moveHandle = new TransformHandle(this, LineStart, new VecD(24, 24));
+        moveHandle = new TransformHandle(this, LineStart);
         moveHandle.HandlePen = blackPen;
         moveHandle.HandlePen = blackPen;
         moveHandle.OnDrag += MoveHandleOnDrag;
         moveHandle.OnDrag += MoveHandleOnDrag;
+        AddHandle(moveHandle);
 
 
         Loaded += OnLoaded;
         Loaded += OnLoaded;
     }
     }
@@ -100,13 +92,11 @@ internal class LineToolOverlay : Overlay
 
 
     private static void OnZoomboxScaleChanged(AvaloniaPropertyChangedEventArgs<double> args)
     private static void OnZoomboxScaleChanged(AvaloniaPropertyChangedEventArgs<double> args)
     {
     {
-        var self = (LineToolOverlay)args.Sender;
-        double newScale = args.NewValue.Value;
-        self.blackPen.Thickness = 1.0 / newScale;
+        if (args.Sender is not LineToolOverlay overlay)
+            return;
 
 
-        self.startHandle.ZoomboxScale = newScale;
-        self.endHandle.ZoomboxScale = newScale;
-        self.moveHandle.ZoomboxScale = newScale;
+        double newScale = args.NewValue.Value;
+        overlay.blackPen.Thickness = 1.0 / newScale;
     }
     }
 
 
     public override void Render(DrawingContext context)
     public override void Render(DrawingContext context)
@@ -139,19 +129,19 @@ internal class LineToolOverlay : Overlay
         e.Pointer.Capture(this);
         e.Pointer.Capture(this);
     }
     }
 
 
-    private void StartHandleOnDrag(VecD position)
+    private void StartHandleOnDrag(Handle source, VecD position)
     {
     {
         LineStart = position;
         LineStart = position;
         movedWhileMouseDown = true;
         movedWhileMouseDown = true;
     }
     }
 
 
-    private void EndHandleOnDrag(VecD position)
+    private void EndHandleOnDrag(Handle source, VecD position)
     {
     {
         LineEnd = position;
         LineEnd = position;
         movedWhileMouseDown = true;
         movedWhileMouseDown = true;
     }
     }
 
 
-    private void MoveHandleOnDrag(VecD position)
+    private void MoveHandleOnDrag(Handle source, VecD position)
     {
     {
         var delta = position - mouseDownPos;
         var delta = position - mouseDownPos;
 
 

+ 54 - 1
src/PixiEditor.AvaloniaUI/Views/Overlays/Overlay.cs

@@ -1,11 +1,64 @@
-using Avalonia;
+using System.Collections.Generic;
+using Avalonia;
 using Avalonia.Controls;
 using Avalonia.Controls;
 using Avalonia.Controls.Shapes;
 using Avalonia.Controls.Shapes;
 using Avalonia.Media;
 using Avalonia.Media;
+using PixiEditor.AvaloniaUI.Views.Overlays.Handles;
 
 
 namespace PixiEditor.AvaloniaUI.Views.Overlays;
 namespace PixiEditor.AvaloniaUI.Views.Overlays;
 
 
 public class Overlay : Decorator
 public class Overlay : Decorator
 {
 {
+    public List<Handle> Handles { get; } = new();
 
 
+    public static readonly StyledProperty<double> ZoomboxScaleProperty =
+        AvaloniaProperty.Register<Overlay, double>(nameof(ZoomboxScale), defaultValue: 1.0);
+
+    public double ZoomboxScale
+    {
+        get => GetValue(ZoomboxScaleProperty);
+        set => SetValue(ZoomboxScaleProperty, value);
+    }
+
+    public Overlay()
+    {
+        ZoomboxScaleProperty.Changed.Subscribe(OnZoomboxScaleChanged);
+    }
+
+    public void AddHandle(Handle handle)
+    {
+        if (Handles.Contains(handle)) return;
+
+        Handles.Add(handle);
+    }
+
+    public void ForAllHandles(Action<Handle> action)
+    {
+        foreach (var handle in Handles)
+        {
+            action(handle);
+        }
+    }
+
+    public void ForAllHandles<T>(Action<T> action) where T : Handle
+    {
+        foreach (var handle in Handles)
+        {
+            if (handle is T tHandle)
+            {
+                action(tHandle);
+            }
+        }
+    }
+
+    private static void OnZoomboxScaleChanged(AvaloniaPropertyChangedEventArgs<double> e)
+    {
+        if (e.Sender is Overlay overlay)
+        {
+            foreach (var handle in overlay.Handles)
+            {
+                handle.ZoomboxScale = e.NewValue.Value;
+            }
+        }
+    }
 }
 }

+ 2 - 7
src/PixiEditor.AvaloniaUI/Views/Overlays/SymmetryOverlay/SymmetryOverlay.cs

@@ -6,6 +6,7 @@ using Avalonia.Input;
 using Avalonia.Interactivity;
 using Avalonia.Interactivity;
 using Avalonia.Media;
 using Avalonia.Media;
 using Hardware.Info;
 using Hardware.Info;
+using PixiEditor.AvaloniaUI.Helpers;
 using PixiEditor.AvaloniaUI.Models.Controllers.InputDevice;
 using PixiEditor.AvaloniaUI.Models.Controllers.InputDevice;
 using PixiEditor.ChangeableDocument.Enums;
 using PixiEditor.ChangeableDocument.Enums;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.DrawingApi.Core.Numerics;
@@ -320,13 +321,7 @@ internal class SymmetryOverlay : Overlay
     {
     {
         base.OnPointerPressed(e);
         base.OnPointerPressed(e);
 
 
-        MouseButton button = e.GetCurrentPoint(this).Properties.PointerUpdateKind switch
-        {
-            PointerUpdateKind.LeftButtonPressed => MouseButton.Left,
-            PointerUpdateKind.RightButtonPressed => MouseButton.Right,
-            PointerUpdateKind.MiddleButtonPressed => MouseButton.Middle,
-            _ => MouseButton.None
-        };
+        MouseButton button = e.GetMouseButton(this);
 
 
         if (button != MouseButton.Left)
         if (button != MouseButton.Left)
             return;
             return;

+ 147 - 106
src/PixiEditor.AvaloniaUI/Views/Overlays/TransformOverlay/TransformOverlay.cs

@@ -1,4 +1,6 @@
-using System.Windows.Input;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows.Input;
 using Avalonia;
 using Avalonia;
 using Avalonia.Controls;
 using Avalonia.Controls;
 using Avalonia.Input;
 using Avalonia.Input;
@@ -7,6 +9,7 @@ using ChunkyImageLib.DataHolders;
 using Hardware.Info;
 using Hardware.Info;
 using PixiEditor.AvaloniaUI.Helpers;
 using PixiEditor.AvaloniaUI.Helpers;
 using PixiEditor.AvaloniaUI.Helpers.Extensions;
 using PixiEditor.AvaloniaUI.Helpers.Extensions;
+using PixiEditor.AvaloniaUI.Views.Overlays.Handles;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.DrawingApi.Core.Numerics;
 
 
 namespace PixiEditor.AvaloniaUI.Views.Overlays.TransformOverlay;
 namespace PixiEditor.AvaloniaUI.Views.Overlays.TransformOverlay;
@@ -31,15 +34,6 @@ internal class TransformOverlay : Overlay
         set => SetValue(CornersProperty, value);
         set => SetValue(CornersProperty, value);
     }
     }
 
 
-    public static readonly StyledProperty<double> ZoomboxScaleProperty =
-        AvaloniaProperty.Register<TransformOverlay, double>(nameof(ZoomboxScale), defaultValue: 1.0);
-
-    public double ZoomboxScale
-    {
-        get => GetValue(ZoomboxScaleProperty);
-        set => SetValue(ZoomboxScaleProperty, value);
-    }
-
     public static readonly StyledProperty<TransformSideFreedom> SideFreedomProperty =
     public static readonly StyledProperty<TransformSideFreedom> SideFreedomProperty =
         AvaloniaProperty.Register<TransformOverlay, TransformSideFreedom>(nameof(SideFreedom), defaultValue: TransformSideFreedom.Locked);
         AvaloniaProperty.Register<TransformOverlay, TransformSideFreedom>(nameof(SideFreedom), defaultValue: TransformSideFreedom.Locked);
 
 
@@ -144,13 +138,65 @@ internal class TransformOverlay : Overlay
     private Pen blackFreqDashedPen = new Pen(Brushes.Black, 1) { DashStyle = new DashStyle(new double[] { 2, 2 }, 0) };
     private Pen blackFreqDashedPen = new Pen(Brushes.Black, 1) { DashStyle = new DashStyle(new double[] { 2, 2 }, 0) };
     private Pen whiteFreqDashedPen = new Pen(Brushes.White, 1) { DashStyle = new DashStyle(new double[] { 2, 2 }, 2) };
     private Pen whiteFreqDashedPen = new Pen(Brushes.White, 1) { DashStyle = new DashStyle(new double[] { 2, 2 }, 2) };
 
 
-    private Geometry handleGeometry /*= GetHandleGeometry("MoveHandle")*/;
+    private TransformHandle moveHandle;
+    private RectangleHandle topLeftHandle;
+    private RectangleHandle topRightHandle;
+    private RectangleHandle bottomLeftHandle;
+    private RectangleHandle bottomRightHandle;
+    private RectangleHandle topHandle;
+    private RectangleHandle bottomHandle;
+    private RectangleHandle leftHandle;
+    private RectangleHandle rightHandle;
 
 
-    private Geometry rotateCursorGeometry /*= GetHandleGeometry("RotateHandle")*/;
+    private Dictionary<Handle, Anchor> anchorMap = new();
+
+    private Geometry rotateCursorGeometry = Handle.GetHandleGeometry("RotateHandle");
 
 
     private Point lastPointerPos;
     private Point lastPointerPos;
     private IPointer? capturedPointer;
     private IPointer? capturedPointer;
 
 
+    public TransformOverlay()
+    {
+        moveHandle = new TransformHandle(this, VecD.Zero);
+        moveHandle.OnDrag += MoveHandleOnDrag;
+        moveHandle.OnRelease += OnMoveHandleReleased;
+
+        topLeftHandle = new AnchorHandle(this, VecD.Zero);
+        topRightHandle = new AnchorHandle(this, VecD.Zero);
+        bottomLeftHandle = new AnchorHandle(this, VecD.Zero);
+        bottomRightHandle = new AnchorHandle(this, VecD.Zero);
+        topHandle = new AnchorHandle(this, VecD.Zero);
+        bottomHandle = new AnchorHandle(this, VecD.Zero);
+        leftHandle = new AnchorHandle(this, VecD.Zero);
+        rightHandle = new AnchorHandle(this, VecD.Zero);
+
+        AddHandle(moveHandle);
+        AddHandle(topLeftHandle);
+        AddHandle(topRightHandle);
+        AddHandle(bottomLeftHandle);
+        AddHandle(bottomRightHandle);
+        AddHandle(topHandle);
+        AddHandle(bottomHandle);
+        AddHandle(leftHandle);
+        AddHandle(rightHandle);
+
+        anchorMap.Add(topLeftHandle, Anchor.TopLeft);
+        anchorMap.Add(topRightHandle, Anchor.TopRight);
+        anchorMap.Add(bottomLeftHandle, Anchor.BottomLeft);
+        anchorMap.Add(bottomRightHandle, Anchor.BottomRight);
+        anchorMap.Add(topHandle, Anchor.Top);
+        anchorMap.Add(bottomHandle, Anchor.Bottom);
+        anchorMap.Add(leftHandle, Anchor.Left);
+        anchorMap.Add(rightHandle, Anchor.Right);
+
+        ForAllHandles<AnchorHandle>(x =>
+        {
+            x.OnPress += OnAnchorHandlePressed;
+            x.OnRelease += OnAnchorHandleReleased;
+        });
+
+        moveHandle.OnPress += OnMoveHandlePressed;
+    }
 
 
     public override void Render(DrawingContext drawingContext)
     public override void Render(DrawingContext drawingContext)
     {
     {
@@ -204,7 +250,7 @@ internal class TransformOverlay : Overlay
     private void DrawOverlay
     private void DrawOverlay
         (DrawingContext context, VecD size, ShapeCorners corners, VecD origin, double zoomboxScale)
         (DrawingContext context, VecD size, ShapeCorners corners, VecD origin, double zoomboxScale)
     {
     {
-        /*// draw transparent background to enable mouse input
+        // draw transparent background to enable mouse input
         DrawMouseInputArea(context, size);
         DrawMouseInputArea(context, size);
 
 
         blackPen.Thickness = 1 / zoomboxScale;
         blackPen.Thickness = 1 / zoomboxScale;
@@ -217,6 +263,10 @@ internal class TransformOverlay : Overlay
         VecD topRight = corners.TopRight;
         VecD topRight = corners.TopRight;
         VecD bottomLeft = corners.BottomLeft;
         VecD bottomLeft = corners.BottomLeft;
         VecD bottomRight = corners.BottomRight;
         VecD bottomRight = corners.BottomRight;
+        VecD top = (topLeft + topRight) / 2;
+        VecD bottom = (bottomLeft + bottomRight) / 2;
+        VecD left = (topLeft + bottomLeft) / 2;
+        VecD right = (topRight + bottomRight) / 2;
 
 
         // lines
         // lines
         context.DrawLine(blackDashedPen, TransformHelper.ToPoint(topLeft), TransformHelper.ToPoint(topRight));
         context.DrawLine(blackDashedPen, TransformHelper.ToPoint(topLeft), TransformHelper.ToPoint(topRight));
@@ -230,16 +280,23 @@ internal class TransformOverlay : Overlay
 
 
         // corner anchors
         // corner anchors
 
 
-        context.DrawRectangle(Brushes.White, blackPen, TransformHelper.ToAnchorRect(topLeft, zoomboxScale));
-        context.DrawRectangle(Brushes.White, blackPen, TransformHelper.ToAnchorRect(topRight, zoomboxScale));
-        context.DrawRectangle(Brushes.White, blackPen, TransformHelper.ToAnchorRect(bottomLeft, zoomboxScale));
-        context.DrawRectangle(Brushes.White, blackPen, TransformHelper.ToAnchorRect(bottomRight, zoomboxScale));
-
-        // side anchors
-        context.DrawRectangle(Brushes.White, blackPen, TransformHelper.ToAnchorRect((topLeft - topRight) / 2 + topRight, zoomboxScale));
-        context.DrawRectangle(Brushes.White, blackPen, TransformHelper.ToAnchorRect((topLeft - bottomLeft) / 2 + bottomLeft, zoomboxScale));
-        context.DrawRectangle(Brushes.White, blackPen, TransformHelper.ToAnchorRect((bottomLeft - bottomRight) / 2 + bottomRight, zoomboxScale));
-        context.DrawRectangle(Brushes.White, blackPen, TransformHelper.ToAnchorRect((topRight - bottomRight) / 2 + bottomRight, zoomboxScale));
+        topLeftHandle.Position = topLeft;
+        topRightHandle.Position = topRight;
+        bottomLeftHandle.Position = bottomLeft;
+        bottomRightHandle.Position = bottomRight;
+        topHandle.Position = top;
+        bottomHandle.Position = bottom;
+        leftHandle.Position = left;
+        rightHandle.Position = right;
+
+        topLeftHandle.Draw(context);
+        topRightHandle.Draw(context);
+        bottomLeftHandle.Draw(context);
+        bottomRightHandle.Draw(context);
+        topHandle.Draw(context);
+        bottomHandle.Draw(context);
+        leftHandle.Draw(context);
+        rightHandle.Draw(context);
 
 
         // origin
         // origin
         double radius = TransformHelper.AnchorSize / zoomboxScale / 2;
         double radius = TransformHelper.AnchorSize / zoomboxScale / 2;
@@ -248,19 +305,11 @@ internal class TransformOverlay : Overlay
 
 
         // move handle
         // move handle
         VecD handlePos = TransformHelper.GetDragHandlePos(corners, zoomboxScale);
         VecD handlePos = TransformHelper.GetDragHandlePos(corners, zoomboxScale);
-        const double CrossSize = TransformHelper.MoveHandleSize - 1;
-        context.DrawRectangle(Brushes.White, blackPen, TransformHelper.ToHandleRect(handlePos, zoomboxScale));
-        handleGeometry.Transform = new MatrixTransform(
-            new Matrix(
-            0, CrossSize / zoomboxScale,
-            CrossSize / zoomboxScale, 0,
-            handlePos.X - CrossSize / (zoomboxScale * 2), handlePos.Y - CrossSize / (zoomboxScale * 2))
-        );
-
-        context.DrawGeometry(Brushes.Black, null, handleGeometry);
+        moveHandle.Position = handlePos;
+        moveHandle.Draw(context);
 
 
         // rotate cursor
         // rotate cursor
-        context.DrawGeometry(Brushes.White, blackPen, rotateCursorGeometry);*/
+        context.DrawGeometry(Brushes.White, blackPen, rotateCursorGeometry);
     }
     }
 
 
     protected override void OnPointerExited(PointerEventArgs e)
     protected override void OnPointerExited(PointerEventArgs e)
@@ -269,23 +318,30 @@ internal class TransformOverlay : Overlay
         rotateCursorGeometry.Transform = new ScaleTransform(0, 0);
         rotateCursorGeometry.Transform = new ScaleTransform(0, 0);
     }
     }
 
 
+    private void OnMoveHandlePressed(Handle source, VecD position)
+    {
+        isMoving = true;
+        mousePosOnStartMove = position;
+        originOnStartMove = InternalState.Origin;
+        cornersOnStartMove = Corners;
+    }
+
+    private void OnAnchorHandlePressed(Handle source, VecD position)
+    {
+        capturedAnchor = anchorMap[source];
+        cornersOnStartAnchorDrag = Corners;
+        originOnStartAnchorDrag = InternalState.Origin;
+        mousePosOnStartAnchorDrag = TransformHelper.ToVecD(lastPointerPos);
+    }
+
     protected override void OnPointerPressed(PointerPressedEventArgs e)
     protected override void OnPointerPressed(PointerPressedEventArgs e)
     {
     {
-        /*base.OnPointerPressed(e);
+        base.OnPointerPressed(e);
         if (e.GetMouseButton(this) != MouseButton.Left)
         if (e.GetMouseButton(this) != MouseButton.Left)
             return;
             return;
 
 
-        e.Handled = true;
         VecD pos = TransformHelper.ToVecD(e.GetPosition(this));
         VecD pos = TransformHelper.ToVecD(e.GetPosition(this));
-        Anchor? anchor = TransformHelper.GetAnchorInPosition(pos, Corners, InternalState.Origin, ZoomboxScale);
-        if (anchor is not null)
-        {
-            capturedAnchor = anchor;
-            cornersOnStartAnchorDrag = Corners;
-            originOnStartAnchorDrag = InternalState.Origin;
-            mousePosOnStartAnchorDrag = pos;
-        }
-        else if (!ShouldRotate(pos) || TransformHelper.IsWithinTransformHandle(TransformHelper.GetDragHandlePos(Corners, ZoomboxScale), pos, ZoomboxScale))
+        if (!CanRotate(pos))
         {
         {
             isMoving = true;
             isMoving = true;
             mousePosOnStartMove = TransformHelper.ToVecD(e.GetPosition(this));
             mousePosOnStartMove = TransformHelper.ToVecD(e.GetPosition(this));
@@ -306,23 +362,17 @@ internal class TransformOverlay : Overlay
         }
         }
         
         
         e.Pointer.Capture(this);
         e.Pointer.Capture(this);
-        capturedPointer = e.Pointer;*/
+        capturedPointer = e.Pointer;
     }
     }
 
 
-    private bool ShouldRotate(VecD mousePos)
+    private bool CanRotate(VecD mousePos)
     {
     {
-        return false;
-        /*if (Corners.IsPointInside(mousePos) ||
-            TransformHelper.GetAnchorInPosition(mousePos, Corners, InternalState.Origin, ZoomboxScale) is not null ||
-            TransformHelper.IsWithinTransformHandle(TransformHelper.GetDragHandlePos(Corners, ZoomboxScale), mousePos, ZoomboxScale))
-            return false;
-        return TransformHelper.GetAnchorInPosition(mousePos, Corners, InternalState.Origin, ZoomboxScale, anchorSizeMultiplierForRotation) is not null;*/
+        return !Corners.IsPointInside(mousePos) && Handles.All(x => !x.IsWithinHandle(x.Position, mousePos, ZoomboxScale));
     }
     }
 
 
     private bool UpdateRotationCursor(VecD mousePos)
     private bool UpdateRotationCursor(VecD mousePos)
     {
     {
-        return false;
-        if ((!ShouldRotate(mousePos) && !isRotating) || LockRotation)
+        if ((!CanRotate(mousePos) && !isRotating) || LockRotation)
         {
         {
             rotateCursorGeometry.Transform = new ScaleTransform(0, 0);
             rotateCursorGeometry.Transform = new ScaleTransform(0, 0);
             return false;
             return false;
@@ -335,6 +385,24 @@ internal class TransformOverlay : Overlay
         return true;
         return true;
     }
     }
 
 
+    private void MoveHandleOnDrag(Handle source, VecD position)
+    {
+        VecD delta = position - mousePosOnStartMove;
+
+        if (Corners.IsSnappedToPixels)
+            delta = delta.Round();
+
+        Corners = new ShapeCorners()
+        {
+            BottomLeft = cornersOnStartMove.BottomLeft + delta,
+            BottomRight = cornersOnStartMove.BottomRight + delta,
+            TopLeft = cornersOnStartMove.TopLeft + delta,
+            TopRight = cornersOnStartMove.TopRight + delta,
+        };
+
+        InternalState = InternalState with { Origin = originOnStartMove + delta };
+    }
+
     protected override void OnPointerMoved(PointerEventArgs e)
     protected override void OnPointerMoved(PointerEventArgs e)
     {
     {
         Cursor finalCursor = new Cursor(StandardCursorType.Arrow);
         Cursor finalCursor = new Cursor(StandardCursorType.Arrow);
@@ -353,27 +421,9 @@ internal class TransformOverlay : Overlay
         }
         }
 
 
         VecD pos = TransformHelper.ToVecD(e.GetPosition(this));
         VecD pos = TransformHelper.ToVecD(e.GetPosition(this));
-        //Anchor? anchor = TransformHelper.GetAnchorInPosition(pos, Corners, InternalState.Origin, ZoomboxScale);
+        Anchor? anchor = TransformHelper.GetAnchorInPosition(pos, Corners, InternalState.Origin, ZoomboxScale, topLeftHandle.Size);
 
 
-        if (isMoving)
-        {
-            finalCursor = new Cursor(StandardCursorType.SizeAll);
-            VecD delta = pos - mousePosOnStartMove;
-
-            if (Corners.IsSnappedToPixels)
-                delta = delta.Round();
-
-            Corners = new ShapeCorners()
-            {
-                BottomLeft = cornersOnStartMove.BottomLeft + delta,
-                BottomRight = cornersOnStartMove.BottomRight + delta,
-                TopLeft = cornersOnStartMove.TopLeft + delta,
-                TopRight = cornersOnStartMove.TopRight + delta,
-            };
-
-            InternalState = InternalState with { Origin = originOnStartMove + delta };
-        }
-        else if (isRotating)
+        if (isRotating)
         {
         {
             finalCursor = new Cursor(StandardCursorType.None);
             finalCursor = new Cursor(StandardCursorType.None);
             double angle = (mousePosOnStartRotate - InternalState.Origin).CCWAngleTo(pos - InternalState.Origin);
             double angle = (mousePosOnStartRotate - InternalState.Origin).CCWAngleTo(pos - InternalState.Origin);
@@ -382,14 +432,14 @@ internal class TransformOverlay : Overlay
             InternalState = InternalState with { ProportionalAngle1 = propAngle1OnStartRotate + angle, ProportionalAngle2 = propAngle2OnStartRotate + angle, };
             InternalState = InternalState with { ProportionalAngle1 = propAngle1OnStartRotate + angle, ProportionalAngle2 = propAngle2OnStartRotate + angle, };
             Corners = TransformUpdateHelper.UpdateShapeFromRotation(cornersOnStartRotate, InternalState.Origin, angle);
             Corners = TransformUpdateHelper.UpdateShapeFromRotation(cornersOnStartRotate, InternalState.Origin, angle);
         }
         }
-        /*else if (anchor is not null)
+        else if (anchor is not null)
         {
         {
             if ((TransformHelper.IsCorner((Anchor)anchor) && CornerFreedom == TransformCornerFreedom.Free) ||
             if ((TransformHelper.IsCorner((Anchor)anchor) && CornerFreedom == TransformCornerFreedom.Free) ||
                 (TransformHelper.IsSide((Anchor)anchor) && SideFreedom == TransformSideFreedom.Free))
                 (TransformHelper.IsSide((Anchor)anchor) && SideFreedom == TransformSideFreedom.Free))
                 finalCursor = new Cursor(StandardCursorType.Arrow);
                 finalCursor = new Cursor(StandardCursorType.Arrow);
             else
             else
                 finalCursor = TransformHelper.GetResizeCursor((Anchor)anchor, Corners, ZoomboxAngle);
                 finalCursor = TransformHelper.GetResizeCursor((Anchor)anchor, Corners, ZoomboxAngle);
-        }*/
+        }
 
 
         if (Cursor != finalCursor)
         if (Cursor != finalCursor)
             Cursor = finalCursor;
             Cursor = finalCursor;
@@ -401,7 +451,7 @@ internal class TransformOverlay : Overlay
     {
     {
         if (capturedAnchor is null)
         if (capturedAnchor is null)
             throw new InvalidOperationException("No anchor is captured");
             throw new InvalidOperationException("No anchor is captured");
-        e.Handled = true;
+
         if ((TransformHelper.IsCorner((Anchor)capturedAnchor) && CornerFreedom == TransformCornerFreedom.Locked) ||
         if ((TransformHelper.IsCorner((Anchor)capturedAnchor) && CornerFreedom == TransformCornerFreedom.Locked) ||
             (TransformHelper.IsSide((Anchor)capturedAnchor) && SideFreedom == TransformSideFreedom.Locked))
             (TransformHelper.IsSide((Anchor)capturedAnchor) && SideFreedom == TransformSideFreedom.Locked))
             return;
             return;
@@ -440,38 +490,39 @@ internal class TransformOverlay : Overlay
         }
         }
     }
     }
 
 
+    private void OnAnchorHandleReleased(Handle source)
+    {
+        capturedPointer = null;
+        capturedAnchor = null;
+
+        if (ActionCompleted is not null && ActionCompleted.CanExecute(null))
+            ActionCompleted.Execute(null);
+    }
+
+    private void OnMoveHandleReleased(Handle source)
+    {
+        isMoving = false;
+        capturedPointer = null;
+
+        if (ActionCompleted is not null && ActionCompleted.CanExecute(null))
+            ActionCompleted.Execute(null);
+    }
+
     protected override void OnPointerReleased(PointerReleasedEventArgs e)
     protected override void OnPointerReleased(PointerReleasedEventArgs e)
     {
     {
         base.OnPointerReleased(e);
         base.OnPointerReleased(e);
         if (e.InitialPressMouseButton != MouseButton.Left)
         if (e.InitialPressMouseButton != MouseButton.Left)
             return;
             return;
 
 
-        bool handled = false;
-        if (ReleaseAnchor())
-        {
-            handled = true;
-        }
-        else if (isMoving)
-        {
-            isMoving = false;
-            handled = true;
-            e.Pointer.Capture(null);
-            capturedPointer = null;
-        }
-        else if (isRotating)
+        if (isRotating)
         {
         {
             isRotating = false;
             isRotating = false;
-            handled = true;
             e.Pointer.Capture(null);
             e.Pointer.Capture(null);
             capturedPointer = null;
             capturedPointer = null;
             Cursor = new Cursor(StandardCursorType.Arrow);
             Cursor = new Cursor(StandardCursorType.Arrow);
             var pos = TransformHelper.ToVecD(e.GetPosition(this));
             var pos = TransformHelper.ToVecD(e.GetPosition(this));
             UpdateRotationCursor(pos);
             UpdateRotationCursor(pos);
-        }
 
 
-        if (handled)
-        {
-            e.Handled = true;
             if (ActionCompleted is not null && ActionCompleted.CanExecute(null))
             if (ActionCompleted is not null && ActionCompleted.CanExecute(null))
                 ActionCompleted.Execute(null);
                 ActionCompleted.Execute(null);
         }
         }
@@ -496,14 +547,4 @@ internal class TransformOverlay : Overlay
         overlay.RequestedCorners = new ShapeCorners();
         overlay.RequestedCorners = new ShapeCorners();
         overlay.isResettingRequestedCorners = false;
         overlay.isResettingRequestedCorners = false;
     }
     }
-
-    private bool ReleaseAnchor()
-    {
-        if (capturedAnchor is null)
-            return false;
-        capturedPointer?.Capture(null);
-        capturedPointer = null;
-        capturedAnchor = null;
-        return true;
-    }
 }
 }