From c8fae36d36047869bac3a1c055ec30a3d0bd9757 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 25 Oct 2022 18:49:09 +0100 Subject: [PATCH 1/4] implement touch. --- src/Web/Avalonia.Web/AvaloniaView.cs | 113 +++++++++++++----- src/Web/Avalonia.Web/BrowserTopLevelImpl.cs | 8 ++ src/Web/Avalonia.Web/Interop/InputHelper.cs | 12 ++ .../webapp/modules/avalonia/input.ts | 60 ++++++++++ 4 files changed, 166 insertions(+), 27 deletions(-) diff --git a/src/Web/Avalonia.Web/AvaloniaView.cs b/src/Web/Avalonia.Web/AvaloniaView.cs index 098b06a0a2..d61733b722 100644 --- a/src/Web/Avalonia.Web/AvaloniaView.cs +++ b/src/Web/Avalonia.Web/AvaloniaView.cs @@ -97,6 +97,8 @@ namespace Avalonia.Web OnCompositionEnd); InputHelper.SubscribePointerEvents(_containerElement, OnPointerMove, OnPointerDown, OnPointerUp, OnWheel); + + InputHelper.SubscribeTouchEvents(_containerElement, OnTouchStart, OnTouchEnd, OnTouchCancel, OnTouchMove); var skiaOptions = AvaloniaLocator.Current.GetService(); @@ -139,6 +141,62 @@ namespace Avalonia.Web InputHelper.FocusElement(_containerElement); } + private void OnTouchStart(JSObject arg, JSObject touch) + { + var x = touch.GetPropertyAsDouble("clientX"); + var y = touch.GetPropertyAsDouble("clientY"); + long identifier = touch.GetPropertyAsInt32("identifier"); + + _topLevelImpl.RawTouchEvent(RawPointerEventType.TouchBegin, new Point(x, y), + GetTouchModifiers(arg), identifier); + } + + private void OnTouchEnd(JSObject arg, JSObject touch) + { + var x = touch.GetPropertyAsDouble("clientX"); + var y = touch.GetPropertyAsDouble("clientY"); + long identifier = touch.GetPropertyAsInt32("identifier"); + + _topLevelImpl.RawTouchEvent(RawPointerEventType.TouchEnd, new Point(x, y), + GetTouchModifiers(arg), identifier); + } + + private void OnTouchCancel(JSObject arg, JSObject touch) + { + var x = touch.GetPropertyAsDouble("clientX"); + var y = touch.GetPropertyAsDouble("clientY"); + long identifier = touch.GetPropertyAsInt32("identifier"); + + _topLevelImpl.RawTouchEvent(RawPointerEventType.TouchCancel, new Point(x, y), + GetTouchModifiers(arg), identifier); + } + + private void OnTouchMove(JSObject arg, JSObject touch) + { + var x = touch.GetPropertyAsDouble("clientX"); + var y = touch.GetPropertyAsDouble("clientY"); + long identifier = touch.GetPropertyAsInt32("identifier"); + + _topLevelImpl.RawTouchEvent(RawPointerEventType.TouchUpdate, new Point(x, y), + GetTouchModifiers(arg), identifier); + } + + private static RawInputModifiers GetTouchModifiers(JSObject e) + { + var modifiers = RawInputModifiers.None; + + if (e.GetPropertyAsBoolean("ctrlKey")) + modifiers |= RawInputModifiers.Control; + if (e.GetPropertyAsBoolean("altKey")) + modifiers |= RawInputModifiers.Alt; + if(e.GetPropertyAsBoolean("shiftKey")) + modifiers |= RawInputModifiers.Shift; + if(e.GetPropertyAsBoolean("metaKey")) + modifiers |= RawInputModifiers.Meta; + + return modifiers; + } + private static RawPointerPoint ExtractRawPointerFromJSArgs(JSObject args) { var point = new RawPointerPoint @@ -155,30 +213,32 @@ namespace Avalonia.Web private bool OnPointerMove(JSObject args) { - var type = args.GetPropertyAsString("pointertype"); + var pointerType = args.GetPropertyAsString("pointerType"); + + if (pointerType == "touch") + return false; var point = ExtractRawPointerFromJSArgs(args); - - return _topLevelImpl.RawPointerEvent(RawPointerEventType.Move, type!, point, GetModifiers(args), args.GetPropertyAsInt32("pointerId")); + + return _topLevelImpl.RawPointerEvent(RawPointerEventType.Move, pointerType!, point, GetModifiers(args), args.GetPropertyAsInt32("pointerId")); } private bool OnPointerDown(JSObject args) { var pointerType = args.GetPropertyAsString("pointerType"); - var type = pointerType switch + if (pointerType == "touch") + return false; + + var type = args.GetPropertyAsInt32("button") switch { - "touch" => RawPointerEventType.TouchBegin, - _ => args.GetPropertyAsInt32("button") switch - { - 0 => RawPointerEventType.LeftButtonDown, - 1 => RawPointerEventType.MiddleButtonDown, - 2 => RawPointerEventType.RightButtonDown, - 3 => RawPointerEventType.XButton1Down, - 4 => RawPointerEventType.XButton2Down, - // 5 => Pen eraser button, - _ => RawPointerEventType.Move - } + 0 => RawPointerEventType.LeftButtonDown, + 1 => RawPointerEventType.MiddleButtonDown, + 2 => RawPointerEventType.RightButtonDown, + 3 => RawPointerEventType.XButton1Down, + 4 => RawPointerEventType.XButton2Down, + // 5 => Pen eraser button, + _ => RawPointerEventType.Move }; var point = ExtractRawPointerFromJSArgs(args); @@ -190,19 +250,18 @@ namespace Avalonia.Web { var pointerType = args.GetPropertyAsString("pointerType") ?? "mouse"; - var type = pointerType switch + if (pointerType == "touch") + return false; + + var type = args.GetPropertyAsInt32("button") switch { - "touch" => RawPointerEventType.TouchEnd, - _ => 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 - } + 0 => RawPointerEventType.LeftButtonUp, + 1 => RawPointerEventType.MiddleButtonUp, + 2 => RawPointerEventType.RightButtonUp, + 3 => RawPointerEventType.XButton1Up, + 4 => RawPointerEventType.XButton2Up, + // 5 => Pen eraser button, + _ => RawPointerEventType.Move }; var point = ExtractRawPointerFromJSArgs(args); diff --git a/src/Web/Avalonia.Web/BrowserTopLevelImpl.cs b/src/Web/Avalonia.Web/BrowserTopLevelImpl.cs index b955da6df2..bb1d79bd3d 100644 --- a/src/Web/Avalonia.Web/BrowserTopLevelImpl.cs +++ b/src/Web/Avalonia.Web/BrowserTopLevelImpl.cs @@ -64,6 +64,14 @@ namespace Avalonia.Web Resized?.Invoke(newSize, PlatformResizeReason.User); } } + + public void RawTouchEvent(RawPointerEventType type, Point p, RawInputModifiers modifiers, long touchPointId) + { + if (_inputRoot is { } && Input is { } input) + { + input.Invoke(new RawTouchEventArgs(_touchDevice, Timestamp, _inputRoot, type, p, modifiers, touchPointId)); + } + } public bool RawPointerEvent( RawPointerEventType eventType, string pointerType, diff --git a/src/Web/Avalonia.Web/Interop/InputHelper.cs b/src/Web/Avalonia.Web/Interop/InputHelper.cs index cfec9f30dc..3329026b70 100644 --- a/src/Web/Avalonia.Web/Interop/InputHelper.cs +++ b/src/Web/Avalonia.Web/Interop/InputHelper.cs @@ -37,6 +37,18 @@ internal static partial class InputHelper Func pointerUp, [JSMarshalAs>] Func wheel); + + [JSImport("InputHelper.subscribeTouchEvents", AvaloniaModule.MainModuleName)] + public static partial void SubscribeTouchEvents( + JSObject htmlElement, + [JSMarshalAs>] + Action touchStart, + [JSMarshalAs>] + Action touchEnd, + [JSMarshalAs>] + Action touchCancel, + [JSMarshalAs>] + Action touchMove); [JSImport("InputHelper.subscribeInputEvents", AvaloniaModule.MainModuleName)] diff --git a/src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts b/src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts index faede82e0d..74e87a8e2e 100644 --- a/src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts +++ b/src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts @@ -130,6 +130,66 @@ export class InputHelper { }; } + public static subscribeTouchEvents( + element: HTMLInputElement, + touchStartCallback: (args: TouchEvent, touch: Touch) => void, + touchEndCallback: (args: TouchEvent, touch: Touch) => void, + touchCancelCallback: (args: TouchEvent, touch: Touch) => void, + touchMoveCallback: (args: TouchEvent, touch: Touch) => void + ) { + const touchStartHandler = (args: TouchEvent) => { + for (let i = 0; i < args.changedTouches.length; i++) { + const touch = args.changedTouches.item(i); + if (touch) { + touchStartCallback(args, touch); + } + } + args.preventDefault(); + }; + + const touchEndHandler = (args: TouchEvent) => { + for (let i = 0; i < args.changedTouches.length; i++) { + const touch = args.changedTouches.item(i); + if (touch) { + touchEndCallback(args, touch); + } + } + args.preventDefault(); + }; + + const touchCancelHandler = (args: TouchEvent) => { + for (let i = 0; i < args.changedTouches.length; i++) { + const touch = args.changedTouches.item(i); + if (touch) { + touchCancelCallback(args, touch); + } + } + args.preventDefault(); + }; + + const touchMoveHandler = (args: TouchEvent) => { + for (let i = 0; i < args.changedTouches.length; i++) { + const touch = args.changedTouches.item(i); + if (touch) { + touchMoveCallback(args, touch); + } + } + args.preventDefault(); + }; + + element.addEventListener("touchstart", touchStartHandler); + element.addEventListener("touchend", touchEndHandler); + element.addEventListener("touchcancel", touchCancelHandler); + element.addEventListener("touchmove", touchMoveHandler); + + return () => { + element.removeEventListener("touchstart", touchStartHandler); + element.removeEventListener("touchend", touchEndHandler); + element.removeEventListener("touchcancel", touchCancelHandler); + element.removeEventListener("touchmove", touchMoveHandler); + }; + } + public static subscribeInputEvents( element: HTMLInputElement, inputCallback: (value: string) => boolean From 985e4cf2ab983d25575d210edc1555ba7776e3c9 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 25 Oct 2022 21:50:45 +0100 Subject: [PATCH 2/4] implement touch via pointer events only. --- src/Web/Avalonia.Web/AvaloniaView.cs | 108 +++++++++--------- src/Web/Avalonia.Web/BrowserTopLevelImpl.cs | 11 +- src/Web/Avalonia.Web/Interop/InputHelper.cs | 14 +-- .../webapp/modules/avalonia/input.ts | 68 ++--------- 4 files changed, 74 insertions(+), 127 deletions(-) diff --git a/src/Web/Avalonia.Web/AvaloniaView.cs b/src/Web/Avalonia.Web/AvaloniaView.cs index d61733b722..8d8d6691ec 100644 --- a/src/Web/Avalonia.Web/AvaloniaView.cs +++ b/src/Web/Avalonia.Web/AvaloniaView.cs @@ -52,13 +52,13 @@ namespace Avalonia.Web } _containerElement = hostContent.GetPropertyAsJSObject("host") - ?? throw new InvalidOperationException("Host cannot be null"); + ?? throw new InvalidOperationException("Host cannot be null"); _canvas = hostContent.GetPropertyAsJSObject("canvas") - ?? throw new InvalidOperationException("Canvas cannot be null"); + ?? throw new InvalidOperationException("Canvas cannot be null"); _nativeControlsContainer = hostContent.GetPropertyAsJSObject("nativeHost") - ?? throw new InvalidOperationException("NativeHost cannot be null"); + ?? throw new InvalidOperationException("NativeHost cannot be null"); _inputElement = hostContent.GetPropertyAsJSObject("inputElement") - ?? throw new InvalidOperationException("InputElement cannot be null"); + ?? throw new InvalidOperationException("InputElement cannot be null"); _splash = DomHelper.GetElementById("avalonia-splash"); @@ -96,9 +96,8 @@ namespace Avalonia.Web OnCompositionUpdate, OnCompositionEnd); - InputHelper.SubscribePointerEvents(_containerElement, OnPointerMove, OnPointerDown, OnPointerUp, OnWheel); - - InputHelper.SubscribeTouchEvents(_containerElement, OnTouchStart, OnTouchEnd, OnTouchCancel, OnTouchMove); + InputHelper.SubscribePointerEvents(_containerElement, OnPointerMove, OnPointerDown, OnPointerUp, + OnPointerCancel, OnWheel); var skiaOptions = AvaloniaLocator.Current.GetService(); @@ -119,7 +118,12 @@ namespace Avalonia.Web _context.SetResourceCacheLimit(skiaOptions?.MaxGpuResourceSizeBytes ?? 32 * 1024 * 1024); } - _topLevelImpl.Surfaces = new[] { new BrowserSkiaSurface(_context, _jsGlInfo, ColorType, new PixelSize((int)_canvasSize.Width, (int)_canvasSize.Height), _dpi, GRSurfaceOrigin.BottomLeft) }; + _topLevelImpl.Surfaces = new[] + { + new BrowserSkiaSurface(_context, _jsGlInfo, ColorType, + new PixelSize((int)_canvasSize.Width, (int)_canvasSize.Height), _dpi, + GRSurfaceOrigin.BottomLeft) + }; } else { @@ -137,48 +141,8 @@ namespace Avalonia.Web DomHelper.ObserveSize(host, null, OnSizeChanged); CanvasHelper.RequestAnimationFrame(_canvas, true); - - InputHelper.FocusElement(_containerElement); - } - private void OnTouchStart(JSObject arg, JSObject touch) - { - var x = touch.GetPropertyAsDouble("clientX"); - var y = touch.GetPropertyAsDouble("clientY"); - long identifier = touch.GetPropertyAsInt32("identifier"); - - _topLevelImpl.RawTouchEvent(RawPointerEventType.TouchBegin, new Point(x, y), - GetTouchModifiers(arg), identifier); - } - - private void OnTouchEnd(JSObject arg, JSObject touch) - { - var x = touch.GetPropertyAsDouble("clientX"); - var y = touch.GetPropertyAsDouble("clientY"); - long identifier = touch.GetPropertyAsInt32("identifier"); - - _topLevelImpl.RawTouchEvent(RawPointerEventType.TouchEnd, new Point(x, y), - GetTouchModifiers(arg), identifier); - } - - private void OnTouchCancel(JSObject arg, JSObject touch) - { - var x = touch.GetPropertyAsDouble("clientX"); - var y = touch.GetPropertyAsDouble("clientY"); - long identifier = touch.GetPropertyAsInt32("identifier"); - - _topLevelImpl.RawTouchEvent(RawPointerEventType.TouchCancel, new Point(x, y), - GetTouchModifiers(arg), identifier); - } - - private void OnTouchMove(JSObject arg, JSObject touch) - { - var x = touch.GetPropertyAsDouble("clientX"); - var y = touch.GetPropertyAsDouble("clientY"); - long identifier = touch.GetPropertyAsInt32("identifier"); - - _topLevelImpl.RawTouchEvent(RawPointerEventType.TouchUpdate, new Point(x, y), - GetTouchModifiers(arg), identifier); + InputHelper.FocusElement(_containerElement); } private static RawInputModifiers GetTouchModifiers(JSObject e) @@ -214,9 +178,16 @@ namespace Avalonia.Web private bool OnPointerMove(JSObject args) { var pointerType = args.GetPropertyAsString("pointerType"); - + if (pointerType == "touch") - return false; + { + var x = args.GetPropertyAsDouble("clientX"); + var y = args.GetPropertyAsDouble("clientY"); + long identifier = args.GetPropertyAsInt32("identifier"); + + return _topLevelImpl.RawTouchEvent(RawPointerEventType.TouchUpdate, new Point(x, y), + GetTouchModifiers(args), identifier); + } var point = ExtractRawPointerFromJSArgs(args); @@ -228,7 +199,14 @@ namespace Avalonia.Web var pointerType = args.GetPropertyAsString("pointerType"); if (pointerType == "touch") - return false; + { + var x = args.GetPropertyAsDouble("clientX"); + var y = args.GetPropertyAsDouble("clientY"); + long identifier = args.GetPropertyAsInt32("identifier"); + + return _topLevelImpl.RawTouchEvent(RawPointerEventType.TouchBegin, new Point(x, y), + GetTouchModifiers(args), identifier); + } var type = args.GetPropertyAsInt32("button") switch { @@ -251,7 +229,14 @@ namespace Avalonia.Web var pointerType = args.GetPropertyAsString("pointerType") ?? "mouse"; if (pointerType == "touch") - return false; + { + var x = args.GetPropertyAsDouble("clientX"); + var y = args.GetPropertyAsDouble("clientY"); + long identifier = args.GetPropertyAsInt32("identifier"); + + return _topLevelImpl.RawTouchEvent(RawPointerEventType.TouchEnd, new Point(x, y), + GetTouchModifiers(args), identifier); + } var type = args.GetPropertyAsInt32("button") switch { @@ -268,6 +253,23 @@ namespace Avalonia.Web 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), + GetTouchModifiers(args), identifier); + } + + return false; + } private bool OnWheel(JSObject args) { diff --git a/src/Web/Avalonia.Web/BrowserTopLevelImpl.cs b/src/Web/Avalonia.Web/BrowserTopLevelImpl.cs index bb1d79bd3d..3c42deffd8 100644 --- a/src/Web/Avalonia.Web/BrowserTopLevelImpl.cs +++ b/src/Web/Avalonia.Web/BrowserTopLevelImpl.cs @@ -65,12 +65,19 @@ namespace Avalonia.Web } } - public void RawTouchEvent(RawPointerEventType type, Point p, RawInputModifiers modifiers, long touchPointId) + public bool RawTouchEvent(RawPointerEventType type, Point p, RawInputModifiers modifiers, long touchPointId) { if (_inputRoot is { } && Input is { } input) { - input.Invoke(new RawTouchEventArgs(_touchDevice, Timestamp, _inputRoot, type, p, modifiers, touchPointId)); + var args = new RawTouchEventArgs(_touchDevice, Timestamp, _inputRoot, type, p, modifiers, + touchPointId); + + input.Invoke(args); + + return args.Handled; } + + return false; } public bool RawPointerEvent( diff --git a/src/Web/Avalonia.Web/Interop/InputHelper.cs b/src/Web/Avalonia.Web/Interop/InputHelper.cs index 3329026b70..0100701714 100644 --- a/src/Web/Avalonia.Web/Interop/InputHelper.cs +++ b/src/Web/Avalonia.Web/Interop/InputHelper.cs @@ -36,19 +36,9 @@ internal static partial class InputHelper [JSMarshalAs>] Func pointerUp, [JSMarshalAs>] + Func pointerCancel, + [JSMarshalAs>] Func wheel); - - [JSImport("InputHelper.subscribeTouchEvents", AvaloniaModule.MainModuleName)] - public static partial void SubscribeTouchEvents( - JSObject htmlElement, - [JSMarshalAs>] - Action touchStart, - [JSMarshalAs>] - Action touchEnd, - [JSMarshalAs>] - Action touchCancel, - [JSMarshalAs>] - Action touchMove); [JSImport("InputHelper.subscribeInputEvents", AvaloniaModule.MainModuleName)] diff --git a/src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts b/src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts index 74e87a8e2e..f5a6c6e806 100644 --- a/src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts +++ b/src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts @@ -95,6 +95,7 @@ export class InputHelper { pointerMoveCallback: (args: PointerEvent) => boolean, pointerDownCallback: (args: PointerEvent) => boolean, pointerUpCallback: (args: PointerEvent) => boolean, + pointerCancelCallback: (args: PointerEvent) => boolean, wheelCallback: (args: WheelEvent) => boolean ) { const pointerMoveHandler = (args: PointerEvent) => { @@ -112,6 +113,11 @@ export class InputHelper { args.preventDefault(); }; + const pointerCancelHandler = (args: PointerEvent) => { + pointerCancelCallback(args); + args.preventDefault(); + }; + const wheelHandler = (args: WheelEvent) => { wheelCallback(args); args.preventDefault(); @@ -121,75 +127,17 @@ export class InputHelper { element.addEventListener("pointerdown", pointerDownHandler); element.addEventListener("pointerup", pointerUpHandler); element.addEventListener("wheel", wheelHandler); + element.addEventListener("pointercancel", pointerCancelHandler); return () => { element.removeEventListener("pointerover", pointerMoveHandler); element.removeEventListener("pointerdown", pointerDownHandler); element.removeEventListener("pointerup", pointerUpHandler); + element.removeEventListener("pointercancel", pointerCancelHandler); element.removeEventListener("wheel", wheelHandler); }; } - public static subscribeTouchEvents( - element: HTMLInputElement, - touchStartCallback: (args: TouchEvent, touch: Touch) => void, - touchEndCallback: (args: TouchEvent, touch: Touch) => void, - touchCancelCallback: (args: TouchEvent, touch: Touch) => void, - touchMoveCallback: (args: TouchEvent, touch: Touch) => void - ) { - const touchStartHandler = (args: TouchEvent) => { - for (let i = 0; i < args.changedTouches.length; i++) { - const touch = args.changedTouches.item(i); - if (touch) { - touchStartCallback(args, touch); - } - } - args.preventDefault(); - }; - - const touchEndHandler = (args: TouchEvent) => { - for (let i = 0; i < args.changedTouches.length; i++) { - const touch = args.changedTouches.item(i); - if (touch) { - touchEndCallback(args, touch); - } - } - args.preventDefault(); - }; - - const touchCancelHandler = (args: TouchEvent) => { - for (let i = 0; i < args.changedTouches.length; i++) { - const touch = args.changedTouches.item(i); - if (touch) { - touchCancelCallback(args, touch); - } - } - args.preventDefault(); - }; - - const touchMoveHandler = (args: TouchEvent) => { - for (let i = 0; i < args.changedTouches.length; i++) { - const touch = args.changedTouches.item(i); - if (touch) { - touchMoveCallback(args, touch); - } - } - args.preventDefault(); - }; - - element.addEventListener("touchstart", touchStartHandler); - element.addEventListener("touchend", touchEndHandler); - element.addEventListener("touchcancel", touchCancelHandler); - element.addEventListener("touchmove", touchMoveHandler); - - return () => { - element.removeEventListener("touchstart", touchStartHandler); - element.removeEventListener("touchend", touchEndHandler); - element.removeEventListener("touchcancel", touchCancelHandler); - element.removeEventListener("touchmove", touchMoveHandler); - }; - } - public static subscribeInputEvents( element: HTMLInputElement, inputCallback: (value: string) => boolean From 385c26741a1aec9b16d9c354ec6182fd0971a0fa Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 25 Oct 2022 21:59:04 +0100 Subject: [PATCH 3/4] remove duplicated code. --- src/Web/Avalonia.Web/AvaloniaView.cs | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/src/Web/Avalonia.Web/AvaloniaView.cs b/src/Web/Avalonia.Web/AvaloniaView.cs index 8d8d6691ec..bd08940306 100644 --- a/src/Web/Avalonia.Web/AvaloniaView.cs +++ b/src/Web/Avalonia.Web/AvaloniaView.cs @@ -144,22 +144,6 @@ namespace Avalonia.Web InputHelper.FocusElement(_containerElement); } - - private static RawInputModifiers GetTouchModifiers(JSObject e) - { - var modifiers = RawInputModifiers.None; - - if (e.GetPropertyAsBoolean("ctrlKey")) - modifiers |= RawInputModifiers.Control; - if (e.GetPropertyAsBoolean("altKey")) - modifiers |= RawInputModifiers.Alt; - if(e.GetPropertyAsBoolean("shiftKey")) - modifiers |= RawInputModifiers.Shift; - if(e.GetPropertyAsBoolean("metaKey")) - modifiers |= RawInputModifiers.Meta; - - return modifiers; - } private static RawPointerPoint ExtractRawPointerFromJSArgs(JSObject args) { @@ -186,7 +170,7 @@ namespace Avalonia.Web long identifier = args.GetPropertyAsInt32("identifier"); return _topLevelImpl.RawTouchEvent(RawPointerEventType.TouchUpdate, new Point(x, y), - GetTouchModifiers(args), identifier); + GetModifiers(args), identifier); } var point = ExtractRawPointerFromJSArgs(args); @@ -205,7 +189,7 @@ namespace Avalonia.Web long identifier = args.GetPropertyAsInt32("identifier"); return _topLevelImpl.RawTouchEvent(RawPointerEventType.TouchBegin, new Point(x, y), - GetTouchModifiers(args), identifier); + GetModifiers(args), identifier); } var type = args.GetPropertyAsInt32("button") switch @@ -235,7 +219,7 @@ namespace Avalonia.Web long identifier = args.GetPropertyAsInt32("identifier"); return _topLevelImpl.RawTouchEvent(RawPointerEventType.TouchEnd, new Point(x, y), - GetTouchModifiers(args), identifier); + GetModifiers(args), identifier); } var type = args.GetPropertyAsInt32("button") switch @@ -265,7 +249,7 @@ namespace Avalonia.Web long identifier = args.GetPropertyAsInt32("identifier"); return _topLevelImpl.RawTouchEvent(RawPointerEventType.TouchCancel, new Point(x, y), - GetTouchModifiers(args), identifier); + GetModifiers(args), identifier); } return false; From 5f5d596b1ccad47fab45a424117f53734e29dafe Mon Sep 17 00:00:00 2001 From: Max Katz Date: Tue, 25 Oct 2022 18:18:25 -0400 Subject: [PATCH 4/4] Fix multitouch and add historical points support --- src/Web/Avalonia.Web/Avalonia.Web.csproj | 1 + src/Web/Avalonia.Web/AvaloniaView.cs | 113 +++++++++--------- src/Web/Avalonia.Web/BrowserTopLevelImpl.cs | 26 ++-- src/Web/Avalonia.Web/Interop/InputHelper.cs | 4 + .../webapp/modules/avalonia/input.ts | 4 + 5 files changed, 71 insertions(+), 77 deletions(-) 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 = ""; }