diff --git a/samples/RenderTest/Pages/ClippingPage.xaml b/samples/RenderTest/Pages/ClippingPage.xaml
index 895edd65ca..0e352c4bec 100644
--- a/samples/RenderTest/Pages/ClippingPage.xaml
+++ b/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 ">
-
+
+
diff --git a/src/Avalonia.Visuals/Media/DrawingContext.cs b/src/Avalonia.Visuals/Media/DrawingContext.cs
index b724f53f20..860fb2ed9c 100644
--- a/src/Avalonia.Visuals/Media/DrawingContext.cs
+++ b/src/Avalonia.Visuals/Media/DrawingContext.cs
@@ -227,7 +227,7 @@ namespace Avalonia.Media
public PushedState PushGeometryClip(Geometry clip)
{
Contract.Requires(clip != null);
- PlatformImpl.PushGeometryClip(clip);
+ PlatformImpl.PushGeometryClip(clip.PlatformImpl);
return new PushedState(this, PushedState.PushedStateType.GeometryClip);
}
diff --git a/src/Avalonia.Visuals/Media/IDrawingContextImpl.cs b/src/Avalonia.Visuals/Media/IDrawingContextImpl.cs
index 4a52bc8324..8d97f45e19 100644
--- a/src/Avalonia.Visuals/Media/IDrawingContextImpl.cs
+++ b/src/Avalonia.Visuals/Media/IDrawingContextImpl.cs
@@ -104,7 +104,7 @@ namespace Avalonia.Media
/// Pushes a clip geometry.
///
/// The clip geometry.
- void PushGeometryClip(Geometry clip);
+ void PushGeometryClip(IGeometryImpl clip);
void PopGeometryClip();
}
diff --git a/src/Avalonia.Visuals/Platform/IGeometryImpl.cs b/src/Avalonia.Visuals/Platform/IGeometryImpl.cs
index 9dc00ac14f..132e00e56b 100644
--- a/src/Avalonia.Visuals/Platform/IGeometryImpl.cs
+++ b/src/Avalonia.Visuals/Platform/IGeometryImpl.cs
@@ -34,6 +34,13 @@ namespace Avalonia.Platform
/// true if the geometry contains the point; otherwise, false.
bool FillContains(Point point);
+ ///
+ /// Intersects the geometry with another geometry.
+ ///
+ /// The other geometry.
+ /// A new representing the intersection.
+ IGeometryImpl Intersect(IGeometryImpl geometry);
+
///
/// Indicates whether the geometry's stroke contains the specified point.
///
diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
index 4ac206acf1..e6cf53918e 100644
--- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
+++ b/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)
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
index 0bcefb79c6..03a4929808 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
+++ b/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
}
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs
index 6c5ec7b762..994b4a258e 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs
+++ b/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
///
/// Gets the node's clip geometry, if any.
///
- Geometry GeometryClip { get; set; }
+ IGeometryImpl GeometryClip { get; set; }
+
+ ///
+ /// Gets a value indicating whether one of the node's ancestors has a geometry clip.
+ ///
+ bool HasAncestorGeometryClip { get; }
///
/// Gets the child scene graph nodes.
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
index 75ecad7943..e472192c38 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
+++ b/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;
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneLayer.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneLayer.cs
index ffe38b7a8f..253842ea3b 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneLayer.cs
+++ b/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; }
}
}
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs
index e3d1ab164a..dd5740e4a9 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs
+++ b/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);
}
///
@@ -54,7 +57,10 @@ namespace Avalonia.Rendering.SceneGraph
public bool ClipToBounds { get; set; }
///
- public Geometry GeometryClip { get; set; }
+ public IGeometryImpl GeometryClip { get; set; }
+
+ ///
+ public bool HasAncestorGeometryClip { get; }
///
/// Gets or sets the opacity of the scene graph node.
diff --git a/src/Gtk/Avalonia.Cairo/Media/DrawingContext.cs b/src/Gtk/Avalonia.Cairo/Media/DrawingContext.cs
index 89c10ee2a2..ee01d5ecb0 100644
--- a/src/Gtk/Avalonia.Cairo/Media/DrawingContext.cs
+++ b/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();
}
diff --git a/src/Gtk/Avalonia.Cairo/Media/StreamGeometryImpl.cs b/src/Gtk/Avalonia.Cairo/Media/StreamGeometryImpl.cs
index 18d0060bff..31cba39276 100644
--- a/src/Gtk/Avalonia.Cairo/Media/StreamGeometryImpl.cs
+++ b/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);
diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs
index 05b1b27eb6..67fab819f6 100644
--- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs
+++ b/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()
diff --git a/src/Skia/Avalonia.Skia/StreamGeometryImpl.cs b/src/Skia/Avalonia.Skia/StreamGeometryImpl.cs
index aebf404c66..11e7f5c79e 100644
--- a/src/Skia/Avalonia.Skia/StreamGeometryImpl.cs
+++ b/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();
diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
index 1700fc9205..26207c23d2 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
+++ b/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);
diff --git a/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs
index cc99adccc8..8bb901a1e4 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs
@@ -38,6 +38,18 @@ namespace Avalonia.Direct2D1.Media
return Geometry.FillContainsPoint(point.ToSharpDX());
}
+ ///
+ 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);
+ }
+ }
+
///
public bool StrokeContains(Avalonia.Media.Pen pen, Point point)
{
diff --git a/src/Windows/Avalonia.Direct2D1/Media/StreamGeometryImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/StreamGeometryImpl.cs
index a32426843f..4c1bc3d6f7 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/StreamGeometryImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/StreamGeometryImpl.cs
@@ -23,7 +23,7 @@ namespace Avalonia.Direct2D1.Media
/// Initializes a new instance of the class.
///
/// An existing Direct2D .
- protected StreamGeometryImpl(PathGeometry geometry)
+ public StreamGeometryImpl(PathGeometry geometry)
: base(geometry)
{
}
diff --git a/tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs b/tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs
index 2a252579fc..6e100270ab 100644
--- a/tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs
+++ b/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
diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs
index 5a76c35669..af3abb0136 100644
--- a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs
+++ b/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);
}
}
diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests_Layers.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests_Layers.cs
index fcc4c68ffd..e65487ac44 100644
--- a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests_Layers.cs
+++ b/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();
+ 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);
+ }
+ }
}
}