From 6b80d51fe6b66e4ef2e89cea51a74ac7c8014f8c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 29 Jul 2016 20:34:46 +0200 Subject: [PATCH] Fix TransformedBounds for invisible controls. Previously the BoundsTracker.TransformedBounds attached property wasn't being cleared for controls that were invisible, resulting in incorrect hit testing in certain situations (the newly added TreeView page in ControlCatalog demonstrated the behavior when fully expanding the first node and its children). --- .../Primitives/AdornerLayer.cs | 2 +- .../Rendering/RendererMixin.cs | 17 ++++++++++++ .../VisualTree/BoundsTracker.cs | 26 +++++++++++++------ .../VisualTree/VisualExtensions.cs | 2 +- .../VisualTree/BoundsTrackerTests.cs | 6 ++--- 5 files changed, 40 insertions(+), 13 deletions(-) diff --git a/src/Avalonia.Controls/Primitives/AdornerLayer.cs b/src/Avalonia.Controls/Primitives/AdornerLayer.cs index 1dfa2d7af2..08b795e9a1 100644 --- a/src/Avalonia.Controls/Primitives/AdornerLayer.cs +++ b/src/Avalonia.Controls/Primitives/AdornerLayer.cs @@ -120,7 +120,7 @@ namespace Avalonia.Controls.Primitives info.Subscription = _tracker.Track(adorned).Subscribe(x => { - info.Bounds = x; + info.Bounds = x.Value; InvalidateArrange(); }); } diff --git a/src/Avalonia.SceneGraph/Rendering/RendererMixin.cs b/src/Avalonia.SceneGraph/Rendering/RendererMixin.cs index 0bb5bf63f8..416cdb9855 100644 --- a/src/Avalonia.SceneGraph/Rendering/RendererMixin.cs +++ b/src/Avalonia.SceneGraph/Rendering/RendererMixin.cs @@ -142,11 +142,28 @@ namespace Avalonia.Rendering var childClipRect = clipRect.Translate(-childBounds.Position); context.Render(child, childClipRect); } + else + { + ClearTransformedBounds(child); + } } ReturnListToPool(lst); } } + + if (!visual.IsVisible) + { + ClearTransformedBounds(visual); + } + } + + private static void ClearTransformedBounds(IVisual visual) + { + foreach (var e in visual.GetSelfAndVisualDescendents()) + { + BoundsTracker.SetTransformedBounds((Visual)visual, null); + } } private static void ReturnListToPool(List lst) diff --git a/src/Avalonia.SceneGraph/VisualTree/BoundsTracker.cs b/src/Avalonia.SceneGraph/VisualTree/BoundsTracker.cs index 4ad67c45b7..42c4e3c98e 100644 --- a/src/Avalonia.SceneGraph/VisualTree/BoundsTracker.cs +++ b/src/Avalonia.SceneGraph/VisualTree/BoundsTracker.cs @@ -9,33 +9,43 @@ namespace Avalonia.VisualTree /// Tracks the bounds of a control. /// /// - /// This class is used by Adorners to track the control that the adorner is attached to. + /// This class is used to track a controls's bounds for hit testing. + /// TODO: This shouldn't be implemented as an attached property: it would be more performant + /// to just store bounds in some sort of central repository. /// public class BoundsTracker { - private static AttachedProperty TransformedBoundsProperty = - AvaloniaProperty.RegisterAttached("TransformedBounds"); + /// + /// Defines the TransformedBounds attached property. + /// + private static AttachedProperty TransformedBoundsProperty = + AvaloniaProperty.RegisterAttached("TransformedBounds"); /// /// Starts tracking the specified visual. /// /// The visual. /// An observable that returns the tracked bounds. - public IObservable Track(Visual visual) + public IObservable Track(Visual visual) { return visual.GetObservable(TransformedBoundsProperty); } - internal static void SetTransformedBounds(Visual visual, TransformedBounds bounds) + /// + /// Sets the transformed bounds of the visual. + /// + /// The visual. + /// The transformed bounds. + internal static void SetTransformedBounds(Visual visual, TransformedBounds? value) { - visual.SetValue(TransformedBoundsProperty, bounds); + visual.SetValue(TransformedBoundsProperty, value); } /// /// Gets the transformed bounds of the visual. /// /// The visual. - /// The transformed bounds. - public static TransformedBounds GetTransformedBounds(Visual visual) => visual.GetValue(TransformedBoundsProperty); + /// The transformed bounds or null if the visual is not visible. + public static TransformedBounds? GetTransformedBounds(Visual visual) => visual.GetValue(TransformedBoundsProperty); } } diff --git a/src/Avalonia.SceneGraph/VisualTree/VisualExtensions.cs b/src/Avalonia.SceneGraph/VisualTree/VisualExtensions.cs index a2e6e1e29f..e7876b762f 100644 --- a/src/Avalonia.SceneGraph/VisualTree/VisualExtensions.cs +++ b/src/Avalonia.SceneGraph/VisualTree/VisualExtensions.cs @@ -104,7 +104,7 @@ namespace Avalonia.VisualTree if (filter?.Invoke(visual) != false) { - bool containsPoint = BoundsTracker.GetTransformedBounds((Visual)visual).Contains(p); + bool containsPoint = BoundsTracker.GetTransformedBounds((Visual)visual)?.Contains(p) == true; if ((containsPoint || !visual.ClipToBounds) && visual.VisualChildren.Any()) { diff --git a/tests/Avalonia.SceneGraph.UnitTests/VisualTree/BoundsTrackerTests.cs b/tests/Avalonia.SceneGraph.UnitTests/VisualTree/BoundsTrackerTests.cs index 75eb9c3a50..93b8a088d8 100644 --- a/tests/Avalonia.SceneGraph.UnitTests/VisualTree/BoundsTrackerTests.cs +++ b/tests/Avalonia.SceneGraph.UnitTests/VisualTree/BoundsTrackerTests.cs @@ -46,11 +46,11 @@ namespace Avalonia.SceneGraph.UnitTests.VisualTree context.Render(tree); var track = target.Track(control); - var results = new List(); + var results = new List(); track.Subscribe(results.Add); - Assert.Equal(new Rect(0, 0, 15, 15), results[0].Bounds); - Assert.Equal(Matrix.CreateTranslation(42, 42), results[0].Transform); + Assert.Equal(new Rect(0, 0, 15, 15), results[0].Value.Bounds); + Assert.Equal(Matrix.CreateTranslation(42, 42), results[0].Value.Transform); } } }