diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Input/GestureRecognizerTests/Manipulation_Inertia.xaml b/src/SamplesApp/UITests.Shared/Windows_UI_Input/GestureRecognizerTests/Manipulation_Inertia.xaml
index f095f60f61c9..f3e3aed2fe32 100644
--- a/src/SamplesApp/UITests.Shared/Windows_UI_Input/GestureRecognizerTests/Manipulation_Inertia.xaml
+++ b/src/SamplesApp/UITests.Shared/Windows_UI_Input/GestureRecognizerTests/Manipulation_Inertia.xaml
@@ -1,26 +1,12 @@
+ x:Class="UITests.Windows_UI_Input.GestureRecognizerTests.Manipulation_Inertia"
+ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:runtimeTests="using:RuntimeTests.Tests.Windows_UI_Xaml_Input.TestPages"
+ xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ mc:Ignorable="d"
+ Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
-
-
-
-
-
-
+
diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Input/GestureRecognizerTests/Manipulation_Inertia.xaml.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Input/GestureRecognizerTests/Manipulation_Inertia.xaml.cs
index c4ef3cb308b0..917f76bc57d3 100644
--- a/src/SamplesApp/UITests.Shared/Windows_UI_Input/GestureRecognizerTests/Manipulation_Inertia.xaml.cs
+++ b/src/SamplesApp/UITests.Shared/Windows_UI_Input/GestureRecognizerTests/Manipulation_Inertia.xaml.cs
@@ -1,76 +1,16 @@
using System;
using System.Linq;
-using Windows.Foundation;
-using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
-using Microsoft.UI.Xaml.Input;
-using Microsoft.UI.Xaml.Media;
using Uno.UI.Samples.Controls;
-#if HAS_UNO_WINUI || WINAPPSDK
-using Microsoft.UI.Input;
-#else
-using Windows.Devices.Input;
-using Windows.UI.Input;
-#endif
-
namespace UITests.Windows_UI_Input.GestureRecognizerTests
{
- [Sample("Gesture Recognizer")]
+ [Sample("Gesture Recognizer", IsManualTest = true)]
public sealed partial class Manipulation_Inertia : Page
{
public Manipulation_Inertia()
{
this.InitializeComponent();
}
-
- private long _inertiaStart = 0;
-
- private void InertiaStarting(object sender, ManipulationInertiaStartingRoutedEventArgs e)
- {
- _inertiaStart = DateTimeOffset.UtcNow.Ticks;
-
- //e.TranslationBehavior.DesiredDisplacement = 5;
- //e.TranslationBehavior.DesiredDeceleration = .00001;
-
- Log(@$"[INERTIA] Inertia starting: {F(default, e.Delta, e.Cumulative, e.Velocities)}
-tr: ↘={e.TranslationBehavior.DesiredDeceleration} | ⌖={e.TranslationBehavior.DesiredDisplacement}
-θ: ↘={e.RotationBehavior.DesiredDeceleration} | ⌖={e.RotationBehavior.DesiredRotation}
-s: ↘={e.ExpansionBehavior.DesiredDeceleration} | ⌖={e.ExpansionBehavior.DesiredExpansion}");
- }
-
- private void ManipDelta(object sender, ManipulationDeltaRoutedEventArgs e)
- {
- if (!(_element.RenderTransform is CompositeTransform tr))
- {
- _element.RenderTransform = tr = new CompositeTransform();
- }
-
- tr.TranslateX = e.Cumulative.Translation.X;
- tr.TranslateY = e.Cumulative.Translation.Y;
- tr.Rotation = e.Cumulative.Rotation;
- tr.ScaleX = e.Cumulative.Scale;
- tr.ScaleY = e.Cumulative.Scale;
-
- Log(
- $"[DELTA] {F(e.Position, e.Delta, e.Cumulative, e.Velocities)} "
- + (e.IsInertial ? $"{TimeSpan.FromTicks(DateTimeOffset.UtcNow.Ticks - _inertiaStart).TotalMilliseconds} ms" : ""));
- }
-
- private void Reset(object sender, RoutedEventArgs e)
- {
- _element.RenderTransform = new CompositeTransform();
- }
-
- private static string F(Point position, ManipulationDelta delta, ManipulationDelta cumulative, ManipulationVelocities velocities)
- => $"@=[{position.X:000.00},{position.Y:000.00}] "
- + $"| X=(Σ:{cumulative.Translation.X:' '000.00;'-'000.00} / Δ:{delta.Translation.X:' '00.00;'-'00.00} / c:{velocities.Linear.X:F2}) "
- + $"| Y=(Σ:{cumulative.Translation.Y:' '000.00;'-'000.00} / Δ:{delta.Translation.Y:' '00.00;'-'00.00} / c:{velocities.Linear.Y:F2}) "
- + $"| θ=(Σ:{cumulative.Rotation:' '000.00;'-'000.00} / Δ:{delta.Rotation:' '00.00;'-'00.00} / c:{velocities.Angular:F2}) "
- + $"| s=(Σ:{cumulative.Scale:000.00} / Δ:{delta.Scale:00.00} / c:{velocities.Expansion:F2}) "
- + $"| e=(Σ:{cumulative.Expansion:' '000.00;'-'000.00} / Δ:{delta.Expansion:' '00.00;'-'00.00} / c:{velocities.Expansion:F2})";
-
- private static void Log(string text)
- => global::System.Diagnostics.Debug.WriteLine(text);
}
}
diff --git a/src/Uno.UI.RuntimeTests/Helpers/UITestHelper.cs b/src/Uno.UI.RuntimeTests/Helpers/UITestHelper.cs
index c6351e42ba42..f08e78f08039 100644
--- a/src/Uno.UI.RuntimeTests/Helpers/UITestHelper.cs
+++ b/src/Uno.UI.RuntimeTests/Helpers/UITestHelper.cs
@@ -25,6 +25,13 @@
#if !HAS_UNO
using System.Runtime.InteropServices;
#endif
+
+#if HAS_UNO_WINUI || WINAPPSDK
+using PointerDeviceType = Microsoft.UI.Input.PointerDeviceType;
+#else
+using PointerDeviceType = Windows.Devices.Input.PointerDeviceType;
+#endif
+
namespace Uno.UI.RuntimeTests.Helpers;
// Note: This file contains a bunch of helpers that are expected to be moved to the test engine among the pointer injection work
@@ -323,6 +330,16 @@ public string TemplateId
public static class InputInjectorExtensions
{
+ public static IInjectedPointer GetPointer(this InputInjector injector, PointerDeviceType pointer)
+ => pointer switch
+ {
+ PointerDeviceType.Touch => GetFinger(injector),
+#if !WINAPPSDK
+ PointerDeviceType.Mouse => GetMouse(injector),
+#endif
+ _ => throw new NotSupportedException($"Injection of {pointer} is not supported on this platform.")
+ };
+
public static Finger GetFinger(this InputInjector injector, uint id = 42)
=> new(injector, id);
@@ -336,7 +353,7 @@ public interface IInjectedPointer
{
void Press(Point position);
- void MoveTo(Point position);
+ void MoveTo(Point position, uint? steps = null, uint? stepOffsetInMilliseconds = null);
void MoveBy(double deltaX = 0, double deltaY = 0);
@@ -366,10 +383,10 @@ public static void Press(this IInjectedPointer pointer, double x, double y)
public static void MoveTo(this IInjectedPointer pointer, double x, double y)
=> pointer.MoveTo(new(x, y));
- public static void Drag(this IInjectedPointer pointer, Point from, Point to)
+ public static void Drag(this IInjectedPointer pointer, Point from, Point to, uint? steps = null, uint? stepOffsetInMilliseconds = null)
{
pointer.Press(from);
- pointer.MoveTo(to);
+ pointer.MoveTo(to, steps, stepOffsetInMilliseconds);
pointer.Release();
}
}
@@ -377,6 +394,7 @@ public static void Drag(this IInjectedPointer pointer, Point from, Point to)
public partial class Finger : IInjectedPointer, IDisposable
{
private const uint _defaultMoveSteps = 10;
+ private const uint _defaultStepOffsetInMilliseconds = 1;
private readonly InputInjector _injector;
private readonly uint _id;
@@ -400,12 +418,13 @@ public void Press(Point position)
}
}
- void IInjectedPointer.MoveTo(Point position) => MoveTo(position);
- public void MoveTo(Point position, uint steps = _defaultMoveSteps)
+ void IInjectedPointer.MoveTo(Point position, uint? steps, uint? stepOffsetInMilliseconds) =>
+ MoveTo(position, steps ?? _defaultMoveSteps, stepOffsetInMilliseconds ?? _defaultStepOffsetInMilliseconds);
+ public void MoveTo(Point position, uint steps = _defaultMoveSteps, uint stepOffsetInMilliseconds = _defaultStepOffsetInMilliseconds)
{
if (_currentPosition is { } current)
{
- Inject(GetMove(current, position, steps));
+ Inject(GetMove(current, position, steps, stepOffsetInMilliseconds));
_currentPosition = position;
}
}
@@ -448,7 +467,7 @@ public static InjectedInputTouchInfo GetPress(uint id, Point position)
}
};
- public static IEnumerable GetMove(Point fromPosition, Point toPosition, uint steps = _defaultMoveSteps)
+ public static IEnumerable GetMove(Point fromPosition, Point toPosition, uint steps = _defaultMoveSteps, uint stepOffsetInMilliseconds = _defaultStepOffsetInMilliseconds)
{
steps += 1; // We need to send at least the final location, but steps refers to the number of intermediate points
@@ -460,6 +479,7 @@ public static IEnumerable GetMove(Point fromPosition, Po
{
PointerInfo = new()
{
+ TimeOffsetInMilliseconds = stepOffsetInMilliseconds,
PixelLocation = At(fromPosition.X + step * stepX, fromPosition.Y + step * stepY),
PointerOptions = InjectedInputPointerOptions.Update
| InjectedInputPointerOptions.FirstButton
@@ -602,11 +622,10 @@ public void ReleaseAny()
}
public void MoveBy(double deltaX, double deltaY)
- => Inject(GetMoveBy(deltaX, deltaY));
+ => Inject(GetMoveBy(deltaX, deltaY, 1));
- void IInjectedPointer.MoveTo(Point position) => MoveTo(position);
- public void MoveTo(Point position, uint? steps = null)
- => Inject(GetMoveTo(position.X, position.Y, steps));
+ public void MoveTo(Point position, uint? steps = null, uint? stepOffsetInMilliseconds = null)
+ => Inject(GetMoveTo(position.X, position.Y, steps, stepOffsetInMilliseconds));
public void WheelUp() => Wheel(ScrollContentPresenter.ScrollViewerDefaultMouseWheelDelta);
public void WheelDown() => Wheel(-ScrollContentPresenter.ScrollViewerDefaultMouseWheelDelta);
@@ -616,7 +635,7 @@ public void MoveTo(Point position, uint? steps = null)
public void Wheel(double delta, bool isHorizontal = false, uint steps = 1)
=> Inject(GetWheel(delta, isHorizontal, steps));
- private IEnumerable GetMoveTo(double x, double y, uint? steps)
+ private IEnumerable GetMoveTo(double x, double y, uint? steps, uint? stepOffsetInMilliseconds = null)
{
var x0 = Current.X;
var y0 = Current.Y;
@@ -624,6 +643,8 @@ private IEnumerable GetMoveTo(double x, double y, uint?
var deltaY = y - y0;
steps ??= (uint)Math.Min(Math.Max(Math.Abs(deltaX), Math.Abs(deltaY)), 512);
+ stepOffsetInMilliseconds ??= 1;
+
if (steps is 0)
{
yield break;
@@ -641,7 +662,7 @@ private IEnumerable GetMoveTo(double x, double y, uint?
var newPositionX = (int)Math.Round(x0 + i * stepX);
var newPositionY = (int)Math.Round(y0 + i * stepY);
- yield return GetMoveBy(newPositionX - prevPositionX, newPositionY - prevPositionY);
+ yield return GetMoveBy(newPositionX - prevPositionX, newPositionY - prevPositionY, stepOffsetInMilliseconds.Value);
prevPositionX = newPositionX;
prevPositionY = newPositionY;
@@ -677,12 +698,12 @@ private static InjectedInputMouseInfo GetRightPress()
MouseOptions = InjectedInputMouseOptions.RightDown,
};
- private static InjectedInputMouseInfo GetMoveBy(double deltaX, double deltaY)
+ private static InjectedInputMouseInfo GetMoveBy(double deltaX, double deltaY, uint stepOffsetInMilliseconds)
=> new()
{
DeltaX = (int)deltaX,
DeltaY = (int)deltaY,
- TimeOffsetInMilliseconds = 1,
+ TimeOffsetInMilliseconds = stepOffsetInMilliseconds,
MouseOptions = InjectedInputMouseOptions.MoveNoCoalesce,
};
diff --git a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Composition/Given_InteractionTracker.cs b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Composition/Given_InteractionTracker.cs
index 55a36911e0d6..e0e264533d82 100644
--- a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Composition/Given_InteractionTracker.cs
+++ b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Composition/Given_InteractionTracker.cs
@@ -224,7 +224,7 @@ public async Task When_UserInteraction()
var injector = InputInjector.TryCreate() ?? throw new InvalidOperationException("Failed to init the InputInjector");
var finger = injector.GetFinger();
- finger.Drag(new(position.Left + 50, position.Top + 50), new(position.Left + 100, position.Top + 50));
+ finger.Drag(new(position.Left + 50, position.Top + 50), new(position.Left + 100, position.Top + 50), stepOffsetInMilliseconds: 0);
string logs = await WaitTrackerLogs(tracker);
var helper = new TrackerAssertHelper(logs);
diff --git a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Input/Given_GestureRecognizer.cs b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Input/Given_GestureRecognizer.cs
new file mode 100644
index 000000000000..d9d4fa02450f
--- /dev/null
+++ b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Input/Given_GestureRecognizer.cs
@@ -0,0 +1,70 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Windows.Foundation;
+using Windows.UI.Input.Preview.Injection;
+using FluentAssertions;
+using RuntimeTests.Tests.Windows_UI_Xaml_Input.TestPages;
+using Uno.Extensions;
+using Uno.UI.RuntimeTests.Helpers;
+
+#if WINAPPSDK
+using Uno.UI.Toolkit.Extensions;
+#endif
+
+#if HAS_UNO_WINUI || WINAPPSDK
+using PointerDeviceType = Microsoft.UI.Input.PointerDeviceType;
+#else
+using PointerDeviceType = Windows.Devices.Input.PointerDeviceType;
+#endif
+
+namespace Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml_Input
+{
+ [TestClass]
+ public class Given_GestureRecognizer
+ {
+ [TestMethod]
+ [RunsOnUIThread]
+#if !HAS_INPUT_INJECTOR || (!HAS_UNO_WINUI && !WINAPPSDK) // Requires pointer injection and WinUI API
+ [Ignore("This test is not supported on this platform.")]
+#endif
+#if !WINAPPSDK
+ [DataRow(PointerDeviceType.Mouse)]
+#endif
+ [DataRow(PointerDeviceType.Touch)]
+ public async Task When_ManipulateWithVelocity_Then_InertiaKicksIn(PointerDeviceType type)
+ {
+ var sample = new Manipulation_Inertia();
+ await UITestHelper.Load(sample);
+
+ var started = false;
+ var completed = new TaskCompletionSource();
+ var completedTimeout = new CancellationTokenSource(15000);
+ using var _ = completedTimeout.Token.Register(() => completed.TrySetException(new TimeoutException("Cannot get complete in given delay.")));
+ sample.IsRunningChanged += (snd, isRunning) =>
+ {
+ if (isRunning)
+ {
+ started = true;
+ }
+ else
+ {
+ completed.TrySetResult();
+ }
+ };
+
+ var origin = sample.Element.GetAbsoluteBounds().GetCenter();
+ var pointer = (InputInjector.TryCreate() ?? throw new InvalidOperationException("Input injection is not supported on this device")).GetPointer(type);
+ pointer.Press(origin);
+ pointer.MoveTo(new Point(origin.X, origin.Y + 25), steps: 100); // 1 step per ms!
+
+ started.Should().Be(true, "Manipulation should have started.");
+
+ pointer.Release();
+
+ await completed.Task;
+
+ sample.Validate().Should().BeTrue();
+ }
+ }
+}
diff --git a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Input/TestPages/Manipulation_Inertia.xaml b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Input/TestPages/Manipulation_Inertia.xaml
new file mode 100644
index 000000000000..3ab9a66f90c7
--- /dev/null
+++ b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Input/TestPages/Manipulation_Inertia.xaml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Input/TestPages/Manipulation_Inertia.xaml.cs b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Input/TestPages/Manipulation_Inertia.xaml.cs
new file mode 100644
index 000000000000..cf9f37162968
--- /dev/null
+++ b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Input/TestPages/Manipulation_Inertia.xaml.cs
@@ -0,0 +1,118 @@
+#pragma warning disable SYSLIB1045
+using System;
+using System.Linq;
+using Windows.Foundation;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Input;
+using Microsoft.UI.Xaml.Media;
+using Microsoft.UI.Input;
+using System.Text.RegularExpressions;
+
+namespace RuntimeTests.Tests.Windows_UI_Xaml_Input.TestPages
+{
+ public sealed partial class Manipulation_Inertia : Page
+ {
+ public Manipulation_Inertia()
+ {
+ this.InitializeComponent();
+ }
+
+ private long _inertiaStart = 0;
+
+ public event EventHandler IsRunningChanged;
+
+ public FrameworkElement Element => _element;
+
+ public bool IsRunning { get; private set; }
+
+ private void ManipStarting(object sender, ManipulationStartingRoutedEventArgs e)
+ {
+ IsRunning = true;
+ IsRunningChanged?.Invoke(this, true);
+ Reset(null, null);
+ }
+
+ private void InertiaStarting(object sender, ManipulationInertiaStartingRoutedEventArgs e)
+ {
+ _inertiaStart = DateTimeOffset.UtcNow.Ticks;
+
+ //e.TranslationBehavior.DesiredDisplacement = 5;
+ //e.TranslationBehavior.DesiredDeceleration = .00001;
+#if HAS_UNO_WINUI || WINAPPSDK
+ Log(@$"[INERTIA] Inertia starting: {F(default, e.Delta, e.Cumulative, e.Velocities)}
+tr: ↘={e.TranslationBehavior.DesiredDeceleration} | ⌖={e.TranslationBehavior.DesiredDisplacement}
+θ: ↘={e.RotationBehavior.DesiredDeceleration} | ⌖={e.RotationBehavior.DesiredRotation}
+s: ↘={e.ExpansionBehavior.DesiredDeceleration} | ⌖={e.ExpansionBehavior.DesiredExpansion}");
+#endif
+ }
+
+ private void ManipDelta(object sender, ManipulationDeltaRoutedEventArgs e)
+ {
+ if (!(_element.RenderTransform is CompositeTransform tr))
+ {
+ _element.RenderTransform = tr = new CompositeTransform();
+ }
+
+ tr.TranslateX = e.Cumulative.Translation.X;
+ tr.TranslateY = e.Cumulative.Translation.Y;
+ tr.Rotation = e.Cumulative.Rotation;
+ tr.ScaleX = e.Cumulative.Scale;
+ tr.ScaleY = e.Cumulative.Scale;
+
+#if HAS_UNO_WINUI || WINAPPSDK
+ Log(
+ $"[DELTA] {F(e.Position, e.Delta, e.Cumulative, e.Velocities)} "
+ + (e.IsInertial ? $"{TimeSpan.FromTicks(DateTimeOffset.UtcNow.Ticks - _inertiaStart).TotalMilliseconds} ms" : ""));
+#endif
+ }
+
+ private void ManipCompleted(object sender, ManipulationCompletedRoutedEventArgs e)
+ {
+ IsRunning = false;
+ IsRunningChanged?.Invoke(this, false);
+ }
+
+ private void Reset(object sender, RoutedEventArgs e)
+ {
+ _element.RenderTransform = new CompositeTransform();
+ Output.Text = string.Empty;
+ }
+
+ public bool Validate()
+ {
+ Validate(null, null);
+ return Output.Text.Contains("PASSED", StringComparison.OrdinalIgnoreCase);
+ }
+
+ private void Validate(object sender, RoutedEventArgs e)
+ {
+ var log = Output.Text;
+
+ Output.Text = log?.IndexOf("[INERTIA]", StringComparison.OrdinalIgnoreCase) switch
+ {
+ null => "FAILED - Test not ran",
+ <= -1 => "FAILED - No inertia detected",
+ { } i when Regex.Count((ReadOnlySpan)log[i..], "\\[DELTA\\]") > 1 => "PASSED - Inertia detected",
+ _ => "FAILED - No delta detected after inertia started",
+ };
+ }
+
+#if HAS_UNO_WINUI || WINAPPSDK
+ private static string F(Point position, ManipulationDelta delta, ManipulationDelta cumulative, ManipulationVelocities velocities)
+ => $"@=[{position.X:000.00},{position.Y:000.00}] "
+ + $"| X=(Σ:{cumulative.Translation.X:' '000.00;'-'000.00} / Δ:{delta.Translation.X:' '00.00;'-'00.00} / c:{velocities.Linear.X:F2}) "
+ + $"| Y=(Σ:{cumulative.Translation.Y:' '000.00;'-'000.00} / Δ:{delta.Translation.Y:' '00.00;'-'00.00} / c:{velocities.Linear.Y:F2}) "
+ + $"| θ=(Σ:{cumulative.Rotation:' '000.00;'-'000.00} / Δ:{delta.Rotation:' '00.00;'-'00.00} / c:{velocities.Angular:F2}) "
+ + $"| s=(Σ:{cumulative.Scale:000.00} / Δ:{delta.Scale:00.00} / c:{velocities.Expansion:F2}) "
+ + $"| e=(Σ:{cumulative.Expansion:' '000.00;'-'000.00} / Δ:{delta.Expansion:' '00.00;'-'00.00} / c:{velocities.Expansion:F2})";
+#endif
+
+ private void Log(string text)
+ {
+ global::System.Diagnostics.Debug.WriteLine(text);
+
+ Output.Text += text + Environment.NewLine;
+ }
+ }
+}
diff --git a/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml.Controls/ScrollViewer.cs b/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml.Controls/ScrollViewer.cs
index 0ce1c1054585..ae63ba7d5f9a 100644
--- a/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml.Controls/ScrollViewer.cs
+++ b/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml.Controls/ScrollViewer.cs
@@ -125,7 +125,7 @@ public bool IsVerticalRailEnabled
}
}
#endif
-#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__
+#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || false || __NETSTD_REFERENCE__ || false
[global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")]
public bool IsScrollInertiaEnabled
{
@@ -316,7 +316,7 @@ public bool ReduceViewportForCoreInputViewOcclusions
typeof(global::Microsoft.UI.Xaml.Controls.ScrollViewer),
new Microsoft.UI.Xaml.FrameworkPropertyMetadata(default(bool)));
#endif
-#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__
+#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || false || __NETSTD_REFERENCE__ || false
[global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")]
public static global::Microsoft.UI.Xaml.DependencyProperty IsScrollInertiaEnabledProperty { get; } =
Microsoft.UI.Xaml.DependencyProperty.RegisterAttached(
@@ -645,14 +645,14 @@ public static void SetIsZoomChainingEnabled(global::Microsoft.UI.Xaml.Dependency
}
#endif
// Forced skipping of method Microsoft.UI.Xaml.Controls.ScrollViewer.IsScrollInertiaEnabledProperty.get
-#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__
+#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || false || __NETSTD_REFERENCE__ || false
[global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")]
public static bool GetIsScrollInertiaEnabled(global::Microsoft.UI.Xaml.DependencyObject element)
{
return (bool)element.GetValue(IsScrollInertiaEnabledProperty);
}
#endif
-#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__
+#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || false || __NETSTD_REFERENCE__ || false
[global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")]
public static void SetIsScrollInertiaEnabled(global::Microsoft.UI.Xaml.DependencyObject element, bool isScrollInertiaEnabled)
{
diff --git a/src/Uno.UI/UI/Input/GestureRecognizer.cs b/src/Uno.UI/UI/Input/GestureRecognizer.cs
index b714182873c2..e019af849a51 100644
--- a/src/Uno.UI/UI/Input/GestureRecognizer.cs
+++ b/src/Uno.UI/UI/Input/GestureRecognizer.cs
@@ -157,14 +157,7 @@ internal void ProcessUpEvent(PointerPoint value, bool isRelevant)
_log.Debug($"{Owner} Received a 'Up' for a pointer which was not considered as down. Ignoring event.");
}
- if (isRelevant)
- {
- _manipulation?.Remove(value);
- }
- else
- {
- _manipulation?.Complete();
- }
+ _manipulation?.Remove(value);
}
#if IS_UNIT_TESTS
diff --git a/src/Uno.UI/UI/Xaml/Controls/ScrollContentPresenter/ScrollContentPresenter.Managed.cs b/src/Uno.UI/UI/Xaml/Controls/ScrollContentPresenter/ScrollContentPresenter.Managed.cs
index bce88ecdcca5..38e919b18389 100644
--- a/src/Uno.UI/UI/Xaml/Controls/ScrollContentPresenter/ScrollContentPresenter.Managed.cs
+++ b/src/Uno.UI/UI/Xaml/Controls/ScrollContentPresenter/ScrollContentPresenter.Managed.cs
@@ -3,7 +3,6 @@
using Microsoft.UI.Xaml.Input;
using Windows.Devices.Input;
using Windows.Foundation;
-using Uno.UI.Xaml;
using Uno.Disposables;
@@ -218,6 +217,11 @@ private void PrepareTouchScroll(object sender, ManipulationStartingRoutedEventAr
return;
}
+ if (Scroller?.IsScrollInertiaEnabled is true)
+ {
+ e.Mode |= ManipulationModes.TranslateInertia;
+ }
+
if (!CanVerticallyScroll || ExtentHeight <= 0)
{
e.Mode &= ~ManipulationModes.TranslateY;
diff --git a/src/Uno.UI/UI/Xaml/Controls/ScrollViewer/ScrollViewer.Managed.cs b/src/Uno.UI/UI/Xaml/Controls/ScrollViewer/ScrollViewer.Managed.cs
index 9d507d5b7d2f..b1e5082efce6 100644
--- a/src/Uno.UI/UI/Xaml/Controls/ScrollViewer/ScrollViewer.Managed.cs
+++ b/src/Uno.UI/UI/Xaml/Controls/ScrollViewer/ScrollViewer.Managed.cs
@@ -18,6 +18,25 @@ namespace Microsoft.UI.Xaml.Controls
{
public partial class ScrollViewer
{
+ public bool IsScrollInertiaEnabled
+ {
+ get => (bool)GetValue(IsScrollInertiaEnabledProperty);
+ set => SetValue(IsScrollInertiaEnabledProperty, value);
+ }
+
+ public static DependencyProperty IsScrollInertiaEnabledProperty { get; } =
+ DependencyProperty.RegisterAttached(
+ nameof(IsScrollInertiaEnabled),
+ typeof(bool),
+ typeof(ScrollViewer),
+ new FrameworkPropertyMetadata(true));
+
+ public static bool GetIsScrollInertiaEnabled(DependencyObject element) =>
+ (bool)element.GetValue(IsScrollInertiaEnabledProperty);
+
+ public static void SetIsScrollInertiaEnabled(DependencyObject element, bool isScrollInertiaEnabled) =>
+ element.SetValue(IsScrollInertiaEnabledProperty, isScrollInertiaEnabled);
+
internal Size ScrollBarSize => (_presenter as ScrollContentPresenter)?.ScrollBarSize ?? default;
private bool ChangeViewNative(double? horizontalOffset, double? verticalOffset, double? zoomFactor, bool disableAnimation)
diff --git a/src/Uno.UWP/UI/Input/Preview.Injection/InjectedInputMouseInfo.cs b/src/Uno.UWP/UI/Input/Preview.Injection/InjectedInputMouseInfo.cs
index e92d773bb4df..001e75e98fb0 100644
--- a/src/Uno.UWP/UI/Input/Preview.Injection/InjectedInputMouseInfo.cs
+++ b/src/Uno.UWP/UI/Input/Preview.Injection/InjectedInputMouseInfo.cs
@@ -1,5 +1,6 @@
#nullable enable
+using System;
using Windows.Devices.Input;
using Windows.Foundation;
using Windows.System;
diff --git a/src/Uno.UWP/UI/Input/Preview.Injection/InjectedInputState.cs b/src/Uno.UWP/UI/Input/Preview.Injection/InjectedInputState.cs
index c66f41dee36b..908213c778a1 100644
--- a/src/Uno.UWP/UI/Input/Preview.Injection/InjectedInputState.cs
+++ b/src/Uno.UWP/UI/Input/Preview.Injection/InjectedInputState.cs
@@ -1,6 +1,7 @@
#nullable enable
using System;
+using System.Diagnostics;
using System.Linq;
using Windows.Devices.Input;
using Windows.Foundation;
@@ -10,10 +11,12 @@ namespace Windows.UI.Input.Preview.Injection;
internal class InjectedInputState
{
+ private static long _initialTimestamp = Stopwatch.GetTimestamp();
+
public InjectedInputState(PointerDeviceType type)
{
Type = type;
- StartNewSequence();
+ StartNewSequence(true);
}
public PointerDeviceType Type { get; }
@@ -28,10 +31,18 @@ public InjectedInputState(PointerDeviceType type)
public PointerPointProperties Properties { get; set; } = new();
- public void StartNewSequence()
+ public void StartNewSequence(bool initial = false)
{
- Timestamp = (ulong)DateTime.Now.Ticks;
- FrameId = (uint)(Timestamp / TimeSpan.TicksPerMillisecond);
+ if (initial)
+ {
+ Timestamp = (ulong)Stopwatch.GetElapsedTime(_initialTimestamp).TotalMicroseconds;
+ }
+ else
+ {
+ Timestamp = Timestamp + 1000; // Continue from the previous timestamp, but move forward in time by 1ms
+ }
+
+ FrameId = (uint)(Timestamp / 1000);
}
public void Update(PointerEventArgs args)