summaryrefslogtreecommitdiff
path: root/Views/Image/ImageControl.xaml.cs
diff options
context:
space:
mode:
Diffstat (limited to 'Views/Image/ImageControl.xaml.cs')
-rw-r--r--Views/Image/ImageControl.xaml.cs100
1 files changed, 100 insertions, 0 deletions
diff --git a/Views/Image/ImageControl.xaml.cs b/Views/Image/ImageControl.xaml.cs
new file mode 100644
index 0000000..2189bb0
--- /dev/null
+++ b/Views/Image/ImageControl.xaml.cs
@@ -0,0 +1,100 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Input;
+using Avalonia.Markup.Xaml;
+using Avalonia.Media;
+using System;
+
+namespace Avalar.Views.Image
+{
+ public class ImageControl : UserControl
+ {
+ private const double ZoomTick = 0.1;
+ private double CurrentZoom { get; set; } = 1.0;
+
+ private double InvCurrentZoom => 1 / CurrentZoom;
+
+ private ImageBrush Image { get; }
+
+ private Canvas ImageCanvas { get; }
+ public ImageControl()
+ {
+ InitializeComponent();
+
+ Image = this.FindResource("ImageBrush") as ImageBrush ?? throw new System.ArgumentException("ImageBrush");
+ ImageCanvas = this.FindControl<Canvas>("ImageCanvas");
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+
+ private Point MousePointerInImageSpace(Point mouseCoordinates)
+ {
+ var imageSize = Image.Source.Size;
+ var canvasSize = new Size(ImageCanvas.Bounds.Width, ImageCanvas.Bounds.Height);
+
+ double focalPointX;
+ double focalPointY;
+
+ if (canvasSize.AspectRatio > imageSize.AspectRatio)
+ {
+ focalPointY = mouseCoordinates.Y / canvasSize.Height;
+
+ var halfUnusedX = (canvasSize.AspectRatio - imageSize.AspectRatio) * canvasSize.Width / 2;
+ var clampedX = Math.Clamp(mouseCoordinates.X - halfUnusedX, 0, imageSize.Width);
+
+ focalPointX = clampedX / imageSize.Width;
+ }
+ else
+ {
+ focalPointX = mouseCoordinates.X / canvasSize.Width;
+
+ var halfUnusedY = 1 / (canvasSize.AspectRatio - imageSize.AspectRatio) * canvasSize.Height / 2;
+ var clampedY = Math.Clamp(mouseCoordinates.Y - halfUnusedY, 0, imageSize.Height);
+
+ focalPointY = clampedY / imageSize.Height;
+ }
+
+ return new Point(focalPointX, focalPointY);
+ }
+
+ private Point TransformToSourceRectSpace(Point from)
+ {
+ var sourceRectPosition = Image.SourceRect.Rect.Position;
+ var sourceRectSize = Image.SourceRect.Rect.Size;
+ return new Point(
+ from.X * sourceRectSize.Width + sourceRectPosition.X,
+ from.Y * sourceRectSize.Height + sourceRectPosition.Y
+ );
+ }
+
+ public void OnPointerWheelChanged(object sender, PointerWheelEventArgs e)
+ {
+ var invOldZoom = InvCurrentZoom;
+ var zoomFactor = e.Delta.Y * ZoomTick + 1.0;
+ CurrentZoom = Math.Clamp(CurrentZoom * zoomFactor, 1, double.PositiveInfinity);
+
+ var sourceRect = Image.SourceRect.Rect;
+
+ var canvasMouseCoordinates = e.GetPosition(ImageCanvas);
+
+ var currentCenter = sourceRect.Center;
+ var desiredCenterInImageCoords = MousePointerInImageSpace(canvasMouseCoordinates);
+ if(e.Delta.Y < 0)
+ {
+ desiredCenterInImageCoords = new Point(1 - desiredCenterInImageCoords.X, 1 - desiredCenterInImageCoords.Y);
+ }
+ var desiredCenter = TransformToSourceRectSpace(desiredCenterInImageCoords);
+
+ var maxTravelDistance = Math.Abs(invOldZoom - InvCurrentZoom);
+
+ var newCenterX = Math.Clamp(desiredCenter.X, currentCenter.X - maxTravelDistance, currentCenter.X + maxTravelDistance);
+ var newCenterY = Math.Clamp(desiredCenter.Y, currentCenter.Y - maxTravelDistance, currentCenter.Y + maxTravelDistance);
+
+ var sourceRectNewTopLeft = new Point(newCenterX - InvCurrentZoom / 2, newCenterY - InvCurrentZoom / 2);
+ Image.SourceRect = new RelativeRect(sourceRectNewTopLeft, new Size(InvCurrentZoom, InvCurrentZoom), RelativeUnit.Relative);
+ }
+ }
+}