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 098b06a0a2..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;
@@ -52,13 +57,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,7 +101,8 @@ namespace Avalonia.Web
OnCompositionUpdate,
OnCompositionEnd);
- InputHelper.SubscribePointerEvents(_containerElement, OnPointerMove, OnPointerDown, OnPointerUp, OnWheel);
+ InputHelper.SubscribePointerEvents(_containerElement, OnPointerMove, OnPointerDown, OnPointerUp,
+ OnPointerCancel, OnWheel);
var skiaOptions = AvaloniaLocator.Current.GetService();
@@ -117,7 +123,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
{
@@ -135,7 +146,7 @@ namespace Avalonia.Web
DomHelper.ObserveSize(host, null, OnSizeChanged);
CanvasHelper.RequestAnimationFrame(_canvas, true);
-
+
InputHelper.FocusElement(_containerElement);
}
@@ -155,17 +166,36 @@ namespace Avalonia.Web
private bool OnPointerMove(JSObject args)
{
- var type = args.GetPropertyAsString("pointertype");
-
+ var pointerType = args.GetPropertyAsString("pointerType");
var point = ExtractRawPointerFromJSArgs(args);
+ var type = pointerType switch
+ {
+ "touch" => RawPointerEventType.TouchUpdate,
+ _ => RawPointerEventType.Move
+ };
+
+ var coalescedEvents = new Lazy?>(() =>
+ {
+ var points = InputHelper.GetCoalescedEvents(args);
+ s_intermediatePointsPooledList.Clear();
+ s_intermediatePointsPooledList.Capacity = points.Length - 1;
+
+ // 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));
+ }
+
+ return s_intermediatePointsPooledList;
+ });
- return _topLevelImpl.RawPointerEvent(RawPointerEventType.Move, type!, point, GetModifiers(args), args.GetPropertyAsInt32("pointerId"));
+ return _topLevelImpl.RawPointerEvent(type, pointerType!, point, GetModifiers(args), args.GetPropertyAsInt32("pointerId"), coalescedEvents);
}
private bool OnPointerDown(JSObject args)
{
- var pointerType = args.GetPropertyAsString("pointerType");
-
+ var pointerType = args.GetPropertyAsString("pointerType") ?? "mouse";
var type = pointerType switch
{
"touch" => RawPointerEventType.TouchBegin,
@@ -176,20 +206,18 @@ namespace Avalonia.Web
2 => RawPointerEventType.RightButtonDown,
3 => RawPointerEventType.XButton1Down,
4 => RawPointerEventType.XButton2Down,
- // 5 => Pen eraser button,
+ 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";
-
var type = pointerType switch
{
"touch" => RawPointerEventType.TouchEnd,
@@ -200,15 +228,27 @@ namespace Avalonia.Web
2 => RawPointerEventType.RightButtonUp,
3 => RawPointerEventType.XButton1Up,
4 => RawPointerEventType.XButton2Up,
- // 5 => Pen eraser button,
+ 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 point = ExtractRawPointerFromJSArgs(args);
+ _topLevelImpl.RawPointerEvent(RawPointerEventType.TouchCancel, pointerType, point,
+ GetModifiers(args), args.GetPropertyAsInt32("pointerId"));
+ }
+
+ return false;
+ }
private bool OnWheel(JSObject args)
{
diff --git a/src/Web/Avalonia.Web/BrowserTopLevelImpl.cs b/src/Web/Avalonia.Web/BrowserTopLevelImpl.cs
index b955da6df2..ed8f417870 100644
--- a/src/Web/Avalonia.Web/BrowserTopLevelImpl.cs
+++ b/src/Web/Avalonia.Web/BrowserTopLevelImpl.cs
@@ -67,17 +67,22 @@ namespace Avalonia.Web
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 cfec9f30dc..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;
@@ -36,6 +37,8 @@ internal static partial class InputHelper
[JSMarshalAs>]
Func pointerUp,
[JSMarshalAs>]
+ Func pointerCancel,
+ [JSMarshalAs>]
Func wheel);
@@ -45,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 faede82e0d..83e8ee7f1c 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,11 +127,13 @@ 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);
};
}
@@ -146,6 +154,10 @@ export class InputHelper {
};
}
+ public static getCoalescedEvents(pointerEvent: PointerEvent): PointerEvent[] {
+ return pointerEvent.getCoalescedEvents();
+ }
+
public static clearInput(inputElement: HTMLInputElement) {
inputElement.value = "";
}