Skip to content
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

feat(wpf): Add Multi-Touch support on WPF platform #16378

Merged
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
143 changes: 106 additions & 37 deletions src/Uno.UI.Runtime.Skia.Wpf/Input/WpfCorePointerInputSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
using Windows.System;
using Windows.UI.Core;
using Windows.UI.Input;
using Point = System.Windows.Point;
using Rect = Windows.Foundation.Rect;
using WpfControl = System.Windows.Controls.Control;
using WpfMouseEventArgs = System.Windows.Input.MouseEventArgs;

Expand Down Expand Up @@ -52,9 +54,15 @@

_hostControl.MouseEnter += HostOnMouseEnter;
_hostControl.MouseLeave += HostOnMouseLeave;

_hostControl.StylusMove += HostControlOnStylusMove;
_hostControl.StylusDown += HostControlOnStylusDown;
_hostControl.StylusUp += HostControlOnStylusUp;

_hostControl.MouseMove += HostOnMouseMove;
_hostControl.MouseDown += HostOnMouseDown;
_hostControl.MouseUp += HostOnMouseUp;

_hostControl.LostMouseCapture += HostOnMouseCaptureLost;

// Hook for native events
Expand Down Expand Up @@ -103,7 +111,7 @@
=> _hostControl.ReleaseMouseCapture();

#region Native events
private void HostOnMouseEvent(WpfMouseEventArgs args, TypedEventHandler<object, PointerEventArgs>? @event, [CallerArgumentExpression(nameof(@event))] string eventName = "")
private void HostOnMouseEvent(InputEventArgs args, TypedEventHandler<object, PointerEventArgs>? @event, [CallerArgumentExpression(nameof(@event))] string eventName = "")
{
var current = SynchronizationContext.Current;
try
Expand Down Expand Up @@ -137,18 +145,39 @@
HostOnMouseEvent(args, PointerExited);
}


private void HostControlOnStylusMove(object sender, StylusEventArgs args) => HostOnMouseEvent(args, PointerMoved);
private void HostControlOnStylusDown(object sender, StylusEventArgs args) => HostOnMouseEvent(args, PointerPressed);
private void HostControlOnStylusUp(object sender, StylusEventArgs args) => HostOnMouseEvent(args, PointerReleased);


private void HostOnMouseMove(object sender, WpfMouseEventArgs args)
{
if (args.StylusDevice != null)
{
return;
}

HostOnMouseEvent(args, PointerMoved);
}

private void HostOnMouseDown(object sender, MouseButtonEventArgs args)
{
if (args.StylusDevice != null)
{
return;
}

HostOnMouseEvent(args, PointerPressed);
}

private void HostOnMouseUp(object sender, MouseButtonEventArgs args)
{
if (args.StylusDevice != null)
{
return;
}

HostOnMouseEvent(args, PointerReleased);
}

Expand Down Expand Up @@ -230,21 +259,86 @@
#endregion

#region Convert helpers
private PointerEventArgs BuildPointerArgs(WpfMouseEventArgs args)
private PointerEventArgs BuildPointerArgs(InputEventArgs args)
{
if (args is null)
{
throw new ArgumentNullException(nameof(args));
}

var position = args.GetPosition(_hostControl);
var properties = BuildPointerProperties(args).SetUpdateKindFromPrevious(_previous?.CurrentPoint.Properties);
Point position;
PointerPointProperties properties;

uint pointerId;
if (args is WpfMouseEventArgs mouseEventArgs)
{
pointerId = 1;
position = mouseEventArgs.GetPosition(_hostControl);
properties = new()
{
IsLeftButtonPressed = mouseEventArgs.LeftButton == MouseButtonState.Pressed,
IsMiddleButtonPressed = mouseEventArgs.MiddleButton == MouseButtonState.Pressed,
IsRightButtonPressed = mouseEventArgs.RightButton == MouseButtonState.Pressed,
IsXButton1Pressed = mouseEventArgs.XButton1 == MouseButtonState.Pressed,
IsXButton2Pressed = mouseEventArgs.XButton2 == MouseButtonState.Pressed,
IsPrimary = true,
IsInRange = true
};
}
else if (args is StylusEventArgs stylusEventArgs)
{
pointerId = (uint)stylusEventArgs.StylusDevice.Id;
position = stylusEventArgs.GetPosition(_hostControl);

properties = new()
{
IsLeftButtonPressed = true,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For touch, this has to be false for release and cancel.
For pen, it has to be true only when !stylusEventArgs.InAir.

Also for the pen do we have information about the barel button and the eraser (for the IsRightButtonPressed and IsEraser)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dr1rrb Yeah. Get the barel button by this code:

        private void MainWindow_StylusMove(object sender, StylusEventArgs e)
        {
            if (e.StylusDevice.StylusButtons.GetStylusButtonByGuid(StylusPointProperties.BarrelButton.Id).StylusButtonState == StylusButtonState.Down)
            {
                
            }
        }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @dr1rrb and I create the PR in #16517 . Could you review my code?

IsPrimary = true,
IsInRange = !stylusEventArgs.InAir,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is actually always true except for some Cancel (and maybe also Release for touch).

Copy link
Contributor Author

@lindexi lindexi Apr 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dr1rrb Will it mean the value of IsInRange is same as IsLeftButtonPressed ? I think it's the opposite of InAir.

Copy link
Member

@dr1rrb dr1rrb May 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From what I remember of my investigations, the IsInRange is about "do we know where the pointer currently is?", no matter if it's pressed or not.

  • For mouse:
    • true if the mouse is still connected on the computer
    • false if the USB cable as been unplugged
  • For pen:
    • true if the pen is in the detectable range (over) or touching the screen (pressed)
    • false if the pen is too far from the screen to be detected
  • For touch:
    • true if the finger is touching the screen
    • false if not touching

So when we raise a pointer event it's almost always true (except some Cancel and Release as mentioned). BUT you can actually keep a reference of a pointer events args (or the Pointer) for longer than the pointer event handler scope! Properties on that args/Pointer will then be updated by WinUI (this is currently not supported by uno BTW), and the IsInRange can become false in those cases.

(Note: not sure about the IsInRange state in windowed environment, when the pointer is above another window)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh and @lindexi,

I think it's the opposite of InAir.

This is the IsInContact

};

var stylusPointCollection = stylusEventArgs.GetStylusPoints(_hostControl);
if (stylusPointCollection.Count > 0)
{
var stylusPoint = stylusPointCollection[0];

properties.Pressure = stylusPoint.PressureFactor;

if (stylusPoint.HasProperty(StylusPointProperties.Width) && stylusPoint.HasProperty(StylusPointProperties.Height))
{
var width = stylusPoint.GetPropertyValue(StylusPointProperties.Width);
var height = stylusPoint.GetPropertyValue(StylusPointProperties.Height);

// Consider enable the ContactRectRaw property.
//properties.ContactRectRaw = new Rect(position.X, position.Y, width, height);

Check warning on line 313 in src/Uno.UI.Runtime.Skia.Wpf/Input/WpfCorePointerInputSource.cs

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/Uno.UI.Runtime.Skia.Wpf/Input/WpfCorePointerInputSource.cs#L313

Remove this commented out code.
properties.ContactRect = new Rect(position.X, position.Y, width, height);
}

if (stylusPoint.HasProperty(StylusPointProperties.XTiltOrientation))
{
var xTilt = stylusPoint.GetPropertyValue(StylusPointProperties.XTiltOrientation);
properties.XTilt = xTilt;
}

if (stylusPoint.HasProperty(StylusPointProperties.YTiltOrientation))
{
var yTilt = stylusPoint.GetPropertyValue(StylusPointProperties.YTiltOrientation);
properties.YTilt = yTilt;
}
}
}
else
{
throw new ArgumentException();
}

properties = properties.SetUpdateKindFromPrevious(_previous?.CurrentPoint.Properties);
var modifiers = GetKeyModifiers();
var point = new PointerPoint(
frameId: FrameIdProvider.GetNextFrameId(),
timestamp: (ulong)(args.Timestamp * TimeSpan.TicksPerMillisecond),
device: GetPointerDevice(args),
pointerId: 1,
pointerId: pointerId,
rawPosition: new Windows.Foundation.Point(position.X, position.Y),
position: new Windows.Foundation.Point(position.X, position.Y),
isInContact: properties.HasPressedButton,
Expand All @@ -254,45 +348,16 @@
return new PointerEventArgs(point, modifiers);
}

private static PointerPointProperties BuildPointerProperties(WpfMouseEventArgs args)
{
if (args is null)
{
throw new ArgumentNullException(nameof(args));
}

return new()
{
IsLeftButtonPressed = args.LeftButton == MouseButtonState.Pressed,
IsMiddleButtonPressed = args.MiddleButton == MouseButtonState.Pressed,
IsRightButtonPressed = args.RightButton == MouseButtonState.Pressed,
IsXButton1Pressed = args.XButton1 == MouseButtonState.Pressed,
IsXButton2Pressed = args.XButton2 == MouseButtonState.Pressed,
IsPrimary = true,
IsInRange = true
};
}

private static PointerDevice GetPointerDevice(WpfMouseEventArgs args)
private static PointerDevice GetPointerDevice(InputEventArgs args)
{
if (args is null)
{
throw new ArgumentNullException(nameof(args));
}

if (args.StylusDevice is null)
if (args is StylusEventArgs stylusEventArgs)
{
return args.Device switch
{
System.Windows.Input.MouseDevice _ => PointerDevice.For(PointerDeviceType.Mouse),
StylusDevice _ => PointerDevice.For(PointerDeviceType.Pen),
TouchDevice _ => PointerDevice.For(PointerDeviceType.Touch),
_ => PointerDevice.For(PointerDeviceType.Mouse),
};
}
else
{
if (args.StylusDevice.TabletDevice?.Type == TabletDeviceType.Touch)
if (stylusEventArgs.StylusDevice.TabletDevice?.Type == TabletDeviceType.Touch)
{
return PointerDevice.For(PointerDeviceType.Touch);
}
Expand All @@ -301,6 +366,10 @@
return PointerDevice.For(PointerDeviceType.Pen);
}
}
else
{
return PointerDevice.For(PointerDeviceType.Mouse);
}
}

private static VirtualKeyModifiers GetKeyModifiers()
Expand Down