diff --git a/Perspex.Controls/Primitives/AdornerLayer.cs b/Perspex.Controls/Primitives/AdornerLayer.cs index e2c9291bbd..3b769c9a2a 100644 --- a/Perspex.Controls/Primitives/AdornerLayer.cs +++ b/Perspex.Controls/Primitives/AdornerLayer.cs @@ -6,6 +6,7 @@ namespace Perspex.Controls.Primitives { + using System; using System.Collections.Generic; using System.Linq; using Perspex.VisualTree; @@ -16,8 +17,14 @@ namespace Perspex.Controls.Primitives public static PerspexProperty AdornedElementProperty = PerspexProperty.RegisterAttached("AdornedElement"); + private static PerspexProperty AdornedElementInfoProperty = + PerspexProperty.RegisterAttached("AdornedElementInfo"); + + private BoundsTracker tracker = new BoundsTracker(); + static AdornerLayer() { + AdornedElementProperty.Changed.Subscribe(AdornedElementChanged); IsHitTestVisibleProperty.OverrideDefaultValue(typeof(AdornerLayer), false); } @@ -45,13 +52,11 @@ namespace Perspex.Controls.Primitives foreach (var child in this.Children) { - var adorned = GetAdornedElement(child); + var info = child.GetValue(AdornedElementInfoProperty); - if (adorned != null) + if (info != null) { - var transform = adorned.TransformToVisual(parent); - var position = new Point(0, 0) * transform; - child.Arrange(new Rect(position, adorned.Bounds.Size)); + child.Arrange(info.Bounds.Bounds); } else { @@ -62,8 +67,13 @@ namespace Perspex.Controls.Primitives return finalSize; } - protected override void OnChildrenAdded(IEnumerable child) + protected override void OnChildrenAdded(IEnumerable children) { + foreach (var i in children) + { + this.UpdateAdornedElement(i, i.GetValue(AdornedElementProperty)); + } + this.InvalidateArrange(); } @@ -71,5 +81,54 @@ namespace Perspex.Controls.Primitives { this.InvalidateArrange(); } + + private void UpdateAdornedElement(Visual adorner, Visual adorned) + { + var info = adorner.GetValue(AdornedElementInfoProperty); + + if (info != null) + { + info.Subscription.Dispose(); + + if (adorned == null) + { + adorner.ClearValue(AdornedElementInfoProperty); + } + } + + if (adorned != null) + { + if (info == null) + { + info = new AdornedElementInfo(); + adorner.SetValue(AdornedElementInfoProperty, info); + } + + info.Subscription = this.tracker.Track(adorned).Subscribe(x => + { + info.Bounds = x; + this.InvalidateArrange(); + }); + } + } + + private static void AdornedElementChanged(PerspexPropertyChangedEventArgs e) + { + var adorner = (Visual)e.Sender; + var adorned = (Visual)e.NewValue; + var layer = adorner.GetVisualParent(); + + if (layer != null) + { + layer.UpdateAdornedElement(adorner, adorned); + } + } + + private class AdornedElementInfo + { + public IDisposable Subscription { get; set; } + + public TransformedBounds Bounds { get; set; } + } } } diff --git a/Perspex.SceneGraph/Perspex.SceneGraph.csproj b/Perspex.SceneGraph/Perspex.SceneGraph.csproj index ba9fe51244..e2699ea80f 100644 --- a/Perspex.SceneGraph/Perspex.SceneGraph.csproj +++ b/Perspex.SceneGraph/Perspex.SceneGraph.csproj @@ -95,6 +95,8 @@ + + diff --git a/Perspex.SceneGraph/VisualTree/BoundsTracker.cs b/Perspex.SceneGraph/VisualTree/BoundsTracker.cs new file mode 100644 index 0000000000..35dd95b867 --- /dev/null +++ b/Perspex.SceneGraph/VisualTree/BoundsTracker.cs @@ -0,0 +1,45 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2015 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.VisualTree +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reactive.Linq; + + public class BoundsTracker + { + public IObservable Track(Visual visual) + { + return this.Track(visual, (Visual)visual.GetVisualRoot()); + } + + public IObservable Track(Visual visual, Visual relativeTo) + { + var visuals = visual.GetSelfAndVisualAncestors() + .TakeWhile(x => x != relativeTo) + .Reverse(); + var boundsSubscriptions = new List>(); + + foreach (var v in visuals.Cast()) + { + boundsSubscriptions.Add(v.GetObservable(Visual.BoundsProperty)); + } + + var bounds = Observable.CombineLatest(boundsSubscriptions).Select(ExtractBounds); + + // TODO: Track transform and clip rectangle. + return Observable.Select(bounds, x => new TransformedBounds((Rect)x, (Rect)new Rect(), (Matrix)Matrix.Identity)); + } + + private static Rect ExtractBounds(IList rects) + { + var position = rects.Select(x => x.Position).Aggregate((a, b) => a + b); + return new Rect(position, rects.Last().Size); + } + } +} diff --git a/Perspex.SceneGraph/VisualTree/TransformedBounds.cs b/Perspex.SceneGraph/VisualTree/TransformedBounds.cs new file mode 100644 index 0000000000..b241d88a43 --- /dev/null +++ b/Perspex.SceneGraph/VisualTree/TransformedBounds.cs @@ -0,0 +1,24 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2015 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.VisualTree +{ + public class TransformedBounds + { + public TransformedBounds(Rect bounds, Rect clip, Matrix transform) + { + this.Bounds = bounds; + this.Clip = clip; + this.Transform = transform; + } + + public Rect Bounds { get; } + + public Rect Clip { get; } + + public Matrix Transform { get; } + } +} diff --git a/Perspex.SceneGraph/VisualTree/VisualExtensions.cs b/Perspex.SceneGraph/VisualTree/VisualExtensions.cs index 4aa53208c3..754734a4fd 100644 --- a/Perspex.SceneGraph/VisualTree/VisualExtensions.cs +++ b/Perspex.SceneGraph/VisualTree/VisualExtensions.cs @@ -102,5 +102,20 @@ namespace Perspex.VisualTree { return visual.VisualParent as T; } + + public static IVisual GetVisualRoot(this IVisual visual) + { + Contract.Requires(visual != null); + + var parent = visual.VisualParent; + + while (parent != null) + { + visual = parent; + parent = visual.VisualParent; + } + + return visual; + } } } diff --git a/Tests/Perspex.SceneGraph.UnitTests/Perspex.SceneGraph.UnitTests.csproj b/Tests/Perspex.SceneGraph.UnitTests/Perspex.SceneGraph.UnitTests.csproj index 0eede2512c..3c3dec3304 100644 --- a/Tests/Perspex.SceneGraph.UnitTests/Perspex.SceneGraph.UnitTests.csproj +++ b/Tests/Perspex.SceneGraph.UnitTests/Perspex.SceneGraph.UnitTests.csproj @@ -37,11 +37,31 @@ 4 + + ..\..\packages\Moq.4.2.1502.911\lib\net40\Moq.dll + True + False ..\..\packages\Splat.1.6.1\lib\Net45\Splat.dll + + ..\..\packages\Rx-Core.2.2.5\lib\net45\System.Reactive.Core.dll + True + + + ..\..\packages\Rx-Interfaces.2.2.5\lib\net45\System.Reactive.Interfaces.dll + True + + + ..\..\packages\Rx-Linq.2.2.5\lib\net45\System.Reactive.Linq.dll + True + + + ..\..\packages\Rx-PlatformServices.2.2.5\lib\net45\System.Reactive.PlatformServices.dll + True + ..\..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll @@ -65,6 +85,7 @@ + @@ -75,10 +96,30 @@ {b09b78d8-9b26-48b0-9149-d64a2f120f3f} Perspex.Base + + {d2221c82-4a25-4583-9b43-d791e3f6820c} + Perspex.Controls + + + {62024b2d-53eb-4638-b26b-85eeaa54866e} + Perspex.Input + + + {6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b} + Perspex.Interactivity + + + {42472427-4774-4c81-8aff-9f27b8e31721} + Perspex.Layout + {eb582467-6abb-43a1-b052-e981ba910e3a} Perspex.SceneGraph + + {f1baa01a-f176-4c6a-b39d-5b40bb1b148f} + Perspex.Styling + diff --git a/Tests/Perspex.SceneGraph.UnitTests/VisualTree/BoundsTrackerTests.cs b/Tests/Perspex.SceneGraph.UnitTests/VisualTree/BoundsTrackerTests.cs new file mode 100644 index 0000000000..329583723d --- /dev/null +++ b/Tests/Perspex.SceneGraph.UnitTests/VisualTree/BoundsTrackerTests.cs @@ -0,0 +1,55 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2015 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.SceneGraph.UnitTests.VisualTree +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reactive.Linq; + using Perspex.Controls; + using Perspex.Controls.Shapes; + using Perspex.VisualTree; + using Xunit; + + public class BoundsTrackerTests + { + [Fact] + public void Should_Track_Bounds() + { + var target = new BoundsTracker(); + var control = default(Rectangle); + var tree = new Decorator + { + Padding = new Thickness(10), + Content = new Decorator + { + Padding = new Thickness(5), + Content = (control = new Rectangle + { + Width = 15, + Height = 15, + }), + } + }; + + tree.Measure(Size.Infinity); + tree.Arrange(new Rect(0, 0, 100, 100)); + + var track = target.Track(control, tree); + var results = new List(); + track.Subscribe(results.Add); + + Assert.Equal(new Rect(15, 15, 15, 15), results.Last().Bounds); + + tree.Padding = new Thickness(15); + tree.Measure(Size.Infinity); + tree.Arrange(new Rect(0, 0, 100, 100), true); + + Assert.Equal(new Rect(20, 20, 15, 15), results.Last().Bounds); + } + } +} diff --git a/Tests/Perspex.SceneGraph.UnitTests/packages.config b/Tests/Perspex.SceneGraph.UnitTests/packages.config index 9598650bf3..4afa207788 100644 --- a/Tests/Perspex.SceneGraph.UnitTests/packages.config +++ b/Tests/Perspex.SceneGraph.UnitTests/packages.config @@ -1,10 +1,16 @@  - - - - - - - + + + + + + + + + + + + + \ No newline at end of file