Browse Source

Cull controls outside clip bounds.

Steven Kirk 10 years ago
parent
commit
c427ab49f6

+ 18 - 0
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 - 0
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>

+ 49 - 6
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);

+ 1 - 1
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 - 0
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 - 0
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 - 0
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" />