From 0f98b398520a3f5cc6a299e7fd3b8fab494480d7 Mon Sep 17 00:00:00 2001 From: Dennis Brentjes Date: Sun, 15 Dec 2019 22:30:38 +0100 Subject: Initial commit, skeleton project. --- Views/AvalarViewService.cs | 32 +++++++++++++ Views/IAvalarViewService.cs | 9 ++++ Views/Image/ImageControl.xaml | 16 +++++++ Views/Image/ImageControl.xaml.cs | 100 +++++++++++++++++++++++++++++++++++++++ Views/Image/ImagePanel.xaml | 26 ++++++++++ Views/Image/ImagePanel.xaml.cs | 19 ++++++++ Views/Settings/Settings.xaml | 9 ++++ Views/Settings/Settings.xaml.cs | 18 +++++++ 8 files changed, 229 insertions(+) create mode 100644 Views/AvalarViewService.cs create mode 100644 Views/IAvalarViewService.cs create mode 100644 Views/Image/ImageControl.xaml create mode 100644 Views/Image/ImageControl.xaml.cs create mode 100644 Views/Image/ImagePanel.xaml create mode 100644 Views/Image/ImagePanel.xaml.cs create mode 100644 Views/Settings/Settings.xaml create mode 100644 Views/Settings/Settings.xaml.cs (limited to 'Views') diff --git a/Views/AvalarViewService.cs b/Views/AvalarViewService.cs new file mode 100644 index 0000000..994f88b --- /dev/null +++ b/Views/AvalarViewService.cs @@ -0,0 +1,32 @@ +using Avalonia.Controls; +using System.Threading.Tasks; + +namespace Avalar.Views +{ + class AvalarViewService : IAvalarViewService + { + private Window m_Window; + + public AvalarViewService(Window window) + { + m_Window = window; + } + + public async Task ShowOpenImageFileDialog() + { + var openFileDialog = new OpenFileDialog(); + + openFileDialog.AllowMultiple = false; + + var filter = new FileDialogFilter(); + filter.Extensions.Add("png"); + filter.Name = "png"; + openFileDialog.Filters.Add(filter); + + openFileDialog.Title = "Open Image"; + + var result = await openFileDialog.ShowAsync(m_Window); + return result; + } + } +} diff --git a/Views/IAvalarViewService.cs b/Views/IAvalarViewService.cs new file mode 100644 index 0000000..cb0c896 --- /dev/null +++ b/Views/IAvalarViewService.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Avalar.Views +{ + public interface IAvalarViewService + { + Task ShowOpenImageFileDialog(); + } +} \ No newline at end of file diff --git a/Views/Image/ImageControl.xaml b/Views/Image/ImageControl.xaml new file mode 100644 index 0000000..c1c176e --- /dev/null +++ b/Views/Image/ImageControl.xaml @@ -0,0 +1,16 @@ + + + + + + + + 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("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); + } + } +} diff --git a/Views/Image/ImagePanel.xaml b/Views/Image/ImagePanel.xaml new file mode 100644 index 0000000..f54458d --- /dev/null +++ b/Views/Image/ImagePanel.xaml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + diff --git a/Views/Image/ImagePanel.xaml.cs b/Views/Image/ImagePanel.xaml.cs new file mode 100644 index 0000000..bc18611 --- /dev/null +++ b/Views/Image/ImagePanel.xaml.cs @@ -0,0 +1,19 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace Avalar.Views.Image +{ + public class ImagePanel : UserControl + { + public ImagePanel() + { + this.InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/Views/Settings/Settings.xaml b/Views/Settings/Settings.xaml new file mode 100644 index 0000000..dfc8602 --- /dev/null +++ b/Views/Settings/Settings.xaml @@ -0,0 +1,9 @@ + + Welcome to Avalonia! + diff --git a/Views/Settings/Settings.xaml.cs b/Views/Settings/Settings.xaml.cs new file mode 100644 index 0000000..4504460 --- /dev/null +++ b/Views/Settings/Settings.xaml.cs @@ -0,0 +1,18 @@ +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace Avalar.Views.Settings +{ + public class Settings : UserControl + { + public Settings() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} \ No newline at end of file -- cgit v1.2.3-70-g09d2