Browse Source

Merge pull request #1811 from AvaloniaUI/fixes/pointer-enter-leave

Fix pointer enter/leave events
pull/1819/head
danwalmsley 8 years ago
committed by GitHub
parent
commit
1412c1aca1
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 26
      src/Avalonia.Input/InputElement.cs
  2. 24
      src/Avalonia.Input/MouseDevice.cs
  3. 159
      tests/Avalonia.Input.UnitTests/MouseDeviceTests.cs

26
src/Avalonia.Input/InputElement.cs

@ -162,8 +162,8 @@ namespace Avalonia.Input
KeyDownEvent.AddClassHandler<InputElement>(x => x.OnKeyDown);
KeyUpEvent.AddClassHandler<InputElement>(x => x.OnKeyUp);
TextInputEvent.AddClassHandler<InputElement>(x => x.OnTextInput);
PointerEnterEvent.AddClassHandler<InputElement>(x => x.OnPointerEnter);
PointerLeaveEvent.AddClassHandler<InputElement>(x => x.OnPointerLeave);
PointerEnterEvent.AddClassHandler<InputElement>(x => x.OnPointerEnterCore);
PointerLeaveEvent.AddClassHandler<InputElement>(x => x.OnPointerLeaveCore);
PointerMovedEvent.AddClassHandler<InputElement>(x => x.OnPointerMoved);
PointerPressedEvent.AddClassHandler<InputElement>(x => x.OnPointerPressed);
PointerReleasedEvent.AddClassHandler<InputElement>(x => x.OnPointerReleased);
@ -445,7 +445,6 @@ namespace Avalonia.Input
/// <param name="e">The event args.</param>
protected virtual void OnPointerEnter(PointerEventArgs e)
{
IsPointerOver = true;
}
/// <summary>
@ -454,7 +453,6 @@ namespace Avalonia.Input
/// <param name="e">The event args.</param>
protected virtual void OnPointerLeave(PointerEventArgs e)
{
IsPointerOver = false;
}
/// <summary>
@ -494,6 +492,26 @@ namespace Avalonia.Input
((InputElement)e.Sender).UpdateIsEnabledCore();
}
/// <summary>
/// Called before the <see cref="PointerEnter"/> event occurs.
/// </summary>
/// <param name="e">The event args.</param>
private void OnPointerEnterCore(PointerEventArgs e)
{
IsPointerOver = true;
OnPointerEnter(e);
}
/// <summary>
/// Called before the <see cref="PointerLeave"/> event occurs.
/// </summary>
/// <param name="e">The event args.</param>
private void OnPointerLeaveCore(PointerEventArgs e)
{
IsPointerOver = false;
OnPointerLeave(e);
}
/// <summary>
/// Updates the <see cref="IsEnabledCore"/> property value.
/// </summary>

24
src/Avalonia.Input/MouseDevice.cs

@ -346,12 +346,7 @@ namespace Avalonia.Input
IInputElement branch = null;
var e = new PointerEventArgs
{
RoutedEvent = InputElement.PointerEnterEvent,
Device = device,
};
var e = new PointerEventArgs { Device = device, };
var el = element;
while (el != null)
@ -361,9 +356,6 @@ namespace Avalonia.Input
branch = el;
break;
}
e.Source = el;
el.RaiseEvent(e);
el = (IInputElement)el.VisualParent;
}
@ -373,11 +365,21 @@ namespace Avalonia.Input
while (el != null && el != branch)
{
e.Source = el;
e.Handled = false;
el.RaiseEvent(e);
el = (IInputElement)el.VisualParent;
}
root.PointerOverElement = element;
el = root.PointerOverElement = element;
e.RoutedEvent = InputElement.PointerEnterEvent;
while (el != null && el != branch)
{
e.Source = el;
e.Handled = false;
el.RaiseEvent(e);
el = (IInputElement)el.VisualParent;
}
}
}
}
}

159
tests/Avalonia.Input.UnitTests/MouseDeviceTests.cs

@ -1,10 +1,12 @@
using Avalonia.Controls;
using Avalonia.Input.Raw;
using Avalonia.Interactivity;
using Avalonia.Rendering;
using Avalonia.UnitTests;
using Avalonia.VisualTree;
using Moq;
using System;
using System.Collections.Generic;
using Xunit;
namespace Avalonia.Input.UnitTests
@ -30,7 +32,7 @@ namespace Avalonia.Input.UnitTests
}
[Fact]
public void MouseMove_Should_Update_PointerOver()
public void MouseMove_Should_Update_IsPointerOver()
{
var renderer = new Mock<IRenderer>();
@ -59,40 +61,157 @@ namespace Avalonia.Input.UnitTests
}
};
renderer.Setup(x => x.HitTest(It.IsAny<Point>(), It.IsAny<IVisual>(), It.IsAny<Func<IVisual, bool>>()))
.Returns(new[] { decorator });
inputManager.ProcessInput(new RawMouseEventArgs(
root.MouseDevice,
0,
root,
RawMouseEventType.Move,
new Point(),
InputModifiers.None));
SetHit(renderer, decorator);
SendMouseMove(inputManager, root);
Assert.True(decorator.IsPointerOver);
Assert.True(border.IsPointerOver);
Assert.False(canvas.IsPointerOver);
Assert.True(root.IsPointerOver);
renderer.Setup(x => x.HitTest(It.IsAny<Point>(), It.IsAny<IVisual>(), It.IsAny<Func<IVisual, bool>>()))
.Returns(new[] { canvas });
SetHit(renderer, canvas);
SendMouseMove(inputManager, root);
Assert.False(decorator.IsPointerOver);
Assert.False(border.IsPointerOver);
Assert.True(canvas.IsPointerOver);
Assert.True(root.IsPointerOver);
}
}
[Fact]
public void IsPointerOver_Should_Be_Updated_When_Child_Sets_Handled_True()
{
var renderer = new Mock<IRenderer>();
using (TestApplication(renderer.Object))
{
var inputManager = InputManager.Instance;
inputManager.ProcessInput(new RawMouseEventArgs(
root.MouseDevice,
0,
root,
RawMouseEventType.Move,
new Point(),
InputModifiers.None));
Canvas canvas;
Border border;
Decorator decorator;
var root = new TestRoot
{
MouseDevice = new MouseDevice(),
Renderer = renderer.Object,
Child = new Panel
{
Children =
{
(canvas = new Canvas()),
(border = new Border
{
Child = decorator = new Decorator(),
})
}
}
};
SetHit(renderer, canvas);
SendMouseMove(inputManager, root);
Assert.False(decorator.IsPointerOver);
Assert.False(border.IsPointerOver);
Assert.True(canvas.IsPointerOver);
Assert.True(root.IsPointerOver);
// Ensure that e.Handled is reset between controls.
decorator.PointerEnter += (s, e) => e.Handled = true;
SetHit(renderer, decorator);
SendMouseMove(inputManager, root);
Assert.True(decorator.IsPointerOver);
Assert.True(border.IsPointerOver);
Assert.False(canvas.IsPointerOver);
Assert.True(root.IsPointerOver);
}
}
[Fact]
public void PointerEnter_Leave_Should_Be_Raised_In_Correct_Order()
{
var renderer = new Mock<IRenderer>();
var result = new List<(object, string)>();
void HandleEvent(object sender, PointerEventArgs e)
{
result.Add((sender, e.RoutedEvent.Name));
}
using (TestApplication(renderer.Object))
{
var inputManager = InputManager.Instance;
Canvas canvas;
Border border;
Decorator decorator;
var root = new TestRoot
{
MouseDevice = new MouseDevice(),
Renderer = renderer.Object,
Child = new Panel
{
Children =
{
(canvas = new Canvas()),
(border = new Border
{
Child = decorator = new Decorator(),
})
}
}
};
SetHit(renderer, canvas);
SendMouseMove(inputManager, root);
AddEnterLeaveHandlers(HandleEvent, root, canvas, border, decorator);
SetHit(renderer, decorator);
SendMouseMove(inputManager, root);
Assert.Equal(
new[]
{
((object)canvas, "PointerLeave"),
((object)decorator, "PointerEnter"),
((object)border, "PointerEnter"),
},
result);
}
}
private void AddEnterLeaveHandlers(
EventHandler<PointerEventArgs> handler,
params IControl[] controls)
{
foreach (var c in controls)
{
c.PointerEnter += handler;
c.PointerLeave += handler;
}
}
private void SendMouseMove(IInputManager inputManager, TestRoot root)
{
inputManager.ProcessInput(new RawMouseEventArgs(
root.MouseDevice,
0,
root,
RawMouseEventType.Move,
new Point(),
InputModifiers.None));
}
private void SetHit(Mock<IRenderer> renderer, IControl hit)
{
renderer.Setup(x => x.HitTest(It.IsAny<Point>(), It.IsAny<IVisual>(), It.IsAny<Func<IVisual, bool>>()))
.Returns(new[] { hit });
}
private IDisposable TestApplication(IRenderer renderer)
{
return UnitTestApplication.Start(

Loading…
Cancel
Save