From c8fae36d36047869bac3a1c055ec30a3d0bd9757 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 25 Oct 2022 18:49:09 +0100 Subject: [PATCH] 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