Browse Source

Cull controls outside clip bounds.

pull/386/head
Steven Kirk 11 years ago
parent
commit
c427ab49f6
  1. 18
      src/Perspex.SceneGraph/Point.cs
  2. 42
      src/Perspex.SceneGraph/Rect.cs
  3. 55
      src/Perspex.SceneGraph/Rendering/RendererMixin.cs
  4. 2
      src/Perspex.SceneGraph/VisualTree/BoundsTracker.cs
  5. 5
      tests/Perspex.SceneGraph.UnitTests/Perspex.SceneGraph.UnitTests.csproj
  6. 188
      tests/Perspex.SceneGraph.UnitTests/RenderTests_Culling.cs
  7. 1
      tests/Perspex.SceneGraph.UnitTests/packages.config

18
src/Perspex.SceneGraph/Point.cs

@ -229,6 +229,24 @@ namespace Perspex
return string.Format(CultureInfo.InvariantCulture, "{0}, {1}", _x, _y);
}
/// <summary>
/// Transforms the point by a matrix.
/// </summary>
/// <param name="transform">The transform.</param>
/// <returns>The transformed point.</returns>
public Point Transform(Matrix transform)
{
var x = X;
var y = Y;
var xadd = y * transform.M21 + transform.M31;
var yadd = x * transform.M12 + transform.M32;
x *= transform.M11;
x += xadd;
y *= transform.M22;
y += yadd;
return new Point(x, y);
}
/// <summary>
/// Returns a new point with the specified X coordinate.
/// </summary>

42
src/Perspex.SceneGraph/Rect.cs

@ -3,6 +3,7 @@
using System;
using System.Globalization;
using System.Linq;
namespace Perspex
{
@ -360,6 +361,47 @@ namespace Perspex
return (rect.X < Right) && (X < rect.Right) && (rect.Y < Bottom) && (Y < rect.Bottom);
}
/// <summary>
/// Returns the axis-aligned bounding box of a transformed rectangle.
/// </summary>
/// <param name="matrix">The transform.</param>
/// <returns>The bounding box</returns>
public Rect TransformToAABB(Matrix matrix)
{
var points = new[]
{
TopLeft.Transform(matrix),
TopRight.Transform(matrix),
BottomRight.Transform(matrix),
BottomLeft.Transform(matrix),
};
var left = double.MaxValue;
var right = double.MinValue;
var top = double.MaxValue;
var bottom = double.MinValue;
foreach (var p in points)
{
if (p.X < left) left = p.X;
if (p.X > right) right = p.X;
if (p.Y < top) top = p.Y;
if (p.Y > bottom) bottom = p.Y;
}
return new Rect(new Point(left, top), new Point(right, bottom));
}
/// <summary>
/// Translates the rectangle by an offset.
/// </summary>
/// <param name="offset">The offset.</param>
/// <returns>The translated rectangle.</returns>
public Rect Translate(Vector offset)
{
return new Rect(Position + offset, Size);
}
/// <summary>
/// Returns the string representation of the rectangle.
/// </summary>

55
src/Perspex.SceneGraph/Rendering/RendererMixin.cs

@ -76,8 +76,24 @@ namespace Perspex.Rendering
/// <param name="visual">The visual to render.</param>
/// <param name="context">The drawing context.</param>
public static void Render(this DrawingContext context, IVisual visual)
{
context.Render(visual, visual.Bounds);
}
/// <summary>
/// Renders the specified visual.
/// </summary>
/// <param name="visual">The visual to render.</param>
/// <param name="context">The drawing context.</param>
/// <param name="clipRect">
/// The current clip rect, in coordinates relative to <paramref name="visual"/>.
/// </param>
private static void Render(this DrawingContext context, IVisual visual, Rect clipRect)
{
var opacity = visual.Opacity;
var clipToBounds = visual.ClipToBounds;
var bounds = new Rect(visual.Bounds.Size);
if (visual.IsVisible && opacity > 0)
{
var m = Matrix.CreateTranslation(visual.Bounds.Position);
@ -88,33 +104,48 @@ namespace Perspex.Rendering
{
var origin = visual.TransformOrigin.ToPixels(new Size(visual.Bounds.Width, visual.Bounds.Height));
var offset = Matrix.CreateTranslation(origin);
renderTransform = (-offset)*visual.RenderTransform.Value*(offset);
renderTransform = (-offset) * visual.RenderTransform.Value * (offset);
}
m = renderTransform * m;
if (clipToBounds)
{
clipRect = clipRect.Intersect(new Rect(visual.Bounds.Size));
}
m = renderTransform*m;
using (context.PushPostTransform(m))
using (context.PushOpacity(opacity))
using (visual.ClipToBounds ? context.PushClip(new Rect(visual.Bounds.Size)) : default(DrawingContext.PushedState))
using (clipToBounds ? context.PushClip(bounds) : default(DrawingContext.PushedState))
using (context.PushTransformContainer())
{
visual.Render(context);
var lst = GetSortedVisualList(visual.VisualChildren);
foreach (var child in lst)
{
context.Render(child);
var childBounds = GetTransformedBounds(child);
if (clipRect.Intersects(childBounds))
{
var childClipRect = clipRect.Translate(-childBounds.Position);
context.Render(child, childClipRect);
}
}
ReturnListToPool(lst);
}
}
}
static void ReturnListToPool(List<IVisual> lst)
private static void ReturnListToPool(List<IVisual> lst)
{
lst.Clear();
s_listPool.Push(lst);
}
static List<IVisual> GetSortedVisualList(IReadOnlyList<IVisual> source)
private static List<IVisual> GetSortedVisualList(IReadOnlyList<IVisual> source)
{
var lst = s_listPool.Count == 0 ? new List<IVisual>() : s_listPool.Pop();
for (var c = 0; c < source.Count; c++)
@ -123,6 +154,18 @@ namespace Perspex.Rendering
return lst;
}
private static Rect GetTransformedBounds(IVisual visual)
{
if (visual.RenderTransform == null)
{
return visual.Bounds;
}
else
{
return visual.Bounds.TransformToAABB(visual.RenderTransform.Value);
}
}
class ZIndexComparer : IComparer<IVisual>
{
public int Compare(IVisual x, IVisual y) => x.ZIndex.CompareTo(y.ZIndex);

2
src/Perspex.SceneGraph/VisualTree/BoundsTracker.cs

@ -47,7 +47,7 @@ namespace Perspex.VisualTree
var bounds = boundsSubscriptions.CombineLatest().Select(ExtractBounds);
// TODO: Track transform and clip rectangle.
return bounds.Select(x => new TransformedBounds((Rect)x, (Rect)new Rect(), (Matrix)Matrix.Identity));
return bounds.Select(x => new TransformedBounds(x, new Rect(), Matrix.Identity));
}
/// <summary>

5
tests/Perspex.SceneGraph.UnitTests/Perspex.SceneGraph.UnitTests.csproj

@ -40,6 +40,10 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Moq, Version=4.2.1507.118, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL">
<HintPath>..\..\packages\Moq.4.2.1507.0118\lib\net40\Moq.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="xunit.assert">
<HintPath>..\..\packages\xunit.assert.2.0.0\lib\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.assert.dll</HintPath>
@ -80,6 +84,7 @@
<Compile Include="TestRoot.cs" />
<Compile Include="TestVisual.cs" />
<Compile Include="RelativePointTests.cs" />
<Compile Include="RenderTests_Culling.cs" />
<Compile Include="VisualTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="VisualTree\BoundsTrackerTests.cs" />

188
tests/Perspex.SceneGraph.UnitTests/RenderTests_Culling.cs

@ -0,0 +1,188 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Moq;
using Perspex.Controls;
using Perspex.Media;
using Perspex.Rendering;
using Xunit;
namespace Perspex.SceneGraph.UnitTests
{
public class RenderTests_Culling
{
[Fact]
public void In_Bounds_Control_Should_Be_Rendered()
{
TestControl target;
var container = new Canvas
{
Width = 100,
Height = 100,
ClipToBounds = true,
Children = new Controls.Controls
{
(target = new TestControl
{
Width = 10,
Height = 10,
[Canvas.LeftProperty] = 98,
[Canvas.TopProperty] = 98,
})
}
};
Render(container);
Assert.True(target.Rendered);
}
[Fact]
public void Out_Of_Bounds_Control_Should_Not_Be_Rendered()
{
TestControl target;
var container = new Canvas
{
Width = 100,
Height = 100,
ClipToBounds = true,
Children = new Controls.Controls
{
(target = new TestControl
{
Width = 10,
Height = 10,
[Canvas.LeftProperty] = 110,
[Canvas.TopProperty] = 110,
})
}
};
Render(container);
Assert.False(target.Rendered);
}
[Fact]
public void Out_Of_Bounds_Child_Control_Should_Not_Be_Rendered()
{
TestControl target;
var container = new Canvas
{
Width = 100,
Height = 100,
ClipToBounds = true,
Children = new Controls.Controls
{
new Canvas
{
Width = 100,
Height = 100,
[Canvas.LeftProperty] = 50,
[Canvas.TopProperty] = 50,
Children = new Controls.Controls
{
(target = new TestControl
{
Width = 10,
Height = 10,
[Canvas.LeftProperty] = 50,
[Canvas.TopProperty] = 50,
})
}
}
}
};
Render(container);
Assert.False(target.Rendered);
}
[Fact]
public void Nested_ClipToBounds_Should_Be_Respected()
{
TestControl target;
var container = new Canvas
{
Width = 100,
Height = 100,
ClipToBounds = true,
Children = new Controls.Controls
{
new Canvas
{
Width = 50,
Height = 50,
ClipToBounds = true,
Children = new Controls.Controls
{
(target = new TestControl
{
Width = 10,
Height = 10,
[Canvas.LeftProperty] = 50,
[Canvas.TopProperty] = 50,
})
}
}
}
};
Render(container);
Assert.False(target.Rendered);
}
[Fact]
public void RenderTransform_Should_Be_Respected()
{
TestControl target;
var container = new Canvas
{
Width = 100,
Height = 100,
ClipToBounds = true,
Children = new Controls.Controls
{
(target = new TestControl
{
Width = 10,
Height = 10,
[Canvas.LeftProperty] = 110,
[Canvas.TopProperty] = 110,
RenderTransform = new TranslateTransform(-100, -100),
})
}
};
Render(container);
Assert.True(target.Rendered);
}
private void Render(IControl control)
{
var ctx = CreateDrawingContext();
control.Measure(Size.Infinity);
control.Arrange(new Rect(control.DesiredSize));
ctx.Render(control);
}
private DrawingContext CreateDrawingContext()
{
return new DrawingContext(Mock.Of<IDrawingContextImpl>());
}
private class TestControl : Control
{
public bool Rendered { get; private set; }
public override void Render(DrawingContext context)
{
Rendered = true;
}
}
}
}

1
tests/Perspex.SceneGraph.UnitTests/packages.config

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Moq" version="4.2.1507.0118" targetFramework="net45" />
<package id="Rx-Core" version="2.2.5" targetFramework="net45" />
<package id="Rx-Interfaces" version="2.2.5" targetFramework="net45" />
<package id="Rx-Linq" version="2.2.5" targetFramework="net45" />

Loading…
Cancel
Save