Browse Source

Track adorned element position.

Closes #38. Still need to track transforms and clip rectangles, but good
enough for now.
pull/58/head
Steven Kirk 11 years ago
parent
commit
06e0dde700
  1. 71
      Perspex.Controls/Primitives/AdornerLayer.cs
  2. 2
      Perspex.SceneGraph/Perspex.SceneGraph.csproj
  3. 45
      Perspex.SceneGraph/VisualTree/BoundsTracker.cs
  4. 24
      Perspex.SceneGraph/VisualTree/TransformedBounds.cs
  5. 15
      Perspex.SceneGraph/VisualTree/VisualExtensions.cs
  6. 41
      Tests/Perspex.SceneGraph.UnitTests/Perspex.SceneGraph.UnitTests.csproj
  7. 55
      Tests/Perspex.SceneGraph.UnitTests/VisualTree/BoundsTrackerTests.cs
  8. 20
      Tests/Perspex.SceneGraph.UnitTests/packages.config

71
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<Visual> AdornedElementProperty =
PerspexProperty.RegisterAttached<AdornerLayer, Visual, Visual>("AdornedElement");
private static PerspexProperty<AdornedElementInfo> AdornedElementInfoProperty =
PerspexProperty.RegisterAttached<AdornerLayer, Visual, AdornedElementInfo>("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<Control> child)
protected override void OnChildrenAdded(IEnumerable<Control> 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<AdornerLayer>();
if (layer != null)
{
layer.UpdateAdornedElement(adorner, adorned);
}
}
private class AdornedElementInfo
{
public IDisposable Subscription { get; set; }
public TransformedBounds Bounds { get; set; }
}
}
}

2
Perspex.SceneGraph/Perspex.SceneGraph.csproj

@ -95,6 +95,8 @@
<Compile Include="Vector.cs" />
<Compile Include="Visual.cs" />
<Compile Include="LogicalTree\LogicalExtensions.cs" />
<Compile Include="VisualTree\TransformedBounds.cs" />
<Compile Include="VisualTree\BoundsTracker.cs" />
<Compile Include="VisualTree\VisualExtensions.cs" />
</ItemGroup>
<ItemGroup>

45
Perspex.SceneGraph/VisualTree/BoundsTracker.cs

@ -0,0 +1,45 @@
// -----------------------------------------------------------------------
// <copyright file="BoundsTracker.cs" company="Steven Kirk">
// Copyright 2015 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.VisualTree
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
public class BoundsTracker
{
public IObservable<TransformedBounds> Track(Visual visual)
{
return this.Track(visual, (Visual)visual.GetVisualRoot());
}
public IObservable<TransformedBounds> Track(Visual visual, Visual relativeTo)
{
var visuals = visual.GetSelfAndVisualAncestors()
.TakeWhile(x => x != relativeTo)
.Reverse();
var boundsSubscriptions = new List<IObservable<Rect>>();
foreach (var v in visuals.Cast<Visual>())
{
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<Rect> rects)
{
var position = rects.Select(x => x.Position).Aggregate((a, b) => a + b);
return new Rect(position, rects.Last().Size);
}
}
}

24
Perspex.SceneGraph/VisualTree/TransformedBounds.cs

@ -0,0 +1,24 @@
// -----------------------------------------------------------------------
// <copyright file="TransformedBounds.cs" company="Steven Kirk">
// Copyright 2015 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
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; }
}
}

15
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<NullReferenceException>(visual != null);
var parent = visual.VisualParent;
while (parent != null)
{
visual = parent;
parent = visual.VisualParent;
}
return visual;
}
}
}

41
Tests/Perspex.SceneGraph.UnitTests/Perspex.SceneGraph.UnitTests.csproj

@ -37,11 +37,31 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Moq, Version=4.2.1502.911, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL">
<HintPath>..\..\packages\Moq.4.2.1502.911\lib\net40\Moq.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Splat, Version=1.6.1.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\packages\Splat.1.6.1\lib\Net45\Splat.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Reactive.Core, Version=2.2.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Rx-Core.2.2.5\lib\net45\System.Reactive.Core.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Reactive.Interfaces, Version=2.2.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Rx-Interfaces.2.2.5\lib\net45\System.Reactive.Interfaces.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Reactive.Linq, Version=2.2.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Rx-Linq.2.2.5\lib\net45\System.Reactive.Linq.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Reactive.PlatformServices, Version=2.2.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Rx-PlatformServices.2.2.5\lib\net45\System.Reactive.PlatformServices.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="xunit.abstractions, Version=2.0.0.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll</HintPath>
</Reference>
@ -65,6 +85,7 @@
<Compile Include="TestVisual.cs" />
<Compile Include="VisualTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="VisualTree\BoundsTrackerTests.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Perspex.Animation\Perspex.Animation.csproj">
@ -75,10 +96,30 @@
<Project>{b09b78d8-9b26-48b0-9149-d64a2f120f3f}</Project>
<Name>Perspex.Base</Name>
</ProjectReference>
<ProjectReference Include="..\..\Perspex.Controls\Perspex.Controls.csproj">
<Project>{d2221c82-4a25-4583-9b43-d791e3f6820c}</Project>
<Name>Perspex.Controls</Name>
</ProjectReference>
<ProjectReference Include="..\..\Perspex.Input\Perspex.Input.csproj">
<Project>{62024b2d-53eb-4638-b26b-85eeaa54866e}</Project>
<Name>Perspex.Input</Name>
</ProjectReference>
<ProjectReference Include="..\..\Perspex.Interactivity\Perspex.Interactivity.csproj">
<Project>{6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b}</Project>
<Name>Perspex.Interactivity</Name>
</ProjectReference>
<ProjectReference Include="..\..\Perspex.Layout\Perspex.Layout.csproj">
<Project>{42472427-4774-4c81-8aff-9f27b8e31721}</Project>
<Name>Perspex.Layout</Name>
</ProjectReference>
<ProjectReference Include="..\..\Perspex.SceneGraph\Perspex.SceneGraph.csproj">
<Project>{eb582467-6abb-43a1-b052-e981ba910e3a}</Project>
<Name>Perspex.SceneGraph</Name>
</ProjectReference>
<ProjectReference Include="..\..\Perspex.Styling\Perspex.Styling.csproj">
<Project>{f1baa01a-f176-4c6a-b39d-5b40bb1b148f}</Project>
<Name>Perspex.Styling</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="app.config" />

55
Tests/Perspex.SceneGraph.UnitTests/VisualTree/BoundsTrackerTests.cs

@ -0,0 +1,55 @@
// -----------------------------------------------------------------------
// <copyright file="BoundsTracker.cs" company="Steven Kirk">
// Copyright 2015 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
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<TransformedBounds>();
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);
}
}
}

20
Tests/Perspex.SceneGraph.UnitTests/packages.config

@ -1,10 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Splat" version="1.6.1" targetFramework="net45" />
<package id="xunit" version="2.0.0" targetFramework="net45" />
<package id="xunit.abstractions" version="2.0.0" targetFramework="net45" />
<package id="xunit.assert" version="2.0.0" targetFramework="net45" />
<package id="xunit.core" version="2.0.0" targetFramework="net45" />
<package id="xunit.extensibility.core" version="2.0.0" targetFramework="net45" />
<package id="xunit.runner.visualstudio" version="2.0.0" targetFramework="net45" />
<package id="Moq" version="4.2.1502.911" targetFramework="net45" userInstalled="true" />
<package id="Rx-Core" version="2.2.5" targetFramework="net45" userInstalled="true" />
<package id="Rx-Interfaces" version="2.2.5" targetFramework="net45" userInstalled="true" />
<package id="Rx-Linq" version="2.2.5" targetFramework="net45" userInstalled="true" />
<package id="Rx-Main" version="2.2.5" targetFramework="net45" userInstalled="true" />
<package id="Rx-PlatformServices" version="2.2.5" targetFramework="net45" userInstalled="true" />
<package id="Splat" version="1.6.1" targetFramework="net45" userInstalled="true" />
<package id="xunit" version="2.0.0" targetFramework="net45" userInstalled="true" />
<package id="xunit.abstractions" version="2.0.0" targetFramework="net45" userInstalled="true" />
<package id="xunit.assert" version="2.0.0" targetFramework="net45" userInstalled="true" />
<package id="xunit.core" version="2.0.0" targetFramework="net45" userInstalled="true" />
<package id="xunit.extensibility.core" version="2.0.0" targetFramework="net45" userInstalled="true" />
<package id="xunit.runner.visualstudio" version="2.0.0" targetFramework="net45" userInstalled="true" />
</packages>
Loading…
Cancel
Save