Browse Source

Handle geometry clips on child layers.

scenegraph-after-breakage
Steven Kirk 9 years ago
parent
commit
5e4f5a6160
  1. 3
      samples/RenderTest/Pages/ClippingPage.xaml
  2. 2
      src/Avalonia.Visuals/Media/DrawingContext.cs
  3. 2
      src/Avalonia.Visuals/Media/IDrawingContextImpl.cs
  4. 7
      src/Avalonia.Visuals/Platform/IGeometryImpl.cs
  5. 10
      src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
  6. 2
      src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
  7. 8
      src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs
  8. 39
      src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
  9. 3
      src/Avalonia.Visuals/Rendering/SceneGraph/SceneLayer.cs
  10. 8
      src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs
  11. 4
      src/Gtk/Avalonia.Cairo/Media/DrawingContext.cs
  12. 5
      src/Gtk/Avalonia.Cairo/Media/StreamGeometryImpl.cs
  13. 4
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  14. 5
      src/Skia/Avalonia.Skia/StreamGeometryImpl.cs
  15. 4
      src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
  16. 12
      src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs
  17. 2
      src/Windows/Avalonia.Direct2D1/Media/StreamGeometryImpl.cs
  18. 35
      tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs
  19. 4
      tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs
  20. 35
      tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests_Layers.cs

3
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>

2
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);
}

2
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
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
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)

2
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
}

8
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.

39
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
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; }
}
}

8
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.

4
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
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);

4
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
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();

4
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
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)
{

2
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)
{
}

35
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

4
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
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);
}
}
}
}

Loading…
Cancel
Save