using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using Avalonia.Input.Raw; using Avalonia.Platform; namespace Avalonia.Input { /// /// Handles raw touch events /// /// /// This class is supposed to be used on per-toplevel basis, don't use a shared one /// public class TouchDevice : IPointerDevice, IDisposable { private readonly Dictionary _pointers = new Dictionary(); private bool _disposed; private int _clickCount; private Rect _lastClickRect; private ulong _lastClickTime; static RawInputModifiers GetModifiers(RawInputModifiers modifiers, bool isLeftButtonDown) { var rv = modifiers &= RawInputModifiers.KeyboardMask; if (isLeftButtonDown) rv |= RawInputModifiers.LeftMouseButton; return rv; } public void ProcessRawEvent(RawInputEventArgs ev) { if (ev.Handled || _disposed) return; var args = (RawPointerEventArgs)ev; if (!_pointers.TryGetValue(args.RawPointerId, out var pointer)) { if (args.Type == RawPointerEventType.TouchEnd) return; var hit = args.InputHitTestResult; _pointers[args.RawPointerId] = pointer = new Pointer(Pointer.GetNextFreeId(), PointerType.Touch, _pointers.Count == 0); pointer.Capture(hit); } var target = pointer.Captured ?? args.Root; var updateKind = args.Type.ToUpdateKind(); var keyModifier = args.InputModifiers.ToKeyModifiers(); if (args.Type == RawPointerEventType.TouchBegin) { if (_pointers.Count > 1) { _clickCount = 1; _lastClickTime = 0; _lastClickRect = new Rect(); } else { var settings = AvaloniaLocator.Current.GetRequiredService(); var doubleClickTime = settings.GetDoubleTapTime(PointerType.Touch).TotalMilliseconds; var doubleClickSize = settings.GetDoubleTapSize(PointerType.Touch); if (!_lastClickRect.Contains(args.Position) || ev.Timestamp - _lastClickTime > doubleClickTime) { _clickCount = 0; } ++_clickCount; _lastClickTime = ev.Timestamp; _lastClickRect = new Rect(args.Position, new Size()) .Inflate(new Thickness(doubleClickSize.Width / 2, doubleClickSize.Height / 2)); } target.RaiseEvent(new PointerPressedEventArgs(target, pointer, (Visual)args.Root, args.Position, ev.Timestamp, new PointerPointProperties(GetModifiers(args.InputModifiers, true), updateKind), keyModifier, _clickCount)); } if (args.Type == RawPointerEventType.TouchEnd) { _pointers.Remove(args.RawPointerId); using (pointer) { target.RaiseEvent(new PointerReleasedEventArgs(target, pointer, (Visual)args.Root, args.Position, ev.Timestamp, new PointerPointProperties(GetModifiers(args.InputModifiers, false), updateKind), keyModifier, MouseButton.Left)); } } if (args.Type == RawPointerEventType.TouchCancel) { _pointers.Remove(args.RawPointerId); using (pointer) pointer.Capture(null); } if (args.Type == RawPointerEventType.TouchUpdate) { target.RaiseEvent(new PointerEventArgs(InputElement.PointerMovedEvent, target, pointer, (Visual)args.Root, args.Position, ev.Timestamp, new PointerPointProperties(GetModifiers(args.InputModifiers, true), updateKind), keyModifier, args.IntermediatePoints)); } } public void Dispose() { if (_disposed) return; var values = _pointers.Values.ToArray(); _pointers.Clear(); _disposed = true; foreach (var p in values) p.Dispose(); } public IPointer? TryGetPointer(RawPointerEventArgs ev) { return _pointers.TryGetValue(ev.RawPointerId, out var pointer) ? pointer : null; } } }