Ver código fonte

Crop - major refactor

Ruben 1 ano atrás
pai
commit
fa4b6b0e3a

+ 98 - 0
src/PicView.Avalonia/Crop/CropDragHandler.cs

@@ -0,0 +1,98 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Input;
+using PicView.Avalonia.ViewModels;
+using PicView.Avalonia.Views.UC;
+
+namespace PicView.Avalonia.Crop;
+public class CropDragHandler(CropControl control)
+{
+    private Point _dragStart;
+    private bool _isDragging;
+    private Rect _originalRect;
+
+    public void OnDragStart(object? sender, PointerPressedEventArgs e)
+    {
+        if (!e.GetCurrentPoint(control).Properties.IsLeftButtonPressed || 
+            control.DataContext is not ImageCropperViewModel vm)
+        {
+            return;
+        }
+
+        _dragStart = e.GetPosition(control.RootCanvas);
+        
+        // Get current left and top values; ensure they are initialized
+        var currentLeft = Canvas.GetLeft(control.MainRectangle);
+        var currentTop = Canvas.GetTop(control.MainRectangle);
+
+        // Set default values if NaN
+        if (double.IsNaN(currentLeft))
+        {
+            currentLeft = 0;
+        }
+
+        if (double.IsNaN(currentTop))
+        {
+            currentTop = 0;
+        }
+
+        _originalRect = new Rect(currentLeft, currentTop, vm.SelectionWidth, vm.SelectionHeight);
+        _isDragging = true;
+    }
+
+    public void OnDragMove(object? sender, PointerEventArgs e)
+    {
+        if (!e.GetCurrentPoint(control).Properties.IsLeftButtonPressed)
+        {
+            return;
+        }
+
+        if (control.DataContext is not ImageCropperViewModel vm)
+        {
+            return;
+        }
+
+        if (!_isDragging)
+        {
+            return;
+        }
+
+        var currentPos = e.GetPosition(control.RootCanvas); // Ensure it's relative to RootCanvas
+        var delta = currentPos - _dragStart;
+
+        // Calculate new left and top positions, ensure _originalRect is valid
+        var newLeft = _originalRect.X + delta.X;
+        var newTop = _originalRect.Y + delta.Y;
+
+        // Clamp the newLeft and newTop values to keep the rectangle within bounds
+        newLeft = Math.Max(0, Math.Min(vm.ImageWidth - vm.SelectionWidth, newLeft));
+        newTop = Math.Max(0, Math.Min(vm.ImageHeight - vm.SelectionHeight, newTop));
+
+        // Only proceed if new positions are valid (i.e., not NaN)
+        if (double.IsNaN(newLeft) || double.IsNaN(newTop))
+        {
+            return;
+        }
+
+        // Update the main rectangle's position
+        Canvas.SetLeft(control.MainRectangle, newLeft);
+        Canvas.SetTop(control.MainRectangle, newTop);
+        
+        Canvas.SetLeft(control.SizeBorder, newLeft + 11);
+        Canvas.SetTop(control.SizeBorder, newTop - control.SizeBorder.Bounds.Height - 3);
+
+        // Update view model values
+        vm.SelectionX = Convert.ToInt32(newLeft);
+        vm.SelectionY = Convert.ToInt32(newTop);
+    }
+
+    public void OnDragEnd(object? sender, PointerReleasedEventArgs e)
+    {
+        Reset();
+    }
+
+    public void Reset()
+    {
+        _isDragging = false;
+    }
+}

+ 110 - 0
src/PicView.Avalonia/Crop/CropKeyboardManager.cs

@@ -0,0 +1,110 @@
+using System.Reactive.Linq;
+using System.Runtime.InteropServices;
+using Avalonia.Input;
+using PicView.Avalonia.Input;
+using PicView.Avalonia.UI;
+using PicView.Avalonia.ViewModels;
+using PicView.Avalonia.Views.UC;
+
+namespace PicView.Avalonia.Crop;
+
+public class CropKeyboardManager(CropControl control)
+{
+    public async Task KeyDownHandler(KeyEventArgs e)
+    {
+        if (control.DataContext is not ImageCropperViewModel vm)
+        {
+            return;
+        }
+
+        switch (e.Key)
+        {
+            case Key.Enter:
+                await vm.CropImageCommand.Execute();
+                return;
+            case Key.Escape:
+                CropFunctions.CloseCropControl(UIHelper.GetMainView.DataContext as MainViewModel);
+                return;
+        }
+
+        KeyGesture currentKeys;
+        if (MainKeyboardShortcuts.CtrlDown || MainKeyboardShortcuts.AltOrOptionDown ||
+            MainKeyboardShortcuts.ShiftDown || MainKeyboardShortcuts.CommandDown)
+        {
+            var modifiers = KeyModifiers.None;
+
+            if (MainKeyboardShortcuts.CtrlDown)
+            {
+                modifiers |= KeyModifiers.Control;
+            }
+
+            if (MainKeyboardShortcuts.AltOrOptionDown)
+            {
+                modifiers |= KeyModifiers.Alt;
+            }
+
+            if (MainKeyboardShortcuts.ShiftDown)
+            {
+                modifiers |= KeyModifiers.Shift;
+            }
+
+            if (MainKeyboardShortcuts.CommandDown && RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+            {
+                modifiers |= KeyModifiers.Meta;
+            }
+
+            currentKeys = new KeyGesture(e.Key, modifiers);
+        }
+        else
+        {
+            currentKeys = new KeyGesture(e.Key);
+        }
+
+        if (KeybindingManager.CustomShortcuts.TryGetValue(currentKeys, out var func))
+        {
+            var function = await FunctionsHelper.GetFunctionByName(func.Method.Name);
+            switch (function.Method.Name)
+            {
+                case "Up":
+                case "RotateLeft":
+                    Rotate(false);
+                    return;
+                case "Down":
+                case "RotateRight":
+                    Rotate(true);
+                    return;
+                case "ZoomIn":
+                    ZoomIn();
+                    return;
+                case "ZoomOut":
+                    ZoomOut();
+                    return;
+                case "ResetZoom":
+                    ResetZoom();
+                    return;
+                case "Save":
+                case "SaveAs":
+                case "GalleryClick":
+                    await vm.CropImageCommand.Execute();
+                    return;
+            }
+        }
+    }
+
+
+    private void ZoomIn()
+    {
+    }
+
+    private void ZoomOut()
+    {
+    }
+
+    private void ResetZoom()
+    {
+    }
+
+    private void Rotate(bool clockwise)
+    {
+    }
+}

+ 214 - 0
src/PicView.Avalonia/Crop/CropLayoutManager.cs

@@ -0,0 +1,214 @@
+using Avalonia.Controls;
+using PicView.Avalonia.ViewModels;
+using PicView.Avalonia.Views.UC;
+
+namespace PicView.Avalonia.Crop;
+
+public class CropLayoutManager(CropControl control)
+{
+    public void InitializeLayout()
+    {
+
+            if (control.DataContext is not ImageCropperViewModel vm)
+            {
+                return;
+            }
+
+            // Ensure image dimensions are valid before proceeding
+            if (vm.ImageWidth <= 0 || vm.ImageHeight <= 0)
+            {
+                return;
+            }
+
+            // Set initial width and height for the crop rectangle
+            var pixelWidth = vm.ImageWidth / vm.AspectRatio;
+            var pixelHeight = vm.ImageHeight / vm.AspectRatio;
+
+            if (pixelWidth >= 400 || pixelHeight >= 400)
+            {
+                vm.SelectionWidth = 200;
+                vm.SelectionHeight = 200;
+            }
+            else if (pixelWidth <= 200 || pixelHeight <= 200)
+            {
+                vm.SelectionWidth = pixelWidth / 2;
+                vm.SelectionHeight = pixelHeight / 2;
+            }
+
+
+            // Calculate centered position
+            vm.SelectionX = Convert.ToInt32((vm.ImageWidth - vm.SelectionWidth) / 2);
+            vm.SelectionY = Convert.ToInt32((vm.ImageHeight - vm.SelectionHeight) / 2);
+
+            // Apply the calculated position to the MainRectangle
+            Canvas.SetLeft(control.MainRectangle, vm.SelectionX);
+            Canvas.SetTop(control.MainRectangle, vm.SelectionY);
+
+            UpdateLayout();
+    }
+
+    public void UpdateLayout()
+    {
+        try
+        {
+            UpdateButtonPositions();
+            UpdateSurroundingRectangles();
+        }
+        catch (Exception e)
+        {
+            //
+        }
+    }
+
+    private void UpdateSurroundingRectangles()
+    {
+        if (control.DataContext is not ImageCropperViewModel vm)
+        {
+            return;
+        }
+
+        // Converting to int fixes black border
+        var left = Convert.ToInt32(Canvas.GetLeft(control.MainRectangle));
+        var top = Convert.ToInt32(Canvas.GetTop(control.MainRectangle));
+        var right = Convert.ToInt32(left + vm.SelectionWidth);
+        var bottom = Convert.ToInt32(top + vm.SelectionHeight);
+
+        // Calculate the positions and sizes for the surrounding rectangles
+        // Top Rectangle (above MainRectangle)
+        control.TopRectangle.Width = vm.ImageWidth;
+        control.TopRectangle.Height = top < 0 ? 0 : top;
+        Canvas.SetTop(control.TopRectangle, 0);
+
+        // Bottom Rectangle (below MainRectangle)
+        control.BottomRectangle.Width = vm.ImageWidth;
+        var newBottomRectangleHeight = vm.ImageHeight - bottom < 0 ? 0 : vm.ImageHeight - bottom;
+        control.BottomRectangle.Height = newBottomRectangleHeight;
+        Canvas.SetTop(control.BottomRectangle, bottom);
+
+        // Left Rectangle (left of MainRectangle)
+        control.LeftRectangle.Width = left < 0 ? 0 : left;
+        control.LeftRectangle.Height = vm.SelectionHeight;
+        Canvas.SetLeft(control.LeftRectangle, 0);
+        Canvas.SetTop(control.LeftRectangle, top);
+
+        // Right Rectangle (right of MainRectangle)
+        var newRightRectangleWidth = vm.ImageWidth - right < 0 ? 0 : vm.ImageWidth - right;
+        control.RightRectangle.Width = newRightRectangleWidth;
+        control.RightRectangle.Height = vm.SelectionHeight;
+        Canvas.SetLeft(control.RightRectangle, right);
+        Canvas.SetTop(control.RightRectangle, top);
+    }
+
+    public void UpdateButtonPositions()
+    {
+        if (control.DataContext is not ImageCropperViewModel vm)
+        {
+            return;
+        }
+
+        var selectionX = vm.SelectionX;
+        var selectionY = vm.SelectionY;
+        var selectionWidth = vm.SelectionWidth;
+        var selectionHeight = vm.SelectionHeight;
+
+        // Get the bounds of the RootCanvas (the control container)
+        const int rootCanvasLeft = 0;
+        const int rootCanvasTop = 0;
+        var rootCanvasRight = control.RootCanvas.Bounds.Width;
+        var rootCanvasBottom = control.RootCanvas.Bounds.Height;
+
+        // Calculate the positions for each button
+        var topLeftX = selectionX - control.TopLeftButton.Width / 2;
+        var topLeftY = selectionY - control.TopLeftButton.Height / 2;
+
+        var topRightX = selectionX + selectionWidth - control.TopRightButton.Width / 2;
+        var topRightY = selectionY - control.TopRightButton.Height / 2;
+
+        var topMiddleX = selectionX + selectionWidth / 2 - control.TopMiddleButton.Width / 2;
+        var topMiddleY = selectionY - control.TopMiddleButton.Height / 2;
+
+        var bottomLeftX = selectionX - control.BottomLeftButton.Width / 2;
+        var bottomLeftY = selectionY + selectionHeight - control.BottomLeftButton.Height / 2;
+
+        var bottomRightX = selectionX + selectionWidth - control.BottomRightButton.Width / 2;
+        var bottomRightY = selectionY + selectionHeight - control.BottomRightButton.Height / 2;
+
+        var bottomMiddleX = selectionX + selectionWidth / 2 - control.BottomMiddleButton.Width / 2;
+        var bottomMiddleY = selectionY + selectionHeight - control.BottomMiddleButton.Height / 2;
+
+        var leftMiddleX = selectionX - control.LeftMiddleButton.Width / 2;
+        var leftMiddleY = selectionY + selectionHeight / 2 - control.LeftMiddleButton.Height / 2;
+
+        var rightMiddleX = selectionX + selectionWidth - control.RightMiddleButton.Width / 2;
+        var rightMiddleY = selectionY + selectionHeight / 2 - control.RightMiddleButton.Height / 2;
+
+        // Ensure buttons stay within RootCanvas bounds (by clamping positions)
+        topLeftX = Math.Max(rootCanvasLeft, Math.Min(rootCanvasRight - control.TopLeftButton.Width, topLeftX));
+        topLeftY = Math.Max(rootCanvasTop, Math.Min(rootCanvasBottom - control.TopLeftButton.Height, topLeftY));
+
+        topRightX = Math.Max(rootCanvasLeft, Math.Min(rootCanvasRight - control.TopRightButton.Width, topRightX));
+        topRightY = Math.Max(rootCanvasTop, Math.Min(rootCanvasBottom - control.TopRightButton.Height, topRightY));
+
+        topMiddleX = Math.Max(rootCanvasLeft, Math.Min(rootCanvasRight - control.TopMiddleButton.Width, topMiddleX));
+        topMiddleY = Math.Max(rootCanvasTop, Math.Min(rootCanvasBottom - control.TopMiddleButton.Height, topMiddleY));
+
+        bottomLeftX = Math.Max(rootCanvasLeft, Math.Min(rootCanvasRight - control.BottomLeftButton.Width, bottomLeftX));
+        bottomLeftY = Math.Max(rootCanvasTop,
+            Math.Min(rootCanvasBottom - control.BottomLeftButton.Height, bottomLeftY));
+
+        bottomRightX = Math.Max(rootCanvasLeft,
+            Math.Min(rootCanvasRight - control.BottomRightButton.Width, bottomRightX));
+        bottomRightY = Math.Max(rootCanvasTop,
+            Math.Min(rootCanvasBottom - control.BottomRightButton.Height, bottomRightY));
+
+        bottomMiddleX = Math.Max(rootCanvasLeft,
+            Math.Min(rootCanvasRight - control.BottomMiddleButton.Width, bottomMiddleX));
+        bottomMiddleY = Math.Max(rootCanvasTop,
+            Math.Min(rootCanvasBottom - control.BottomMiddleButton.Height, bottomMiddleY));
+
+        leftMiddleX = Math.Max(rootCanvasLeft, Math.Min(rootCanvasRight - control.LeftMiddleButton.Width, leftMiddleX));
+        leftMiddleY = Math.Max(rootCanvasTop,
+            Math.Min(rootCanvasBottom - control.LeftMiddleButton.Height, leftMiddleY));
+
+        rightMiddleX = Math.Max(rootCanvasLeft,
+            Math.Min(rootCanvasRight - control.RightMiddleButton.Width, rightMiddleX));
+        rightMiddleY = Math.Max(rootCanvasTop,
+            Math.Min(rootCanvasBottom - control.RightMiddleButton.Height, rightMiddleY));
+
+        // Set the final button positions
+        Canvas.SetLeft(control.TopLeftButton, topLeftX);
+        Canvas.SetTop(control.TopLeftButton, topLeftY);
+
+        Canvas.SetLeft(control.TopRightButton, topRightX);
+        Canvas.SetTop(control.TopRightButton, topRightY);
+
+        Canvas.SetLeft(control.TopMiddleButton, topMiddleX);
+        Canvas.SetTop(control.TopMiddleButton, topMiddleY);
+
+        Canvas.SetLeft(control.BottomLeftButton, bottomLeftX);
+        Canvas.SetTop(control.BottomLeftButton, bottomLeftY);
+
+        Canvas.SetLeft(control.BottomRightButton, bottomRightX);
+        Canvas.SetTop(control.BottomRightButton, bottomRightY);
+
+        Canvas.SetLeft(control.BottomMiddleButton, bottomMiddleX);
+        Canvas.SetTop(control.BottomMiddleButton, bottomMiddleY);
+
+        Canvas.SetLeft(control.LeftMiddleButton, leftMiddleX);
+        Canvas.SetTop(control.LeftMiddleButton, leftMiddleY);
+
+        Canvas.SetLeft(control.RightMiddleButton, rightMiddleX);
+        Canvas.SetTop(control.RightMiddleButton, rightMiddleY);
+
+        Canvas.SetLeft(control.SizeBorder, topLeftX + control.TopLeftButton.Bounds.Width + 2);
+        
+        if (topLeftY != 0)
+        {
+            Canvas.SetTop(control.SizeBorder, topLeftY - control.TopLeftButton.Bounds.Height);
+        }
+        else
+        {
+            Canvas.SetTop(control.SizeBorder, topLeftY);
+        }
+    }
+}

+ 49 - 0
src/PicView.Avalonia/Crop/CropResizeHandler.cs

@@ -0,0 +1,49 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Input;
+using PicView.Avalonia.ViewModels;
+using PicView.Avalonia.Views.UC;
+
+namespace PicView.Avalonia.Crop;
+
+public class CropResizeHandler(CropControl control)
+{
+    private bool _isResizing;
+    private Rect _originalRect;
+    private Point _resizeStart;
+
+    public void OnResizeStart(PointerPressedEventArgs e)
+    {
+        if (!e.GetCurrentPoint(control).Properties.IsLeftButtonPressed ||
+            control.DataContext is not ImageCropperViewModel vm)
+        {
+            return;
+        }
+
+        _resizeStart = e.GetPosition(control.RootCanvas);
+        _originalRect = new Rect(Canvas.GetLeft(control.MainRectangle), Canvas.GetTop(control.MainRectangle), vm.SelectionWidth,
+            vm.SelectionHeight);
+        _isResizing = true;
+    }
+
+    public void OnResizeMove(object? sender, PointerEventArgs e, CropResizeMode mode)
+    {
+        if (!_isResizing || control.DataContext is not ImageCropperViewModel vm)
+        {
+            return;
+        }
+
+        var resizer = CropResizeStrategyFactory.Create(mode);
+        resizer.Resize(control, e, _resizeStart, _originalRect, vm);
+    }
+
+    public void OnResizeEnd(object? sender, PointerReleasedEventArgs e)
+    {
+        Reset();
+    }
+
+    public void Reset()
+    {
+        _isResizing = false;
+    }
+}

+ 12 - 0
src/PicView.Avalonia/Crop/CropResizeMode.cs

@@ -0,0 +1,12 @@
+namespace PicView.Avalonia.Crop;
+public enum CropResizeMode
+{
+    TopLeft,
+    TopRight,
+    BottomLeft,
+    BottomRight,
+    Left,
+    Right,
+    Top,
+    Bottom
+}

+ 414 - 0
src/PicView.Avalonia/Crop/CropResizeStrategyFactory.cs

@@ -0,0 +1,414 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Input;
+using PicView.Avalonia.Input;
+using PicView.Avalonia.ViewModels;
+using PicView.Avalonia.Views.UC;
+
+namespace PicView.Avalonia.Crop;
+
+public interface IResizeStrategy
+{
+    void Resize(CropControl control, PointerEventArgs e, Point resizeStart, Rect originalRect,
+        ImageCropperViewModel vm);
+}
+
+public static class CropResizeStrategyFactory
+{
+    public static IResizeStrategy Create(CropResizeMode mode)
+    {
+        return mode switch
+        {
+            CropResizeMode.TopLeft => new TopLeftResizeStrategy(),
+            CropResizeMode.TopRight => new TopRightResizeStrategy(),
+            CropResizeMode.BottomLeft => new BottomLeftResizeStrategy(),
+            CropResizeMode.BottomRight => new BottomRightResizeStrategy(),
+            CropResizeMode.Left => new LeftResizeStrategy(),
+            CropResizeMode.Right => new RightResizeStrategy(),
+            CropResizeMode.Top => new TopResizeStrategy(),
+            CropResizeMode.Bottom => new BottomResizeStrategy(),
+            _ => throw new ArgumentException($"Unsupported resize mode: {mode}")
+        };
+    }
+}
+
+public class BottomResizeStrategy : IResizeStrategy
+{
+    public void Resize(CropControl control, PointerEventArgs e, Point resizeStart, Rect originalRect,
+        ImageCropperViewModel vm)
+    {
+        // Get the current mouse position relative to RootCanvas
+        var currentPos = e.GetPosition(control.RootCanvas);
+        var delta = currentPos - resizeStart;
+
+        // Calculate the new height based on vertical movement
+        var newHeight = originalRect.Height + delta.Y;
+
+        // Ensure the new height doesn't go negative or too small
+        newHeight = Math.Max(newHeight, 1);
+
+        // Ensure the bottom edge doesn't go beyond the canvas bounds
+        if (originalRect.Y + newHeight > control.RootCanvas.Bounds.Height)
+        {
+            newHeight = control.RootCanvas.Bounds.Height - originalRect.Y;
+        }
+
+        // Prevent the size from becoming too small
+        newHeight = Math.Max(newHeight, 1);
+
+        // Update the view model with the new height
+        vm.SelectionHeight = newHeight;
+
+        // Update the rectangle on the canvas
+        Canvas.SetLeft(control.MainRectangle, originalRect.X);
+        Canvas.SetTop(control.MainRectangle, originalRect.Y);
+    }
+}
+
+public class TopResizeStrategy : IResizeStrategy
+{
+    public void Resize(CropControl control, PointerEventArgs e, Point resizeStart, Rect originalRect,
+        ImageCropperViewModel vm)
+    {
+        // Get the current mouse position relative to RootCanvas
+        var currentPos = e.GetPosition(control.RootCanvas);
+        var delta = currentPos - resizeStart;
+
+        // Calculate the new top position and height
+        var newTop = originalRect.Y + delta.Y;
+        var newHeight = originalRect.Height - delta.Y;
+
+        // Ensure the new height doesn't go negative or too small
+        if (newHeight < 1)
+        {
+            newHeight = 1;
+            newTop = originalRect.Y + (originalRect.Height - 1); // Adjust top to preserve min height
+        }
+
+        // Ensure the top edge doesn't go beyond the canvas bounds
+        if (newTop < 0)
+        {
+            newTop = 0;
+            newHeight = originalRect.Height + originalRect.Y; // Adjust height to compensate
+        }
+
+        // Prevent the size from becoming too small
+        newHeight = Math.Max(newHeight, 1);
+
+        // Update the view model with the new top and height
+        vm.SelectionHeight = newHeight;
+        vm.SelectionY = Convert.ToInt32(newTop);
+
+        // Update the rectangle on the canvas
+        Canvas.SetLeft(control.MainRectangle, originalRect.X);
+        Canvas.SetTop(control.MainRectangle, newTop);
+    }
+}
+
+public class RightResizeStrategy : IResizeStrategy
+{
+    public void Resize(CropControl control, PointerEventArgs e, Point resizeStart, Rect originalRect,
+        ImageCropperViewModel vm)
+    {
+        // Get the current mouse position relative to RootCanvas
+        var currentPos = e.GetPosition(control.RootCanvas);
+        var delta = currentPos - resizeStart;
+
+        // Calculate the new width based on horizontal movement
+        var newWidth = originalRect.Width + delta.X;
+
+        // Ensure the new width doesn't go beyond the bounds of the RootCanvas
+        if (originalRect.X + newWidth > vm.ImageWidth)
+        {
+            newWidth = vm.ImageWidth - originalRect.X;
+        }
+
+        // Constrain the width to a minimum value (e.g., 1 to prevent zero or negative width)
+        newWidth = Math.Max(newWidth, 1);
+
+        // Update the view model with the new width
+        vm.SelectionWidth = newWidth;
+
+        // Update the rectangle on the canvas
+        Canvas.SetLeft(control.MainRectangle, originalRect.X);
+        Canvas.SetTop(control.MainRectangle, originalRect.Y);
+    }
+}
+
+public class LeftResizeStrategy : IResizeStrategy
+{
+    public void Resize(CropControl control, PointerEventArgs e, Point resizeStart, Rect originalRect,
+        ImageCropperViewModel vm)
+    {
+// Get the current mouse position relative to RootCanvas
+        var currentPos = e.GetPosition(control.RootCanvas);
+        var delta = currentPos - resizeStart;
+
+        // Calculate the new X position
+        var newX = originalRect.X + delta.X;
+
+        // Calculate the new width based on horizontal movement
+        var newWidth = originalRect.Width - delta.X;
+
+        // Ensure that the rectangle doesn't go beyond the right boundary
+        if (newX < 0)
+        {
+            newWidth = originalRect.Width + originalRect.X;
+            newX = 0;
+        }
+
+        // Ensure the new width doesn't go negative or too small
+        newWidth = Math.Max(newWidth, 1);
+
+        // Update the view model with the new X position and width
+        vm.SelectionX = Convert.ToInt32(newX);
+        vm.SelectionWidth = newWidth;
+
+        // Update the rectangle on the canvas
+        Canvas.SetLeft(control.MainRectangle, newX);
+        Canvas.SetTop(control.MainRectangle, originalRect.Y);
+    }
+}
+
+public class BottomRightResizeStrategy : IResizeStrategy
+{
+    public void Resize(CropControl control, PointerEventArgs e, Point resizeStart, Rect originalRect,
+        ImageCropperViewModel vm)
+    {
+        var currentPos = e.GetPosition(control.RootCanvas);
+        var delta = currentPos - resizeStart;
+
+        // Calculate the new width and height based on the drag delta
+        var newWidth = originalRect.Width + delta.X;
+        var newHeight = originalRect.Height + delta.Y;
+
+        // If shift is pressed, maintain square aspect ratio
+        if (MainKeyboardShortcuts.ShiftDown)
+        {
+            // Use the larger of the two dimensions to maintain square shape
+            var size = Math.Max(newWidth, newHeight);
+            newWidth = size;
+            newHeight = size;
+        }
+
+        // Ensure the new width and height do not exceed the image bounds
+        var newRight = originalRect.X + newWidth;
+        var newBottom = originalRect.Y + newHeight;
+
+        if (newRight > vm.ImageWidth)
+        {
+            newWidth = vm.ImageWidth - originalRect.X;
+            if (MainKeyboardShortcuts.ShiftDown)
+            {
+                newHeight = newWidth;
+            }
+        }
+
+        if (newBottom > vm.ImageHeight)
+        {
+            newHeight = vm.ImageHeight - originalRect.Y;
+            if (MainKeyboardShortcuts.ShiftDown)
+            {
+                newWidth = newHeight;
+            }
+        }
+
+        // Constrain the minimum size
+        newWidth = Math.Max(newWidth, 1);
+        newHeight = Math.Max(newHeight, 1);
+
+        // Apply the new width and height
+        vm.SelectionWidth = newWidth;
+        vm.SelectionHeight = newHeight;
+        
+        // Update the rectangle on the canvas
+        Canvas.SetLeft(control.MainRectangle, originalRect.X);
+        Canvas.SetTop(control.MainRectangle, originalRect.Y);
+    }
+}
+
+public class BottomLeftResizeStrategy : IResizeStrategy
+{
+    public void Resize(CropControl control, PointerEventArgs e, Point resizeStart, Rect originalRect,
+        ImageCropperViewModel vm)
+    {
+        var currentPos = e.GetPosition(control.RootCanvas);
+        var delta = currentPos - resizeStart;
+
+        var newWidth = originalRect.Width - delta.X;
+        var newHeight = originalRect.Height + delta.Y;
+        var newX = originalRect.X + delta.X;
+
+        // If shift is pressed, maintain square aspect ratio
+        if (MainKeyboardShortcuts.ShiftDown)
+        {
+            // Use the larger dimension to determine the square size
+            var size = Math.Max(newWidth, newHeight);
+            // Calculate how much to adjust X to maintain the right edge
+            var widthDiff = size - newWidth;
+            newX -= widthDiff;
+            newWidth = size;
+            newHeight = size;
+        }
+
+        // Ensure the left doesn't move beyond the left edge
+        if (newX < 0)
+        {
+            var adjustment = -newX;
+            newX = 0;
+            newWidth -= adjustment;
+            if (MainKeyboardShortcuts.ShiftDown)
+            {
+                newHeight = newWidth;
+            }
+        }
+
+        // Ensure the height doesn't exceed the canvas' bottom edge
+        if (originalRect.Y + newHeight > vm.ImageHeight)
+        {
+            newHeight = vm.ImageHeight - originalRect.Y;
+            if (MainKeyboardShortcuts.ShiftDown)
+            {
+                newWidth = newHeight;
+                newX = originalRect.X + (originalRect.Width - newWidth);
+            }
+        }
+
+        // Prevent the width and height from becoming too small
+        newWidth = Math.Max(newWidth, 1);
+        newHeight = Math.Max(newHeight, 1);
+
+        // Apply the new size and position
+        vm.SelectionX = Convert.ToInt32(newX);
+        vm.SelectionWidth = newWidth;
+        vm.SelectionHeight = newHeight;
+        Canvas.SetLeft(control.MainRectangle, newX);
+        Canvas.SetTop(control.MainRectangle, originalRect.Y);
+    }
+}
+
+public class TopRightResizeStrategy : IResizeStrategy
+{
+    public void Resize(CropControl control, PointerEventArgs e, Point resizeStart, Rect originalRect,
+        ImageCropperViewModel vm)
+    {
+        var currentPos = e.GetPosition(control.RootCanvas);
+        var delta = currentPos - resizeStart;
+
+        var newWidth = originalRect.Width + delta.X;
+        var newHeight = originalRect.Height - delta.Y;
+        var newY = originalRect.Y + delta.Y;
+
+        // If shift is pressed, maintain square aspect ratio
+        if (MainKeyboardShortcuts.ShiftDown)
+        {
+            // Use the larger dimension to determine the square size
+            var size = Math.Max(newWidth, newHeight);
+            // Calculate how much to adjust Y to maintain the bottom edge
+            var heightDiff = size - newHeight;
+            newY -= heightDiff;
+            newWidth = size;
+            newHeight = size;
+        }
+
+        // Ensure the width doesn't exceed the canvas' right edge
+        if (originalRect.X + newWidth > vm.ImageWidth)
+        {
+            newWidth = vm.ImageWidth - originalRect.X;
+            if (MainKeyboardShortcuts.ShiftDown)
+            {
+                newHeight = newWidth;
+                newY = originalRect.Y + (originalRect.Height - newHeight);
+            }
+        }
+
+        // Ensure the top doesn't move above the top edge of the canvas
+        if (newY < 0)
+        {
+            var adjustment = -newY;
+            newY = 0;
+            newHeight = originalRect.Height + adjustment;
+            if (MainKeyboardShortcuts.ShiftDown)
+            {
+                newWidth = newHeight;
+            }
+        }
+
+        // Prevent the size from becoming too small
+        newHeight = Math.Max(newHeight, 1);
+        newWidth = Math.Max(newWidth, 1);
+
+        // Apply the new size and position
+        vm.SelectionY = Convert.ToInt32(newY);
+        vm.SelectionWidth = newWidth;
+        vm.SelectionHeight = newHeight;
+        Canvas.SetLeft(control.MainRectangle, originalRect.X);
+        Canvas.SetTop(control.MainRectangle, newY);
+    }
+}
+
+public class TopLeftResizeStrategy : IResizeStrategy
+{
+    public void Resize(CropControl control, PointerEventArgs e, Point resizeStart, Rect originalRect,
+        ImageCropperViewModel vm)
+    {
+        var currentPos = e.GetPosition(control.RootCanvas);
+        var delta = currentPos - resizeStart;
+
+        var newWidth = originalRect.Width - delta.X;
+        var newHeight = originalRect.Height - delta.Y;
+        var newLeft = originalRect.X + delta.X;
+        var newTop = originalRect.Y + delta.Y;
+
+        // If shift is pressed, maintain square aspect ratio
+        if (MainKeyboardShortcuts.ShiftDown)
+        {
+            // Use the larger dimension to determine the square size
+            var size = Math.Max(newWidth, newHeight);
+            // Calculate adjustments needed to maintain right and bottom edges
+            var widthDiff = size - newWidth;
+            var heightDiff = size - newHeight;
+            newLeft -= widthDiff;
+            newTop -= heightDiff;
+            newWidth = size;
+            newHeight = size;
+        }
+
+        // Ensure we don't go beyond boundaries
+        if (newLeft < 0)
+        {
+            var adjustment = -newLeft;
+            newLeft = 0;
+            newWidth -= adjustment;
+            if (MainKeyboardShortcuts.ShiftDown)
+            {
+                newHeight = newWidth;
+                newTop = originalRect.Y + (originalRect.Height - newHeight);
+            }
+        }
+
+        if (newTop < 0)
+        {
+            var adjustment = -newTop;
+            newTop = 0;
+            newHeight -= adjustment;
+            if (MainKeyboardShortcuts.ShiftDown)
+            {
+                newWidth = newHeight;
+                newLeft = originalRect.X + (originalRect.Width - newWidth);
+            }
+        }
+
+        // Prevent the size from becoming too small
+        newHeight = Math.Max(newHeight, 1);
+        newWidth = Math.Max(newWidth, 1);
+
+        // Apply the new size and position
+        vm.SelectionX = Convert.ToInt32(newLeft);
+        vm.SelectionY = Convert.ToInt32(newTop);
+        vm.SelectionWidth = newWidth;
+        vm.SelectionHeight = newHeight;
+        Canvas.SetLeft(control.MainRectangle, newLeft);
+        Canvas.SetTop(control.MainRectangle, newTop);
+    }
+}

+ 58 - 848
src/PicView.Avalonia/Views/UC/CropControl.axaml.cs

@@ -1,882 +1,92 @@
-using System.Reactive.Linq;
-using System.Runtime.InteropServices;
-using Avalonia;
-using Avalonia.Controls;
+using Avalonia.Controls;
 using Avalonia.Input;
 using Avalonia.Interactivity;
 using PicView.Avalonia.Crop;
-using PicView.Avalonia.Input;
-using PicView.Avalonia.UI;
-using PicView.Avalonia.ViewModels;
 
 namespace PicView.Avalonia.Views.UC;
 
 public partial class CropControl : UserControl
 {
-    private Point _dragStart;
-    private bool _isDragging;
-    private bool _isResizing;
-    private Rect _originalRect;
-    private Point _resizeStart;
+    private readonly CropKeyboardManager? _keyboardManager;
+    private readonly CropDragHandler? _dragHandler;
+    private readonly CropLayoutManager? _layoutManager;
+    private readonly CropResizeHandler? _resizeHandler;
 
     public CropControl()
     {
         InitializeComponent();
+        _keyboardManager = new CropKeyboardManager(this);
+        _dragHandler = new CropDragHandler(this);
+        _resizeHandler = new CropResizeHandler(this);
+        _layoutManager = new CropLayoutManager(this);
+
         Loaded += delegate
         {
-            InitializeLayout();
-            MainRectangle.PointerPressed += OnPointerPressed;
-            MainRectangle.PointerReleased += OnPointerReleased;
-            MainRectangle.PointerMoved += OnPointerMoved;
-
-            TopLeftButton.PointerPressed += OnResizePointerPressed;
-            TopRightButton.PointerPressed += OnResizePointerPressed;
-            BottomLeftButton.PointerPressed += OnResizePointerPressed;
-            BottomRightButton.PointerPressed += OnResizePointerPressed;
-            LeftMiddleButton.PointerPressed += OnResizePointerPressed;
-            RightMiddleButton.PointerPressed += OnResizePointerPressed;
-            TopMiddleButton.PointerPressed += OnResizePointerPressed;
-            BottomMiddleButton.PointerPressed += OnResizePointerPressed;
-
-            TopLeftButton.PointerMoved += (_, e) => ResizeTopLeft(e);
-            TopRightButton.PointerMoved += (_, e) => ResizeTopRight(e);
-            BottomLeftButton.PointerMoved += (_, e) => ResizeBottomLeft(e);
-            BottomRightButton.PointerMoved += (_, e) => ResizeBottomRight(e);
-            LeftMiddleButton.PointerMoved += (_, e) => ResizeLeftMiddle(e);
-            RightMiddleButton.PointerMoved += (_, e) => ResizeRightMiddle(e);
-            TopMiddleButton.PointerMoved += (_, e) => ResizeTopMiddle(e);
-            BottomMiddleButton.PointerMoved += (_, e) => ResizeBottomMiddle(e);
-
-            TopLeftButton.PointerReleased += OnResizePointerReleased;
-            TopRightButton.PointerReleased += OnResizePointerReleased;
-            BottomLeftButton.PointerReleased += OnResizePointerReleased;
-            BottomRightButton.PointerReleased += OnResizePointerReleased;
-            LeftMiddleButton.PointerReleased += OnResizePointerReleased;
-            RightMiddleButton.PointerReleased += OnResizePointerReleased;
-            TopMiddleButton.PointerReleased += OnResizePointerReleased;
-            BottomMiddleButton.PointerReleased += OnResizePointerReleased;
-
-            LostFocus += delegate
-            {
-                _isResizing = false;
-                _isDragging = false;
-            };
-            
+            MainRectangle.PointerPressed += _dragHandler.OnDragStart;
+            MainRectangle.PointerReleased += _dragHandler.OnDragEnd;
+            MainRectangle.PointerMoved += _dragHandler.OnDragMove;
+            MainRectangle.PointerMoved += (_, _) => _layoutManager.UpdateLayout();
+
+            TopLeftButton.PointerPressed += (_, e) => _resizeHandler.OnResizeStart(e);
+            TopRightButton.PointerPressed += (_, e) => _resizeHandler.OnResizeStart(e);
+            BottomLeftButton.PointerPressed += (_, e) => _resizeHandler.OnResizeStart(e);
+            BottomRightButton.PointerPressed += (_, e) => _resizeHandler.OnResizeStart(e);
+            LeftMiddleButton.PointerPressed += (_, e) => _resizeHandler.OnResizeStart(e);
+            RightMiddleButton.PointerPressed += (_, e) => _resizeHandler.OnResizeStart(e);
+            TopMiddleButton.PointerPressed += (_, e) => _resizeHandler.OnResizeStart(e);
+            BottomMiddleButton.PointerPressed += (_, e) => _resizeHandler.OnResizeStart(e);
+
+            TopLeftButton.PointerReleased += _resizeHandler.OnResizeEnd;
+            TopRightButton.PointerReleased += _resizeHandler.OnResizeEnd;
+            BottomLeftButton.PointerReleased += _resizeHandler.OnResizeEnd;
+            BottomRightButton.PointerReleased += _resizeHandler.OnResizeEnd;
+            LeftMiddleButton.PointerReleased += _resizeHandler.OnResizeEnd;
+            RightMiddleButton.PointerReleased += _resizeHandler.OnResizeEnd;
+            TopMiddleButton.PointerReleased += _resizeHandler.OnResizeEnd;
+            BottomMiddleButton.PointerReleased += _resizeHandler.OnResizeEnd;
         };
-        AddHandler(KeyDownEvent, KeyDownHandler, RoutingStrategies.Tunnel);
-    }
-    
-    public async Task KeyDownHandler(object? sender, KeyEventArgs e)
-    {
-        if (DataContext is not ImageCropperViewModel vm)
-        {
-            return;
-        }
-        
-        switch (e.Key)
-        {
-            case Key.Enter:
-                await vm.CropImageCommand.Execute();
-                return;
-            case Key.Escape:
-                CropFunctions.CloseCropControl(UIHelper.GetMainView.DataContext as MainViewModel);
-                return;
-        }
-        
-        KeyGesture currentKeys;
-        if (MainKeyboardShortcuts.CtrlDown || MainKeyboardShortcuts.AltOrOptionDown || MainKeyboardShortcuts.ShiftDown || MainKeyboardShortcuts.CommandDown)
-        {
-            var modifiers = KeyModifiers.None;
-
-            if (MainKeyboardShortcuts.CtrlDown) modifiers |= KeyModifiers.Control;
-            if (MainKeyboardShortcuts.AltOrOptionDown) modifiers |= KeyModifiers.Alt;
-            if (MainKeyboardShortcuts.ShiftDown) modifiers |= KeyModifiers.Shift;
-            if (MainKeyboardShortcuts.CommandDown && RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) modifiers |= KeyModifiers.Meta;
-
-            currentKeys = new KeyGesture(e.Key, modifiers);
-        }
-        else
-        {
-            currentKeys = new KeyGesture(e.Key);
-        }
-
-        if (KeybindingManager.CustomShortcuts.TryGetValue(currentKeys, out var func))
-        {
-            var function = await FunctionsHelper.GetFunctionByName(func.Method.Name);
-            switch (function.Method.Name)
-            {
-                case "Up":
-                case "RotateLeft":
-                    Rotate(false);
-                    return;
-                case "Down":
-                case "RotateRight":
-                    Rotate(true);
-                    return;   
-                case "ZoomIn":
-                    ZoomIn();
-                    return;
-                case "ZoomOut":
-                    ZoomOut();
-                    return;
-                case "ResetZoom":
-                    ResetZoom();
-                    return;
-                case "Save":
-                case "SaveAs":
-                case "GalleryClick":
-                    await vm.CropImageCommand.Execute();
-                return;
-            }
-        }
-    }
-    
-
-    private void ZoomIn()
-    {
-        
-    }
-    
-    private void ZoomOut()
-    {
-        
-    }
-    
-    private void ResetZoom()
-    {
-        
-    }
-
-    private void Rotate(bool clockwise)
-    {
-        
-    }
-
-    private void InitializeLayout()
-    {
-        if (DataContext is not ImageCropperViewModel vm)
-        {
-            return;
-        }
-
-        // Ensure image dimensions are valid before proceeding
-        if (vm.ImageWidth <= 0 || vm.ImageHeight <= 0)
-        {
-            return;
-        }
-
-        // Set initial width and height for the crop rectangle
-        var pixelWidth = vm.ImageWidth / vm.AspectRatio;
-        var pixelHeight = vm.ImageHeight / vm.AspectRatio;
-
-        if (pixelWidth >= 400 || pixelHeight >= 400)
-        {
-            vm.SelectionWidth = 200;
-            vm.SelectionHeight = 200;
-        }
-        else if (pixelWidth <= 200 || pixelHeight <= 200)
-        {
-            vm.SelectionWidth = pixelWidth / 2;
-            vm.SelectionHeight = pixelHeight / 2;
-        }
-
-
-        // Calculate centered position
-        vm.SelectionX = Convert.ToInt32((vm.ImageWidth - vm.SelectionWidth) / 2);
-        vm.SelectionY = Convert.ToInt32((vm.ImageHeight - vm.SelectionHeight) / 2);
-
-        // Apply the calculated position to the MainRectangle
-        Canvas.SetLeft(MainRectangle, vm.SelectionX);
-        Canvas.SetTop(MainRectangle, vm.SelectionY);
-
-        // Set buttons positions based on MainRectangle's position
-        UpdateButtonPositions(vm.SelectionX, vm.SelectionY, vm.SelectionWidth, vm.SelectionHeight);
-
-        try
-        {
-            UpdateSurroundingRectangles();
-        }
-        catch (Exception e)
-        {
-#if DEBUG
-            Console.WriteLine(e);
-#endif
-        }
-    }
-
-    private void OnResizePointerPressed(object? sender, PointerPressedEventArgs e)
-    {
-        if (DataContext is not ImageCropperViewModel vm || !e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
-        {
-            return;
-        }
-
-        // Capture the start position of the mouse and the initial size/position of the rectangle
-        _resizeStart = e.GetPosition(RootCanvas);
-        _originalRect = new Rect(Canvas.GetLeft(MainRectangle), Canvas.GetTop(MainRectangle), vm.SelectionWidth,
-            vm.SelectionHeight);
-
-        _isResizing = true;
-    }
-
-    private void UpdateButtonPositions(double selectionX, double selectionY, double selectionWidth,
-        double selectionHeight)
-    {
-        try
-        {
-            // Get the bounds of the RootCanvas (the control container)
-            const int rootCanvasLeft = 0;
-            const int rootCanvasTop = 0;
-            var rootCanvasRight = RootCanvas.Bounds.Width;
-            var rootCanvasBottom = RootCanvas.Bounds.Height;
-
-            // Calculate the positions for each button
-            var topLeftX = selectionX - TopLeftButton.Width / 2;
-            var topLeftY = selectionY - TopLeftButton.Height / 2;
-
-            var topRightX = selectionX + selectionWidth - TopRightButton.Width / 2;
-            var topRightY = selectionY - TopRightButton.Height / 2;
-
-            var topMiddleX = selectionX + selectionWidth / 2 - TopMiddleButton.Width / 2;
-            var topMiddleY = selectionY - TopMiddleButton.Height / 2;
-
-            var bottomLeftX = selectionX - BottomLeftButton.Width / 2;
-            var bottomLeftY = selectionY + selectionHeight - BottomLeftButton.Height / 2;
-
-            var bottomRightX = selectionX + selectionWidth - BottomRightButton.Width / 2;
-            var bottomRightY = selectionY + selectionHeight - BottomRightButton.Height / 2;
-
-            var bottomMiddleX = selectionX + selectionWidth / 2 - BottomMiddleButton.Width / 2;
-            var bottomMiddleY = selectionY + selectionHeight - BottomMiddleButton.Height / 2;
-
-            var leftMiddleX = selectionX - LeftMiddleButton.Width / 2;
-            var leftMiddleY = selectionY + selectionHeight / 2 - LeftMiddleButton.Height / 2;
-
-            var rightMiddleX = selectionX + selectionWidth - RightMiddleButton.Width / 2;
-            var rightMiddleY = selectionY + selectionHeight / 2 - RightMiddleButton.Height / 2;
-
-            // Ensure buttons stay within RootCanvas bounds (by clamping positions)
-            topLeftX = Math.Max(rootCanvasLeft, Math.Min(rootCanvasRight - TopLeftButton.Width, topLeftX));
-            topLeftY = Math.Max(rootCanvasTop, Math.Min(rootCanvasBottom - TopLeftButton.Height, topLeftY));
-
-            topRightX = Math.Max(rootCanvasLeft, Math.Min(rootCanvasRight - TopRightButton.Width, topRightX));
-            topRightY = Math.Max(rootCanvasTop, Math.Min(rootCanvasBottom - TopRightButton.Height, topRightY));
-
-            topMiddleX = Math.Max(rootCanvasLeft, Math.Min(rootCanvasRight - TopMiddleButton.Width, topMiddleX));
-            topMiddleY = Math.Max(rootCanvasTop, Math.Min(rootCanvasBottom - TopMiddleButton.Height, topMiddleY));
-
-            bottomLeftX = Math.Max(rootCanvasLeft, Math.Min(rootCanvasRight - BottomLeftButton.Width, bottomLeftX));
-            bottomLeftY = Math.Max(rootCanvasTop, Math.Min(rootCanvasBottom - BottomLeftButton.Height, bottomLeftY));
-
-            bottomRightX = Math.Max(rootCanvasLeft, Math.Min(rootCanvasRight - BottomRightButton.Width, bottomRightX));
-            bottomRightY = Math.Max(rootCanvasTop, Math.Min(rootCanvasBottom - BottomRightButton.Height, bottomRightY));
-
-            bottomMiddleX = Math.Max(rootCanvasLeft,
-                Math.Min(rootCanvasRight - BottomMiddleButton.Width, bottomMiddleX));
-            bottomMiddleY = Math.Max(rootCanvasTop,
-                Math.Min(rootCanvasBottom - BottomMiddleButton.Height, bottomMiddleY));
-
-            leftMiddleX = Math.Max(rootCanvasLeft, Math.Min(rootCanvasRight - LeftMiddleButton.Width, leftMiddleX));
-            leftMiddleY = Math.Max(rootCanvasTop, Math.Min(rootCanvasBottom - LeftMiddleButton.Height, leftMiddleY));
-
-            rightMiddleX = Math.Max(rootCanvasLeft, Math.Min(rootCanvasRight - RightMiddleButton.Width, rightMiddleX));
-            rightMiddleY = Math.Max(rootCanvasTop, Math.Min(rootCanvasBottom - RightMiddleButton.Height, rightMiddleY));
-
-            // Set the final button positions
-            Canvas.SetLeft(TopLeftButton, topLeftX);
-            Canvas.SetTop(TopLeftButton, topLeftY);
-
-            Canvas.SetLeft(TopRightButton, topRightX);
-            Canvas.SetTop(TopRightButton, topRightY);
-
-            Canvas.SetLeft(TopMiddleButton, topMiddleX);
-            Canvas.SetTop(TopMiddleButton, topMiddleY);
-
-            Canvas.SetLeft(BottomLeftButton, bottomLeftX);
-            Canvas.SetTop(BottomLeftButton, bottomLeftY);
-
-            Canvas.SetLeft(BottomRightButton, bottomRightX);
-            Canvas.SetTop(BottomRightButton, bottomRightY);
-
-            Canvas.SetLeft(BottomMiddleButton, bottomMiddleX);
-            Canvas.SetTop(BottomMiddleButton, bottomMiddleY);
-
-            Canvas.SetLeft(LeftMiddleButton, leftMiddleX);
-            Canvas.SetTop(LeftMiddleButton, leftMiddleY);
-
-            Canvas.SetLeft(RightMiddleButton, rightMiddleX);
-            Canvas.SetTop(RightMiddleButton, rightMiddleY);
-            
-            Canvas.SetLeft(SizeBorder, topLeftX + TopLeftButton .Bounds.Width + 2);
-            if (topLeftY != 0)
-            {
-                Canvas.SetTop(SizeBorder, topLeftY - TopLeftButton.Bounds.Height);
-            }
-            else
-            {
-                Canvas.SetTop(SizeBorder, topLeftY);
-            }
-            
-        }
-        catch (Exception e)
-        {
-#if DEBUG
-            Console.WriteLine(e);
-#endif
-        }
-    }
-
-    private void UpdateSurroundingRectangles()
-    {
-        if (DataContext is not ImageCropperViewModel vm)
-        {
-            return;
-        }
-
-        // Converting to int fixes black border
-        var left = Convert.ToInt32(Canvas.GetLeft(MainRectangle));
-        var top = Convert.ToInt32(Canvas.GetTop(MainRectangle));
-        var right = Convert.ToInt32(left + vm.SelectionWidth);
-        var bottom = Convert.ToInt32(top + vm.SelectionHeight);
-
-        // Calculate the positions and sizes for the surrounding rectangles
-        // Top Rectangle (above MainRectangle)
-        TopRectangle.Width = vm.ImageWidth;
-        TopRectangle.Height = top < 0 ? 0 : top;
-        Canvas.SetTop(TopRectangle, 0);
-
-        // Bottom Rectangle (below MainRectangle)
-        BottomRectangle.Width = vm.ImageWidth;
-        var newBottomRectangleHeight = vm.ImageHeight - bottom < 0 ? 0 : vm.ImageHeight - bottom;
-        BottomRectangle.Height = newBottomRectangleHeight;
-        Canvas.SetTop(BottomRectangle, bottom);
-
-        // Left Rectangle (left of MainRectangle)
-        LeftRectangle.Width = left < 0 ? 0 : left;
-        LeftRectangle.Height = vm.SelectionHeight;
-        Canvas.SetLeft(LeftRectangle, 0);
-        Canvas.SetTop(LeftRectangle, top);
-
-        // Right Rectangle (right of MainRectangle)
-        var newRightRectangleWidth = vm.ImageWidth - right < 0 ? 0 : vm.ImageWidth - right;
-        RightRectangle.Width = newRightRectangleWidth;
-        RightRectangle.Height = vm.SelectionHeight;
-        Canvas.SetLeft(RightRectangle, right);
-        Canvas.SetTop(RightRectangle, top);
-    }
-
-    private void OnPointerPressed(object? sender, PointerPressedEventArgs e)
-    {
-        if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
-        {
-            return;
-        }
-
-        if (DataContext is not ImageCropperViewModel vm)
-        {
-            return;
-        }
-
-        _dragStart = e.GetPosition(RootCanvas); // Make sure to get position relative to RootCanvas
-
-        // Get current left and top values; ensure they are initialized
-        var currentLeft = Canvas.GetLeft(MainRectangle);
-        var currentTop = Canvas.GetTop(MainRectangle);
-
-        // Set default values if NaN
-        if (double.IsNaN(currentLeft))
-        {
-            currentLeft = 0;
-        }
-
-        if (double.IsNaN(currentTop))
-        {
-            currentTop = 0;
-        }
-
-        _originalRect = new Rect(currentLeft, currentTop, vm.SelectionWidth, vm.SelectionHeight);
-        _isDragging = true;
-    }
-
-    private void OnPointerMoved(object? sender, PointerEventArgs e)
-    {
-        if (DataContext is not ImageCropperViewModel vm)
-        {
-            return;
-        }
-
-        if (!_isDragging)
-        {
-            return;
-        }
-
-        var currentPos = e.GetPosition(RootCanvas); // Ensure it's relative to RootCanvas
-        var delta = currentPos - _dragStart;
-
-        // Calculate new left and top positions, ensure _originalRect is valid
-        var newLeft = _originalRect.X + delta.X;
-        var newTop = _originalRect.Y + delta.Y;
-
-        // Clamp the newLeft and newTop values to keep the rectangle within bounds
-        newLeft = Math.Max(0, Math.Min(vm.ImageWidth - vm.SelectionWidth, newLeft));
-        newTop = Math.Max(0, Math.Min(vm.ImageHeight - vm.SelectionHeight, newTop));
-
-        // Only proceed if new positions are valid (i.e., not NaN)
-        if (double.IsNaN(newLeft) || double.IsNaN(newTop))
-        {
-            return;
-        }
-
-        // Update the main rectangle's position
-        Canvas.SetLeft(MainRectangle, newLeft);
-        Canvas.SetTop(MainRectangle, newTop);
-        
-        Canvas.SetLeft(SizeBorder, newLeft + 11);
-        Canvas.SetTop(SizeBorder, newTop - SizeBorder.Bounds.Height - 3);
-
-        // Update view model values
-        vm.SelectionX = Convert.ToInt32(newLeft);
-        vm.SelectionY = Convert.ToInt32(newTop);
-
-        // Update the surrounding rectangles to fill the space
-        try
-        {
-            UpdateSurroundingRectangles();
-            UpdateButtonPositions(vm.SelectionX, vm.SelectionY, vm.SelectionWidth, vm.SelectionHeight);
-        }
-        catch (Exception exception)
-        {
-#if DEBUG
-            Console.WriteLine(exception);
-#endif
-        }
-    }
-
 
-    private void OnPointerReleased(object? sender, PointerReleasedEventArgs e)
-    {
-        _isDragging = false;
-    }
-
-    private void ResizeTopLeft(PointerEventArgs e)
-{
-    if (!_isResizing || DataContext is not ImageCropperViewModel vm)
-    {
-        return;
+        Loaded += OnControlLoaded;
+        LostFocus += OnControlLostFocus;
     }
 
-    var currentPos = e.GetPosition(RootCanvas);
-    var delta = currentPos - _resizeStart;
-
-    var newWidth = _originalRect.Width - delta.X;
-    var newHeight = _originalRect.Height - delta.Y;
-    var newLeft = _originalRect.X + delta.X;
-    var newTop = _originalRect.Y + delta.Y;
-
-    // If shift is pressed, maintain square aspect ratio
-    if (MainKeyboardShortcuts.ShiftDown)
+    private void OnControlLoaded(object? sender, RoutedEventArgs e)
     {
-        // Use the larger dimension to determine the square size
-        var size = Math.Max(newWidth, newHeight);
-        // Calculate adjustments needed to maintain right and bottom edges
-        var widthDiff = size - newWidth;
-        var heightDiff = size - newHeight;
-        newLeft -= widthDiff;
-        newTop -= heightDiff;
-        newWidth = size;
-        newHeight = size;
+        InitializeResizeHandlers();
+        _layoutManager.InitializeLayout();
     }
 
-    // Ensure we don't go beyond boundaries
-    if (newLeft < 0)
+    private void OnControlLostFocus(object? sender, RoutedEventArgs e)
     {
-        var adjustment = -newLeft;
-        newLeft = 0;
-        newWidth -= adjustment;
-        if (MainKeyboardShortcuts.ShiftDown)
-        {
-            newHeight = newWidth;
-            newTop = _originalRect.Y + (_originalRect.Height - newHeight);
-        }
+        _dragHandler.Reset();
+        _resizeHandler.Reset();
     }
 
-    if (newTop < 0)
+    private void InitializeResizeHandlers()
     {
-        var adjustment = -newTop;
-        newTop = 0;
-        newHeight -= adjustment;
-        if (MainKeyboardShortcuts.ShiftDown)
+        var resizeControls = new Dictionary<Border, CropResizeMode>
         {
-            newWidth = newHeight;
-            newLeft = _originalRect.X + (_originalRect.Width - newWidth);
-        }
-    }
-
-    // Prevent the size from becoming too small
-    newHeight = Math.Max(newHeight, 1);
-    newWidth = Math.Max(newWidth, 1);
-
-    // Apply the new size and position
-    vm.SelectionX = Convert.ToInt32(newLeft);
-    vm.SelectionY = Convert.ToInt32(newTop);
-    vm.SelectionWidth = newWidth;
-    vm.SelectionHeight = newHeight;
-    Canvas.SetLeft(MainRectangle, newLeft);
-    Canvas.SetTop(MainRectangle, newTop);
-
-    UpdateButtonPositions(vm.SelectionX, vm.SelectionY, vm.SelectionWidth, vm.SelectionHeight);
-    UpdateSurroundingRectangles();
-}
-
-    private void ResizeTopRight(PointerEventArgs e)
-    {
-        if (!_isResizing || DataContext is not ImageCropperViewModel vm)
-        {
-            return;
-        }
-
-        var currentPos = e.GetPosition(RootCanvas);
-        var delta = currentPos - _resizeStart;
-
-        var newWidth = _originalRect.Width + delta.X;
-        var newHeight = _originalRect.Height - delta.Y;
-        var newY = _originalRect.Y + delta.Y;
-
-        // If shift is pressed, maintain square aspect ratio
-        if (MainKeyboardShortcuts.ShiftDown)
-        {
-            // Use the larger dimension to determine the square size
-            var size = Math.Max(newWidth, newHeight);
-            // Calculate how much to adjust Y to maintain the bottom edge
-            var heightDiff = size - newHeight;
-            newY -= heightDiff;
-            newWidth = size;
-            newHeight = size;
-        }
-
-        // Ensure the width doesn't exceed the canvas' right edge
-        if (_originalRect.X + newWidth > vm.ImageWidth)
-        {
-            newWidth = vm.ImageWidth - _originalRect.X;
-            if (MainKeyboardShortcuts.ShiftDown)
-            {
-                newHeight = newWidth;
-                newY = _originalRect.Y + (_originalRect.Height - newHeight);
-            }
-        }
-
-        // Ensure the top doesn't move above the top edge of the canvas
-        if (newY < 0)
-        {
-            var adjustment = -newY;
-            newY = 0;
-            newHeight = _originalRect.Height + adjustment;
-            if (MainKeyboardShortcuts.ShiftDown)
-            {
-                newWidth = newHeight;
-            }
-        }
-
-        // Prevent the size from becoming too small
-        newHeight = Math.Max(newHeight, 1);
-        newWidth = Math.Max(newWidth, 1);
-
-        // Apply the new size and position
-        vm.SelectionY = Convert.ToInt32(newY);
-        vm.SelectionWidth = newWidth;
-        vm.SelectionHeight = newHeight;
-        Canvas.SetLeft(MainRectangle, _originalRect.X);
-        Canvas.SetTop(MainRectangle, newY);
-
-        UpdateButtonPositions(_originalRect.X, newY, newWidth, newHeight);
-        UpdateSurroundingRectangles();
-    }
-
-
-    private void ResizeBottomLeft(PointerEventArgs e)
-    {
-        if (!_isResizing || DataContext is not ImageCropperViewModel vm)
-        {
-            return;
-        }
-
-        var currentPos = e.GetPosition(RootCanvas);
-        var delta = currentPos - _resizeStart;
-
-        var newWidth = _originalRect.Width - delta.X;
-        var newHeight = _originalRect.Height + delta.Y;
-        var newX = _originalRect.X + delta.X;
-
-        // If shift is pressed, maintain square aspect ratio
-        if (MainKeyboardShortcuts.ShiftDown)
-        {
-            // Use the larger dimension to determine the square size
-            var size = Math.Max(newWidth, newHeight);
-            // Calculate how much to adjust X to maintain the right edge
-            var widthDiff = size - newWidth;
-            newX -= widthDiff;
-            newWidth = size;
-            newHeight = size;
-        }
-
-        // Ensure the left doesn't move beyond the left edge
-        if (newX < 0)
-        {
-            var adjustment = -newX;
-            newX = 0;
-            newWidth -= adjustment;
-            if (MainKeyboardShortcuts.ShiftDown)
-            {
-                newHeight = newWidth;
-            }
-        }
-
-        // Ensure the height doesn't exceed the canvas' bottom edge
-        if (_originalRect.Y + newHeight > vm.ImageHeight)
-        {
-            newHeight = vm.ImageHeight - _originalRect.Y;
-            if (MainKeyboardShortcuts.ShiftDown)
-            {
-                newWidth = newHeight;
-                newX = _originalRect.X + (_originalRect.Width - newWidth);
-            }
-        }
-
-        // Prevent the width and height from becoming too small
-        newWidth = Math.Max(newWidth, 1);
-        newHeight = Math.Max(newHeight, 1);
-
-        // Apply the new size and position
-        vm.SelectionX = Convert.ToInt32(newX);
-        vm.SelectionWidth = newWidth;
-        vm.SelectionHeight = newHeight;
-        Canvas.SetLeft(MainRectangle, newX);
-        Canvas.SetTop(MainRectangle, _originalRect.Y);
-
-        UpdateButtonPositions(newX, _originalRect.Y, newWidth, newHeight);
-        UpdateSurroundingRectangles();
-    }
-
-
-    private void ResizeBottomRight(PointerEventArgs e)
-    {
-        if (!_isResizing || DataContext is not ImageCropperViewModel vm)
-        {
-            return;
-        }
-
-        var currentPos = e.GetPosition(RootCanvas);
-        var delta = currentPos - _resizeStart;
-
-        // Calculate the new width and height based on the drag delta
-        var newWidth = _originalRect.Width + delta.X;
-        var newHeight = _originalRect.Height + delta.Y;
-
-        // If shift is pressed, maintain square aspect ratio
-        if (MainKeyboardShortcuts.ShiftDown)
-        {
-            // Use the larger of the two dimensions to maintain square shape
-            var size = Math.Max(newWidth, newHeight);
-            newWidth = size;
-            newHeight = size;
-        }
-
-        // Ensure the new width and height do not exceed the image bounds
-        var newRight = _originalRect.X + newWidth;
-        var newBottom = _originalRect.Y + newHeight;
-
-        if (newRight > vm.ImageWidth)
-        {
-            newWidth = vm.ImageWidth - _originalRect.X;
-            if (MainKeyboardShortcuts.ShiftDown)
-            {
-                newHeight = newWidth;
-            }
-        }
-
-        if (newBottom > vm.ImageHeight)
-        {
-            newHeight = vm.ImageHeight - _originalRect.Y;
-            if (MainKeyboardShortcuts.ShiftDown)
-            {
-                newWidth = newHeight;
-            }
-        }
-
-        // Constrain the minimum size
-        newWidth = Math.Max(newWidth, 1);
-        newHeight = Math.Max(newHeight, 1);
-
-        // Apply the new width and height
-        vm.SelectionWidth = newWidth;
-        vm.SelectionHeight = newHeight;
-
-        UpdateButtonPositions(vm.SelectionX, vm.SelectionY, vm.SelectionWidth, vm.SelectionHeight);
-        UpdateSurroundingRectangles();
-    }
-
-    private void ResizeLeftMiddle(PointerEventArgs e)
-    {
-        if (!_isResizing || DataContext is not ImageCropperViewModel vm)
-        {
-            return;
-        }
-
-        // Get the current mouse position relative to RootCanvas
-        var currentPos = e.GetPosition(RootCanvas);
-        var delta = currentPos - _resizeStart;
-
-        // Calculate the new X position
-        var newX = _originalRect.X + delta.X;
-
-        // Calculate the new width based on horizontal movement
-        var newWidth = _originalRect.Width - delta.X;
-
-        // Ensure that the rectangle doesn't go beyond the right boundary
-        if (newX < 0)
-        {
-            newWidth = _originalRect.Width + _originalRect.X;
-            newX = 0;
-        }
-
-        // Ensure the new width doesn't go negative or too small
-        newWidth = Math.Max(newWidth, 1);
-
-        // Update the view model with the new X position and width
-        vm.SelectionX = Convert.ToInt32(newX);
-        vm.SelectionWidth = newWidth;
-
-        // Update the rectangle on the canvas
-        Canvas.SetLeft(MainRectangle, newX);
-        Canvas.SetTop(MainRectangle, _originalRect.Y);
-
-        // Update buttons and surrounding rectangles
-        UpdateButtonPositions(newX, _originalRect.Y, vm.SelectionWidth, vm.SelectionHeight);
-        UpdateSurroundingRectangles();
-    }
-
-
-    private void ResizeRightMiddle(PointerEventArgs e)
-    {
-        if (!_isResizing || DataContext is not ImageCropperViewModel vm)
-        {
-            return;
-        }
-
-        // Get the current mouse position relative to RootCanvas
-        var currentPos = e.GetPosition(RootCanvas);
-        var delta = currentPos - _resizeStart;
-
-        // Calculate the new width based on horizontal movement
-        var newWidth = _originalRect.Width + delta.X;
-
-        // Ensure the new width doesn't go beyond the bounds of the RootCanvas
-        if (_originalRect.X + newWidth > vm.ImageWidth)
-        {
-            newWidth = vm.ImageWidth - _originalRect.X;
-        }
-
-        // Constrain the width to a minimum value (e.g., 1 to prevent zero or negative width)
-        newWidth = Math.Max(newWidth, 1);
-
-        // Update the view model with the new width
-        vm.SelectionWidth = newWidth;
-
-        // Update the rectangle on the canvas
-        Canvas.SetLeft(MainRectangle, _originalRect.X);
-        Canvas.SetTop(MainRectangle, _originalRect.Y);
-
-        // Update buttons and surrounding rectangles
-        UpdateButtonPositions(_originalRect.X, _originalRect.Y, vm.SelectionWidth, vm.SelectionHeight);
-        UpdateSurroundingRectangles();
-    }
-
-    private void ResizeTopMiddle(PointerEventArgs e)
-    {
-        if (!_isResizing || DataContext is not ImageCropperViewModel vm)
-        {
-            return;
-        }
-
-        // Get the current mouse position relative to RootCanvas
-        var currentPos = e.GetPosition(RootCanvas);
-        var delta = currentPos - _resizeStart;
-
-        // Calculate the new top position and height
-        var newTop = _originalRect.Y + delta.Y;
-        var newHeight = _originalRect.Height - delta.Y;
-
-        // Ensure the new height doesn't go negative or too small
-        if (newHeight < 1)
-        {
-            newHeight = 1;
-            newTop = _originalRect.Y + (_originalRect.Height - 1); // Adjust top to preserve min height
-        }
-
-        // Ensure the top edge doesn't go beyond the canvas bounds
-        if (newTop < 0)
-        {
-            newTop = 0;
-            newHeight = _originalRect.Height + _originalRect.Y; // Adjust height to compensate
-        }
-        
-        // Prevent the size from becoming too small
-        newHeight = Math.Max(newHeight, 1);
-
-        // Update the view model with the new top and height
-        vm.SelectionHeight = newHeight;
-        vm.SelectionY = Convert.ToInt32(newTop);
-
-        // Update the rectangle on the canvas
-        Canvas.SetLeft(MainRectangle, _originalRect.X);
-        Canvas.SetTop(MainRectangle, newTop);
-
-        // Update buttons and surrounding rectangles
-        UpdateButtonPositions(_originalRect.X, newTop, vm.SelectionWidth, newHeight);
-        UpdateSurroundingRectangles();
-    }
-
-
-    private void ResizeBottomMiddle(PointerEventArgs e)
-    {
-        if (!_isResizing || DataContext is not ImageCropperViewModel vm)
-        {
-            return;
-        }
-
-        // Get the current mouse position relative to RootCanvas
-        var currentPos = e.GetPosition(RootCanvas);
-        var delta = currentPos - _resizeStart;
-
-        // Calculate the new height based on vertical movement
-        var newHeight = _originalRect.Height + delta.Y;
-
-        // Ensure the new height doesn't go negative or too small
-        newHeight = Math.Max(newHeight, 1);
+            { TopLeftButton, CropResizeMode.TopLeft },
+            { TopRightButton, CropResizeMode.TopRight },
+            { BottomLeftButton, CropResizeMode.BottomLeft },
+            { BottomRightButton, CropResizeMode.BottomRight },
+            { LeftMiddleButton, CropResizeMode.Left },
+            { RightMiddleButton, CropResizeMode.Right },
+            { TopMiddleButton, CropResizeMode.Top },
+            { BottomMiddleButton, CropResizeMode.Bottom }
+        };
 
-        // Ensure the bottom edge doesn't go beyond the canvas bounds
-        if (_originalRect.Y + newHeight > RootCanvas.Bounds.Height)
+        foreach (var control in resizeControls)
         {
-            newHeight = RootCanvas.Bounds.Height - _originalRect.Y;
+            control.Key.PointerPressed += (_, e) => _resizeHandler.OnResizeStart(e);
+            control.Key.PointerMoved += (s, e) => _resizeHandler.OnResizeMove(s, e, control.Value);
+            control.Key.PointerMoved += (s, e) => _layoutManager.UpdateLayout();
+            control.Key.PointerReleased += _resizeHandler.OnResizeEnd;
         }
-        
-        // Prevent the size from becoming too small
-        newHeight = Math.Max(newHeight, 1);
-        
-        // Update the view model with the new height
-        vm.SelectionHeight = newHeight;
-
-        // Update the rectangle on the canvas
-        Canvas.SetLeft(MainRectangle, _originalRect.X);
-        Canvas.SetTop(MainRectangle, _originalRect.Y);
-
-        // Update buttons and surrounding rectangles
-        UpdateButtonPositions(_originalRect.X, _originalRect.Y, vm.SelectionWidth, newHeight);
-        UpdateSurroundingRectangles();
     }
 
-    private void OnResizePointerReleased(object? sender, PointerReleasedEventArgs e)
+    public async Task KeyDownHandler(object? sender, KeyEventArgs e)
     {
-        _isResizing = false;
+        await _keyboardManager.KeyDownHandler(e).ConfigureAwait(false);
     }
 }