-
Notifications
You must be signed in to change notification settings - Fork 691
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
|
||
|
@@ -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 | ||
|
@@ -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 | ||
|
@@ -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); | ||
} | ||
|
||
|
@@ -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, | ||
IsPrimary = true, | ||
IsInRange = !stylusEventArgs.InAir, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is actually always There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. From what I remember of my investigations, the
So when we raise a pointer event it's almost always (Note: not sure about the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
}; | ||
|
||
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); | ||
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, | ||
|
@@ -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); | ||
} | ||
|
@@ -301,6 +366,10 @@ | |
return PointerDevice.For(PointerDeviceType.Pen); | ||
} | ||
} | ||
else | ||
{ | ||
return PointerDevice.For(PointerDeviceType.Mouse); | ||
} | ||
} | ||
|
||
private static VirtualKeyModifiers GetKeyModifiers() | ||
|
There was a problem hiding this comment.
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
andIsEraser
)?There was a problem hiding this comment.
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:
There was a problem hiding this comment.
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?