Merge branch 'master' into dev/jela/nav-view-itemsource

This commit is contained in:
Jérôme Laban 2020-01-17 08:01:17 -05:00 коммит произвёл GitHub
Родитель 654de19964 942c11ac95
Коммит 00da42300c
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
97 изменённых файлов: 5303 добавлений и 637 удалений

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

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

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

@ -45,13 +45,15 @@ jobs:
msbuildLocationMethod: version
msbuildVersion: latest
msbuildArchitecture: x86
msbuildArguments: /r /p:CheckExclusions=True "/p:CombinedConfiguration=$(CombinedConfiguration)" /nodeReuse:true /detailedsummary /m:16 /nr:false /bl:$(build.artifactstagingdirectory)\build.binlog
msbuildArguments: /r /p:CheckExclusions=True "/p:CombinedConfiguration=$(CombinedConfiguration)" /nodeReuse:true /detailedsummary /m /nr:false /bl:$(build.artifactstagingdirectory)\build.binlog
clean: false
maximumCpuCount: true
restoreNugetPackages: false
logProjectEvents: false
createLogFile: false
- task: VisualStudioTestPlatformInstaller@1
- task: VSTest@2
inputs:
testAssemblyVer2: |

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

@ -38,6 +38,7 @@ else
namespace = 'SamplesApp.UITests.Windows_UI_Xaml_Media.Animation_Tests' or \
namespace = 'SamplesApp.UITests.Windows_UI_Xaml_Controls.ControlTests' or \
namespace = 'SamplesApp.UITests.Windows_UI_Xaml_Controls.TextBlockTests' or \
namespace = 'SamplesApp.UITests.Microsoft_UI_Xaml_Controls.NumberBoxTests' or \
namespace = 'SamplesApp.UITests.Windows_UI_Xaml_Controls.ImageTests'
"
fi

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

@ -12,6 +12,8 @@
- Add support for `Popup.LightDismissOverlayMode`, as well as `DatePicker.LightDismissOverlayMode` and `Flyout.LightDismissOverlayMode`
- `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
-
@ -34,6 +36,7 @@
- [Android] Fix Image margin calculation on fixed size
- [Android] Native views weren't clipped correctly
- [iOS] #2361 ListView would measure children with infinite width
- [iOS] Fix crash when using ComboBox template with native Picker and changing ItemsSource to null after SelectedItem was set
- [#2398] Fully qualify the `MethodName` value for `CreateFromStringAttribute' if it's not fully qualified it the code
- [WASM] Fix bug where changing a property could remove the required clipping on a view
- [Android] Fix unconstrained Image loading issue when contained in a ContentControl template

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

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

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

@ -154,6 +154,9 @@
- ViewBox
- PersonPicture
## WinUI Specific Controls (Pre 3.0)
- [NumberBox](https://docs.microsoft.com/en-us/uwp/api/microsoft.ui.xaml.controls.numberbox?view=winui-2.3)
### Non-UI features
- Windows.UI.Storage (StorageFile, StorageFolder, Settings)

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

@ -0,0 +1,256 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NUnit.Framework;
using SamplesApp.UITests.TestFramework;
using Uno.UITest.Helpers;
using Uno.UITest.Helpers.Queries;
namespace SamplesApp.UITests.Microsoft_UI_Xaml_Controls.NumberBoxTests
{
public class NumberBoxTests : SampleControlUITestBase
{
[Test]
[AutoRetry]
public void UpDownTest()
{
Run("UITests.Shared.Microsoft_UI_Xaml_Controls.NumberBoxTests.MUX_Test");
var numBox = _app.Marked("TestNumberBox");
Assert.AreEqual(0, numBox.GetDependencyPropertyValue<double>("Value"));
numBox.SetDependencyPropertyValue("SpinButtonPlacementMode", "Inline");
var upButton = numBox.Descendant().Marked("UpSpinButton");
var downButton = numBox.Descendant().Marked("DownSpinButton");
Console.WriteLine("Assert that up button increases value by 1");
upButton.Tap();
Assert.AreEqual(1, numBox.GetDependencyPropertyValue<double>("Value"));
Console.WriteLine("Assert that down button decreases value by 1");
downButton.Tap();
Assert.AreEqual(0, numBox.GetDependencyPropertyValue<double>("Value"));
Console.WriteLine("Change SmallChange value to 5");
var smallChangeNumBox = _app.Marked("SmallChangeNumberBox");
smallChangeNumBox.SetDependencyPropertyValue("Text", "5");
Console.WriteLine("Assert that up button increases value by 5");
upButton.Tap();
Assert.AreEqual(5, numBox.GetDependencyPropertyValue<double>("Value"));
_app.Tap("MinCheckBox");
_app.Tap("MaxCheckBox");
numBox.SetDependencyPropertyValue("Value", "100");
_app.Tap("WrapCheckBox");
Console.WriteLine("Assert that when wrapping is on, and value is at max, clicking the up button wraps to the min value.");
upButton.Tap();
Assert.AreEqual(0, numBox.GetDependencyPropertyValue<double>("Value"));
Console.WriteLine("Assert that when wrapping is on, clicking the down button wraps to the max value.");
downButton.Tap();
Assert.AreEqual(100, numBox.GetDependencyPropertyValue<double>("Value"));
Console.WriteLine("Assert that incrementing after typing in a value validates the text first.");
numBox.ClearText();
numBox.EnterText("50");
_app.PressEnter();
upButton.Tap();
Assert.AreEqual(55, numBox.GetDependencyPropertyValue<double>("Value"));
}
[Test]
[AutoRetry]
public void MinMaxTest()
{
Run("UITests.Shared.Microsoft_UI_Xaml_Controls.NumberBoxTests.MUX_Test");
_app.Tap("MinCheckBox");
_app.Tap("MaxCheckBox");
var numBox = _app.Marked("TestNumberBox");
Assert.AreEqual(0, numBox.GetDependencyPropertyValue<double>("Minimum"));
Assert.AreEqual(100, numBox.GetDependencyPropertyValue<double>("Maximum"));
numBox.SetDependencyPropertyValue("Value", "10");
Console.Write("Assert that setting the value to -1 changes the value to 0");
numBox.SetDependencyPropertyValue("Value", "-1");
Assert.AreEqual(0, numBox.GetDependencyPropertyValue<double>("Value"));
Console.Write("Assert that typing '123' in the NumberBox changes the value to 100");
numBox.ClearText();
numBox.EnterText("123");
_app.PressEnter();
Assert.AreEqual(100, numBox.GetDependencyPropertyValue<double>("Value"));
Console.Write("Changing Max to 90; Assert value also changes to 90");
var maxBox = _app.Marked("MaxNumberBox");
maxBox.SetDependencyPropertyValue("Value", "90");
Assert.AreEqual(90, numBox.GetDependencyPropertyValue<double>("Value"));
Console.Write("Assert that setting the minimum above the maximum changes the maximum");
var minBox = _app.Marked("MinNumberBox");
minBox.SetDependencyPropertyValue("Value", "200");
Assert.AreEqual(200, numBox.GetDependencyPropertyValue<double>("Minimum"));
Assert.AreEqual(200, numBox.GetDependencyPropertyValue<double>("Maximum"));
Assert.AreEqual(200, numBox.GetDependencyPropertyValue<double>("Value"));
Console.Write("Assert that setting the maximum below the minimum changes the minimum");
maxBox.SetDependencyPropertyValue("Value", "150");
Assert.AreEqual(150, numBox.GetDependencyPropertyValue<double>("Minimum"));
Assert.AreEqual(150, numBox.GetDependencyPropertyValue<double>("Maximum"));
Assert.AreEqual(150, numBox.GetDependencyPropertyValue<double>("Value"));
}
[Test]
[AutoRetry]
public void UpDownEnabledTest()
{
Run("UITests.Shared.Microsoft_UI_Xaml_Controls.NumberBoxTests.MUX_Test");
var numBox = _app.Marked("TestNumberBox");
numBox.SetDependencyPropertyValue("SpinButtonPlacementMode", "Inline");
var upButton = numBox.Descendant().Marked("UpSpinButton");
var downButton = numBox.Descendant().Marked("DownSpinButton");
_app.Tap("MinCheckBox");
_app.Tap("MaxCheckBox");
Console.WriteLine("Assert that when Value is at Minimum, the down spin button is disabled.");
Assert.IsTrue(upButton.GetDependencyPropertyValue<bool>("IsEnabled"));
Assert.IsFalse(downButton.GetDependencyPropertyValue<bool>("IsEnabled"));
Console.WriteLine("Assert that when Value is at Maximum, the up spin button is disabled.");
numBox.SetDependencyPropertyValue("Value", "100");
Assert.IsFalse(upButton.GetDependencyPropertyValue<bool>("IsEnabled"));
Assert.IsTrue(downButton.GetDependencyPropertyValue<bool>("IsEnabled"));
Console.WriteLine("Assert that when wrapping is enabled, spin buttons are enabled.");
_app.Tap("WrapCheckBox");
Assert.IsTrue(upButton.GetDependencyPropertyValue<bool>("IsEnabled"));
Assert.IsTrue(downButton.GetDependencyPropertyValue<bool>("IsEnabled"));
_app.Tap("WrapCheckBox");
Console.WriteLine("Assert that when Maximum is updated the up button is updated also.");
var maxBox = _app.Marked("MaxNumberBox");
maxBox.SetDependencyPropertyValue("Value", "200");
Assert.IsTrue(upButton.GetDependencyPropertyValue<bool>("IsEnabled"));
Assert.IsTrue(downButton.GetDependencyPropertyValue<bool>("IsEnabled"));
Console.WriteLine("Assert that spin buttons are disabled if value is NaN.");
numBox.SetDependencyPropertyValue("Value", "NaN");
Assert.IsFalse(upButton.GetDependencyPropertyValue<bool>("IsEnabled"));
Assert.IsFalse(downButton.GetDependencyPropertyValue<bool>("IsEnabled"));
numBox.SetDependencyPropertyValue("ValidationMode", "Disabled");
Console.WriteLine("Assert that when validation is off, spin buttons are enabled");
numBox.SetDependencyPropertyValue("Value", "0");
Assert.IsTrue(upButton.GetDependencyPropertyValue<bool>("IsEnabled"));
Assert.IsTrue(downButton.GetDependencyPropertyValue<bool>("IsEnabled"));
Console.WriteLine("...except in the NaN case");
numBox.SetDependencyPropertyValue("Value", "NaN");
Assert.IsFalse(upButton.GetDependencyPropertyValue<bool>("IsEnabled"));
Assert.IsFalse(downButton.GetDependencyPropertyValue<bool>("IsEnabled"));
}
[Test]
[AutoRetry]
public void BasicExpressionTest()
{
Run("UITests.Shared.Microsoft_UI_Xaml_Controls.NumberBoxTests.MUX_Test");
var numBox = _app.Marked("TestNumberBox");
_app.EnterText(numBox, "5 + 3");
Assert.AreEqual("0", numBox.GetText());
_app.Tap("ExpressionCheckBox");
int numErrors = 0;
const double resetValue = double.NaN;
Dictionary<string, double> expressions = new Dictionary<string, double>
{
// Valid expressions. None of these should evaluate to the reset value.
{ "5", 5 },
{ "-358", -358 },
{ "12.34", 12.34 },
{ "5 + 3", 8 },
{ "12345 + 67 + 890", 13302 },
{ "000 + 0011", 11 },
{ "5 - 3 + 2", 4 },
{ "3 + 2 - 5", 0 },
{ "9 - 2 * 6 / 4", 6 },
{ "9 - -7", 16 },
{ "9-3*2", 3 }, // no spaces
{ " 10 * 6 ", 60 }, // extra spaces
{ "10 /( 2 + 3 )", 2 },
{ "5 * -40", -200 },
{ "(1 - 4) / (2 + 1)", -1 },
{ "3 * ((4 + 8) / 2)", 18 },
{ "23 * ((0 - 48) / 8)", -138 },
{ "((74-71)*2)^3", 216 },
{ "2 - 2 ^ 3", -6 },
{ "2 ^ 2 ^ 2 / 2 + 9", 17 },
{ "5 ^ -2", 0.04 },
{ "5.09 + 14.333", 19.423 },
{ "2.5 * 0.35", 0.875 },
{ "-2 - 5", -7 }, // begins with negative number
{ "(10)", 10 }, // number in parens
{ "(-9)", -9 }, // negative number in parens
{ "0^0", 1 }, // who knew?
// These should not parse, which means they will reset back to the previous value.
{ "5x + 3y", resetValue }, // invalid chars
{ "5 + (3", resetValue }, // mismatched parens
{ "9 + (2 + 3))", resetValue },
{ "(2 + 3)(1 + 5)", resetValue }, // missing operator
{ "9 + + 7", resetValue }, // extra operators
{ "9 - * 7", resetValue },
{ "9 - - 7", resetValue },
{ "+9", resetValue },
{ "1 / 0", resetValue }, // divide by zero
// These don't currently work, but maybe should.
{ "-(3 + 5)", resetValue }, // negative sign in front of parens -- should be -8
};
foreach (KeyValuePair<string, double> pair in expressions)
{
numBox.ClearText();
numBox.EnterText(pair.Key);
_app.PressEnter();
var value = numBox.GetDependencyPropertyValue<double>("Value");
string output = "Expression '" + pair.Key + "' - expected: " + pair.Value + ", actual: " + value;
if (Math.Abs(pair.Value - value) > 0.00001)
{
numErrors++;
Console.WriteLine(output);
}
else
{
Console.WriteLine(output);
}
}
Assert.AreEqual(0, numErrors);
}
}
}

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

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

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

@ -98,7 +98,7 @@
<Version>6.0.0</Version>
</PackageReference>
<PackageReference Include="Microsoft.UI.Xaml">
<Version>2.1.190218001-prerelease</Version>
<Version>2.1.190606001</Version>
</PackageReference>
<PackageReference Include="MSTest.TestFramework" Version="2.0.0" />
<PackageReference Include="BenchmarkDotNet" Version="0.11.4-develop" />

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

@ -0,0 +1,172 @@
<UserControl x:Class="UITests.Shared.Microsoft_UI_Xaml_Controls.NumberBoxTests.MUX_Test"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:UITests.Shared.Microsoft_UI_Xaml_Controls.NumberBoxTests"
xmlns:controls="using:Microsoft.UI.Xaml.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:contract5Present="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:xamarin="http://uno.ui/xamarin"
mc:Ignorable="d xamarin"
d:DesignHeight="300"
d:DesignWidth="400">
<xamarin:Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel Orientation="Vertical"
contract5Present:Spacing="4"
Background="{ThemeResource SystemControlBackgroundBaseLowBrush}"
Padding="12">
<ComboBox x:Name="ValidationComboBox"
AutomationProperties.Name="ValidationComboBox"
Header="Validation Mode"
SelectedIndex="0"
SelectionChanged="Validation_Changed">
<ComboBoxItem Content="InvalidInputOverwritten" />
<ComboBoxItem Content="Disabled" />
</ComboBox>
<ComboBox x:Name="SpinModeComboBox"
AutomationProperties.Name="SpinModeComboBox"
SelectedIndex="0"
SelectionChanged="SpinMode_Changed"
Header="SpinButtonMode">
<ComboBoxItem Content="Hidden" />
<ComboBoxItem Content="Compact" />
<ComboBoxItem Content="Inline" />
</ComboBox>
<CheckBox x:Name="EnabledCheckBox"
AutomationProperties.Name="EnabledCheckBox"
IsChecked="True"
Content="Enabled" />
<CheckBox x:Name="ExpressionCheckBox"
AutomationProperties.Name="ExpressionCheckBox"
IsChecked="False"
Content="Accepts Expression" />
<CheckBox x:Name="WrapCheckBox"
AutomationProperties.Name="WrapCheckBox"
IsChecked="False"
Content="Wrap Enabled" />
<!-- Set Text instead of Value to verify that it works -->
<controls:NumberBox x:Name="SmallChangeNumberBox"
AutomationProperties.Name="SmallChangeNumberBox"
Text="1"
Header="SmallChange" />
<controls:NumberBox x:Name="LargeChangeNumberBox"
AutomationProperties.Name="LargeChangeNumberBox"
Text="10"
Header="LargeChange" />
<StackPanel Orientation="Horizontal">
<CheckBox x:Name="MinCheckBox"
AutomationProperties.Name="MinCheckBox"
IsChecked="False"
Checked="MinCheckBox_CheckChanged"
Unchecked="MinCheckBox_CheckChanged"
MinWidth="32"
VerticalAlignment="Bottom" />
<controls:NumberBox x:Name="MinNumberBox"
AutomationProperties.Name="MinNumberBox"
Header="Minimum"
Value="0"
IsEnabled="False"
ValueChanged="MinValueChanged" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<CheckBox x:Name="MaxCheckBox"
AutomationProperties.Name="MaxCheckBox"
IsChecked="False"
Checked="MaxCheckBox_CheckChanged"
Unchecked="MaxCheckBox_CheckChanged"
MinWidth="32"
VerticalAlignment="Bottom" />
<!-- Verify that setting Value overrides setting Text -->
<controls:NumberBox x:Name="MaxNumberBox"
AutomationProperties.Name="MaxNumberBox"
Header="Maximum"
Value="100"
Text="50"
IsEnabled="False"
ValueChanged="MaxValueChanged" />
</StackPanel>
<Button x:Name="CustomFormatterButton"
AutomationProperties.Name="CustomFormatterButton"
Content="Custom Formatter"
Click="CustomFormatterButton_Click"
Margin="0,4,0,0" />
<Button x:Name="SetTextButton"
AutomationProperties.Name="SetTextButton"
Content="Set text to 15"
Click="SetTextButton_Click"
Margin="0,4,0,0" />
<Button x:Name="SetValueButton"
AutomationProperties.Name="SetValueButton"
Content="Set value to 42"
Click="SetValueButton_Click"
Margin="0,4,0,0" />
<Button x:Name="SetValueNaNButton"
AutomationProperties.Name="SetValueNaNButton"
Content="Set value to NaN"
Click="SetNaNButton_Click"
Margin="0,4,0,0" />
</StackPanel>
<Grid Grid.Column="1">
<StackPanel HorizontalAlignment="Center"
VerticalAlignment="Center"
contract5Present:Spacing="4">
<controls:NumberBox x:Name="TestNumberBox"
AutomationProperties.Name="TestNumberBox"
Header="NumberBox"
PlaceholderText="Text"
ValueChanged="NumberBoxValueChanged"
SmallChange="{x:Bind SmallChangeNumberBox.Value, Mode=OneWay}"
LargeChange="{x:Bind LargeChangeNumberBox.Value, Mode=OneWay}"
AcceptsExpression="{x:Bind ExpressionCheckBox.IsChecked.Value, Mode=OneWay}"
IsWrapEnabled="{x:Bind WrapCheckBox.IsChecked.Value, Mode=OneWay}"
IsEnabled="{x:Bind EnabledCheckBox.IsChecked.Value, Mode=OneWay}" />
<StackPanel Orientation="Horizontal">
<TextBlock Text="Value:"
Margin="0,0,5,0" />
<TextBlock x:Name="NewValueTextBox"
AutomationProperties.Name="NewValueTextBox"
Text="0" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Old Value:"
Margin="0,0,5,0" />
<TextBlock x:Name="OldValueTextBox"
AutomationProperties.Name="OldValueTextBox" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Text:"
Margin="0,0,5,0" />
<TextBlock x:Name="TextTextBox"
AutomationProperties.Name="TextTextBox"
Text="0" />
</StackPanel>
</StackPanel>
</Grid>
</xamarin:Grid>
</UserControl>

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

@ -0,0 +1,140 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Uno.UI.Samples.Controls;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
using Windows.Globalization.NumberFormatting;
// Condition to be removed when UWP will be updated to 18362+
#if HAS_UNO
using Microsoft.UI.Xaml.Controls;
#endif
// The User Control item template is documented at https://go.microsoft.com/fwlink/?LinkId=234236
namespace UITests.Shared.Microsoft_UI_Xaml_Controls.NumberBoxTests
{
[SampleControlInfo("NumberBox", "MUX_Test")]
public sealed partial class MUX_Test : UserControl
{
public MUX_Test()
{
this.InitializeComponent();
#if HAS_UNO
TestNumberBox.RegisterPropertyChangedCallback(NumberBox.TextProperty, new DependencyPropertyChangedCallback(TextPropertyChanged));
#endif
}
#if HAS_UNO
private void SpinMode_Changed(object sender, RoutedEventArgs e)
{
if (TestNumberBox != null)
{
if (SpinModeComboBox.SelectedIndex == 0)
{
TestNumberBox.SpinButtonPlacementMode = NumberBoxSpinButtonPlacementMode.Hidden;
}
else if (SpinModeComboBox.SelectedIndex == 1)
{
TestNumberBox.SpinButtonPlacementMode = NumberBoxSpinButtonPlacementMode.Compact;
}
else if (SpinModeComboBox.SelectedIndex == 2)
{
TestNumberBox.SpinButtonPlacementMode = NumberBoxSpinButtonPlacementMode.Inline;
}
}
}
private void Validation_Changed(object sender, RoutedEventArgs e)
{
if (TestNumberBox != null)
{
if (ValidationComboBox.SelectedIndex == 0)
{
TestNumberBox.ValidationMode = NumberBoxValidationMode.InvalidInputOverwritten;
}
else if (ValidationComboBox.SelectedIndex == 1)
{
TestNumberBox.ValidationMode = NumberBoxValidationMode.Disabled;
}
}
}
private void MinCheckBox_CheckChanged(object sender, RoutedEventArgs e)
{
MinNumberBox.IsEnabled = (bool)MinCheckBox.IsChecked;
MinValueChanged(null, null);
}
private void MaxCheckBox_CheckChanged(object sender, RoutedEventArgs e)
{
MaxNumberBox.IsEnabled = (bool)MaxCheckBox.IsChecked;
MaxValueChanged(null, null);
}
private void MinValueChanged(object sender, object e)
{
if (TestNumberBox != null)
{
TestNumberBox.Minimum = (bool)MinCheckBox.IsChecked ? MinNumberBox.Value : double.MinValue;
}
}
private void MaxValueChanged(object sender, object e)
{
if (TestNumberBox != null)
{
TestNumberBox.Maximum = (bool)MaxCheckBox.IsChecked ? MaxNumberBox.Value : double.MaxValue;
}
}
private void NumberBoxValueChanged(object sender, NumberBoxValueChangedEventArgs e)
{
if (TestNumberBox != null && NewValueTextBox != null && OldValueTextBox != null)
{
NewValueTextBox.Text = e.NewValue.ToString();
OldValueTextBox.Text = e.OldValue.ToString();
}
}
private void CustomFormatterButton_Click(object sender, RoutedEventArgs e)
{
List<string> languages = new List<string>() { "fr-FR" };
DecimalFormatter formatter = new DecimalFormatter(languages, "FR");
formatter.IntegerDigits = 1;
formatter.FractionDigits = 2;
TestNumberBox.NumberFormatter = formatter;
}
private void SetTextButton_Click(object sender, RoutedEventArgs e)
{
TestNumberBox.Text = "15";
}
private void SetValueButton_Click(object sender, RoutedEventArgs e)
{
TestNumberBox.Value = 42;
}
private void SetNaNButton_Click(object sender, RoutedEventArgs e)
{
TestNumberBox.Value = Double.NaN;
}
private void TextPropertyChanged(DependencyObject o, DependencyProperty p)
{
TextTextBox.Text = TestNumberBox.Text;
}
#endif
}
}

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

@ -13,6 +13,10 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="$(MSBuildThisFileDirectory)Microsoft_UI_Xaml_Controls\NumberBoxTests\MUX_Test.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="$(MSBuildThisFileDirectory)Resources\StaticResource\StaticResource_Simple.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
@ -125,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>
@ -137,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>
@ -661,6 +677,10 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml_Controls\RepeatButton\RepeatButton_Automated.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml_Controls\ScrollViewerTests\ScrollViewer_Simple.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
@ -2890,6 +2910,9 @@
<Compile Include="$(MSBuildThisFileDirectory)Helpers\BindableBase.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Helpers\UWPViewHelper.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Helpers\ViewModelBase.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Microsoft_UI_Xaml_Controls\NumberBoxTests\MUX_Test.xaml.cs">
<DependentUpon>MUX_Test.xaml</DependentUpon>
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)Toolkit\Elevation.xaml.cs">
<DependentUpon>Elevation.xaml</DependentUpon>
</Compile>
@ -2960,6 +2983,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>
@ -2969,6 +2998,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>
@ -3246,6 +3278,9 @@
<Compile Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml_Controls\Popup\Popup_Overlay_On.xaml.cs">
<DependentUpon>Popup_Overlay_On.xaml</DependentUpon>
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml_Controls\RepeatButton\RepeatButton_Automated.xaml.cs">
<DependentUpon>RepeatButton_Automated.xaml</DependentUpon>
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml_Controls\TextBlockControl\TextBlock_Foreground_While_Collapsed.xaml.cs">
<DependentUpon>TextBlock_Foreground_While_Collapsed.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);

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

@ -0,0 +1,20 @@
<UserControl x:Class="UITests.Shared.Windows_UI_Xaml_Controls.RepeatButton_Automated"
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_Xaml_Controls.RepeatButton"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<Grid>
<StackPanel>
<RepeatButton x:Uid="repeat01"
Click="OnClicked"
Content="Click me" />
<TextBlock x:Name="counter"
Text="0" />
</StackPanel>
</Grid>
</UserControl>

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

@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Uno.UI.Samples.Controls;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
// The User Control item template is documented at https://go.microsoft.com/fwlink/?LinkId=234236
namespace UITests.Shared.Windows_UI_Xaml_Controls
{
[SampleControlInfo("RepeatButton")]
public sealed partial class RepeatButton_Automated : UserControl
{
private int clickCount;
public RepeatButton_Automated()
{
this.InitializeComponent();
}
private void OnClicked(object sender, object args)
{
clickCount++;
counter.Text = clickCount.ToString();
}
}
}

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

@ -20,13 +20,15 @@ namespace Windows.Foundation.Metadata
"Uno"
};
private static bool IsImplementedByUno(MemberInfo member) => (member?.GetCustomAttributes(typeof(Uno.NotImplementedAttribute), false)?.Length ?? -1) == 0;
public static bool IsTypePresent(string typeName)
{
lock (_gate)
{
if (!_isTypePresent.TryGetValue(typeName, out var result))
{
_isTypePresent[typeName] = result = GetValidType(typeName)?.GetCustomAttributes(typeof(Uno.NotImplementedAttribute), false)?.Length == 0;
_isTypePresent[typeName] = result = IsImplementedByUno(GetValidType(typeName));
}
return result;
@ -43,19 +45,21 @@ namespace Windows.Foundation.Metadata
.Any() ?? false;
public static bool IsEventPresent(string typeName, string eventName)
=> GetValidType(typeName)
?.GetEvent(eventName) != null;
=> IsImplementedByUno(
GetValidType(typeName)
?.GetEvent(eventName));
public static bool IsPropertyPresent(string typeName, string propertyName)
=> GetValidType(typeName)
?.GetProperty(propertyName) != null;
=> IsImplementedByUno(
GetValidType(typeName)
?.GetProperty(propertyName));
public static bool IsReadOnlyPropertyPresent(string typeName, string propertyName)
{
var property = GetValidType(typeName)
?.GetProperty(propertyName);
if(property != null)
if(IsImplementedByUno(property))
{
return property.GetMethod != null && property.SetMethod == null;
}
@ -68,7 +72,7 @@ namespace Windows.Foundation.Metadata
var property = GetValidType(typeName)
?.GetProperty(propertyName);
if (property != null)
if (IsImplementedByUno(property))
{
return property.GetMethod != null && property.SetMethod != null;
}

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

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

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

@ -157,6 +157,9 @@
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
<Generator>ResXFileCodeGenerator</Generator>
</None>
<None Update="Windows_UI_Xaml_Data\xBindTests\Controls\Binding_Nullable.xaml">
<Generator>MSBuild:Compile</Generator>
</None>
<None Update="Windows_UI_Xaml_Data\xBindTests\Controls\Binding_Control.xaml">
<Generator>MSBuild:Compile</Generator>
</None>

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

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

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

@ -0,0 +1,17 @@
<Page x:Class="Uno.UI.Tests.Windows_UI_Xaml_Data.xBindTests.Controls.Binding_Nullable"
xmlns:sys="using:System"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Uno.UI.Tests.Windows_UI_Xaml_Data.xBindTests"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid>
<CheckBox x:Name="myCheckBox"
x:FieldModifier="public" />
<TextBlock x:Name="_NullableBinding"
x:FieldModifier="public"
Text="{x:Bind myCheckBox.IsChecked.Value, Mode=OneWay}" />
</Grid>
</Page>

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

@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238
namespace Uno.UI.Tests.Windows_UI_Xaml_Data.xBindTests.Controls
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class Binding_Nullable : Page
{
public Binding_Nullable()
{
this.InitializeComponent();
}
}
}

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

@ -159,5 +159,56 @@ namespace Uno.UI.Tests.Windows_UI_Xaml_Data.xBindTests
Assert.AreEqual(4, SUT.AddIntCallCount);
Assert.AreEqual(4, SUT.OffsetCallCount);
}
[TestMethod]
public void When_NullableProperty()
{
var SUT = new Binding_Nullable();
Assert.IsNull(SUT._NullableBinding.Text);
SUT.ForceLoaded();
Assert.AreEqual("False", SUT._NullableBinding.Text);
SUT.myCheckBox.IsChecked = true;
Assert.AreEqual("True", SUT._NullableBinding.Text);
SUT.myCheckBox.IsChecked = false;
Assert.AreEqual("False", SUT._NullableBinding.Text);
SUT.myCheckBox.IsChecked = null;
Assert.AreEqual("False", SUT._NullableBinding.Text);
}
[TestMethod]
public void When_NullableProperty_And_ThreeState()
{
var SUT = new Binding_Nullable();
SUT.myCheckBox.IsThreeState = true;
SUT.myCheckBox.IsChecked = null;
Assert.IsNull(SUT._NullableBinding.Text);
SUT.ForceLoaded();
Assert.IsNull(SUT._NullableBinding.Text);
SUT.myCheckBox.IsChecked = true;
Assert.AreEqual("True", SUT._NullableBinding.Text);
SUT.myCheckBox.IsChecked = false;
Assert.AreEqual("False", SUT._NullableBinding.Text);
SUT.myCheckBox.IsChecked = null;
Assert.AreEqual("False", SUT._NullableBinding.Text);
}
}
}

15
src/Uno.UI.Wasm/WasmScripts/Uno.UI.d.ts поставляемый
Просмотреть файл

@ -254,7 +254,7 @@ declare namespace Uno.UI {
*/
setStyle(elementId: number, styles: {
[name: string]: string;
}, setAsArranged?: boolean, clipToBounds?: boolean): string;
}): string;
/**
* Set the CSS style of a html element.
*
@ -267,18 +267,13 @@ declare namespace Uno.UI {
*
*/
setStyleDoubleNative(pParams: number): boolean;
setArrangeProperties(elementId: number, clipToBounds: boolean): string;
/**
* Set the CSS style of a html element.
*
* To remove a value, set it to empty string.
* @param styles A dictionary of styles to apply on html element.
* Remove the CSS style of a html element.
*/
resetStyle(elementId: number, names: string[]): string;
/**
* Set the CSS style of a html element.
*
* To remove a value, set it to empty string.
* @param styles A dictionary of styles to apply on html element.
* Remove the CSS style of a html element.
*/
resetStyleNative(pParams: number): boolean;
private resetStyleInternal;
@ -695,10 +690,8 @@ declare class WindowManagerSetStyleDoubleParams {
}
declare class WindowManagerSetStylesParams {
HtmlId: number;
SetAsArranged: boolean;
Pairs_Length: number;
Pairs: Array<string>;
ClipToBounds: boolean;
static unmarshal(pData: number): WindowManagerSetStylesParams;
}
declare class WindowManagerSetXUidParams {

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

@ -641,19 +641,13 @@ var Uno;
* To remove a value, set it to empty string.
* @param styles A dictionary of styles to apply on html element.
*/
setStyle(elementId, styles, setAsArranged = false, clipToBounds) {
setStyle(elementId, styles) {
const element = this.getView(elementId);
for (const style in styles) {
if (styles.hasOwnProperty(style)) {
element.style.setProperty(style, styles[style]);
}
}
if (setAsArranged) {
this.setAsArranged(element);
}
if (typeof clipToBounds === "boolean") {
this.setClipToBounds(element, clipToBounds);
}
return "ok";
}
/**
@ -672,10 +666,6 @@ var Uno;
const value = pairs[i + 1];
elementStyle.setProperty(key, value);
}
if (params.SetAsArranged) {
this.setAsArranged(element);
}
this.setClipToBounds(element, params.ClipToBounds);
return true;
}
/**
@ -688,21 +678,21 @@ var Uno;
element.style.setProperty(params.Name, String(params.Value));
return true;
}
setArrangeProperties(elementId, clipToBounds) {
const element = this.getView(elementId);
this.setAsArranged(element);
this.setClipToBounds(element, clipToBounds);
return "ok";
}
/**
* Set the CSS style of a html element.
*
* To remove a value, set it to empty string.
* @param styles A dictionary of styles to apply on html element.
* Remove the CSS style of a html element.
*/
resetStyle(elementId, names) {
this.resetStyleInternal(elementId, names);
return "ok";
}
/**
* Set the CSS style of a html element.
*
* To remove a value, set it to empty string.
* @param styles A dictionary of styles to apply on html element.
* Remove the CSS style of a html element.
*/
resetStyleNative(pParams) {
const params = WindowManagerResetStyleParams.unmarshal(pParams);
@ -781,7 +771,7 @@ var Uno;
const element = this.getView(params.HtmlId);
var style = element.style;
style.transform = `matrix(${params.M11},${params.M12},${params.M21},${params.M22},${params.M31},${params.M32})`;
element.classList.remove(WindowManager.unoUnarrangedClassName);
this.setAsArranged(element);
return true;
}
/**
@ -1000,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
@ -2214,13 +2204,10 @@ class WindowManagerSetStylesParams {
ret.HtmlId = Number(Module.getValue(pData + 0, "*"));
}
{
ret.SetAsArranged = Boolean(Module.getValue(pData + 4, "i32"));
ret.Pairs_Length = Number(Module.getValue(pData + 4, "i32"));
}
{
ret.Pairs_Length = Number(Module.getValue(pData + 8, "i32"));
}
{
var pArray = Module.getValue(pData + 12, "*");
var pArray = Module.getValue(pData + 8, "*");
if (pArray !== 0) {
ret.Pairs = new Array();
for (var i = 0; i < ret.Pairs_Length; i++) {
@ -2237,9 +2224,6 @@ class WindowManagerSetStylesParams {
ret.Pairs = null;
}
}
{
ret.ClipToBounds = Boolean(Module.getValue(pData + 16, "i32"));
}
return ret;
}
}

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

@ -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}`;
}
/**

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

@ -3,10 +3,8 @@ class WindowManagerSetStylesParams
{
/* Pack=4 */
HtmlId : number;
SetAsArranged : boolean;
Pairs_Length : number;
Pairs : Array<string>;
ClipToBounds : boolean;
public static unmarshal(pData:number) : WindowManagerSetStylesParams
{
let ret = new WindowManagerSetStylesParams();
@ -16,15 +14,11 @@ class WindowManagerSetStylesParams
}
{
ret.SetAsArranged = Boolean(Module.getValue(pData + 4, "i32"));
ret.Pairs_Length = Number(Module.getValue(pData + 4, "i32"));
}
{
ret.Pairs_Length = Number(Module.getValue(pData + 8, "i32"));
}
{
var pArray = Module.getValue(pData + 12, "*");
var pArray = Module.getValue(pData + 8, "*");
if(pArray !== 0)
{
ret.Pairs = new Array<string>();
@ -48,10 +42,6 @@ class WindowManagerSetStylesParams
ret.Pairs = null;
}
}
{
ret.ClipToBounds = Boolean(Module.getValue(pData + 16, "i32"));
}
return ret;
}
}

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

@ -306,6 +306,14 @@ namespace Uno.UI.DataBinding
return attachedPropertyGetter.ReturnType;
}
if(type.IsPrimitive && property == "Value")
{
// This case is trying assuming that Value for a primitive is used for the case
// of a Nullable primitive.
return type;
}
_log.ErrorFormat("The [{0}] property getter does not exist on type [{1}]", property, type);
return null;
}

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

@ -7,59 +7,6 @@ namespace Windows.UI.Xaml.Controls.Primitives
#endif
public partial class ButtonBase : global::Windows.UI.Xaml.Controls.ContentControl
{
// Skipping already declared property CommandParameter
// Skipping already declared property Command
#if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __MACOS__
[global::Uno.NotImplemented]
public global::Windows.UI.Xaml.Controls.ClickMode ClickMode
{
get
{
return (global::Windows.UI.Xaml.Controls.ClickMode)this.GetValue(ClickModeProperty);
}
set
{
this.SetValue(ClickModeProperty, value);
}
}
#endif
// Skipping already declared property IsPointerOver
#if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __MACOS__
[global::Uno.NotImplemented]
public bool IsPressed
{
get
{
return (bool)this.GetValue(IsPressedProperty);
}
}
#endif
#if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __MACOS__
[global::Uno.NotImplemented]
public static global::Windows.UI.Xaml.DependencyProperty ClickModeProperty { get; } =
Windows.UI.Xaml.DependencyProperty.Register(
"ClickMode", typeof(global::Windows.UI.Xaml.Controls.ClickMode),
typeof(global::Windows.UI.Xaml.Controls.Primitives.ButtonBase),
new FrameworkPropertyMetadata(default(global::Windows.UI.Xaml.Controls.ClickMode)));
#endif
// Skipping already declared property CommandParameterProperty
// Skipping already declared property CommandProperty
#if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __MACOS__
[global::Uno.NotImplemented]
public static global::Windows.UI.Xaml.DependencyProperty IsPointerOverProperty { get; } =
Windows.UI.Xaml.DependencyProperty.Register(
"IsPointerOver", typeof(bool),
typeof(global::Windows.UI.Xaml.Controls.Primitives.ButtonBase),
new FrameworkPropertyMetadata(default(bool)));
#endif
#if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __MACOS__
[global::Uno.NotImplemented]
public static global::Windows.UI.Xaml.DependencyProperty IsPressedProperty { get; } =
Windows.UI.Xaml.DependencyProperty.Register(
"IsPressed", typeof(bool),
typeof(global::Windows.UI.Xaml.Controls.Primitives.ButtonBase),
new FrameworkPropertyMetadata(default(bool)));
#endif
// Skipping already declared method Windows.UI.Xaml.Controls.Primitives.ButtonBase.ButtonBase()
// Forced skipping of method Windows.UI.Xaml.Controls.Primitives.ButtonBase.ButtonBase()
// Forced skipping of method Windows.UI.Xaml.Controls.Primitives.ButtonBase.ClickMode.get

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

@ -2,68 +2,4 @@
#pragma warning disable 114 // new keyword hiding
namespace Windows.UI.Xaml.Controls.Primitives
{
#if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __MACOS__
[global::Uno.NotImplemented]
#endif
public partial class RepeatButton : global::Windows.UI.Xaml.Controls.Primitives.ButtonBase
{
#if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __MACOS__
[global::Uno.NotImplemented]
public int Interval
{
get
{
return (int)this.GetValue(IntervalProperty);
}
set
{
this.SetValue(IntervalProperty, value);
}
}
#endif
#if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __MACOS__
[global::Uno.NotImplemented]
public int Delay
{
get
{
return (int)this.GetValue(DelayProperty);
}
set
{
this.SetValue(DelayProperty, value);
}
}
#endif
#if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __MACOS__
[global::Uno.NotImplemented]
public static global::Windows.UI.Xaml.DependencyProperty DelayProperty { get; } =
Windows.UI.Xaml.DependencyProperty.Register(
"Delay", typeof(int),
typeof(global::Windows.UI.Xaml.Controls.Primitives.RepeatButton),
new FrameworkPropertyMetadata(default(int)));
#endif
#if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __MACOS__
[global::Uno.NotImplemented]
public static global::Windows.UI.Xaml.DependencyProperty IntervalProperty { get; } =
Windows.UI.Xaml.DependencyProperty.Register(
"Interval", typeof(int),
typeof(global::Windows.UI.Xaml.Controls.Primitives.RepeatButton),
new FrameworkPropertyMetadata(default(int)));
#endif
#if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __MACOS__
[global::Uno.NotImplemented]
public RepeatButton() : base()
{
global::Windows.Foundation.Metadata.ApiInformation.TryRaiseNotImplemented("Windows.UI.Xaml.Controls.Primitives.RepeatButton", "RepeatButton.RepeatButton()");
}
#endif
// Forced skipping of method Windows.UI.Xaml.Controls.Primitives.RepeatButton.RepeatButton()
// Forced skipping of method Windows.UI.Xaml.Controls.Primitives.RepeatButton.Delay.get
// Forced skipping of method Windows.UI.Xaml.Controls.Primitives.RepeatButton.Delay.set
// Forced skipping of method Windows.UI.Xaml.Controls.Primitives.RepeatButton.Interval.get
// Forced skipping of method Windows.UI.Xaml.Controls.Primitives.RepeatButton.Interval.set
// Forced skipping of method Windows.UI.Xaml.Controls.Primitives.RepeatButton.DelayProperty.get
// Forced skipping of method Windows.UI.Xaml.Controls.Primitives.RepeatButton.IntervalProperty.get
}
}

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

@ -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)
{

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

@ -7,7 +7,7 @@ namespace Windows.UI.Xaml.Input
#endif
public partial class KeyRoutedEventArgs : global::Windows.UI.Xaml.RoutedEventArgs
{
#if false || false || NET461 || false || false
#if false || false || false || false || false
[global::Uno.NotImplemented]
public bool Handled
{
@ -21,7 +21,7 @@ namespace Windows.UI.Xaml.Input
}
}
#endif
#if false || false || NET461 || false || false
#if false || false || false || false || false
[global::Uno.NotImplemented]
public global::Windows.System.VirtualKey Key
{
@ -43,16 +43,6 @@ namespace Windows.UI.Xaml.Input
#endif
#if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __MACOS__
[global::Uno.NotImplemented]
public global::Windows.System.VirtualKey OriginalKey
{
get
{
throw new global::System.NotImplementedException("The member VirtualKey KeyRoutedEventArgs.OriginalKey is not implemented in Uno.");
}
}
#endif
#if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __MACOS__
[global::Uno.NotImplemented]
public string DeviceId
{
get

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

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

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

@ -308,7 +308,7 @@ namespace Uno.UI.Helpers.WinUI
}
static bool? s_isThemeShadowAvailable;
bool IsThemeShadowAvailable()
static public bool IsThemeShadowAvailable()
{
if (s_isThemeShadowAvailable == null)
{

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

@ -0,0 +1,25 @@
namespace Microsoft.UI.Xaml.Controls
{
internal struct MathToken
{
public MathToken(MathTokenType t, char c)
{
Type = t;
Char = c;
Value = double.NaN;
}
public MathToken(MathTokenType t, double d)
{
Type = t;
Char = '\0';
Value = d;
}
public MathTokenType Type { get; }
public char Char { get; }
public double Value { get; }
}
}

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

@ -0,0 +1,9 @@
namespace Microsoft.UI.Xaml.Controls
{
internal enum MathTokenType
{
Numeric,
Operator,
Parenthesis,
}
}

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

@ -0,0 +1,205 @@
#pragma warning disable 109
using System;
using System.Collections.Generic;
using System.Text;
using Uno.UI.Helpers.WinUI;
using Windows.Globalization.NumberFormatting;
using Windows.System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Automation;
using Windows.UI.Xaml.Automation.Peers;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
namespace Microsoft.UI.Xaml.Controls
{
public partial class NumberBox : Control
{
public double Minimum
{
get => (double)GetValue(MinimumProperty);
set => SetValue(MinimumProperty, value);
}
public static readonly DependencyProperty MinimumProperty =
DependencyProperty.Register(
name: nameof(Minimum),
propertyType: typeof(double),
ownerType: typeof(NumberBox),
typeMetadata: new PropertyMetadata(
defaultValue: -double.MaxValue,
propertyChangedCallback: (s, e) => (s as NumberBox)?.OnMinimumPropertyChanged(e)));
public double Maximum
{
get => (double)GetValue(MaximumProperty);
set => SetValue(MaximumProperty, value);
}
public static readonly DependencyProperty MaximumProperty =
DependencyProperty.Register("Maximum", typeof(double), typeof(NumberBox), new PropertyMetadata(double.MaxValue, (s, e) => (s as NumberBox)?.OnMaximumPropertyChanged(e)));
public double Value
{
get => (double)GetValue(ValueProperty);
set => SetValue(ValueProperty, value);
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(double), typeof(NumberBox), new PropertyMetadata(0.0, (s, e) => (s as NumberBox)?.OnValuePropertyChanged(e)));
public double SmallChange
{
get => (double)GetValue(SmallChangeProperty);
set => SetValue(SmallChangeProperty, value);
}
public static readonly DependencyProperty SmallChangeProperty =
DependencyProperty.Register("SmallChange", typeof(double), typeof(NumberBox), new PropertyMetadata(1.0, (s, e) => (s as NumberBox)?.OnSmallChangePropertyChanged(e)));
public double LargeChange
{
get => (double)GetValue(LargeChangeProperty);
set => SetValue(LargeChangeProperty, value);
}
public static readonly DependencyProperty LargeChangeProperty =
DependencyProperty.Register("LargeChange", typeof(double), typeof(NumberBox), new PropertyMetadata(10.0));
public string Text
{
get => (string)GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(NumberBox), new PropertyMetadata("", (s, e) => (s as NumberBox)?.OnTextPropertyChanged(e)));
// TextBox properties
public object Header
{
get => (object)GetValue(HeaderProperty);
set => SetValue(HeaderProperty, value);
}
public static readonly DependencyProperty HeaderProperty =
DependencyProperty.Register("Header", typeof(object), typeof(NumberBox), new PropertyMetadata(null));
public DataTemplate HeaderTemplate
{
get => (DataTemplate)GetValue(HeaderTemplateProperty);
set => SetValue(HeaderTemplateProperty, value);
}
public static readonly DependencyProperty HeaderTemplateProperty =
DependencyProperty.Register("HeaderTemplate", typeof(DataTemplate), typeof(NumberBox), new PropertyMetadata(null));
public string PlaceholderText
{
get => (string)GetValue(PlaceholderTextProperty);
set => SetValue(PlaceholderTextProperty, value);
}
public static readonly DependencyProperty PlaceholderTextProperty =
DependencyProperty.Register("PlaceholderText", typeof(string), typeof(NumberBox), new PropertyMetadata(null));
public FlyoutBase SelectionFlyout
{
get => (FlyoutBase)GetValue(SelectionFlyoutProperty);
set => SetValue(SelectionFlyoutProperty, value);
}
public static readonly DependencyProperty SelectionFlyoutProperty =
DependencyProperty.Register("SelectionFlyout", typeof(FlyoutBase), typeof(NumberBox), new PropertyMetadata(null));
public SolidColorBrush SelectionHighlightColor
{
get => (SolidColorBrush)GetValue(SelectionHighlightColorProperty);
set => SetValue(SelectionHighlightColorProperty, value);
}
public static readonly DependencyProperty SelectionHighlightColorProperty =
DependencyProperty.Register("SelectionHighlightColor", typeof(SolidColorBrush), typeof(NumberBox), new PropertyMetadata(null));
public TextReadingOrder TextReadingOrder
{
get => (TextReadingOrder)GetValue(TextReadingOrderProperty);
set => SetValue(TextReadingOrderProperty, value);
}
public static readonly DependencyProperty TextReadingOrderProperty =
DependencyProperty.Register("TextReadingOrder", typeof(TextReadingOrder), typeof(NumberBox), new PropertyMetadata(null));
public bool PreventKeyboardDisplayOnProgrammaticFocus
{
get => (bool)GetValue(PreventKeyboardDisplayOnProgrammaticFocusProperty);
set => SetValue(PreventKeyboardDisplayOnProgrammaticFocusProperty, value);
}
public static readonly DependencyProperty PreventKeyboardDisplayOnProgrammaticFocusProperty =
DependencyProperty.Register("PreventKeyboardDisplayOnProgrammaticFocus", typeof(bool), typeof(NumberBox), new PropertyMetadata(null));
public new object Description
{
get => (object)GetValue(DescriptionProperty);
set => SetValue(DescriptionProperty, value);
}
public static readonly DependencyProperty DescriptionProperty =
DependencyProperty.Register("Description", typeof(object), typeof(NumberBox), new PropertyMetadata(null));
public NumberBoxValidationMode ValidationMode
{
get => (NumberBoxValidationMode)GetValue(ValidationModeProperty);
set => SetValue(ValidationModeProperty, value);
}
public static readonly DependencyProperty ValidationModeProperty =
DependencyProperty.Register("ValidationMode", typeof(NumberBoxValidationMode), typeof(NumberBox), new PropertyMetadata(NumberBoxValidationMode.InvalidInputOverwritten, (s, e) => (s as NumberBox)?.OnValidationModePropertyChanged(e)));
public NumberBoxSpinButtonPlacementMode SpinButtonPlacementMode
{
get => (NumberBoxSpinButtonPlacementMode)GetValue(SpinButtonPlacementModeProperty);
set => SetValue(SpinButtonPlacementModeProperty, value);
}
public static readonly DependencyProperty SpinButtonPlacementModeProperty =
DependencyProperty.Register("SpinButtonPlacementMode", typeof(NumberBoxSpinButtonPlacementMode), typeof(NumberBox), new PropertyMetadata(NumberBoxSpinButtonPlacementMode.Hidden, (s, e) => (s as NumberBox)?.OnSpinButtonPlacementModePropertyChanged(e)));
public bool IsWrapEnabled
{
get => (bool)GetValue(IsWrapEnabledProperty);
set => SetValue(IsWrapEnabledProperty, value);
}
public static readonly DependencyProperty IsWrapEnabledProperty =
DependencyProperty.Register("IsWrapEnabled", typeof(bool), typeof(NumberBox), new PropertyMetadata(false, (s, e) => (s as NumberBox)?.OnIsWrapEnabledPropertyChanged(e)));
public bool AcceptsExpression
{
get => (bool)GetValue(AcceptsExpressionProperty);
set => SetValue(AcceptsExpressionProperty, value);
}
public static readonly DependencyProperty AcceptsExpressionProperty =
DependencyProperty.Register("AcceptsExpression", typeof(bool), typeof(NumberBox), new PropertyMetadata(false /* ,UNO TODO (s, e) => (s as NumberBox)?.OnAcceptsExpressionPropertyChanged(e)*/));
public INumberFormatter2 NumberFormatter
{
get => (INumberFormatter2)GetValue(NumberFormatterProperty);
set => SetValue(NumberFormatterProperty, value);
}
public static readonly DependencyProperty NumberFormatterProperty =
DependencyProperty.Register("NumberFormatter", typeof(INumberFormatter2), typeof(NumberBox), new PropertyMetadata(null, (s, e) => (s as NumberBox)?.OnNumberFormatterPropertyChanged(e)));
public event Windows.Foundation.TypedEventHandler<NumberBox, NumberBoxValueChangedEventArgs> ValueChanged;
}
}

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

@ -0,0 +1,602 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using Uno.UI.Helpers.WinUI;
using Windows.Globalization.NumberFormatting;
using Windows.System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Automation;
using Windows.UI.Xaml.Automation.Peers;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.Foundation.Metadata;
using System.Globalization;
using Uno.Disposables;
namespace Microsoft.UI.Xaml.Controls
{
public partial class NumberBox : Control
{
bool m_valueUpdating = false;
bool m_textUpdating = false;
SignificantDigitsNumberRounder m_displayRounder = new SignificantDigitsNumberRounder();
TextBox m_textBox;
Windows.UI.Xaml.Controls.Primitives.Popup m_popup;
SerialDisposable _eventSubscriptions = new SerialDisposable();
static string c_numberBoxDownButtonName = "DownSpinButton";
static string c_numberBoxUpButtonName = "UpSpinButton";
static string c_numberBoxTextBoxName = "InputBox";
// UNO TODO static string c_numberBoxPopupButtonName= "PopupButton";
static string c_numberBoxPopupName = "UpDownPopup";
static string c_numberBoxPopupDownButtonName = "PopupDownSpinButton";
static string c_numberBoxPopupUpButtonName = "PopupUpSpinButton";
// UNO TODO static string c_numberBoxPopupContentRootName= "PopupContentRoot";
// UNO TODO static double c_popupShadowDepth = 16.0;
// UNO TODO static string c_numberBoxPopupShadowDepthName= "NumberBoxPopupShadowDepth";
// Shockingly, there is no standard function for trimming strings.
const string c_whitespace = " \n\r\t\f\v";
private static string trim(string s)
{
IEnumerable<(char c, int i)> GetNonWhiteSpace()
=> s.Select((c, i) => (c, i)).Where(p => !c_whitespace.Contains(p.c.ToString()));
var start = GetNonWhiteSpace().FirstOrDefault();
var end = GetNonWhiteSpace().LastOrDefault();
return (start.c == '\0' || end.c == '\0') ? "" : s.Substring(start.i, end.i - start.i + 1);
}
public NumberBox()
{
// Default values for the number formatter
var formatter = new DecimalFormatter();
formatter.IntegerDigits = 1;
formatter.FractionDigits = 0;
NumberFormatter = formatter;
PointerWheelChanged += OnNumberBoxScroll;
GotFocus += OnNumberBoxGotFocus;
LostFocus += OnNumberBoxLostFocus;
Loaded += (s, e) => InitializeTemplate();
Unloaded += (s, e) => DisposeRegistrations();
DefaultStyleKey = this;
}
protected override AutomationPeer OnCreateAutomationPeer()
{
return new NumberBoxAutomationPeer(this);
}
protected override void OnApplyTemplate()
{
InitializeTemplate();
}
private void InitializeTemplate()
{
_eventSubscriptions.Disposable = null;
var registrations = new CompositeDisposable();
var spinDownName = ResourceAccessor.GetLocalizedStringResource("NumberBoxDownSpinButtonName");
var spinUpName = ResourceAccessor.GetLocalizedStringResource("NumberBoxUpSpinButtonName");
if (this.GetTemplateChild(c_numberBoxDownButtonName) is RepeatButton spinDown)
{
spinDown.Click += OnSpinDownClick;
registrations.Add(() => spinDown.Click -= OnSpinDownClick);
// Do localization for the down button
if (string.IsNullOrEmpty(AutomationProperties.GetName(spinDown)))
{
AutomationProperties.SetName(spinDown, spinDownName);
}
}
if (GetTemplateChild(c_numberBoxUpButtonName) is RepeatButton spinUp)
{
spinUp.Click += OnSpinUpClick;
registrations.Add(() => spinUp.Click -= OnSpinUpClick);
// Do localization for the up button
if (string.IsNullOrEmpty(AutomationProperties.GetName(spinUp)))
{
AutomationProperties.SetName(spinUp, spinUpName);
}
}
if (GetTemplateChild(c_numberBoxTextBoxName) is TextBox textBox)
{
textBox.KeyDown += OnNumberBoxKeyDown;
registrations.Add(() => textBox.KeyDown -= OnNumberBoxKeyDown);
textBox.KeyUp += OnNumberBoxKeyUp;
registrations.Add(() => textBox.KeyUp -= OnNumberBoxKeyUp);
m_textBox = textBox;
}
m_popup = GetTemplateChild(c_numberBoxPopupName) as Windows.UI.Xaml.Controls.Primitives.Popup;
if (SharedHelpers.IsThemeShadowAvailable())
{
// UNO TODO
//if (GetTemplateChildT(c_numberBoxPopupContentRootName) is UIElement popupRoot)
//{
// if (!popupRoot.Shadow())
// {
// popupRoot.Shadow(ThemeShadow{});
// auto&& translation = popupRoot.Translation();
// const double shadowDepth = unbox_value<double>(SharedHelpers.FindResource(c_numberBoxPopupShadowDepthName, Application.Current().Resources(), box_value(c_popupShadowDepth)));
// popupRoot.Translation({ translation.x, translation.y, (float)shadowDepth });
// }
//}
}
if (GetTemplateChild(c_numberBoxPopupDownButtonName) is RepeatButton popupSpinDown)
{
popupSpinDown.Click += OnSpinDownClick;
registrations.Add(() => popupSpinDown.Click -= OnSpinDownClick);
}
if (GetTemplateChild(c_numberBoxPopupUpButtonName) is RepeatButton popupSpinUp)
{
popupSpinUp.Click += OnSpinUpClick;
registrations.Add(() => popupSpinUp.Click -= OnSpinUpClick);
}
// .NET rounds to 12 significant digits when displaying doubles, so we will do the same.
m_displayRounder.SignificantDigits = 12;
UpdateSpinButtonPlacement();
UpdateSpinButtonEnabled();
if (ReadLocalValue(ValueProperty) == DependencyProperty.UnsetValue
&& ReadLocalValue(TextProperty) != DependencyProperty.UnsetValue)
{
// If Text has been set, but Value hasn't, update Value based on Text.
UpdateValueToText();
}
else
{
UpdateTextToValue();
}
_eventSubscriptions.Disposable = registrations;
}
private void DisposeRegistrations()
{
_eventSubscriptions.Disposable = null;
}
private void OnValuePropertyChanged(DependencyPropertyChangedEventArgs args)
{
// This handler may change Value; don't send extra events in that case.
if (!m_valueUpdating)
{
var oldValue = (double)args.OldValue;
try
{
m_valueUpdating = true;
CoerceValue();
var newValue = (double)Value;
if (newValue != oldValue && !(double.IsNaN(newValue) && double.IsNaN(oldValue)))
{
// Fire ValueChanged event
var valueChangedArgs = new NumberBoxValueChangedEventArgs(oldValue, newValue);
ValueChanged?.Invoke(this, valueChangedArgs);
// Fire value property change for UIA
if (FrameworkElementAutomationPeer.FromElement(this) is NumberBoxAutomationPeer peer)
{
peer.RaiseValueChangedEvent(oldValue, newValue);
}
}
UpdateTextToValue();
UpdateSpinButtonEnabled();
}
finally
{
m_valueUpdating = false;
}
}
}
private void OnMinimumPropertyChanged(DependencyPropertyChangedEventArgs args)
{
CoerceMaximum();
CoerceValue();
UpdateSpinButtonEnabled();
}
private void OnMaximumPropertyChanged(DependencyPropertyChangedEventArgs args)
{
CoerceMinimum();
CoerceValue();
UpdateSpinButtonEnabled();
}
private void OnSmallChangePropertyChanged(DependencyPropertyChangedEventArgs args)
{
UpdateSpinButtonEnabled();
}
private void OnIsWrapEnabledPropertyChanged(DependencyPropertyChangedEventArgs args)
{
UpdateSpinButtonEnabled();
}
private void OnNumberFormatterPropertyChanged(DependencyPropertyChangedEventArgs args)
{
// Update text with new formatting
UpdateTextToValue();
}
private void ValidateNumberFormatter(INumberFormatter2 value)
{
// NumberFormatter also needs to be an INumberParser
if (!(value is INumberParser))
{
throw new ArgumentException(nameof(value));
}
}
private void OnSpinButtonPlacementModePropertyChanged(DependencyPropertyChangedEventArgs args)
{
UpdateSpinButtonPlacement();
}
private void OnTextPropertyChanged(DependencyPropertyChangedEventArgs args)
{
if (!m_textUpdating)
{
UpdateValueToText();
}
}
private void UpdateValueToText()
{
if (m_textBox != null)
{
m_textBox.Text = Text;
ValidateInput();
}
}
private void OnValidationModePropertyChanged(DependencyPropertyChangedEventArgs args)
{
ValidateInput();
UpdateSpinButtonEnabled();
}
private void OnNumberBoxGotFocus(object sender, RoutedEventArgs args)
{
// When the control receives focus, select the text
if (m_textBox != null)
{
m_textBox.SelectAll();
}
if (SpinButtonPlacementMode == NumberBoxSpinButtonPlacementMode.Compact)
{
if (m_popup != null)
{
m_popup.IsOpen = true;
}
}
}
private void OnNumberBoxLostFocus(object sender, RoutedEventArgs args)
{
ValidateInput();
if (m_popup != null)
{
m_popup.IsOpen = false;
}
}
private void CoerceMinimum()
{
var max = Maximum;
if (Minimum > max)
{
Minimum = max;
}
}
private void CoerceMaximum()
{
var min = Minimum;
if (Maximum < min)
{
Maximum = min;
}
}
private void CoerceValue()
{
// Validate that the value is in bounds
var value = Value;
if (!double.IsNaN(value) && !IsInBounds(value) && ValidationMode == NumberBoxValidationMode.InvalidInputOverwritten)
{
// Coerce value to be within range
var max = Maximum;
if (value > max)
{
Value = max;
}
else
{
Value = Minimum;
}
}
}
private void ValidateInput()
{
// Validate the content of the inner textbox
if (m_textBox != null)
{
var text = trim(m_textBox.Text);
// Handles empty TextBox case, set text to current value
if (string.IsNullOrEmpty(text))
{
Value = double.NaN;
}
else
{
// Setting NumberFormatter to something that isn't an INumberParser will throw an exception, so this should be safe
var numberParser = NumberFormatter as INumberParser;
var value = AcceptsExpression
? NumberBoxParser.Compute(text, numberParser)
: ApiInformation.IsTypePresent(numberParser?.GetType().FullName)
? numberParser.ParseDouble(text)
: double.TryParse(text, out var v)
? (double?)v
: null;
if (value == null)
{
if (ValidationMode == NumberBoxValidationMode.InvalidInputOverwritten)
{
// Override text to current value
UpdateTextToValue();
}
}
else
{
if (value.Value == Value)
{
// Even if the value hasn't changed, we still want to update the text (e.g. Value is 3, user types 1 + 2, we want to replace the text with 3)
UpdateTextToValue();
}
else
{
Value = value.Value;
}
}
}
}
}
private void OnSpinDownClick(object sender, RoutedEventArgs args)
{
StepValue(-SmallChange);
}
private void OnSpinUpClick(object sender, RoutedEventArgs args)
{
StepValue(SmallChange);
}
private void OnNumberBoxKeyDown(object sender, KeyRoutedEventArgs args)
{
// Handle these on key down so that we get repeat behavior.
switch (args.OriginalKey)
{
case VirtualKey.Up:
StepValue(SmallChange);
args.Handled = true;
break;
case VirtualKey.Down:
StepValue(-SmallChange);
args.Handled = true;
break;
case VirtualKey.PageUp:
StepValue(LargeChange);
args.Handled = true;
break;
case VirtualKey.PageDown:
StepValue(-LargeChange);
args.Handled = true;
break;
}
}
private void OnNumberBoxKeyUp(object sender, KeyRoutedEventArgs args)
{
switch (args.OriginalKey)
{
case VirtualKey.Enter:
case VirtualKey.GamepadA:
ValidateInput();
args.Handled = true;
break;
case VirtualKey.Escape:
case VirtualKey.GamepadB:
UpdateTextToValue();
args.Handled = true;
break;
}
}
private void OnNumberBoxScroll(object sender, PointerRoutedEventArgs args)
{
if (m_textBox != null)
{
if (m_textBox.FocusState != FocusState.Unfocused)
{
var delta = args.GetCurrentPoint(this).Properties.MouseWheelDelta;
if (delta > 0)
{
StepValue(SmallChange);
}
else if (delta < 0)
{
StepValue(-SmallChange);
}
}
}
}
private void StepValue(double change)
{
// Before adjusting the value, validate the contents of the textbox so we don't override it.
ValidateInput();
var newVal = Value;
if (!double.IsNaN(newVal))
{
newVal += change;
if (IsWrapEnabled)
{
var max = Maximum;
var min = Minimum;
if (newVal > max)
{
newVal = min;
}
else if (newVal < min)
{
newVal = max;
}
}
Value = newVal;
}
}
// Updates TextBox.Text with the formatted Value
private void UpdateTextToValue()
{
if (m_textBox != null)
{
string newText = "";
var value = Value;
if (!double.IsNaN(value))
{
// Rounding the value here will prevent displaying digits caused by floating point imprecision.
var roundedValue = m_displayRounder.RoundDouble(value);
if (ApiInformation.IsTypePresent(NumberFormatter.GetType().FullName))
{
newText = NumberFormatter.FormatDouble(roundedValue);
}
else
{
newText = roundedValue.ToString($"0." + new string('#', (int)m_displayRounder.SignificantDigits), CultureInfo.CurrentCulture);
}
}
m_textBox.Text = newText;
try
{
m_textUpdating = true;
Text = newText;
// This places the caret at the end of the text.
m_textBox.Select(newText.Length, 0);
}
finally
{
m_textUpdating = false;
}
}
}
private void UpdateSpinButtonPlacement()
{
var spinButtonMode = SpinButtonPlacementMode;
if (spinButtonMode == NumberBoxSpinButtonPlacementMode.Inline)
{
VisualStateManager.GoToState(this, "SpinButtonsVisible", false);
}
else if (spinButtonMode == NumberBoxSpinButtonPlacementMode.Compact)
{
VisualStateManager.GoToState(this, "SpinButtonsPopup", false);
}
else
{
VisualStateManager.GoToState(this, "SpinButtonsCollapsed", false);
}
}
private void UpdateSpinButtonEnabled()
{
var value = Value;
bool isUpButtonEnabled = false;
bool isDownButtonEnabled = false;
if (!double.IsNaN(value))
{
if (IsWrapEnabled || ValidationMode != NumberBoxValidationMode.InvalidInputOverwritten)
{
// If wrapping is enabled, or invalid values are allowed, then the buttons should be enabled
isUpButtonEnabled = true;
isDownButtonEnabled = true;
}
else
{
if (value < Maximum)
{
isUpButtonEnabled = true;
}
if (value > Minimum)
{
isDownButtonEnabled = true;
}
}
}
VisualStateManager.GoToState(this, isUpButtonEnabled ? "UpSpinButtonEnabled" : "UpSpinButtonDisabled", false);
VisualStateManager.GoToState(this, isDownButtonEnabled ? "DownSpinButtonEnabled" : "DownSpinButtonDisabled", false);
}
private bool IsInBounds(double value)
{
return (value >= Minimum && value <= Maximum);
}
}
}

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

@ -0,0 +1,455 @@
<!-- Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License. See LICENSE in the project root for license information. -->
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Microsoft.UI.Xaml.Controls"
xmlns:contract5Present="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractPresent"
xmlns:contract6Present="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractPresent"
xmlns:contract7Present="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractPresent">
<Style TargetType="local:NumberBox">
<Setter Property="SelectionHighlightColor" Value="{ThemeResource TextControlSelectionHighlightColor}" />
<!--UNO TODO <contract7Present:Setter Property="SelectionFlyout" Value="{StaticResource TextControlCommandBarSelectionFlyout}" /> -->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:NumberBox">
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="SpinButtonStates">
<VisualState x:Name="SpinButtonsCollapsed" />
<VisualState x:Name="SpinButtonsVisible">
<VisualState.Setters>
<Setter Target="DownSpinButton.Visibility" Value="Visible" />
<Setter Target="UpSpinButton.Visibility" Value="Visible" />
<!--UNO TODO <contract7Present:Setter Target="InputBox.CornerRadius" Value="{Binding Source={ThemeResource ControlCornerRadius}, Converter={StaticResource LeftCornerRadiusFilterConverter}}" />-->
</VisualState.Setters>
</VisualState>
<VisualState x:Name="SpinButtonsPopup">
<VisualState.Setters>
<Setter Target="InputBox.Style" Value="{StaticResource NumberBoxTextBoxStyle}" />
<!--UNO TODO <contract7Present:Setter Target="InputBox.CornerRadius" Value="{Binding Source={ThemeResource ControlCornerRadius}}" />-->
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="UpSpinButtonEnabledStates">
<VisualState x:Name="UpSpinButtonEnabled" />
<VisualState x:Name="UpSpinButtonDisabled">
<VisualState.Setters>
<Setter Target="UpSpinButton.IsEnabled" Value="False" />
<Setter Target="PopupUpSpinButton.IsEnabled" Value="False" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="DownSpinButtonEnabledStates">
<VisualState x:Name="DownSpinButtonEnabled" />
<VisualState x:Name="DownSpinButtonDisabled">
<VisualState.Setters>
<Setter Target="DownSpinButton.IsEnabled" Value="False" />
<Setter Target="PopupDownSpinButton.IsEnabled" Value="False" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid.Resources>
<ResourceDictionary>
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Light">
<StaticResource x:Key="RepeatButtonBorderBrushPointerOver" ResourceKey="TextControlBorderBrush"/>
<StaticResource x:Key="RepeatButtonBorderBrushPressed" ResourceKey="TextControlBorderBrush"/>
</ResourceDictionary>
<ResourceDictionary x:Key="Dark">
<StaticResource x:Key="RepeatButtonBorderBrushPointerOver" ResourceKey="TextControlBorderBrush"/>
<StaticResource x:Key="RepeatButtonBorderBrushPressed" ResourceKey="TextControlBorderBrush"/>
</ResourceDictionary>
<ResourceDictionary x:Key="HighContrast">
<StaticResource x:Key="RepeatButtonBorderBrushPointerOver" ResourceKey="SystemControlHighlightBaseMediumLowBrush" />
<StaticResource x:Key="RepeatButtonBorderBrushPressed" ResourceKey="SystemControlHighlightTransparentBrush" />
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox x:Name="InputBox"
Grid.Column="0"
Header="{TemplateBinding Header}"
HeaderTemplate="{TemplateBinding HeaderTemplate}"
PlaceholderText="{TemplateBinding PlaceholderText}"
SelectionHighlightColor="{TemplateBinding SelectionHighlightColor}"
TextReadingOrder="{TemplateBinding TextReadingOrder}"
PreventKeyboardDisplayOnProgrammaticFocus="{TemplateBinding PreventKeyboardDisplayOnProgrammaticFocus}"
/>
<!--
UNO TODO
contract7Present:SelectionFlyout="{TemplateBinding SelectionFlyout}"
contract7Present:Description="{TemplateBinding Description}"-->
<Popup x:Name="UpDownPopup"
Grid.Column="1"
VerticalOffset="{ThemeResource NumberBoxPopupVerticalOffset}"
HorizontalOffset="{ThemeResource NumberBoxPopupHorizonalOffset}"
HorizontalAlignment="Left">
<Grid x:Name="PopupContentRoot"
Background="{ThemeResource SystemControlBackgroundAltHighBrush}"
>
<!-- UNO TODO contract7Present:CornerRadius="{ThemeResource OverlayCornerRadius}"-->
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<RepeatButton x:Name="PopupUpSpinButton"
Style="{StaticResource NumberBoxPopupSpinButtonStyle}"
Grid.Row="0"
Content="&#xE0E4;" />
<!-- UNO TODO Content="&#xE70E;"-->
<RepeatButton x:Name="PopupDownSpinButton"
Style="{StaticResource NumberBoxPopupSpinButtonStyle}"
Grid.Row="1"
Content="&#xE0E5;"/>
<!-- UNO TODO Content="&#xE70D;"-->
</Grid>
</Popup>
<RepeatButton x:Name="UpSpinButton"
Grid.Column="1"
Visibility="Collapsed"
FontSize="{TemplateBinding FontSize}"
Content="&#xE0E4;"
Style="{StaticResource NumberBoxSpinButtonStyle}"
/>
<!--Content="&#xE70E;"-->
<!-- UNO TODO contract7Present:CornerRadius="0"-->
<RepeatButton x:Name="DownSpinButton"
Grid.Column="2"
Visibility="Collapsed"
FontSize="{TemplateBinding FontSize}"
Content="&#xE0E5;"
Style="{StaticResource NumberBoxSpinButtonStyle}" />
<!--Content="&#xE70D;"-->
<!-- UNO TODO contract7Present:CornerRadius="{Binding Source={ThemeResource ControlCornerRadius}, Converter={StaticResource RightCornerRadiusFilterConverter}}"-->
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Name="NumberBoxSpinButtonStyle" TargetType="RepeatButton">
<Style.Setters>
<Setter Property="IsTabStop" Value="False"/>
<Setter Property="MinWidth" Value="34"/>
<Setter Property="Height" Value="{ThemeResource TextControlThemeMinHeight}"/>
<Setter Property="VerticalAlignment" Value="Bottom"/>
<Setter Property="Background" Value="{ThemeResource TextControlBackground}"/>
<Setter Property="BorderBrush" Value="{ThemeResource TextControlBorderBrush}"/>
<Setter Property="BorderThickness" Value="{ThemeResource NumberBoxSpinButtonBorderThickness}"/>
<Setter Property="FontFamily" Value="{ThemeResource SymbolThemeFontFamily}"/>
</Style.Setters>
</Style>
<Style x:Name="NumberBoxPopupSpinButtonStyle" TargetType="RepeatButton">
<Style.Setters>
<Setter Property="AutomationProperties.AccessibilityView" Value="Raw"/>
<Setter Property="IsTabStop" Value="False"/>
<Setter Property="Width" Value="40"/>
<Setter Property="Height" Value="32"/>
<Setter Property="Background" Value="{ThemeResource TextControlBackground}"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="FontFamily" Value="{ThemeResource SymbolThemeFontFamily}"/>
</Style.Setters>
</Style>
<Style x:Name="NumberBoxTextBoxStyle" TargetType="TextBox">
<Setter Property="Foreground" Value="{ThemeResource TextControlForeground}" />
<Setter Property="Background" Value="{ThemeResource TextControlBackground}" />
<Setter Property="BorderBrush" Value="{ThemeResource TextControlBorderBrush}" />
<Setter Property="SelectionHighlightColor" Value="{ThemeResource TextControlSelectionHighlightColor}" />
<Setter Property="BorderThickness" Value="{ThemeResource TextControlBorderThemeThickness}" />
<Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" />
<Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}" />
<Setter Property="ScrollViewer.HorizontalScrollMode" Value="Auto" />
<Setter Property="ScrollViewer.VerticalScrollMode" Value="Auto" />
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Hidden" />
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Hidden" />
<Setter Property="ScrollViewer.IsDeferredScrollingEnabled" Value="False" />
<Setter Property="Padding" Value="{ThemeResource TextControlThemePadding}" />
<contract6Present:Setter Property="UseSystemFocusVisuals" Value="{ThemeResource IsApplicationFocusVisualKindReveal}" />
<!--
UNO TODO
<contract7Present:Setter Property="ContextFlyout" Value="{StaticResource TextControlCommandBarContextFlyout}" />
<contract7Present:Setter Property="SelectionFlyout" Value="{StaticResource TextControlCommandBarSelectionFlyout}" />
-->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TextBox">
<Grid>
<Grid.Resources>
<Style x:Name="DeleteButtonStyle" TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid x:Name="ButtonLayoutGrid"
BorderBrush="{ThemeResource TextControlButtonBorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{ThemeResource TextControlButtonBackground}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="PointerOver">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ButtonLayoutGrid" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource TextControlButtonBackgroundPointerOver}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ButtonLayoutGrid" Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource TextControlButtonBorderBrushPointerOver}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="GlyphElement" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource TextControlButtonForegroundPointerOver}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ButtonLayoutGrid" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource TextControlButtonBackgroundPressed}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ButtonLayoutGrid" Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource TextControlButtonBorderBrushPressed}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="GlyphElement" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource TextControlButtonForegroundPressed}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Disabled">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="ButtonLayoutGrid"
Storyboard.TargetProperty="Opacity"
To="0"
Duration="0" />
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<TextBlock x:Name="GlyphElement"
Foreground="{ThemeResource TextControlButtonForeground}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
FontStyle="Normal"
FontSize="12"
Text="&#xE10A;"
FontFamily="{ThemeResource SymbolThemeFontFamily}"
AutomationProperties.AccessibilityView="Raw" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Grid.Resources>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HeaderContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource TextControlHeaderForegroundDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="BorderElement" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource TextControlBackgroundDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="BorderElement" Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource TextControlBorderBrushDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentElement" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource TextControlForegroundDisabled}" />
</ObjectAnimationUsingKeyFrames>
<contract5Present:ObjectAnimationUsingKeyFrames Storyboard.TargetName="PlaceholderTextContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{Binding PlaceholderForeground, RelativeSource={RelativeSource TemplatedParent}, TargetNullValue={ThemeResource TextControlPlaceholderForegroundDisabled}}" />
</contract5Present:ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="PointerOver">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="BorderElement" Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource TextControlBorderBrushPointerOver}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="BorderElement" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource TextControlBackgroundPointerOver}" />
</ObjectAnimationUsingKeyFrames>
<contract5Present:ObjectAnimationUsingKeyFrames Storyboard.TargetName="PlaceholderTextContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{Binding PlaceholderForeground, RelativeSource={RelativeSource TemplatedParent}, TargetNullValue={ThemeResource TextControlPlaceholderForegroundPointerOver}}" />
</contract5Present:ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentElement" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource TextControlForegroundPointerOver}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Focused">
<Storyboard>
<contract5Present:ObjectAnimationUsingKeyFrames Storyboard.TargetName="PlaceholderTextContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{Binding PlaceholderForeground, RelativeSource={RelativeSource TemplatedParent}, TargetNullValue={ThemeResource TextControlPlaceholderForegroundFocused}}" />
</contract5Present:ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="BorderElement" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource TextControlBackgroundFocused}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="BorderElement" Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource TextControlBorderBrushFocused}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentElement" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource TextControlForegroundFocused}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentElement" Storyboard.TargetProperty="RequestedTheme">
<DiscreteObjectKeyFrame KeyTime="0" Value="Light" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="ButtonStates">
<VisualState x:Name="ButtonVisible">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="DeleteButton" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="ButtonCollapsed" />
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ContentPresenter x:Name="HeaderContentPresenter"
Grid.Row="0"
Grid.Column="0"
Grid.ColumnSpan="2"
Content="{TemplateBinding Header}"
ContentTemplate="{TemplateBinding HeaderTemplate}"
FontWeight="Normal"
Foreground="{ThemeResource TextControlHeaderForeground}"
Margin="{ThemeResource TextBoxTopHeaderMargin}"
TextWrapping="Wrap"
VerticalAlignment="Top"
Visibility="Collapsed"
x:DeferLoadStrategy="Lazy" />
<Border x:Name="BorderElement"
Grid.Row="1"
Grid.Column="0"
Grid.RowSpan="1"
Grid.ColumnSpan="3"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}"
Control.IsTemplateFocusTarget="True"
MinWidth="{ThemeResource TextControlThemeMinWidth}"
MinHeight="{ThemeResource TextControlThemeMinHeight}" />
<ScrollViewer x:Name="ContentElement"
Grid.Row="1"
Grid.Column="0"
HorizontalScrollMode="{TemplateBinding ScrollViewer.HorizontalScrollMode}"
HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}"
VerticalScrollMode="{TemplateBinding ScrollViewer.VerticalScrollMode}"
VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}"
IsHorizontalRailEnabled="{TemplateBinding ScrollViewer.IsHorizontalRailEnabled}"
IsVerticalRailEnabled="{TemplateBinding ScrollViewer.IsVerticalRailEnabled}"
IsDeferredScrollingEnabled="{TemplateBinding ScrollViewer.IsDeferredScrollingEnabled}"
Margin="{TemplateBinding BorderThickness}"
Padding="{TemplateBinding Padding}"
IsTabStop="False"
AutomationProperties.AccessibilityView="Raw"
ZoomMode="Disabled" />
<TextBlock x:Name="PlaceholderTextContentPresenter"
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
Margin="{TemplateBinding BorderThickness}"
Padding="{TemplateBinding Padding}"
Text="{TemplateBinding PlaceholderText}"
TextAlignment="{TemplateBinding TextAlignment}"
TextWrapping="{TemplateBinding TextWrapping}"
IsHitTestVisible="False" />
<Button x:Name="DeleteButton"
Grid.Row="1"
Grid.Column="1"
Style="{StaticResource DeleteButtonStyle}"
BorderThickness="{TemplateBinding BorderThickness}"
Margin="{ThemeResource HelperButtonThemePadding}"
IsTabStop="False"
Visibility="Collapsed"
AutomationProperties.AccessibilityView="Raw"
FontSize="{TemplateBinding FontSize}"
MinWidth="34"
VerticalAlignment="Stretch" />
<ContentPresenter x:Name="DescriptionPresenter"
Grid.Row="2"
Grid.Column="0"
Grid.ColumnSpan="2"
Content="{TemplateBinding Description}"
Foreground="{ThemeResource SystemControlDescriptionTextForegroundBrush}"
AutomationProperties.AccessibilityView="Raw"
x:Load="False"/>
<TextBlock
Grid.Row="1"
Grid.Column="2"
Margin="{StaticResource NumberBoxPopupIndicatorMargin}"
Foreground="{ThemeResource NumberBoxPopupIndicatorForeground}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
FontSize="12"
Text="&#xEC8F;"
FontFamily="{ThemeResource SymbolThemeFontFamily}"
AutomationProperties.AccessibilityView="Raw" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>

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

@ -0,0 +1,64 @@
using System;
using Windows.Foundation.Metadata;
using Windows.UI.Xaml.Automation;
using Windows.UI.Xaml.Automation.Peers;
using Windows.UI.Xaml.Automation.Provider;
namespace Microsoft.UI.Xaml.Controls
{
public class NumberBoxAutomationPeer : AutomationPeer, IRangeValueProvider
{
private NumberBox _owner;
internal NumberBoxAutomationPeer(NumberBox owner)
{
_owner = owner;
}
// IAutomationPeerOverrides
protected override object GetPatternCore(PatternInterface patternInterface)
{
if (patternInterface == PatternInterface.RangeValue)
{
return this;
}
return base.GetPatternCore(patternInterface);
}
protected override AutomationControlType GetAutomationControlTypeCore()
{
return AutomationControlType.Spinner;
}
NumberBox GetImpl()
{
return _owner;
}
// IRangeValueProvider
public double Minimum => GetImpl().Minimum;
public double Maximum => GetImpl().Maximum;
public double Value => GetImpl().Value;
public double SmallChange => GetImpl().SmallChange;
public double LargeChange => GetImpl().LargeChange;
public bool IsReadOnly => false;
public void SetValue(double value) => GetImpl().Value = value;
internal void RaiseValueChangedEvent(double oldValue, double newValue)
{
if (ApiInformation.IsPropertyPresent("Windows.UI.Xaml.Automation.RangeValuePatternIdentifiers", nameof(RangeValuePatternIdentifiers.ValueProperty)))
{
RaisePropertyChangedEvent(RangeValuePatternIdentifiers.ValueProperty,
oldValue,
newValue);
}
}
}
}

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

@ -0,0 +1,285 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using Windows.Foundation.Metadata;
using Windows.Globalization.NumberFormatting;
namespace Microsoft.UI.Xaml.Controls
{
internal class NumberBoxParser
{
static string c_numberBoxOperators = "+-*/^";
// Returns list of MathTokens from expression input string. If there are any parsing errors, it returns an empty vector.
private static List<MathToken> GetTokens(string input, INumberParser numberParser)
{
var tokens = new List<MathToken>();
var expectNumber = true;
while (input.Length > 0)
{
// Skip spaces
var nextChar = input[0];
if (nextChar != ' ')
{
if (expectNumber)
{
if (nextChar == '(')
{
// Open parens are also acceptable, but don't change the next expected token type.
tokens.Add(new MathToken(MathTokenType.Parenthesis, nextChar));
}
else
{
var (value, charLength) = GetNextNumber(input, numberParser);
if (charLength > 0)
{
tokens.Add(new MathToken(MathTokenType.Numeric, value));
// UNO TODO: Was pointer manipulation, may be better off with Span<char>.
input = input.Substring(charLength - 1);// advance the end of the token
expectNumber = false; // next token should be an operator
}
else
{
// Error case -- next token is not a number
return new List<MathToken>();
}
}
}
else
{
if (c_numberBoxOperators.IndexOf(nextChar) != -1)
{
tokens.Add(new MathToken(MathTokenType.Operator, nextChar));
expectNumber = true; // next token should be a number
}
else if (nextChar == ')')
{
// Closed parens are also acceptable, but don't change the next expected token type.
tokens.Add(new MathToken(MathTokenType.Parenthesis, nextChar));
}
else
{
// Error case -- could not evaluate part of the expression
return new List<MathToken>();
}
}
}
// UNO TODO: Was pointer manipulation, may be better off with Span<char>.
input = input.Substring(1);
}
return tokens;
}
// Attempts to parse a number from the beginning of the given input string. Returns the character size of the matched string.
static (double, int) GetNextNumber(string input, INumberParser numberParser)
{
// Attempt to parse anything before an operator or space as a number
Regex regex = new Regex("^-?([^-+/*\\(\\)\\^\\s]+)");
var match = regex.Match(input);
if (match.Success)
{
// Might be a number
var matchLength = match.Groups[0].Length;
var parsedNum = ApiInformation.IsTypePresent(numberParser?.GetType().FullName)
? numberParser.ParseDouble(input.Substring(0, matchLength))
: double.TryParse(input.Substring(0, matchLength), out var d)
? (double?)d
: null;
if (parsedNum != null)
{
// Parsing was successful
return (parsedNum.Value, matchLength);
}
}
return (double.NaN, 0);
}
static int GetPrecedenceValue(char c)
{
int opPrecedence = 0;
if (c == '*' || c == '/')
{
opPrecedence = 1;
}
else if (c == '^')
{
opPrecedence = 2;
}
return opPrecedence;
}
// Converts a list of tokens from infix format (e.g. "3 + 5") to postfix (e.g. "3 5 +")
static List<MathToken> ConvertInfixToPostfix(List<MathToken> infixTokens)
{
List<MathToken> postfixTokens= new List<MathToken>();
Stack<MathToken> operatorStack = new Stack<MathToken>();
foreach(var token in infixTokens)
{
if (token.Type == MathTokenType.Numeric)
{
postfixTokens.Add(token);
}
else if (token.Type == MathTokenType.Operator)
{
while (operatorStack.Count != 0)
{
var top = operatorStack.Peek();
if (top.Type != MathTokenType.Parenthesis && (GetPrecedenceValue(top.Char) >= GetPrecedenceValue(token.Char)))
{
postfixTokens.Add(operatorStack.Peek());
operatorStack.Pop();
}
else
{
break;
}
}
operatorStack.Push(token);
}
else if (token.Type == MathTokenType.Parenthesis)
{
if (token.Char == '(')
{
operatorStack.Push(token);
}
else
{
while (operatorStack.Count!=0 && operatorStack.Peek().Char != '(')
{
// Pop operators onto output until we reach a left paren
postfixTokens.Add(operatorStack.Peek());
operatorStack.Pop();
}
if (operatorStack.Count == 0)
{
// Broken parenthesis
return new List<MathToken>();
}
// Pop left paren and discard
operatorStack.Pop();
}
}
}
// Pop all remaining operators.
while (operatorStack.Count != 0)
{
if (operatorStack.Peek().Type == MathTokenType.Parenthesis)
{
// Broken parenthesis
return new List<MathToken>();
}
postfixTokens.Add(operatorStack.Peek());
operatorStack.Pop();
}
return postfixTokens;
}
static double? ComputePostfixExpression(List<MathToken> tokens)
{
Stack<double> stack = new Stack<double>();
foreach(var token in tokens)
{
if (token.Type == MathTokenType.Operator)
{
// There has to be at least two values on the stack to apply
if (stack.Count < 2)
{
return null;
}
var op1 = stack.Peek();
stack.Pop();
var op2 = stack.Peek();
stack.Pop();
double result;
switch (token.Char)
{
case '-':
result = op2 - op1;
break;
case '+':
result = op1 + op2;
break;
case '*':
result = op1 * op2;
break;
case '/':
if (op1 == 0)
{
// divide by zero
return double.NaN;
}
else
{
result = op2 / op1;
}
break;
case '^':
result = Math.Pow(op2, op1);
break;
default:
return null;
}
stack.Push(result);
}
else if (token.Type == MathTokenType.Numeric)
{
stack.Push(token.Value);
}
}
// If there is more than one number on the stack, we didn't have enough operations, which is also an error.
if (stack.Count != 1)
{
return null;
}
return stack.Peek();
}
public static double? Compute(string expr, INumberParser numberParser)
{
// Tokenize the input string
var tokens = GetTokens(expr, numberParser);
if (tokens.Count > 0)
{
// Rearrange to postfix notation
var postfixTokens = ConvertInfixToPostfix(tokens);
if (postfixTokens.Count > 0)
{
// Compute expression
return ComputePostfixExpression(postfixTokens);
}
}
return null;
}
}
}

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

@ -0,0 +1,9 @@
namespace Microsoft.UI.Xaml.Controls
{
public enum NumberBoxSpinButtonPlacementMode
{
Hidden,
Compact,
Inline
};
}

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

@ -0,0 +1,8 @@
namespace Microsoft.UI.Xaml.Controls
{
public enum NumberBoxValidationMode
{
InvalidInputOverwritten,
Disabled
};
}

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

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Microsoft.UI.Xaml.Controls
{
public class NumberBoxValueChangedEventArgs
{
internal NumberBoxValueChangedEventArgs(double oldValue, double newValue)
{
OldValue = oldValue;
NewValue = newValue;
}
public double OldValue { get; }
public double NewValue { get; }
}
}

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

@ -0,0 +1,29 @@
<!-- Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License. See LICENSE in the project root for license information. -->
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Light">
<StaticResource x:Key="NumberBoxPopupIndicatorForeground" ResourceKey="SystemControlForegroundBaseMediumBrush" />
</ResourceDictionary>
<ResourceDictionary x:Key="Dark">
<StaticResource x:Key="NumberBoxPopupIndicatorForeground" ResourceKey="SystemControlForegroundBaseMediumBrush" />
</ResourceDictionary>
<ResourceDictionary x:Key="HighContrast">
<StaticResource x:Key="NumberBoxPopupIndicatorForeground" ResourceKey="SystemControlForegroundBaseMediumBrush" />
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
<Thickness x:Key="NumberBoxSpinButtonBorderThickness">0,1,1,1</Thickness>
<Thickness x:Key="NumberBoxIconMargin">10,0,0,0</Thickness>
<x:Double x:Key="NumberBoxPopupHorizonalOffset">-20</x:Double>
<x:Double x:Key="NumberBoxPopupVerticalOffset">8</x:Double>
<x:Double x:Key="NumberBoxPopupShadowDepth">16</x:Double>
<Thickness x:Key="NumberBoxPopupIndicatorMargin">0,0,8,0</Thickness>
</ResourceDictionary>

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

@ -0,0 +1,128 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="NumberBoxDownSpinButtonName" xml:space="preserve">
<value>Decrease</value>
<comment>Automation name for the down button</comment>
</data>
<data name="NumberBoxUpSpinButtonName" xml:space="preserve">
<value>Increase</value>
<comment>Automation name for the up button</comment>
</data>
</root>

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

@ -4,6 +4,7 @@
<#
AddClass("Windows.UI.Xaml.Controls", "TextBox", hasCommonStates: true, hasCommonOverState: true, hasCommonFocusedState: true);
AddClass("Windows.UI.Xaml.Controls", "Button", hasCommonStates: true, hasCommonOverState: true, hasCommonPressedState: true);
AddClass("Windows.UI.Xaml.Controls.Primitives", "RepeatButton", hasCommonStates: true, hasCommonOverState: true, hasCommonPressedState: true);
AddClass("Windows.UI.Xaml.Controls.Primitives", "ToggleButton", hasCommonStates: true, hasCommonCheckedState: true, hasCommonOverState: true, hasCommonPressedState: true);
AddClass("Windows.UI.Xaml.Controls", "CheckBox", hasCommonStates: true, hasCombinedCheckedState: true, hasCommonOverState: true, hasCommonPressedState: true);
AddClass("Windows.UI.Xaml.Controls", "RadioButton", hasCommonStates: true, hasCheckedStates: true, hasCommonOverState: true, hasCommonPressedState: true);

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

@ -4,6 +4,7 @@
<#
AddClass("Windows.UI.Xaml.Controls", "TextBox", hasCommonStates: true, hasCommonOverState: true, hasCommonFocusedState: true);
AddClass("Windows.UI.Xaml.Controls", "Button", hasCommonStates: true, hasCommonOverState: true, hasCommonPressedState: true);
AddClass("Windows.UI.Xaml.Controls.Primitives", "RepeatButton", hasCommonStates: true, hasCommonOverState: true, hasCommonPressedState: true);
AddClass("Windows.UI.Xaml.Controls.Primitives", "ToggleButton", hasCommonStates: true, hasCommonCheckedState: true, hasCommonOverState: true, hasCommonPressedState: true);
AddClass("Windows.UI.Xaml.Controls", "RadioButton", hasCommonStates: true, hasCheckedStates: true, hasCommonOverState: true, hasCommonPressedState: true);
AddClass("Windows.UI.Xaml.Controls", "HyperlinkButton", hasCommonStates: true, hasCommonOverState: true, hasCommonPressedState: true);

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

@ -4,6 +4,7 @@
<#
AddClass("Windows.UI.Xaml.Controls", "TextBox", hasCommonStates: true, hasCommonOverState: true, hasCommonFocusedState: true);
AddClass("Windows.UI.Xaml.Controls", "Button", hasCommonStates: true, hasCommonPressedState: true);
AddClass("Windows.UI.Xaml.Controls.Primitives", "RepeatButton", hasCommonStates: true, hasCommonOverState: true, hasCommonPressedState: true);
AddClass("Windows.UI.Xaml.Controls.Primitives", "ToggleButton", hasCommonStates: true, hasCommonCheckedState: true, hasCommonPressedState: true);
AddClass("Windows.UI.Xaml.Controls", "RadioButton", hasCommonStates: true, hasCheckedStates: true, hasCommonPressedState: true);
AddClass("Windows.UI.Xaml.Controls", "HyperlinkButton", hasCommonStates: true, hasCommonPressedState: true);

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

@ -4,6 +4,7 @@
<#
AddClass("Windows.UI.Xaml.Controls", "TextBox", hasCommonStates: true, hasCommonOverState: true, hasCommonFocusedState: true);
AddClass("Windows.UI.Xaml.Controls", "Button", hasCommonStates: true, hasCommonPressedState: true);
AddClass("Windows.UI.Xaml.Controls.Primitives", "RepeatButton", hasCommonStates: true, hasCommonOverState: true, hasCommonPressedState: true);
AddClass("Windows.UI.Xaml.Controls.Primitives", "ToggleButton", hasCommonStates: true, hasCommonCheckedState: true, hasCommonPressedState: true);
AddClass("Windows.UI.Xaml.Controls", "RadioButton", hasCommonStates: true, hasCheckedStates: true, hasCommonPressedState: true);
AddClass("Windows.UI.Xaml.Controls", "HyperlinkButton", hasCommonStates: true, hasCommonPressedState: true);

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

@ -4,6 +4,7 @@
<#
AddClass("Windows.UI.Xaml.Controls", "TextBox", hasCommonStates: true, hasCommonFocusedState: true);
AddClass("Windows.UI.Xaml.Controls", "Button", hasCommonStates: true, hasCommonPressedState: true);
AddClass("Windows.UI.Xaml.Controls.Primitives", "RepeatButton", hasCommonStates: true, hasCommonOverState: true, hasCommonPressedState: true);
AddClass("Windows.UI.Xaml.Controls.Primitives", "ToggleButton", hasCommonStates: true, hasCommonCheckedState: true, hasCommonPressedState: true);
AddClass("Windows.UI.Xaml.Controls", "RadioButton", hasCommonStates: true, hasCheckedStates: true, hasCommonPressedState: true);
AddClass("Windows.UI.Xaml.Controls", "HyperlinkButton", hasCommonStates: true, hasCommonPressedState: true);

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

@ -59,8 +59,6 @@ namespace Windows.UI.Xaml.Controls
}
protected override AutomationPeer OnCreateAutomationPeer()
{
return new ButtonAutomationPeer(this);
}
=> new ButtonAutomationPeer(this);
}
}

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

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

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

@ -117,7 +117,10 @@ namespace Windows.UI.Xaml.Controls
return;
}
Select(row, component: 0, animated: true);
if (Items.Length > 0) // If we have no Items, we don't need to call UIPickerView.Select(). Skipping the call avoids a native crash under certain narrow circumstances. (https://github.com/unoplatform/private/issues/115)
{
Select(row, component: 0, animated: true);
}
}
else if (newSelectedItem != null && Items[0] == null)
{

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

@ -10,6 +10,8 @@ using Windows.UI.Input;
using Windows.UI.Xaml.Input;
using Uno.Extensions.Specialized;
using Uno.Logging;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
#if XAMARIN_IOS
using View = UIKit.UIView;
#elif __MACOS__
@ -47,6 +49,9 @@ namespace Windows.UI.Xaml.Controls.Primitives
public ButtonBase()
{
InitializeProperties();
Unloaded += (s, e) =>
IsPressed = false;
}
public new bool IsPointerOver
@ -80,10 +85,11 @@ namespace Windows.UI.Xaml.Controls.Primitives
#endregion
#region CommandParameter
public static global::Windows.UI.Xaml.DependencyProperty CommandParameterProperty { get; } =
Windows.UI.Xaml.DependencyProperty.Register(
"CommandParameter", typeof(object),
typeof(global::Windows.UI.Xaml.Controls.Primitives.ButtonBase),
public static DependencyProperty CommandParameterProperty { get; } =
DependencyProperty.Register(
"CommandParameter",
typeof(object),
typeof(Controls.Primitives.ButtonBase),
new FrameworkPropertyMetadata(default(object), OnCommandParameterChanged));
public object CommandParameter
@ -98,6 +104,39 @@ namespace Windows.UI.Xaml.Controls.Primitives
}
#endregion
public ClickMode ClickMode
{
get => (ClickMode)this.GetValue(ClickModeProperty);
set => this.SetValue(ClickModeProperty, value);
}
public new bool IsPressed
{
get => (bool)GetValue(IsPressedProperty);
internal set => SetValue(IsPressedProperty, value);
}
public static DependencyProperty ClickModeProperty { get; } =
DependencyProperty.Register(
name: nameof(ClickMode),
propertyType: typeof(ClickMode),
ownerType: typeof(Controls.Primitives.ButtonBase),
typeMetadata: new FrameworkPropertyMetadata(ClickMode.Release));
public static DependencyProperty IsPointerOverProperty { get; } =
DependencyProperty.Register(
name: nameof(IsPointerOver),
propertyType: typeof(bool),
ownerType: typeof(Controls.Primitives.ButtonBase),
typeMetadata: new FrameworkPropertyMetadata(default(bool)));
public static DependencyProperty IsPressedProperty { get; } =
DependencyProperty.Register(
name: nameof(IsPressed),
propertyType: typeof(bool),
ownerType: typeof(Controls.Primitives.ButtonBase),
typeMetadata: new FrameworkPropertyMetadata(default(bool)));
partial void RegisterEvents();
private void OnCommandChanged(ICommand newCommand)
@ -191,6 +230,8 @@ namespace Windows.UI.Xaml.Controls.Primitives
var handle = args.GetCurrentPoint(this).Properties.IsLeftButtonPressed && CapturePointer(args.Pointer);
args.Handled = handle;
IsPressed = true;
if (handle && mode == ClickMode.Press)
{
RaiseClick(args);
@ -224,6 +265,8 @@ namespace Windows.UI.Xaml.Controls.Primitives
}
}
IsPressed = false;
// This should be automatically done by the pointers due to release, but if for any reason
// the state is invalid, this makes sure to not keep invalid capture longer than needed.
// Note: This must be done ** after ** the click event (UWP raise CaptureLost event after)

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

@ -0,0 +1,240 @@
using System;
using Windows.System;
using Windows.UI.Xaml.Automation.Peers;
using Windows.UI.Xaml.Input;
namespace Windows.UI.Xaml.Controls.Primitives
{
public partial class RepeatButton : ButtonBase
{
private bool _keyboardCausingRepeat;
private bool _pointerCausingRepeat;
private DispatcherTimer _timer;
public int Interval
{
get => (int)GetValue(IntervalProperty);
set => SetValue(IntervalProperty, value);
}
public static DependencyProperty IntervalProperty { get; } =
Windows.UI.Xaml.DependencyProperty.Register(
name: nameof(Interval),
propertyType: typeof(int),
ownerType: typeof(RepeatButton),
typeMetadata: new FrameworkPropertyMetadata(250, propertyChangedCallback: (s, e) => (s as RepeatButton)?.OnIntervalChanged(e)));
public int Delay
{
get => (int)this.GetValue(DelayProperty);
set => this.SetValue(DelayProperty, value);
}
public static DependencyProperty DelayProperty { get; } =
Windows.UI.Xaml.DependencyProperty.Register(
name: nameof(Delay),
propertyType: typeof(int),
ownerType: typeof(RepeatButton),
typeMetadata: new FrameworkPropertyMetadata(250, propertyChangedCallback: (s, e) => (s as RepeatButton)?.OnDelayChanged(e)));
public RepeatButton() : base()
{
InitializeVisualStates();
ClickMode = ClickMode.Press;
DefaultStyleKey = typeof(RepeatButton);
}
protected override AutomationPeer OnCreateAutomationPeer()
=> new RepeatButtonAutomationPeer(this);
private void OnIntervalChanged(DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is int newValue)
{
if (newValue <= 0)
{
throw new ArgumentException($"Interval cannot be positive", DelayProperty.ToString());
}
}
}
private void OnDelayChanged(DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is int newValue)
{
if (newValue < 0)
{
throw new ArgumentException($"Delay cannot be negative", DelayProperty.ToString());
}
}
}
protected override void OnIsEnabledChanged(bool oldValue, bool newValue)
{
base.OnIsEnabledChanged(oldValue, newValue);
_keyboardCausingRepeat = false;
_pointerCausingRepeat = false;
UpdateRepeatState();
}
protected override void OnKeyDown(KeyRoutedEventArgs args)
{
base.OnKeyDown(args);
if(args.Key == VirtualKey.Space
&& ClickMode != ClickMode.Hover)
{
_keyboardCausingRepeat = true;
UpdateRepeatState();
}
}
protected override void OnKeyUp(KeyRoutedEventArgs args)
{
base.OnKeyUp(args);
if (args.Key == VirtualKey.Space
& ClickMode != ClickMode.Hover)
{
_keyboardCausingRepeat = false;
UpdateRepeatState();
}
}
protected override void OnLostFocus(RoutedEventArgs e)
{
base.OnLostFocus(e);
if (ClickMode != ClickMode.Hover)
{
_keyboardCausingRepeat = false;
_pointerCausingRepeat = false;
UpdateRepeatState();
}
}
protected override void OnPointerEntered(PointerRoutedEventArgs args)
{
base.OnPointerEntered(args);
if (ClickMode == ClickMode.Hover)
{
_pointerCausingRepeat = true;
}
UpdateRepeatState();
}
protected override void OnPointerMoved(PointerRoutedEventArgs args)
{
base.OnPointerMoved(args);
}
protected override void OnPointerExited(PointerRoutedEventArgs args)
{
base.OnPointerExited(args);
if (ClickMode == ClickMode.Hover)
{
_pointerCausingRepeat = false;
UpdateRepeatState();
}
}
protected override void OnPointerPressed(PointerRoutedEventArgs args)
{
if (args.Handled)
{
return;
}
base.OnPointerPressed(args);
var pointerPoint = args.GetCurrentPoint(this);
var isLeftButtonPressed = pointerPoint.Properties.IsLeftButtonPressed;
if (isLeftButtonPressed)
{
if (ClickMode != ClickMode.Hover)
{
_pointerCausingRepeat = true;
UpdateRepeatState();
}
}
}
protected override void OnPointerReleased(PointerRoutedEventArgs args)
{
if (args.Handled)
{
return;
}
base.OnPointerReleased(args);
if (ClickMode != ClickMode.Hover)
{
_pointerCausingRepeat = false;
UpdateRepeatState();
}
}
private void StartTimer()
{
if (_timer == null)
{
_timer = new DispatcherTimer();
_timer.Tick += TimerCallback;
}
if (!_timer.IsEnabled)
{
_timer.Interval = TimeSpan.FromMilliseconds(Delay);
_timer.Start();
}
}
private void StopTimer()
{
_timer?.Stop();
}
private void UpdateRepeatState()
{
if((_pointerCausingRepeat && IsPointerOver) || _keyboardCausingRepeat)
{
StartTimer();
}
else
{
StopTimer();
}
}
private void TimerCallback(object sender, object state)
{
var interval = TimeSpan.FromMilliseconds(Interval);
if (_timer.Interval != interval)
{
_timer.Interval = interval;
}
var isPressed = IsPressed;
if ((isPressed && IsPointerOver) || (isPressed && _keyboardCausingRepeat))
{
RaiseClick();
}
else
{
StopTimer();
}
}
}
}

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

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

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

@ -1,6 +1,4 @@
#if !NET461
using System;
using System;
using System.Collections.Generic;
using System.Text;
using Windows.System;
@ -14,6 +12,7 @@ namespace Windows.UI.Xaml.Input
: base(originalSource)
{
Key = key;
OriginalKey = key;
}
public bool Handled { get; set; }
@ -21,6 +20,6 @@ namespace Windows.UI.Xaml.Input
//TODO
//public CorePhysicalKeyStatus KeyStatus { get; }
public global::Windows.System.VirtualKey OriginalKey { get; }
}
}
#endif

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

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

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

@ -7977,6 +7977,99 @@
</Setter>
</Style>
<!-- Default style for Windows.UI.Xaml.Controls.Primitives.RepeatButton -->
<Style x:Name="XamlDefaultRepeatButton" TargetType="RepeatButton">
<Setter Property="Background" Value="{ThemeResource RepeatButtonBackground}" />
<Setter Property="BackgroundSizing" Value="OuterBorderEdge" />
<Setter Property="Foreground" Value="{ThemeResource RepeatButtonForeground}" />
<Setter Property="BorderBrush" Value="{ThemeResource RepeatButtonBorderBrush}" />
<Setter Property="BorderThickness" Value="{ThemeResource ButtonBorderThemeThickness}" />
<Setter Property="Padding" Value="{StaticResource ButtonPadding}" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" />
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}" />
<Setter Property="UseSystemFocusVisuals" Value="{StaticResource UseSystemFocusVisuals}" />
<Setter Property="FocusVisualMargin" Value="-3" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="RepeatButton">
<ContentPresenter x:Name="ContentPresenter"
Background="{TemplateBinding Background}"
BackgroundSizing="{TemplateBinding BackgroundSizing}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
ContentTransitions="{TemplateBinding ContentTransitions}"
CornerRadius="{TemplateBinding CornerRadius}"
Padding="{TemplateBinding Padding}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
AutomationProperties.AccessibilityView="Raw">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="PointerOver">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource RepeatButtonBackgroundPointerOver}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource RepeatButtonBorderBrushPointerOver}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource RepeatButtonForegroundPointerOver}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource RepeatButtonBackgroundPressed}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource RepeatButtonBorderBrushPressed}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource RepeatButtonForegroundPressed}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Disabled">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource RepeatButtonBackgroundDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource RepeatButtonBorderBrushDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource RepeatButtonForegroundDisabled}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</ContentPresenter>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="RepeatButton" BasedOn="{StaticResource XamlDefaultRepeatButton}" />
<win:AcrylicBrush x:Key="SystemControlChromeMediumLowAcrylicElementMediumBrush" BackgroundSource="Backdrop" TintColor="{StaticResource SystemChromeAltHighColor}" TintOpacity="0.6" FallbackColor="{StaticResource SystemChromeMediumLowColor}" />
<xamarin:SolidColorBrush x:Key="SystemControlChromeMediumLowAcrylicElementMediumBrush" Color="{StaticResource SystemChromeMediumLowColor}" />
<StaticResource x:Key="NavigationViewDefaultPaneBackground" ResourceKey="SystemControlChromeMediumLowAcrylicElementMediumBrush" />

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

@ -196,7 +196,7 @@
<Thickness x:Key="PivotNavButtonMargin">0,6,0,0</Thickness>
<Thickness x:Key="PivotPortraitThemePadding">12,14,0,13</Thickness>
<Thickness x:Key="ProgressBarBorderThemeThickness">0</Thickness>
<Thickness x:Key="RepeatButtonBorderThemeThickness">0</Thickness>
<Thickness x:Key="RepeatButtonBorderThemeThickness">2</Thickness>
<Thickness x:Key="ScrollBarPanningBorderThemeThickness">1</Thickness>
<Thickness x:Key="SearchBoxQuerySuggestionThemeMargin">12,11,8,13</Thickness>
<Thickness x:Key="SearchBoxResultSuggestionThemeMargin">12,11,8,13</Thickness>
@ -746,6 +746,15 @@
<SolidColorBrush x:Key="ScrollBarThumbPressedBorderThemeBrush" Color="#ED555555" />
<SolidColorBrush x:Key="ScrollBarTrackBackgroundThemeBrush" Color="#59D5D5D5" />
<SolidColorBrush x:Key="ScrollBarTrackBorderThemeBrush" Color="#59D5D5D5" />
<SolidColorBrush x:Key="RepeatButtonBorderThemeBrush" Color="#FFFFFFFF" />
<SolidColorBrush x:Key="RepeatButtonDisabledBackgroundThemeBrush" Color="Transparent" />
<SolidColorBrush x:Key="RepeatButtonDisabledBorderThemeBrush" Color="#66FFFFFF" />
<SolidColorBrush x:Key="RepeatButtonDisabledForegroundThemeBrush" Color="#66FFFFFF" />
<SolidColorBrush x:Key="RepeatButtonForegroundThemeBrush" Color="#FFFFFFFF" />
<SolidColorBrush x:Key="RepeatButtonPointerOverBackgroundThemeBrush" Color="#21FFFFFF" />
<SolidColorBrush x:Key="RepeatButtonPointerOverForegroundThemeBrush" Color="#FFFFFFFF" />
<SolidColorBrush x:Key="RepeatButtonPressedBackgroundThemeBrush" Color="#FFFFFFFF" />
<SolidColorBrush x:Key="RepeatButtonPressedForegroundThemeBrush" Color="#FF000000" />
<SolidColorBrush x:Key="SearchBoxBackgroundThemeBrush" Color="#CCFFFFFF" />
<SolidColorBrush x:Key="SearchBoxBorderThemeBrush" Color="#FF2A2A2A" />
<SolidColorBrush x:Key="SearchBoxDisabledBackgroundThemeBrush" Color="Transparent" />
@ -989,6 +998,20 @@
<xamarin:StaticResource x:Key="HyperlinkButtonBorderBrushPressed" ResourceKey="SystemControlTransparentBrush" />
<xamarin:StaticResource x:Key="HyperlinkButtonBorderBrushDisabled" ResourceKey="SystemControlTransparentBrush" />
<!-- Resources for Windows.UI.Xaml.Controls.Primitives.RepeatButton -->
<StaticResource x:Key="RepeatButtonBackground" ResourceKey="SystemControlBackgroundBaseLowBrush" />
<StaticResource x:Key="RepeatButtonBackgroundPointerOver" ResourceKey="SystemControlBackgroundBaseLowBrush" />
<StaticResource x:Key="RepeatButtonBackgroundPressed" ResourceKey="SystemControlBackgroundBaseMediumLowBrush" />
<StaticResource x:Key="RepeatButtonBackgroundDisabled" ResourceKey="SystemControlBackgroundBaseLowBrush" />
<StaticResource x:Key="RepeatButtonForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="RepeatButtonForegroundPointerOver" ResourceKey="SystemControlHighlightBaseHighBrush" />
<StaticResource x:Key="RepeatButtonForegroundPressed" ResourceKey="SystemControlHighlightBaseHighBrush" />
<StaticResource x:Key="RepeatButtonForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="RepeatButtonBorderBrush" ResourceKey="SystemControlForegroundTransparentBrush" />
<StaticResource x:Key="RepeatButtonBorderBrushPointerOver" ResourceKey="SystemControlHighlightBaseMediumLowBrush" />
<StaticResource x:Key="RepeatButtonBorderBrushPressed" ResourceKey="SystemControlHighlightTransparentBrush" />
<StaticResource x:Key="RepeatButtonBorderBrushDisabled" ResourceKey="SystemControlDisabledTransparentBrush" />
<!-- Resources for AccentButtonStyle -->
<StaticResource x:Key="AccentButtonBackground" ResourceKey="SystemControlForegroundAccentBrush" />
<StaticResource x:Key="AccentButtonBackgroundPointerOver" ResourceKey="SystemControlForegroundAccentBrush" />
@ -1199,7 +1222,7 @@
<Thickness x:Key="PivotNavButtonMargin">0,6,0,0</Thickness>
<Thickness x:Key="PivotPortraitThemePadding">12,14,0,13</Thickness>
<Thickness x:Key="ProgressBarBorderThemeThickness">0</Thickness>
<Thickness x:Key="RepeatButtonBorderThemeThickness">0</Thickness>
<Thickness x:Key="RepeatButtonBorderThemeThickness">2</Thickness>
<Thickness x:Key="ScrollBarPanningBorderThemeThickness">1</Thickness>
<Thickness x:Key="SearchBoxQuerySuggestionThemeMargin">12,11,8,13</Thickness>
<Thickness x:Key="SearchBoxResultSuggestionThemeMargin">12,11,8,13</Thickness>
@ -1987,6 +2010,20 @@
<xamarin:StaticResource x:Key="HyperlinkButtonBorderBrushPressed" ResourceKey="SystemControlTransparentBrush" />
<xamarin:StaticResource x:Key="HyperlinkButtonBorderBrushDisabled" ResourceKey="SystemControlTransparentBrush" />
<!-- Resources for Windows.UI.Xaml.Controls.Primitives.RepeatButton -->
<StaticResource x:Key="RepeatButtonBackground" ResourceKey="SystemControlBackgroundBaseLowBrush" />
<StaticResource x:Key="RepeatButtonBackgroundPointerOver" ResourceKey="SystemControlBackgroundBaseLowBrush" />
<StaticResource x:Key="RepeatButtonBackgroundPressed" ResourceKey="SystemControlBackgroundBaseMediumLowBrush" />
<StaticResource x:Key="RepeatButtonBackgroundDisabled" ResourceKey="SystemControlBackgroundBaseLowBrush" />
<StaticResource x:Key="RepeatButtonForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="RepeatButtonForegroundPointerOver" ResourceKey="SystemControlHighlightBaseHighBrush" />
<StaticResource x:Key="RepeatButtonForegroundPressed" ResourceKey="SystemControlHighlightBaseHighBrush" />
<StaticResource x:Key="RepeatButtonForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="RepeatButtonBorderBrush" ResourceKey="SystemControlForegroundTransparentBrush" />
<StaticResource x:Key="RepeatButtonBorderBrushPointerOver" ResourceKey="SystemControlHighlightBaseMediumLowBrush" />
<StaticResource x:Key="RepeatButtonBorderBrushPressed" ResourceKey="SystemControlHighlightTransparentBrush" />
<StaticResource x:Key="RepeatButtonBorderBrushDisabled" ResourceKey="SystemControlDisabledTransparentBrush" />
<!-- Resources for AccentButtonStyle -->
<StaticResource x:Key="AccentButtonBackground" ResourceKey="SystemControlForegroundAccentBrush" />
<StaticResource x:Key="AccentButtonBackgroundPointerOver" ResourceKey="SystemControlForegroundAccentBrush" />
@ -2983,6 +3020,20 @@
<xamarin:StaticResource x:Key="HyperlinkButtonBorderBrushPointerOver" ResourceKey="SystemControlTransparentBrush" />
<xamarin:StaticResource x:Key="HyperlinkButtonBorderBrushPressed" ResourceKey="SystemControlTransparentBrush" />
<xamarin:StaticResource x:Key="HyperlinkButtonBorderBrushDisabled" ResourceKey="SystemControlTransparentBrush" />
<!-- Resources for Windows.UI.Xaml.Controls.Primitives.RepeatButton -->
<StaticResource x:Key="RepeatButtonBackground" ResourceKey="SystemControlBackgroundBaseLowBrush" />
<StaticResource x:Key="RepeatButtonBackgroundPointerOver" ResourceKey="SystemControlBackgroundBaseLowBrush" />
<StaticResource x:Key="RepeatButtonBackgroundPressed" ResourceKey="SystemControlBackgroundBaseMediumLowBrush" />
<StaticResource x:Key="RepeatButtonBackgroundDisabled" ResourceKey="SystemControlBackgroundBaseLowBrush" />
<StaticResource x:Key="RepeatButtonForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="RepeatButtonForegroundPointerOver" ResourceKey="SystemControlHighlightBaseHighBrush" />
<StaticResource x:Key="RepeatButtonForegroundPressed" ResourceKey="SystemControlHighlightBaseHighBrush" />
<StaticResource x:Key="RepeatButtonForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="RepeatButtonBorderBrush" ResourceKey="SystemControlForegroundTransparentBrush" />
<StaticResource x:Key="RepeatButtonBorderBrushPointerOver" ResourceKey="SystemControlHighlightBaseMediumLowBrush" />
<StaticResource x:Key="RepeatButtonBorderBrushPressed" ResourceKey="SystemControlHighlightTransparentBrush" />
<StaticResource x:Key="RepeatButtonBorderBrushDisabled" ResourceKey="SystemControlDisabledTransparentBrush" />
<!-- Resources for AccentButtonStyle -->
<StaticResource x:Key="AccentButtonBackground" ResourceKey="SystemControlForegroundAccentBrush" />
@ -3013,4 +3064,65 @@
<StaticResource x:Key="TimePickerLightDismissOverlayBackground" ResourceKey="SystemControlPageBackgroundMediumAltMediumBrush" />
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
<!-- Control resources that are customizable but do not vary based on theme -->
<Thickness x:Key="TextBoxTopHeaderMargin">0,0,0,4</Thickness>
<Thickness x:Key="TextBoxLeftHeaderMargin">0,5,32,0</Thickness>
<Thickness x:Key="AutoSuggestBoxTopHeaderMargin">0,0,0,4</Thickness>
<Thickness x:Key="AutoSuggestBoxLeftHeaderMargin">0,5,32,0</Thickness>
<Thickness x:Key="PasswordBoxTopHeaderMargin">0,0,0,4</Thickness>
<Thickness x:Key="PasswordBoxLeftHeaderMargin">0,5,32,0</Thickness>
<Thickness x:Key="RichEditBoxTopHeaderMargin">0,0,0,4</Thickness>
<Thickness x:Key="RichEditBoxLeftHeaderMargin">0,5,32,0</Thickness>
<Thickness x:Key="TimePickerTopHeaderMargin">0,0,0,4</Thickness>
<Thickness x:Key="TimePickerLeftHeaderMargin">0,5,32,0</Thickness>
<Thickness x:Key="DatePickerTopHeaderMargin">0,0,0,4</Thickness>
<Thickness x:Key="DatePickerLeftHeaderMargin">0,5,32,0</Thickness>
<Thickness x:Key="CalendarDatePickerTopHeaderMargin">0,0,0,4</Thickness>
<Thickness x:Key="CalendarDatePickerLeftHeaderMargin">0,5,32,0</Thickness>
<Thickness x:Key="SliderTopHeaderMargin">0,0,0,4</Thickness>
<Thickness x:Key="SliderLeftHeaderMargin">0,5,32,0</Thickness>
<Thickness x:Key="ComboBoxTopHeaderMargin">0,0,0,4</Thickness>
<Thickness x:Key="ComboBoxLeftHeaderMargin">0,5,32,0</Thickness>
<Thickness x:Key="ToggleSwitchTopHeaderMargin">0,0,0,4</Thickness>
<Thickness x:Key="ToggleSwitchLeftHeaderMargin">0,6,32,0</Thickness>
<Thickness x:Key="ListBoxItemPadding">12,9,12,12</Thickness>
<Thickness x:Key="TimePickerFlyoutPresenterItemPadding">0,3,0,6</Thickness>
<Thickness x:Key="DatePickerFlyoutPresenterItemPadding">0,3,0,6</Thickness>
<Thickness x:Key="DatePickerFlyoutPresenterMonthPadding">9,3,0,6</Thickness>
<Thickness x:Key="ToggleSwitchHeaderMargin">0,0,0,4</Thickness>
<Thickness x:Key="ButtonPadding">8,4,8,5</Thickness>
<Thickness x:Key="HyperlinkButtonPadding">0,6,0,7</Thickness>
<x:Double x:Key="TimePickerFlyoutPresenterHighlightHeight">40</x:Double>
<x:Double x:Key="TimePickerFlyoutPresenterAcceptDismissHostGridHeight">41</x:Double>
<x:Double x:Key="TimePickerFlyoutPresenterItemHeight">40</x:Double>
<x:Double x:Key="DatePickerFlyoutPresenterHighlightHeight">40</x:Double>
<x:Double x:Key="DatePickerFlyoutPresenterAcceptDismissHostGridHeight">41</x:Double>
<x:Double x:Key="DatePickerFlyoutPresenterItemHeight">40</x:Double>
<x:Double x:Key="TreeViewItemMinHeight">32</x:Double>
<x:Double x:Key="TreeViewItemContentHeight">32</x:Double>
<x:Double x:Key="SliderPreContentMargin">15</x:Double>
<x:Double x:Key="SliderPostContentMargin">15</x:Double>
<x:Double x:Key="SliderHorizontalHeight">32</x:Double>
<x:Double x:Key="SliderVerticalWidth">32</x:Double>
<x:Double x:Key="ToggleSwitchPreContentMargin">6</x:Double>
<x:Double x:Key="ToggleSwitchPostContentMargin">6</x:Double>
<x:Double x:Key="DatePickerThemeMinWidth">296</x:Double>
<x:Double x:Key="DatePickerThemeMaxWidth">456</x:Double>
<x:Double x:Key="TimePickerThemeMinWidth">242</x:Double>
<x:Double x:Key="TimePickerThemeMaxWidth">456</x:Double>
<x:Double x:Key="ToggleSwitchThemeMinWidth">154</x:Double>
<x:Double x:Key="AutoSuggestBoxLeftHeaderMaxWidth">296</x:Double>
<x:Double x:Key="CalendarDatePickerLeftHeaderMaxWidth">296</x:Double>
<x:Double x:Key="ComboBoxLeftHeaderMaxWidth">296</x:Double>
<x:Double x:Key="DatePickerLeftHeaderMaxWidth">296</x:Double>
<x:Double x:Key="PasswordBoxLeftHeaderMaxWidth">296</x:Double>
<x:Double x:Key="RichEditBoxLeftHeaderMaxWidth">296</x:Double>
<x:Double x:Key="SliderLeftHeaderMaxWidth">296</x:Double>
<x:Double x:Key="TextBoxLeftHeaderMaxWidth">296</x:Double>
<x:Double x:Key="TimePickerLeftHeaderMaxWidth">296</x:Double>
<x:Double x:Key="ToggleSwitchLeftHeaderMaxWidth">296</x:Double>
</ResourceDictionary>

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

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

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

@ -2,93 +2,5 @@
#pragma warning disable 114 // new keyword hiding
namespace Windows.Globalization.NumberFormatting
{
#if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __MACOS__
[global::Uno.NotImplemented]
#endif
public partial class SignificantDigitsNumberRounder : global::Windows.Globalization.NumberFormatting.INumberRounder
{
#if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __MACOS__
[global::Uno.NotImplemented]
public uint SignificantDigits
{
get
{
throw new global::System.NotImplementedException("The member uint SignificantDigitsNumberRounder.SignificantDigits is not implemented in Uno.");
}
set
{
global::Windows.Foundation.Metadata.ApiInformation.TryRaiseNotImplemented("Windows.Globalization.NumberFormatting.SignificantDigitsNumberRounder", "uint SignificantDigitsNumberRounder.SignificantDigits");
}
}
#endif
#if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __MACOS__
[global::Uno.NotImplemented]
public global::Windows.Globalization.NumberFormatting.RoundingAlgorithm RoundingAlgorithm
{
get
{
throw new global::System.NotImplementedException("The member RoundingAlgorithm SignificantDigitsNumberRounder.RoundingAlgorithm is not implemented in Uno.");
}
set
{
global::Windows.Foundation.Metadata.ApiInformation.TryRaiseNotImplemented("Windows.Globalization.NumberFormatting.SignificantDigitsNumberRounder", "RoundingAlgorithm SignificantDigitsNumberRounder.RoundingAlgorithm");
}
}
#endif
#if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __MACOS__
[global::Uno.NotImplemented]
public SignificantDigitsNumberRounder()
{
global::Windows.Foundation.Metadata.ApiInformation.TryRaiseNotImplemented("Windows.Globalization.NumberFormatting.SignificantDigitsNumberRounder", "SignificantDigitsNumberRounder.SignificantDigitsNumberRounder()");
}
#endif
// Forced skipping of method Windows.Globalization.NumberFormatting.SignificantDigitsNumberRounder.SignificantDigitsNumberRounder()
#if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __MACOS__
[global::Uno.NotImplemented]
public int RoundInt32( int value)
{
throw new global::System.NotImplementedException("The member int SignificantDigitsNumberRounder.RoundInt32(int value) is not implemented in Uno.");
}
#endif
#if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __MACOS__
[global::Uno.NotImplemented]
public uint RoundUInt32( uint value)
{
throw new global::System.NotImplementedException("The member uint SignificantDigitsNumberRounder.RoundUInt32(uint value) is not implemented in Uno.");
}
#endif
#if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __MACOS__
[global::Uno.NotImplemented]
public long RoundInt64( long value)
{
throw new global::System.NotImplementedException("The member long SignificantDigitsNumberRounder.RoundInt64(long value) is not implemented in Uno.");
}
#endif
#if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __MACOS__
[global::Uno.NotImplemented]
public ulong RoundUInt64( ulong value)
{
throw new global::System.NotImplementedException("The member ulong SignificantDigitsNumberRounder.RoundUInt64(ulong value) is not implemented in Uno.");
}
#endif
#if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __MACOS__
[global::Uno.NotImplemented]
public float RoundSingle( float value)
{
throw new global::System.NotImplementedException("The member float SignificantDigitsNumberRounder.RoundSingle(float value) is not implemented in Uno.");
}
#endif
#if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __MACOS__
[global::Uno.NotImplemented]
public double RoundDouble( double value)
{
throw new global::System.NotImplementedException("The member double SignificantDigitsNumberRounder.RoundDouble(double value) is not implemented in Uno.");
}
#endif
// Forced skipping of method Windows.Globalization.NumberFormatting.SignificantDigitsNumberRounder.RoundingAlgorithm.get
// Forced skipping of method Windows.Globalization.NumberFormatting.SignificantDigitsNumberRounder.RoundingAlgorithm.set
// Forced skipping of method Windows.Globalization.NumberFormatting.SignificantDigitsNumberRounder.SignificantDigits.get
// Forced skipping of method Windows.Globalization.NumberFormatting.SignificantDigitsNumberRounder.SignificantDigits.set
// Processing: Windows.Globalization.NumberFormatting.INumberRounder
}
}

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

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

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

@ -0,0 +1,51 @@
using System;
using Uno;
namespace Windows.Globalization.NumberFormatting
{
public partial class SignificantDigitsNumberRounder : INumberRounder
{
public uint SignificantDigits { get; set; } = 0;
[NotImplemented]
public RoundingAlgorithm RoundingAlgorithm { get; set; } = RoundingAlgorithm.RoundHalfUp;
public SignificantDigitsNumberRounder()
{
}
[NotImplemented]
public int RoundInt32( int value)
{
throw new global::System.NotImplementedException("The member int SignificantDigitsNumberRounder.RoundInt32(int value) is not implemented in Uno.");
}
[NotImplemented]
public uint RoundUInt32( uint value)
{
throw new global::System.NotImplementedException("The member uint SignificantDigitsNumberRounder.RoundUInt32(uint value) is not implemented in Uno.");
}
[NotImplemented]
public long RoundInt64( long value)
{
throw new global::System.NotImplementedException("The member long SignificantDigitsNumberRounder.RoundInt64(long value) is not implemented in Uno.");
}
[NotImplemented]
public ulong RoundUInt64( ulong value)
{
throw new global::System.NotImplementedException("The member ulong SignificantDigitsNumberRounder.RoundUInt64(ulong value) is not implemented in Uno.");
}
public float RoundSingle( float value)
{
return (float)Math.Round(value, (int)SignificantDigits, MidpointRounding.AwayFromZero);
}
public double RoundDouble( double value)
{
return Math.Round(value, (int)SignificantDigits, MidpointRounding.AwayFromZero);
}
}
}

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

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