Bläddra i källkod

Handle geometry clips on child layers.

Steven Kirk 8 år sedan
förälder
incheckning
5e4f5a6160

+ 2 - 1
samples/RenderTest/Pages/ClippingPage.xaml

@@ -5,7 +5,8 @@
             Height="100"
             Clip="M 58.625 0.07421875 C 50.305778 0.26687364 42.411858 7.0346526 41.806641 15.595703 C 42.446442 22.063923 39.707425 13.710754 36.982422 12.683594 C 29.348395 6.1821635 16.419398 8.4359222 11.480469 17.195312 C 6.0935256 25.476803 9.8118851 37.71125 18.8125 41.6875 C 9.1554771 40.62945 -0.070876925 49.146842 0.21679688 58.857422 C 0.21545578 60.872512 0.56758794 62.88911 1.2617188 64.78125 C 4.3821886 74.16708 16.298268 78.921772 25.03125 74.326172 C 28.266843 72.062552 26.298191 74.214838 25.414062 76.398438 C 21.407348 85.589198 27.295992 97.294293 37.097656 99.501953 C 46.864883 102.3541 57.82177 94.726518 58.539062 84.580078 C 58.142158 79.498998 59.307538 83.392694 61.207031 85.433594 C 67.532324 93.056874 80.440232 93.192029 86.882812 85.630859 C 93.836392 78.456939 92.396838 65.538666 84.115234 60.009766 C 79.783641 57.904836 83.569793 58.802369 86.375 58.193359 C 96.383335 56.457569 102.87506 44.824101 99.083984 35.394531 C 95.963498 26.008711 84.047451 21.254079 75.314453 25.849609 C 72.078834 28.113269 74.047517 25.960974 74.931641 23.777344 C 78.93827 14.586564 73.049722 2.8815081 63.248047 0.67382812 C 61.721916 0.22817968 60.165597 0.038541919 58.625 0.07421875 z ">
       <Border Name="geometryClipped" Background="{StyleResource ThemeAccentBrush}" Margin="4">
-        <TextBox Text="Avalonia" VerticalAlignment="Center"/>
+        <!-- Setting opacity puts the TextBox on a new layer -->
+        <TextBox Text="Avalonia" Opacity="0.9" VerticalAlignment="Center"/>
       </Border>
     </Border>
   </Grid>

+ 1 - 1
src/Avalonia.Visuals/Media/DrawingContext.cs

@@ -227,7 +227,7 @@ namespace Avalonia.Media
         public PushedState PushGeometryClip(Geometry clip)
         {
             Contract.Requires<ArgumentNullException>(clip != null);
-            PlatformImpl.PushGeometryClip(clip);
+            PlatformImpl.PushGeometryClip(clip.PlatformImpl);
             return new PushedState(this, PushedState.PushedStateType.GeometryClip);
         }
 

+ 1 - 1
src/Avalonia.Visuals/Media/IDrawingContextImpl.cs

@@ -104,7 +104,7 @@ namespace Avalonia.Media
         /// Pushes a clip geometry.
         /// </summary>
         /// <param name="clip">The clip geometry.</param>
-        void PushGeometryClip(Geometry clip);
+        void PushGeometryClip(IGeometryImpl clip);
 
         void PopGeometryClip();
     }

+ 7 - 0
src/Avalonia.Visuals/Platform/IGeometryImpl.cs

@@ -34,6 +34,13 @@ namespace Avalonia.Platform
         /// <returns><c>true</c> if the geometry contains the point; otherwise, <c>false</c>.</returns>
         bool FillContains(Point point);
 
+        /// <summary>
+        /// Intersects the geometry with another geometry.
+        /// </summary>
+        /// <param name="geometry">The other geometry.</param>
+        /// <returns>A new <see cref="IGeometryImpl"/> representing the intersection.</returns>
+        IGeometryImpl Intersect(IGeometryImpl geometry);
+
         /// <summary>
         /// Indicates whether the geometry's stroke contains the specified point.
         /// </summary>

+ 10 - 0
src/Avalonia.Visuals/Rendering/DeferredRenderer.cs

@@ -263,6 +263,11 @@ namespace Avalonia.Rendering
                         var bitmap = _layers[layer.LayerRoot].Bitmap;
                         var sourceRect = new Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight);
 
+                        if (layer.GeometryClip != null)
+                        {
+                            context.PushGeometryClip(layer.GeometryClip);
+                        }
+
                         if (layer.OpacityMask == null)
                         {
                             context.DrawImage(bitmap, layer.Opacity, sourceRect, clientRect);
@@ -271,6 +276,11 @@ namespace Avalonia.Rendering
                         {
                             context.DrawImage(bitmap, layer.OpacityMask, layer.OpacityMaskRect, sourceRect);
                         }
+
+                        if (layer.GeometryClip != null)
+                        {
+                            context.PopGeometryClip();
+                        }
                     }
 
                     if (_overlay != null)

+ 1 - 1
src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs

@@ -179,7 +179,7 @@ namespace Avalonia.Rendering.SceneGraph
             // TODO: Implement
         }
 
-        public void PushGeometryClip(Geometry clip)
+        public void PushGeometryClip(IGeometryImpl clip)
         {
             // TODO: Implement
         }

+ 7 - 1
src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs

@@ -4,6 +4,7 @@
 using System;
 using System.Collections.Generic;
 using Avalonia.Media;
+using Avalonia.Platform;
 using Avalonia.VisualTree;
 
 namespace Avalonia.Rendering.SceneGraph
@@ -45,7 +46,12 @@ namespace Avalonia.Rendering.SceneGraph
         /// <summary>
         /// Gets the node's clip geometry, if any.
         /// </summary>
-        Geometry GeometryClip { get; set; }
+        IGeometryImpl GeometryClip { get; set; }
+
+        /// <summary>
+        /// Gets a value indicating whether one of the node's ancestors has a geometry clip.
+        /// </summary>
+        bool HasAncestorGeometryClip { get; }
 
         /// <summary>
         /// Gets the child scene graph nodes.

+ 34 - 5
src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs

@@ -4,6 +4,7 @@
 using System;
 using System.Linq;
 using Avalonia.Media;
+using Avalonia.Platform;
 using Avalonia.Threading;
 using Avalonia.VisualTree;
 
@@ -162,7 +163,7 @@ namespace Avalonia.Rendering.SceneGraph
                     node.Transform = contextImpl.Transform;
                     node.ClipBounds = bounds.TransformToAABB(node.Transform).Intersect(clip);
                     node.ClipToBounds = clipToBounds;
-                    node.GeometryClip = visual.Clip;
+                    node.GeometryClip = visual.Clip?.PlatformImpl;
                     node.Opacity = opacity;
                     node.OpacityMask = visual.OpacityMask;
 
@@ -293,7 +294,7 @@ namespace Avalonia.Rendering.SceneGraph
             }
 
             var oldLayer = scene.Layers[oldLayerRoot];
-            SetDescendentsLayer(node, scene.Layers[newLayerRoot], oldLayer);
+            PropagateLayer(node, scene.Layers[newLayerRoot], oldLayer);
             scene.Layers.Remove(oldLayer);
         }
 
@@ -304,7 +305,7 @@ namespace Avalonia.Rendering.SceneGraph
             var oldLayer = scene.Layers[oldLayerRoot];
 
             UpdateLayer(node, layer);
-            SetDescendentsLayer(node, layer, scene.Layers[oldLayerRoot]);
+            PropagateLayer(node, layer, scene.Layers[oldLayerRoot]);
         }
 
         private static void UpdateLayer(VisualNode node, SceneLayer layer)
@@ -321,9 +322,13 @@ namespace Avalonia.Rendering.SceneGraph
                 layer.OpacityMask = null;
                 layer.OpacityMaskRect = Rect.Empty;
             }
+
+            layer.GeometryClip = node.HasAncestorGeometryClip ?
+                CreateLayerGeometryClip(node) :
+                null;
         }
 
-        private static void SetDescendentsLayer(VisualNode node, SceneLayer layer, SceneLayer oldLayer)
+        private static void PropagateLayer(VisualNode node, SceneLayer layer, SceneLayer oldLayer)
         {
             node.LayerRoot = layer.LayerRoot;
 
@@ -335,11 +340,35 @@ namespace Avalonia.Rendering.SceneGraph
                 // If the child is not the start of a new layer, recurse.
                 if (child.LayerRoot != child.Visual)
                 {
-                    SetDescendentsLayer(child, layer, oldLayer);
+                    PropagateLayer(child, layer, oldLayer);
                 }
             }
         }
 
+        private static IGeometryImpl CreateLayerGeometryClip(VisualNode node)
+        {
+            IGeometryImpl result = null;
+
+            for (;;)
+            {
+                node = (VisualNode)node.Parent;
+
+                if (node == null || (node.GeometryClip == null && !node.HasAncestorGeometryClip))
+                {
+                    break;
+                }
+
+                if (node?.GeometryClip != null)
+                {
+                    var transformed = node.GeometryClip.WithTransform(node.Transform);
+
+                    result = result == null ? transformed : result.Intersect(transformed);
+                }
+            }
+
+            return result;
+        }
+
         private static IBrush ToImmutable(IBrush brush)
         {
             return (brush as IMutableBrush)?.ToImmutable() ?? brush;

+ 3 - 0
src/Avalonia.Visuals/Rendering/SceneGraph/SceneLayer.cs

@@ -1,5 +1,6 @@
 using System;
 using Avalonia.Media;
+using Avalonia.Platform;
 using Avalonia.VisualTree;
 
 namespace Avalonia.Rendering.SceneGraph
@@ -20,6 +21,7 @@ namespace Avalonia.Rendering.SceneGraph
                 Opacity = Opacity,
                 OpacityMask = OpacityMask,
                 OpacityMaskRect = OpacityMaskRect,
+                GeometryClip = GeometryClip,
             };
         }
 
@@ -29,5 +31,6 @@ namespace Avalonia.Rendering.SceneGraph
         public double Opacity { get; set; } = 1;
         public IBrush OpacityMask { get; set; }
         public Rect OpacityMaskRect { get; set; }
+        public IGeometryImpl GeometryClip { get; set; }
     }
 }

+ 7 - 1
src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs

@@ -4,6 +4,7 @@
 using System;
 using System.Collections.Generic;
 using Avalonia.Media;
+using Avalonia.Platform;
 using Avalonia.VisualTree;
 
 namespace Avalonia.Rendering.SceneGraph
@@ -33,6 +34,8 @@ namespace Avalonia.Rendering.SceneGraph
 
             Visual = visual;
             Parent = parent;
+            HasAncestorGeometryClip = parent != null && 
+                (parent.HasAncestorGeometryClip || parent.GeometryClip != null);
         }
 
         /// <inheritdoc/>
@@ -54,7 +57,10 @@ namespace Avalonia.Rendering.SceneGraph
         public bool ClipToBounds { get; set; }
 
         /// <inheritdoc/>
-        public Geometry GeometryClip { get; set; }
+        public IGeometryImpl GeometryClip { get; set; }
+
+        /// <inheritdoc/>
+        public bool HasAncestorGeometryClip { get; }
 
         /// <summary>
         /// Gets or sets the opacity of the scene graph node.

+ 2 - 2
src/Gtk/Avalonia.Cairo/Media/DrawingContext.cs

@@ -356,10 +356,10 @@ namespace Avalonia.Cairo.Media
             return SetBrush(pen.Brush, destinationSize);
         }
 
-        public void PushGeometryClip(Geometry clip)
+        public void PushGeometryClip(IGeometryImpl clip)
         {
             _context.Save();
-            _context.AppendPath(((StreamGeometryImpl)clip.PlatformImpl).Path);
+            _context.AppendPath(((StreamGeometryImpl)clip).Path);
             _context.Clip();
         }
 

+ 5 - 0
src/Gtk/Avalonia.Cairo/Media/StreamGeometryImpl.cs

@@ -65,6 +65,11 @@ namespace Avalonia.Cairo.Media
             return _impl.FillContains(point);
         }
 
+        public IGeometryImpl Intersect(IGeometryImpl geometry)
+        {
+            throw new NotImplementedException();
+        }
+
         public bool StrokeContains(Pen pen, Point point)
         {
             return _impl.StrokeContains(pen, point);

+ 2 - 2
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@@ -331,10 +331,10 @@ namespace Avalonia.Skia
                     disposable?.Dispose();
         }
 
-        public void PushGeometryClip(Geometry clip)
+        public void PushGeometryClip(IGeometryImpl clip)
         {
             Canvas.Save();
-            Canvas.ClipPath(((StreamGeometryImpl)clip.PlatformImpl).EffectivePath);
+            Canvas.ClipPath(((StreamGeometryImpl)clip).EffectivePath);
         }
 
         public void PopGeometryClip()

+ 5 - 0
src/Skia/Avalonia.Skia/StreamGeometryImpl.cs

@@ -63,6 +63,11 @@ namespace Avalonia.Skia
             return GetRenderBounds(0).Contains(point);
         }
 
+        public IGeometryImpl Intersect(IGeometryImpl geometry)
+        {
+            throw new NotImplementedException();
+        }
+
         public IGeometryImpl WithTransform(Matrix transform)
         {
             var result = (StreamGeometryImpl)Clone();

+ 2 - 2
src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs

@@ -403,14 +403,14 @@ namespace Avalonia.Direct2D1.Media
             return new SolidColorBrushImpl(null, _renderTarget);
         }
 
-        public void PushGeometryClip(Avalonia.Media.Geometry clip)
+        public void PushGeometryClip(IGeometryImpl clip)
         {
             var parameters = new LayerParameters
             {
                 ContentBounds = PrimitiveExtensions.RectangleInfinite,
                 MaskTransform = PrimitiveExtensions.Matrix3x2Identity,
                 Opacity = 1,
-                GeometricMask = ((GeometryImpl)clip.PlatformImpl).Geometry
+                GeometricMask = ((GeometryImpl)clip).Geometry
             };
             var layer = _layerPool.Count != 0 ? _layerPool.Pop() : new Layer(_renderTarget);
             _renderTarget.PushLayer(ref parameters, layer);

+ 12 - 0
src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs

@@ -38,6 +38,18 @@ namespace Avalonia.Direct2D1.Media
             return Geometry.FillContainsPoint(point.ToSharpDX());
         }
 
+        /// <inheritdoc/>
+        public IGeometryImpl Intersect(IGeometryImpl geometry)
+        {
+            var result = new PathGeometry(Geometry.Factory);
+
+            using (var sink = result.Open())
+            {
+                Geometry.Combine(((GeometryImpl)geometry).Geometry, CombineMode.Intersect, sink);
+                return new StreamGeometryImpl(result);
+            }
+        }
+
         /// <inheritdoc/>
         public bool StrokeContains(Avalonia.Media.Pen pen, Point point)
         {

+ 1 - 1
src/Windows/Avalonia.Direct2D1/Media/StreamGeometryImpl.cs

@@ -23,7 +23,7 @@ namespace Avalonia.Direct2D1.Media
         /// Initializes a new instance of the <see cref="StreamGeometryImpl"/> class.
         /// </summary>
         /// <param name="geometry">An existing Direct2D <see cref="PathGeometry"/>.</param>
-        protected StreamGeometryImpl(PathGeometry geometry)
+        public StreamGeometryImpl(PathGeometry geometry)
             : base(geometry)
         {
         }

+ 29 - 6
tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs

@@ -7,11 +7,29 @@ namespace Avalonia.UnitTests
 {
     public class MockStreamGeometryImpl : IStreamGeometryImpl
     {
-        private MockStreamGeometryContext _impl = new MockStreamGeometryContext();
+        private MockStreamGeometryContext _context;
 
-        public Rect Bounds => _impl.CalculateBounds();
+        public MockStreamGeometryImpl()
+        {
+            Transform = Matrix.Identity;
+            _context = new MockStreamGeometryContext();
+        }
 
-        public Matrix Transform { get; set; }
+        public MockStreamGeometryImpl(Matrix transform)
+        {
+            Transform = transform;
+            _context = new MockStreamGeometryContext();
+        }
+
+        private MockStreamGeometryImpl(Matrix transform, MockStreamGeometryContext context)
+        {
+            Transform = transform;
+            _context = context;
+        }
+
+        public Rect Bounds => _context.CalculateBounds();
+
+        public Matrix Transform { get; }
 
         public IStreamGeometryImpl Clone()
         {
@@ -20,7 +38,7 @@ namespace Avalonia.UnitTests
 
         public bool FillContains(Point point)
         {
-            return _impl.FillContains(point);
+            return _context.FillContains(point);
         }
 
         public bool StrokeContains(Pen pen, Point point)
@@ -30,14 +48,19 @@ namespace Avalonia.UnitTests
 
         public Rect GetRenderBounds(double strokeThickness) => Bounds;
 
+        public IGeometryImpl Intersect(IGeometryImpl geometry)
+        {
+            return new MockStreamGeometryImpl(Transform);
+        }
+
         public IStreamGeometryContextImpl Open()
         {
-            return _impl;
+            return _context;
         }
 
         public IGeometryImpl WithTransform(Matrix transform)
         {
-            return this;
+            return new MockStreamGeometryImpl(transform, _context);
         }
 
         class MockStreamGeometryContext : IStreamGeometryContextImpl

+ 3 - 1
tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs

@@ -7,6 +7,8 @@ using Avalonia.UnitTests;
 using Avalonia.VisualTree;
 using Xunit;
 using Avalonia.Layout;
+using Moq;
+using Avalonia.Platform;
 
 namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
 {
@@ -618,7 +620,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
                 sceneBuilder.UpdateAll(scene);
 
                 var decoratorNode = scene.FindNode(decorator);
-                Assert.Same(clip, decoratorNode.GeometryClip);
+                Assert.Same(clip.PlatformImpl, decoratorNode.GeometryClip);
             }
         }
 

+ 35 - 0
tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests_Layers.cs

@@ -238,5 +238,40 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
                 Assert.Equal(1, scene.Layers.Count);
             }
         }
+
+        [Fact]
+        public void GeometryClip_Should_Affect_Child_Layers()
+        {
+            using (TestApplication())
+            {
+                var clip = StreamGeometry.Parse("M100,0 L0,100 100,100");
+                Decorator decorator;
+                Border border;
+                var tree = new TestRoot
+                {
+                    Child = decorator = new Decorator
+                    {
+                        Clip = clip,
+                        Margin = new Thickness(12, 16),
+                        Child = border = new Border
+                        {
+                            Opacity = 0.5,
+                        }
+                    }
+                };
+
+                var layout = AvaloniaLocator.Current.GetService<ILayoutManager>();
+                layout.ExecuteInitialLayoutPass(tree);
+
+                var scene = new Scene(tree);
+                var sceneBuilder = new SceneBuilder();
+                sceneBuilder.UpdateAll(scene);
+
+                var borderLayer = scene.Layers[border];
+                Assert.Equal(
+                    Matrix.CreateTranslation(12, 16),
+                    ((MockStreamGeometryImpl)borderLayer.GeometryClip).Transform);
+            }
+        }
     }
 }