Merge pull request #2460 from unoplatform/dev/dr/RightTapped
Add RightTapped event support
This commit is contained in:
Коммит
942c11ac95
|
@ -580,16 +580,16 @@
|
|||
<Methods>
|
||||
<Member
|
||||
fullName="System.Void Windows.UI.Xaml.RoutedEvent..ctor(System.String name)"
|
||||
reason="Not part of the UWP API." />
|
||||
reason="Not part of the WinUI API." />
|
||||
<Member
|
||||
fullName="Windows.Foundation.Point Windows.UI.Xaml.Input.DoubleTappedRoutedEventArgs.GetPosition()"
|
||||
reason="Not part of the UWP API." />
|
||||
reason="Not part of the WinUI API." />
|
||||
<Member
|
||||
fullName="Windows.Foundation.Point Windows.UI.Xaml.Input.TappedRoutedEventArgs.GetPosition()"
|
||||
reason="Not part of the UWP API." />
|
||||
reason="Not part of the WinUI API." />
|
||||
<Member
|
||||
fullName="System.Void Windows.UI.ViewManagement.ApplicationViewTitleBar..ctor()"
|
||||
reason="Parameter-less ctor does not exist in UWP" />
|
||||
reason="Parameter-less ctor does not exist in WinUI" />
|
||||
|
||||
<Member
|
||||
fullName="CoreGraphics.CGSize Windows.UI.Xaml.Controls.VirtualizingPanelLayout.GetItemSizeForIndexPath(Foundation.NSIndexPath indexPath)"
|
||||
|
@ -597,6 +597,16 @@
|
|||
<Member
|
||||
fullName="CoreGraphics.CGSize Windows.UI.Xaml.Controls.ListViewBaseSource.GetItemSize(UIKit.UICollectionView collectionView, Foundation.NSIndexPath indexPath)"
|
||||
reason="Made internal, shouldn't normally be called by consumer code"/>
|
||||
|
||||
<Member
|
||||
fullName="System.Boolean Windows.UI.Xaml.Input.RightTappedRoutedEventArgs.get_Handled()"
|
||||
reason="Auto property"/>
|
||||
<Member
|
||||
fullName="System.Void Windows.UI.Xaml.Input.RightTappedRoutedEventArgs.set_Handled(System.Boolean value)"
|
||||
reason="Auto property"/>
|
||||
<Member
|
||||
fullName="System.Void Windows.UI.Input.RightTappedEventArgs..ctor()"
|
||||
reason="Not part of the WinUI API."/>
|
||||
</Methods>
|
||||
</IgnoreSet>
|
||||
</IgnoreSets>
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
- `TransformToVisual` now returns a real transform to convert coordinates between views (was only returning a translate transform to offset the origin of controls)
|
||||
- Multiple pointers at same time on screen (a.k.a. Multi-touch) are now supported
|
||||
- Add support for WinUI 2.3 [`NumberBox`](https://docs.microsoft.com/en-us/uwp/api/microsoft.ui.xaml.controls.numberbox?view=winui-2.3)
|
||||
- Add support of the `UIElement.RightTapped` event
|
||||
|
||||
### Breaking changes
|
||||
-
|
||||
|
|
|
@ -239,6 +239,9 @@ As those events are tightly coupled to the native events, Uno has to make some c
|
|||
* On Firefox, pressed pointers are reported as fingers. This means you will receive events with `PointerDeviceType == Pen` only for hovering
|
||||
(i.e. `Pointer<Enter|Move|Exit>` - note that, as of 2019-11-28, once pressed `PointerMove` will be flagged as "touch")
|
||||
and you won't be able to track the barrel button nor the eraser. (cf. https://bugzilla.mozilla.org/show_bug.cgi?id=1449660)
|
||||
* On WASM, if you touch the screen with the pen **then** you press the barrel button (still while touching the screen), the pointer events will
|
||||
have the `IsRightButtonPressed` set (in addition of the `IsBarrelButtonPressed`). On WinUI and Android you get this flag only if the barrel
|
||||
button was pressed at the moment where you touched the screen, otherwise you will have the `IsLeftButtonPressed` and the `IsBarrelButtonPressed`.
|
||||
|
||||
### Pointer capture
|
||||
|
||||
|
|
|
@ -0,0 +1,128 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using SamplesApp.UITests.TestFramework;
|
||||
using Uno.UITest.Helpers;
|
||||
using Uno.UITest.Helpers.Queries;
|
||||
using Uno.UITests.Helpers;
|
||||
|
||||
namespace SamplesApp.UITests.Windows_UI_Xaml_Input
|
||||
{
|
||||
[Ignore("DoubleTapCoordinates is not implemented yet https://github.com/unoplatform/Uno.UITest/issues/29")]
|
||||
public class DoubleTapped_Tests : SampleControlUITestBase
|
||||
{
|
||||
private const string _xamlTestPage = "UITests.Shared.Windows_UI_Input.GestureRecognizerTests.DoubleTappedTests";
|
||||
|
||||
[Test]
|
||||
[AutoRetry]
|
||||
public void When_Basic()
|
||||
{
|
||||
Run(_xamlTestPage);
|
||||
|
||||
const string targetName = "Basic_Target";
|
||||
const int tapX = 10, tapY = 10;
|
||||
|
||||
// Double tap the target
|
||||
var target = _app.WaitForElement(targetName).Single().Rect;
|
||||
_app.DoubleTapCoordinates(target.X + tapX, target.Y + tapY);
|
||||
|
||||
var result = GestureResult.Get(_app.Marked("LastDoubleTapped"));
|
||||
result.Element.Should().Be(targetName);
|
||||
((int)result.X).Should().Be(tapX);
|
||||
((int)result.Y).Should().Be(tapY);
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
[AutoRetry]
|
||||
[ActivePlatforms(Platform.Browser, Platform.iOS)] // Disabled on Android: The test engine is not able to find "Transformed_Target"
|
||||
public void When_Transformed()
|
||||
{
|
||||
Run(_xamlTestPage);
|
||||
|
||||
const string parentName = "Transformed_Parent";
|
||||
const string targetName = "Transformed_Target";
|
||||
|
||||
var parent = _app.WaitForElement(parentName).Single().Rect;
|
||||
var target = _app.WaitForElement(targetName).Single().Rect;
|
||||
|
||||
// Double tap the target
|
||||
_app.DoubleTapCoordinates(parent.Right - target.Width, parent.Bottom - 3);
|
||||
|
||||
var result = GestureResult.Get(_app.Marked("LastDoubleTapped"));
|
||||
result.Element.Should().Be(targetName);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[AutoRetry]
|
||||
public void When_InScroll()
|
||||
{
|
||||
Run(_xamlTestPage);
|
||||
|
||||
const string targetName = "InScroll_Target";
|
||||
const int tapX = 10, tapY = 10;
|
||||
|
||||
// Scroll to make the target visible
|
||||
var scroll = _app.WaitForElement("InScroll_ScrollViewer").Single().Rect;
|
||||
_app.DragCoordinates(scroll.Right - 3, scroll.Bottom - 3, 0, 0);
|
||||
|
||||
// Double tap the target
|
||||
var target = _app.WaitForElement(targetName).Single();
|
||||
_app.DoubleTapCoordinates(target.Rect.X + tapX, target.Rect.Y + tapY);
|
||||
|
||||
var result = GestureResult.Get(_app.Marked("LastDoubleTapped"));
|
||||
result.Element.Should().Be(targetName);
|
||||
((int)result.X).Should().Be(tapX);
|
||||
((int)result.Y).Should().Be(tapY);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[AutoRetry]
|
||||
public void When_InListViewWithItemClick()
|
||||
{
|
||||
Run(_xamlTestPage);
|
||||
|
||||
const string targetName = "ListViewWithItemClick";
|
||||
|
||||
// Scroll a bit in the ListView
|
||||
var target = _app.WaitForElement(targetName).Single().Rect;
|
||||
_app.DragCoordinates(target.CenterX, target.Bottom - 3, target.CenterX, target.Y + 3);
|
||||
|
||||
// Tap and hold an item
|
||||
_app.DoubleTapCoordinates(target.CenterX, target.CenterY - 5);
|
||||
|
||||
var result = GestureResult.Get(_app.Marked("LastDoubleTapped"));
|
||||
var expectedItem = AppInitializer.GetLocalPlatform() == Platform.Browser
|
||||
? "Item_1" // We were not able to scroll on WASM!
|
||||
: "Item_3";
|
||||
result.Element.Should().Be(expectedItem);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[AutoRetry]
|
||||
public void When_InListViewWithoutItemClick()
|
||||
{
|
||||
Run(_xamlTestPage);
|
||||
|
||||
const string targetName = "ListViewWithoutItemClick";
|
||||
|
||||
// Scroll a bit in the ListView
|
||||
var target = _app.WaitForElement(targetName).Single().Rect;
|
||||
_app.DragCoordinates(target.CenterX, target.Bottom - 3, target.CenterX, target.Y + 3);
|
||||
|
||||
// Tap and hold an item
|
||||
_app.DoubleTapCoordinates(target.CenterX, target.CenterY - 5);
|
||||
|
||||
var result = GestureResult.Get(_app.Marked("LastDoubleTapped"));
|
||||
var expectedItem = AppInitializer.GetLocalPlatform() == Platform.Browser
|
||||
? "Item_1" // We were not able to scroll on WASM!
|
||||
: "Item_3";
|
||||
result.Element.Should().Be(expectedItem);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,14 +11,14 @@ using Uno.UITest.Helpers.Queries;
|
|||
|
||||
namespace SamplesApp.UITests.Windows_UI_Xaml_Input
|
||||
{
|
||||
public class Tapped_Tests : SampleControlUITestBase
|
||||
public class GestureEventsCommons_Tests : SampleControlUITestBase
|
||||
{
|
||||
[Test]
|
||||
[AutoRetry]
|
||||
[ActivePlatforms(Platform.Android)] // Fixed in another commit coming soon
|
||||
public void When_Tapped_Then_ArgsLocationIsValid()
|
||||
{
|
||||
Run("UITests.Shared.Windows_UI_Input.GestureRecognizerTests.TappedTest");
|
||||
Run("UITests.Shared.Windows_UI_Input.GestureRecognizerTests.GestureEventsCommons");
|
||||
|
||||
var root = _app.WaitForElement("WhenTappedThenArgsLocationIsValid_Root").Single();
|
||||
var target = _app.WaitForElement("WhenTappedThenArgsLocationIsValid_Target").Single();
|
||||
|
@ -39,7 +39,7 @@ namespace SamplesApp.UITests.Windows_UI_Xaml_Input
|
|||
[AutoRetry]
|
||||
public void When_ChildHandlesPointers()
|
||||
{
|
||||
Run("UITests.Shared.Windows_UI_Input.GestureRecognizerTests.TappedTest");
|
||||
Run("UITests.Shared.Windows_UI_Input.GestureRecognizerTests.GestureEventsCommons");
|
||||
|
||||
var target = _app.WaitForElement("WhenChildHandlesPointers_Target").Single();
|
||||
|
||||
|
@ -47,14 +47,14 @@ namespace SamplesApp.UITests.Windows_UI_Xaml_Input
|
|||
_app.TapCoordinates(target.Rect.X + tapX, target.Rect.Y + tapY);
|
||||
|
||||
var result = _app.Marked("WhenChildHandlesPointers_Result").GetDependencyPropertyValue<string>("Text");
|
||||
result.Should().Be("Tapped");
|
||||
result.Should().Be("Yes");
|
||||
}
|
||||
|
||||
[Test]
|
||||
[AutoRetry]
|
||||
public void When_MultipleTappedRecognizer()
|
||||
{
|
||||
Run("UITests.Shared.Windows_UI_Input.GestureRecognizerTests.TappedTest");
|
||||
Run("UITests.Shared.Windows_UI_Input.GestureRecognizerTests.GestureEventsCommons");
|
||||
|
||||
var target = _app.WaitForElement("WhenMultipleTappedRecognizer_Target").Single();
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Uno.UITest.Helpers.Queries;
|
||||
|
||||
namespace SamplesApp.UITests.Windows_UI_Xaml_Input
|
||||
{
|
||||
internal class GestureResult
|
||||
{
|
||||
public static GestureResult Get(QueryEx textBlock)
|
||||
=> Parse(textBlock.GetDependencyPropertyValue<string>("Text"));
|
||||
|
||||
public static GestureResult Parse(string text)
|
||||
{
|
||||
var regex = new Regex(@"(?<elt>[\w_]+)@(?<x>[\d\.]+),(?<y>[\d\.]+)");
|
||||
var result = regex.Match(text);
|
||||
if (!result.Success)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(text), $"Cannot parse '{text}'.");
|
||||
}
|
||||
|
||||
return new GestureResult(
|
||||
result.Groups["elt"].Value,
|
||||
float.Parse(result.Groups["x"].Value, CultureInfo.InvariantCulture),
|
||||
float.Parse(result.Groups["y"].Value, CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
private GestureResult(string element, float x, float y)
|
||||
{
|
||||
Element = element;
|
||||
X = x;
|
||||
Y = y;
|
||||
}
|
||||
|
||||
public string Element { get; }
|
||||
|
||||
public float X { get; }
|
||||
|
||||
public float Y { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using SamplesApp.UITests.TestFramework;
|
||||
using Uno.UITest.Helpers;
|
||||
using Uno.UITest.Helpers.Queries;
|
||||
using Uno.UITests.Helpers;
|
||||
|
||||
namespace SamplesApp.UITests.Windows_UI_Xaml_Input
|
||||
{
|
||||
public class RightTapped_Tests : SampleControlUITestBase
|
||||
{
|
||||
private const string _xamlTestPage = "UITests.Shared.Windows_UI_Input.GestureRecognizerTests.RightTappedTests";
|
||||
|
||||
[Test]
|
||||
[AutoRetry]
|
||||
[ActivePlatforms(Platform.Android, Platform.iOS)] // We cannot test right button click on WASM yet
|
||||
public void When_Basic()
|
||||
{
|
||||
Run(_xamlTestPage);
|
||||
|
||||
const string targetName = "Basic_Target";
|
||||
const int tapX = 10, tapY = 10;
|
||||
|
||||
// Tap and hold the target
|
||||
var target = _app.WaitForElement(targetName).Single().Rect;
|
||||
_app.TouchAndHoldCoordinates(target.X + tapX, target.Y + tapY);
|
||||
|
||||
var result = GestureResult.Get(_app.Marked("LastRightTapped"));
|
||||
result.Element.Should().Be(targetName);
|
||||
((int)result.X).Should().Be(tapX);
|
||||
((int)result.Y).Should().Be(tapY);
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
[AutoRetry]
|
||||
[ActivePlatforms(Platform.iOS)] // We cannot test right button click on WASM yet + Disabled on Android: The test engine is not able to find "Transformed_Target"
|
||||
public void When_Transformed()
|
||||
{
|
||||
Run(_xamlTestPage);
|
||||
|
||||
const string parentName = "Transformed_Parent";
|
||||
const string targetName = "Transformed_Target";
|
||||
|
||||
var parent = _app.WaitForElement(parentName).Single().Rect;
|
||||
var target = _app.WaitForElement(targetName).Single().Rect;
|
||||
|
||||
// Tap and hold the target
|
||||
_app.TouchAndHoldCoordinates(parent.Right - target.Width, parent.Bottom - 3);
|
||||
|
||||
var result = GestureResult.Get(_app.Marked("LastRightTapped"));
|
||||
result.Element.Should().Be(targetName);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[AutoRetry]
|
||||
[ActivePlatforms(Platform.Android, Platform.iOS)] // We cannot test right button click on WASM yet
|
||||
public void When_InScroll()
|
||||
{
|
||||
Run(_xamlTestPage);
|
||||
|
||||
const string targetName = "InScroll_Target";
|
||||
const int tapX = 10, tapY = 10;
|
||||
|
||||
// Scroll to make the target visible
|
||||
var scroll = _app.WaitForElement("InScroll_ScrollViewer").Single().Rect;
|
||||
_app.DragCoordinates(scroll.Right - 3, scroll.Bottom - 3, 0, 0);
|
||||
|
||||
// Tap and hold the target
|
||||
var target = _app.WaitForElement(targetName).Single();
|
||||
_app.TouchAndHoldCoordinates(target.Rect.X + tapX, target.Rect.Y + tapY);
|
||||
|
||||
var result = GestureResult.Get(_app.Marked("LastRightTapped"));
|
||||
result.Element.Should().Be(targetName);
|
||||
((int)result.X).Should().Be(tapX);
|
||||
((int)result.Y).Should().Be(tapY);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[AutoRetry]
|
||||
[ActivePlatforms(Platform.Android, Platform.iOS)] // We cannot test right button click on WASM yet
|
||||
public void When_InListViewWithItemClick()
|
||||
{
|
||||
Run(_xamlTestPage);
|
||||
|
||||
const string targetName = "ListViewWithItemClick";
|
||||
|
||||
// Scroll a bit in the ListView
|
||||
var target = _app.WaitForElement(targetName).Single().Rect;
|
||||
_app.DragCoordinates(target.CenterX, target.Bottom - 3, target.CenterX, target.Y + 3);
|
||||
|
||||
// Tap and hold an item
|
||||
_app.TouchAndHoldCoordinates(target.CenterX, target.CenterY - 5);
|
||||
|
||||
var result = GestureResult.Get(_app.Marked("LastRightTapped"));
|
||||
var expectedItem = AppInitializer.GetLocalPlatform() == Platform.Browser
|
||||
? "none" // Long press not supported with mouse
|
||||
: "Item_3";
|
||||
result.Element.Should().Be(expectedItem);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[AutoRetry]
|
||||
[ActivePlatforms(Platform.Android, Platform.iOS)] // We cannot test right button click on WASM yet
|
||||
public void When_InListViewWithoutItemClick()
|
||||
{
|
||||
Run(_xamlTestPage);
|
||||
|
||||
const string targetName = "ListViewWithoutItemClick";
|
||||
|
||||
// Scroll a bit in the ListView
|
||||
var target = _app.WaitForElement(targetName).Single().Rect;
|
||||
_app.DragCoordinates(target.CenterX, target.Bottom - 3, target.CenterX, target.Y + 3);
|
||||
|
||||
// Tap and hold an item
|
||||
_app.TouchAndHoldCoordinates(target.CenterX, target.CenterY - 5);
|
||||
|
||||
var result = GestureResult.Get(_app.Marked("LastRightTapped"));
|
||||
var expectedItem = AppInitializer.GetLocalPlatform() == Platform.Browser
|
||||
? "none" // Long press not supported with mouse
|
||||
: "Item_3";
|
||||
result.Element.Should().Be(expectedItem);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using SamplesApp.UITests.TestFramework;
|
||||
using Uno.UITest.Helpers;
|
||||
using Uno.UITest.Helpers.Queries;
|
||||
using Uno.UITests.Helpers;
|
||||
|
||||
namespace SamplesApp.UITests.Windows_UI_Xaml_Input
|
||||
{
|
||||
public class Tapped_Tests : SampleControlUITestBase
|
||||
{
|
||||
private const string _xamlTestPage = "UITests.Shared.Windows_UI_Input.GestureRecognizerTests.TappedTest";
|
||||
|
||||
[Test]
|
||||
[AutoRetry]
|
||||
public void When_Basic()
|
||||
{
|
||||
Run(_xamlTestPage);
|
||||
|
||||
const string targetName = "Basic_Target";
|
||||
const int tapX = 10, tapY = 10;
|
||||
|
||||
// Tap the target
|
||||
var target = _app.WaitForElement(targetName).Single().Rect;
|
||||
_app.TapCoordinates(target.X + tapX, target.Y + tapY);
|
||||
|
||||
var result = GestureResult.Get(_app.Marked("LastTapped"));
|
||||
result.Element.Should().Be(targetName);
|
||||
((int)result.X).Should().Be(tapX);
|
||||
((int)result.Y).Should().Be(tapY);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[AutoRetry]
|
||||
[ActivePlatforms(Platform.iOS)] // Disabled on Android: The test engine is not able to find "Transformed_Target"
|
||||
public void When_Transformed()
|
||||
{
|
||||
Run(_xamlTestPage);
|
||||
|
||||
const string parentName = "Transformed_Parent";
|
||||
const string targetName = "Transformed_Target";
|
||||
|
||||
var parent = _app.WaitForElement(parentName).Single().Rect;
|
||||
var target = _app.WaitForElement(targetName).Single().Rect;
|
||||
|
||||
// Tap the target
|
||||
_app.TapCoordinates(parent.Right - target.Width, parent.Bottom - 3);
|
||||
|
||||
var result = GestureResult.Get(_app.Marked("LastTapped"));
|
||||
result.Element.Should().Be(targetName);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[AutoRetry]
|
||||
[ActivePlatforms(Platform.Android, Platform.iOS)] // We cannot scroll to reach the target on WASM yet
|
||||
public void When_InScroll()
|
||||
{
|
||||
Run(_xamlTestPage);
|
||||
|
||||
const string targetName = "InScroll_Target";
|
||||
const int tapX = 10, tapY = 10;
|
||||
|
||||
// Scroll to make the target visible
|
||||
var scroll = _app.WaitForElement("InScroll_ScrollViewer").Single().Rect;
|
||||
_app.DragCoordinates(scroll.Right - 3, scroll.Bottom - 3, 0, 0);
|
||||
|
||||
// Tap the target
|
||||
var target = _app.WaitForElement(targetName).Single();
|
||||
_app.TapCoordinates(target.Rect.X + tapX, target.Rect.Y + tapY);
|
||||
|
||||
var result = GestureResult.Get(_app.Marked("LastTapped"));
|
||||
result.Element.Should().Be(targetName);
|
||||
((int)result.X).Should().Be(tapX);
|
||||
((int)result.Y).Should().Be(tapY);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[AutoRetry]
|
||||
[ActivePlatforms(Platform.Android, Platform.iOS)] // We cannot scroll to reach the target on WASM yet
|
||||
public void When_InListViewWithItemClick()
|
||||
{
|
||||
Run(_xamlTestPage);
|
||||
|
||||
const string targetName = "ListViewWithItemClick";
|
||||
|
||||
// Scroll a bit in the ListView
|
||||
var target = _app.WaitForElement(targetName).Single().Rect;
|
||||
_app.DragCoordinates(target.CenterX, target.Bottom - 3, target.CenterX, target.Y + 3);
|
||||
|
||||
// Tap and hold an item
|
||||
_app.TapCoordinates(target.CenterX, target.CenterY - 5);
|
||||
|
||||
var result = GestureResult.Get(_app.Marked("LastTapped"));
|
||||
var expectedItem = AppInitializer.GetLocalPlatform() == Platform.Browser
|
||||
? "Item_1" // We were not able to scroll on WASM!
|
||||
: "Item_3";
|
||||
result.Element.Should().Be(expectedItem);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[AutoRetry]
|
||||
public void When_InListViewWithoutItemClick()
|
||||
{
|
||||
Run(_xamlTestPage);
|
||||
|
||||
const string targetName = "ListViewWithoutItemClick";
|
||||
|
||||
// Scroll a bit in the ListView
|
||||
var target = _app.WaitForElement(targetName).Single().Rect;
|
||||
_app.DragCoordinates(target.CenterX, target.Bottom - 3, target.CenterX, target.Y + 3);
|
||||
|
||||
// Tap and hold an item
|
||||
_app.TapCoordinates(target.CenterX, target.CenterY - 5);
|
||||
|
||||
var result = GestureResult.Get(_app.Marked("LastTapped"));
|
||||
var expectedItem = AppInitializer.GetLocalPlatform() == Platform.Browser
|
||||
? "Item_1" // We were not able to scroll on WASM!
|
||||
: "Item_3";
|
||||
result.Element.Should().Be(expectedItem);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -129,6 +129,14 @@
|
|||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="$(MSBuildThisFileDirectory)Windows_UI_Input\GestureRecognizerTests\DoubleTappedTests.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="$(MSBuildThisFileDirectory)Windows_UI_Input\GestureRecognizerTests\GestureEventsCommons.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="$(MSBuildThisFileDirectory)Windows_UI_Input\GestureRecognizerTests\ManipulationEvents.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
|
@ -141,6 +149,10 @@
|
|||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="$(MSBuildThisFileDirectory)Windows_UI_Input\GestureRecognizerTests\RightTappedTests.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="$(MSBuildThisFileDirectory)Windows_UI_Input\GestureRecognizerTests\TappedTest.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
|
@ -2967,6 +2979,12 @@
|
|||
<Compile Include="$(MSBuildThisFileDirectory)Windows_UI.Xaml_Automation\AutomationProperties_Name.xaml.cs">
|
||||
<DependentUpon>AutomationProperties_Name.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Windows_UI_Input\GestureRecognizerTests\DoubleTappedTests.xaml.cs">
|
||||
<DependentUpon>DoubleTappedTests.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Windows_UI_Input\GestureRecognizerTests\GestureEventsCommons.xaml.cs">
|
||||
<DependentUpon>GestureEventsCommons.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Windows_UI_Input\GestureRecognizerTests\ManipulationEvents.xaml.cs">
|
||||
<DependentUpon>ManipulationEvents.xaml</DependentUpon>
|
||||
</Compile>
|
||||
|
@ -2976,6 +2994,9 @@
|
|||
<Compile Include="$(MSBuildThisFileDirectory)Windows_UI_Input\GestureRecognizerTests\Manipulation_WhenInScrollViewer.xaml.cs">
|
||||
<DependentUpon>Manipulation_WhenInScrollViewer.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Windows_UI_Input\GestureRecognizerTests\RightTappedTests.xaml.cs">
|
||||
<DependentUpon>$fileinputname$.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Windows_UI_Input\GestureRecognizerTests\TappedTest.xaml.cs">
|
||||
<DependentUpon>TappedTest.xaml</DependentUpon>
|
||||
</Compile>
|
||||
|
|
|
@ -0,0 +1,207 @@
|
|||
<Page
|
||||
x:Class="UITests.Shared.Windows_UI_Input.GestureRecognizerTests.DoubleTappedTests"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:UITests.Shared.Windows_UI_Input.GestureRecognizerTests"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock
|
||||
Text="Double on each zones. Last double tapped:"
|
||||
TextWrapping="Wrap"
|
||||
Grid.Row="0"
|
||||
Grid.ColumnSpan="2"/>
|
||||
<TextBlock
|
||||
x:Name="LastDoubleTapped"
|
||||
Text="__none__"
|
||||
Grid.Row="1"
|
||||
Grid.ColumnSpan="2"/>
|
||||
|
||||
<TextBlock
|
||||
Text="Basic"
|
||||
Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
HorizontalAlignment="Center"/>
|
||||
<Border
|
||||
x:Name="Basic_Target"
|
||||
Grid.Row="3"
|
||||
Grid.Column="0"
|
||||
Width="150"
|
||||
Height="150"
|
||||
Background="#FF0018"
|
||||
DoubleTapped="TargetDoubleTapped" />
|
||||
|
||||
<TextBlock
|
||||
Text="With transformation"
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Center"/>
|
||||
<Border
|
||||
Grid.Row="3"
|
||||
Grid.Column="1"
|
||||
Width="150"
|
||||
Height="150"
|
||||
Background="#FFA52C"
|
||||
x:Name="Transformed_Parent">
|
||||
<Border
|
||||
x:Name="Transformed_Target"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
Width="50"
|
||||
Height="50"
|
||||
Background="#33000000"
|
||||
DoubleTapped="TargetDoubleTapped">
|
||||
<Border.RenderTransform>
|
||||
<CompositeTransform Rotation="45" TranslateX="100" TranslateY="100" />
|
||||
</Border.RenderTransform>
|
||||
</Border>
|
||||
</Border>
|
||||
|
||||
<TextBlock
|
||||
Text="In ScrollViewer"
|
||||
Grid.Row="4"
|
||||
Grid.Column="0"
|
||||
HorizontalAlignment="Center"/>
|
||||
<Border
|
||||
Grid.Row="5"
|
||||
Grid.Column="0"
|
||||
Width="150"
|
||||
Height="150"
|
||||
Background="#FFFF41">
|
||||
<ScrollViewer
|
||||
VerticalScrollMode="Enabled"
|
||||
VerticalScrollBarVisibility="Visible"
|
||||
HorizontalScrollMode="Enabled"
|
||||
HorizontalScrollBarVisibility="Visible"
|
||||
x:Name="InScroll_ScrollViewer">
|
||||
<Grid
|
||||
Width="300"
|
||||
Height="300">
|
||||
<Border
|
||||
x:Name="InScroll_Target"
|
||||
Width="100"
|
||||
Height="100"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Bottom"
|
||||
Background="#33000000"
|
||||
DoubleTapped="TargetDoubleTapped" />
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
|
||||
<TextBlock
|
||||
Text="In button"
|
||||
Grid.Row="4"
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Center"/>
|
||||
<Button
|
||||
Grid.Row="5"
|
||||
Grid.Column="1"
|
||||
Width="150"
|
||||
Height="150"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Background="#008018">
|
||||
<Border
|
||||
x:Name="InButton_Target"
|
||||
Width="100"
|
||||
Height="100"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Background="#33000000"
|
||||
DoubleTapped="TargetDoubleTapped" />
|
||||
</Button>
|
||||
|
||||
<TextBlock
|
||||
Text="ListView"
|
||||
Grid.Row="6"
|
||||
Grid.Column="0"
|
||||
HorizontalAlignment="Center"/>
|
||||
<Border
|
||||
Grid.Row="7"
|
||||
Grid.Column="0"
|
||||
Width="150"
|
||||
Height="150"
|
||||
Background="#0000F9">
|
||||
<ListView
|
||||
ItemsSource="0123456789abcdef"
|
||||
IsItemClickEnabled="True"
|
||||
SelectionMode="Single"
|
||||
x:Name="ListViewWithItemClick">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border
|
||||
Height="50"
|
||||
Width="125"
|
||||
Background="#11000000"
|
||||
BorderBrush="#33000000"
|
||||
BorderThickness="3"
|
||||
Margin="3"
|
||||
DoubleTapped="ItemDoubleTapped">
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center"
|
||||
Text="{Binding}" />
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
</Border>
|
||||
|
||||
|
||||
<TextBlock
|
||||
Text="ListView item click disabled"
|
||||
Grid.Row="6"
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Center"/>
|
||||
<Border
|
||||
Grid.Row="7"
|
||||
Grid.Column="1"
|
||||
Width="150"
|
||||
Height="150"
|
||||
Background="#86007D">
|
||||
<ListView
|
||||
ItemsSource="0123456789abcdef"
|
||||
IsItemClickEnabled="False"
|
||||
SelectionMode="None"
|
||||
x:Name="ListViewWithoutItemClick">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border
|
||||
Height="50"
|
||||
Width="125"
|
||||
Background="#11000000"
|
||||
BorderBrush="#33000000"
|
||||
BorderThickness="3"
|
||||
Margin="3"
|
||||
DoubleTapped="ItemDoubleTapped">
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center"
|
||||
Text="{Binding}" />
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Page>
|
|
@ -0,0 +1,35 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Input;
|
||||
using Uno.UI;
|
||||
using Uno.UI.Samples.Controls;
|
||||
|
||||
namespace UITests.Shared.Windows_UI_Input.GestureRecognizerTests
|
||||
{
|
||||
[SampleControlInfo("Gesture recognizer")]
|
||||
public sealed partial class DoubleTappedTests : Page
|
||||
{
|
||||
public DoubleTappedTests()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
|
||||
private void TargetDoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
|
||||
{
|
||||
var target = (FrameworkElement)sender;
|
||||
var position = e.GetPosition(target).LogicalToPhysicalPixels();
|
||||
|
||||
LastDoubleTapped.Text = $"{target.Name}@{position.X:F2},{position.Y:F2}";
|
||||
}
|
||||
|
||||
private void ItemDoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
|
||||
{
|
||||
var target = (FrameworkElement)sender;
|
||||
var position = e.GetPosition(target).LogicalToPhysicalPixels();
|
||||
|
||||
LastDoubleTapped.Text = $"Item_{target.DataContext}@{position.X:F2},{position.Y:F2}";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,148 @@
|
|||
<Page
|
||||
x:Class="UITests.Shared.Windows_UI_Input.GestureRecognizerTests.GestureEventsCommons"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:UITests.Shared.Windows_UI_Input.GestureRecognizerTests"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
|
||||
<StackPanel>
|
||||
<Border
|
||||
x:Name="WhenTappedThenArgsLocationIsValid_Root"
|
||||
Width="250"
|
||||
BorderThickness="3"
|
||||
BorderBrush="#FF0018"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top">
|
||||
<StackPanel>
|
||||
<TextBlock Text="Test event args location" />
|
||||
<Border
|
||||
x:Name="WhenTappedThenArgsLocationIsValid_Target"
|
||||
Width="100"
|
||||
Height="100"
|
||||
Tapped="WhenTappedThenArgsLocationIsValid_OnTargetTapped"
|
||||
Background="#FF0018">
|
||||
<TextBlock Text="Touch target" />
|
||||
</Border>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="Rel. to test root (physical px): " />
|
||||
<TextBlock Name="WhenTappedThenArgsLocationIsValid_Result_RelativeToRoot" />
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="Rel. to target (physical px): " />
|
||||
<TextBlock Name="WhenTappedThenArgsLocationIsValid_Result_RelativeToTarget" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Border
|
||||
Width="250"
|
||||
BorderThickness="3"
|
||||
BorderBrush="#FFA52C"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top">
|
||||
<StackPanel>
|
||||
<TextBlock Text="Test when child handles all pointer events" />
|
||||
<Border
|
||||
Width="100"
|
||||
Height="100"
|
||||
Background="#FFA52C"
|
||||
Tapped="WhenChildHandlesPointers_OnParentTapped">
|
||||
<Border
|
||||
x:Name="WhenChildHandlesPointers_Target"
|
||||
Width="70"
|
||||
Height="70"
|
||||
Background="#66FFFFFF"
|
||||
PointerEntered="HandlePointerEvent"
|
||||
PointerPressed="HandlePointerEvent"
|
||||
PointerMoved="HandlePointerEvent"
|
||||
PointerReleased="HandlePointerEvent"
|
||||
PointerExited="HandlePointerEvent"
|
||||
PointerCanceled="HandlePointerEvent"
|
||||
PointerCaptureLost="HandlePointerEvent"
|
||||
PointerWheelChanged="HandlePointerEvent">
|
||||
<TextBlock Text="Touch target"/>
|
||||
</Border>
|
||||
</Border>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="Tapped on parent: " />
|
||||
<TextBlock x:Name="WhenChildHandlesPointers_Result" Text="__no__" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Border
|
||||
Width="250"
|
||||
BorderThickness="3"
|
||||
BorderBrush="#FFFF41"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top">
|
||||
<StackPanel>
|
||||
<TextBlock Text="Test parent and child listen tapped event" />
|
||||
<Border
|
||||
Width="100"
|
||||
Height="100"
|
||||
Background="#FFFF41"
|
||||
Tapped="WhenMultipleTappedRecognizer_OnParentTapped">
|
||||
<Border
|
||||
x:Name="WhenMultipleTappedRecognizer_Target"
|
||||
Width="70"
|
||||
Height="70"
|
||||
Background="#66FFFFFF"
|
||||
Tapped="WhenMultipleTappedRecognizer_OnTargetTapped">
|
||||
<TextBlock Text="Touch target"/>
|
||||
</Border>
|
||||
</Border>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="Tapped on parent: " />
|
||||
<TextBlock x:Name="WhenMultipleTappedRecognizer_Result_Parent" Text="0" />
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="Tapped on target: " />
|
||||
<TextBlock x:Name="WhenMultipleTappedRecognizer_Result_Target" Text="0" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Border
|
||||
Width="250"
|
||||
BorderThickness="3"
|
||||
BorderBrush="#008018"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top">
|
||||
<StackPanel>
|
||||
<TextBlock Text="Test when parent capture pointer" />
|
||||
<Border
|
||||
x:Name="WhenParentCapturesPointer_Parent"
|
||||
Width="100"
|
||||
Height="100"
|
||||
Background="#008018"
|
||||
PointerPressed="WhenParentCapturesPointer_OnParentPointerPressed"
|
||||
Tapped="WhenParentCapturesPointer_OnParentTapped">
|
||||
<Border
|
||||
x:Name="WhenParentCapturesPointer_Target"
|
||||
Width="70"
|
||||
Height="70"
|
||||
Background="#66FFFFFF"
|
||||
Tapped="WhenParentCapturesPointer_OnTargetTapped">
|
||||
<TextBlock Text="Touch target"/>
|
||||
</Border>
|
||||
</Border>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="Captured by parent: " />
|
||||
<TextBlock x:Name="WhenParentCapturesPointer_Result_Captured" Text="__false__" />
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="Tapped on parent: " />
|
||||
<TextBlock x:Name="WhenParentCapturesPointer_Result_Parent" Text="__no__" />
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="Tapped on target: " />
|
||||
<TextBlock x:Name="WhenParentCapturesPointer_Result_Target" Text="__no__" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</Page>
|
|
@ -0,0 +1,53 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Input;
|
||||
using Uno.UI;
|
||||
using Uno.UI.Samples.Controls;
|
||||
|
||||
namespace UITests.Shared.Windows_UI_Input.GestureRecognizerTests
|
||||
{
|
||||
[SampleControlInfo("Gesture recognizer")]
|
||||
public sealed partial class GestureEventsCommons : Page
|
||||
{
|
||||
public GestureEventsCommons()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
|
||||
private void WhenTappedThenArgsLocationIsValid_OnTargetTapped(object sender, TappedRoutedEventArgs e)
|
||||
{
|
||||
var relativeToRoot = e.GetPosition(WhenTappedThenArgsLocationIsValid_Root).LogicalToPhysicalPixels();
|
||||
var relativeToTarget = e.GetPosition(WhenTappedThenArgsLocationIsValid_Target).LogicalToPhysicalPixels();
|
||||
|
||||
WhenTappedThenArgsLocationIsValid_Result_RelativeToRoot.Text = $"({(int)relativeToRoot.X:D},{(int)relativeToRoot.Y:D})";
|
||||
WhenTappedThenArgsLocationIsValid_Result_RelativeToTarget.Text = $"({(int)relativeToTarget.X:D},{(int)relativeToTarget.Y:D})";
|
||||
}
|
||||
|
||||
private void HandlePointerEvent(object sender, PointerRoutedEventArgs e)
|
||||
=> e.Handled = true;
|
||||
|
||||
private void WhenChildHandlesPointers_OnParentTapped(object sender, TappedRoutedEventArgs e)
|
||||
=> WhenChildHandlesPointers_Result.Text = "Yes";
|
||||
|
||||
private void WhenMultipleTappedRecognizer_OnParentTapped(object sender, TappedRoutedEventArgs e)
|
||||
=> WhenMultipleTappedRecognizer_Result_Parent.Text = int.TryParse(WhenMultipleTappedRecognizer_Result_Parent.Text, out var count)
|
||||
? (count + 1).ToString()
|
||||
: "1";
|
||||
|
||||
private void WhenMultipleTappedRecognizer_OnTargetTapped(object sender, TappedRoutedEventArgs e)
|
||||
=> WhenMultipleTappedRecognizer_Result_Target.Text = int.TryParse(WhenMultipleTappedRecognizer_Result_Target.Text, out var count)
|
||||
? (count + 1).ToString()
|
||||
: "1";
|
||||
|
||||
private void WhenParentCapturesPointer_OnParentPointerPressed(object sender, PointerRoutedEventArgs e)
|
||||
=> WhenParentCapturesPointer_Result_Captured.Text = ((UIElement)sender).CapturePointer(e.Pointer).ToString();
|
||||
|
||||
private void WhenParentCapturesPointer_OnParentTapped(object sender, TappedRoutedEventArgs e)
|
||||
=> WhenParentCapturesPointer_Result_Parent.Text = "Yes";
|
||||
|
||||
private void WhenParentCapturesPointer_OnTargetTapped(object sender, TappedRoutedEventArgs e)
|
||||
=> WhenParentCapturesPointer_Result_Target.Text = "Yes";
|
||||
}
|
||||
}
|
|
@ -342,6 +342,18 @@
|
|||
Content="Handle event"
|
||||
Margin="20, 0"
|
||||
Visibility="{Binding ElementName=_gestureDoubleTapped, Path=IsOn}"/>
|
||||
<ToggleSwitch
|
||||
x:Name="_gestureRightTapped"
|
||||
Header="Right tapped"
|
||||
OnContent="Subscribed to right tap"
|
||||
OffContent="Right tap disabled"
|
||||
Toggled="OnConfigChanged"
|
||||
IsOn="False" />
|
||||
<CheckBox
|
||||
x:Name="_gestureRightTappedHandle"
|
||||
Content="Handle event"
|
||||
Margin="20, 0"
|
||||
Visibility="{Binding ElementName=_gestureRightTapped, Path=IsOn}"/>
|
||||
|
||||
|
||||
<Slider
|
||||
|
|
|
@ -49,6 +49,7 @@ namespace UITests.Shared.Windows_UI_Input.GestureRecognizer
|
|||
private readonly ManipulationCompletedEventHandler _logManipulationCompleted;
|
||||
private readonly TappedEventHandler _logTapped;
|
||||
private readonly DoubleTappedEventHandler _logDoubleTapped;
|
||||
private readonly RightTappedEventHandler _logRightTapped;
|
||||
|
||||
private bool _isReady;
|
||||
|
||||
|
@ -77,6 +78,7 @@ namespace UITests.Shared.Windows_UI_Input.GestureRecognizer
|
|||
_logManipulationCompleted = new ManipulationCompletedEventHandler(CreateHandler(ManipulationCompletedEvent, "Manip completed", _manipCompletedHandle));
|
||||
_logTapped = new TappedEventHandler(CreateHandler(TappedEvent, "Tapped", _gestureTappedHandle));
|
||||
_logDoubleTapped = new DoubleTappedEventHandler(CreateHandler(DoubleTappedEvent, "DoubleTapped", _gestureDoubleTappedHandle));
|
||||
_logRightTapped = new RightTappedEventHandler(CreateHandler(RightTappedEvent, "RightTapped", _gestureRightTappedHandle));
|
||||
|
||||
_log.ItemsSource = _eventLog;
|
||||
_pointerType.ItemsSource = Enum.GetNames(typeof(PointerDeviceType));
|
||||
|
@ -162,6 +164,7 @@ namespace UITests.Shared.Windows_UI_Input.GestureRecognizer
|
|||
target.RemoveHandler(ManipulationCompletedEvent, _logManipulationCompleted);
|
||||
target.RemoveHandler(TappedEvent, _logTapped);
|
||||
target.RemoveHandler(DoubleTappedEvent, _logDoubleTapped);
|
||||
target.RemoveHandler(RightTappedEvent, _logRightTapped);
|
||||
|
||||
if (allEvents || _ptEntered.IsOn)
|
||||
target.AddHandler(PointerEnteredEvent, _logPointerEntered, handledToo);
|
||||
|
@ -191,6 +194,8 @@ namespace UITests.Shared.Windows_UI_Input.GestureRecognizer
|
|||
target.AddHandler(TappedEvent, _logTapped, handledToo);
|
||||
if (allEvents || _gestureDoubleTapped.IsOn)
|
||||
target.AddHandler(DoubleTappedEvent, _logDoubleTapped, handledToo);
|
||||
if (allEvents || _gestureRightTapped.IsOn)
|
||||
target.AddHandler(RightTappedEvent, _logRightTapped, handledToo);
|
||||
}
|
||||
|
||||
private (EventValidity, string error) Validate(object snd, RoutedEvent evt, RoutedEventArgs args)
|
||||
|
@ -329,6 +334,8 @@ namespace UITests.Shared.Windows_UI_Input.GestureRecognizer
|
|||
return $"{Src(tapped)} | hd={tapped.Handled} | position={F(tapped.GetPosition(Sender as UIElement))}";
|
||||
case DoubleTappedRoutedEventArgs doubleTapped:
|
||||
return $"{Src(doubleTapped)} | hd={doubleTapped.Handled} | position={F(doubleTapped.GetPosition(Sender as UIElement))}";
|
||||
case RightTappedRoutedEventArgs rightTapped:
|
||||
return $"{Src(rightTapped)} | hd={rightTapped.Handled} | position={F(rightTapped.GetPosition(Sender as UIElement))}";
|
||||
|
||||
default:
|
||||
return string.Empty;
|
||||
|
|
|
@ -0,0 +1,207 @@
|
|||
<Page
|
||||
x:Class="UITests.Shared.Windows_UI_Input.GestureRecognizerTests.RightTappedTests"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:UITests.Shared.Windows_UI_Input.GestureRecognizerTests"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock
|
||||
Text="Long press (touch), right click (mouse), use barrel button (pen) on each zones. Last right tapped:"
|
||||
TextWrapping="Wrap"
|
||||
Grid.Row="0"
|
||||
Grid.ColumnSpan="2"/>
|
||||
<TextBlock
|
||||
x:Name="LastRightTapped"
|
||||
Text="__none__"
|
||||
Grid.Row="1"
|
||||
Grid.ColumnSpan="2"/>
|
||||
|
||||
<TextBlock
|
||||
Text="Basic"
|
||||
Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
HorizontalAlignment="Center"/>
|
||||
<Border
|
||||
x:Name="Basic_Target"
|
||||
Grid.Row="3"
|
||||
Grid.Column="0"
|
||||
Width="150"
|
||||
Height="150"
|
||||
Background="#FF0018"
|
||||
RightTapped="TargetRightTapped" />
|
||||
|
||||
<TextBlock
|
||||
Text="With transformation"
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Center"/>
|
||||
<Border
|
||||
Grid.Row="3"
|
||||
Grid.Column="1"
|
||||
Width="150"
|
||||
Height="150"
|
||||
Background="#FFA52C"
|
||||
x:Name="Transformed_Parent">
|
||||
<Border
|
||||
x:Name="Transformed_Target"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
Width="50"
|
||||
Height="50"
|
||||
Background="#33000000"
|
||||
RightTapped="TargetRightTapped">
|
||||
<Border.RenderTransform>
|
||||
<CompositeTransform Rotation="45" TranslateX="100" TranslateY="100" />
|
||||
</Border.RenderTransform>
|
||||
</Border>
|
||||
</Border>
|
||||
|
||||
<TextBlock
|
||||
Text="In ScrollViewer"
|
||||
Grid.Row="4"
|
||||
Grid.Column="0"
|
||||
HorizontalAlignment="Center"/>
|
||||
<Border
|
||||
Grid.Row="5"
|
||||
Grid.Column="0"
|
||||
Width="150"
|
||||
Height="150"
|
||||
Background="#FFFF41">
|
||||
<ScrollViewer
|
||||
VerticalScrollMode="Enabled"
|
||||
VerticalScrollBarVisibility="Visible"
|
||||
HorizontalScrollMode="Enabled"
|
||||
HorizontalScrollBarVisibility="Visible"
|
||||
x:Name="InScroll_ScrollViewer">
|
||||
<Grid
|
||||
Width="300"
|
||||
Height="300">
|
||||
<Border
|
||||
x:Name="InScroll_Target"
|
||||
Width="100"
|
||||
Height="100"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Bottom"
|
||||
Background="#33000000"
|
||||
RightTapped="TargetRightTapped" />
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
|
||||
<TextBlock
|
||||
Text="In button"
|
||||
Grid.Row="4"
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Center"/>
|
||||
<Button
|
||||
Grid.Row="5"
|
||||
Grid.Column="1"
|
||||
Width="150"
|
||||
Height="150"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Background="#008018">
|
||||
<Border
|
||||
x:Name="InButton_Target"
|
||||
Width="100"
|
||||
Height="100"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Background="#33000000"
|
||||
RightTapped="TargetRightTapped" />
|
||||
</Button>
|
||||
|
||||
<TextBlock
|
||||
Text="ListView"
|
||||
Grid.Row="6"
|
||||
Grid.Column="0"
|
||||
HorizontalAlignment="Center"/>
|
||||
<Border
|
||||
Grid.Row="7"
|
||||
Grid.Column="0"
|
||||
Width="150"
|
||||
Height="150"
|
||||
Background="#0000F9">
|
||||
<ListView
|
||||
ItemsSource="0123456789abcdef"
|
||||
IsItemClickEnabled="True"
|
||||
SelectionMode="Single"
|
||||
x:Name="ListViewWithItemClick">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border
|
||||
Height="50"
|
||||
Width="125"
|
||||
Background="#11000000"
|
||||
BorderBrush="#33000000"
|
||||
BorderThickness="3"
|
||||
Margin="3"
|
||||
RightTapped="ItemRightTapped">
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center"
|
||||
Text="{Binding}" />
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
</Border>
|
||||
|
||||
|
||||
<TextBlock
|
||||
Text="ListView item click disabled"
|
||||
Grid.Row="6"
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Center"/>
|
||||
<Border
|
||||
Grid.Row="7"
|
||||
Grid.Column="1"
|
||||
Width="150"
|
||||
Height="150"
|
||||
Background="#86007D">
|
||||
<ListView
|
||||
ItemsSource="0123456789abcdef"
|
||||
IsItemClickEnabled="False"
|
||||
SelectionMode="None"
|
||||
x:Name="ListViewWithoutItemClick">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border
|
||||
Height="50"
|
||||
Width="125"
|
||||
Background="#11000000"
|
||||
BorderBrush="#33000000"
|
||||
BorderThickness="3"
|
||||
Margin="3"
|
||||
RightTapped="ItemRightTapped">
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center"
|
||||
Text="{Binding}" />
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Page>
|
|
@ -0,0 +1,35 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Input;
|
||||
using Uno.UI;
|
||||
using Uno.UI.Samples.Controls;
|
||||
|
||||
namespace UITests.Shared.Windows_UI_Input.GestureRecognizerTests
|
||||
{
|
||||
[SampleControlInfo("Gesture recognizer")]
|
||||
public sealed partial class RightTappedTests : Page
|
||||
{
|
||||
public RightTappedTests()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
|
||||
private void TargetRightTapped(object sender, RightTappedRoutedEventArgs e)
|
||||
{
|
||||
var target = (FrameworkElement)sender;
|
||||
var position = e.GetPosition(target).LogicalToPhysicalPixels();
|
||||
|
||||
LastRightTapped.Text = $"{target.Name}@{position.X:F2},{position.Y:F2}";
|
||||
}
|
||||
|
||||
private void ItemRightTapped(object sender, RightTappedRoutedEventArgs e)
|
||||
{
|
||||
var target = (FrameworkElement)sender;
|
||||
var position = e.GetPosition(target).LogicalToPhysicalPixels();
|
||||
|
||||
LastRightTapped.Text = $"Item_{target.DataContext}@{position.X:F2},{position.Y:F2}";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,99 +8,200 @@
|
|||
mc:Ignorable="d"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
|
||||
<StackPanel>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock
|
||||
Text="Double on each zones. Last double tapped:"
|
||||
TextWrapping="Wrap"
|
||||
Grid.Row="0"
|
||||
Grid.ColumnSpan="2"/>
|
||||
<TextBlock
|
||||
x:Name="LastTapped"
|
||||
Text="__none__"
|
||||
Grid.Row="1"
|
||||
Grid.ColumnSpan="2"/>
|
||||
|
||||
<TextBlock
|
||||
Text="Basic"
|
||||
Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
HorizontalAlignment="Center"/>
|
||||
<Border
|
||||
x:Name="WhenTappedThenArgsLocationIsValid_Root"
|
||||
Width="250"
|
||||
BorderThickness="3"
|
||||
BorderBrush="#FF0018"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top">
|
||||
<StackPanel>
|
||||
<TextBlock Text="Test event args location" />
|
||||
<Border
|
||||
x:Name="WhenTappedThenArgsLocationIsValid_Target"
|
||||
Width="100"
|
||||
Height="100"
|
||||
Tapped="WhenTappedThenArgsLocationIsValid_OnTargetTapped"
|
||||
Background="#FF0018">
|
||||
<TextBlock Text="Touch target" />
|
||||
</Border>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="Relative to test root (physical px): " />
|
||||
<TextBlock Name="WhenTappedThenArgsLocationIsValid_Result_RelativeToRoot" />
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="Relative to target (physical px): " />
|
||||
<TextBlock Name="WhenTappedThenArgsLocationIsValid_Result_RelativeToTarget" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
x:Name="Basic_Target"
|
||||
Grid.Row="3"
|
||||
Grid.Column="0"
|
||||
Width="150"
|
||||
Height="150"
|
||||
Background="#FF0018"
|
||||
Tapped="TargetTapped" />
|
||||
|
||||
<TextBlock
|
||||
Text="With transformation"
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Center"/>
|
||||
<Border
|
||||
Grid.Row="3"
|
||||
Grid.Column="1"
|
||||
Width="150"
|
||||
Height="150"
|
||||
Background="#FFA52C"
|
||||
x:Name="Transformed_Parent">
|
||||
<Border
|
||||
x:Name="Transformed_Target"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
Width="50"
|
||||
Height="50"
|
||||
Background="#33000000"
|
||||
Tapped="TargetTapped">
|
||||
<Border.RenderTransform>
|
||||
<CompositeTransform Rotation="45" TranslateX="100" TranslateY="100" />
|
||||
</Border.RenderTransform>
|
||||
</Border>
|
||||
</Border>
|
||||
|
||||
<TextBlock
|
||||
Text="In ScrollViewer"
|
||||
Grid.Row="4"
|
||||
Grid.Column="0"
|
||||
HorizontalAlignment="Center"/>
|
||||
<Border
|
||||
Width="250"
|
||||
BorderThickness="3"
|
||||
BorderBrush="#FFA52C"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top">
|
||||
<StackPanel>
|
||||
<TextBlock Text="Test when child handles all pointer events" />
|
||||
<Border
|
||||
Width="100"
|
||||
Height="100"
|
||||
Background="#FFA52C"
|
||||
Tapped="WhenChildHandlesPointers_OnParentTapped">
|
||||
Grid.Row="5"
|
||||
Grid.Column="0"
|
||||
Width="150"
|
||||
Height="150"
|
||||
Background="#FFFF41">
|
||||
<ScrollViewer
|
||||
VerticalScrollMode="Enabled"
|
||||
VerticalScrollBarVisibility="Visible"
|
||||
HorizontalScrollMode="Enabled"
|
||||
HorizontalScrollBarVisibility="Visible"
|
||||
x:Name="InScroll_ScrollViewer">
|
||||
<Grid
|
||||
Width="300"
|
||||
Height="300">
|
||||
<Border
|
||||
x:Name="WhenChildHandlesPointers_Target"
|
||||
Width="70"
|
||||
Height="70"
|
||||
Background="#66FFFFFF"
|
||||
PointerEntered="HandlePointerEvent"
|
||||
PointerPressed="HandlePointerEvent"
|
||||
PointerMoved="HandlePointerEvent"
|
||||
PointerReleased="HandlePointerEvent"
|
||||
PointerExited="HandlePointerEvent"
|
||||
PointerCanceled="HandlePointerEvent"
|
||||
PointerCaptureLost="HandlePointerEvent"
|
||||
PointerWheelChanged="HandlePointerEvent">
|
||||
<TextBlock Text="Touch target"/>
|
||||
</Border>
|
||||
</Border>
|
||||
<TextBlock x:Name="WhenChildHandlesPointers_Result" />
|
||||
</StackPanel>
|
||||
x:Name="InScroll_Target"
|
||||
Width="100"
|
||||
Height="100"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Bottom"
|
||||
Background="#33000000"
|
||||
Tapped="TargetTapped" />
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
|
||||
<TextBlock
|
||||
Text="In button"
|
||||
Grid.Row="4"
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Center"/>
|
||||
<Button
|
||||
Grid.Row="5"
|
||||
Grid.Column="1"
|
||||
Width="150"
|
||||
Height="150"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Background="#008018">
|
||||
<Border
|
||||
x:Name="InButton_Target"
|
||||
Width="100"
|
||||
Height="100"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Background="#33000000"
|
||||
Tapped="TargetTapped" />
|
||||
</Button>
|
||||
|
||||
<TextBlock
|
||||
Text="ListView"
|
||||
Grid.Row="6"
|
||||
Grid.Column="0"
|
||||
HorizontalAlignment="Center"/>
|
||||
<Border
|
||||
Width="250"
|
||||
BorderThickness="3"
|
||||
BorderBrush="#FFFF41"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top">
|
||||
<StackPanel>
|
||||
<TextBlock Text="Test duplicated tapped event" />
|
||||
<Border
|
||||
Width="100"
|
||||
Height="100"
|
||||
Background="#FFFF41"
|
||||
Tapped="WhenMultipleTappedRecognizer_OnParentTapped">
|
||||
<Border
|
||||
x:Name="WhenMultipleTappedRecognizer_Target"
|
||||
Width="70"
|
||||
Height="70"
|
||||
Background="#66FFFFFF"
|
||||
Tapped="WhenMultipleTappedRecognizer_OnTargetTapped">
|
||||
<TextBlock Text="Touch target"/>
|
||||
</Border>
|
||||
</Border>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="Tapped on parent: " />
|
||||
<TextBlock x:Name="WhenMultipleTappedRecognizer_Result_Parent" Text="0" />
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="Tapped on target: " />
|
||||
<TextBlock x:Name="WhenMultipleTappedRecognizer_Result_Target" Text="0" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
Grid.Row="7"
|
||||
Grid.Column="0"
|
||||
Width="150"
|
||||
Height="150"
|
||||
Background="#0000F9">
|
||||
<ListView
|
||||
ItemsSource="0123456789abcdef"
|
||||
IsItemClickEnabled="True"
|
||||
SelectionMode="Single"
|
||||
x:Name="ListViewWithItemClick">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border
|
||||
Height="50"
|
||||
Width="125"
|
||||
Background="#11000000"
|
||||
BorderBrush="#33000000"
|
||||
BorderThickness="3"
|
||||
Margin="3"
|
||||
Tapped="ItemTapped">
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center"
|
||||
Text="{Binding}" />
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
<TextBlock
|
||||
Text="ListView item click disabled"
|
||||
Grid.Row="6"
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Center"/>
|
||||
<Border
|
||||
Grid.Row="7"
|
||||
Grid.Column="1"
|
||||
Width="150"
|
||||
Height="150"
|
||||
Background="#86007D">
|
||||
<ListView
|
||||
ItemsSource="0123456789abcdef"
|
||||
IsItemClickEnabled="False"
|
||||
SelectionMode="None"
|
||||
x:Name="ListViewWithoutItemClick">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border
|
||||
Height="50"
|
||||
Width="125"
|
||||
Background="#11000000"
|
||||
BorderBrush="#33000000"
|
||||
BorderThickness="3"
|
||||
Margin="3"
|
||||
Tapped="ItemTapped">
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center"
|
||||
Text="{Binding}" />
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Page>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Input;
|
||||
using Uno.UI;
|
||||
|
@ -15,29 +16,20 @@ namespace UITests.Shared.Windows_UI_Input.GestureRecognizerTests
|
|||
this.InitializeComponent();
|
||||
}
|
||||
|
||||
private void WhenTappedThenArgsLocationIsValid_OnTargetTapped(object sender, TappedRoutedEventArgs e)
|
||||
private void TargetTapped(object sender, TappedRoutedEventArgs e)
|
||||
{
|
||||
var relativeToRoot = e.GetPosition(WhenTappedThenArgsLocationIsValid_Root).LogicalToPhysicalPixels();
|
||||
var relativeToTarget = e.GetPosition(WhenTappedThenArgsLocationIsValid_Target).LogicalToPhysicalPixels();
|
||||
var target = (FrameworkElement)sender;
|
||||
var position = e.GetPosition(target).LogicalToPhysicalPixels();
|
||||
|
||||
WhenTappedThenArgsLocationIsValid_Result_RelativeToRoot.Text = $"({(int)relativeToRoot.X:D},{(int)relativeToRoot.Y:D})";
|
||||
WhenTappedThenArgsLocationIsValid_Result_RelativeToTarget.Text = $"({(int)relativeToTarget.X:D},{(int)relativeToTarget.Y:D})";
|
||||
LastTapped.Text = $"{target.Name}@{position.X:F2},{position.Y:F2}";
|
||||
}
|
||||
|
||||
private void HandlePointerEvent(object sender, PointerRoutedEventArgs e)
|
||||
=> e.Handled = true;
|
||||
private void ItemTapped(object sender, TappedRoutedEventArgs e)
|
||||
{
|
||||
var target = (FrameworkElement)sender;
|
||||
var position = e.GetPosition(target).LogicalToPhysicalPixels();
|
||||
|
||||
private void WhenChildHandlesPointers_OnParentTapped(object sender, TappedRoutedEventArgs e)
|
||||
=> WhenChildHandlesPointers_Result.Text = "Tapped";
|
||||
|
||||
private void WhenMultipleTappedRecognizer_OnParentTapped(object sender, TappedRoutedEventArgs e)
|
||||
=> WhenMultipleTappedRecognizer_Result_Parent.Text = int.TryParse(WhenMultipleTappedRecognizer_Result_Parent.Text, out var count)
|
||||
? (count + 1).ToString()
|
||||
: "1";
|
||||
|
||||
private void WhenMultipleTappedRecognizer_OnTargetTapped(object sender, TappedRoutedEventArgs e)
|
||||
=> WhenMultipleTappedRecognizer_Result_Target.Text = int.TryParse(WhenMultipleTappedRecognizer_Result_Target.Text, out var count)
|
||||
? (count + 1).ToString()
|
||||
: "1";
|
||||
LastTapped.Text = $"Item_{target.DataContext}@{position.X:F2},{position.Y:F2}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
</Grid.RowDefinitions>
|
||||
|
||||
<Border
|
||||
x:Name="_Parent"
|
||||
x:Name="TheParent"
|
||||
BorderThickness="10"
|
||||
BorderBrush="DeepSkyBlue"
|
||||
Background="Red"
|
||||
|
|
|
@ -20,7 +20,7 @@ namespace UITests.Shared.Windows_UI_Input.GestureRecognizerTests
|
|||
private void OnParentPointerMoved(object sender, PointerRoutedEventArgs e)
|
||||
{
|
||||
var parentRelToTarget = e.GetCurrentPoint(Target).Position;
|
||||
var parentRelToParent = e.GetCurrentPoint(_Parent).Position;
|
||||
var parentRelToParent = e.GetCurrentPoint(TheParent).Position;
|
||||
|
||||
ParentRelToTarget.Text = F(parentRelToTarget);
|
||||
ParentRelToParent.Text = F(parentRelToParent);
|
||||
|
@ -29,7 +29,7 @@ namespace UITests.Shared.Windows_UI_Input.GestureRecognizerTests
|
|||
private void OnTargetPointerMoved(object sender, PointerRoutedEventArgs e)
|
||||
{
|
||||
var targetRelToTarget = e.GetCurrentPoint(Target).Position;
|
||||
var targetRelToParent = e.GetCurrentPoint(_Parent).Position;
|
||||
var targetRelToParent = e.GetCurrentPoint(TheParent).Position;
|
||||
|
||||
TargetRelToTarget.Text = F(targetRelToTarget);
|
||||
TargetRelToParent.Text = F(targetRelToParent);
|
||||
|
|
|
@ -66,6 +66,11 @@ import android.view.ViewParent;
|
|||
/* internal */ final class UnoMotionHelper {
|
||||
private static final String LOGTAG = "UnoViewGroup";
|
||||
|
||||
// Stylus when barrel is pressed when touching the screen
|
||||
private static final int STYLUS_WITH_BARREL_DOWN = 211;
|
||||
private static final int STYLUS_WITH_BARREL_MOVE = 213;
|
||||
private static final int STYLUS_WITH_BARREL_UP = 212;
|
||||
|
||||
/**
|
||||
* The singleton instance of the helper
|
||||
*/
|
||||
|
@ -76,14 +81,14 @@ import android.view.ViewParent;
|
|||
private View _currentMotionOriginalSource;
|
||||
|
||||
// To trace the pointer events (dispatchTouchEvent and dispatchGenericMotionEvent),
|
||||
// uncomment this and then uncomment logs in the method itself.
|
||||
// uncomment this and then uncomment logs in the method itself (Replace all "// Log" by "Log").
|
||||
/*
|
||||
private static String _indent = "";
|
||||
public boolean dispatchMotionEvent(Uno.UI.MotionTargetAdapter adapter, MotionEvent event)
|
||||
{
|
||||
final ViewGroup view = adapter.asViewGroup();
|
||||
final String originalIndent = _indent;
|
||||
Log.i(LOGTAG, _indent + " + " + view.toString() + "(" + System.identityHashCode(this) + ") " +
|
||||
Log.i(LOGTAG, _indent + " + " + view.toString() + "(" + System.identityHashCode(view) + ") " +
|
||||
"[evt: " + String.format("%.2f", event.getX()) + "," + String.format("%.2f", event.getY()) + " | size: " + view.getWidth() + "x" + view.getHeight() + " | scroll: x="+ view.getScrollX() + " y=" + view.getScrollY() + "]");
|
||||
_indent += " | ";
|
||||
Log.i(LOGTAG, _indent + event.toString());
|
||||
|
@ -137,7 +142,7 @@ import android.view.ViewParent;
|
|||
_currentMotionOriginalSource = null;
|
||||
|
||||
final boolean isDown = event.getAction() == MotionEvent.ACTION_DOWN;
|
||||
final boolean isBeginningOfSequence = isDown || event.getAction() == MotionEvent.ACTION_HOVER_ENTER;
|
||||
final boolean isBeginningOfSequence = isDown || event.getAction() == MotionEvent.ACTION_HOVER_ENTER || event.getAction() == STYLUS_WITH_BARREL_DOWN;
|
||||
if (isBeginningOfSequence) {
|
||||
// When we receive a pointer DOWN, we have to reset the down -> move -> up sequence,
|
||||
// so the dispatch will re-evaluate the _customDispatchTouchTarget;
|
||||
|
@ -164,7 +169,7 @@ import android.view.ViewParent;
|
|||
// (!dispatch.getIsTargetCachingSupported() || isDown), BUT if we do this, we may miss some HOVER_EXIT
|
||||
// So we prefer to not follow the UWP behavior (PointerEnter/Exit are raised only when entering/leaving
|
||||
// non clipped content) and get all the events.
|
||||
// Log.i(LOGTAG, _indent + "BLOCKED (not in view due to clipping)");
|
||||
// Log.i(LOGTAG, _indent + "BLOCKED (not in view due to clipping, or invalid dispatch comming from custom)");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -325,6 +330,8 @@ import android.view.ViewParent;
|
|||
|
||||
final boolean handled = dispatchStaticTransformedMotionEvent(adapter, currentTarget, true, event);
|
||||
if (handled || adapter.getIsStrongSequence()) {
|
||||
// Log.i(LOGTAG, _indent + "Custom dispatched to current target " + currentTarget.toString() + " [handled: " + handled + " | isStrongSequence: " + adapter.getIsStrongSequence() + "]");
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -337,16 +344,23 @@ import android.view.ViewParent;
|
|||
|
||||
// Same check as native "canViewReceivePointerEvents"
|
||||
if (child == currentTarget || child.getVisibility() != View.VISIBLE || child.getAnimation() != null) {
|
||||
// Log.i(LOGTAG, _indent + "Custom dispatch ignored child #" + i + " (" + child.toString() + ") [isCurrent: " + (child == currentTarget) + " | visibility: " + child.getVisibility() + " | isAnimating: " + (child.getAnimation() != null) + "]");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (dispatchStaticTransformedMotionEvent(adapter, child, false, event)) {
|
||||
// Log.i(LOGTAG, _indent + "Custom dispatch is setting child #" + i + " (" + child.toString() + ") as current target.");
|
||||
|
||||
adapter.setCurrentTarget(child); // (try to) cache the child for future events
|
||||
return true; // Stop at the first child which is able to handle the event
|
||||
} else {
|
||||
// Log.i(LOGTAG, _indent + "Custom dispatch tried child #" + i + " (" + child.toString() + ") but dispatch return false.");
|
||||
}
|
||||
}
|
||||
|
||||
// No target found ...
|
||||
// Log.i(LOGTAG, _indent + "Custom dispatch was not able to find a target child, current is being cleared.");
|
||||
adapter.setCurrentTarget(null);
|
||||
return false;
|
||||
}
|
||||
|
@ -400,7 +414,7 @@ import android.view.ViewParent;
|
|||
// with an implicit capture (i.e. down->move->up), OR the pointer is over the target.
|
||||
// In both cases, we have to dispatch the motion event to it, and propagates its handling result.
|
||||
|
||||
// Log.i(LOGTAG, _indent + "Dispatching to child " + child.toString() + " [evt: " + String.format("%.2f", transformedEvent.getX()) + "," + String.format("%.2f", transformedEvent.getY()) + " | isSequenceContinuation: " + isSequenceContinuation + " | inView: " + isMotionInView(transformedEvent, child) + "]");
|
||||
// Log.i(LOGTAG, _indent + "Dispatching to child " + child.toString() + " [evt: " + String.format("%.2f", transformedEvent.getX()) + "," + String.format("%.2f", transformedEvent.getY()) + " | isSequenceContinuation: " + isSequenceContinuation + " | isStrongSequence: " + adapter.getIsStrongSequence() + " | inView: " + isMotionInView(transformedEvent, child) + "]");
|
||||
|
||||
return adapter.dispatchToChild(child, transformedEvent);
|
||||
} else if (isSequenceContinuation) {
|
||||
|
@ -454,6 +468,9 @@ import android.view.ViewParent;
|
|||
case MotionEvent.ACTION_HOVER_ENTER:
|
||||
case MotionEvent.ACTION_HOVER_MOVE:
|
||||
case MotionEvent.ACTION_HOVER_EXIT:
|
||||
case STYLUS_WITH_BARREL_DOWN:
|
||||
case STYLUS_WITH_BARREL_MOVE:
|
||||
case STYLUS_WITH_BARREL_UP:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
|
|
|
@ -11,6 +11,7 @@ using Windows.UI.Input;
|
|||
using FluentAssertions;
|
||||
using FluentAssertions.Execution;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Uno.Disposables;
|
||||
using Point = Windows.Foundation.Point;
|
||||
using static Uno.UI.Tests.Windows_UI_Input.GestureRecognizerTestExtensions;
|
||||
|
||||
|
@ -188,6 +189,108 @@ namespace Uno.UI.Tests.Windows_UI_Input
|
|||
taps.Should().BeEquivalentTo(Tap(25, 25));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void RightTapped()
|
||||
{
|
||||
var sut = new GestureRecognizer { GestureSettings = GestureSettings.RightTap };
|
||||
var taps = new List<RightTappedEventArgs>();
|
||||
sut.RightTapped += (snd, e) => taps.Add(e);
|
||||
|
||||
using (MouseRight())
|
||||
{
|
||||
sut.ProcessDownEvent(25, 25);
|
||||
taps.Should().BeEmpty();
|
||||
|
||||
sut.CanBeDoubleTap(GetPoint(28, 28)).Should().BeFalse();
|
||||
sut.ProcessUpEvent(27, 27);
|
||||
taps.Should().BeEquivalentTo(RightTap(25, 25));
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void RightTapped_Duration()
|
||||
{
|
||||
var sut = new GestureRecognizer { GestureSettings = GestureSettings.RightTap };
|
||||
var taps = new List<RightTappedEventArgs>();
|
||||
sut.RightTapped += (snd, e) => taps.Add(e);
|
||||
|
||||
using (MouseRight())
|
||||
{
|
||||
sut.ProcessDownEvent(25, 25);
|
||||
taps.Should().BeEmpty();
|
||||
|
||||
sut.CanBeDoubleTap(GetPoint(28, 28)).Should().BeFalse();
|
||||
sut.ProcessUpEvent(27, 27, ts: long.MaxValue); // No matter the duration for mouse
|
||||
taps.Should().BeEquivalentTo(RightTap(25, 25));
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void RightTapped_Delta_X()
|
||||
{
|
||||
var sut = new GestureRecognizer { GestureSettings = GestureSettings.RightTap };
|
||||
var taps = new List<RightTappedEventArgs>();
|
||||
sut.RightTapped += (snd, e) => taps.Add(e);
|
||||
|
||||
using (MouseRight())
|
||||
{
|
||||
sut.ProcessDownEvent(25, 25);
|
||||
sut.ProcessUpEvent(25 + GestureRecognizer.TapMaxXDelta + 1, 25); // Moved too far
|
||||
|
||||
taps.Should().BeEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void RightTapped_Delta_X_Back_Over()
|
||||
{
|
||||
var sut = new GestureRecognizer { GestureSettings = GestureSettings.RightTap };
|
||||
var taps = new List<RightTappedEventArgs>();
|
||||
sut.RightTapped += (snd, e) => taps.Add(e);
|
||||
|
||||
using (MouseRight())
|
||||
{
|
||||
sut.ProcessDownEvent(25, 25);
|
||||
sut.ProcessMoveEvent(25 + GestureRecognizer.TapMaxXDelta + 1, 25); // Moved too far
|
||||
sut.ProcessUpEvent(25, 25); // Release over
|
||||
|
||||
taps.Should().BeEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void RightTapped_Delta_Y()
|
||||
{
|
||||
var sut = new GestureRecognizer { GestureSettings = GestureSettings.RightTap };
|
||||
var taps = new List<RightTappedEventArgs>();
|
||||
sut.RightTapped += (snd, e) => taps.Add(e);
|
||||
|
||||
using (MouseRight())
|
||||
{
|
||||
sut.ProcessDownEvent(25, 25);
|
||||
sut.ProcessUpEvent(25, 25 + GestureRecognizer.TapMaxXDelta + 1); // Moved too far
|
||||
|
||||
taps.Should().BeEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void RightTapped_Delta_Y_Back_Over()
|
||||
{
|
||||
var sut = new GestureRecognizer { GestureSettings = GestureSettings.RightTap };
|
||||
var taps = new List<RightTappedEventArgs>();
|
||||
sut.RightTapped += (snd, e) => taps.Add(e);
|
||||
|
||||
using (MouseRight())
|
||||
{
|
||||
sut.ProcessDownEvent(25, 25);
|
||||
sut.ProcessMoveEvent(25, 25 + GestureRecognizer.TapMaxXDelta + 1); // Moved too far
|
||||
sut.ProcessUpEvent(25, 25); // Release over
|
||||
|
||||
taps.Should().BeEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Manipulation_Begin()
|
||||
{
|
||||
|
@ -1005,6 +1108,57 @@ namespace Uno.UI.Tests.Windows_UI_Input
|
|||
{
|
||||
private static long _frameId = 0;
|
||||
|
||||
public static PointerDevice MousePointer { get; } = new PointerDevice(PointerDeviceType.Mouse);
|
||||
public static PointerDevice PenPointer { get; } = new PointerDevice(PointerDeviceType.Pen);
|
||||
public static PointerDevice TouchPointer { get; } = new PointerDevice(PointerDeviceType.Touch);
|
||||
|
||||
public static PointerPointProperties LeftButton { get; } = new PointerPointProperties
|
||||
{
|
||||
IsPrimary = true,
|
||||
IsInRange = true,
|
||||
IsLeftButtonPressed = true
|
||||
};
|
||||
|
||||
public static PointerPointProperties RightButton { get; } = new PointerPointProperties
|
||||
{
|
||||
IsPrimary = true,
|
||||
IsInRange = true,
|
||||
IsRightButtonPressed = true
|
||||
};
|
||||
|
||||
public static PointerPointProperties LeftBarrelButton { get; } = new PointerPointProperties
|
||||
{
|
||||
IsPrimary = true,
|
||||
IsInRange = true,
|
||||
IsLeftButtonPressed = true,
|
||||
IsBarrelButtonPressed = true
|
||||
};
|
||||
|
||||
public static PointerPointProperties RightBarrelButton { get; } = new PointerPointProperties
|
||||
{
|
||||
IsPrimary = true,
|
||||
IsInRange = true,
|
||||
IsRightButtonPressed = true,
|
||||
IsBarrelButtonPressed = true
|
||||
};
|
||||
|
||||
private static readonly AsyncLocal<(PointerDevice device, PointerPointProperties properties)> _currentPointer = new AsyncLocal<(PointerDevice device, PointerPointProperties properties)>();
|
||||
public static IDisposable Pointer(PointerDevice device, PointerPointProperties properties)
|
||||
{
|
||||
_currentPointer.Value = (device, properties);
|
||||
|
||||
return Disposable.Create(() =>
|
||||
{
|
||||
_currentPointer.Value = default;
|
||||
});
|
||||
}
|
||||
|
||||
public static IDisposable Mouse() => Pointer(MousePointer, LeftButton);
|
||||
public static IDisposable MouseRight() => Pointer(MousePointer, RightButton);
|
||||
public static IDisposable Pen() => Pointer(PenPointer, LeftButton);
|
||||
public static IDisposable PenWithBarrel() => Pointer(PenPointer, RightBarrelButton);
|
||||
public static IDisposable Touch() => Pointer(TouchPointer, LeftButton);
|
||||
|
||||
public static PointerPoint GetPoint(
|
||||
double x,
|
||||
double y,
|
||||
|
@ -1014,23 +1168,24 @@ namespace Uno.UI.Tests.Windows_UI_Input
|
|||
bool? isInContact = true,
|
||||
PointerPointProperties properties = null)
|
||||
{
|
||||
var currentPointer = _currentPointer.Value;
|
||||
var frameId = (uint)Interlocked.Increment(ref _frameId);
|
||||
id = id ?? 1;
|
||||
ts = ts ?? frameId;
|
||||
var pointer = new PointerDevice(device ?? PointerDeviceType.Touch);
|
||||
var pointer = device.HasValue
|
||||
? new PointerDevice(device.Value)
|
||||
: (currentPointer.device ?? new PointerDevice(PointerDeviceType.Touch));
|
||||
var location = new Windows.Foundation.Point(x, y);
|
||||
properties = properties ?? new PointerPointProperties
|
||||
{
|
||||
IsPrimary = true,
|
||||
IsInRange = true,
|
||||
IsLeftButtonPressed = true,
|
||||
};
|
||||
properties = properties ?? currentPointer.properties ?? LeftButton;
|
||||
|
||||
return new PointerPoint(frameId, ts.Value, pointer, id.Value, location, location, isInContact.GetValueOrDefault(), properties);
|
||||
}
|
||||
|
||||
public static TappedEventArgs Tap(double x, double y, uint tapCount = 1, PointerDeviceType? device = null)
|
||||
=> new TappedEventArgs(device ?? PointerDeviceType.Touch, new Point(x, y), tapCount);
|
||||
=> new TappedEventArgs(device ?? _currentPointer.Value.device?.PointerDeviceType ?? PointerDeviceType.Touch, new Point(x, y), tapCount);
|
||||
|
||||
public static RightTappedEventArgs RightTap(double x, double y, PointerDeviceType? device = null)
|
||||
=> new RightTappedEventArgs(device ?? _currentPointer.Value.device?.PointerDeviceType ?? PointerDeviceType.Touch, new Point(x, y));
|
||||
|
||||
public static void ProcessDownEvent(
|
||||
this GestureRecognizer sut,
|
||||
|
|
|
@ -990,7 +990,7 @@ var Uno;
|
|||
}
|
||||
src = src.parentElement;
|
||||
}
|
||||
return `${evt.pointerId};${evt.clientX};${evt.clientY};${(evt.ctrlKey ? "1" : "0")};${(evt.shiftKey ? "1" : "0")};${evt.buttons};${evt.button};${evt.pointerType};${srcHandle};${evt.timeStamp}`;
|
||||
return `${evt.pointerId};${evt.clientX};${evt.clientY};${(evt.ctrlKey ? "1" : "0")};${(evt.shiftKey ? "1" : "0")};${evt.buttons};${evt.button};${evt.pointerType};${srcHandle};${evt.timeStamp};${evt.pressure}`;
|
||||
}
|
||||
/**
|
||||
* keyboard event extractor to be used with registerEventOnView
|
||||
|
|
|
@ -891,7 +891,7 @@
|
|||
src = src.parentElement;
|
||||
}
|
||||
|
||||
return `${evt.pointerId};${evt.clientX};${evt.clientY};${(evt.ctrlKey ? "1" : "0")};${(evt.shiftKey ? "1" : "0")};${evt.buttons};${evt.button};${evt.pointerType};${srcHandle};${evt.timeStamp}`;
|
||||
return `${evt.pointerId};${evt.clientX};${evt.clientY};${(evt.ctrlKey ? "1" : "0")};${(evt.shiftKey ? "1" : "0")};${evt.buttons};${evt.button};${evt.pointerType};${srcHandle};${evt.timeStamp};${evt.pressure}`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -567,7 +567,7 @@ namespace Windows.UI.Xaml.Controls
|
|||
global::Windows.Foundation.Metadata.ApiInformation.TryRaiseNotImplemented("Windows.UI.Xaml.Controls.Control", "void Control.OnHolding(HoldingRoutedEventArgs e)");
|
||||
}
|
||||
#endif
|
||||
#if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __MACOS__
|
||||
#if false
|
||||
[global::Uno.NotImplemented]
|
||||
protected virtual void OnRightTapped( global::Windows.UI.Xaml.Input.RightTappedRoutedEventArgs e)
|
||||
{
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
#pragma warning disable 114 // new keyword hiding
|
||||
namespace Windows.UI.Xaml.Input
|
||||
{
|
||||
#if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __MACOS__
|
||||
#if false
|
||||
public delegate void RightTappedEventHandler(object @sender, global::Windows.UI.Xaml.Input.RightTappedRoutedEventArgs @e);
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -2,12 +2,12 @@
|
|||
#pragma warning disable 114 // new keyword hiding
|
||||
namespace Windows.UI.Xaml.Input
|
||||
{
|
||||
#if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __MACOS__
|
||||
#if false
|
||||
[global::Uno.NotImplemented]
|
||||
#endif
|
||||
public partial class RightTappedRoutedEventArgs : global::Windows.UI.Xaml.RoutedEventArgs
|
||||
{
|
||||
#if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __MACOS__
|
||||
#if false
|
||||
[global::Uno.NotImplemented]
|
||||
public bool Handled
|
||||
{
|
||||
|
@ -21,7 +21,7 @@ namespace Windows.UI.Xaml.Input
|
|||
}
|
||||
}
|
||||
#endif
|
||||
#if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __MACOS__
|
||||
#if false
|
||||
[global::Uno.NotImplemented]
|
||||
public global::Windows.Devices.Input.PointerDeviceType PointerDeviceType
|
||||
{
|
||||
|
@ -31,7 +31,7 @@ namespace Windows.UI.Xaml.Input
|
|||
}
|
||||
}
|
||||
#endif
|
||||
#if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __MACOS__
|
||||
#if false
|
||||
[global::Uno.NotImplemented]
|
||||
public RightTappedRoutedEventArgs() : base()
|
||||
{
|
||||
|
@ -42,7 +42,7 @@ namespace Windows.UI.Xaml.Input
|
|||
// Forced skipping of method Windows.UI.Xaml.Input.RightTappedRoutedEventArgs.PointerDeviceType.get
|
||||
// Forced skipping of method Windows.UI.Xaml.Input.RightTappedRoutedEventArgs.Handled.get
|
||||
// Forced skipping of method Windows.UI.Xaml.Input.RightTappedRoutedEventArgs.Handled.set
|
||||
#if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __MACOS__
|
||||
#if false
|
||||
[global::Uno.NotImplemented]
|
||||
public global::Windows.Foundation.Point GetPosition( global::Windows.UI.Xaml.UIElement relativeTo)
|
||||
{
|
||||
|
|
|
@ -715,7 +715,7 @@ namespace Windows.UI.Xaml
|
|||
typeof(global::Windows.UI.Xaml.UIElement),
|
||||
new FrameworkPropertyMetadata(default(global::Windows.UI.Xaml.Media.Projection)));
|
||||
#endif
|
||||
#if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __MACOS__
|
||||
#if false
|
||||
[global::Uno.NotImplemented]
|
||||
public static global::Windows.UI.Xaml.RoutedEvent RightTappedEvent
|
||||
{
|
||||
|
@ -1627,7 +1627,7 @@ namespace Windows.UI.Xaml
|
|||
}
|
||||
}
|
||||
#endif
|
||||
#if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __MACOS__
|
||||
#if false
|
||||
[global::Uno.NotImplemented]
|
||||
public event global::Windows.UI.Xaml.Input.RightTappedEventHandler RightTapped
|
||||
{
|
||||
|
|
|
@ -232,6 +232,11 @@ namespace Windows.UI.Xaml.Controls
|
|||
DoubleTapped += OnDoubleTappedHandler;
|
||||
}
|
||||
|
||||
if (implementedEvents.HasFlag(RoutedEventFlag.RightTapped))
|
||||
{
|
||||
RightTapped += OnRightTappedHandler;
|
||||
}
|
||||
|
||||
if (implementedEvents.HasFlag(RoutedEventFlag.KeyDown))
|
||||
{
|
||||
KeyDown += OnKeyDownHandler;
|
||||
|
@ -755,6 +760,7 @@ namespace Windows.UI.Xaml.Controls
|
|||
protected virtual void OnManipulationCompleted(ManipulationCompletedRoutedEventArgs e) { }
|
||||
protected virtual void OnTapped(TappedRoutedEventArgs e) { }
|
||||
protected virtual void OnDoubleTapped(DoubleTappedRoutedEventArgs e) { }
|
||||
protected virtual void OnRightTapped(RightTappedRoutedEventArgs e) { }
|
||||
protected virtual void OnKeyDown(KeyRoutedEventArgs args) { }
|
||||
protected virtual void OnKeyUp(KeyRoutedEventArgs args) { }
|
||||
protected virtual void OnGotFocus(RoutedEventArgs e) { }
|
||||
|
@ -802,6 +808,9 @@ namespace Windows.UI.Xaml.Controls
|
|||
private static readonly DoubleTappedEventHandler OnDoubleTappedHandler =
|
||||
(object sender, DoubleTappedRoutedEventArgs args) => ((Control)sender).OnDoubleTapped(args);
|
||||
|
||||
private static readonly RightTappedEventHandler OnRightTappedHandler =
|
||||
(object sender, RightTappedRoutedEventArgs args) => ((Control)sender).OnRightTapped(args);
|
||||
|
||||
private static readonly KeyEventHandler OnKeyDownHandler =
|
||||
(object sender, KeyRoutedEventArgs args) => ((Control)sender).OnKeyDown(args);
|
||||
|
||||
|
@ -820,6 +829,7 @@ namespace Windows.UI.Xaml.Controls
|
|||
private static readonly Type[] _pointerArgsType = new[] { typeof(PointerRoutedEventArgs) };
|
||||
private static readonly Type[] _tappedArgsType = new[] { typeof(TappedRoutedEventArgs) };
|
||||
private static readonly Type[] _doubleTappedArgsType = new[] { typeof(DoubleTappedRoutedEventArgs) };
|
||||
private static readonly Type[] _rightTappedArgsType = new[] { typeof(RightTappedRoutedEventArgs) };
|
||||
private static readonly Type[] _keyArgsType = new[] { typeof(KeyRoutedEventArgs) };
|
||||
private static readonly Type[] _routedArgsType = new[] { typeof(RoutedEventArgs) };
|
||||
private static readonly Type[] _manipStartingArgsType = new[] { typeof(ManipulationStartingRoutedEventArgs) };
|
||||
|
@ -916,6 +926,11 @@ namespace Windows.UI.Xaml.Controls
|
|||
result |= RoutedEventFlag.DoubleTapped;
|
||||
}
|
||||
|
||||
if (GetIsEventOverrideImplemented(type, nameof(OnRightTapped), _rightTappedArgsType))
|
||||
{
|
||||
result |= RoutedEventFlag.RightTapped;
|
||||
}
|
||||
|
||||
if (GetIsEventOverrideImplemented(type, nameof(OnKeyDown), _keyArgsType))
|
||||
{
|
||||
result |= RoutedEventFlag.KeyDown;
|
||||
|
|
|
@ -20,7 +20,8 @@ namespace Windows.UI.Xaml.Controls
|
|||
private ContentPresenter _headerContentPresenter;
|
||||
private Thumb _horizontalThumb, _verticalThumb;
|
||||
private FrameworkElement _horizontalTemplate, _verticalTemplate;
|
||||
private bool _duringDrag;
|
||||
private bool _isDragging; // between DragStart and DragCompleted
|
||||
private bool _isInDragDelta; // is reacting to a DragDelta
|
||||
private Rectangle _horizontalDecreaseRect;
|
||||
private Rectangle _horizontalTrackRect;
|
||||
private Rectangle _verticalDecreaseRect;
|
||||
|
@ -154,7 +155,7 @@ namespace Windows.UI.Xaml.Controls
|
|||
{
|
||||
ApplyValueToSlide();
|
||||
|
||||
IsPointerPressed = false;
|
||||
_isDragging = false;
|
||||
UpdateCommonState();
|
||||
}
|
||||
|
||||
|
@ -162,7 +163,7 @@ namespace Windows.UI.Xaml.Controls
|
|||
{
|
||||
try
|
||||
{
|
||||
_duringDrag = true;
|
||||
_isInDragDelta = true;
|
||||
|
||||
if (Orientation == Orientation.Horizontal)
|
||||
{
|
||||
|
@ -183,7 +184,7 @@ namespace Windows.UI.Xaml.Controls
|
|||
}
|
||||
finally
|
||||
{
|
||||
_duringDrag = false;
|
||||
_isInDragDelta = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -191,10 +192,6 @@ namespace Windows.UI.Xaml.Controls
|
|||
{
|
||||
if (HasXamlTemplate)
|
||||
{
|
||||
// Disable parent scrolling on Android
|
||||
#if XAMARIN_ANDROID
|
||||
this.RequestDisallowInterceptTouchEvent(true);
|
||||
#endif
|
||||
if (Orientation == Orientation.Horizontal)
|
||||
{
|
||||
_horizontalInitial = GetSanitizedDimension(_horizontalDecreaseRect.Width);
|
||||
|
@ -204,7 +201,7 @@ namespace Windows.UI.Xaml.Controls
|
|||
_verticalInitial = GetSanitizedDimension(_verticalDecreaseRect.Height);
|
||||
}
|
||||
|
||||
IsPointerPressed = true;
|
||||
_isDragging = true;
|
||||
UpdateCommonState();
|
||||
}
|
||||
}
|
||||
|
@ -215,7 +212,7 @@ namespace Windows.UI.Xaml.Controls
|
|||
{
|
||||
VisualStateManager.GoToState(this, "Disabled", useTransitions);
|
||||
}
|
||||
else if (IsPointerPressed)
|
||||
else if (_isDragging)
|
||||
{
|
||||
VisualStateManager.GoToState(this, "Pressed", useTransitions);
|
||||
}
|
||||
|
@ -277,19 +274,19 @@ namespace Windows.UI.Xaml.Controls
|
|||
// The _decreaseRect's height/width is updated, which in turn pushes or pulls the Thumb to its correct position
|
||||
if (Orientation == Orientation.Horizontal)
|
||||
{
|
||||
if (_horizontalThumb != null && _horizontalDecreaseRect != null)
|
||||
{
|
||||
var maxWidth = ActualWidth - _horizontalThumb.ActualWidth;
|
||||
_horizontalDecreaseRect.Width = (float)((Value - Minimum) / (Maximum - Minimum)) * maxWidth;
|
||||
}
|
||||
if (_horizontalThumb != null && _horizontalDecreaseRect != null)
|
||||
{
|
||||
var maxWidth = ActualWidth - _horizontalThumb.ActualWidth;
|
||||
_horizontalDecreaseRect.Width = (float)((Value - Minimum) / (Maximum - Minimum)) * maxWidth;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_verticalThumb != null && _verticalDecreaseRect != null)
|
||||
{
|
||||
var maxHeight = ActualHeight - _verticalThumb.ActualHeight;
|
||||
_verticalDecreaseRect.Height = (float)((Value - Minimum) / (Maximum - Minimum)) * maxHeight;
|
||||
}
|
||||
if (_verticalThumb != null && _verticalDecreaseRect != null)
|
||||
{
|
||||
var maxHeight = ActualHeight - _verticalThumb.ActualHeight;
|
||||
_verticalDecreaseRect.Height = (float)((Value - Minimum) / (Maximum - Minimum)) * maxHeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -311,7 +308,7 @@ namespace Windows.UI.Xaml.Controls
|
|||
{
|
||||
base.OnValueChanged(oldValue, newValue);
|
||||
|
||||
if (!_duringDrag && HasXamlTemplate)
|
||||
if (!_isInDragDelta && HasXamlTemplate)
|
||||
{
|
||||
ApplyValueToSlide();
|
||||
}
|
||||
|
@ -319,21 +316,24 @@ namespace Windows.UI.Xaml.Controls
|
|||
|
||||
private void SubscribeSliderContainerPressed()
|
||||
{
|
||||
if (_sliderContainer != null && IsTrackerEnabled)
|
||||
// This allows the user to start sliding by clicking anywhere on the slider
|
||||
// In that case, the Thumb won't be able to capture the pointer and instead we will replicate
|
||||
// its behavior and "push" to it the drag events (on which we are already subscribed).
|
||||
|
||||
var container = _sliderContainer;
|
||||
if (container != null && IsTrackerEnabled)
|
||||
{
|
||||
_sliderContainerSubscription.Disposable = null;
|
||||
|
||||
_sliderContainer.PointerPressed += OnSliderContainerPressed;
|
||||
_sliderContainer.PointerMoved += OnSliderContainerMoved;
|
||||
_sliderContainer.PointerReleased += OnSliderContainerReleased;
|
||||
_sliderContainer.PointerCanceled += OnSliderContainerCanceled;
|
||||
container.PointerPressed += OnSliderContainerPressed;
|
||||
container.PointerMoved += OnSliderContainerMoved;
|
||||
container.PointerCaptureLost += OnSliderContainerCaptureLost;
|
||||
|
||||
_sliderContainerSubscription.Disposable = Disposable.Create(() =>
|
||||
{
|
||||
_sliderContainer.PointerPressed -= OnSliderContainerPressed;
|
||||
_sliderContainer.PointerMoved -= OnSliderContainerMoved;
|
||||
_sliderContainer.PointerReleased -= OnSliderContainerReleased;
|
||||
_sliderContainer.PointerCanceled -= OnSliderContainerCanceled;
|
||||
container.PointerPressed -= OnSliderContainerPressed;
|
||||
container.PointerMoved -= OnSliderContainerMoved;
|
||||
container.PointerCaptureLost -= OnSliderContainerCaptureLost;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -341,60 +341,36 @@ namespace Windows.UI.Xaml.Controls
|
|||
private void OnSliderContainerPressed(object sender, PointerRoutedEventArgs e)
|
||||
{
|
||||
var container = sender as FrameworkElement;
|
||||
var point = e.GetCurrentPoint(container).Position;
|
||||
if (container.CapturePointer(e.Pointer))
|
||||
{
|
||||
var point = e.GetCurrentPoint(container).Position;
|
||||
var newOffset = Orientation == Orientation.Horizontal
|
||||
? point.X / container.ActualWidth
|
||||
: 1 - (point.Y / container.ActualHeight);
|
||||
|
||||
var newOffset = Orientation == Orientation.Horizontal ?
|
||||
point.X / container.ActualWidth :
|
||||
1 - (point.Y / container.ActualHeight);
|
||||
|
||||
ApplySlideToValue(newOffset);
|
||||
|
||||
Thumb?.StartDrag(point);
|
||||
|
||||
// This captures downstream events outside the slider's bounds.
|
||||
container.CapturePointer(e.Pointer);
|
||||
|
||||
// This is currently obligatory on Android to be able to receive downstream touch events (eg PointerMoved etc).
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void OnSliderContainerCanceled(object sender, PointerRoutedEventArgs e)
|
||||
{
|
||||
OnDragCompleted(sender, null);
|
||||
|
||||
var container = sender as FrameworkElement;
|
||||
container.ReleasePointerCapture(e.Pointer);
|
||||
}
|
||||
|
||||
private void OnSliderContainerReleased(object sender, PointerRoutedEventArgs e)
|
||||
{
|
||||
var container = sender as FrameworkElement;
|
||||
var point = e.GetCurrentPoint(container).Position;
|
||||
|
||||
ApplyValueToSlide();
|
||||
|
||||
Thumb?.CompleteDrag(point);
|
||||
|
||||
container.ReleasePointerCapture(e.Pointer);
|
||||
|
||||
e.Handled = true;
|
||||
ApplySlideToValue(newOffset);
|
||||
Thumb?.StartDrag(point);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSliderContainerMoved(object sender, PointerRoutedEventArgs e)
|
||||
{
|
||||
var container = sender as FrameworkElement;
|
||||
#if __WASM__
|
||||
if (!container.IsCaptured(e.Pointer))
|
||||
if (container.IsCaptured(e.Pointer))
|
||||
{
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
var point = e.GetCurrentPoint(container).Position;
|
||||
|
||||
Thumb?.DeltaDrag(point);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSliderContainerCaptureLost(object sender, PointerRoutedEventArgs e)
|
||||
{
|
||||
var container = sender as FrameworkElement;
|
||||
var point = e.GetCurrentPoint(container).Position;
|
||||
|
||||
Thumb?.DeltaDrag(point);
|
||||
|
||||
e.Handled = true;
|
||||
ApplyValueToSlide();
|
||||
Thumb?.CompleteDrag(point);
|
||||
}
|
||||
|
||||
#region IsTrackerEnabled DependencyProperty
|
||||
|
|
|
@ -15,12 +15,28 @@ namespace Windows.UI.Xaml.Input
|
|||
{
|
||||
partial class PointerRoutedEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// The stylus is pressed while holding the barrel button
|
||||
/// </summary>
|
||||
internal const MotionEventActions StylusWithBarrelDown = (MotionEventActions)211;
|
||||
/// <summary>
|
||||
/// The stylus is moved after having been pressed while holding the barrel button
|
||||
/// </summary>
|
||||
internal const MotionEventActions StylusWithBarrelMove = (MotionEventActions)213;
|
||||
/// <summary>
|
||||
/// The stylus is released after having been pressed while holding the barrel button
|
||||
/// </summary>
|
||||
internal const MotionEventActions StylusWithBarrelUp = (MotionEventActions)212;
|
||||
|
||||
private const int _pointerIdsCount = (int)MotionEventActions.PointerIndexMask >> (int)MotionEventActions.PointerIndexShift; // 0xff
|
||||
private const int _pointerIdsShift = 31 - (int)MotionEventActions.PointerIndexShift; // 23
|
||||
|
||||
private readonly MotionEvent _nativeEvent;
|
||||
private readonly int _pointerIndex;
|
||||
private readonly UIElement _receiver;
|
||||
private readonly PointerPointProperties _properties;
|
||||
|
||||
internal bool HasPressedButton => _properties.HasPressedButton;
|
||||
|
||||
internal PointerRoutedEventArgs(MotionEvent nativeEvent, int pointerIndex, UIElement originalSource, UIElement receiver) : this()
|
||||
{
|
||||
|
@ -32,30 +48,32 @@ namespace Windows.UI.Xaml.Input
|
|||
// and that usually the deviceId is [0, something_not_too_big_hopefully_less_than_0x00ffffff].
|
||||
// If deviceId is greater than 0x00ffffff, we might have a conflict but only in case of multi touch
|
||||
// and with a high variation of deviceId. We assume that's safe enough.
|
||||
|
||||
// Note: Make sure to use the GetPointerId in order to make sure to keep the same id while: down_1 / down_2 / up_1 / up_2
|
||||
// otherwise up_2 will be with the id of 1
|
||||
// otherwise up_2 will be with the id of 1
|
||||
var pointerId = ((uint)nativeEvent.GetPointerId(pointerIndex) & _pointerIdsCount) << _pointerIdsShift | (uint)nativeEvent.DeviceId;
|
||||
var type = nativeEvent.GetToolType(pointerIndex).ToPointerDeviceType();
|
||||
var isInContact = IsInContact(type, nativeEvent, pointerIndex);
|
||||
var nativePointerAction = nativeEvent.Action;
|
||||
var nativePointerButtons = nativeEvent.ButtonState;
|
||||
var nativePointerType = nativeEvent.GetToolType(_pointerIndex);
|
||||
var pointerType = nativePointerType.ToPointerDeviceType();
|
||||
var isInContact = IsInContact(nativeEvent, pointerType, nativePointerAction, nativePointerButtons);
|
||||
var keys = nativeEvent.MetaState.ToVirtualKeyModifiers();
|
||||
|
||||
FrameId = (uint)_nativeEvent.EventTime;
|
||||
Pointer = new Pointer(pointerId, type, isInContact, isInRange: true);
|
||||
Pointer = new Pointer(pointerId, pointerType, isInContact, isInRange: true);
|
||||
KeyModifiers = keys;
|
||||
OriginalSource = originalSource;
|
||||
CanBubbleNatively = true;
|
||||
|
||||
_properties = GetProperties(nativePointerType, nativePointerAction, nativePointerButtons); // Last: we need the Pointer property to be set!
|
||||
}
|
||||
|
||||
public PointerPoint GetCurrentPoint(UIElement relativeTo)
|
||||
{
|
||||
var timestamp = ToTimeStamp(_nativeEvent.EventTime);
|
||||
var device = PointerDevice.For(Pointer.PointerDeviceType);
|
||||
|
||||
var (rawPosition, position) = GetPositions(relativeTo);
|
||||
var properties = GetProperties();
|
||||
|
||||
return new PointerPoint(FrameId, timestamp, device, Pointer.PointerId, rawPosition, position, Pointer.IsInContact, properties);
|
||||
return new PointerPoint(FrameId, timestamp, device, Pointer.PointerId, rawPosition, position, Pointer.IsInContact, _properties);
|
||||
}
|
||||
|
||||
private (Point raw, Point relative) GetPositions(UIElement relativeTo)
|
||||
|
@ -99,7 +117,7 @@ namespace Windows.UI.Xaml.Input
|
|||
return (raw, relative);
|
||||
}
|
||||
|
||||
private PointerPointProperties GetProperties()
|
||||
private PointerPointProperties GetProperties(MotionEventToolType type, MotionEventActions action, MotionEventButtonState buttons)
|
||||
{
|
||||
var props = new PointerPointProperties
|
||||
{
|
||||
|
@ -107,8 +125,6 @@ namespace Windows.UI.Xaml.Input
|
|||
IsInRange = Pointer.IsInRange
|
||||
};
|
||||
|
||||
var type = _nativeEvent.GetToolType(_pointerIndex);
|
||||
var action = _nativeEvent.Action;
|
||||
var isDown = action.HasFlag(MotionEventActions.Down) || action.HasFlag(MotionEventActions.PointerDown);
|
||||
var isUp = action.HasFlag(MotionEventActions.Up) || action.HasFlag(MotionEventActions.PointerUp);
|
||||
var updates = _none;
|
||||
|
@ -117,20 +133,40 @@ namespace Windows.UI.Xaml.Input
|
|||
case MotionEventToolType.Finger:
|
||||
props.IsLeftButtonPressed = Pointer.IsInContact;
|
||||
updates = isDown ? _fingerDownUpdates : isUp ? _fingerUpUpdates : _none;
|
||||
// Pressure = .5f => Keeps default as UWP returns .5 for fingers.
|
||||
break;
|
||||
|
||||
case MotionEventToolType.Mouse:
|
||||
props.IsLeftButtonPressed = _nativeEvent.IsButtonPressed(MotionEventButtonState.Primary);
|
||||
props.IsMiddleButtonPressed = _nativeEvent.IsButtonPressed(MotionEventButtonState.Tertiary);
|
||||
props.IsRightButtonPressed = _nativeEvent.IsButtonPressed(MotionEventButtonState.Secondary);
|
||||
props.IsLeftButtonPressed = buttons.HasFlag(MotionEventButtonState.Primary);
|
||||
props.IsMiddleButtonPressed = buttons.HasFlag(MotionEventButtonState.Tertiary);
|
||||
props.IsRightButtonPressed = buttons.HasFlag(MotionEventButtonState.Secondary);
|
||||
updates = isDown ? _mouseDownUpdates : isUp ? _mouseUpUpdates : _none;
|
||||
// Pressure = .5f => Keeps default as UWP returns .5 for Mouse no matter is button is pressed or not (Android return 1.0 while pressing a button, but 0 otherwise).
|
||||
break;
|
||||
|
||||
// Note: On UWP, if you touch screen while already holding the barrel button, you will get a right + barrel,
|
||||
// ** BUT ** if you touch screen and THEN press the barrel button props will be left + barrel until released.
|
||||
// On Android this distinction seems to be flagged by the "1101 ****" action flag (i.e. "StylusWithBarrel***" actions),
|
||||
// so here we set the Is<Left|Right>ButtonPressed based on the action and we don't try to link it to the barrel button state.
|
||||
case MotionEventToolType.Stylus when action == StylusWithBarrelDown:
|
||||
case MotionEventToolType.Stylus when action == StylusWithBarrelMove:
|
||||
case MotionEventToolType.Stylus when action == StylusWithBarrelUp:
|
||||
// Note: We still validate the "IsButtonPressed(StylusPrimary)" as the user might release the button while pressed.
|
||||
// In that case we will still receive moves and up with the "StylusWithBarrel***" actions.
|
||||
props.IsBarrelButtonPressed = buttons.HasFlag(MotionEventButtonState.StylusPrimary);
|
||||
props.IsRightButtonPressed = Pointer.IsInContact;
|
||||
props.Pressure = Math.Min(1f, _nativeEvent.GetPressure(_pointerIndex)); // Might exceed 1.0 on Android
|
||||
break;
|
||||
case MotionEventToolType.Stylus:
|
||||
props.IsBarrelButtonPressed = _nativeEvent.IsButtonPressed(MotionEventButtonState.StylusPrimary);
|
||||
props.IsLeftButtonPressed = Pointer.IsInContact && !props.IsBarrelButtonPressed;
|
||||
props.IsBarrelButtonPressed = buttons.HasFlag(MotionEventButtonState.StylusPrimary);
|
||||
props.IsLeftButtonPressed = Pointer.IsInContact;
|
||||
props.Pressure = Math.Min(1f, _nativeEvent.GetPressure(_pointerIndex)); // Might exceed 1.0 on Android
|
||||
break;
|
||||
case MotionEventToolType.Eraser:
|
||||
props.IsEraser = true;
|
||||
props.Pressure = Math.Min(1f, _nativeEvent.GetPressure(_pointerIndex)); // Might exceed 1.0 on Android
|
||||
break;
|
||||
|
||||
case MotionEventToolType.Unknown: // used by Xamarin.UITest
|
||||
props.IsLeftButtonPressed = true;
|
||||
break;
|
||||
|
@ -191,23 +227,25 @@ namespace Windows.UI.Xaml.Input
|
|||
}
|
||||
}
|
||||
|
||||
private static bool IsInContact(PointerDeviceType type, MotionEvent nativeEvent, int pointerIndex)
|
||||
private static bool IsInContact(MotionEvent nativeEvent, PointerDeviceType pointerType, MotionEventActions action, MotionEventButtonState buttons)
|
||||
{
|
||||
switch (type)
|
||||
switch (pointerType)
|
||||
{
|
||||
case PointerDeviceType.Mouse:
|
||||
return nativeEvent.ButtonState != 0;
|
||||
// For mouse, we cannot only rely on action: We will get a "HoverExit" when we press the left button.
|
||||
return buttons != 0;
|
||||
|
||||
case PointerDeviceType.Pen:
|
||||
return nativeEvent.GetAxisValue(Axis.Distance, pointerIndex) == 0;
|
||||
return nativeEvent.GetAxisValue(Axis.Distance, nativeEvent.ActionIndex) == 0;
|
||||
|
||||
default:
|
||||
case PointerDeviceType.Touch:
|
||||
return nativeEvent.Action.HasFlag(MotionEventActions.Down)
|
||||
|| nativeEvent.Action.HasFlag(MotionEventActions.PointerDown)
|
||||
|| nativeEvent.Action.HasFlag(MotionEventActions.Move);
|
||||
// WARNING: MotionEventActions.Down == 0, so action.HasFlag(MotionEventActions.Up) is always true!
|
||||
return !action.HasFlag(MotionEventActions.Up)
|
||||
&& !action.HasFlag(MotionEventActions.PointerUp)
|
||||
&& !action.HasFlag(MotionEventActions.Cancel);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,7 +64,8 @@ namespace Windows.UI.Xaml.Input
|
|||
{
|
||||
IsPrimary = true,
|
||||
IsInRange = Pointer.IsInRange,
|
||||
IsLeftButtonPressed = Pointer.IsInContact
|
||||
IsLeftButtonPressed = Pointer.IsInContact,
|
||||
Pressure = (float)(_nativeTouch.Force / _nativeTouch.MaximumPossibleForce)
|
||||
};
|
||||
|
||||
#region Misc static helpers
|
||||
|
|
|
@ -14,6 +14,7 @@ namespace Windows.UI.Xaml.Input
|
|||
private readonly Point _absolutePosition;
|
||||
private readonly WindowManagerInterop.HtmlPointerButtonsState _buttons;
|
||||
private readonly WindowManagerInterop.HtmlPointerButtonUpdate _buttonUpdate;
|
||||
private readonly double _pressure;
|
||||
|
||||
internal PointerRoutedEventArgs(
|
||||
double timestamp,
|
||||
|
@ -24,6 +25,7 @@ namespace Windows.UI.Xaml.Input
|
|||
WindowManagerInterop.HtmlPointerButtonsState buttons,
|
||||
WindowManagerInterop.HtmlPointerButtonUpdate buttonUpdate,
|
||||
VirtualKeyModifiers keys,
|
||||
double pressure,
|
||||
UIElement source,
|
||||
bool canBubbleNatively)
|
||||
: this()
|
||||
|
@ -32,6 +34,7 @@ namespace Windows.UI.Xaml.Input
|
|||
_absolutePosition = absolutePosition;
|
||||
_buttons = buttons;
|
||||
_buttonUpdate = buttonUpdate;
|
||||
_pressure = pressure;
|
||||
|
||||
FrameId = ToFrameId(timestamp);
|
||||
Pointer = new Pointer(pointerId, pointerType, isInContact, isInRange: true);
|
||||
|
@ -63,22 +66,29 @@ namespace Windows.UI.Xaml.Input
|
|||
|
||||
props.IsLeftButtonPressed = _buttons.HasFlag(WindowManagerInterop.HtmlPointerButtonsState.Left);
|
||||
props.IsMiddleButtonPressed = _buttons.HasFlag(WindowManagerInterop.HtmlPointerButtonsState.Middle);
|
||||
if (_buttons.HasFlag(WindowManagerInterop.HtmlPointerButtonsState.Right))
|
||||
{
|
||||
switch (Pointer.PointerDeviceType)
|
||||
{
|
||||
case PointerDeviceType.Mouse:
|
||||
props.IsMiddleButtonPressed = true;
|
||||
break;
|
||||
case PointerDeviceType.Pen:
|
||||
props.IsBarrelButtonPressed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
props.IsRightButtonPressed = _buttons.HasFlag(WindowManagerInterop.HtmlPointerButtonsState.Right);
|
||||
props.IsXButton1Pressed = _buttons.HasFlag(WindowManagerInterop.HtmlPointerButtonsState.X1);
|
||||
props.IsXButton2Pressed = _buttons.HasFlag(WindowManagerInterop.HtmlPointerButtonsState.X2);
|
||||
props.IsEraser = _buttons.HasFlag(WindowManagerInterop.HtmlPointerButtonsState.Eraser);
|
||||
|
||||
switch (Pointer.PointerDeviceType)
|
||||
{
|
||||
// For touch and mouse, we keep the default pressure of .5, as WinUI
|
||||
|
||||
case PointerDeviceType.Pen:
|
||||
// !!! WARNING !!! Here we have a slight different behavior compared to WinUI:
|
||||
// On WinUI we will get IsRightButtonPressed (with IsBarrelButtonPressed) only if the user is pressing
|
||||
// the barrel button when pen goes "in contact" (i.e. touches the screen), otherwise we will get
|
||||
// IsLeftButtonPressed and IsBarrelButtonPressed.
|
||||
// Here we set IsRightButtonPressed as soon as the barrel button was pressed, no matter
|
||||
// if the pen was already in contact or not.
|
||||
// This is acceptable since the UIElement pressed state is **per pointer** (not buttons of pointer)
|
||||
// and GestureRecognizer always checks that pressed buttons didn't changed for a single gesture.
|
||||
props.IsBarrelButtonPressed = props.IsRightButtonPressed;
|
||||
props.Pressure = (float)_pressure;
|
||||
break;
|
||||
}
|
||||
|
||||
props.PointerUpdateKind = ToUpdateKind(_buttonUpdate, props);
|
||||
|
||||
return props;
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
namespace Windows.UI.Xaml.Input
|
||||
{
|
||||
public delegate void RightTappedEventHandler(object sender, RightTappedRoutedEventArgs e);
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
using Windows.Devices.Input;
|
||||
using Windows.Foundation;
|
||||
using Windows.UI.Input;
|
||||
using Uno.UI.Xaml.Input;
|
||||
|
||||
namespace Windows.UI.Xaml.Input
|
||||
{
|
||||
public partial class RightTappedRoutedEventArgs : RoutedEventArgs, ICancellableRoutedEventArgs
|
||||
{
|
||||
private readonly UIElement _originalSource;
|
||||
private readonly Point _position;
|
||||
|
||||
public RightTappedRoutedEventArgs() { }
|
||||
internal RightTappedRoutedEventArgs(UIElement originalSource, RightTappedEventArgs args)
|
||||
: base(originalSource)
|
||||
{
|
||||
_originalSource = originalSource;
|
||||
_position = args.Position;
|
||||
PointerDeviceType = args.PointerDeviceType;
|
||||
}
|
||||
|
||||
public bool Handled { get; set; }
|
||||
|
||||
public PointerDeviceType PointerDeviceType { get; }
|
||||
|
||||
public Point GetPosition(UIElement relativeTo)
|
||||
{
|
||||
if (_originalSource == null)
|
||||
{
|
||||
return default; // Required for the default public ctor ...
|
||||
}
|
||||
else if (relativeTo == _originalSource)
|
||||
{
|
||||
return _position;
|
||||
}
|
||||
else
|
||||
{
|
||||
return _originalSource.GetPosition(_position, relativeTo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -55,7 +55,7 @@ namespace Uno.UI.Xaml
|
|||
// Gestures
|
||||
Tapped = 1UL << 48,
|
||||
DoubleTapped = 1UL << 49,
|
||||
// RightTapped = 1UL << 50, => Reserved for future usage
|
||||
RightTapped = 1UL << 50,
|
||||
// Holding = 1UL << 51, => Reserved for future usage
|
||||
|
||||
// Context menu
|
||||
|
@ -98,8 +98,10 @@ namespace Uno.UI.Xaml
|
|||
| RoutedEventFlag.ManipulationCompleted;
|
||||
|
||||
private const RoutedEventFlag _isGesture = // 0b0000_0000_0001_1111___0000_0000_0000_0000___0000_0000_0000_0000___0000_0000_0000_0000
|
||||
RoutedEventFlag.Tapped
|
||||
| RoutedEventFlag.DoubleTapped;
|
||||
RoutedEventFlag.Tapped
|
||||
| RoutedEventFlag.DoubleTapped
|
||||
| RoutedEventFlag.RightTapped;
|
||||
//| RoutedEventFlag.Holding;
|
||||
|
||||
private const RoutedEventFlag _isContextMenu = (RoutedEventFlag)0b0011_0000_0000_0000___0000_0000_0000_0000___0000_0000_0000_0000___0000_0000_0000_0000;
|
||||
// RoutedEventFlag.ContextRequested
|
||||
|
|
|
@ -84,7 +84,7 @@ namespace Windows.UI.Xaml
|
|||
var args = new PointerRoutedEventArgs(nativeEvent, pointerIndex, srcElement, this);
|
||||
var argsAction = MotionEventActions.Move;
|
||||
|
||||
handled |= OnNativeMotionEvent(args, argsAction, isInView);
|
||||
handled |= OnNativeMotionEvent(nativeEvent, args, argsAction, isInView);
|
||||
}
|
||||
|
||||
return handled;
|
||||
|
@ -94,11 +94,11 @@ namespace Windows.UI.Xaml
|
|||
var args = new PointerRoutedEventArgs(nativeEvent, nativeEvent.ActionIndex, srcElement, this);
|
||||
var argsAction = actionMasked;
|
||||
|
||||
return OnNativeMotionEvent(args, argsAction, isInView);
|
||||
return OnNativeMotionEvent(nativeEvent, args, argsAction, isInView);
|
||||
}
|
||||
}
|
||||
|
||||
private bool OnNativeMotionEvent(PointerRoutedEventArgs args, MotionEventActions action, bool isInView)
|
||||
private bool OnNativeMotionEvent(MotionEvent nativeEvent, PointerRoutedEventArgs args, MotionEventActions action, bool isInView)
|
||||
{
|
||||
// Warning: MotionEvent of other kinds are filtered out in native code (UnoMotionHelper.java)
|
||||
switch (action)
|
||||
|
@ -117,16 +117,27 @@ namespace Windows.UI.Xaml
|
|||
case MotionEventActions.Down when args.Pointer.PointerDeviceType == PointerDeviceType.Touch:
|
||||
case MotionEventActions.PointerDown when args.Pointer.PointerDeviceType == PointerDeviceType.Touch:
|
||||
return OnNativePointerEnter(args) | OnNativePointerDown(args);
|
||||
case PointerRoutedEventArgs.StylusWithBarrelDown:
|
||||
case MotionEventActions.Down:
|
||||
case MotionEventActions.PointerDown:
|
||||
return OnNativePointerDown(args);
|
||||
case MotionEventActions.Up when args.Pointer.PointerDeviceType == PointerDeviceType.Touch:
|
||||
case MotionEventActions.PointerUp when args.Pointer.PointerDeviceType == PointerDeviceType.Touch:
|
||||
return OnNativePointerUp(args) | OnNativePointerExited(args);
|
||||
case PointerRoutedEventArgs.StylusWithBarrelUp:
|
||||
case MotionEventActions.Up:
|
||||
case MotionEventActions.PointerUp:
|
||||
return OnNativePointerUp(args);
|
||||
|
||||
// We get ACTION_DOWN and ACTION_UP only for "left" button, and instead we get a HOVER_MOVE when pressing/releasing the right button of the mouse.
|
||||
// So on each POINTER_MOVE we make sure to update the pressed state if it does not match.
|
||||
// Note: We can also have HOVER_MOVE with barrel button pressed, so we make sure to "PointerDown" only for Mouse.
|
||||
case MotionEventActions.HoverMove when args.Pointer.PointerDeviceType == PointerDeviceType.Mouse && args.HasPressedButton && !IsPressed(args.Pointer):
|
||||
return OnNativePointerDown(args) | OnNativePointerMoveWithOverCheck(args, isInView);
|
||||
case MotionEventActions.HoverMove when !args.HasPressedButton && IsPressed(args.Pointer):
|
||||
return OnNativePointerUp(args) | OnNativePointerMoveWithOverCheck(args, isInView);
|
||||
|
||||
case PointerRoutedEventArgs.StylusWithBarrelMove:
|
||||
case MotionEventActions.Move:
|
||||
case MotionEventActions.HoverMove:
|
||||
// Note: We use the OnNativePointerMove**WithOverCheck** in order to update the over state in case of press -> move out -> release
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
|
@ -132,7 +133,7 @@ namespace Windows.UI.Xaml
|
|||
if (sender is UIElement elt && !elt.IsHitTestVisibleCoalesced)
|
||||
{
|
||||
elt.Release(PointerCaptureKind.Any);
|
||||
elt.SetPressed(null, false, muteEvent: true);
|
||||
elt.ClearPressed();
|
||||
elt.SetOver(null, false, muteEvent: true);
|
||||
}
|
||||
};
|
||||
|
@ -142,7 +143,7 @@ namespace Windows.UI.Xaml
|
|||
if (sender is UIElement elt)
|
||||
{
|
||||
elt.Release(PointerCaptureKind.Any);
|
||||
elt.SetPressed(null, false, muteEvent: true);
|
||||
elt.ClearPressed();
|
||||
elt.SetOver(null, false, muteEvent: true);
|
||||
}
|
||||
};
|
||||
|
@ -221,6 +222,12 @@ namespace Windows.UI.Xaml
|
|||
that.SafeRaiseEvent(DoubleTappedEvent, new DoubleTappedRoutedEventArgs(that, args));
|
||||
}
|
||||
};
|
||||
|
||||
private static readonly TypedEventHandler<GestureRecognizer, RightTappedEventArgs> OnRecognizerRightTapped = (sender, args) =>
|
||||
{
|
||||
var that = (UIElement)sender.Owner;
|
||||
that.SafeRaiseEvent(RightTappedEvent, new RightTappedRoutedEventArgs(that, args));
|
||||
};
|
||||
#endregion
|
||||
|
||||
private bool _isGestureCompleted;
|
||||
|
@ -235,6 +242,7 @@ namespace Windows.UI.Xaml
|
|||
recognizer.ManipulationInertiaStarting += OnRecognizerManipulationInertiaStarting;
|
||||
recognizer.ManipulationCompleted += OnRecognizerManipulationCompleted;
|
||||
recognizer.Tapped += OnRecognizerTapped;
|
||||
recognizer.RightTapped += OnRecognizerRightTapped;
|
||||
|
||||
// Allow partial parts to subscribe to pointer events (WASM)
|
||||
OnGestureRecognizerInitialized(recognizer);
|
||||
|
@ -319,6 +327,10 @@ namespace Windows.UI.Xaml
|
|||
{
|
||||
_gestures.Value.GestureSettings |= GestureSettings.DoubleTap;
|
||||
}
|
||||
else if (routedEvent == RightTappedEvent)
|
||||
{
|
||||
_gestures.Value.GestureSettings |= GestureSettings.RightTap;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
@ -673,6 +685,8 @@ namespace Windows.UI.Xaml
|
|||
#endregion
|
||||
|
||||
#region Pointer pressed state (Updated by the partial API OnNative***)
|
||||
private readonly HashSet<uint> _pressedPointers = new HashSet<uint>();
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if a pointer was pressed while over the element (i.e. PressedState).
|
||||
/// Note: The pressed state will remain true even if the pointer exits the control (while pressed)
|
||||
|
@ -684,7 +698,7 @@ namespace Windows.UI.Xaml
|
|||
/// So it means that this flag will be maintained only if you subscribe at least to one pointer event
|
||||
/// (or override one of the OnPointer*** methods).
|
||||
/// </remarks>
|
||||
internal bool IsPointerPressed { get; set; } // TODO: 'Set' should be private, but we need to update all controls that are setting
|
||||
internal bool IsPointerPressed => _pressedPointers.Count != 0;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if a pointer was pressed while over the element (i.e. PressedState)
|
||||
|
@ -697,30 +711,49 @@ namespace Windows.UI.Xaml
|
|||
/// So it means that this method will give valid state only if you subscribe at least to one pointer event
|
||||
/// (or override one of the OnPointer*** methods).
|
||||
/// </remarks>
|
||||
internal bool IsPressed(Pointer pointer) => IsPointerPressed;
|
||||
/// <remarks>
|
||||
/// Note that on UWP the "pressed" state is managed **PER POINTER**, and not per pressed button on the given pointer.
|
||||
/// It means that with a mouse if you follow this sequence : press left => press right => release right => release left,
|
||||
/// you will get only one 'PointerPressed' and one 'PointerReleased'.
|
||||
/// Same thing if you release left first (press left => press right => release left => release right), and for the pen's barrel button.
|
||||
/// </remarks>
|
||||
internal bool IsPressed(Pointer pointer) => _pressedPointers.Contains(pointer.PointerId);
|
||||
|
||||
private bool SetPressed(PointerRoutedEventArgs args, bool isPressed, bool muteEvent = false)
|
||||
{
|
||||
var wasPressed = IsPointerPressed;
|
||||
IsPointerPressed = isPressed;
|
||||
|
||||
if (muteEvent
|
||||
|| wasPressed == isPressed) // nothing changed
|
||||
var wasPressed = IsPressed(args.Pointer);
|
||||
if (wasPressed == isPressed) // nothing changed
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isPressed) // Pressed
|
||||
{
|
||||
_pressedPointers.Add(args.Pointer.PointerId);
|
||||
|
||||
if (muteEvent)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
args.Handled = false;
|
||||
return RaisePointerEvent(PointerPressedEvent, args);
|
||||
}
|
||||
else // Released
|
||||
{
|
||||
_pressedPointers.Remove(args.Pointer.PointerId);
|
||||
|
||||
if (muteEvent)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
args.Handled = false;
|
||||
return RaisePointerEvent(PointerReleasedEvent, args);
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearPressed() => _pressedPointers.Clear();
|
||||
#endregion
|
||||
|
||||
#region Pointer capture state (Updated by the partial API OnNative***)
|
||||
|
|
|
@ -109,7 +109,7 @@ namespace Windows.UI.Xaml
|
|||
private static PointerRoutedEventArgs PayloadToPointerArgs(object snd, string payload, bool isInContact, bool canBubble = true)
|
||||
{
|
||||
var parts = payload?.Split(';');
|
||||
if (parts?.Length != 10)
|
||||
if (parts?.Length != 11)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
@ -124,6 +124,7 @@ namespace Windows.UI.Xaml
|
|||
var typeStr = parts[7];
|
||||
var srcHandle = int.Parse(parts[8], CultureInfo.InvariantCulture);
|
||||
var timestamp = double.Parse(parts[9], CultureInfo.InvariantCulture);
|
||||
var pressure = double.Parse(parts[10], CultureInfo.InvariantCulture);
|
||||
|
||||
var src = GetElementFromHandle(srcHandle) ?? (UIElement)snd;
|
||||
var position = new Point(x, y);
|
||||
|
@ -141,6 +142,7 @@ namespace Windows.UI.Xaml
|
|||
(WindowManagerInterop.HtmlPointerButtonsState)buttons,
|
||||
(WindowManagerInterop.HtmlPointerButtonUpdate)buttonUpdate,
|
||||
keyModifiers,
|
||||
pressure,
|
||||
src,
|
||||
canBubble);
|
||||
}
|
||||
|
|
|
@ -105,6 +105,8 @@ namespace Windows.UI.Xaml
|
|||
|
||||
public static RoutedEvent DoubleTappedEvent { get; } = new RoutedEvent(RoutedEventFlag.DoubleTapped);
|
||||
|
||||
public static RoutedEvent RightTappedEvent { get; } = new RoutedEvent(RoutedEventFlag.RightTapped);
|
||||
|
||||
public static RoutedEvent KeyDownEvent { get; } = new RoutedEvent(RoutedEventFlag.KeyDown);
|
||||
|
||||
public static RoutedEvent KeyUpEvent { get; } = new RoutedEvent(RoutedEventFlag.KeyUp);
|
||||
|
@ -297,6 +299,12 @@ namespace Windows.UI.Xaml
|
|||
remove => RemoveHandler(DoubleTappedEvent, value);
|
||||
}
|
||||
|
||||
public event RightTappedEventHandler RightTapped
|
||||
{
|
||||
add => AddHandler(RightTappedEvent, value, false);
|
||||
remove => RemoveHandler(RightTappedEvent, value);
|
||||
}
|
||||
|
||||
#if __MACOS__
|
||||
public new event KeyEventHandler KeyDown
|
||||
#else
|
||||
|
@ -661,6 +669,9 @@ namespace Windows.UI.Xaml
|
|||
case DoubleTappedEventHandler doubleTappedEventHandler:
|
||||
doubleTappedEventHandler(this, (DoubleTappedRoutedEventArgs)args);
|
||||
break;
|
||||
case RightTappedEventHandler rightTappedEventHandler:
|
||||
rightTappedEventHandler(this, (RightTappedRoutedEventArgs)args);
|
||||
break;
|
||||
case KeyEventHandler keyEventHandler:
|
||||
keyEventHandler(this, (KeyRoutedEventArgs)args);
|
||||
break;
|
||||
|
|
|
@ -244,7 +244,7 @@ namespace Windows.UI.Xaml
|
|||
|
||||
if (evt.IsTouchInView(this))
|
||||
{
|
||||
IsPointerPressed = true;
|
||||
_pressedPointers.Add(0);
|
||||
IsPointerOver = true;
|
||||
|
||||
// evt.AllTouches raises a invalid selector exception
|
||||
|
@ -317,7 +317,7 @@ namespace Windows.UI.Xaml
|
|||
base.MouseUp(evt);
|
||||
}
|
||||
|
||||
IsPointerPressed = false;
|
||||
ClearPressed();
|
||||
IsPointerOver = false;
|
||||
}
|
||||
catch (Exception e)
|
||||
|
|
|
@ -467,7 +467,7 @@ namespace Windows.UI.Input
|
|||
}
|
||||
}
|
||||
#endif
|
||||
#if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __MACOS__
|
||||
#if false
|
||||
[global::Uno.NotImplemented]
|
||||
public event global::Windows.Foundation.TypedEventHandler<global::Windows.UI.Input.GestureRecognizer, global::Windows.UI.Input.RightTappedEventArgs> RightTapped
|
||||
{
|
||||
|
|
|
@ -177,7 +177,7 @@ namespace Windows.UI.Input
|
|||
}
|
||||
}
|
||||
#endif
|
||||
#if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __MACOS__
|
||||
#if false
|
||||
[global::Uno.NotImplemented]
|
||||
public float Pressure
|
||||
{
|
||||
|
|
|
@ -2,12 +2,12 @@
|
|||
#pragma warning disable 114 // new keyword hiding
|
||||
namespace Windows.UI.Input
|
||||
{
|
||||
#if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __MACOS__
|
||||
#if false
|
||||
[global::Uno.NotImplemented]
|
||||
#endif
|
||||
public partial class RightTappedEventArgs
|
||||
{
|
||||
#if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __MACOS__
|
||||
#if false
|
||||
[global::Uno.NotImplemented]
|
||||
public global::Windows.Devices.Input.PointerDeviceType PointerDeviceType
|
||||
{
|
||||
|
@ -17,7 +17,7 @@ namespace Windows.UI.Input
|
|||
}
|
||||
}
|
||||
#endif
|
||||
#if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __MACOS__
|
||||
#if false
|
||||
[global::Uno.NotImplemented]
|
||||
public global::Windows.Foundation.Point Position
|
||||
{
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Windows.Devices.Input;
|
||||
using Windows.Foundation;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Uno.Extensions;
|
||||
|
@ -72,19 +73,16 @@ namespace Windows.UI.Input
|
|||
|
||||
internal void ProcessMoveEvents(IList<PointerPoint> value, bool isRelevant)
|
||||
{
|
||||
if (isRelevant)
|
||||
foreach (var point in value)
|
||||
{
|
||||
foreach (var point in value)
|
||||
if (_activePointers.TryGetValue(point.PointerId, out var points))
|
||||
{
|
||||
if (_activePointers.TryGetValue(point.PointerId, out var points))
|
||||
{
|
||||
points.Add(point);
|
||||
}
|
||||
else if (_log.IsEnabled(LogLevel.Information))
|
||||
{
|
||||
// info: We might get some PointerMove for mouse even if not pressed!
|
||||
_log.Info("Received a 'Move' for a pointer which was not considered as down. Ignoring event.");
|
||||
}
|
||||
points.Add(point);
|
||||
}
|
||||
else if (_log.IsEnabled(LogLevel.Information))
|
||||
{
|
||||
// info: We might get some PointerMove for mouse even if not pressed!
|
||||
_log.Info("Received a 'Move' for a pointer which was not considered as down. Ignoring event.");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -103,18 +101,14 @@ namespace Windows.UI.Input
|
|||
if (_activePointers.Remove(value.PointerId, out var points))
|
||||
{
|
||||
#endif
|
||||
|
||||
// Note: At this point we MAY be IsActive == false, which is the expected behavior (same as UWP)
|
||||
// even if we will fire some events now.
|
||||
|
||||
if (isRelevant)
|
||||
{
|
||||
// We need to process only events that are bubbling natively to this control (i.e. isIrrelevant == false),
|
||||
// if they are bubbling in managed it means that they where handled a child control,
|
||||
// so we should not use them for gesture recognition.
|
||||
// We need to process only events that are bubbling natively to this control (i.e. isIrrelevant == false),
|
||||
// if they are bubbling in managed it means that they where handled a child control,
|
||||
// so we should not use them for gesture recognition.
|
||||
|
||||
Recognize(points, pointerUp: value);
|
||||
}
|
||||
Recognize(points, pointerUp: value);
|
||||
|
||||
_manipulation?.Remove(value);
|
||||
}
|
||||
|
@ -154,7 +148,8 @@ namespace Windows.UI.Input
|
|||
return;
|
||||
}
|
||||
|
||||
var recognized = TryRecognizeTap(points, pointerUp);
|
||||
var recognized = TryRecognizeRightTap(points, pointerUp) // We check right tap first as for touch a right tap is a press and hold of the finger :)
|
||||
|| TryRecognizeTap(points, pointerUp);
|
||||
|
||||
if (!recognized && _log.IsEnabled(LogLevel.Information))
|
||||
{
|
||||
|
@ -177,47 +172,79 @@ namespace Windows.UI.Input
|
|||
internal Manipulation PendingManipulation => _manipulation;
|
||||
#endregion
|
||||
|
||||
#region Tap (includes DoubleTap)
|
||||
#region Tap (includes DoubleTap and RightTap)
|
||||
internal const ulong MultiTapMaxDelayTicks = TimeSpan.TicksPerMillisecond * 1000;
|
||||
internal const ulong HoldMinDelayTicks = TimeSpan.TicksPerMillisecond * 800;
|
||||
internal const float HoldMinPressure = .75f;
|
||||
internal const int TapMaxXDelta = 10;
|
||||
internal const int TapMaxYDelta = 10;
|
||||
|
||||
public event TypedEventHandler<GestureRecognizer, TappedEventArgs> Tapped;
|
||||
private (ulong id, ulong ts, Point position) _lastSingleTap;
|
||||
|
||||
private (ulong, ulong, Point) _lastSingleTap;
|
||||
public event TypedEventHandler<GestureRecognizer, TappedEventArgs> Tapped;
|
||||
public event TypedEventHandler<GestureRecognizer, RightTappedEventArgs> RightTapped;
|
||||
|
||||
public bool CanBeDoubleTap(PointerPoint value)
|
||||
{
|
||||
if (!_gestureSettings.HasFlag(GestureSettings.DoubleTap))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var (lastTapId, lastTapTs, lastTapLocation) = _lastSingleTap;
|
||||
if (lastTapTs == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var currentId = GetPointerIdentifier(value);
|
||||
var currentTs = value.Timestamp;
|
||||
var currentPosition = value.Position;
|
||||
|
||||
return lastTapId == currentId
|
||||
&& currentTs - lastTapTs <= MultiTapMaxDelayTicks
|
||||
&& !IsOutOfTapRange(lastTapLocation, currentPosition);
|
||||
}
|
||||
=> _gestureSettings.HasFlag(GestureSettings.DoubleTap) && IsMultiTapGesture(_lastSingleTap, value);
|
||||
|
||||
private bool TryRecognizeTap(List<PointerPoint> points, PointerPoint pointerUp = null)
|
||||
{
|
||||
if (pointerUp == null)
|
||||
if (IsTapGesture(LeftButton, points, pointerUp, out var start, out _))
|
||||
{
|
||||
// pointerUp is not null here!
|
||||
|
||||
_lastSingleTap = (start.id, pointerUp.Timestamp, pointerUp.Position);
|
||||
Tapped?.Invoke(this, new TappedEventArgs(start.point.PointerDevice.PointerDeviceType, start.point.Position, tapCount: 1));
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var start = points[0]; // down
|
||||
var end = pointerUp;
|
||||
var startIdentifier = GetPointerIdentifier(start);
|
||||
private bool TryRecognizeMultiTap(PointerPoint pointerDown)
|
||||
{
|
||||
if (_gestureSettings.HasFlag(GestureSettings.DoubleTap) && IsMultiTapGesture(_lastSingleTap, pointerDown))
|
||||
{
|
||||
_lastSingleTap = default; // The Recognizer supports only double tap, even on UWP
|
||||
Tapped?.Invoke(this, new TappedEventArgs(pointerDown.PointerDevice.PointerDeviceType, pointerDown.Position, tapCount: 2));
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryRecognizeRightTap(List<PointerPoint> points, PointerPoint pointerUp = null)
|
||||
{
|
||||
if (_gestureSettings.HasFlag(GestureSettings.RightTap) && IsRightTapGesture(points, pointerUp, out var start))
|
||||
{
|
||||
RightTapped?.Invoke(this, new RightTappedEventArgs(start.point.PointerDevice.PointerDeviceType, start.point.Position));
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#region Actual Tap gestures recognition (static)
|
||||
// The beginning of a Tap gesture is: 1 down -> * moves close to the down with same buttons pressed
|
||||
private static bool IsBeginningTapGesture(CheckButton isExpectedButton, List<PointerPoint> points, out (PointerPoint point, ulong id) start, out bool isForceTouch)
|
||||
{
|
||||
var startPoint = points[0]; // down
|
||||
start = (startPoint, GetPointerIdentifier(startPoint));
|
||||
isForceTouch = false;
|
||||
|
||||
if (!isExpectedButton(start.point)) // We validate only the start as for other points we validate the full pointer identifier
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate tap gesture
|
||||
// Note: There is no limit for the duration of the tap!
|
||||
|
@ -228,46 +255,136 @@ namespace Windows.UI.Input
|
|||
|
||||
if (
|
||||
// The pointer changed (left vs right click)
|
||||
pointIdentifier != startIdentifier
|
||||
pointIdentifier != start.id
|
||||
|
||||
// Pointer moved to far
|
||||
|| IsOutOfTapRange(point.Position, start.Position)
|
||||
|| IsOutOfTapRange(point.Position, start.point.Position)
|
||||
)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
isForceTouch |= point.Properties.Pressure >= HoldMinPressure;
|
||||
}
|
||||
// For the pointer up, we check only the distance, as it's expected that the IsLeftButtonPressed changed!
|
||||
if (IsOutOfTapRange(end.Position, start.Position))
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// A Tap gesture is: 1 down -> * moves close to the down with same buttons pressed -> 1 up
|
||||
private static bool IsTapGesture(CheckButton isExpectedButton, List<PointerPoint> points, PointerPoint pointerUp, out (PointerPoint point, ulong id) start, out bool isForceTouch)
|
||||
{
|
||||
if (pointerUp == null) // no tap if no up!
|
||||
{
|
||||
start = default;
|
||||
isForceTouch = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate that all the intermediates points are valid
|
||||
if (!IsBeginningTapGesture(isExpectedButton, points, out start, out isForceTouch))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_lastSingleTap = (startIdentifier, end.Timestamp, end.Position);
|
||||
Tapped?.Invoke(this, new TappedEventArgs(start.PointerDevice.PointerDeviceType, start.Position, tapCount: 1));
|
||||
// For the pointer up, we check only the distance, as it's expected that the pressed button changed!
|
||||
if (IsOutOfTapRange(pointerUp.Position, start.point.Position))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool IsMultiTapGesture((ulong id, ulong ts, Point position) previousTap, PointerPoint down)
|
||||
{
|
||||
if (previousTap.ts == 0) // i.s. no previous tap to compare with
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var currentId = GetPointerIdentifier(down);
|
||||
var currentTs = down.Timestamp;
|
||||
var currentPosition = down.Position;
|
||||
|
||||
return previousTap.id == currentId
|
||||
&& currentTs - previousTap.ts <= MultiTapMaxDelayTicks
|
||||
&& !IsOutOfTapRange(previousTap.position, currentPosition);
|
||||
}
|
||||
|
||||
private static bool IsRightTapGesture(List<PointerPoint> points, PointerPoint pointerUp, out (PointerPoint point, ulong id) start)
|
||||
{
|
||||
switch (points[0].PointerDevice.PointerDeviceType)
|
||||
{
|
||||
case PointerDeviceType.Touch:
|
||||
var isLeftTap = IsTapGesture(LeftButton, points, pointerUp, out start, out var isForceTouch);
|
||||
if (isLeftTap && pointerUp.Timestamp - start.point.Timestamp > HoldMinDelayTicks)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
#if __IOS__
|
||||
if (Uno.WinRTFeatureConfiguration.GestureRecognizer.InterpretForceTouchAsRightTap
|
||||
&& isLeftTap
|
||||
&& isForceTouch)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
|
||||
case PointerDeviceType.Pen:
|
||||
if (IsTapGesture(BarrelButton, points, pointerUp, out start, out _))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Some pens does not have a barrel button, so we also allow long press (and anyway it's the UWP behavior)
|
||||
if (IsTapGesture(LeftButton, points, pointerUp, out start, out _)
|
||||
&& pointerUp.Timestamp - start.point.Timestamp > HoldMinDelayTicks)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
case PointerDeviceType.Mouse:
|
||||
if (IsTapGesture(RightButton, points, pointerUp, out start, out _))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
#if __ANDROID__
|
||||
// On Android, usually the right button is mapped to back navigation. So, unlike UWP,
|
||||
// we also allow a long press with the left button to be more user friendly.
|
||||
if (Uno.WinRTFeatureConfiguration.GestureRecognizer.InterpretMouseLeftLongPressAsRightTap
|
||||
&& IsTapGesture(LeftButton, points, pointerUp, out start, out _)
|
||||
&& pointerUp.Timestamp - start.point.Timestamp > HoldMinDelayTicks)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
|
||||
default:
|
||||
start = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Tap helpers
|
||||
private delegate bool CheckButton(PointerPoint point);
|
||||
|
||||
private static readonly CheckButton LeftButton = (PointerPoint point) => point.Properties.IsLeftButtonPressed;
|
||||
private static readonly CheckButton RightButton = (PointerPoint point) => point.Properties.IsRightButtonPressed;
|
||||
private static readonly CheckButton BarrelButton = (PointerPoint point) => point.Properties.IsBarrelButtonPressed;
|
||||
|
||||
private static bool IsOutOfTapRange(Point p1, Point p2)
|
||||
=> Math.Abs(p1.X - p2.X) > TapMaxXDelta
|
||||
|| Math.Abs(p1.Y - p2.Y) > TapMaxYDelta;
|
||||
|
||||
private bool TryRecognizeMultiTap(PointerPoint pointerDown)
|
||||
{
|
||||
if (!CanBeDoubleTap(pointerDown))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_lastSingleTap = default; // The Recognizer supports only double tap, even on UWP
|
||||
Tapped?.Invoke(this, new TappedEventArgs(pointerDown.PointerDevice.PointerDeviceType, pointerDown.Position, tapCount: 2));
|
||||
|
||||
return true;
|
||||
}
|
||||
#endregion
|
||||
|
||||
private ulong GetPointerIdentifier(PointerPoint point)
|
||||
#endregion
|
||||
|
||||
private static ulong GetPointerIdentifier(PointerPoint point)
|
||||
{
|
||||
// For mouse, the PointerId is the same, no matter the button pressed.
|
||||
// The only thing that changes are flags in the properties.
|
||||
|
|
|
@ -23,7 +23,6 @@ namespace Windows.UI.Input
|
|||
[global::Uno.NotImplemented] // The GestureRecognizer won't raise this event
|
||||
HoldWithMouse = 8U,
|
||||
/// <summary>Enable support for a right-tap interaction. The RightTapped event is raised when the contact is lifted or the mouse button released.</summary>
|
||||
[global::Uno.NotImplemented] // The GestureRecognizer won't raise this event
|
||||
RightTap = 16U,
|
||||
/// <summary>Enable support for the slide or swipe gesture with a mouse or pen/stylus (single contact). The Dragging event is raised when either gesture is detected.This gesture can be used for text selection, selecting or rearranging objects, or scrolling and panning.</summary>
|
||||
[global::Uno.NotImplemented] // The GestureRecognizer won't raise this event
|
||||
|
|
|
@ -8,6 +8,8 @@ namespace Windows.UI.Input
|
|||
{
|
||||
}
|
||||
|
||||
internal bool HasPressedButton => IsLeftButtonPressed || IsMiddleButtonPressed || IsRightButtonPressed || IsXButton1Pressed || IsXButton2Pressed || IsBarrelButtonPressed;
|
||||
|
||||
public bool IsPrimary { get; internal set; }
|
||||
|
||||
public bool IsInRange { get; internal set; }
|
||||
|
@ -28,6 +30,8 @@ namespace Windows.UI.Input
|
|||
|
||||
public bool IsEraser { get; internal set; }
|
||||
|
||||
public float Pressure { get; internal set; } = 0.5f; // According to the doc, the default value is .5
|
||||
|
||||
public PointerUpdateKind PointerUpdateKind { get; internal set; }
|
||||
|
||||
[global::Uno.NotImplemented]
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
using Windows.Devices.Input;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Windows.UI.Input
|
||||
{
|
||||
public partial class RightTappedEventArgs
|
||||
{
|
||||
internal RightTappedEventArgs(PointerDeviceType type, Point position)
|
||||
{
|
||||
PointerDeviceType = type;
|
||||
Position = position;
|
||||
}
|
||||
|
||||
public PointerDeviceType PointerDeviceType { get; }
|
||||
|
||||
public Point Position { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Text;
|
||||
|
||||
namespace Uno
|
||||
{
|
||||
public static class WinRTFeatureConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// Used by tests cleanup to restore the default configuration for other tests!
|
||||
/// </summary>
|
||||
internal static void RestoreDefaults()
|
||||
{
|
||||
GestureRecognizer.RestoreDefaults();
|
||||
}
|
||||
|
||||
public static class GestureRecognizer
|
||||
{
|
||||
internal static void RestoreDefaults()
|
||||
{
|
||||
#if __ANDROID__
|
||||
InterpretMouseLeftLongPressAsRightTap = _defaultInterpretMouseLeftLongPressAsRightTap;
|
||||
#elif __IOS__
|
||||
InterpretForceTouchAsRightTap = _defaultInterpretForceTouchAsRightTap;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if __ANDROID__
|
||||
private const bool _defaultInterpretMouseLeftLongPressAsRightTap = true;
|
||||
/// <summary>
|
||||
/// Determines if unlike UWP, long press on the left button of a mouse should be interpreted as a right tap.
|
||||
/// This is useful as the right button is commonly used by Android devices for back navigation.
|
||||
/// Using a long press with left button will be more intuitive for Android's users.
|
||||
/// Note that a long press on the right button is usually not used for back navigation, and will always be interpreted
|
||||
/// as a right tap no matter the value of this flag.
|
||||
/// </summary>
|
||||
[DefaultValue(_defaultInterpretMouseLeftLongPressAsRightTap)]
|
||||
public static bool InterpretMouseLeftLongPressAsRightTap { get; set; } = _defaultInterpretMouseLeftLongPressAsRightTap;
|
||||
#endif
|
||||
#if __IOS__
|
||||
private const bool _defaultInterpretForceTouchAsRightTap = true;
|
||||
/// <summary>
|
||||
/// Determines if force touch (a.k.a. 3D touch) should be interpreted as a right tap.
|
||||
/// Note that a long press will always be interpreted as a right tap no matter the value of this flag.
|
||||
/// </summary>
|
||||
[DefaultValue(_defaultInterpretForceTouchAsRightTap)]
|
||||
public static bool InterpretForceTouchAsRightTap { get; set; } = _defaultInterpretForceTouchAsRightTap;
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче