diff --git a/src/Perspex.SceneGraph/Visual.cs b/src/Perspex.SceneGraph/Visual.cs index 53c6b8e66d..f559fd0f6e 100644 --- a/src/Perspex.SceneGraph/Visual.cs +++ b/src/Perspex.SceneGraph/Visual.cs @@ -307,8 +307,9 @@ namespace Perspex /// A containing the transform. public Matrix TransformToVisual(IVisual visual) { - var thisOffset = GetOffsetFromRoot(this).Item2; - var thatOffset = GetOffsetFromRoot(visual).Item2; + var common = this.FindCommonVisualAncestor(visual); + var thisOffset = GetOffsetFrom(common, this); + var thatOffset = GetOffsetFrom(common, visual); return Matrix.CreateTranslation(-thatOffset) * Matrix.CreateTranslation(thisOffset); } @@ -468,6 +469,30 @@ namespace Perspex } } + /// + /// Gets the visual offset from the specified ancestor. + /// + /// The ancestor visual. + /// The visual. + /// The visual offset. + private static Vector GetOffsetFrom(IVisual ancestor, IVisual visual) + { + var result = new Vector(); + + while (visual != ancestor) + { + result = new Vector(result.X + visual.Bounds.X, result.Y + visual.Bounds.Y); + visual = visual.VisualParent; + + if (visual == null) + { + throw new ArgumentException("'visual' is not a descendent of 'ancestor'."); + } + } + + return result; + } + /// /// Gets the root of the controls visual tree and the distance from the root. /// diff --git a/src/Perspex.SceneGraph/VisualTree/VisualExtensions.cs b/src/Perspex.SceneGraph/VisualTree/VisualExtensions.cs index 901bf678f1..d08682c829 100644 --- a/src/Perspex.SceneGraph/VisualTree/VisualExtensions.cs +++ b/src/Perspex.SceneGraph/VisualTree/VisualExtensions.cs @@ -8,10 +8,22 @@ using System.Linq; namespace Perspex.VisualTree { /// - /// Provides extension methods for working with visual tree. + /// Provides extension methods for working with the visual tree. /// public static class VisualExtensions { + /// + /// Tries to get the first common ancestor of two visuals. + /// + /// The first visual. + /// The second visual. + /// The common ancestor, or null if not found. + public static IVisual FindCommonVisualAncestor(this IVisual visual, IVisual target) + { + return visual.GetSelfAndVisualAncestors().Intersect(target.GetSelfAndVisualAncestors()) + .FirstOrDefault(); + } + /// /// Enumerates the ancestors of an in the visual tree. /// diff --git a/tests/Perspex.Controls.UnitTests/Presenters/ScrollContentPresenterTests.cs b/tests/Perspex.Controls.UnitTests/Presenters/ScrollContentPresenterTests.cs index 38d6ca5e1c..9da287f4b9 100644 --- a/tests/Perspex.Controls.UnitTests/Presenters/ScrollContentPresenterTests.cs +++ b/tests/Perspex.Controls.UnitTests/Presenters/ScrollContentPresenterTests.cs @@ -189,6 +189,28 @@ namespace Perspex.Controls.UnitTests.Presenters Assert.Equal(new Vector(10, 10), target.Offset); } + [Fact] + public void BringDescendentIntoView_Should_Work() + { + var target = new ScrollContentPresenter + { + Width = 100, + Height = 100, + Content = new Border + { + Width = 200, + Height = 200, + } + }; + + target.ApplyTemplate(); + target.Measure(Size.Infinity); + target.Arrange(new Rect(0, 0, 100, 100)); + target.BringDescendentIntoView(target.Child, new Rect(200, 200, 0, 0)); + + Assert.Equal(new Vector(100, 100), target.Offset); + } + private class TestControl : Control { protected override Size MeasureOverride(Size availableSize)