diff --git a/src/Web/Avalonia.Web/Avalonia.Web.csproj b/src/Web/Avalonia.Web/Avalonia.Web.csproj index cdfa095865..88b23cdad2 100644 --- a/src/Web/Avalonia.Web/Avalonia.Web.csproj +++ b/src/Web/Avalonia.Web/Avalonia.Web.csproj @@ -7,6 +7,7 @@ + diff --git a/src/Web/Avalonia.Web/AvaloniaView.cs b/src/Web/Avalonia.Web/AvaloniaView.cs index bd08940306..37614399ee 100644 --- a/src/Web/Avalonia.Web/AvaloniaView.cs +++ b/src/Web/Avalonia.Web/AvaloniaView.cs @@ -1,5 +1,9 @@ using System; +using System.Collections.Generic; +using System.Reflection; using System.Runtime.InteropServices.JavaScript; + +using Avalonia.Collections.Pooled; using Avalonia.Controls; using Avalonia.Controls.Embedding; using Avalonia.Controls.Platform; @@ -18,6 +22,7 @@ namespace Avalonia.Web [System.Runtime.Versioning.SupportedOSPlatform("browser")] // gets rid of callsite warnings public partial class AvaloniaView : ITextInputMethodImpl { + private static readonly PooledList s_intermediatePointsPooledList = new(ClearMode.Never); private readonly BrowserTopLevelImpl _topLevelImpl; private EmbeddableControlRoot _topLevel; @@ -162,94 +167,84 @@ namespace Avalonia.Web private bool OnPointerMove(JSObject args) { var pointerType = args.GetPropertyAsString("pointerType"); + var point = ExtractRawPointerFromJSArgs(args); + var type = pointerType switch + { + "touch" => RawPointerEventType.TouchUpdate, + _ => RawPointerEventType.Move + }; - if (pointerType == "touch") + var coalescedEvents = new Lazy?>(() => { - var x = args.GetPropertyAsDouble("clientX"); - var y = args.GetPropertyAsDouble("clientY"); - long identifier = args.GetPropertyAsInt32("identifier"); + var points = InputHelper.GetCoalescedEvents(args); + s_intermediatePointsPooledList.Clear(); + s_intermediatePointsPooledList.Capacity = points.Length - 1; - return _topLevelImpl.RawTouchEvent(RawPointerEventType.TouchUpdate, new Point(x, y), - GetModifiers(args), identifier); - } + // Skip the last one, as it is already processed point. + for (var i = 0; i < points.Length - 1; i++) + { + var point = points[i]; + s_intermediatePointsPooledList.Add(ExtractRawPointerFromJSArgs(point)); + } - var point = ExtractRawPointerFromJSArgs(args); - - return _topLevelImpl.RawPointerEvent(RawPointerEventType.Move, pointerType!, point, GetModifiers(args), args.GetPropertyAsInt32("pointerId")); + return s_intermediatePointsPooledList; + }); + + return _topLevelImpl.RawPointerEvent(type, pointerType!, point, GetModifiers(args), args.GetPropertyAsInt32("pointerId"), coalescedEvents); } private bool OnPointerDown(JSObject args) { - var pointerType = args.GetPropertyAsString("pointerType"); - - if (pointerType == "touch") - { - var x = args.GetPropertyAsDouble("clientX"); - var y = args.GetPropertyAsDouble("clientY"); - long identifier = args.GetPropertyAsInt32("identifier"); - - return _topLevelImpl.RawTouchEvent(RawPointerEventType.TouchBegin, new Point(x, y), - GetModifiers(args), identifier); - } - - var type = args.GetPropertyAsInt32("button") switch + var pointerType = args.GetPropertyAsString("pointerType") ?? "mouse"; + var type = pointerType switch { - 0 => RawPointerEventType.LeftButtonDown, - 1 => RawPointerEventType.MiddleButtonDown, - 2 => RawPointerEventType.RightButtonDown, - 3 => RawPointerEventType.XButton1Down, - 4 => RawPointerEventType.XButton2Down, - // 5 => Pen eraser button, - _ => RawPointerEventType.Move + "touch" => RawPointerEventType.TouchBegin, + _ => args.GetPropertyAsInt32("button") switch + { + 0 => RawPointerEventType.LeftButtonDown, + 1 => RawPointerEventType.MiddleButtonDown, + 2 => RawPointerEventType.RightButtonDown, + 3 => RawPointerEventType.XButton1Down, + 4 => RawPointerEventType.XButton2Down, + 5 => RawPointerEventType.XButton1Down, // should be pen eraser button, + _ => RawPointerEventType.Move + } }; var point = ExtractRawPointerFromJSArgs(args); - - return _topLevelImpl.RawPointerEvent(type, pointerType!, point, GetModifiers(args), args.GetPropertyAsInt32("pointerId")); + return _topLevelImpl.RawPointerEvent(type, pointerType, point, GetModifiers(args), args.GetPropertyAsInt32("pointerId")); } private bool OnPointerUp(JSObject args) { var pointerType = args.GetPropertyAsString("pointerType") ?? "mouse"; - - if (pointerType == "touch") + var type = pointerType switch { - var x = args.GetPropertyAsDouble("clientX"); - var y = args.GetPropertyAsDouble("clientY"); - long identifier = args.GetPropertyAsInt32("identifier"); - - return _topLevelImpl.RawTouchEvent(RawPointerEventType.TouchEnd, new Point(x, y), - GetModifiers(args), identifier); - } - - var type = args.GetPropertyAsInt32("button") switch - { - 0 => RawPointerEventType.LeftButtonUp, - 1 => RawPointerEventType.MiddleButtonUp, - 2 => RawPointerEventType.RightButtonUp, - 3 => RawPointerEventType.XButton1Up, - 4 => RawPointerEventType.XButton2Up, - // 5 => Pen eraser button, - _ => RawPointerEventType.Move + "touch" => RawPointerEventType.TouchEnd, + _ => args.GetPropertyAsInt32("button") switch + { + 0 => RawPointerEventType.LeftButtonUp, + 1 => RawPointerEventType.MiddleButtonUp, + 2 => RawPointerEventType.RightButtonUp, + 3 => RawPointerEventType.XButton1Up, + 4 => RawPointerEventType.XButton2Up, + 5 => RawPointerEventType.XButton1Up, // should be pen eraser button, + _ => RawPointerEventType.Move + } }; var point = ExtractRawPointerFromJSArgs(args); - return _topLevelImpl.RawPointerEvent(type, pointerType, point, GetModifiers(args), args.GetPropertyAsInt32("pointerId")); } private bool OnPointerCancel(JSObject args) { var pointerType = args.GetPropertyAsString("pointerType") ?? "mouse"; - if (pointerType == "touch") { - var x = args.GetPropertyAsDouble("clientX"); - var y = args.GetPropertyAsDouble("clientY"); - long identifier = args.GetPropertyAsInt32("identifier"); - - return _topLevelImpl.RawTouchEvent(RawPointerEventType.TouchCancel, new Point(x, y), - GetModifiers(args), identifier); + var point = ExtractRawPointerFromJSArgs(args); + _topLevelImpl.RawPointerEvent(RawPointerEventType.TouchCancel, pointerType, point, + GetModifiers(args), args.GetPropertyAsInt32("pointerId")); } return false; diff --git a/src/Web/Avalonia.Web/BrowserTopLevelImpl.cs b/src/Web/Avalonia.Web/BrowserTopLevelImpl.cs index 3c42deffd8..ed8f417870 100644 --- a/src/Web/Avalonia.Web/BrowserTopLevelImpl.cs +++ b/src/Web/Avalonia.Web/BrowserTopLevelImpl.cs @@ -64,35 +64,25 @@ namespace Avalonia.Web Resized?.Invoke(newSize, PlatformResizeReason.User); } } - - public bool RawTouchEvent(RawPointerEventType type, Point p, RawInputModifiers modifiers, long touchPointId) - { - if (_inputRoot is { } && Input is { } input) - { - var args = new RawTouchEventArgs(_touchDevice, Timestamp, _inputRoot, type, p, modifiers, - touchPointId); - - input.Invoke(args); - - return args.Handled; - } - - return false; - } public bool RawPointerEvent( RawPointerEventType eventType, string pointerType, - RawPointerPoint p, RawInputModifiers modifiers, long touchPointId) + RawPointerPoint p, RawInputModifiers modifiers, long touchPointId, + Lazy?>? intermediatePoints = null) { if (_inputRoot is { } && Input is { } input) { var device = GetPointerDevice(pointerType); var args = device is TouchDevice ? - new RawTouchEventArgs(device, Timestamp, _inputRoot, eventType, p, modifiers, touchPointId) : + new RawTouchEventArgs(device, Timestamp, _inputRoot, eventType, p, modifiers, touchPointId) + { + IntermediatePoints = intermediatePoints + } : new RawPointerEventArgs(device, Timestamp, _inputRoot, eventType, p, modifiers) { - RawPointerId = touchPointId + RawPointerId = touchPointId, + IntermediatePoints = intermediatePoints }; input.Invoke(args); diff --git a/src/Web/Avalonia.Web/Interop/InputHelper.cs b/src/Web/Avalonia.Web/Interop/InputHelper.cs index 0100701714..904fa915a8 100644 --- a/src/Web/Avalonia.Web/Interop/InputHelper.cs +++ b/src/Web/Avalonia.Web/Interop/InputHelper.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.InteropServices; using System.Runtime.InteropServices.JavaScript; using System.Threading.Tasks; @@ -47,6 +48,9 @@ internal static partial class InputHelper [JSMarshalAs>] Func input); + [JSImport("InputHelper.getCoalescedEvents", AvaloniaModule.MainModuleName)] + [return: JSMarshalAs>] + public static partial JSObject[] GetCoalescedEvents(JSObject pointerEvent); [JSImport("InputHelper.clearInput", AvaloniaModule.MainModuleName)] public static partial void ClearInputElement(JSObject htmlElement); diff --git a/src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts b/src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts index f5a6c6e806..83e8ee7f1c 100644 --- a/src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts +++ b/src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts @@ -154,6 +154,10 @@ export class InputHelper { }; } + public static getCoalescedEvents(pointerEvent: PointerEvent): PointerEvent[] { + return pointerEvent.getCoalescedEvents(); + } + public static clearInput(inputElement: HTMLInputElement) { inputElement.value = ""; }