diff --git a/GSS2.UI.Core/Helpers/PointHelper.cs b/GSS2.UI.Core/Helpers/PointHelper.cs new file mode 100644 index 0000000..6fd1673 --- /dev/null +++ b/GSS2.UI.Core/Helpers/PointHelper.cs @@ -0,0 +1,10 @@ +using Avalonia; + +namespace GSS2.Core.Extentions; + +public static class PointHelper +{ + public static Point Center(Point p1, Point p2) => new Point((p1.X + p2.X) / 2, (p1.Y + p2.Y) / 2); + public static Point Center(params Point[] ps) => new Point(ps.Average(p => p.X), ps.Average(p => p.Y)); +} + diff --git a/GSS2.UI.Core/OpenCvImage.cs b/GSS2.UI.Core/OpenCvImage.cs index ca3d332..1403998 100644 --- a/GSS2.UI.Core/OpenCvImage.cs +++ b/GSS2.UI.Core/OpenCvImage.cs @@ -7,6 +7,8 @@ using Avalonia.Media.Imaging; using Mat = OpenCvSharp.Mat; using Cv2 = OpenCvSharp.Cv2; +using GSS2.Core.Extentions; + namespace GSS2.UI.Core; public class OpenCvImage : Control @@ -25,7 +27,8 @@ public class OpenCvImage : Control { } } - private Point? _lastPosition = null; + private Point? _lastmousePosition = null; + private Dictionary? _lastTouchPositions = null; private float _zoom = 1; private float _panX = 0; private float _panY = 0; @@ -58,7 +61,14 @@ public class OpenCvImage : Control public OpenCvImage() : base() - { } + { + DoubleTapped += (_, _) => + { + _zoom = 1f; + _panX = 0f; + _panY = 0f; + }; + } protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { @@ -124,10 +134,6 @@ public class OpenCvImage : Control _panY = 0f; } } - else if (ea.Pointer.Type == PointerType.Touch) - { - Console.WriteLine(string.Format("PointerWheelChanged Type Touch {} {}", ea.GetPosition(this), ea.Delta)); - } base.OnPointerWheelChanged(ea); InvalidateVisual(); } @@ -144,12 +150,18 @@ public class OpenCvImage : Control _panY = 0f; } else - _lastPosition = ea.GetPosition(this); + _lastmousePosition = ea.GetPosition(this); } } else if (ea.Pointer.Type == PointerType.Touch) { + if (_lastTouchPositions is null) + _lastTouchPositions = new Dictionary(); + if (!_lastTouchPositions.ContainsKey(ea.Pointer.Id) && _lastTouchPositions.Count() < 2) + _lastTouchPositions.Add(ea.Pointer.Id, ea.GetPosition(this)); + else if (_lastTouchPositions.ContainsKey(ea.Pointer.Id)) + _lastTouchPositions[ea.Pointer.Id] = ea.GetPosition(this); } base.OnPointerPressed(ea); InvalidateVisual(); @@ -159,11 +171,12 @@ public class OpenCvImage : Control if (ea.Pointer.Type == PointerType.Mouse) { if (!ea.Properties.IsLeftButtonPressed) - _lastPosition = null; + _lastmousePosition = null; } else if (ea.Pointer.Type == PointerType.Touch) { - + if (_lastTouchPositions is not null && _lastTouchPositions.ContainsKey(ea.Pointer.Id)) + _lastTouchPositions.Remove(ea.Pointer.Id); } base.OnPointerReleased(ea); InvalidateVisual(); @@ -172,21 +185,75 @@ public class OpenCvImage : Control { if (ea.Pointer.Type == PointerType.Mouse) { - if (_lastPosition is null || _zoom == 1) // Если кнопка не нажата или зум == 1, то ничего не делаем + if (_lastmousePosition is null || _zoom == 1) // Если кнопка не нажата или зум == 1, то ничего не делаем { base.OnPointerMoved(ea); return; } var position = ea.GetPosition(this); - _panX -= (float)(_lastPosition.Value.X - position.X); - _panY -= (float)(_lastPosition.Value.Y - position.Y); - _lastPosition = position; - base.OnPointerMoved(ea); + _panX -= (float)(_lastmousePosition.Value.X - position.X); + _panY -= (float)(_lastmousePosition.Value.Y - position.Y); + _lastmousePosition = position; } else if (ea.Pointer.Type == PointerType.Touch) { + if (_lastTouchPositions is null) + { + base.OnPointerMoved(ea); + return; + } + if (!_lastTouchPositions.ContainsKey(ea.Pointer.Id) && _lastTouchPositions.Count() < 2) + { + _lastTouchPositions.Add(ea.Pointer.Id, ea.GetPosition(this)); + base.OnPointerMoved(ea); + return; + } + + if (_lastTouchPositions.Count() == 1 && _zoom != 1) // Если есть только одно прикосновение и зум != 1, то токлько перемещение + { + var position = ea.GetPosition(this); + _panX -= (float)(_lastTouchPositions[ea.Pointer.Id].X - position.X); + _panY -= (float)(_lastTouchPositions[ea.Pointer.Id].Y - position.Y); // Ось Y инвертированна + _lastTouchPositions[ea.Pointer.Id] = position; + } + else if (_lastTouchPositions.Count() == 2) // Если два прикосновения, то перемещение с приближением + { + var lastPosition1 = _lastTouchPositions.Skip(0).First().Value; + var lastPosition2 = _lastTouchPositions.Skip(1).First().Value; + var lastDistance = Point.Distance(lastPosition1, lastPosition2); + var lastCenter = PointHelper.Center(lastPosition1, lastPosition2); + + _lastTouchPositions[ea.Pointer.Id] = ea.GetPosition(this); + + var newPosition1 = _lastTouchPositions.Skip(0).First().Value; + var newPosition2 = _lastTouchPositions.Skip(1).First().Value; + var newDistance = Point.Distance(newPosition1, newPosition2); + var newCenter = PointHelper.Center(newPosition1, newPosition2); + + var zoomFactor = newDistance / lastDistance; + var newZoom = (float)(_zoom * zoomFactor); + newZoom = (float)Math.Clamp(newZoom, 1f, 10f); + + var imageX = (newCenter.X - Bounds.Width / 2 - _panX) / _zoom; + var imageY = (newCenter.Y - Bounds.Height / 2 - _panY) / _zoom; + + _zoom = newZoom; + + if (_zoom > 1f) + { + _panX = (float)(newCenter.X - Bounds.Width / 2 - imageX * _zoom); + _panY = (float)(newCenter.Y - Bounds.Height / 2 - imageY * _zoom); + } + else + { + _zoom = 1f; + _panX = 0f; + _panY = 0f; + } + } } + base.OnPointerMoved(ea); InvalidateVisual(); } diff --git a/GSS2.UI.Core/Views/CameraView.axaml.cs b/GSS2.UI.Core/Views/CameraView.axaml.cs index 9f8c69a..68085af 100644 --- a/GSS2.UI.Core/Views/CameraView.axaml.cs +++ b/GSS2.UI.Core/Views/CameraView.axaml.cs @@ -14,6 +14,7 @@ using Avalonia.Input; using GSS2.UI.Core.ViewModels; using Avalonia.Markup.Xaml; +using GSS2.Core.Extentions; namespace GSS2.UI.Core.Views; @@ -191,7 +192,8 @@ public partial class CameraView : OpenGlControlBase private readonly Dictionary _importedDmaBufers = new Dictionary(); private bool _isViewFinderStarted = false; private OpenGLContext? _openGLContext = null; - private Point? _lastPosition = null; + private Point? _lastMousePosition = null; + private Dictionary? _lastTouchPositions = null; private float _zoom = 1; private float _panX = 0; private float _panY = 0; @@ -220,6 +222,13 @@ public partial class CameraView : OpenGlControlBase _logger = logger; AvaloniaXamlLoader.Load(this); _logger.LogInformation("{} initialized", nameof(CameraView)); + + DoubleTapped += (_, _) => + { + _zoom = 1f; + _panX = 0f; + _panY = 0f; + }; } protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs ea) @@ -294,10 +303,6 @@ public partial class CameraView : OpenGlControlBase _panY = 0f; } } - else if (ea.Pointer.Type == PointerType.Touch) - { - _logger.LogInformation("PointerWheelChanged Type Touch {} {}", ea.GetPosition(this), ea.Delta); - } base.OnPointerWheelChanged(ea); } protected override void OnPointerPressed(PointerPressedEventArgs ea) @@ -313,12 +318,18 @@ public partial class CameraView : OpenGlControlBase _panY = 0f; } else - _lastPosition = ea.GetPosition(this); + _lastMousePosition = ea.GetPosition(this); } } else if (ea.Pointer.Type == PointerType.Touch) { - _logger.LogInformation("PointerPressed Type Touch {}", ea.GetPosition(this)); + if (_lastTouchPositions is null) + _lastTouchPositions = new Dictionary(); + + if (!_lastTouchPositions.ContainsKey(ea.Pointer.Id) && _lastTouchPositions.Count() < 2) + _lastTouchPositions.Add(ea.Pointer.Id, ea.GetPosition(this)); + else if (_lastTouchPositions.ContainsKey(ea.Pointer.Id)) + _lastTouchPositions[ea.Pointer.Id] = ea.GetPosition(this); } base.OnPointerPressed(ea); } @@ -327,11 +338,12 @@ public partial class CameraView : OpenGlControlBase if (ea.Pointer.Type == PointerType.Mouse) { if (!ea.Properties.IsLeftButtonPressed) - _lastPosition = null; + _lastMousePosition = null; } else if (ea.Pointer.Type == PointerType.Touch) { - _logger.LogInformation("PointerReleased Type Touch {}", ea.GetPosition(this)); + if (_lastTouchPositions is not null && _lastTouchPositions.ContainsKey(ea.Pointer.Id)) + _lastTouchPositions.Remove(ea.Pointer.Id); } base.OnPointerReleased(ea); } @@ -339,21 +351,75 @@ public partial class CameraView : OpenGlControlBase { if (ea.Pointer.Type == PointerType.Mouse) { - if (_lastPosition is null || _zoom == 1) // Если кнопка не нажата или зум == 1, то ничего не делаем + if (_lastMousePosition is null || _zoom == 1) // Если кнопка не нажата или зум == 1, то ничего не делаем { base.OnPointerMoved(ea); return; } var position = ea.GetPosition(this); - _panX -= (float)(_lastPosition.Value.X - position.X); - _panY += (float)(_lastPosition.Value.Y - position.Y); // Ось Y инвертированна - _lastPosition = position; - base.OnPointerMoved(ea); + _panX -= (float)(_lastMousePosition.Value.X - position.X); + _panY += (float)(_lastMousePosition.Value.Y - position.Y); // Ось Y инвертированна + _lastMousePosition = position; } else if (ea.Pointer.Type == PointerType.Touch) { - _logger.LogInformation("PointerMoved Type Touch {}", ea.GetPosition(this)); + if (_lastTouchPositions is null) + { + base.OnPointerMoved(ea); + return; + } + + if (!_lastTouchPositions.ContainsKey(ea.Pointer.Id) && _lastTouchPositions.Count() < 2) + { + _lastTouchPositions.Add(ea.Pointer.Id, ea.GetPosition(this)); + base.OnPointerMoved(ea); + return; + } + + if (_lastTouchPositions.Count() == 1 && _zoom != 1) // Если есть только одно прикосновение и зум != 1, то токлько перемещение + { + var position = ea.GetPosition(this); + _panX -= (float)(_lastTouchPositions[ea.Pointer.Id].X - position.X); + _panY += (float)(_lastTouchPositions[ea.Pointer.Id].Y - position.Y); // Ось Y инвертированна + _lastTouchPositions[ea.Pointer.Id] = position; + } + else if (_lastTouchPositions.Count() == 2) // Если два прикосновения, то перемещение с приближением + { + var lastPosition1 = _lastTouchPositions.Skip(0).First().Value; + var lastPosition2 = _lastTouchPositions.Skip(1).First().Value; + var lastDistance = Point.Distance(lastPosition1, lastPosition2); + var lastCenter = PointHelper.Center(lastPosition1, lastPosition2); + + _lastTouchPositions[ea.Pointer.Id] = ea.GetPosition(this); + + var newPosition1 = _lastTouchPositions.Skip(0).First().Value; + var newPosition2 = _lastTouchPositions.Skip(1).First().Value; + var newDistance = Point.Distance(newPosition1, newPosition2); + var newCenter = PointHelper.Center(newPosition1, newPosition2); + + var zoomFactor = newDistance / lastDistance; + var newZoom = (float)(_zoom * zoomFactor); + newZoom = (float)Math.Clamp(newZoom, 1f, 10f); + + var imageX = (newCenter.X - Bounds.Width / 2 - _panX) / _zoom; + var imageY = (-newCenter.Y + Bounds.Height / 2 - _panY) / _zoom; // Ось Y инвертированна + + _zoom = newZoom; + + if (_zoom > 1f) + { + _panX = (float)(newCenter.X - Bounds.Width / 2 - imageX * _zoom); + _panY = (float)(-newCenter.Y + Bounds.Height / 2 - imageY * _zoom); // Ось Y инвертированна + } + else + { + _zoom = 1f; + _panX = 0f; + _panY = 0f; + } + } } + base.OnPointerMoved(ea); } public override void Render(DrawingContext context)