Skip to content

Commit

Permalink
Merge pull request unoplatform#320 from unoplatform/dev/spouliot/poin…
Browse files Browse the repository at this point in the history
…ter-support

fix: complete macos/skia pointer/mouse support
  • Loading branch information
spouliot authored Feb 15, 2024
2 parents afccde3 + 90c6d32 commit c3eeaf8
Show file tree
Hide file tree
Showing 6 changed files with 188 additions and 35 deletions.
102 changes: 78 additions & 24 deletions src/Uno.UI.Runtime.Skia.MacOS/MacOSUnoCorePointerInputSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Windows.Foundation;
using Windows.System;
using Windows.UI.Core;
using Windows.UI.Input;

using Uno.Foundation.Extensibility;
using Uno.Foundation.Logging;
Expand All @@ -16,10 +17,18 @@ namespace Uno.UI.Runtime.Skia.MacOS;

internal class MacOSUnoCorePointerInputSource : IUnoCorePointerInputSource
{
// https://developer.apple.com/documentation/appkit/nseventtype
const int NSEventTypeLeftMouseDown = 1;
const int NSEventTypeRightMouseDown = 2;
const int NSEventTypeOtherMouseDown = 25;

public static MacOSUnoCorePointerInputSource Instance = new();

private CoreCursor? _pointerCursor = new(CoreCursorType.Arrow, 0);

private static Point _previousPosition;
private static PointerPointProperties? _previousProperties;

private MacOSUnoCorePointerInputSource()
{
}
Expand Down Expand Up @@ -63,7 +72,7 @@ public CoreCursor? PointerCursor
}
}

public Point PointerPosition { get; private set; }
public Point PointerPosition => _previousPosition;

#pragma warning disable CS0067
public event TypedEventHandler<object, PointerEventArgs>? PointerCaptureLost;
Expand All @@ -77,54 +86,99 @@ public CoreCursor? PointerCursor
#pragma warning restore CS0067

[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
internal static int MouseEvent(int type, double x, double y, VirtualKeyModifiers mods, PointerDeviceType pdt, uint frameId, ulong timestamp, uint pid)
internal static unsafe int MouseEvent(NativeMouseEventData* data)
{
var position = new Point(x, y);
try
{
TypedEventHandler<object, PointerEventArgs>? evnt = null;
switch (type)
TypedEventHandler<object, PointerEventArgs>? mouseEvent = null;
switch (data->EventType)
{
case 1:
evnt = Instance.PointerEntered;
case NativeMouseEvents.Entered:
mouseEvent = Instance.PointerEntered;
break;
case 2:
evnt = Instance.PointerExited;
case NativeMouseEvents.Exited:
mouseEvent = Instance.PointerExited;
break;
case 3:
evnt = Instance.PointerPressed;
case NativeMouseEvents.Down:
mouseEvent = Instance.PointerPressed;
break;
case 4:
evnt = Instance.PointerReleased;
case NativeMouseEvents.Up:
mouseEvent = Instance.PointerReleased;
break;
case 5:
evnt = Instance.PointerMoved;
case NativeMouseEvents.Moved:
mouseEvent = Instance.PointerMoved;
break;
case 6:
evnt = Instance.PointerWheelChanged;
case NativeMouseEvents.ScrollWheel:
mouseEvent = Instance.PointerWheelChanged;
break;
}
if (evnt is null)
if (mouseEvent is null)
{
return 0; // unhandled
}
// TODO: collect/marshal more data from native
var ppp = new Windows.UI.Input.PointerPointProperties();
var pp = new Windows.UI.Input.PointerPoint(frameId, timestamp, PointerDevice.For(pdt), pid, position, position, false, ppp);
evnt(Instance, new PointerEventArgs(pp, mods));

mouseEvent(Instance, BuildPointerArgs(*data));
return 1; // handled
}
catch (Exception e)
{
Microsoft.UI.Xaml.Application.Current.RaiseRecoverableUnhandledException(e);
return 0;
}
finally
}

private static PointerEventArgs BuildPointerArgs(NativeMouseEventData data)
{
var position = new Point(data.X, data.Y);
var pointerDevice = PointerDevice.For(data.PointerDeviceType);
var properties = GetPointerProperties(data).SetUpdateKindFromPrevious(_previousProperties);

var point = new PointerPoint(data.FrameId, data.Timestamp, pointerDevice, data.Pid, position, position, data.InContact, properties);
var args = new PointerEventArgs(point, data.KeyModifiers);

_previousPosition = position;
_previousProperties = properties;

return args;
}

private static PointerPointProperties GetPointerProperties(NativeMouseEventData data)
{
var properties = new PointerPointProperties()
{
IsInRange = true,
IsPrimary = true,
IsLeftButtonPressed = (data.MouseButtons & NSEventTypeLeftMouseDown) == NSEventTypeLeftMouseDown,
IsRightButtonPressed = (data.MouseButtons & NSEventTypeRightMouseDown) == NSEventTypeRightMouseDown,
IsMiddleButtonPressed = (data.MouseButtons & NSEventTypeOtherMouseDown) == NSEventTypeOtherMouseDown,
};

if (data.PointerDeviceType == PointerDeviceType.Pen)
{
properties.XTilt = data.TiltX;
properties.YTilt = data.TiltY;
properties.Pressure = data.Pressure;
}

if (data.EventType == NativeMouseEvents.ScrollWheel)
{
Instance.PointerPosition = position;
var y = data.ScrollingDeltaY;
if (y == 0)
{
// Note: if X and Y are != 0, we should raise 2 events!
properties.IsHorizontalMouseWheel = true;
properties.MouseWheelDelta = data.ScrollingDeltaX;
}
else
{
properties.MouseWheelDelta = -y;
}
}

return properties;
}


public void ReleasePointerCapture() => LogNotSupported();
public void ReleasePointerCapture(PointerIdentifier p) => LogNotSupported();
public void SetPointerCapture() => LogNotSupported();
Expand Down
42 changes: 41 additions & 1 deletion src/Uno.UI.Runtime.Skia.MacOS/NativeUno.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,46 @@

namespace Uno.UI.Runtime.Skia.MacOS;

// keep in sync with UNOWindow.h
internal enum NativeMouseEvents
{
None,
Entered,
Exited,
Down,
Up,
Moved,
ScrollWheel,
}

// values are set in native code
#pragma warning disable 0649
[StructLayout(LayoutKind.Sequential)]
internal struct NativeMouseEventData
{
public NativeMouseEvents EventType;
public double /* CGFloat */ X;
public double /* CGFloat */ Y;
// mouse
[MarshalAs(UnmanagedType.I4)]
public bool InContact;
public uint MouseButtons;
// pen
public float TiltX;
public float TiltY;
public float Pressure;
// scrollwheel
public int ScrollingDeltaX;
public int ScrollingDeltaY;
// others
public VirtualKeyModifiers KeyModifiers;
public PointerDeviceType PointerDeviceType;
public uint FrameId;
public ulong Timestamp;
public uint Pid;
};
#pragma warning restore 0649

internal static partial class NativeUno
{
[LibraryImport("libUnoNativeMac.dylib")]
Expand Down Expand Up @@ -72,7 +112,7 @@ internal static partial class NativeUno
internal static partial nint uno_window_set_title(nint window, string title);

[LibraryImport("libUnoNativeMac.dylib")]
internal static unsafe partial void uno_set_window_mouse_event_callback(delegate* unmanaged[Cdecl]<int, double, double, VirtualKeyModifiers, PointerDeviceType, uint, ulong, uint, int> callback);
internal static unsafe partial void uno_set_window_mouse_event_callback(delegate* unmanaged[Cdecl]<NativeMouseEventData*, int> callback);

[LibraryImport("libUnoNativeMac.dylib")]
internal static unsafe partial void uno_set_window_should_close_callback(delegate* unmanaged[Cdecl]<int> callback);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ typedef void (*system_theme_change_fn_ptr)(void);
system_theme_change_fn_ptr uno_get_system_theme_change_callback(void);
void uno_set_system_theme_change_callback(system_theme_change_fn_ptr p);
uint32 uno_get_system_theme(void);
NSTimeInterval uno_get_system_uptime(void);

bool uno_app_initialize(bool *supportsMetal);
NSWindow* uno_app_get_main_window(void);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
static UNOApplicationDelegate *ad;
static system_theme_change_fn_ptr system_theme_change;
static id<MTLDevice> device;
static NSTimeInterval uptime = 0;

inline system_theme_change_fn_ptr uno_get_system_theme_change_callback(void)
{
Expand All @@ -27,6 +28,14 @@ void uno_set_system_theme_change_callback(system_theme_change_fn_ptr p)
return [appearanceName isEqualToString:NSAppearanceNameAqua] ? 0 : 1;
}

NSTimeInterval uno_get_system_uptime(void)
{
if (uptime == 0) {
uptime = NSProcessInfo.processInfo.systemUptime;
}
return uptime;
}

bool uno_app_initialize(bool *metal)
{
NSApplication *app = [NSApplication sharedApplication];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ typedef int32_t (*window_key_callback_fn_ptr)(VirtualKey key, VirtualKeyModifier
void uno_set_window_key_down_callback(window_key_callback_fn_ptr p);
void uno_set_window_key_up_callback(window_key_callback_fn_ptr p);

// keep in sync with MacOSUnoCorePointerInputSource.cs
typedef NS_ENUM(uint32, MouseEvents) {
MouseEventsNone,
MouseEventsEntered,
Expand All @@ -240,7 +241,29 @@ typedef NS_ENUM(uint32, PointerDeviceType) {
PointerDeviceTypeMouse,
};

typedef int32_t (*window_mouse_callback_fn_ptr)(MouseEvents eventType, CGFloat x, CGFloat y, VirtualKeyModifiers mods, PointerDeviceType type, uint32 frameId, uint64 timestamp, uint32 pid);
struct MouseEventData {
MouseEvents eventType;
CGFloat x;
CGFloat y;
// mouse
int32_t inContact;
uint32 mouseButtons;
// pen
float tiltX;
float tiltY;
float pressure;
// scrollwheel
int32_t scrollingDeltaX;
int32_t scrollingDeltaY;
// others
VirtualKeyModifiers mods;
PointerDeviceType pointerDeviceType;
uint32 frameId;
uint64 timestamp;
uint32 pid;
};

typedef int32_t (*window_mouse_callback_fn_ptr)(struct MouseEventData *data);
void uno_set_window_mouse_event_callback(window_mouse_callback_fn_ptr p);

typedef bool (*window_should_close_fn_ptr)(void);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -480,8 +480,11 @@ - (void)sendEvent:(NSEvent *)event {
}

if (mouse != MouseEventsNone) {
CGFloat px, py;
if ([self getPositionFrom:event x:&px y:&py]) {
struct MouseEventData data;
memset(&data, 0, sizeof(struct MouseEventData));
data.eventType = mouse;
data.inContact = inContact;
if ([self getPositionFrom:event x:&data.x y:&data.y]) {
#if false
// check subtype for most mouse events
// FIXME: does not work, the mouse also issue the NSEventSubtypeTabletPoint subevent and this cause assertions
Expand All @@ -493,18 +496,41 @@ - (void)sendEvent:(NSEvent *)event {
}
}
#endif
// mouse
data.mouseButtons = (uint32)NSEvent.pressedMouseButtons;

// Pen
if (pdt == PointerDeviceTypePen) {
// do not call if event is not from a pen -> *** Assertion failure in -[NSEvent tilt], NSEvent.m:4625
NSPoint tilt = event.tilt;
data.tiltX = (float)tilt.x;
data.tiltY = (float)tilt.y;
data.pressure = event.pressure;
data.pid = (uint32)event.pointingDeviceID;
} else {
data.pid = 1;
}

// scrollwheel
if (mouse == MouseEventsScrollWheel) {
// do not call if not in the scrollwheel event -> *** Assertion failure in -[NSEvent scrollingDeltaX], NSEvent.m:2202
data.scrollingDeltaX = (int32_t)event.scrollingDeltaX;
data.scrollingDeltaY = (int32_t)event.scrollingDeltaY;
}

// other
NSTimeInterval ts = event.timestamp;

// The precision of the frameId is 10 frame per ms ... which should be enough
uint32 frameId = (uint)(ts * 1000.0 * 10.0);
uint64 bootTime = /* NSDate.now +*/ NSProcessInfo.processInfo.systemUptime * 10000000;
uint64 timestamp = ts * 10000000 + bootTime; // FIXME

uint32 pid = (pdt == PointerDeviceTypePen) ? (uint32) event.pointingDeviceID : 1;
data.frameId = (uint)(ts * 1000.0 * 10.0);

NSDate *now = [[NSDate alloc] init];
NSDate *boot = [[NSDate alloc] initWithTimeInterval:uno_get_system_uptime() sinceDate:now];
data.timestamp = (uint64)(boot.timeIntervalSinceNow * 1000000);

handled = uno_get_window_mouse_event_callback()(mouse, px, py, get_modifiers(event.modifierFlags), pdt, frameId, timestamp, pid);
handled = uno_get_window_mouse_event_callback()(&data);
#if DEBUG_MOUSE // very noisy
NSLog(@"NSEventTypeMouse*: %@ %g %g handled? %s", event, px, py, handled ? "true" : "false");
NSLog(@"NSEventTypeMouse*: %@ %g %g handled? %s", event, data.x, data.y, handled ? "true" : "false");
#endif
}
}
Expand Down

0 comments on commit c3eeaf8

Please sign in to comment.