Skip to content

Refactor WASM input and dom-callbacks to work with multithreading #15849

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Jun 6, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 12 additions & 23 deletions src/Browser/Avalonia.Browser/BrowserActivatableLifetime.cs
Original file line number Diff line number Diff line change
@@ -1,36 +1,25 @@
using System;
using Avalonia.Browser.Interop;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Threading;

namespace Avalonia.Browser;

internal class BrowserActivatableLifetime : IActivatableLifetime
internal class BrowserActivatableLifetime : ActivatableLifetimeBase
{
public BrowserActivatableLifetime()
{
bool? initiallyVisible = InputHelper.SubscribeVisibilityChange(visible =>
OnVisibilityStateChanged(DomHelper.GetCurrentDocumentVisibility(), true);
}

public void OnVisibilityStateChanged(string visibilityState, bool initial = false)
{
var visible = visibilityState == "visible";
if (visible)
{
initiallyVisible = null;
(visible ? Activated : Deactivated)?.Invoke(this, new ActivatedEventArgs(ActivationKind.Background));
});

// Trigger Activated as an initial state, if web page is visible, and wasn't hidden during initialization.
if (initiallyVisible == true)
OnActivated(ActivationKind.Background);
}
else if (!initial)
{
_ = Dispatcher.UIThread.InvokeAsync(() =>
{
if (initiallyVisible == true)
{
Activated?.Invoke(this, new ActivatedEventArgs(ActivationKind.Background));
}
}, DispatcherPriority.Background);
OnDeactivated(ActivationKind.Background);
}
}

public event EventHandler<ActivatedEventArgs>? Activated;
public event EventHandler<ActivatedEventArgs>? Deactivated;

public bool TryLeaveBackground() => false;
public bool TryEnterBackground() => false;
}
149 changes: 57 additions & 92 deletions src/Browser/Avalonia.Browser/BrowserInputHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ internal class BrowserInputHandler

private static readonly PooledList<RawPointerPoint> s_intermediatePointsPooledList = new(ClearMode.Never);

public BrowserInputHandler(BrowserTopLevelImpl topLevelImpl, JSObject container)
public BrowserInputHandler(BrowserTopLevelImpl topLevelImpl, JSObject container, JSObject inputElement, int topLevelId)
{
_topLevelImpl = topLevelImpl;
_container = container ?? throw new ArgumentNullException(nameof(container));
Expand All @@ -32,40 +32,36 @@ public BrowserInputHandler(BrowserTopLevelImpl topLevelImpl, JSObject container)
_wheelMouseDevice = new MouseDevice();
_mouseDevices = new();

InputHelper.SubscribeKeyEvents(
container,
OnKeyDown,
OnKeyUp);
InputHelper.SubscribePointerEvents(container, OnPointerMove, OnPointerDown, OnPointerUp,
OnPointerCancel, OnWheel);
InputHelper.SubscribeDropEvents(container, OnDragEvent);
TextInputMethod = new BrowserTextInputMethod(this, container, inputElement);
InputPane = new BrowserInputPane();

InputHelper.SubscribeInputEvents(container, topLevelId);
}

public BrowserTextInputMethod TextInputMethod { get; }
public BrowserInputPane InputPane { get; }

public ulong Timestamp => (ulong)_sw.ElapsedMilliseconds;

internal void SetInputRoot(IInputRoot inputRoot)
{
_inputRoot = inputRoot;
}

private static RawPointerPoint ExtractRawPointerFromJsArgs(JSObject args)
private static RawPointerPoint CreateRawPointer(double offsetX, double offsetY,
double pressure, double tiltX, double tiltY, double twist) => new()
{
var point = new RawPointerPoint
{
Position = new Point(args.GetPropertyAsDouble("offsetX"), args.GetPropertyAsDouble("offsetY")),
Pressure = (float)args.GetPropertyAsDouble("pressure"),
XTilt = (float)args.GetPropertyAsDouble("tiltX"),
YTilt = (float)args.GetPropertyAsDouble("tiltY"),
Twist = (float)args.GetPropertyAsDouble("twist")
};

return point;
}

private bool OnPointerMove(JSObject args)
Position = new Point(offsetX, offsetY),
Pressure = (float)pressure,
XTilt = (float)tiltX,
YTilt = (float)tiltY,
Twist = (float)twist
};

public bool OnPointerMove(string pointerType, long pointerId, double offsetX, double offsetY,
double pressure, double tiltX, double tiltY, double twist, int modifier, JSObject argsObj)
{
var pointerType = args.GetPropertyAsString("pointerType");
var point = ExtractRawPointerFromJsArgs(args);
var point = CreateRawPointer(offsetX, offsetY, pressure, tiltX, tiltY, twist);
var type = pointerType switch
{
"touch" => RawPointerEventType.TouchUpdate,
Expand All @@ -74,31 +70,36 @@ private bool OnPointerMove(JSObject args)

var coalescedEvents = new Lazy<IReadOnlyList<RawPointerPoint>?>(() =>
{
var points = InputHelper.GetCoalescedEvents(args);
// To minimize JS interop usage, we resolve all points properties in a single call.
var pointsProps = InputHelper.GetCoalescedEvents(argsObj);
var pointsCount = pointsProps.Length / 6;
argsObj.Dispose();
s_intermediatePointsPooledList.Clear();
s_intermediatePointsPooledList.Capacity = points.Length - 1;
s_intermediatePointsPooledList.Capacity = pointsCount - 1;

// Skip the last one, as it is already processed point.
for (var i = 0; i < points.Length - 1; i++)
for (var i = 0; i < pointsCount - 1; i++)
{
var point = points[i];
s_intermediatePointsPooledList.Add(ExtractRawPointerFromJsArgs(point));
s_intermediatePointsPooledList.Add(CreateRawPointer(
pointsProps[i], pointsProps[i + 1],
pointsProps[i + 2], pointsProps[i + 3],
pointsProps[i + 4], pointsProps[i + 5]));
}

return s_intermediatePointsPooledList;
});

return RawPointerEvent(type, pointerType!, point, GetModifiers(args), args.GetPropertyAsInt32("pointerId"),
return RawPointerEvent(type, pointerType!, point, (RawInputModifiers)modifier, pointerId,
coalescedEvents);
}

private bool OnPointerDown(JSObject args)
public bool OnPointerDown(string pointerType, long pointerId, int buttons, double offsetX, double offsetY,
double pressure, double tiltX, double tiltY, double twist, int modifier)
{
var pointerType = args.GetPropertyAsString("pointerType") ?? "mouse";
var type = pointerType switch
{
"touch" => RawPointerEventType.TouchBegin,
_ => args.GetPropertyAsInt32("button") switch
_ => buttons switch
{
0 => RawPointerEventType.LeftButtonDown,
1 => RawPointerEventType.MiddleButtonDown,
Expand All @@ -110,17 +111,17 @@ private bool OnPointerDown(JSObject args)
}
};

var point = ExtractRawPointerFromJsArgs(args);
return RawPointerEvent(type, pointerType, point, GetModifiers(args), args.GetPropertyAsInt32("pointerId"));
var point = CreateRawPointer(offsetX, offsetY, pressure, tiltX, tiltY, twist);
return RawPointerEvent(type, pointerType, point, (RawInputModifiers)modifier, pointerId);
}

private bool OnPointerUp(JSObject args)
public bool OnPointerUp(string pointerType, long pointerId, int buttons, double offsetX, double offsetY,
double pressure, double tiltX, double tiltY, double twist, int modifier)
{
var pointerType = args.GetPropertyAsString("pointerType") ?? "mouse";
var type = pointerType switch
{
"touch" => RawPointerEventType.TouchEnd,
_ => args.GetPropertyAsInt32("button") switch
_ => buttons switch
{
0 => RawPointerEventType.LeftButtonUp,
1 => RawPointerEventType.MiddleButtonUp,
Expand All @@ -132,65 +133,28 @@ private bool OnPointerUp(JSObject args)
}
};

var point = ExtractRawPointerFromJsArgs(args);
return RawPointerEvent(type, pointerType, point, GetModifiers(args), args.GetPropertyAsInt32("pointerId"));
var point = CreateRawPointer(offsetX, offsetY, pressure, tiltX, tiltY, twist);
return RawPointerEvent(type, pointerType, point, (RawInputModifiers)modifier, pointerId);
}

private bool OnPointerCancel(JSObject args)
public bool OnPointerCancel(string pointerType, long pointerId, double offsetX, double offsetY,
double pressure, double tiltX, double tiltY, double twist, int modifier)
{
var pointerType = args.GetPropertyAsString("pointerType") ?? "mouse";
if (pointerType == "touch")
{
var point = ExtractRawPointerFromJsArgs(args);
var point = CreateRawPointer(offsetX, offsetY, pressure, tiltX, tiltY, twist);
RawPointerEvent(RawPointerEventType.TouchCancel, pointerType, point,
GetModifiers(args), args.GetPropertyAsInt32("pointerId"));
(RawInputModifiers)modifier, pointerId);
}

return false;
}

private bool OnWheel(JSObject args)
{
return RawMouseWheelEvent(new Point(args.GetPropertyAsDouble("offsetX"), args.GetPropertyAsDouble("offsetY")),
new Vector(-(args.GetPropertyAsDouble("deltaX") / 50), -(args.GetPropertyAsDouble("deltaY") / 50)),
GetModifiers(args));
}

private static RawInputModifiers GetModifiers(JSObject e)
public bool OnWheel(double offsetX, double offsetY, double deltaX, double deltaY, int modifier)
{
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;

var buttons = e.GetPropertyAsInt32("buttons");
if ((buttons & 1L) == 1)
modifiers |= RawInputModifiers.LeftMouseButton;

if ((buttons & 2L) == 2)
modifiers |= e.GetPropertyAsString("type") == "pen" ?
RawInputModifiers.PenBarrelButton :
RawInputModifiers.RightMouseButton;

if ((buttons & 4L) == 4)
modifiers |= RawInputModifiers.MiddleMouseButton;

if ((buttons & 8L) == 8)
modifiers |= RawInputModifiers.XButton1MouseButton;

if ((buttons & 16L) == 16)
modifiers |= RawInputModifiers.XButton2MouseButton;

if ((buttons & 32L) == 32)
modifiers |= RawInputModifiers.PenEraser;

return modifiers;
return RawMouseWheelEvent(new Point(offsetX, offsetY),
new Vector(-(deltaX / 50), -(deltaY / 50)),
(RawInputModifiers)modifier);
}

public bool OnDragEvent(JSObject args)
Expand All @@ -214,7 +178,7 @@ public bool OnDragEvent(JSObject args)
_ = AvaloniaModule.ImportStorage();

var position = new Point(args.GetPropertyAsDouble("offsetX"), args.GetPropertyAsDouble("offsetY"));
var modifiers = GetModifiers(args);
var modifiers = RawInputModifiers.None;// GetModifiers(args);

var effectAllowedStr = dataObject.GetPropertyAsString("effectAllowed") ?? "none";
var effectAllowed = DragDropEffects.None;
Expand Down Expand Up @@ -246,13 +210,15 @@ public bool OnDragEvent(JSObject args)
var dropEffect = RawDragEvent(eventType, position, modifiers, new BrowserDataObject(dataObject), effectAllowed);
dataObject.SetProperty("dropEffect", dropEffect.ToString().ToLowerInvariant());

// Note, due to complications of JS interop, we ignore this return value.
// And instead assume, that event is handled for any "drop" and "drag-over" stages.
return eventType is RawDragEventType.Drop or RawDragEventType.DragOver
&& dropEffect != DragDropEffects.None;
}

private bool OnKeyDown(string code, string key, string modifier)
public bool OnKeyDown(string code, string key, int modifier)
{
var handled = RawKeyboardEvent(RawKeyEventType.KeyDown, code, key, (RawInputModifiers)int.Parse(modifier));
var handled = RawKeyboardEvent(RawKeyEventType.KeyDown, code, key, (RawInputModifiers)modifier);

if (!handled && key.Length == 1)
{
Expand All @@ -262,18 +228,17 @@ private bool OnKeyDown(string code, string key, string modifier)
return handled;
}

private bool OnKeyUp(string code, string key, string modifier)
public bool OnKeyUp(string code, string key, int modifier)
{
return RawKeyboardEvent(RawKeyEventType.KeyUp, code, key, (RawInputModifiers)int.Parse(modifier));
return RawKeyboardEvent(RawKeyEventType.KeyUp, code, key, (RawInputModifiers)modifier);
}

private bool RawPointerEvent(
RawPointerEventType eventType, string pointerType,
RawPointerPoint p, RawInputModifiers modifiers, long touchPointId,
Lazy<IReadOnlyList<RawPointerPoint>?>? intermediatePoints = null)
{
if (_inputRoot is { }
&& _topLevelImpl.Input is { } input)
if (_inputRoot is not null && _topLevelImpl.Input is { } input)
{
var device = GetPointerDevice(pointerType, touchPointId);
var args = device is TouchDevice ?
Expand Down
13 changes: 2 additions & 11 deletions src/Browser/Avalonia.Browser/BrowserInputPane.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,11 @@ namespace Avalonia.Browser;

internal class BrowserInputPane : InputPaneBase
{
public BrowserInputPane(JSObject container)
{
InputHelper.SubscribeKeyboardGeometryChange(container, OnGeometryChange);
}

private bool OnGeometryChange(JSObject args)
public bool OnGeometryChange(double x, double y, double width, double height)
{
var oldState = (OccludedRect, State);

OccludedRect = new Rect(
args.GetPropertyAsDouble("x"),
args.GetPropertyAsDouble("y"),
args.GetPropertyAsDouble("width"),
args.GetPropertyAsDouble("height"));
OccludedRect = new Rect(x, y, width, height);
State = OccludedRect.Width != 0 ? InputPaneState.Open : InputPaneState.Closed;

if (oldState != (OccludedRect, State))
Expand Down
5 changes: 0 additions & 5 deletions src/Browser/Avalonia.Browser/BrowserInsetsManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,6 @@ namespace Avalonia.Browser
{
internal class BrowserInsetsManager : InsetsManagerBase
{
public BrowserInsetsManager()
{
DomHelper.InitSafeAreaPadding();
}

public override bool? IsSystemBarVisible
{
get
Expand Down
20 changes: 12 additions & 8 deletions src/Browser/Avalonia.Browser/BrowserPlatformSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,25 @@ public override PlatformColorValues GetColorValues()
};
}

public void OnValuesChanged(bool isDarkMode, bool isHighContrast)
{
_isDarkMode = isDarkMode;
_isHighContrast = isHighContrast;
OnColorValuesChanged(GetColorValues());
}

private void EnsureBackend()
{
if (!_isInitialized)
{
// WASM module has async nature of initialization. We can't native code right away during components registration.
_isInitialized = true;

var obj = DomHelper.ObserveDarkMode((isDarkMode, isHighContrast) =>
var values = DomHelper.GetDarkMode();
if (values.Length == 2)
{
_isDarkMode = isDarkMode;
_isHighContrast = isHighContrast;
OnColorValuesChanged(GetColorValues());
});
_isDarkMode = obj.GetPropertyAsBoolean("isDarkMode");
_isHighContrast = obj.GetPropertyAsBoolean("isHighContrast");
_isDarkMode = values[0] > 0;
_isHighContrast = values[1] > 0;
}
}
}
}
Loading
Loading