Browse Source

Refactor pointer events to support touch events

pull/2581/head
Nikita Tsukanov 7 years ago
parent
commit
f556db57b4
  1. 13
      src/Avalonia.Controls/Calendar/CalendarButton.cs
  2. 13
      src/Avalonia.Controls/Calendar/CalendarDayButton.cs
  3. 32
      src/Avalonia.Controls/Calendar/CalendarItem.cs
  4. 18
      src/Avalonia.Controls/MenuItem.cs
  5. 2
      src/Avalonia.Input/IMouseDevice.cs
  6. 17
      src/Avalonia.Input/IPointer.cs
  7. 2
      src/Avalonia.Input/IPointerDevice.cs
  8. 134
      src/Avalonia.Input/MouseDevice.cs
  9. 98
      src/Avalonia.Input/PointerEventArgs.cs
  10. 45
      src/Avalonia.Input/PointerPoint.cs
  11. 11
      src/Avalonia.Input/PointerWheelEventArgs.cs
  12. 3
      src/Avalonia.Input/Raw/RawMouseEventArgs.cs
  13. 15
      src/Avalonia.Input/Raw/RawTouchEventArgs.cs
  14. 106
      tests/Avalonia.Controls.UnitTests/ButtonTests.cs
  15. 14
      tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs
  16. 43
      tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs
  17. 9
      tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
  18. 41
      tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs
  19. 114
      tests/Avalonia.Controls.UnitTests/MouseTestHelper.cs
  20. 52
      tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs
  21. 15
      tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
  22. 89
      tests/Avalonia.Controls.UnitTests/TreeViewTests.cs
  23. 2
      tests/Avalonia.Interactivity.UnitTests/Avalonia.Interactivity.UnitTests.csproj
  24. 14
      tests/Avalonia.Interactivity.UnitTests/GestureTests.cs
  25. 13
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs

13
src/Avalonia.Controls/Calendar/CalendarButton.cs

@ -176,18 +176,5 @@ namespace Avalonia.Controls.Primitives
if (e.MouseButton == MouseButton.Left)
CalendarLeftMouseButtonUp?.Invoke(this, e);
}
/// <summary>
/// We need to simulate the MouseLeftButtonUp event for the
/// CalendarButton that stays in Pressed state after MouseCapture is
/// released since there is no actual MouseLeftButtonUp event for the
/// release.
/// </summary>
/// <param name="e">Event arguments.</param>
internal void SendMouseLeftButtonUp(PointerReleasedEventArgs e)
{
e.Handled = false;
base.OnPointerReleased(e);
}
}
}

13
src/Avalonia.Controls/Calendar/CalendarDayButton.cs

@ -234,18 +234,5 @@ namespace Avalonia.Controls.Primitives
if (e.MouseButton == MouseButton.Left)
CalendarDayButtonMouseUp?.Invoke(this, e);
}
/// <summary>
/// We need to simulate the MouseLeftButtonUp event for the
/// CalendarDayButton that stays in Pressed state after MouseCapture is
/// released since there is no actual MouseLeftButtonUp event for the
/// release.
/// </summary>
/// <param name="e">Event arguments.</param>
internal void SendMouseLeftButtonUp(PointerReleasedEventArgs e)
{
e.Handled = false;
base.OnPointerReleased(e);
}
}
}

32
src/Avalonia.Controls/Calendar/CalendarItem.cs

@ -934,22 +934,6 @@ namespace Avalonia.Controls.Primitives
// The button is in Pressed state. Change the state to normal.
if (e.Device.Captured == b)
e.Device.Capture(null);
// null check is added for unit tests
if (_downEventArg != null)
{
var arg =
new PointerReleasedEventArgs()
{
Device = _downEventArg.Device,
MouseButton = _downEventArg.MouseButton,
Handled = _downEventArg.Handled,
InputModifiers = _downEventArg.InputModifiers,
Route = _downEventArg.Route,
Source = _downEventArg.Source
};
b.SendMouseLeftButtonUp(arg);
}
_lastCalendarDayButton = b;
}
}
@ -1221,21 +1205,7 @@ namespace Avalonia.Controls.Primitives
if (e.Device.Captured == b)
e.Device.Capture(null);
//b.ReleaseMouseCapture();
if (_downEventArgYearView != null)
{
var args =
new PointerReleasedEventArgs()
{
Device = _downEventArgYearView.Device,
MouseButton = _downEventArgYearView.MouseButton,
Handled = _downEventArgYearView.Handled,
InputModifiers = _downEventArgYearView.InputModifiers,
Route = _downEventArgYearView.Route,
Source = _downEventArgYearView.Source
};
b.SendMouseLeftButtonUp(args);
}
_lastCalendarButton = b;
}
}

18
src/Avalonia.Controls/MenuItem.cs

@ -337,12 +337,9 @@ namespace Avalonia.Controls
{
base.OnPointerEnter(e);
RaiseEvent(new PointerEventArgs
{
Device = e.Device,
RoutedEvent = PointerEnterItemEvent,
Source = this,
});
var point = e.GetPointerPoint(null);
RaiseEvent(new PointerEventArgs(PointerEnterItemEvent, this, e.Pointer, this.VisualRoot, point.Position,
point.Properties, e.InputModifiers));
}
/// <inheritdoc/>
@ -350,12 +347,9 @@ namespace Avalonia.Controls
{
base.OnPointerLeave(e);
RaiseEvent(new PointerEventArgs
{
Device = e.Device,
RoutedEvent = PointerLeaveItemEvent,
Source = this,
});
var point = e.GetPointerPoint(null);
RaiseEvent(new PointerEventArgs(PointerLeaveItemEvent, this, e.Pointer, this.VisualRoot, point.Position,
point.Properties, e.InputModifiers));
}
/// <summary>

2
src/Avalonia.Input/IMouseDevice.cs

@ -12,5 +12,7 @@ namespace Avalonia.Input
/// Gets the mouse position, in screen coordinates.
/// </summary>
PixelPoint Position { get; }
void SceneInvalidated(IInputRoot root, Rect rect);
}
}

17
src/Avalonia.Input/IPointer.cs

@ -0,0 +1,17 @@
namespace Avalonia.Input
{
public interface IPointer
{
void Capture(IInputElement control);
IInputElement Captured { get; }
PointerType Type { get; }
bool IsPrimary { get; }
}
public enum PointerType
{
Mouse,
Touch
}
}

2
src/Avalonia.Input/IPointerDevice.cs

@ -12,7 +12,5 @@ namespace Avalonia.Input
void Capture(IInputElement control);
Point GetPosition(IVisual relativeTo);
void SceneInvalidated(IInputRoot root, Rect rect);
}
}

134
src/Avalonia.Input/MouseDevice.cs

@ -14,14 +14,17 @@ namespace Avalonia.Input
/// <summary>
/// Represents a mouse device.
/// </summary>
public class MouseDevice : IMouseDevice
public class MouseDevice : IMouseDevice, IPointer
{
private int _clickCount;
private Rect _lastClickRect;
private ulong _lastClickTime;
private IInputElement _captured;
private IDisposable _capturedSubscription;
PointerType IPointer.Type => PointerType.Mouse;
bool IPointer.IsPrimary => true;
/// <summary>
/// Gets the control that is currently capturing by the mouse, if any.
/// </summary>
@ -117,6 +120,18 @@ namespace Avalonia.Input
}
}
int ButtonCount(PointerPointProperties props)
{
var rv = 0;
if (props.IsLeftButtonPressed)
rv++;
if (props.IsMiddleButtonPressed)
rv++;
if (props.IsRightButtonPressed)
rv++;
return rv;
}
private void ProcessRawEvent(RawMouseEventArgs e)
{
Contract.Requires<ArgumentNullException>(e != null);
@ -124,7 +139,7 @@ namespace Avalonia.Input
var mouse = (IMouseDevice)e.Device;
Position = e.Root.PointToScreen(e.Position);
var props = CreateProperties(e);
switch (e.Type)
{
case RawMouseEventType.LeaveWindow:
@ -133,26 +148,25 @@ namespace Avalonia.Input
case RawMouseEventType.LeftButtonDown:
case RawMouseEventType.RightButtonDown:
case RawMouseEventType.MiddleButtonDown:
e.Handled = MouseDown(mouse, e.Timestamp, e.Root, e.Position,
e.Type == RawMouseEventType.LeftButtonDown
? MouseButton.Left
: e.Type == RawMouseEventType.RightButtonDown ? MouseButton.Right : MouseButton.Middle,
e.InputModifiers);
if (ButtonCount(props) > 1)
e.Handled = MouseMove(mouse, e.Root, e.Position, props, e.InputModifiers);
else
e.Handled = MouseDown(mouse, e.Timestamp, e.Root, e.Position,
props, e.InputModifiers);
break;
case RawMouseEventType.LeftButtonUp:
case RawMouseEventType.RightButtonUp:
case RawMouseEventType.MiddleButtonUp:
e.Handled = MouseUp(mouse, e.Root, e.Position,
e.Type == RawMouseEventType.LeftButtonUp
? MouseButton.Left
: e.Type == RawMouseEventType.RightButtonUp ? MouseButton.Right : MouseButton.Middle,
e.InputModifiers);
if (ButtonCount(props) != 0)
e.Handled = MouseMove(mouse, e.Root, e.Position, props, e.InputModifiers);
else
e.Handled = MouseUp(mouse, e.Root, e.Position, props, e.InputModifiers);
break;
case RawMouseEventType.Move:
e.Handled = MouseMove(mouse, e.Root, e.Position, e.InputModifiers);
e.Handled = MouseMove(mouse, e.Root, e.Position, props, e.InputModifiers);
break;
case RawMouseEventType.Wheel:
e.Handled = MouseWheel(mouse, e.Root, e.Position, ((RawMouseWheelEventArgs)e).Delta, e.InputModifiers);
e.Handled = MouseWheel(mouse, e.Root, e.Position, props, ((RawMouseWheelEventArgs)e).Delta, e.InputModifiers);
break;
}
}
@ -165,7 +179,30 @@ namespace Avalonia.Input
ClearPointerOver(this, root, inputModifiers);
}
private bool MouseDown(IMouseDevice device, ulong timestamp, IInputElement root, Point p, MouseButton button, InputModifiers inputModifiers)
PointerPointProperties CreateProperties(RawMouseEventArgs args)
{
var rv = new PointerPointProperties(args.InputModifiers);
if (args.Type == RawMouseEventType.LeftButtonDown)
rv.IsLeftButtonPressed = true;
if (args.Type == RawMouseEventType.MiddleButtonDown)
rv.IsMiddleButtonPressed = true;
if (args.Type == RawMouseEventType.RightButtonDown)
rv.IsRightButtonPressed = true;
if (args.Type == RawMouseEventType.LeftButtonUp)
rv.IsLeftButtonPressed = false;
if (args.Type == RawMouseEventType.MiddleButtonUp)
rv.IsMiddleButtonPressed = false;
if (args.Type == RawMouseEventType.RightButtonDown)
rv.IsRightButtonPressed = false;
return rv;
}
private MouseButton _lastMouseDownButton;
private bool MouseDown(IMouseDevice device, ulong timestamp, IInputElement root, Point p,
PointerPointProperties properties,
InputModifiers inputModifiers)
{
Contract.Requires<ArgumentNullException>(device != null);
Contract.Requires<ArgumentNullException>(root != null);
@ -190,16 +227,8 @@ namespace Avalonia.Input
_lastClickTime = timestamp;
_lastClickRect = new Rect(p, new Size())
.Inflate(new Thickness(settings.DoubleClickSize.Width / 2, settings.DoubleClickSize.Height / 2));
var e = new PointerPressedEventArgs
{
Device = this,
RoutedEvent = InputElement.PointerPressedEvent,
Source = source,
ClickCount = _clickCount,
MouseButton = button,
InputModifiers = inputModifiers
};
_lastMouseDownButton = properties.GetObsoleteMouseButton();
var e = new PointerPressedEventArgs(source, this, root, p, properties, inputModifiers, _clickCount);
source.RaiseEvent(e);
return e.Handled;
@ -209,7 +238,8 @@ namespace Avalonia.Input
return false;
}
private bool MouseMove(IMouseDevice device, IInputRoot root, Point p, InputModifiers inputModifiers)
private bool MouseMove(IMouseDevice device, IInputRoot root, Point p, PointerPointProperties properties,
InputModifiers inputModifiers)
{
Contract.Requires<ArgumentNullException>(device != null);
Contract.Requires<ArgumentNullException>(root != null);
@ -226,19 +256,15 @@ namespace Avalonia.Input
source = Captured;
}
var e = new PointerEventArgs
{
Device = this,
RoutedEvent = InputElement.PointerMovedEvent,
Source = source,
InputModifiers = inputModifiers
};
var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, this, root,
p, properties, inputModifiers);
source?.RaiseEvent(e);
return e.Handled;
}
private bool MouseUp(IMouseDevice device, IInputRoot root, Point p, MouseButton button, InputModifiers inputModifiers)
private bool MouseUp(IMouseDevice device, IInputRoot root, Point p, PointerPointProperties props,
InputModifiers inputModifiers)
{
Contract.Requires<ArgumentNullException>(device != null);
Contract.Requires<ArgumentNullException>(root != null);
@ -248,14 +274,7 @@ namespace Avalonia.Input
if (hit != null)
{
var source = GetSource(hit);
var e = new PointerReleasedEventArgs
{
Device = this,
RoutedEvent = InputElement.PointerReleasedEvent,
Source = source,
MouseButton = button,
InputModifiers = inputModifiers
};
var e = new PointerReleasedEventArgs(source, this, root, p, props, inputModifiers, _lastMouseDownButton);
source?.RaiseEvent(e);
return e.Handled;
@ -264,7 +283,9 @@ namespace Avalonia.Input
return false;
}
private bool MouseWheel(IMouseDevice device, IInputRoot root, Point p, Vector delta, InputModifiers inputModifiers)
private bool MouseWheel(IMouseDevice device, IInputRoot root, Point p,
PointerPointProperties props,
Vector delta, InputModifiers inputModifiers)
{
Contract.Requires<ArgumentNullException>(device != null);
Contract.Requires<ArgumentNullException>(root != null);
@ -274,14 +295,7 @@ namespace Avalonia.Input
if (hit != null)
{
var source = GetSource(hit);
var e = new PointerWheelEventArgs
{
Device = this,
RoutedEvent = InputElement.PointerWheelChangedEvent,
Source = source,
Delta = delta,
InputModifiers = inputModifiers
};
var e = new PointerWheelEventArgs(source, this, root, p, props, inputModifiers, delta);
source?.RaiseEvent(e);
return e.Handled;
@ -306,18 +320,19 @@ namespace Avalonia.Input
return Captured ?? root.InputHitTest(p);
}
PointerEventArgs CreateSimpleEvent(RoutedEvent ev, IInteractive source, InputModifiers inputModifiers)
{
return new PointerEventArgs(ev, source, this, null, default,
new PointerPointProperties(inputModifiers), inputModifiers);
}
private void ClearPointerOver(IPointerDevice device, IInputRoot root, InputModifiers inputModifiers)
{
Contract.Requires<ArgumentNullException>(device != null);
Contract.Requires<ArgumentNullException>(root != null);
var element = root.PointerOverElement;
var e = new PointerEventArgs
{
RoutedEvent = InputElement.PointerLeaveEvent,
Device = device,
InputModifiers = inputModifiers
};
var e = CreateSimpleEvent(InputElement.PointerLeaveEvent, element, inputModifiers);
if (element!=null && !element.IsAttachedToVisualTree)
{
@ -384,7 +399,6 @@ namespace Avalonia.Input
IInputElement branch = null;
var e = new PointerEventArgs { Device = device, InputModifiers = inputModifiers };
var el = element;
while (el != null)
@ -398,8 +412,8 @@ namespace Avalonia.Input
}
el = root.PointerOverElement;
e.RoutedEvent = InputElement.PointerLeaveEvent;
var e = CreateSimpleEvent(InputElement.PointerLeaveEvent, el, inputModifiers);
if (el!=null && branch!=null && !el.IsAttachedToVisualTree)
{
ClearChildrenPointerOver(e,branch,false);

98
src/Avalonia.Input/PointerEventArgs.cs

@ -1,6 +1,8 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Avalonia.Input.Raw;
using Avalonia.Interactivity;
using Avalonia.VisualTree;
@ -8,25 +10,68 @@ namespace Avalonia.Input
{
public class PointerEventArgs : RoutedEventArgs
{
public PointerEventArgs()
{
private readonly IVisual _rootVisual;
private readonly Point _rootVisualPosition;
private readonly PointerPointProperties _properties;
public PointerEventArgs(RoutedEvent routedEvent,
IInteractive source,
IPointer pointer,
IVisual rootVisual, Point rootVisualPosition, PointerPointProperties properties,
InputModifiers modifiers)
: base(routedEvent)
{
Source = source;
_rootVisual = rootVisual;
_rootVisualPosition = rootVisualPosition;
_properties = properties;
Pointer = pointer;
InputModifiers = modifiers;
#pragma warning disable 612
#pragma warning disable 618
Device = new EmulatedDevice(this);
#pragma warning restore 618
#pragma warning restore 612
}
public PointerEventArgs(RoutedEvent routedEvent)
: base(routedEvent)
class EmulatedDevice : IPointerDevice
{
private readonly PointerEventArgs _ev;
public EmulatedDevice(PointerEventArgs ev)
{
_ev = ev;
}
public void ProcessRawEvent(RawInputEventArgs ev) => throw new NotSupportedException();
public IInputElement Captured => _ev.Pointer.Captured;
public void Capture(IInputElement control)
{
_ev.Pointer.Capture(control);
}
public Point GetPosition(IVisual relativeTo) => _ev.GetPosition(relativeTo);
}
public IPointerDevice Device { get; set; }
public IPointer Pointer { get; }
[Obsolete("Use Pointer to get pointer-specific information")]
public IPointerDevice Device { get; }
public InputModifiers InputModifiers { get; set; }
public InputModifiers InputModifiers { get; }
public Point GetPosition(IVisual relativeTo)
{
return Device.GetPosition(relativeTo);
if (_rootVisual == null)
return default;
if (relativeTo == null)
return _rootVisualPosition;
return _rootVisualPosition * _rootVisual.TransformToVisual(relativeTo) ?? default;
}
public PointerPoint GetPointerPoint(IVisual relativeTo)
=> new PointerPoint(Pointer, GetPosition(relativeTo), _properties);
}
public enum MouseButton
@ -39,32 +84,39 @@ namespace Avalonia.Input
public class PointerPressedEventArgs : PointerEventArgs
{
public PointerPressedEventArgs()
: base(InputElement.PointerPressedEvent)
{
}
private readonly int _obsoleteClickCount;
public PointerPressedEventArgs(RoutedEvent routedEvent)
: base(routedEvent)
public PointerPressedEventArgs(
IInteractive source,
IPointer pointer,
IVisual rootVisual, Point rootVisualPosition, PointerPointProperties properties,
InputModifiers modifiers,
int obsoleteClickCount = 1)
: base(InputElement.PointerPressedEvent, source, pointer, rootVisual, rootVisualPosition, properties,
modifiers)
{
_obsoleteClickCount = obsoleteClickCount;
}
public int ClickCount { get; set; }
public MouseButton MouseButton { get; set; }
[Obsolete("Use DoubleTapped or DoubleRightTapped event instead")]
public int ClickCount => _obsoleteClickCount;
[Obsolete] public MouseButton MouseButton => GetPointerPoint(null).Properties.GetObsoleteMouseButton();
}
public class PointerReleasedEventArgs : PointerEventArgs
{
public PointerReleasedEventArgs()
: base(InputElement.PointerReleasedEvent)
{
}
public PointerReleasedEventArgs(RoutedEvent routedEvent)
: base(routedEvent)
public PointerReleasedEventArgs(
IInteractive source, IPointer pointer,
IVisual rootVisual, Point rootVisualPosition, PointerPointProperties properties, InputModifiers modifiers,
MouseButton obsoleteMouseButton)
: base(InputElement.PointerReleasedEvent, source, pointer, rootVisual, rootVisualPosition,
properties, modifiers)
{
MouseButton = obsoleteMouseButton;
}
public MouseButton MouseButton { get; set; }
[Obsolete()]
public MouseButton MouseButton { get; private set; }
}
}

45
src/Avalonia.Input/PointerPoint.cs

@ -0,0 +1,45 @@
namespace Avalonia.Input
{
public sealed class PointerPoint
{
public PointerPoint(IPointer pointer, Point position, PointerPointProperties properties)
{
Pointer = pointer;
Position = position;
Properties = properties;
}
public IPointer Pointer { get; }
public PointerPointProperties Properties { get; }
public Point Position { get; }
}
public sealed class PointerPointProperties
{
public bool IsLeftButtonPressed { get; set; }
public bool IsMiddleButtonPressed { get; set; }
public bool IsRightButtonPressed { get; set; }
public PointerPointProperties()
{
}
public PointerPointProperties(InputModifiers modifiers)
{
IsLeftButtonPressed = modifiers.HasFlag(InputModifiers.LeftMouseButton);
IsMiddleButtonPressed = modifiers.HasFlag(InputModifiers.MiddleMouseButton);
IsRightButtonPressed = modifiers.HasFlag(InputModifiers.RightMouseButton);
}
public MouseButton GetObsoleteMouseButton()
{
if (IsLeftButtonPressed)
return MouseButton.Left;
if (IsMiddleButtonPressed)
return MouseButton.Middle;
if (IsRightButtonPressed)
return MouseButton.Right;
return MouseButton.None;
}
}
}

11
src/Avalonia.Input/PointerWheelEventArgs.cs

@ -1,10 +1,21 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Interactivity;
using Avalonia.VisualTree;
namespace Avalonia.Input
{
public class PointerWheelEventArgs : PointerEventArgs
{
public Vector Delta { get; set; }
public PointerWheelEventArgs(IInteractive source, IPointer pointer, IVisual rootVisual,
Point rootVisualPosition,
PointerPointProperties properties, InputModifiers modifiers, Vector delta)
: base(InputElement.PointerWheelChangedEvent, source, pointer, rootVisual, rootVisualPosition, properties, modifiers)
{
Delta = delta;
}
}
}

3
src/Avalonia.Input/Raw/RawMouseEventArgs.cs

@ -17,6 +17,9 @@ namespace Avalonia.Input.Raw
Move,
Wheel,
NonClientLeftButtonDown,
TouchBegin,
TouchUpdate,
TouchEnd
}
/// <summary>

15
src/Avalonia.Input/Raw/RawTouchEventArgs.cs

@ -0,0 +1,15 @@
namespace Avalonia.Input.Raw
{
public class RawTouchEventArgs : RawMouseEventArgs
{
public RawTouchEventArgs(IInputDevice device, ulong timestamp, IInputRoot root,
RawMouseEventType type, Point position, InputModifiers inputModifiers,
long touchPointId)
: base(device, timestamp, root, type, position, inputModifiers)
{
TouchPointId = touchPointId;
}
public long TouchPointId { get; set; }
}
}

106
tests/Avalonia.Controls.UnitTests/ButtonTests.cs

@ -14,6 +14,8 @@ namespace Avalonia.Controls.UnitTests
{
public class ButtonTests
{
private MouseTestHelper _helper = new MouseTestHelper();
[Fact]
public void Button_Is_Disabled_When_Command_Is_Disabled()
{
@ -102,12 +104,8 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Button_Raises_Click()
{
var mouse = Mock.Of<IMouseDevice>();
var renderer = Mock.Of<IRenderer>();
IInputElement captured = null;
Mock.Get(mouse).Setup(m => m.GetPosition(It.IsAny<IVisual>())).Returns(new Point(50, 50));
Mock.Get(mouse).Setup(m => m.Capture(It.IsAny<IInputElement>())).Callback<IInputElement>(v => captured = v);
Mock.Get(mouse).Setup(m => m.Captured).Returns(() => captured);
var pt = new Point(50, 50);
Mock.Get(renderer).Setup(r => r.HitTest(It.IsAny<Point>(), It.IsAny<IVisual>(), It.IsAny<Func<IVisual, bool>>()))
.Returns<Point, IVisual, Func<IVisual, bool>>((p, r, f) =>
r.Bounds.Contains(p) ? new IVisual[] { r } : new IVisual[0]);
@ -122,15 +120,15 @@ namespace Avalonia.Controls.UnitTests
target.Click += (s, e) => clicked = true;
RaisePointerEnter(target, mouse);
RaisePointerMove(target, mouse);
RaisePointerPressed(target, mouse, 1, MouseButton.Left);
RaisePointerEnter(target);
RaisePointerMove(target, pt);
RaisePointerPressed(target, 1, MouseButton.Left, pt);
Assert.Equal(captured, target);
Assert.Equal(_helper.Captured, target);
RaisePointerReleased(target, mouse, MouseButton.Left);
RaisePointerReleased(target, MouseButton.Left, pt);
Assert.Equal(captured, null);
Assert.Equal(_helper.Captured, null);
Assert.True(clicked);
}
@ -138,12 +136,8 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Button_Does_Not_Raise_Click_When_PointerReleased_Outside()
{
var mouse = Mock.Of<IMouseDevice>();
var renderer = Mock.Of<IRenderer>();
IInputElement captured = null;
Mock.Get(mouse).Setup(m => m.GetPosition(It.IsAny<IVisual>())).Returns(new Point(200, 50));
Mock.Get(mouse).Setup(m => m.Capture(It.IsAny<IInputElement>())).Callback<IInputElement>(v => captured = v);
Mock.Get(mouse).Setup(m => m.Captured).Returns(() => captured);
Mock.Get(renderer).Setup(r => r.HitTest(It.IsAny<Point>(), It.IsAny<IVisual>(), It.IsAny<Func<IVisual, bool>>()))
.Returns<Point, IVisual, Func<IVisual, bool>>((p, r, f) =>
r.Bounds.Contains(p) ? new IVisual[] { r } : new IVisual[0]);
@ -158,16 +152,16 @@ namespace Avalonia.Controls.UnitTests
target.Click += (s, e) => clicked = true;
RaisePointerEnter(target, mouse);
RaisePointerMove(target, mouse);
RaisePointerPressed(target, mouse, 1, MouseButton.Left);
RaisePointerLeave(target, mouse);
RaisePointerEnter(target);
RaisePointerMove(target, new Point(50,50));
RaisePointerPressed(target, 1, MouseButton.Left, new Point(50, 50));
RaisePointerLeave(target);
Assert.Equal(captured, target);
Assert.Equal(_helper.Captured, target);
RaisePointerReleased(target, mouse, MouseButton.Left);
RaisePointerReleased(target, MouseButton.Left, new Point(200, 50));
Assert.Equal(captured, null);
Assert.Equal(_helper.Captured, null);
Assert.False(clicked);
}
@ -175,12 +169,8 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Button_With_RenderTransform_Raises_Click()
{
var mouse = Mock.Of<IMouseDevice>();
var renderer = Mock.Of<IRenderer>();
IInputElement captured = null;
Mock.Get(mouse).Setup(m => m.GetPosition(It.IsAny<IVisual>())).Returns(new Point(150, 50));
Mock.Get(mouse).Setup(m => m.Capture(It.IsAny<IInputElement>())).Callback<IInputElement>(v => captured = v);
Mock.Get(mouse).Setup(m => m.Captured).Returns(() => captured);
var pt = new Point(150, 50);
Mock.Get(renderer).Setup(r => r.HitTest(It.IsAny<Point>(), It.IsAny<IVisual>(), It.IsAny<Func<IVisual, bool>>()))
.Returns<Point, IVisual, Func<IVisual, bool>>((p, r, f) =>
r.Bounds.Contains(p.Transform(r.RenderTransform.Value.Invert())) ?
@ -204,15 +194,15 @@ namespace Avalonia.Controls.UnitTests
target.Click += (s, e) => clicked = true;
RaisePointerEnter(target, mouse);
RaisePointerMove(target, mouse);
RaisePointerPressed(target, mouse, 1, MouseButton.Left);
RaisePointerEnter(target);
RaisePointerMove(target, pt);
RaisePointerPressed(target, 1, MouseButton.Left, pt);
Assert.Equal(captured, target);
Assert.Equal(_helper.Captured, target);
RaisePointerReleased(target, mouse, MouseButton.Left);
RaisePointerReleased(target, MouseButton.Left, pt);
Assert.Equal(captured, null);
Assert.Equal(_helper.Captured, null);
Assert.True(clicked);
}
@ -278,57 +268,29 @@ namespace Avalonia.Controls.UnitTests
public PixelPoint PointToScreen(Point p) => throw new NotImplementedException();
}
private void RaisePointerPressed(Button button, IMouseDevice device, int clickCount, MouseButton mouseButton)
private void RaisePointerPressed(Button button, int clickCount, MouseButton mouseButton, Point position)
{
button.RaiseEvent(new PointerPressedEventArgs
{
RoutedEvent = InputElement.PointerPressedEvent,
Source = button,
MouseButton = mouseButton,
ClickCount = clickCount,
Device = device,
});
_helper.Down(button, mouseButton, position, clickCount: clickCount);
}
private void RaisePointerReleased(Button button, IMouseDevice device, MouseButton mouseButton)
private void RaisePointerReleased(Button button, MouseButton mouseButton, Point pt)
{
button.RaiseEvent(new PointerReleasedEventArgs
{
RoutedEvent = InputElement.PointerReleasedEvent,
Source = button,
MouseButton = mouseButton,
Device = device,
});
_helper.Up(button, mouseButton, pt);
}
private void RaisePointerEnter(Button button, IMouseDevice device)
private void RaisePointerEnter(Button button)
{
button.RaiseEvent(new PointerEventArgs
{
RoutedEvent = InputElement.PointerEnterEvent,
Source = button,
Device = device,
});
_helper.Enter(button);
}
private void RaisePointerLeave(Button button, IMouseDevice device)
private void RaisePointerLeave(Button button)
{
button.RaiseEvent(new PointerEventArgs
{
RoutedEvent = InputElement.PointerLeaveEvent,
Source = button,
Device = device,
});
_helper.Leave(button);
}
private void RaisePointerMove(Button button, IMouseDevice device)
private void RaisePointerMove(Button button, Point pos)
{
button.RaiseEvent(new PointerEventArgs
{
RoutedEvent = InputElement.PointerMovedEvent,
Source = button,
Device = device,
});
_helper.Move(button, pos);
}
private class TestCommand : ICommand

14
tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs

@ -15,6 +15,8 @@ namespace Avalonia.Controls.UnitTests
{
public class ComboBoxTests
{
MouseTestHelper _helper = new MouseTestHelper();
[Fact]
public void Clicking_On_Control_Toggles_IsDropDownOpen()
{
@ -23,17 +25,11 @@ namespace Avalonia.Controls.UnitTests
Items = new[] { "Foo", "Bar" },
};
target.RaiseEvent(new PointerPressedEventArgs
{
RoutedEvent = InputElement.PointerPressedEvent,
});
_helper.Down(target);
_helper.Up(target);
Assert.True(target.IsDropDownOpen);
target.RaiseEvent(new PointerPressedEventArgs
{
RoutedEvent = InputElement.PointerPressedEvent,
});
_helper.Down(target);
Assert.False(target.IsDropDownOpen);
}

43
tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs

@ -14,6 +14,7 @@ namespace Avalonia.Controls.UnitTests
public class ContextMenuTests
{
private Mock<IPopupImpl> popupImpl;
private MouseTestHelper _mouse = new MouseTestHelper();
[Fact]
public void Clicking_On_Control_Toggles_ContextMenu()
@ -31,19 +32,11 @@ namespace Avalonia.Controls.UnitTests
new Window { Content = target };
target.RaiseEvent(new PointerReleasedEventArgs
{
RoutedEvent = InputElement.PointerReleasedEvent,
MouseButton = MouseButton.Right
});
_mouse.Click(target, MouseButton.Right);
Assert.True(sut.IsOpen);
target.RaiseEvent(new PointerReleasedEventArgs
{
RoutedEvent = InputElement.PointerReleasedEvent,
MouseButton = MouseButton.None
});
_mouse.Click(target);
Assert.False(sut.IsOpen);
popupImpl.Verify(x => x.Show(), Times.Once);
@ -69,19 +62,11 @@ namespace Avalonia.Controls.UnitTests
Avalonia.Application.Current.MainWindow = window;
target.RaiseEvent(new PointerReleasedEventArgs
{
RoutedEvent = InputElement.PointerReleasedEvent,
MouseButton = MouseButton.Right
});
_mouse.Click(target, MouseButton.Right);
Assert.True(sut.IsOpen);
target.RaiseEvent(new PointerReleasedEventArgs
{
RoutedEvent = InputElement.PointerReleasedEvent,
MouseButton = MouseButton.Right
});
_mouse.Click(target, MouseButton.Right);
Assert.True(sut.IsOpen);
popupImpl.Verify(x => x.Hide(), Times.Once);
@ -106,11 +91,7 @@ namespace Avalonia.Controls.UnitTests
sut.ContextMenuOpening += (c, e) => { eventCalled = true; e.Cancel = true; };
target.RaiseEvent(new PointerReleasedEventArgs
{
RoutedEvent = InputElement.PointerReleasedEvent,
MouseButton = MouseButton.Right
});
_mouse.Click(target, MouseButton.Right);
Assert.True(eventCalled);
Assert.False(sut.IsOpen);
@ -136,19 +117,11 @@ namespace Avalonia.Controls.UnitTests
sut.ContextMenuClosing += (c, e) => { eventCalled = true; e.Cancel = true; };
target.RaiseEvent(new PointerReleasedEventArgs
{
RoutedEvent = InputElement.PointerReleasedEvent,
MouseButton = MouseButton.Right
});
_mouse.Click(target, MouseButton.Right);
Assert.True(sut.IsOpen);
target.RaiseEvent(new PointerReleasedEventArgs
{
RoutedEvent = InputElement.PointerReleasedEvent,
MouseButton = MouseButton.None
});
_mouse.Click(target, MouseButton.Right);
Assert.True(eventCalled);
Assert.True(sut.IsOpen);

9
tests/Avalonia.Controls.UnitTests/ListBoxTests.cs

@ -16,6 +16,8 @@ namespace Avalonia.Controls.UnitTests
{
public class ListBoxTests
{
private MouseTestHelper _mouse = new MouseTestHelper();
[Fact]
public void Should_Use_ItemTemplate_To_Create_Item_Content()
{
@ -225,12 +227,7 @@ namespace Avalonia.Controls.UnitTests
private void RaisePressedEvent(ListBox listBox, ListBoxItem item, MouseButton mouseButton)
{
listBox.RaiseEvent(new PointerPressedEventArgs
{
Source = item,
RoutedEvent = InputElement.PointerPressedEvent,
MouseButton = mouseButton
});
_mouse.Click(listBox, item, mouseButton);
}
[Fact]

41
tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs

@ -18,6 +18,8 @@ namespace Avalonia.Controls.UnitTests
{
public class ListBoxTests_Single
{
MouseTestHelper _mouse = new MouseTestHelper();
[Fact]
public void Focusing_Item_With_Tab_Should_Not_Select_It()
{
@ -68,12 +70,7 @@ namespace Avalonia.Controls.UnitTests
};
ApplyTemplate(target);
target.Presenter.Panel.Children[0].RaiseEvent(new PointerPressedEventArgs
{
RoutedEvent = InputElement.PointerPressedEvent,
MouseButton = MouseButton.Left,
});
_mouse.Click(target.Presenter.Panel.Children[0]);
Assert.Equal(0, target.SelectedIndex);
}
@ -90,11 +87,7 @@ namespace Avalonia.Controls.UnitTests
ApplyTemplate(target);
target.SelectedIndex = 0;
target.Presenter.Panel.Children[0].RaiseEvent(new PointerPressedEventArgs
{
RoutedEvent = InputElement.PointerPressedEvent,
MouseButton = MouseButton.Left,
});
_mouse.Click(target.Presenter.Panel.Children[0]);
Assert.Equal(0, target.SelectedIndex);
}
@ -111,11 +104,7 @@ namespace Avalonia.Controls.UnitTests
ApplyTemplate(target);
target.Presenter.Panel.Children[0].RaiseEvent(new PointerPressedEventArgs
{
RoutedEvent = InputElement.PointerPressedEvent,
MouseButton = MouseButton.Left,
});
_mouse.Click(target.Presenter.Panel.Children[0]);
Assert.Equal(0, target.SelectedIndex);
}
@ -133,11 +122,7 @@ namespace Avalonia.Controls.UnitTests
ApplyTemplate(target);
target.SelectedIndex = 0;
target.Presenter.Panel.Children[0].RaiseEvent(new PointerPressedEventArgs
{
RoutedEvent = InputElement.PointerPressedEvent,
MouseButton = MouseButton.Left,
});
_mouse.Click(target.Presenter.Panel.Children[0]);
Assert.Equal(-1, target.SelectedIndex);
}
@ -155,11 +140,7 @@ namespace Avalonia.Controls.UnitTests
ApplyTemplate(target);
target.SelectedIndex = 0;
target.Presenter.Panel.Children[0].RaiseEvent(new PointerPressedEventArgs
{
RoutedEvent = InputElement.PointerPressedEvent,
MouseButton = MouseButton.Left,
});
_mouse.Click(target.Presenter.Panel.Children[0]);
Assert.Equal(0, target.SelectedIndex);
}
@ -177,11 +158,7 @@ namespace Avalonia.Controls.UnitTests
ApplyTemplate(target);
target.SelectedIndex = 1;
target.Presenter.Panel.Children[0].RaiseEvent(new PointerPressedEventArgs
{
RoutedEvent = InputElement.PointerPressedEvent,
MouseButton = MouseButton.Left,
});
_mouse.Click(target.Presenter.Panel.Children[0]);
Assert.Equal(0, target.SelectedIndex);
}
@ -306,4 +283,4 @@ namespace Avalonia.Controls.UnitTests
target.Presenter.ApplyTemplate();
}
}
}
}

114
tests/Avalonia.Controls.UnitTests/MouseTestHelper.cs

@ -0,0 +1,114 @@
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.VisualTree;
namespace Avalonia.Controls.UnitTests
{
public class MouseTestHelper
{
class TestPointer : IPointer
{
public void Capture(IInputElement control)
{
Captured = control;
}
public IInputElement Captured { get; set; }
public PointerType Type => PointerType.Mouse;
public bool IsPrimary => true;
}
TestPointer _pointer = new TestPointer();
private InputModifiers _pressedButtons;
public IInputElement Captured => _pointer.Captured;
InputModifiers Convert(MouseButton mouseButton)
=> (mouseButton == MouseButton.Left ? InputModifiers.LeftMouseButton
: mouseButton == MouseButton.Middle ? InputModifiers.MiddleMouseButton
: mouseButton == MouseButton.Right ? InputModifiers.RightMouseButton : InputModifiers.None);
int ButtonCount(PointerPointProperties props)
{
var rv = 0;
if (props.IsLeftButtonPressed)
rv++;
if (props.IsMiddleButtonPressed)
rv++;
if (props.IsRightButtonPressed)
rv++;
return rv;
}
private MouseButton _pressedButton;
InputModifiers GetModifiers(InputModifiers modifiers) => modifiers | _pressedButtons;
public void Down(IInteractive target, MouseButton mouseButton = MouseButton.Left, Point position = default,
InputModifiers modifiers = default, int clickCount = 1)
=> Down(target, target, mouseButton, position, modifiers, clickCount);
public void Down(IInteractive target, IInteractive source, MouseButton mouseButton = MouseButton.Left,
Point position = default, InputModifiers modifiers = default, int clickCount = 1)
{
_pressedButtons |= Convert(mouseButton);
var props = new PointerPointProperties(_pressedButtons);
if (ButtonCount(props) > 1)
Move(target, source, position);
else
{
_pressedButton = mouseButton;
target.RaiseEvent(new PointerPressedEventArgs(source, _pointer, (IVisual)source, position, props,
GetModifiers(modifiers), clickCount));
}
}
public void Move(IInteractive target, in Point position, InputModifiers modifiers = default) => Move(target, target, position, modifiers);
public void Move(IInteractive target, IInteractive source, in Point position, InputModifiers modifiers = default)
{
target.RaiseEvent(new PointerEventArgs(InputElement.PointerMovedEvent, source, _pointer, (IVisual)target, position,
new PointerPointProperties(_pressedButtons), GetModifiers(modifiers)));
}
public void Up(IInteractive target, MouseButton mouseButton = MouseButton.Left, Point position = default,
InputModifiers modifiers = default)
=> Up(target, target, mouseButton, position, modifiers);
public void Up(IInteractive target, IInteractive source, MouseButton mouseButton = MouseButton.Left,
Point position = default, InputModifiers modifiers = default)
{
var conv = Convert(mouseButton);
_pressedButtons = (_pressedButtons | conv) ^ conv;
var props = new PointerPointProperties(_pressedButtons);
if (ButtonCount(props) == 0)
target.RaiseEvent(new PointerReleasedEventArgs(source, _pointer, (IVisual)target, position, props,
GetModifiers(modifiers), _pressedButton));
else
Move(target, source, position);
}
public void Click(IInteractive target, MouseButton button = MouseButton.Left, Point position = default,
InputModifiers modifiers = default)
=> Click(target, target, button, position, modifiers);
public void Click(IInteractive target, IInteractive source, MouseButton button = MouseButton.Left,
Point position = default, InputModifiers modifiers = default)
{
Down(target, source, button, position, modifiers);
Up(target, source, button, position, modifiers);
}
public void Enter(IInteractive target)
{
target.RaiseEvent(new PointerEventArgs(InputElement.PointerEnterEvent, target, _pointer, (IVisual)target, default,
new PointerPointProperties(_pressedButtons), _pressedButtons));
}
public void Leave(IInteractive target)
{
target.RaiseEvent(new PointerEventArgs(InputElement.PointerLeaveEvent, target, _pointer, (IVisual)target, default,
new PointerPointProperties(_pressedButtons), _pressedButtons));
}
}
}

52
tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs

@ -2,6 +2,7 @@
using Avalonia.Controls.Platform;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.VisualTree;
using Moq;
using Xunit;
@ -9,6 +10,28 @@ namespace Avalonia.Controls.UnitTests.Platform
{
public class DefaultMenuInteractionHandlerTests
{
class FakePointer : IPointer
{
public void Capture(IInputElement control)
{
Captured = control;
}
public IInputElement Captured { get; set; }
public PointerType Type { get; }
public bool IsPrimary { get; } = true;
}
static PointerEventArgs CreateArgs(RoutedEvent ev, IInteractive source)
=> new PointerEventArgs(ev, source, new FakePointer(), (IVisual)source, default, new PointerPointProperties(), default);
static PointerPressedEventArgs CreatePressed(IInteractive source) => new PointerPressedEventArgs(source,
new FakePointer(), (IVisual)source, default, new PointerPointProperties {IsLeftButtonPressed = true},
default);
static PointerReleasedEventArgs CreateReleased(IInteractive source) => new PointerReleasedEventArgs(source,
new FakePointer(), (IVisual)source, default, new PointerPointProperties(), default, MouseButton.Left);
public class TopLevel
{
[Fact]
@ -121,7 +144,8 @@ namespace Avalonia.Controls.UnitTests.Platform
x.IsTopLevel == true &&
x.HasSubMenu == true &&
x.Parent == menu);
var e = new PointerPressedEventArgs { MouseButton = MouseButton.Left, Source = item };
var e = CreatePressed(item);
target.PointerPressed(item, e);
Mock.Get(menu).Verify(x => x.Close());
@ -141,7 +165,7 @@ namespace Avalonia.Controls.UnitTests.Platform
x.IsTopLevel == true &&
x.HasSubMenu == true &&
x.Parent == menu.Object);
var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerEnterItemEvent, Source = nextItem };
var e = CreateArgs(MenuItem.PointerEnterItemEvent, nextItem);
menu.SetupGet(x => x.SelectedItem).Returns(item);
@ -161,7 +185,7 @@ namespace Avalonia.Controls.UnitTests.Platform
var target = new DefaultMenuInteractionHandler(false);
var menu = new Mock<IMenu>();
var item = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.Parent == menu.Object);
var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerLeaveItemEvent, Source = item };
var e = CreateArgs(MenuItem.PointerLeaveItemEvent, item);
menu.SetupGet(x => x.SelectedItem).Returns(item);
target.PointerLeave(item, e);
@ -176,7 +200,7 @@ namespace Avalonia.Controls.UnitTests.Platform
var target = new DefaultMenuInteractionHandler(false);
var menu = new Mock<IMenu>();
var item = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.Parent == menu.Object);
var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerLeaveItemEvent, Source = item };
var e = CreateArgs(MenuItem.PointerLeaveItemEvent, item);
menu.SetupGet(x => x.IsOpen).Returns(true);
menu.SetupGet(x => x.SelectedItem).Returns(item);
@ -330,7 +354,7 @@ namespace Avalonia.Controls.UnitTests.Platform
var menu = Mock.Of<IMenu>();
var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem);
var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerEnterItemEvent, Source = item };
var e = CreateArgs(MenuItem.PointerEnterItemEvent, item);
target.PointerEnter(item, e);
@ -346,7 +370,7 @@ namespace Avalonia.Controls.UnitTests.Platform
var menu = Mock.Of<IMenu>();
var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem && x.HasSubMenu == true);
var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerEnterItemEvent, Source = item };
var e = CreateArgs(MenuItem.PointerEnterItemEvent, item);
target.PointerEnter(item, e);
Mock.Get(item).Verify(x => x.Open(), Times.Never);
@ -366,7 +390,7 @@ namespace Avalonia.Controls.UnitTests.Platform
var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem);
var sibling = Mock.Of<IMenuItem>(x => x.Parent == parentItem && x.HasSubMenu == true && x.IsSubMenuOpen == true);
var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerEnterItemEvent, Source = item };
var e = CreateArgs(MenuItem.PointerEnterItemEvent, item);
Mock.Get(parentItem).SetupGet(x => x.SubItems).Returns(new[] { item, sibling });
@ -386,7 +410,7 @@ namespace Avalonia.Controls.UnitTests.Platform
var menu = Mock.Of<IMenu>();
var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem);
var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerLeaveItemEvent, Source = item };
var e = CreateArgs(MenuItem.PointerLeaveItemEvent, item);
Mock.Get(parentItem).SetupGet(x => x.SelectedItem).Returns(item);
target.PointerLeave(item, e);
@ -403,7 +427,7 @@ namespace Avalonia.Controls.UnitTests.Platform
var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem);
var sibling = Mock.Of<IMenuItem>(x => x.Parent == parentItem);
var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerLeaveItemEvent, Source = item };
var e = CreateArgs(MenuItem.PointerLeaveItemEvent, item);
Mock.Get(parentItem).SetupGet(x => x.SelectedItem).Returns(sibling);
target.PointerLeave(item, e);
@ -419,7 +443,7 @@ namespace Avalonia.Controls.UnitTests.Platform
var menu = Mock.Of<IMenu>();
var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem && x.HasSubMenu == true && x.IsPointerOverSubMenu == true);
var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerLeaveItemEvent, Source = item };
var e = CreateArgs(MenuItem.PointerLeaveItemEvent, item);
target.PointerLeave(item, e);
@ -434,7 +458,7 @@ namespace Avalonia.Controls.UnitTests.Platform
var menu = Mock.Of<IMenu>();
var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem);
var e = new PointerReleasedEventArgs { MouseButton = MouseButton.Left, Source = item };
var e = CreateReleased(item);
target.PointerReleased(item, e);
@ -452,8 +476,8 @@ namespace Avalonia.Controls.UnitTests.Platform
var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem && x.HasSubMenu == true);
var childItem = Mock.Of<IMenuItem>(x => x.Parent == item);
var enter = new PointerEventArgs { RoutedEvent = MenuItem.PointerEnterItemEvent, Source = item };
var leave = new PointerEventArgs { RoutedEvent = MenuItem.PointerLeaveItemEvent, Source = item };
var enter = CreateArgs(MenuItem.PointerEnterItemEvent, item);
var leave = CreateArgs(MenuItem.PointerLeaveItemEvent, item);
// Pointer enters item; item is selected.
target.PointerEnter(item, enter);
@ -488,7 +512,7 @@ namespace Avalonia.Controls.UnitTests.Platform
var menu = Mock.Of<IMenu>();
var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem && x.HasSubMenu == true);
var e = new PointerPressedEventArgs { MouseButton = MouseButton.Left, Source = item };
var e = CreatePressed(item);
target.PointerPressed(item, e);

15
tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs

@ -22,6 +22,8 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
public class SelectingItemsControlTests
{
private MouseTestHelper _helper = new MouseTestHelper();
[Fact]
public void SelectedIndex_Should_Initially_Be_Minus_1()
{
@ -675,12 +677,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
target.Presenter.Panel.Children[1].RaiseEvent(new PointerPressedEventArgs
{
RoutedEvent = InputElement.PointerPressedEvent,
MouseButton = MouseButton.Left,
});
_helper.Down((Interactive)target.Presenter.Panel.Children[1]);
var panel = target.Presenter.Panel;
@ -703,11 +700,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
target.Presenter.Panel.Children[1].RaiseEvent(new PointerPressedEventArgs
{
RoutedEvent = InputElement.PointerPressedEvent,
MouseButton = MouseButton.Left,
});
_helper.Down(target.Presenter.Panel.Children[1]);
items.RemoveAt(1);

89
tests/Avalonia.Controls.UnitTests/TreeViewTests.cs

@ -19,6 +19,8 @@ namespace Avalonia.Controls.UnitTests
{
public class TreeViewTests
{
MouseTestHelper _mouse = new MouseTestHelper();
[Fact]
public void Items_Should_Be_Created()
{
@ -130,11 +132,7 @@ namespace Avalonia.Controls.UnitTests
Assert.NotNull(container);
container.RaiseEvent(new PointerPressedEventArgs
{
RoutedEvent = InputElement.PointerPressedEvent,
MouseButton = MouseButton.Left,
});
_mouse.Click(container);
Assert.Equal(item, target.SelectedItem);
Assert.True(container.IsSelected);
@ -165,12 +163,7 @@ namespace Avalonia.Controls.UnitTests
Assert.True(container.IsSelected);
container.RaiseEvent(new PointerPressedEventArgs
{
RoutedEvent = InputElement.PointerPressedEvent,
MouseButton = MouseButton.Left,
InputModifiers = InputModifiers.Control
});
_mouse.Click(container, modifiers: InputModifiers.Control);
Assert.Null(target.SelectedItem);
Assert.False(container.IsSelected);
@ -205,13 +198,8 @@ namespace Avalonia.Controls.UnitTests
Assert.True(container1.IsSelected);
container2.RaiseEvent(new PointerPressedEventArgs
{
RoutedEvent = InputElement.PointerPressedEvent,
MouseButton = MouseButton.Left,
InputModifiers = InputModifiers.Control
});
_mouse.Click(container2, modifiers: InputModifiers.Control);
Assert.Equal(item2, target.SelectedItem);
Assert.False(container1.IsSelected);
Assert.True(container2.IsSelected);
@ -242,15 +230,15 @@ namespace Avalonia.Controls.UnitTests
var item1Container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item1);
var item2Container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item2);
TreeTestHelper.ClickContainer(item1Container, InputModifiers.Control);
ClickContainer(item1Container, InputModifiers.Control);
Assert.True(item1Container.IsSelected);
TreeTestHelper.ClickContainer(item2Container, InputModifiers.Control);
ClickContainer(item2Container, InputModifiers.Control);
Assert.True(item2Container.IsSelected);
Assert.Equal(new[] {item1, item2}, target.SelectedItems.OfType<Node>());
TreeTestHelper.ClickContainer(item1Container, InputModifiers.Control);
ClickContainer(item1Container, InputModifiers.Control);
Assert.False(item1Container.IsSelected);
Assert.DoesNotContain(item1, target.SelectedItems.OfType<Node>());
@ -281,12 +269,12 @@ namespace Avalonia.Controls.UnitTests
var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from);
var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to);
TreeTestHelper.ClickContainer(fromContainer, InputModifiers.None);
ClickContainer(fromContainer, InputModifiers.None);
Assert.True(fromContainer.IsSelected);
TreeTestHelper.ClickContainer(toContainer, InputModifiers.Shift);
TreeTestHelper.AssertChildrenSelected(target, rootNode);
ClickContainer(toContainer, InputModifiers.Shift);
AssertChildrenSelected(target, rootNode);
}
[Fact]
@ -314,12 +302,12 @@ namespace Avalonia.Controls.UnitTests
var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from);
var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to);
TreeTestHelper.ClickContainer(fromContainer, InputModifiers.None);
ClickContainer(fromContainer, InputModifiers.None);
Assert.True(fromContainer.IsSelected);
TreeTestHelper.ClickContainer(toContainer, InputModifiers.Shift);
TreeTestHelper.AssertChildrenSelected(target, rootNode);
ClickContainer(toContainer, InputModifiers.Shift);
AssertChildrenSelected(target, rootNode);
}
[Fact]
@ -347,12 +335,12 @@ namespace Avalonia.Controls.UnitTests
var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from);
var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to);
TreeTestHelper.ClickContainer(fromContainer, InputModifiers.None);
ClickContainer(fromContainer, InputModifiers.None);
TreeTestHelper.ClickContainer(toContainer, InputModifiers.Shift);
TreeTestHelper.AssertChildrenSelected(target, rootNode);
ClickContainer(toContainer, InputModifiers.Shift);
AssertChildrenSelected(target, rootNode);
TreeTestHelper.ClickContainer(fromContainer, InputModifiers.None);
ClickContainer(fromContainer, InputModifiers.None);
Assert.True(fromContainer.IsSelected);
@ -656,7 +644,7 @@ namespace Avalonia.Controls.UnitTests
target.RaiseEvent(keyEvent);
TreeTestHelper.AssertChildrenSelected(target, rootNode);
AssertChildrenSelected(target, rootNode);
}
}
@ -687,8 +675,8 @@ namespace Avalonia.Controls.UnitTests
var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from);
var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to);
TreeTestHelper.ClickContainer(fromContainer, InputModifiers.None);
TreeTestHelper.ClickContainer(toContainer, InputModifiers.Shift);
ClickContainer(fromContainer, InputModifiers.None);
ClickContainer(toContainer, InputModifiers.Shift);
var keymap = AvaloniaLocator.Current.GetService<PlatformHotkeyConfiguration>();
var selectAllGesture = keymap.SelectAll.First();
@ -702,7 +690,7 @@ namespace Avalonia.Controls.UnitTests
target.RaiseEvent(keyEvent);
TreeTestHelper.AssertChildrenSelected(target, rootNode);
AssertChildrenSelected(target, rootNode);
}
}
@ -733,8 +721,8 @@ namespace Avalonia.Controls.UnitTests
var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from);
var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to);
TreeTestHelper.ClickContainer(fromContainer, InputModifiers.None);
TreeTestHelper.ClickContainer(toContainer, InputModifiers.Shift);
ClickContainer(fromContainer, InputModifiers.None);
ClickContainer(toContainer, InputModifiers.Shift);
var keymap = AvaloniaLocator.Current.GetService<PlatformHotkeyConfiguration>();
var selectAllGesture = keymap.SelectAll.First();
@ -748,7 +736,7 @@ namespace Avalonia.Controls.UnitTests
target.RaiseEvent(keyEvent);
TreeTestHelper.AssertChildrenSelected(target, rootNode);
AssertChildrenSelected(target, rootNode);
}
}
@ -871,29 +859,22 @@ namespace Avalonia.Controls.UnitTests
}
}
private static class TreeTestHelper
void ClickContainer(IControl container, InputModifiers modifiers)
{
public static void ClickContainer(IControl container, InputModifiers modifiers)
{
container.RaiseEvent(new PointerPressedEventArgs
{
RoutedEvent = InputElement.PointerPressedEvent,
MouseButton = MouseButton.Left,
InputModifiers = modifiers
});
}
_mouse.Click(container, modifiers: modifiers);
}
public static void AssertChildrenSelected(TreeView treeView, Node rootNode)
void AssertChildrenSelected(TreeView treeView, Node rootNode)
{
foreach (var child in rootNode.Children)
{
foreach (var child in rootNode.Children)
{
var container = (TreeViewItem)treeView.ItemContainerGenerator.Index.ContainerFromItem(child);
var container = (TreeViewItem)treeView.ItemContainerGenerator.Index.ContainerFromItem(child);
Assert.True(container.IsSelected);
}
Assert.True(container.IsSelected);
}
}
private class Node : NotifyingBase
{
private IAvaloniaList<Node> _children;

2
tests/Avalonia.Interactivity.UnitTests/Avalonia.Interactivity.UnitTests.csproj

@ -4,6 +4,7 @@
<TargetFrameworks>netcoreapp2.0;net47</TargetFrameworks>
<OutputType>Library</OutputType>
<IsTestProject>true</IsTestProject>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<Import Project="..\..\build\UnitTests.NetCore.targets" />
<Import Project="..\..\build\UnitTests.NetFX.props" />
@ -19,6 +20,7 @@
<ProjectReference Include="..\..\src\Avalonia.Layout\Avalonia.Layout.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Visuals\Avalonia.Visuals.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Styling\Avalonia.Styling.csproj" />
<Compile Include="..\Avalonia.Controls.UnitTests\MouseTestHelper.cs" />
</ItemGroup>
<ItemGroup>
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />

14
tests/Avalonia.Interactivity.UnitTests/GestureTests.cs

@ -3,6 +3,7 @@
using System.Collections.Generic;
using Avalonia.Controls;
using Avalonia.Controls.UnitTests;
using Avalonia.Input;
using Xunit;
@ -10,6 +11,8 @@ namespace Avalonia.Interactivity.UnitTests
{
public class GestureTests
{
private MouseTestHelper _mouse = new MouseTestHelper();
[Fact]
public void Tapped_Should_Follow_Pointer_Pressed_Released()
{
@ -27,8 +30,7 @@ namespace Avalonia.Interactivity.UnitTests
border.AddHandler(Border.PointerReleasedEvent, (s, e) => result.Add("br"));
border.AddHandler(Gestures.TappedEvent, (s, e) => result.Add("bt"));
border.RaiseEvent(new PointerPressedEventArgs());
border.RaiseEvent(new PointerReleasedEventArgs());
_mouse.Click(border);
Assert.Equal(new[] { "bp", "dp", "br", "dr", "bt", "dt" }, result);
}
@ -47,8 +49,7 @@ namespace Avalonia.Interactivity.UnitTests
decorator.AddHandler(Gestures.TappedEvent, (s, e) => result.Add("dt"));
border.AddHandler(Gestures.TappedEvent, (s, e) => result.Add("bt"));
border.RaiseEvent(new PointerPressedEventArgs());
border.RaiseEvent(new PointerReleasedEventArgs());
_mouse.Click(border);
Assert.Equal(new[] { "bt", "dt" }, result);
}
@ -72,9 +73,8 @@ namespace Avalonia.Interactivity.UnitTests
border.AddHandler(Gestures.TappedEvent, (s, e) => result.Add("bt"));
border.AddHandler(Gestures.DoubleTappedEvent, (s, e) => result.Add("bdt"));
border.RaiseEvent(new PointerPressedEventArgs());
border.RaiseEvent(new PointerReleasedEventArgs());
border.RaiseEvent(new PointerPressedEventArgs { ClickCount = 2 });
_mouse.Click(border);
_mouse.Down(border, clickCount: 2);
Assert.Equal(new[] { "bp", "dp", "br", "dr", "bt", "dt", "bp", "dp", "bdt", "ddt" }, result);
}

13
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs

@ -137,8 +137,9 @@ namespace Avalonia.Markup.Xaml.UnitTests
.GetVisualChildren().First()
.GetVisualChildren().First()
.GetVisualChildren().First();
((Control)item).RaiseEvent(new PointerPressedEventArgs {ClickCount = 20});
Assert.Equal(20, w.Args.ClickCount);
((Control)item).DataContext = "test";
Assert.Equal("test", w.SavedContext);
}
}
@ -161,10 +162,10 @@ namespace Avalonia.Markup.Xaml.UnitTests
public class XamlIlBugTestsEventHandlerCodeBehind : Window
{
public PointerPressedEventArgs Args;
public void HandlePointerPressed(object sender, PointerPressedEventArgs args)
public object SavedContext;
public void HandleDataContextChanged(object sender, EventArgs args)
{
Args = args;
SavedContext = ((Control)sender).DataContext;
}
public XamlIlBugTestsEventHandlerCodeBehind()
@ -178,7 +179,7 @@ namespace Avalonia.Markup.Xaml.UnitTests
<ItemsControl>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button PointerPressed='HandlePointerPressed' Content='{Binding .}' />
<Button DataContextChanged='HandleDataContextChanged' Content='{Binding .}' />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

Loading…
Cancel
Save