Browse Source

fix merge

repro/minimal-repro-stackoverflow-onewaytosource-binding
Tako 4 years ago
committed by Dan Walmsley
parent
commit
5875f77640
  1. 18
      src/Avalonia.Input/Gestures.cs
  2. 9
      src/Avalonia.Input/PointerEventArgs.cs
  3. 44
      src/Avalonia.Input/TouchDevice.cs
  4. 272
      tests/Avalonia.Input.UnitTests/TouchDeviceTests.cs

18
src/Avalonia.Input/Gestures.cs

@ -6,6 +6,7 @@ namespace Avalonia.Input
{
public static class Gestures
{
private static bool s_isDoubleTapped = false;
public static readonly RoutedEvent<RoutedEventArgs> TappedEvent = RoutedEvent.Register<RoutedEventArgs>(
"Tapped",
RoutingStrategies.Bubble,
@ -81,20 +82,23 @@ namespace Avalonia.Input
var e = (PointerPressedEventArgs)ev;
var visual = (IVisual)ev.Source;
#pragma warning disable CS0618 // Type or member is obsolete
var clickCount = e.ClickCount;
#pragma warning restore CS0618 // Type or member is obsolete
if (clickCount <= 1)
if (e.ClickCount <= 1)
{
s_isDoubleTapped = false;
s_lastPress.SetTarget(ev.Source);
}
else if (clickCount == 2 && e.GetCurrentPoint(visual).Properties.IsLeftButtonPressed)
else if (e.ClickCount % 2 == 0 && e.GetCurrentPoint(visual).Properties.IsLeftButtonPressed)
{
if (s_lastPress.TryGetTarget(out var target) && target == e.Source)
{
s_isDoubleTapped = true;
e.Source.RaiseEvent(new TappedEventArgs(DoubleTappedEvent, e));
}
}
else
{
s_isDoubleTapped = false;
}
}
}
@ -112,7 +116,9 @@ namespace Avalonia.Input
{
e.Source.RaiseEvent(new TappedEventArgs(RightTappedEvent, e));
}
else
//s_isDoubleTapped needed here to prevent invoking Tapped event when DoubleTapped is called.
//This behaviour matches UWP behaviour.
else if (s_isDoubleTapped == false)
{
e.Source.RaiseEvent(new TappedEventArgs(TappedEvent, e));
}

9
src/Avalonia.Input/PointerEventArgs.cs

@ -114,7 +114,7 @@ namespace Avalonia.Input
public class PointerPressedEventArgs : PointerEventArgs
{
private readonly int _obsoleteClickCount;
private readonly int _clickCount;
public PointerPressedEventArgs(
IInteractive source,
@ -123,15 +123,14 @@ namespace Avalonia.Input
ulong timestamp,
PointerPointProperties properties,
KeyModifiers modifiers,
int obsoleteClickCount = 1)
int clickCount = 1)
: base(InputElement.PointerPressedEvent, source, pointer, rootVisual, rootVisualPosition,
timestamp, properties, modifiers)
{
_obsoleteClickCount = obsoleteClickCount;
_clickCount = clickCount;
}
[Obsolete("Use DoubleTapped event or Gestures.DoubleRightTapped attached event")]
public int ClickCount => _obsoleteClickCount;
public int ClickCount => _clickCount;
[Obsolete("Use PointerPressedEventArgs.GetCurrentPoint(this).Properties")]
public MouseButton MouseButton => Properties.PointerUpdateKind.GetMouseButton();

44
src/Avalonia.Input/TouchDevice.cs

@ -2,7 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Input.Raw;
using Avalonia.VisualTree;
using Avalonia.Platform;
namespace Avalonia.Input
{
@ -16,7 +16,9 @@ namespace Avalonia.Input
{
private readonly Dictionary<long, Pointer> _pointers = new Dictionary<long, Pointer>();
private bool _disposed;
private int _clickCount;
private Rect _lastClickRect;
private ulong _lastClickTime;
KeyModifiers GetKeyModifiers(RawInputModifiers modifiers) =>
(KeyModifiers)(modifiers & RawInputModifiers.KeyboardMask);
@ -27,10 +29,10 @@ namespace Avalonia.Input
rv |= RawInputModifiers.LeftMouseButton;
return rv;
}
public void ProcessRawEvent(RawInputEventArgs ev)
{
if(_disposed)
if (_disposed)
return;
var args = (RawTouchEventArgs)ev;
if (!_pointers.TryGetValue(args.TouchPointId, out var pointer))
@ -43,16 +45,40 @@ namespace Avalonia.Input
PointerType.Touch, _pointers.Count == 0);
pointer.Capture(hit);
}
var target = pointer.Captured ?? args.Root;
if (args.Type == RawPointerEventType.TouchBegin)
{
if (_pointers.Count > 1)
{
_clickCount = 1;
_lastClickTime = 0;
_lastClickRect = new Rect();
}
else
{
var settings = AvaloniaLocator.Current.GetService<IPlatformSettings>();
if (settings == null)
{
throw new Exception("IPlatformSettings can not be null");
}
if (!_lastClickRect.Contains(args.Position)
|| ev.Timestamp - _lastClickTime > settings.DoubleClickTime.TotalMilliseconds)
{
_clickCount = 0;
}
++_clickCount;
_lastClickTime = ev.Timestamp;
_lastClickRect = new Rect(args.Position, new Size())
.Inflate(new Thickness(16, 16));
}
target.RaiseEvent(new PointerPressedEventArgs(target, pointer,
args.Root, args.Position, ev.Timestamp,
new PointerPointProperties(GetModifiers(args.InputModifiers, true),
PointerUpdateKind.LeftButtonPressed),
GetKeyModifiers(args.InputModifiers)));
GetKeyModifiers(args.InputModifiers), _clickCount));
}
if (args.Type == RawPointerEventType.TouchEnd)
@ -84,12 +110,12 @@ namespace Avalonia.Input
GetKeyModifiers(args.InputModifiers)));
}
}
public void Dispose()
{
if(_disposed)
if (_disposed)
return;
var values = _pointers.Values.ToList();
_pointers.Clear();
@ -97,6 +123,6 @@ namespace Avalonia.Input
foreach (var p in values)
p.Dispose();
}
}
}

272
tests/Avalonia.Input.UnitTests/TouchDeviceTests.cs

@ -0,0 +1,272 @@
using System;
using Avalonia.Input.Raw;
using Avalonia.Platform;
using Avalonia.UnitTests;
using Moq;
using Xunit;
namespace Avalonia.Input.UnitTests
{
public class TouchDeviceTests
{
[Fact]
public void Tapped_Event_Is_Fired_With_Touch()
{
using (UnitTestApplication.Start(
new TestServices(inputManager: new InputManager())))
{
var root = new TestRoot();
var touchDevice = new TouchDevice();
var isTapped = false;
var executedTimes = 0;
root.Tapped += (a, e) =>
{
isTapped = true;
executedTimes++;
};
TapOnce(InputManager.Instance, touchDevice, root);
Assert.True(isTapped);
Assert.Equal(1, executedTimes);
}
}
[Fact]
public void DoubleTapped_Event_Is_Fired_With_Touch()
{
var platformSettingsMock = new Mock<IPlatformSettings>();
platformSettingsMock.Setup(x => x.DoubleClickTime).Returns(new TimeSpan(200));
AvaloniaLocator.CurrentMutable.BindToSelf(this)
.Bind<IPlatformSettings>().ToConstant(platformSettingsMock.Object);
using (UnitTestApplication.Start(
new TestServices(inputManager: new InputManager())))
{
var root = new TestRoot();
var touchDevice = new TouchDevice();
var isDoubleTapped = false;
var doubleTappedExecutedTimes = 0;
var tappedExecutedTimes = 0;
root.DoubleTapped += (a, e) =>
{
isDoubleTapped = true;
doubleTappedExecutedTimes++;
};
root.Tapped += (a, e) =>
{
tappedExecutedTimes++;
};
TapOnce(InputManager.Instance, touchDevice, root);
TapOnce(InputManager.Instance, touchDevice, root, touchPointId: 1);
Assert.Equal(1, tappedExecutedTimes);
Assert.True(isDoubleTapped);
Assert.Equal(1, doubleTappedExecutedTimes);
}
}
[Theory]
[InlineData(1)]
[InlineData(2)]
[InlineData(3)]
[InlineData(4)]
[InlineData(5)]
public void PointerPressed_Counts_Clicks_Correctly(int clickCount)
{
var platformSettingsMock = new Mock<IPlatformSettings>();
platformSettingsMock.Setup(x => x.DoubleClickTime).Returns(new TimeSpan(200));
AvaloniaLocator.CurrentMutable.BindToSelf(this)
.Bind<IPlatformSettings>().ToConstant(platformSettingsMock.Object);
using (UnitTestApplication.Start(
new TestServices(inputManager: new InputManager())))
{
var root = new TestRoot();
var touchDevice = new TouchDevice();
var pointerPressedExecutedTimes = 0;
var pointerPressedClicks = 0;
root.PointerPressed += (a, e) =>
{
pointerPressedClicks = e.ClickCount;
pointerPressedExecutedTimes++;
};
for (int i = 0; i < clickCount; i++)
{
TapOnce(InputManager.Instance, touchDevice, root, touchPointId: i);
}
Assert.Equal(clickCount, pointerPressedExecutedTimes);
Assert.Equal(pointerPressedClicks, clickCount);
}
}
[Fact]
public void DoubleTapped_Not_Fired_When_Click_Too_Late()
{
var platformSettingsMock = new Mock<IPlatformSettings>();
platformSettingsMock.Setup(x => x.DoubleClickTime).Returns(new TimeSpan(0, 0, 0, 0, 20));
AvaloniaLocator.CurrentMutable.BindToSelf(this)
.Bind<IPlatformSettings>().ToConstant(platformSettingsMock.Object);
using (UnitTestApplication.Start(
new TestServices(inputManager: new InputManager())))
{
var root = new TestRoot();
var touchDevice = new TouchDevice();
var isDoubleTapped = false;
var doubleTappedExecutedTimes = 0;
var tappedExecutedTimes = 0;
root.DoubleTapped += (a, e) =>
{
isDoubleTapped = true;
doubleTappedExecutedTimes++;
};
root.Tapped += (a, e) =>
{
tappedExecutedTimes++;
};
TapOnce(InputManager.Instance, touchDevice, root);
TapOnce(InputManager.Instance, touchDevice, root, 21, 1);
Assert.Equal(2, tappedExecutedTimes);
Assert.False(isDoubleTapped);
Assert.Equal(0, doubleTappedExecutedTimes);
}
}
[Fact]
public void DoubleTapped_Not_Fired_When_Second_Click_Is_From_Different_Touch_Contact()
{
var tmp = new Mock<IPlatformSettings>();
tmp.Setup(x => x.DoubleClickTime).Returns(new TimeSpan(200));
AvaloniaLocator.CurrentMutable.BindToSelf(this)
.Bind<IPlatformSettings>().ToConstant(tmp.Object);
using (UnitTestApplication.Start(
new TestServices(inputManager: new InputManager())))
{
var root = new TestRoot();
var touchDevice = new TouchDevice();
var isDoubleTapped = false;
var doubleTappedExecutedTimes = 0;
var tappedExecutedTimes = 0;
root.DoubleTapped += (a, e) =>
{
isDoubleTapped = true;
doubleTappedExecutedTimes++;
};
root.Tapped += (a, e) =>
{
tappedExecutedTimes++;
};
SendXTouchContactsWithIds(InputManager.Instance, touchDevice, root, RawPointerEventType.TouchBegin, 0, 1);
SendXTouchContactsWithIds(InputManager.Instance, touchDevice, root, RawPointerEventType.TouchEnd, 0, 1);
Assert.Equal(2, tappedExecutedTimes);
Assert.False(isDoubleTapped);
Assert.Equal(0, doubleTappedExecutedTimes);
}
}
[Fact]
public void Click_Counting_Should_Work_Correctly_With_Few_Touch_Contacts()
{
var tmp = new Mock<IPlatformSettings>();
tmp.Setup(x => x.DoubleClickTime).Returns(new TimeSpan(200));
AvaloniaLocator.CurrentMutable.BindToSelf(this)
.Bind<IPlatformSettings>().ToConstant(tmp.Object);
using (UnitTestApplication.Start(
new TestServices(inputManager: new InputManager())))
{
var root = new TestRoot();
var touchDevice = new TouchDevice();
var pointerPressedExecutedTimes = 0;
var tappedExecutedTimes = 0;
var isDoubleTapped = false;
var doubleTappedExecutedTimes = 0;
root.PointerPressed += (a, e) =>
{
pointerPressedExecutedTimes++;
switch (pointerPressedExecutedTimes)
{
case <= 2:
Assert.True(e.ClickCount == 1);
break;
case 3:
Assert.True(e.ClickCount == 2);
break;
case 4:
Assert.True(e.ClickCount == 3);
break;
case 5:
Assert.True(e.ClickCount == 4);
break;
case 6:
Assert.True(e.ClickCount == 5);
break;
case 7:
Assert.True(e.ClickCount == 1);
break;
case 8:
Assert.True(e.ClickCount == 1);
break;
case 9:
Assert.True(e.ClickCount == 2);
break;
default:
break;
}
};
root.DoubleTapped += (a, e) =>
{
isDoubleTapped = true;
doubleTappedExecutedTimes++;
};
root.Tapped += (a, e) =>
{
tappedExecutedTimes++;
};
SendXTouchContactsWithIds(InputManager.Instance, touchDevice, root, RawPointerEventType.TouchBegin, 0, 1);
SendXTouchContactsWithIds(InputManager.Instance, touchDevice, root, RawPointerEventType.TouchEnd, 0, 1);
TapOnce(InputManager.Instance, touchDevice, root, touchPointId: 2);
TapOnce(InputManager.Instance, touchDevice, root, touchPointId: 3);
TapOnce(InputManager.Instance, touchDevice, root, touchPointId: 4);
SendXTouchContactsWithIds(InputManager.Instance, touchDevice, root, RawPointerEventType.TouchBegin, 5, 6, 7);
SendXTouchContactsWithIds(InputManager.Instance, touchDevice, root, RawPointerEventType.TouchEnd, 5, 6, 7);
TapOnce(InputManager.Instance, touchDevice, root, touchPointId: 8);
Assert.Equal(6, tappedExecutedTimes);
Assert.Equal(9, pointerPressedExecutedTimes);
Assert.True(isDoubleTapped);
Assert.Equal(3, doubleTappedExecutedTimes);
}
}
private static void SendXTouchContactsWithIds(IInputManager inputManager, TouchDevice device, IInputRoot root, RawPointerEventType type, params long[] touchPointIds)
{
for (int i = 0; i < touchPointIds.Length; i++)
{
inputManager.ProcessInput(new RawTouchEventArgs(device, 0,
root,
type,
new Point(0, 0),
RawInputModifiers.None,
touchPointIds[i]));
}
}
private static void TapOnce(IInputManager inputManager, TouchDevice device, IInputRoot root, ulong timestamp = 0, long touchPointId = 0)
{
inputManager.ProcessInput(new RawTouchEventArgs(device, timestamp,
root,
RawPointerEventType.TouchBegin,
new Point(0, 0),
RawInputModifiers.None,
touchPointId));
inputManager.ProcessInput(new RawTouchEventArgs(device, timestamp,
root,
RawPointerEventType.TouchEnd,
new Point(0, 0),
RawInputModifiers.None,
touchPointId));
}
}
}
Loading…
Cancel
Save