浏览代码

avalonia: Added ZoomPanel

Kenric Nugteren 3 周之前
父节点
当前提交
f961d8913a

+ 155 - 0
InABox.Avalonia/Components/ZoomPanel/ZoomPanel.cs

@@ -0,0 +1,155 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.Metadata;
+using Avalonia.Controls.Primitives;
+using Avalonia.Input;
+using Avalonia.Interactivity;
+using Avalonia.Layout;
+using Avalonia.Markup.Xaml;
+using Avalonia.Media;
+using Avalonia.Metadata;
+
+namespace InABox.Avalonia.Components;
+
+[TemplatePart("PART_ZoomContent", typeof(ContentControl))]
+[TemplatePart("PART_ZoomCanvas", typeof(Canvas))]
+[TemplatePart("PART_ZoomContentBorder", typeof(Border))]
+public partial class ZoomPanel : TemplatedControl
+{
+    public static readonly StyledProperty<Layoutable?> ContentProperty =
+        AvaloniaProperty.Register<ZoomPanel, Layoutable?>(nameof(Content));
+
+    [Content]
+    public Layoutable? Content
+    {
+        get => GetValue(ContentProperty);
+        set => SetValue(ContentProperty, value);
+    }
+
+    private double ContentWidth => Content?.Width ?? 1;
+
+    private double ContentHeight => Content?.Height ?? 1;
+
+    private Canvas OuterCanvas = null!;
+    private ContentControl ZoomContent = null!;
+    private Border ZoomContentBorder = null!;
+
+    private double ScaleFactor = 1.0;
+    private double _originalScaleFactor = 1.0;
+
+    private const double _wheelSpeed = 0.1;
+    private const double _panSpeed = 30;
+
+    // Center of the image.
+    private Point ContentCentre = new();
+
+    protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
+    {
+        base.OnApplyTemplate(e);
+
+        OuterCanvas = e.NameScope.Get<Canvas>("PART_ZoomCanvas");
+        ZoomContent = e.NameScope.Get<ContentControl>("PART_ZoomContent");
+        ZoomContentBorder = e.NameScope.Get<Border>("PART_ZoomContentBorder");
+
+        OuterCanvas.LayoutUpdated += OuterCanvas_LayoutUpdated;
+        OuterCanvas.AddHandler(PanAndZoomGestureRecognizer.PanAndZoomEndedEvent, OuterCanvas_PinchEnded);
+        OuterCanvas.AddHandler(PanAndZoomGestureRecognizer.PanAndZoomEvent, OuterCanvas_Pinch);
+
+        OuterCanvas.PointerWheelChanged += OuterCanvas_PointerWheelChanged;
+    }
+
+    private void OuterCanvas_PinchEnded(object? sender, PanAndZoomEndedEventArgs e)
+    {
+        _originalScaleFactor = ScaleFactor;
+    }
+
+    private void OuterCanvas_Pinch(object? sender, PanAndZoomEventArgs e)
+    {
+        Zoom(e.ScaleOrigin - e.Pan, e.ScaleOrigin, _originalScaleFactor * e.Scale);
+    }
+
+    private void Zoom(Point originalOrigin, Point newOrigin, double newScaleFactor)
+    {
+        // Convert Scale Origin to image coordinates (relative to center).
+        // Work out where this position will move to under the new scaling.
+        // Adjust so that these are the same.
+
+        var pos = originalOrigin - ContentCentre;
+        var contentMPos = pos / ScaleFactor;
+
+        ScaleFactor = newScaleFactor;
+
+        var scaledPos = ContentCentre + contentMPos * ScaleFactor;
+        var offset = scaledPos - newOrigin;
+
+        ContentCentre -= offset;
+        UpdateCanvasPosition();
+    }
+
+    private void Pan(double x, double y)
+    {
+        ContentCentre += new Vector(x, y);
+        UpdateCanvasPosition();
+    }
+
+    private void OuterCanvas_PointerWheelChanged(object? sender, PointerWheelEventArgs e)
+    {
+        if (e.KeyModifiers.HasFlag(KeyModifiers.Control))
+        {
+            var pos = e.GetPosition(OuterCanvas);
+            var wheelSpeed = _wheelSpeed;
+            Zoom(pos, pos, e.Delta.Y > 0 ? ScaleFactor * (1 + e.Delta.Y * wheelSpeed) : ScaleFactor / (1 + (-e.Delta.Y) * wheelSpeed));
+        }
+        else if(e.KeyModifiers.HasFlag(KeyModifiers.Shift))
+        {
+            Pan(e.Delta.Y * _panSpeed, e.Delta.X * _panSpeed);
+        }
+        else
+        {
+            Pan(e.Delta.X * _panSpeed, e.Delta.Y * _panSpeed);
+        }
+    }
+
+    private void OuterCanvas_LayoutUpdated(object? sender, EventArgs e)
+    {
+        // PositionImage();
+    }
+
+    protected override void OnLoaded(RoutedEventArgs e)
+    {
+        base.OnLoaded(e);
+        PositionContent();
+    }
+
+    private void PositionContent()
+    {
+        var canvasWidth = OuterCanvas.Bounds.Width;
+        var canvasHeight = OuterCanvas.Bounds.Height;
+
+        var scaleFactor = Math.Min(canvasWidth / ContentWidth, canvasHeight / ContentHeight);
+        ScaleFactor = scaleFactor;
+        _originalScaleFactor = ScaleFactor;
+
+        ContentCentre = new Point(
+            OuterCanvas.Bounds.Width / 2,
+            OuterCanvas.Bounds.Height / 2);
+
+        UpdateCanvasPosition();
+    }
+
+    private void UpdateCanvasPosition()
+    {
+        var imageWidth = ContentWidth * ScaleFactor;
+        var imageHeight = ContentHeight * ScaleFactor;
+
+        ZoomContentBorder.Width = imageWidth;
+        ZoomContentBorder.Height = imageHeight;
+
+        Canvas.SetLeft(ZoomContentBorder, ContentCentre.X - imageWidth / 2);
+        Canvas.SetTop(ZoomContentBorder, ContentCentre.Y - imageHeight / 2);
+
+        ZoomContent.RenderTransform = new ScaleTransform(ScaleFactor, ScaleFactor);
+        ZoomContent.Width = ContentWidth;
+        ZoomContent.Height = ContentHeight;
+    }
+}

+ 26 - 0
InABox.Avalonia/Theme/Classes/ZoomPanel.axaml

@@ -0,0 +1,26 @@
+<Styles xmlns="https://github.com/avaloniaui"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+		xmlns:components="using:InABox.Avalonia.Components">
+	<Style Selector="components|ZoomPanel">
+		<Setter Property="Template">
+			<ControlTemplate TargetType="components:ZoomPanel">
+				<Border Background="{TemplateBinding Background}"
+						BorderBrush="{TemplateBinding BorderBrush}"
+						BorderThickness="{TemplateBinding BorderThickness}"
+						CornerRadius="{TemplateBinding CornerRadius}"
+						Margin="{TemplateBinding Margin}"
+						Padding="{TemplateBinding Padding}">
+					<Canvas Name="PART_ZoomCanvas" Grid.Row="1"
+							Background="Transparent">
+						<Canvas.GestureRecognizers>
+							<components:PanAndZoomGestureRecognizer/>
+						</Canvas.GestureRecognizers>
+						<Border Name="PART_ZoomContentBorder">
+							<ContentControl Name="PART_ZoomContent" Content="{TemplateBinding Content}"/>
+						</Border>
+					</Canvas>
+				</Border>
+			</ControlTemplate>
+		</Setter>
+	</Style>
+</Styles>

+ 1 - 0
InABox.Avalonia/Theme/Styles.axaml

@@ -16,4 +16,5 @@
 	<StyleInclude Source="/Theme/Classes/TabStrip.axaml" />
 	<StyleInclude Source="/Theme/Classes/TextBox.axaml" />
 	<StyleInclude Source="/Theme/Classes/TimeSelectorButton.axaml" />
+	<StyleInclude Source="/Theme/Classes/ZoomPanel.axaml" />
 </Styles>