Merge pull request #2460 from unoplatform/dev/dr/RightTapped

Add RightTapped event support
This commit is contained in:
David 2020-01-16 16:45:52 -05:00 коммит произвёл GitHub
Родитель 09cf0ef5b2 a0604b4e0d
Коммит 942c11ac95
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
50 изменённых файлов: 2113 добавлений и 343 удалений

Просмотреть файл

@ -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
}
}
}