From 3f0c7650e5230e818544b22c410c518a237aea81 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 13 Aug 2018 09:09:37 +0200 Subject: [PATCH 1/2] Make sure IsPointerOver is correctly set. Reset the handled state for `PointerEnter`/`PointerLeave` on each control invocation, and don't allow the user to skip setting `IsPointerOver` --- src/Avalonia.Input/InputElement.cs | 26 +++++- src/Avalonia.Input/MouseDevice.cs | 4 +- .../MouseDeviceTests.cs | 92 +++++++++++++++---- 3 files changed, 97 insertions(+), 25 deletions(-) diff --git a/src/Avalonia.Input/InputElement.cs b/src/Avalonia.Input/InputElement.cs index 9935dbe27c..82e626f9c6 100644 --- a/src/Avalonia.Input/InputElement.cs +++ b/src/Avalonia.Input/InputElement.cs @@ -162,8 +162,8 @@ namespace Avalonia.Input KeyDownEvent.AddClassHandler(x => x.OnKeyDown); KeyUpEvent.AddClassHandler(x => x.OnKeyUp); TextInputEvent.AddClassHandler(x => x.OnTextInput); - PointerEnterEvent.AddClassHandler(x => x.OnPointerEnter); - PointerLeaveEvent.AddClassHandler(x => x.OnPointerLeave); + PointerEnterEvent.AddClassHandler(x => x.OnPointerEnterCore); + PointerLeaveEvent.AddClassHandler(x => x.OnPointerLeaveCore); PointerMovedEvent.AddClassHandler(x => x.OnPointerMoved); PointerPressedEvent.AddClassHandler(x => x.OnPointerPressed); PointerReleasedEvent.AddClassHandler(x => x.OnPointerReleased); @@ -445,7 +445,6 @@ namespace Avalonia.Input /// The event args. protected virtual void OnPointerEnter(PointerEventArgs e) { - IsPointerOver = true; } /// @@ -454,7 +453,6 @@ namespace Avalonia.Input /// The event args. protected virtual void OnPointerLeave(PointerEventArgs e) { - IsPointerOver = false; } /// @@ -494,6 +492,26 @@ namespace Avalonia.Input ((InputElement)e.Sender).UpdateIsEnabledCore(); } + /// + /// Called before the event occurs. + /// + /// The event args. + private void OnPointerEnterCore(PointerEventArgs e) + { + IsPointerOver = true; + OnPointerEnter(e); + } + + /// + /// Called before the event occurs. + /// + /// The event args. + private void OnPointerLeaveCore(PointerEventArgs e) + { + IsPointerOver = false; + OnPointerLeave(e); + } + /// /// Updates the property value. /// diff --git a/src/Avalonia.Input/MouseDevice.cs b/src/Avalonia.Input/MouseDevice.cs index 50cdb5945c..af1953fa9e 100644 --- a/src/Avalonia.Input/MouseDevice.cs +++ b/src/Avalonia.Input/MouseDevice.cs @@ -363,6 +363,7 @@ namespace Avalonia.Input } e.Source = el; + e.Handled = false; el.RaiseEvent(e); el = (IInputElement)el.VisualParent; } @@ -373,6 +374,7 @@ namespace Avalonia.Input while (el != null && el != branch) { e.Source = el; + e.Handled = false; el.RaiseEvent(e); el = (IInputElement)el.VisualParent; } @@ -380,4 +382,4 @@ namespace Avalonia.Input root.PointerOverElement = element; } } -} \ No newline at end of file +} diff --git a/tests/Avalonia.Input.UnitTests/MouseDeviceTests.cs b/tests/Avalonia.Input.UnitTests/MouseDeviceTests.cs index 7764c47dbf..a732742b0b 100644 --- a/tests/Avalonia.Input.UnitTests/MouseDeviceTests.cs +++ b/tests/Avalonia.Input.UnitTests/MouseDeviceTests.cs @@ -30,7 +30,7 @@ namespace Avalonia.Input.UnitTests } [Fact] - public void MouseMove_Should_Update_PointerOver() + public void MouseMove_Should_Update_IsPointerOver() { var renderer = new Mock(); @@ -59,40 +59,92 @@ namespace Avalonia.Input.UnitTests } }; - renderer.Setup(x => x.HitTest(It.IsAny(), It.IsAny(), It.IsAny>())) - .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(), It.IsAny(), It.IsAny>())) - .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(); + + 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(), + }) + } + } + }; - inputManager.ProcessInput(new RawMouseEventArgs( - root.MouseDevice, - 0, - root, - RawMouseEventType.Move, - new Point(), - InputModifiers.None)); + 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); } } + 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 renderer, IControl hit) + { + renderer.Setup(x => x.HitTest(It.IsAny(), It.IsAny(), It.IsAny>())) + .Returns(new[] { hit }); + } + private IDisposable TestApplication(IRenderer renderer) { return UnitTestApplication.Start( From 895a24fb9e55b613cb738816b11f6f2e71205d45 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 13 Aug 2018 09:27:54 +0200 Subject: [PATCH 2/2] Raise pointer enter/leave events in correct order. Previously, enter events were raised before leave events. --- src/Avalonia.Input/MouseDevice.cs | 20 +++--- .../MouseDeviceTests.cs | 67 +++++++++++++++++++ 2 files changed, 77 insertions(+), 10 deletions(-) diff --git a/src/Avalonia.Input/MouseDevice.cs b/src/Avalonia.Input/MouseDevice.cs index af1953fa9e..7689f55d80 100644 --- a/src/Avalonia.Input/MouseDevice.cs +++ b/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,15 +356,22 @@ namespace Avalonia.Input branch = el; break; } + el = (IInputElement)el.VisualParent; + } + el = root.PointerOverElement; + e.RoutedEvent = InputElement.PointerLeaveEvent; + + while (el != null && el != branch) + { e.Source = el; e.Handled = false; el.RaiseEvent(e); el = (IInputElement)el.VisualParent; } - el = root.PointerOverElement; - e.RoutedEvent = InputElement.PointerLeaveEvent; + el = root.PointerOverElement = element; + e.RoutedEvent = InputElement.PointerEnterEvent; while (el != null && el != branch) { @@ -378,8 +380,6 @@ namespace Avalonia.Input el.RaiseEvent(e); el = (IInputElement)el.VisualParent; } - - root.PointerOverElement = element; } } } diff --git a/tests/Avalonia.Input.UnitTests/MouseDeviceTests.cs b/tests/Avalonia.Input.UnitTests/MouseDeviceTests.cs index a732742b0b..5853d1e82b 100644 --- a/tests/Avalonia.Input.UnitTests/MouseDeviceTests.cs +++ b/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 @@ -128,6 +130,71 @@ namespace Avalonia.Input.UnitTests } } + [Fact] + public void PointerEnter_Leave_Should_Be_Raised_In_Correct_Order() + { + var renderer = new Mock(); + 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 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(