ZoomPanel.cs 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. using Avalonia;
  2. using Avalonia.Controls;
  3. using Avalonia.Controls.Metadata;
  4. using Avalonia.Controls.Primitives;
  5. using Avalonia.Input;
  6. using Avalonia.Interactivity;
  7. using Avalonia.Layout;
  8. using Avalonia.Markup.Xaml;
  9. using Avalonia.Media;
  10. using Avalonia.Metadata;
  11. namespace InABox.Avalonia.Components;
  12. [TemplatePart("PART_ZoomContent", typeof(ContentControl))]
  13. [TemplatePart("PART_ZoomCanvas", typeof(Canvas))]
  14. [TemplatePart("PART_ZoomContentBorder", typeof(Border))]
  15. public partial class ZoomPanel : TemplatedControl
  16. {
  17. public static readonly StyledProperty<Layoutable?> ContentProperty =
  18. AvaloniaProperty.Register<ZoomPanel, Layoutable?>(nameof(Content));
  19. [Content]
  20. public Layoutable? Content
  21. {
  22. get => GetValue(ContentProperty);
  23. set => SetValue(ContentProperty, value);
  24. }
  25. private double ContentWidth => Content?.Width ?? 1;
  26. private double ContentHeight => Content?.Height ?? 1;
  27. private Canvas OuterCanvas = null!;
  28. private ContentControl ZoomContent = null!;
  29. private Border ZoomContentBorder = null!;
  30. private double ScaleFactor = 1.0;
  31. private double _originalScaleFactor = 1.0;
  32. private const double _wheelSpeed = 0.1;
  33. private const double _panSpeed = 30;
  34. // Center of the image.
  35. private Point ContentCentre = new();
  36. protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
  37. {
  38. base.OnApplyTemplate(e);
  39. OuterCanvas = e.NameScope.Get<Canvas>("PART_ZoomCanvas");
  40. ZoomContent = e.NameScope.Get<ContentControl>("PART_ZoomContent");
  41. ZoomContentBorder = e.NameScope.Get<Border>("PART_ZoomContentBorder");
  42. OuterCanvas.LayoutUpdated += OuterCanvas_LayoutUpdated;
  43. OuterCanvas.AddHandler(PanAndZoomGestureRecognizer.PanAndZoomEndedEvent, OuterCanvas_PinchEnded);
  44. OuterCanvas.AddHandler(PanAndZoomGestureRecognizer.PanAndZoomEvent, OuterCanvas_Pinch);
  45. OuterCanvas.PointerWheelChanged += OuterCanvas_PointerWheelChanged;
  46. }
  47. private void OuterCanvas_PinchEnded(object? sender, PanAndZoomEndedEventArgs e)
  48. {
  49. _originalScaleFactor = ScaleFactor;
  50. }
  51. private void OuterCanvas_Pinch(object? sender, PanAndZoomEventArgs e)
  52. {
  53. Zoom(e.ScaleOrigin - e.Pan, e.ScaleOrigin, _originalScaleFactor * e.Scale);
  54. }
  55. private void Zoom(Point originalOrigin, Point newOrigin, double newScaleFactor)
  56. {
  57. // Convert Scale Origin to image coordinates (relative to center).
  58. // Work out where this position will move to under the new scaling.
  59. // Adjust so that these are the same.
  60. var pos = originalOrigin - ContentCentre;
  61. var contentMPos = pos / ScaleFactor;
  62. ScaleFactor = newScaleFactor;
  63. var scaledPos = ContentCentre + contentMPos * ScaleFactor;
  64. var offset = scaledPos - newOrigin;
  65. ContentCentre -= offset;
  66. UpdateCanvasPosition();
  67. }
  68. private void Pan(double x, double y)
  69. {
  70. ContentCentre += new Vector(x, y);
  71. UpdateCanvasPosition();
  72. }
  73. private void OuterCanvas_PointerWheelChanged(object? sender, PointerWheelEventArgs e)
  74. {
  75. if (e.KeyModifiers.HasFlag(KeyModifiers.Control))
  76. {
  77. var pos = e.GetPosition(OuterCanvas);
  78. var wheelSpeed = _wheelSpeed;
  79. Zoom(pos, pos, e.Delta.Y > 0 ? ScaleFactor * (1 + e.Delta.Y * wheelSpeed) : ScaleFactor / (1 + (-e.Delta.Y) * wheelSpeed));
  80. }
  81. else if(e.KeyModifiers.HasFlag(KeyModifiers.Shift))
  82. {
  83. Pan(e.Delta.Y * _panSpeed, e.Delta.X * _panSpeed);
  84. }
  85. else
  86. {
  87. Pan(e.Delta.X * _panSpeed, e.Delta.Y * _panSpeed);
  88. }
  89. }
  90. private void OuterCanvas_LayoutUpdated(object? sender, EventArgs e)
  91. {
  92. // PositionImage();
  93. }
  94. protected override void OnLoaded(RoutedEventArgs e)
  95. {
  96. base.OnLoaded(e);
  97. PositionContent();
  98. }
  99. private void PositionContent()
  100. {
  101. var canvasWidth = OuterCanvas.Bounds.Width;
  102. var canvasHeight = OuterCanvas.Bounds.Height;
  103. var scaleFactor = Math.Min(canvasWidth / ContentWidth, canvasHeight / ContentHeight);
  104. ScaleFactor = scaleFactor;
  105. _originalScaleFactor = ScaleFactor;
  106. ContentCentre = new Point(
  107. OuterCanvas.Bounds.Width / 2,
  108. OuterCanvas.Bounds.Height / 2);
  109. UpdateCanvasPosition();
  110. }
  111. private void UpdateCanvasPosition()
  112. {
  113. var imageWidth = ContentWidth * ScaleFactor;
  114. var imageHeight = ContentHeight * ScaleFactor;
  115. ZoomContentBorder.Width = imageWidth;
  116. ZoomContentBorder.Height = imageHeight;
  117. Canvas.SetLeft(ZoomContentBorder, ContentCentre.X - imageWidth / 2);
  118. Canvas.SetTop(ZoomContentBorder, ContentCentre.Y - imageHeight / 2);
  119. ZoomContent.RenderTransform = new ScaleTransform(ScaleFactor, ScaleFactor);
  120. ZoomContent.Width = ContentWidth;
  121. ZoomContent.Height = ContentHeight;
  122. }
  123. }