diff --git a/src/SamplesApp/UITests.Shared/UITests.Shared.projitems b/src/SamplesApp/UITests.Shared/UITests.Shared.projitems index 55205d5a899e..cb7f2da5b0e7 100644 --- a/src/SamplesApp/UITests.Shared/UITests.Shared.projitems +++ b/src/SamplesApp/UITests.Shared/UITests.Shared.projitems @@ -4266,6 +4266,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + Designer MSBuild:Compile @@ -8238,6 +8242,9 @@ PointerEventArgsTests.xaml + + PointerEvent_Timestamp.xaml + DragCoordinates_Automated.xaml diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Xaml/DragAndDrop/DragDrop_Basics.xaml.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml/DragAndDrop/DragDrop_Basics.xaml.cs index 358f27386660..5b0895c7e68d 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Xaml/DragAndDrop/DragDrop_Basics.xaml.cs +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml/DragAndDrop/DragDrop_Basics.xaml.cs @@ -43,7 +43,7 @@ private static void SleepOnTouchDown(object sender, PointerRoutedEventArgs e) { // Ugly hack: The test engine does not allows us to perform a custom gesture (hold for 300 ms then drag) // So we just freeze the UI thread enough to simulate the delay ... - const int holdDelay = 300 /* GestureRecognizer.DragWithTouchMinDelayTicks */ + 50 /* safety */; + const int holdDelay = 300 /* GestureRecognizer.DragWithTouchMinDelayMicroseconds */ + 50 /* safety */; Thread.Sleep(holdDelay); } } diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Xaml/DragAndDrop/DragDrop_Nested.xaml.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml/DragAndDrop/DragDrop_Nested.xaml.cs index 3bb33644da5d..9ba6f313cf46 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Xaml/DragAndDrop/DragDrop_Nested.xaml.cs +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml/DragAndDrop/DragDrop_Nested.xaml.cs @@ -41,7 +41,7 @@ protected override void OnPointerPressed(PointerRoutedEventArgs e) { // Ugly hack: The test engine does not allows us to perform a custom gesture (hold for 300 ms then drag) // So we just freeze the UI thread enough to simulate the delay ... - const int holdDelay = 300 /* GestureRecognizer.DragWithTouchMinDelayTicks */ + 50 /* safety */; + const int holdDelay = 300 /* GestureRecognizer.DragWithTouchMinDelayMicroseconds */ + 50 /* safety */; Thread.Sleep(holdDelay); } diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Input/Pointers/PointerEvent_Timestamp.xaml b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Input/Pointers/PointerEvent_Timestamp.xaml new file mode 100644 index 000000000000..c74efebaf40d --- /dev/null +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Input/Pointers/PointerEvent_Timestamp.xaml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Input/Pointers/PointerEvent_Timestamp.xaml.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Input/Pointers/PointerEvent_Timestamp.xaml.cs new file mode 100644 index 000000000000..1fa9862df39c --- /dev/null +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Input/Pointers/PointerEvent_Timestamp.xaml.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices.WindowsRuntime; +using Uno.UI.Samples.Controls; +using Windows.Foundation; +using Windows.Foundation.Collections; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Controls.Primitives; +using Microsoft.UI.Xaml.Data; +using Microsoft.UI.Xaml.Input; +using Microsoft.UI.Xaml.Media; +using Microsoft.UI.Xaml.Navigation; +using System.Collections.ObjectModel; + +namespace UITests.Shared.Windows_UI_Xaml_Input.Pointers +{ + [SampleControlInfo( + "Pointers", + Description = + "Click the red rectangle repeatedly. You should see tickmarks in the logs (✔️) indicating that time delta matches timestamp delta.", + IsManualTest = true)] + public sealed partial class PointerEvent_Timestamp : UserControl + { + private ulong? _lastTimestamp; + private uint? _lastFrameId; + private double? _lastElapsedTime; + private readonly Stopwatch _stopwatch = new(); + + public PointerEvent_Timestamp() + { + this.InitializeComponent(); + TestBorder.PointerPressed += PointerEventArgsTests_PointerPressed; + _stopwatch.Start(); + Unloaded += (s, e) => _stopwatch.Stop(); + } + + public ObservableCollection Logs { get; } = new ObservableCollection(); + + private void PointerEventArgsTests_PointerPressed(object sender, PointerRoutedEventArgs e) + { + var point = e.GetCurrentPoint(TestBorder); + var timestamp = point.Timestamp; + var frameId = point.FrameId; + var time = _stopwatch.Elapsed.TotalMicroseconds; + + var log = $"Timestamp: {timestamp}, FrameId: {frameId}" + Environment.NewLine; + if (_lastTimestamp.HasValue) + { + var timeDelta = (ulong)(time - _lastElapsedTime.Value); + var timestampDelta = (timestamp - _lastTimestamp.Value); + log += $"Time Δ: {timeDelta}"; + + // As long as the delta differs by less than 100ms, it probably is correct. + var seemsCorrect = Math.Abs((double)timeDelta - timestampDelta) < 50_000; + log += $", Timestamp Δ: {timestampDelta} {(seemsCorrect ? "✔️" : "❌")}"; + + var frameIdDelta = frameId - _lastFrameId.Value; + log += $", FrameId Δ: {frameIdDelta}"; + } + _lastElapsedTime = time; + _lastTimestamp = timestamp; + _lastFrameId = frameId; + Logs.Add(log); + } + } +} diff --git a/src/Uno.UI.Runtime.Skia.Gtk/Input/GtkCorePointerInputSource.cs b/src/Uno.UI.Runtime.Skia.Gtk/Input/GtkCorePointerInputSource.cs index c163cb6e5185..a3cbcb4170fd 100644 --- a/src/Uno.UI.Runtime.Skia.Gtk/Input/GtkCorePointerInputSource.cs +++ b/src/Uno.UI.Runtime.Skia.Gtk/Input/GtkCorePointerInputSource.cs @@ -501,10 +501,10 @@ private void UseDevice(PointerPoint pointer, Gdk.Device device) } properties.IsInRange = true; - + var timeInMicroseconds = time * 1000; var pointerPoint = new Windows.UI.Input.PointerPoint( frameId: time, - timestamp: time * (ulong)TimeSpan.TicksPerMillisecond, // time is in ms, timestamp is in ticks + timestamp: timeInMicroseconds, device: pointerDevice, pointerId: pointerId, rawPosition: rawPosition, diff --git a/src/Uno.UI.Runtime.Skia.Linux.FrameBuffer/FrameBufferPointerInputSource.Mouse.cs b/src/Uno.UI.Runtime.Skia.Linux.FrameBuffer/FrameBufferPointerInputSource.Mouse.cs index 84522357b42e..3d8fa5ac1359 100644 --- a/src/Uno.UI.Runtime.Skia.Linux.FrameBuffer/FrameBufferPointerInputSource.Mouse.cs +++ b/src/Uno.UI.Runtime.Skia.Linux.FrameBuffer/FrameBufferPointerInputSource.Mouse.cs @@ -102,9 +102,10 @@ double GetAxisValue(libinput_pointer_axis axis) properties.IsMiddleButtonPressed = _pointerPressed.Contains(libinput_event_code.BTN_MIDDLE); properties.IsRightButtonPressed = _pointerPressed.Contains(libinput_event_code.BTN_RIGHT); + var timestampInMicroseconds = timestamp; var pointerPoint = new Windows.UI.Input.PointerPoint( frameId: (uint)timestamp, // UNO TODO: How should set the frame, timestamp may overflow. - timestamp: timestamp * TimeSpan.TicksPerMicrosecond, + timestamp: timestampInMicroseconds, device: PointerDevice.For(PointerDeviceType.Mouse), pointerId: 0, rawPosition: _mousePosition, diff --git a/src/Uno.UI.Runtime.Skia.Linux.FrameBuffer/FrameBufferPointerInputSource.Touch.cs b/src/Uno.UI.Runtime.Skia.Linux.FrameBuffer/FrameBufferPointerInputSource.Touch.cs index c261c91a1ff4..9106000a6a56 100644 --- a/src/Uno.UI.Runtime.Skia.Linux.FrameBuffer/FrameBufferPointerInputSource.Touch.cs +++ b/src/Uno.UI.Runtime.Skia.Linux.FrameBuffer/FrameBufferPointerInputSource.Touch.cs @@ -83,9 +83,10 @@ public void ProcessTouchEvent(IntPtr rawEvent, libinput_event_type rawEventType) properties.IsLeftButtonPressed = rawEventType != LIBINPUT_EVENT_TOUCH_UP && rawEventType != LIBINPUT_EVENT_TOUCH_CANCEL; + var timestampInMicroseconds = timestamp; var pointerPoint = new Windows.UI.Input.PointerPoint( frameId: (uint)timestamp, // UNO TODO: How should set the frame, timestamp may overflow. - timestamp: timestamp * TimeSpan.TicksPerMicrosecond, + timestamp: timestampInMicroseconds, device: PointerDevice.For(PointerDeviceType.Touch), pointerId: pointerId, rawPosition: currentPosition, diff --git a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOApplication.h b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOApplication.h index 0fb4900ab266..d2f5766b7e81 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOApplication.h +++ b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOApplication.h @@ -12,7 +12,6 @@ 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); diff --git a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOApplication.m b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOApplication.m index 5eac3ebf1a47..029056058d55 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOApplication.m +++ b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOApplication.m @@ -7,7 +7,6 @@ static UNOApplicationDelegate *ad; static system_theme_change_fn_ptr system_theme_change; static id device; -static NSTimeInterval uptime = 0; inline system_theme_change_fn_ptr uno_get_system_theme_change_callback(void) { @@ -28,14 +27,6 @@ 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]; diff --git a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOWindow.m b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOWindow.m index 9d9a6ddc5142..528cd0e6d481 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOWindow.m +++ b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOWindow.m @@ -912,10 +912,7 @@ - (void)sendEvent:(NSEvent *)event { NSTimeInterval ts = event.timestamp; data.frameId = (uint)(ts * 10.0); - - NSDate *now = [[NSDate alloc] init]; - NSDate *boot = [[NSDate alloc] initWithTimeInterval:uno_get_system_uptime() sinceDate:now]; - data.timestamp = (uint64)(boot.timeIntervalSinceNow * 1000000); + data.timestamp = (uint64)(ts * 1000000); handled = uno_get_window_mouse_event_callback()(self, &data); #if DEBUG_MOUSE // very noisy diff --git a/src/Uno.UI.Runtime.Skia.Wpf/Input/WpfCorePointerInputSource.cs b/src/Uno.UI.Runtime.Skia.Wpf/Input/WpfCorePointerInputSource.cs index 8b9524aa07c1..bf31f2fed63e 100644 --- a/src/Uno.UI.Runtime.Skia.Wpf/Input/WpfCorePointerInputSource.cs +++ b/src/Uno.UI.Runtime.Skia.Wpf/Input/WpfCorePointerInputSource.cs @@ -296,7 +296,7 @@ private IntPtr OnWmMessage(IntPtr hwnd, int msg, IntPtr wparamOriginal, IntPtr l var point = new Windows.UI.Input.PointerPoint( frameId: FrameIdProvider.GetNextFrameId(), - timestamp: (ulong)Environment.TickCount, + timestamp: (ulong)(Environment.TickCount64 * 1000), device: PointerDevice.For(PointerDeviceType.Mouse), pointerId: 1, rawPosition: position, @@ -406,11 +406,12 @@ private PointerEventArgs BuildPointerArgs(InputEventArgs args, bool? isReleaseOr throw new ArgumentException(); } + var timestampInMicroseconds = (ulong)(args.Timestamp * 1000); properties = properties.SetUpdateKindFromPrevious(_previous?.CurrentPoint.Properties); var modifiers = GetKeyModifiers(); var point = new PointerPoint( frameId: FrameIdProvider.GetNextFrameId(), - timestamp: (ulong)(args.Timestamp * TimeSpan.TicksPerMillisecond), + timestamp: timestampInMicroseconds, device: GetPointerDevice(args), pointerId: pointerId, rawPosition: new Windows.Foundation.Point(position.X, position.Y), diff --git a/src/Uno.UI.Runtime.Skia.X11/X11PointerInputSource.CoreProtocol.cs b/src/Uno.UI.Runtime.Skia.X11/X11PointerInputSource.CoreProtocol.cs index 9c241394073b..937fdcf3696a 100644 --- a/src/Uno.UI.Runtime.Skia.X11/X11PointerInputSource.CoreProtocol.cs +++ b/src/Uno.UI.Runtime.Skia.X11/X11PointerInputSource.CoreProtocol.cs @@ -123,9 +123,10 @@ private PointerPoint CreatePointFromCurrentState(IntPtr time) ? root.RasterizationScale : 1; + var timeInMicroseconds = (ulong)(time * 1000); // Time is given in milliseconds since system boot. See also: https://github.com/unoplatform/uno/issues/14535 var point = new PointerPoint( frameId: (uint)time, // UNO TODO: How should set the frame, timestamp may overflow. - timestamp: (uint)(time * TimeSpan.TicksPerMillisecond), // Time is given in milliseconds since system boot. See also: https://github.com/unoplatform/uno/issues/14535 + timestamp: timeInMicroseconds, PointerDevice.For(PointerDeviceType.Mouse), 0, // TODO: XInput new Point(_mousePosition.X / scale, _mousePosition.Y / scale), diff --git a/src/Uno.UI.Runtime.Skia.X11/X11PointerInputSource.XInput.cs b/src/Uno.UI.Runtime.Skia.X11/X11PointerInputSource.XInput.cs index a8f20948e6cb..52bfdf30ba4b 100644 --- a/src/Uno.UI.Runtime.Skia.X11/X11PointerInputSource.XInput.cs +++ b/src/Uno.UI.Runtime.Skia.X11/X11PointerInputSource.XInput.cs @@ -305,11 +305,14 @@ public unsafe PointerEventArgs CreatePointerEventArgsFromDeviceEvent(XIDeviceEve ? XamlRoot.GetDisplayInformation(root).RawPixelsPerViewPixel : 1; + var timeInMicroseconds = (ulong)(data.time * 1000); // Time is given in milliseconds since system boot. See also: https://github.com/unoplatform/uno/issues/14535 + var deviceType = data.evtype is XiEventType.XI_TouchBegin or XiEventType.XI_TouchEnd or XiEventType.XI_TouchUpdate ? PointerDeviceType.Touch : PointerDeviceType.Mouse; + var pointerId = (uint)(data.evtype is XiEventType.XI_TouchBegin or XiEventType.XI_TouchEnd or XiEventType.XI_TouchUpdate ? data.detail : data.sourceid); // for touch, data.detail is the touch ID var point = new PointerPoint( frameId: (uint)data.time, // UNO TODO: How should we set the frame, timestamp may overflow. - timestamp: (uint)(data.time * TimeSpan.TicksPerMillisecond), // Time is given in milliseconds since system boot. See also: https://github.com/unoplatform/uno/issues/14535 - PointerDevice.For(data.evtype is XiEventType.XI_TouchBegin or XiEventType.XI_TouchEnd or XiEventType.XI_TouchUpdate ? PointerDeviceType.Touch : PointerDeviceType.Mouse), - (uint)(data.evtype is XiEventType.XI_TouchBegin or XiEventType.XI_TouchEnd or XiEventType.XI_TouchUpdate ? data.detail : data.sourceid), // for touch, data.detail is the touch ID + timestamp: timeInMicroseconds, + PointerDevice.For(deviceType), + pointerId, new Point(data.event_x / scale, data.event_y / scale), new Point(data.event_x / scale, data.event_y / scale), properties.HasPressedButton, @@ -345,9 +348,10 @@ public unsafe PointerEventArgs CreatePointerEventArgsFromEnterLeaveEvent(XIEnter IsHorizontalMouseWheel = false, }; + var timestampInMicroseconds = (ulong)(data.time * 1000); // Time is given in milliseconds since system boot. See also: https://github.com/unoplatform/uno/issues/14535 var point = new PointerPoint( frameId: (uint)data.time, // UNO TODO: How should we set the frame, timestamp may overflow. - timestamp: (ulong)data.time, + timestamp: timestampInMicroseconds, PointerDevice.For(PointerDeviceType.Mouse), (uint)data.sourceid, new Point(data.event_x, data.event_y), diff --git a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_TextBlock.cs b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_TextBlock.cs index b4e54ec9d649..e9566393b64b 100644 --- a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_TextBlock.cs +++ b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_TextBlock.cs @@ -1144,6 +1144,7 @@ public async Task When_IsTextSelectionEnabled_SurrogatePair_Copy() #endif public async Task When_IsTextSelectionEnabled_CRLF() { + var delayToAvoidDoubleTap = 600; var SUT = new TextBlock { Text = "FirstLine\r\n Second", @@ -1165,7 +1166,7 @@ public async Task When_IsTextSelectionEnabled_CRLF() mouse.Release(); mouse.Press(); mouse.Release(); - await WindowHelper.WaitForIdle(); + await Task.Delay(delayToAvoidDoubleTap); SUT.CopySelectionToClipboard(); await WindowHelper.WaitForIdle(); @@ -1180,7 +1181,7 @@ public async Task When_IsTextSelectionEnabled_CRLF() mouse.Release(); mouse.Press(); mouse.Release(); - await WindowHelper.WaitForIdle(); + await Task.Delay(delayToAvoidDoubleTap); SUT.CopySelectionToClipboard(); await WindowHelper.WaitForIdle(); diff --git a/src/Uno.UI.Tests/Windows_UI_Input/Given_GestureRecognizer.cs b/src/Uno.UI.Tests/Windows_UI_Input/Given_GestureRecognizer.cs index ece88216c482..c109abe69e72 100644 --- a/src/Uno.UI.Tests/Windows_UI_Input/Given_GestureRecognizer.cs +++ b/src/Uno.UI.Tests/Windows_UI_Input/Given_GestureRecognizer.cs @@ -20,6 +20,8 @@ namespace Uno.UI.Tests.Windows_UI_Input [TestClass] public class Given_GestureRecognizer { + private const int MicrosecondsPerMillisecond = 1000; + private const GestureSettings ManipulationsWithoutInertia = GestureSettings.ManipulationTranslateX | GestureSettings.ManipulationTranslateY | GestureSettings.ManipulationTranslateRailsX @@ -162,7 +164,7 @@ public void DoubleTapped_Duration() taps.Should().BeEquivalentTo(Tap(25, 25)); // Double tapped - var tooSlow = GetPoint(25, 25, ts: 1 + GestureRecognizer.MultiTapMaxDelayTicks + 1); + var tooSlow = GetPoint(25, 25, ts: 1 + GestureRecognizer.MultiTapMaxDelayMicroseconds + 1); sut.CanBeDoubleTap(tooSlow).Should().BeFalse(); sut.ProcessDownEvent(tooSlow); @@ -1002,8 +1004,8 @@ public void Manipulation_Inertia_Translate() // flick at 2 px/ms sut.ProcessDownEvent(10, 10, ts: 0); - sut.ProcessMoveEvent(100, 100, ts: 100 * TimeSpan.TicksPerMillisecond); - sut.ProcessUpEvent(102, 102, ts: 101 * TimeSpan.TicksPerMillisecond); + sut.ProcessMoveEvent(100, 100, ts: 100 * MicrosecondsPerMillisecond); + sut.ProcessUpEvent(102, 102, ts: 101 * MicrosecondsPerMillisecond); sut.RunInertiaSync(); @@ -1049,8 +1051,8 @@ public void Manipulation_Inertia_TranslateXOnly() // flick at 2 px/ms sut.ProcessDownEvent(10, 10, ts: 0); - sut.ProcessMoveEvent(100, 100, ts: 100 * TimeSpan.TicksPerMillisecond); - sut.ProcessUpEvent(102, 102, ts: 101 * TimeSpan.TicksPerMillisecond); + sut.ProcessMoveEvent(100, 100, ts: 100 * MicrosecondsPerMillisecond); + sut.ProcessUpEvent(102, 102, ts: 101 * MicrosecondsPerMillisecond); sut.RunInertiaSync(); @@ -1096,8 +1098,8 @@ public void Manipulation_Inertia_TranslateYOnly() // flick at 2 px/ms sut.ProcessDownEvent(10, 10, ts: 0); - sut.ProcessMoveEvent(100, 100, ts: 100 * TimeSpan.TicksPerMillisecond); - sut.ProcessUpEvent(102, 102, ts: 101 * TimeSpan.TicksPerMillisecond); + sut.ProcessMoveEvent(100, 100, ts: 100 * MicrosecondsPerMillisecond); + sut.ProcessUpEvent(102, 102, ts: 101 * MicrosecondsPerMillisecond); sut.RunInertiaSync(); @@ -1143,8 +1145,8 @@ public void Manipulation_Inertia_Translate_Negative() // flick at 2 px/ms sut.ProcessDownEvent(10, 10, ts: 0); - sut.ProcessMoveEvent(100, 100, ts: 100 * TimeSpan.TicksPerMillisecond); - sut.ProcessUpEvent(98, 98, ts: 101 * TimeSpan.TicksPerMillisecond); + sut.ProcessMoveEvent(100, 100, ts: 100 * MicrosecondsPerMillisecond); + sut.ProcessUpEvent(98, 98, ts: 101 * MicrosecondsPerMillisecond); sut.RunInertiaSync(); @@ -1192,8 +1194,8 @@ public void Manipulation_Inertia_RotateOnly_Trigonometric_InFirstQuadrant() // Rotate of pi/2 in a quarter of second sut.ProcessDownEvent(50, -25, id: 2, ts: 1); // Angle = 0 - sut.ProcessMoveEvent(50, -50, id: 2, ts: 100 * TimeSpan.TicksPerMillisecond); // Angle = -Pi/4 - sut.ProcessUpEvent(25, -50, id: 2, ts: 600 * TimeSpan.TicksPerMillisecond); // Angle = -Pi/2 + sut.ProcessMoveEvent(50, -50, id: 2, ts: 100 * MicrosecondsPerMillisecond); // Angle = -Pi/4 + sut.ProcessUpEvent(25, -50, id: 2, ts: 600 * MicrosecondsPerMillisecond); // Angle = -Pi/2 sut.RunInertiaSync(); @@ -1222,8 +1224,8 @@ public void Manipulation_Inertia_RotateOnly_Trigonometric_InSecondQuadrant() // Rotate of Pi/4 in a quarter of second sut.ProcessDownEvent(-25, -50, id: 2, ts: 1); // Angle = 0 - sut.ProcessMoveEvent(-50, -50, id: 2, ts: 100 * TimeSpan.TicksPerMillisecond); // Angle = -Pi/4 - sut.ProcessUpEvent(-50, -25, id: 2, ts: 600 * TimeSpan.TicksPerMillisecond); // Angle = -Pi/2 + sut.ProcessMoveEvent(-50, -50, id: 2, ts: 100 * MicrosecondsPerMillisecond); // Angle = -Pi/4 + sut.ProcessUpEvent(-50, -25, id: 2, ts: 600 * MicrosecondsPerMillisecond); // Angle = -Pi/2 sut.RunInertiaSync(); @@ -1252,8 +1254,8 @@ public void Manipulation_Inertia_RotateOnly_Trigonometric_InThirdQuadrant() // Rotate of pi/2 in a quarter of second sut.ProcessDownEvent(-50, 25, id: 2, ts: 1); // Angle = 0 - sut.ProcessMoveEvent(-50, 50, id: 2, ts: 100 * TimeSpan.TicksPerMillisecond); // Angle = -Pi/4 - sut.ProcessUpEvent(-25, 50, id: 2, ts: 600 * TimeSpan.TicksPerMillisecond); // Angle = -Pi/2 + sut.ProcessMoveEvent(-50, 50, id: 2, ts: 100 * MicrosecondsPerMillisecond); // Angle = -Pi/4 + sut.ProcessUpEvent(-25, 50, id: 2, ts: 600 * MicrosecondsPerMillisecond); // Angle = -Pi/2 sut.RunInertiaSync(); @@ -1282,8 +1284,8 @@ public void Manipulation_Inertia_RotateOnly_Trigonometric_InForthQuadrant() // Rotate of pi/2 in a quarter of second sut.ProcessDownEvent(25, 50, id: 2, ts: 1); // Angle = 0 - sut.ProcessMoveEvent(50, 50, id: 2, ts: 100 * TimeSpan.TicksPerMillisecond); // Angle = -Pi/4 - sut.ProcessUpEvent(50, 25, id: 2, ts: 600 * TimeSpan.TicksPerMillisecond); // Angle = -Pi/2 + sut.ProcessMoveEvent(50, 50, id: 2, ts: 100 * MicrosecondsPerMillisecond); // Angle = -Pi/4 + sut.ProcessUpEvent(50, 25, id: 2, ts: 600 * MicrosecondsPerMillisecond); // Angle = -Pi/2 sut.RunInertiaSync(); @@ -1312,8 +1314,8 @@ public void Manipulation_Inertia_RotateOnly_AntiTrigonometric_InFirstQuadrant() sut.ProcessDownEvent(25, -25, id: 1, ts: 0); sut.ProcessDownEvent(25, -50, id: 2, ts: 1); // Angle = 0 - sut.ProcessMoveEvent(50, -50, id: 2, ts: 100 * TimeSpan.TicksPerMillisecond); // Angle = -Pi/4 - sut.ProcessUpEvent(50, -25, id: 2, ts: 600 * TimeSpan.TicksPerMillisecond); // Angle = -Pi/2 + sut.ProcessMoveEvent(50, -50, id: 2, ts: 100 * MicrosecondsPerMillisecond); // Angle = -Pi/4 + sut.ProcessUpEvent(50, -25, id: 2, ts: 600 * MicrosecondsPerMillisecond); // Angle = -Pi/2 sut.RunInertiaSync(); @@ -1341,8 +1343,8 @@ public void Manipulation_Inertia_RotateOnly_AntiTrigonometric_InSecondQuadrant() sut.ProcessDownEvent(-25, -25, id: 1, ts: 0); sut.ProcessDownEvent(-50, -25, id: 2, ts: 1); // Angle = 0 - sut.ProcessMoveEvent(-50, -50, id: 2, ts: 100 * TimeSpan.TicksPerMillisecond); // Angle = -Pi/4 - sut.ProcessUpEvent(-25, -50, id: 2, ts: 600 * TimeSpan.TicksPerMillisecond); // Angle = -Pi/2 + sut.ProcessMoveEvent(-50, -50, id: 2, ts: 100 * MicrosecondsPerMillisecond); // Angle = -Pi/4 + sut.ProcessUpEvent(-25, -50, id: 2, ts: 600 * MicrosecondsPerMillisecond); // Angle = -Pi/2 sut.RunInertiaSync(); @@ -1371,8 +1373,8 @@ public void Manipulation_Inertia_RotateOnly_AntiTrigonometric_InThirdQuadrant() sut.ProcessDownEvent(-25, 25, id: 1, ts: 0); sut.ProcessDownEvent(-25, 50, id: 2, ts: 1); // Angle = 0 - sut.ProcessMoveEvent(-50, 50, id: 2, ts: 100 * TimeSpan.TicksPerMillisecond); // Angle = -Pi/4 - sut.ProcessUpEvent(-50, 25, id: 2, ts: 600 * TimeSpan.TicksPerMillisecond); // Angle = -Pi/2 + sut.ProcessMoveEvent(-50, 50, id: 2, ts: 100 * MicrosecondsPerMillisecond); // Angle = -Pi/4 + sut.ProcessUpEvent(-50, 25, id: 2, ts: 600 * MicrosecondsPerMillisecond); // Angle = -Pi/2 sut.RunInertiaSync(); @@ -1401,8 +1403,8 @@ public void Manipulation_Inertia_RotateOnly_AntiTrigonometric_InForthQuadrant() sut.ProcessDownEvent(25, 25, id: 1, ts: 0); sut.ProcessDownEvent(50, 25, id: 2, ts: 1); // Angle = 0 - sut.ProcessMoveEvent(50, 50, id: 2, ts: 100 * TimeSpan.TicksPerMillisecond); // Angle = -Pi/4 - sut.ProcessUpEvent(25, 50, id: 2, ts: 600 * TimeSpan.TicksPerMillisecond); // Angle = -Pi/2 + sut.ProcessMoveEvent(50, 50, id: 2, ts: 100 * MicrosecondsPerMillisecond); // Angle = -Pi/4 + sut.ProcessUpEvent(25, 50, id: 2, ts: 600 * MicrosecondsPerMillisecond); // Angle = -Pi/2 sut.RunInertiaSync(); @@ -1449,8 +1451,8 @@ public void Drag_Started_Mouse_Hold() // Start mouse dragging sut.ProcessDownEvent(25, 25, ts: 0); - sut.ProcessMoveEvent(26, 26, ts: GestureRecognizer.DragWithTouchMinDelayTicks + 1); - var start = sut.ProcessMoveEvent(50, 50, ts: GestureRecognizer.DragWithTouchMinDelayTicks + 2); + sut.ProcessMoveEvent(26, 26, ts: GestureRecognizer.DragWithTouchMinDelayMicroseconds + 1); + var start = sut.ProcessMoveEvent(50, 50, ts: GestureRecognizer.DragWithTouchMinDelayMicroseconds + 2); drags.Should().BeEquivalentTo(Drag(start, DraggingState.Started)); } @@ -1480,8 +1482,8 @@ public void Drag_Started_Pen_Hold() using var _ = Pen(); sut.ProcessDownEvent(25, 25, ts: 0); - sut.ProcessMoveEvent(26, 26, ts: GestureRecognizer.DragWithTouchMinDelayTicks + 1); - var start = sut.ProcessMoveEvent(50, 50, ts: GestureRecognizer.DragWithTouchMinDelayTicks + 2); + sut.ProcessMoveEvent(26, 26, ts: GestureRecognizer.DragWithTouchMinDelayMicroseconds + 1); + var start = sut.ProcessMoveEvent(50, 50, ts: GestureRecognizer.DragWithTouchMinDelayMicroseconds + 2); drags.Should().BeEquivalentTo(Drag(start, DraggingState.Started)); } @@ -1511,8 +1513,8 @@ public void Drag_Started_Touch_Hold() using var _ = Touch(); sut.ProcessDownEvent(25, 25, ts: 0); - sut.ProcessMoveEvent(26, 26, ts: GestureRecognizer.DragWithTouchMinDelayTicks + 1); - var start = sut.ProcessMoveEvent(50, 50, ts: GestureRecognizer.DragWithTouchMinDelayTicks + 2); + sut.ProcessMoveEvent(26, 26, ts: GestureRecognizer.DragWithTouchMinDelayMicroseconds + 1); + var start = sut.ProcessMoveEvent(50, 50, ts: GestureRecognizer.DragWithTouchMinDelayMicroseconds + 2); drags.Should().BeEquivalentTo(Drag(start, DraggingState.Started)); } @@ -1529,8 +1531,8 @@ public void Drag_Started_Touch_MovedTooFar() sut.ProcessDownEvent(25, 25, ts: 0); sut.ProcessMoveEvent(50, 50, ts: 1); sut.ProcessMoveEvent(25, 25, ts: 2); - sut.ProcessMoveEvent(25, 25, ts: GestureRecognizer.DragWithTouchMinDelayTicks + 1); - sut.ProcessMoveEvent(50, 50, ts: GestureRecognizer.DragWithTouchMinDelayTicks + 2); + sut.ProcessMoveEvent(25, 25, ts: GestureRecognizer.DragWithTouchMinDelayMicroseconds + 1); + sut.ProcessMoveEvent(50, 50, ts: GestureRecognizer.DragWithTouchMinDelayMicroseconds + 2); drags.Should().BeEmpty(); } @@ -1545,10 +1547,10 @@ public void Drag_CompleteGesture() using var _ = Touch(); sut.ProcessDownEvent(25, 25, ts: 0); - sut.ProcessMoveEvent(26, 26, ts: GestureRecognizer.DragWithTouchMinDelayTicks + 1); - var start = sut.ProcessMoveEvent(50, 50, ts: GestureRecognizer.DragWithTouchMinDelayTicks + 2); - var move = sut.ProcessMoveEvent(51, 51, ts: GestureRecognizer.DragWithTouchMinDelayTicks + 1); - var end = sut.ProcessUpEvent(52, 52, ts: GestureRecognizer.DragWithTouchMinDelayTicks + 2); + sut.ProcessMoveEvent(26, 26, ts: GestureRecognizer.DragWithTouchMinDelayMicroseconds + 1); + var start = sut.ProcessMoveEvent(50, 50, ts: GestureRecognizer.DragWithTouchMinDelayMicroseconds + 2); + var move = sut.ProcessMoveEvent(51, 51, ts: GestureRecognizer.DragWithTouchMinDelayMicroseconds + 1); + var end = sut.ProcessUpEvent(52, 52, ts: GestureRecognizer.DragWithTouchMinDelayMicroseconds + 2); drags.Should().BeEquivalentTo( Drag(start, DraggingState.Started), @@ -1559,7 +1561,7 @@ public void Drag_CompleteGesture() [TestMethod] public void Drag_And_Holding_Touch() { - var delay = (ulong)Math.Max(GestureRecognizer.DragWithTouchMinDelayTicks, GestureRecognizer.HoldMinDelayTicks); + var delay = (ulong)Math.Max(GestureRecognizer.DragWithTouchMinDelayMicroseconds, GestureRecognizer.HoldMinDelayMicroseconds); var sut = new GestureRecognizer { GestureSettings = GestureSettings.Drag | GestureSettings.Hold }; var drags = new List(); var holds = new List(); diff --git a/src/Uno.UI/Runtime/BrowserPointerInputSource.wasm.cs b/src/Uno.UI/Runtime/BrowserPointerInputSource.wasm.cs index 51c2741190c9..f03d40afd4f5 100644 --- a/src/Uno.UI/Runtime/BrowserPointerInputSource.wasm.cs +++ b/src/Uno.UI/Runtime/BrowserPointerInputSource.wasm.cs @@ -28,6 +28,7 @@ internal partial class BrowserPointerInputSource : IUnoCorePointerInputSource private static readonly Logger _log = typeof(BrowserPointerInputSource).Log(); private static readonly Logger? _logTrace = _log.IsTraceEnabled(LogLevel.Trace) ? _log : null; + // TODO: Verify the boot time unit (ms or ticks) private ulong _bootTime; private bool _isOver; private PointerPoint? _lastPoint; @@ -321,7 +322,7 @@ internal static uint ToFrameId(double timestamp) => (uint)(timestamp % uint.MaxValue); private ulong ToTimeStamp(double timestamp) - => _bootTime + (ulong)(timestamp * TimeSpan.TicksPerMillisecond); + => _bootTime + (ulong)(timestamp * 1000); private static PointerUpdateKind ToUpdateKind(HtmlPointerButtonUpdate update, PointerPointProperties props) => update switch diff --git a/src/Uno.UI/UI/Input/GestureRecognizer.Gesture.cs b/src/Uno.UI/UI/Input/GestureRecognizer.Gesture.cs index 03ee67cffdce..ba0d1f858947 100644 --- a/src/Uno.UI/UI/Input/GestureRecognizer.Gesture.cs +++ b/src/Uno.UI/UI/Input/GestureRecognizer.Gesture.cs @@ -249,7 +249,7 @@ private bool SupportsHolding() private void StartHoldingTimer() { _holdingTimer = DispatcherQueue.GetForCurrentThread().CreateTimer(); - _holdingTimer.Interval = TimeSpan.FromTicks(HoldMinDelayTicks); + _holdingTimer.Interval = TimeSpan.FromMicroseconds(HoldMinDelayMicroseconds); _holdingTimer.State = this; _holdingTimer.Tick += OnHoldingTimerTick; _holdingTimer.Start(); @@ -323,7 +323,7 @@ public static bool IsMultiTapGesture((ulong id, ulong ts, Point position) previo var currentPosition = down.Position; return previousTap.id == currentId - && currentTs - previousTap.ts <= MultiTapMaxDelayTicks + && currentTs - previousTap.ts <= MultiTapMaxDelayMicroseconds && !IsOutOfTapRange(previousTap.position, currentPosition); } @@ -394,7 +394,7 @@ private static bool IsRightTapGesture(Gesture points, out bool isLongPress) } private static bool IsLongPress(PointerPoint down, PointerPoint current) - => current.Timestamp - down.Timestamp > HoldMinDelayTicks; + => current.Timestamp - down.Timestamp > HoldMinDelayMicroseconds; #endregion } } diff --git a/src/Uno.UI/UI/Input/GestureRecognizer.Manipulation.InertiaProcessor.cs b/src/Uno.UI/UI/Input/GestureRecognizer.Manipulation.InertiaProcessor.cs index 53d8b981f8eb..94f3a4f9b7df 100644 --- a/src/Uno.UI/UI/Input/GestureRecognizer.Manipulation.InertiaProcessor.cs +++ b/src/Uno.UI/UI/Input/GestureRecognizer.Manipulation.InertiaProcessor.cs @@ -88,7 +88,7 @@ public InertiaProcessor(Manipulation owner, Point position, ManipulationDelta cu /// Depending of the platform, the timestamp provided by pointer events might not be absolute, /// so it's preferable to not compare timestamp between pointers and inertia processor. /// - public long Elapsed => _timer.LastTickElapsed.Ticks; + public double Elapsed => _timer.LastTickElapsed.TotalMicroseconds; public void Start() { diff --git a/src/Uno.UI/UI/Input/GestureRecognizer.Manipulation.cs b/src/Uno.UI/UI/Input/GestureRecognizer.Manipulation.cs index 29813e861dcb..1c6153501259 100644 --- a/src/Uno.UI/UI/Input/GestureRecognizer.Manipulation.cs +++ b/src/Uno.UI/UI/Input/GestureRecognizer.Manipulation.cs @@ -499,8 +499,8 @@ private ManipulationVelocities GetVelocities(ManipulationDelta delta) { // The _currents.Timestamp is not updated once inertia as started, we must get the elapsed duration from the inertia processor // (and not compare it to PointerPoint.Timestamp in any way, cf. remarks on InertiaProcessor.Elapsed) - var elapsedTicks = _inertia?.Elapsed ?? (double)_currents.Timestamp - _lastPublishedState.timestamp; - var elapsedMs = elapsedTicks / TimeSpan.TicksPerMillisecond; + var elapsedMicroseconds = _inertia?.Elapsed ?? (_currents.Timestamp - _lastPublishedState.timestamp); + var elapsedMs = elapsedMicroseconds / 1000; // With uno a single native event might produce multiple managed pointer events. // In that case we would get an empty velocities ... which is often not relevant! @@ -541,7 +541,7 @@ private void StartDragTimer() if (_isDraggingEnable && _deviceType == PointerDeviceType.Touch) { _dragHoldTimer = DispatcherQueue.GetForCurrentThread().CreateTimer(); - _dragHoldTimer.Interval = new TimeSpan(DragWithTouchMinDelayTicks); + _dragHoldTimer.Interval = TimeSpan.FromMicroseconds(DragWithTouchMinDelayMicroseconds); _dragHoldTimer.IsRepeating = false; _dragHoldTimer.Tick += TouchDragMightStart; _dragHoldTimer.Start(); @@ -565,7 +565,7 @@ private void StopDragTimer() } // For pen and mouse this only means down -> * moves out of tap range; - // For touch it means down -> * moves close to origin for DragUsingFingerMinDelayTicks -> * moves far from the origin + // For touch it means down -> * moves close to origin for DragWithTouchMinDelayMicroseconds -> * moves far from the origin private bool IsBeginningOfDragManipulation() { if (!_isDraggingEnable) @@ -592,7 +592,7 @@ private bool IsBeginningOfDragManipulation() // This means that this method is expected to be invoked on each move (until manipulation starts) // in order to update the _isDraggingEnable state. - var isInHoldPhase = current.Timestamp - down.Timestamp < DragWithTouchMinDelayTicks; + var isInHoldPhase = current.Timestamp - down.Timestamp < DragWithTouchMinDelayMicroseconds; if (isInHoldPhase && isOutOfRange) { // The pointer moved out of range while in the hold phase, so we completely disable the drag manipulation diff --git a/src/Uno.UI/UI/Input/GestureRecognizer.cs b/src/Uno.UI/UI/Input/GestureRecognizer.cs index 21e15e9200f4..b714182873c2 100644 --- a/src/Uno.UI/UI/Input/GestureRecognizer.cs +++ b/src/Uno.UI/UI/Input/GestureRecognizer.cs @@ -26,12 +26,12 @@ public partial class GestureRecognizer internal const int TapMaxXDelta = 10; internal const int TapMaxYDelta = 10; - internal const ulong MultiTapMaxDelayTicks = TimeSpan.TicksPerMillisecond * 500; + internal const ulong MultiTapMaxDelayMicroseconds = 500000; - internal const long HoldMinDelayTicks = TimeSpan.TicksPerMillisecond * 800; + internal const long HoldMinDelayMicroseconds = 800000; internal const float HoldMinPressure = .75f; - internal const long DragWithTouchMinDelayTicks = TimeSpan.TicksPerMillisecond * 300; // https://docs.microsoft.com/en-us/windows/uwp/design/input/drag-and-drop#open-a-context-menu-on-an-item-you-can-drag-with-touch + internal const long DragWithTouchMinDelayMicroseconds = 300000; // https://docs.microsoft.com/en-us/windows/uwp/design/input/drag-and-drop#open-a-context-menu-on-an-item-you-can-drag-with-touch private readonly Logger _log; private IDictionary _gestures = new Dictionary(_defaultGesturesSize); diff --git a/src/Uno.UI/UI/Input/PointerPoint.cs b/src/Uno.UI/UI/Input/PointerPoint.cs index d70b5ed43a4e..b6e4d8014a5b 100644 --- a/src/Uno.UI/UI/Input/PointerPoint.cs +++ b/src/Uno.UI/UI/Input/PointerPoint.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Diagnostics; using System.Text; using System.Threading; using Windows.Devices.Input; diff --git a/src/Uno.UI/UI/Xaml/Controls/TextBox/TextBox.pointers.skia.cs b/src/Uno.UI/UI/Xaml/Controls/TextBox/TextBox.pointers.skia.cs index c1197f21be79..a05407a9f6fe 100644 --- a/src/Uno.UI/UI/Xaml/Controls/TextBox/TextBox.pointers.skia.cs +++ b/src/Uno.UI/UI/Xaml/Controls/TextBox/TextBox.pointers.skia.cs @@ -104,7 +104,7 @@ private static bool IsMultiTapGesture((ulong id, ulong ts, Point position) previ var currentPosition = down.Position; return previousTap.id == currentId - && currentTs - previousTap.ts <= GestureRecognizer.MultiTapMaxDelayTicks + && currentTs - previousTap.ts <= GestureRecognizer.MultiTapMaxDelayMicroseconds && !GestureRecognizer.IsOutOfTapRange(previousTap.position, currentPosition); } @@ -176,7 +176,7 @@ partial void OnPointerReleasedPartial(PointerRoutedEventArgs args) _isPressed = false; - if ((args.GetCurrentPoint(null).Timestamp - _lastPointerDown.point.Timestamp) >= GestureRecognizer.HoldMinDelayTicks) + if ((args.GetCurrentPoint(null).Timestamp - _lastPointerDown.point.Timestamp) >= GestureRecognizer.HoldMinDelayMicroseconds) { // Touch holding OpenContextMenu(args.GetCurrentPoint(this).Position); diff --git a/src/Uno.UI/UI/Xaml/Input/PointerRoutedEventArgs.Android.cs b/src/Uno.UI/UI/Xaml/Input/PointerRoutedEventArgs.Android.cs index 6044821033f0..9a45fbb35473 100644 --- a/src/Uno.UI/UI/Xaml/Input/PointerRoutedEventArgs.Android.cs +++ b/src/Uno.UI/UI/Xaml/Input/PointerRoutedEventArgs.Android.cs @@ -250,9 +250,10 @@ private PointerPointProperties GetProperties(MotionEvent nativeEvent, MotionEven private static ulong ToTimeStamp(long uptimeMillis) { + // Timestamp is in microseconds if (FeatureConfiguration.PointerRoutedEventArgs.AllowRelativeTimeStamp) { - return (ulong)(TimeSpan.TicksPerMillisecond * uptimeMillis); + return (ulong)(uptimeMillis * 1000); } else { @@ -261,9 +262,7 @@ private static ulong ToTimeStamp(long uptimeMillis) var sleepTime = Android.OS.SystemClock.ElapsedRealtime() - Android.OS.SystemClock.UptimeMillis(); var realUptime = (ulong)(uptimeMillis + sleepTime); - var timestamp = TimeSpan.TicksPerMillisecond * (_unixEpochMs + realUptime); - - return timestamp; + return realUptime * 1000; } } diff --git a/src/Uno.UI/UI/Xaml/Input/PointerRoutedEventArgs.iOS.cs b/src/Uno.UI/UI/Xaml/Input/PointerRoutedEventArgs.iOS.cs index 2d4427a8879c..80062282b032 100644 --- a/src/Uno.UI/UI/Xaml/Input/PointerRoutedEventArgs.iOS.cs +++ b/src/Uno.UI/UI/Xaml/Input/PointerRoutedEventArgs.iOS.cs @@ -8,8 +8,6 @@ using Uno.UI.Xaml.Core; using Uno.UI.Xaml.Input; - - #if HAS_UNO_WINUI using Microsoft.UI.Input; #else @@ -77,7 +75,7 @@ internal PointerRoutedEventArgs(uint pointerId, UITouch nativeTouch, UIEvent nat public PointerPoint GetCurrentPoint(UIElement relativeTo) { - var timestamp = ToTimeStamp(_nativeTouch.Timestamp); + var timestamp = ToTimestamp(_nativeTouch.Timestamp); var device = global::Windows.Devices.Input.PointerDevice.For((global::Windows.Devices.Input.PointerDeviceType)Pointer.PointerDeviceType); var rawPosition = (Point)_nativeTouch.GetPreciseLocation(null); var position = relativeTo == null @@ -112,13 +110,11 @@ private PointerPointProperties GetProperties() }; #region Misc static helpers - private static long? _bootTime; - private static ulong ToTimeStamp(double timestamp) + private static ulong ToTimestamp(double nativeTimestamp) { - _bootTime ??= DateTime.UtcNow.Ticks - (long)(TimeSpan.TicksPerSecond * new NSProcessInfo().SystemUptime); - - return (ulong)_bootTime.Value + (ulong)(TimeSpan.TicksPerSecond * timestamp); + // iOS Timestamp is in seconds from boot time, convert to microseconds. + return (ulong)(nativeTimestamp * 1000 * 1000); } private static double? _firstTimestamp; diff --git a/src/Uno.UWP/UI/Input/Preview.Injection/InjectedInputMouseInfo.cs b/src/Uno.UWP/UI/Input/Preview.Injection/InjectedInputMouseInfo.cs index 91c38268a3f6..e92d773bb4df 100644 --- a/src/Uno.UWP/UI/Input/Preview.Injection/InjectedInputMouseInfo.cs +++ b/src/Uno.UWP/UI/Input/Preview.Injection/InjectedInputMouseInfo.cs @@ -100,9 +100,10 @@ internal PointerEventArgs ToEventArgs(InjectedInputState state, VirtualKeyModifi properties.PointerUpdateKind = update; + var timestampInMicroseconds = state.Timestamp + TimeOffsetInMilliseconds * 1000; var point = new PointerPoint( state.FrameId + TimeOffsetInMilliseconds, - state.Timestamp + TimeOffsetInMilliseconds, + timestampInMicroseconds, PointerDevice.For(PointerDeviceType.Mouse), uint.MaxValue - 42, // Try to avoid conflict with the real mouse pointer position, diff --git a/src/Uno.UWP/UI/Input/Preview.Injection/InjectedInputPointerInfo.cs b/src/Uno.UWP/UI/Input/Preview.Injection/InjectedInputPointerInfo.cs index 7d521bfeac0f..609de983a4a5 100644 --- a/src/Uno.UWP/UI/Input/Preview.Injection/InjectedInputPointerInfo.cs +++ b/src/Uno.UWP/UI/Input/Preview.Injection/InjectedInputPointerInfo.cs @@ -78,10 +78,11 @@ internal PointerPoint ToPointerPoint(InjectedInputState state) properties.IsBarrelButtonPressed = properties.IsRightButtonPressed; } + var timestampInMicroseconds = state.Timestamp + TimeOffsetInMilliseconds * 1000; var location = new Point(PixelLocation.PositionX, PixelLocation.PositionY); var point = new PointerPoint( state.FrameId + (uint)PerformanceCount, - state.Timestamp + (ulong)(TimeOffsetInMilliseconds * TimeSpan.TicksPerMillisecond), + timestampInMicroseconds, PointerDevice.For(state.Type), isNew ? PointerId : state.PointerId, location,