From b71b5403f036655f8554712be84c6d609c1c47e9 Mon Sep 17 00:00:00 2001 From: michael-hawker <24302614+michael-hawker@users.noreply.github.com> Date: Wed, 15 Jun 2022 19:43:47 -0700 Subject: [PATCH 01/13] Add initial touch injection API test See comments in https://github.com/CommunityToolkit/Labs-Windows/issues/124 --- .../App.xaml.cs | 2 + ...mmunityToolkit.Labs.Tests.Shared.projitems | 1 + .../SimulateInput.cs | 163 ++++++++++++++++++ .../ExampleSizerBaseTestClass.cs | 28 +++ .../SizerBase.Tests/SizerBase.Tests.projitems | 7 + .../SizerBase.Tests/TouchInjectionTest.xaml | 18 ++ .../TouchInjectionTest.xaml.cs | 43 +++++ 7 files changed, 262 insertions(+) create mode 100644 common/CommunityToolkit.Labs.Tests.Shared/SimulateInput.cs create mode 100644 labs/SizerBase/tests/SizerBase.Tests/TouchInjectionTest.xaml create mode 100644 labs/SizerBase/tests/SizerBase.Tests/TouchInjectionTest.xaml.cs diff --git a/common/CommunityToolkit.Labs.Tests.Shared/App.xaml.cs b/common/CommunityToolkit.Labs.Tests.Shared/App.xaml.cs index 1b12ef5c4..a26f0aa0e 100644 --- a/common/CommunityToolkit.Labs.Tests.Shared/App.xaml.cs +++ b/common/CommunityToolkit.Labs.Tests.Shared/App.xaml.cs @@ -42,6 +42,8 @@ public sealed partial class App : Application private static Windows.UI.Xaml.Window currentWindow = Windows.UI.Xaml.Window.Current; #endif + public static Rect Bounds => currentWindow.Bounds; + // Holder for test content to abstract Window.Current.Content public static FrameworkElement? ContentRoot { diff --git a/common/CommunityToolkit.Labs.Tests.Shared/CommunityToolkit.Labs.Tests.Shared.projitems b/common/CommunityToolkit.Labs.Tests.Shared/CommunityToolkit.Labs.Tests.Shared.projitems index 9dad91eb3..bac707d91 100644 --- a/common/CommunityToolkit.Labs.Tests.Shared/CommunityToolkit.Labs.Tests.Shared.projitems +++ b/common/CommunityToolkit.Labs.Tests.Shared/CommunityToolkit.Labs.Tests.Shared.projitems @@ -19,6 +19,7 @@ App.xaml + \ No newline at end of file diff --git a/common/CommunityToolkit.Labs.Tests.Shared/SimulateInput.cs b/common/CommunityToolkit.Labs.Tests.Shared/SimulateInput.cs new file mode 100644 index 000000000..82e7c6d7e --- /dev/null +++ b/common/CommunityToolkit.Labs.Tests.Shared/SimulateInput.cs @@ -0,0 +1,163 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Windows.UI.Input.Preview.Injection; +using Windows.Foundation; + +#if !WINAPPSDK +using Windows.UI.Input; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Media; +#else +using Microsoft.UI.Input; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Media; +#endif + +namespace CommunityToolkit.Labs.UnitTests; + +public static class SimulateInput +{ + private static InputInjector _input = InputInjector.TryCreate(); + private static uint _currentPointerId = 0; + + public static void StartTouch() + { + Assert.IsNotNull(_input); + + _input.InitializeTouchInjection( + InjectedInputVisualizationMode.Default); + } + + /// + /// Simulates a touch press on screen at the coordinates provided, in app-local coordinates. For instance use App.ContentRoot.CoordinatesTo(element). + /// + /// + /// + public static uint TouchDown(Point point) + { + // Create a unique pointer ID for the injected touch pointer. + // Multiple input pointers would require more robust handling. + uint pointerId = _currentPointerId++; + + var injectionPoint = TranslatePointForWindow(point); + + // Create a touch data point for pointer down. + // Each element in the touch data list represents a single touch contact. + // For this example, we're mirroring a single mouse pointer. + List touchData = new() + { + new() + { + Contact = new InjectedInputRectangle + { + Left = 30, Top = 30, Bottom = 30, Right = 30 + }, + PointerInfo = new InjectedInputPointerInfo + { + PointerId = pointerId, + PointerOptions = + InjectedInputPointerOptions.PointerDown | + InjectedInputPointerOptions.InContact | + InjectedInputPointerOptions.New, + TimeOffsetInMilliseconds = 0, + PixelLocation = new InjectedInputPoint + { + PositionX = (int)injectionPoint.X , + PositionY = (int)injectionPoint.Y + } + }, + Pressure = 1.0, + TouchParameters = + InjectedInputTouchParameters.Pressure | + InjectedInputTouchParameters.Contact + } + }; + + // Inject the touch input. + _input.InjectTouchInput(touchData); + + return pointerId; + } + + public static void TouchMove(uint pointerId, int cX, int cY) + { + // Create a touch data point for pointer up. + List touchData = new() + { + new() + { + Contact = new InjectedInputRectangle + { + Left = 30, Top = 30, Bottom = 30, Right = 30 + }, + PointerInfo = new InjectedInputPointerInfo + { + PointerId = pointerId, + PointerOptions = + InjectedInputPointerOptions.InRange | + InjectedInputPointerOptions.InContact, + TimeOffsetInMilliseconds = 0, + PixelLocation = new InjectedInputPoint + { + PositionX = (int)cX , + PositionY = (int)cY + } + }, + Pressure = 1.0, + TouchParameters = + InjectedInputTouchParameters.Pressure | + InjectedInputTouchParameters.Contact + } + }; + + // Inject the touch input. + _input.InjectTouchInput(touchData); + } + + public static void TouchUp(uint pointerId) + { + Assert.IsNotNull(_input); + + // Create a touch data point for pointer up. + List touchData = new() + { + new() + { + PointerInfo = new InjectedInputPointerInfo + { + PointerId = pointerId, + PointerOptions = InjectedInputPointerOptions.PointerUp + } + } + }; + + // Inject the touch input. + _input.InjectTouchInput(touchData); + } + + public static void StopTouch() + { + // Shut down the virtual input device. + _input.UninitializeTouchInjection(); + } + + private static Point TranslatePointForWindow(Point point) + { + // TODO: Do we want a ToPoint extension in the Toolkit? (is there an existing enum we can use to specify which corner/point of the rect? e.g. topleft, center, middleright, etc...? + + // Get the top left screen coordinates of the app window rect. + Point appBoundsTopLeft = new Point(App.Bounds.Left, App.Bounds.Top); + + // Create the point for input injection and calculate its screen location. + return new Point( + appBoundsTopLeft.X + point.X, + appBoundsTopLeft.Y + point.Y); + + } +} diff --git a/labs/SizerBase/tests/SizerBase.Tests/ExampleSizerBaseTestClass.cs b/labs/SizerBase/tests/SizerBase.Tests/ExampleSizerBaseTestClass.cs index 28d0237ca..b7cd30c66 100644 --- a/labs/SizerBase/tests/SizerBase.Tests/ExampleSizerBaseTestClass.cs +++ b/labs/SizerBase/tests/SizerBase.Tests/ExampleSizerBaseTestClass.cs @@ -57,4 +57,32 @@ public void PropertySizer_TestChangeBinding(PropertySizerTestInitialBinding test // Set in XAML Page LINK: PropertySizerTestInitialBinding.xaml#L14 Assert.AreEqual(200, propertySizer.Binding, "Property Sizer not at expected changed value."); } + + [LabsUITestMethod] + public async Task InputInjection_TestClickButton(TouchInjectionTest testControl) + { + var button = testControl.FindDescendant