From b5a9f8ae8ab55edd1e4cfb25a1a3541c775a8859 Mon Sep 17 00:00:00 2001 From: donandren Date: Sat, 25 Jun 2016 17:18:09 +0300 Subject: [PATCH 1/2] Added failing unit tests for hit testing outside parent bounds when parent has ClipToBounds=true --- .../InputElement_HitTesting.cs | 145 +++++++++++++++++- 1 file changed, 138 insertions(+), 7 deletions(-) diff --git a/tests/Avalonia.Input.UnitTests/InputElement_HitTesting.cs b/tests/Avalonia.Input.UnitTests/InputElement_HitTesting.cs index bbec9e239f..f4cca0c684 100644 --- a/tests/Avalonia.Input.UnitTests/InputElement_HitTesting.cs +++ b/tests/Avalonia.Input.UnitTests/InputElement_HitTesting.cs @@ -1,17 +1,18 @@ // 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.Controls; +using Avalonia.Controls.Presenters; using Avalonia.Layout; using Avalonia.Media; using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.UnitTests; using Moq; -using Xunit; +using System; using System.Collections.Generic; using System.IO; +using Xunit; namespace Avalonia.Input.UnitTests { @@ -43,14 +44,14 @@ namespace Avalonia.Input.UnitTests var result = container.InputHitTest(new Point(100, 100)); - Assert.Equal(container.Child, result); + Assert.Equal(container.Child, result); } } [Fact] public void InputHitTest_Should_Not_Find_Control_Outside_Point() { - using (UnitTestApplication.Start(new TestServices(renderInterface:new MockRenderInterface()))) + using (UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface()))) { var container = new Decorator { @@ -73,7 +74,7 @@ namespace Avalonia.Input.UnitTests var result = container.InputHitTest(new Point(10, 10)); - Assert.Equal(container, result); + Assert.Equal(container, result); } } @@ -113,7 +114,7 @@ namespace Avalonia.Input.UnitTests var result = container.InputHitTest(new Point(100, 100)); - Assert.Equal(container.Children[1], result); + Assert.Equal(container.Children[1], result); } } @@ -154,7 +155,7 @@ namespace Avalonia.Input.UnitTests var result = container.InputHitTest(new Point(100, 100)); - Assert.Equal(container.Children[0], result); + Assert.Equal(container.Children[0], result); } } @@ -168,6 +169,7 @@ namespace Avalonia.Input.UnitTests { Width = 200, Height = 200, + ClipToBounds = false, Children = new Controls.Controls { new Border @@ -201,6 +203,135 @@ namespace Avalonia.Input.UnitTests } } + [Fact] + public void InputHitTest_Should_Not_Find_Control_Outside_Parent_Bounds_When_Clipped() + { + using (UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface()))) + { + Border target; + + var container = new Panel + { + Width = 100, + Height = 200, + Children = new Controls.Controls + { + new Panel() + { + Width = 100, + Height = 100, + Margin = new Thickness(0, 100, 0, 0), + ClipToBounds = true, + Children = new Controls.Controls + { + (target = new Border() + { + Width = 100, + Height = 100, + Margin = new Thickness(0, -100, 0, 0) + }) + } + } + } + }; + + container.Measure(Size.Infinity); + container.Arrange(new Rect(container.DesiredSize)); + + var context = new DrawingContext(Mock.Of()); + context.Render(container); + + var result = container.InputHitTest(new Point(50, 50)); + + Assert.NotEqual(target, result); + Assert.Equal(container, result); + } + } + + [Fact] + public void InputHitTest_Should_Not_Find_Control_Outside_Scroll_ViewPort() + { + using (UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface()))) + { + Border target; + Border item1; + Border item2; + ScrollContentPresenter scroll; + + var container = new Panel + { + Width = 100, + Height = 200, + Children = new Controls.Controls + { + (target = new Border() + { + Width = 100, + Height = 100 + }), + new Border() + { + Width = 100, + Height = 100, + Margin = new Thickness(0, 100, 0, 0), + Child = scroll = new ScrollContentPresenter() + { + Content = new StackPanel() + { + Children = new Controls.Controls + { + (item1 = new Border() + { + Width = 100, + Height = 100, + }), + (item2 = new Border() + { + Width = 100, + Height = 100, + }), + } + } + } + } + } + }; + + scroll.UpdateChild(); + + container.Measure(Size.Infinity); + container.Arrange(new Rect(container.DesiredSize)); + + var context = new DrawingContext(Mock.Of()); + context.Render(container); + + var result = container.InputHitTest(new Point(50, 150)); + + Assert.Equal(item1, result); + + result = container.InputHitTest(new Point(50, 50)); + + Assert.Equal(target, result); + + scroll.Offset = new Vector(0, 100); + + //we don't have setup LayoutManager so we will make it manually + scroll.Parent.InvalidateArrange(); + container.InvalidateArrange(); + + container.Arrange(new Rect(container.DesiredSize)); + context.Render(container); + + result = container.InputHitTest(new Point(50, 150)); + + Assert.Equal(item2, result); + + result = container.InputHitTest(new Point(50, 50)); + + Assert.NotEqual(item1, result); + Assert.Equal(target, result); + } + } class MockRenderInterface : IPlatformRenderInterface { From 2bcedd44a7c408fe33a4c2a752413b25981eab03 Mon Sep 17 00:00:00 2001 From: donandren Date: Sat, 18 Jun 2016 14:40:15 +0300 Subject: [PATCH 2/2] fix: handle only events within control bounds or ClipToBounds=false, otherwise there are problems with items not in visible part processing the events --- src/Avalonia.Input/InputExtensions.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Input/InputExtensions.cs b/src/Avalonia.Input/InputExtensions.cs index 5d80653322..35ffeca7bb 100644 --- a/src/Avalonia.Input/InputExtensions.cs +++ b/src/Avalonia.Input/InputExtensions.cs @@ -24,13 +24,14 @@ namespace Avalonia.Input public static IEnumerable GetInputElementsAt(this IInputElement element, Point p) { Contract.Requires(element != null); - var transformedBounds = BoundsTracker.GetTransformedBounds((Visual)element); if (element.IsVisible && element.IsHitTestVisible && element.IsEnabledCore) { - if (element.VisualChildren.Any()) + bool containsPoint = BoundsTracker.GetTransformedBounds((Visual)element).Contains(p); + + if ((containsPoint || !element.ClipToBounds) && element.VisualChildren.Any()) { foreach (var child in ZSort(element.VisualChildren.OfType())) { @@ -41,7 +42,7 @@ namespace Avalonia.Input } } - if (transformedBounds.Contains(p)) + if (containsPoint) { yield return element; }