Browse Source

Merge pull request #4081 from AvaloniaUI/feature/rounded-rect-clip

Border Clipping
pull/4140/head
danwalmsley 6 years ago
committed by GitHub
parent
commit
39f2c333fc
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 7
      samples/ControlCatalog/Pages/BorderPage.xaml
  2. 5
      src/Avalonia.Controls/Border.cs
  3. 6
      src/Avalonia.Visuals/Media/DrawingContext.cs
  4. 6
      src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs
  5. 6
      src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs
  6. 14
      src/Avalonia.Visuals/Rendering/SceneGraph/ClipNode.cs
  7. 19
      src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
  8. 5
      src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs
  9. 5
      src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
  10. 9
      src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs
  11. 11
      src/Avalonia.Visuals/RoundedRect.cs
  12. 15
      src/Avalonia.Visuals/VisualTree/IVisualWithRoundRectClip.cs
  13. 6
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  14. 15
      src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs
  15. 6
      src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs

7
samples/ControlCatalog/Pages/BorderPage.xaml

@ -29,6 +29,13 @@
Padding="16">
<TextBlock>Rounded Corners</TextBlock>
</Border>
<Border BorderBrush="{DynamicResource ThemeAccentBrush2}" Width="100" Height="100"
BorderThickness="0"
Background="White"
CornerRadius="100" ClipToBounds="True">
<Image Source="/Assets/maple-leaf-888807_640.jpg" Stretch="UniformToFill" />
</Border>
<TextBlock Text="Border with Clipping" HorizontalAlignment="Center" />
</StackPanel>
</StackPanel>
</UserControl>

5
src/Avalonia.Controls/Border.cs

@ -1,13 +1,14 @@
using Avalonia.Controls.Utils;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.VisualTree;
namespace Avalonia.Controls
{
/// <summary>
/// A control which decorates a child with a border and background.
/// </summary>
public partial class Border : Decorator
public partial class Border : Decorator, IVisualWithRoundRectClip
{
/// <summary>
/// Defines the <see cref="Background"/> property.
@ -129,5 +130,7 @@ namespace Avalonia.Controls
{
return LayoutHelper.ArrangeChild(Child, finalSize, Padding, BorderThickness);
}
public CornerRadius ClipToBoundsRadius => CornerRadius;
}
}

6
src/Avalonia.Visuals/Media/DrawingContext.cs

@ -283,6 +283,12 @@ namespace Avalonia.Media
}
public PushedState PushClip(RoundedRect clip)
{
PlatformImpl.PushClip(clip);
return new PushedState(this, PushedState.PushedStateType.Clip);
}
/// <summary>
/// Pushes a clip rectangle.
/// </summary>

6
src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs

@ -107,6 +107,12 @@ namespace Avalonia.Platform
/// <param name="clip">The clip rectangle.</param>
void PushClip(Rect clip);
/// <summary>
/// Pushes a clip rounded rectangle.
/// </summary>
/// <param name="clip">The clip rounded rectangle</param>
void PushClip(RoundedRect clip);
/// <summary>
/// Pops the latest pushed clip rectangle.
/// </summary>

6
src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs

@ -289,7 +289,11 @@ namespace Avalonia.Rendering
using (context.PushPostTransform(m))
using (context.PushOpacity(opacity))
using (clipToBounds ? context.PushClip(bounds) : default(DrawingContext.PushedState))
using (clipToBounds
? visual is IVisualWithRoundRectClip roundClipVisual
? context.PushClip(new RoundedRect(bounds, roundClipVisual.ClipToBoundsRadius))
: context.PushClip(bounds)
: default(DrawingContext.PushedState))
using (visual.Clip != null ? context.PushGeometryClip(visual.Clip) : default(DrawingContext.PushedState))
using (visual.OpacityMask != null ? context.PushOpacityMask(visual.OpacityMask, bounds) : default(DrawingContext.PushedState))
using (context.PushTransformContainer())

14
src/Avalonia.Visuals/Rendering/SceneGraph/ClipNode.cs

@ -16,6 +16,16 @@ namespace Avalonia.Rendering.SceneGraph
{
Clip = clip;
}
/// <summary>
/// Initializes a new instance of the <see cref="ClipNode"/> class that represents a
/// clip push.
/// </summary>
/// <param name="clip">The clip to push.</param>
public ClipNode(RoundedRect clip)
{
Clip = clip;
}
/// <summary>
/// Initializes a new instance of the <see cref="ClipNode"/> class that represents a
@ -31,7 +41,7 @@ namespace Avalonia.Rendering.SceneGraph
/// <summary>
/// Gets the clip to be pushed or null if the operation represents a pop.
/// </summary>
public Rect? Clip { get; }
public RoundedRect? Clip { get; }
/// <inheritdoc/>
public bool HitTest(Point p) => false;
@ -45,7 +55,7 @@ namespace Avalonia.Rendering.SceneGraph
/// The properties of the other draw operation are passed in as arguments to prevent
/// allocation of a not-yet-constructed draw operation object.
/// </remarks>
public bool Equals(Rect? clip) => Clip == clip;
public bool Equals(RoundedRect? clip) => Clip == clip;
/// <inheritdoc/>
public void Render(IDrawingContextImpl context)

19
src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs

@ -163,7 +163,7 @@ namespace Avalonia.Rendering.SceneGraph
++_drawOperationindex;
}
}
public void Custom(ICustomDrawOperation custom)
{
var next = NextDrawAs<CustomDrawOperation>();
@ -283,6 +283,21 @@ namespace Avalonia.Rendering.SceneGraph
}
}
/// <inheritdoc />
public void PushClip(RoundedRect clip)
{
var next = NextDrawAs<ClipNode>();
if (next == null || !next.Item.Equals(clip))
{
Add(new ClipNode(clip));
}
else
{
++_drawOperationindex;
}
}
/// <inheritdoc/>
public void PushGeometryClip(IGeometryImpl clip)
{
@ -368,7 +383,7 @@ namespace Avalonia.Rendering.SceneGraph
{
using (var refCounted = RefCountable.Create(node))
{
Add(refCounted);
Add(refCounted);
}
}

5
src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs

@ -26,6 +26,11 @@ namespace Avalonia.Rendering.SceneGraph
/// </summary>
Matrix Transform { get; }
/// <summary>
/// Gets the corner radius of visual. Contents are clipped to this radius.
/// </summary>
CornerRadius ClipToBoundsRadius { get; }
/// <summary>
/// Gets the bounds of the node's geometry in global coordinates.
/// </summary>

5
src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs

@ -163,6 +163,10 @@ namespace Avalonia.Rendering.SceneGraph
var visual = node.Visual;
var opacity = visual.Opacity;
var clipToBounds = visual.ClipToBounds;
var clipToBoundsRadius = visual is IVisualWithRoundRectClip roundRectClip ?
roundRectClip.ClipToBoundsRadius :
default;
var bounds = new Rect(visual.Bounds.Size);
var contextImpl = (DeferredDrawingContextImpl)context.PlatformImpl;
@ -201,6 +205,7 @@ namespace Avalonia.Rendering.SceneGraph
node.ClipBounds = clipBounds;
node.ClipToBounds = clipToBounds;
node.LayoutBounds = globalBounds;
node.ClipToBoundsRadius = clipToBoundsRadius;
node.GeometryClip = visual.Clip?.PlatformImpl;
node.Opacity = opacity;

9
src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs

@ -46,6 +46,9 @@ namespace Avalonia.Rendering.SceneGraph
/// <inheritdoc/>
public IVisualNode Parent { get; }
/// <inheritdoc/>
public CornerRadius ClipToBoundsRadius { get; set; }
/// <inheritdoc/>
public Matrix Transform { get; set; }
@ -262,6 +265,7 @@ namespace Avalonia.Rendering.SceneGraph
{
Transform = Transform,
ClipBounds = ClipBounds,
ClipToBoundsRadius = ClipToBoundsRadius,
ClipToBounds = ClipToBounds,
LayoutBounds = LayoutBounds,
GeometryClip = GeometryClip,
@ -301,7 +305,10 @@ namespace Avalonia.Rendering.SceneGraph
if (ClipToBounds)
{
context.Transform = Matrix.Identity;
context.PushClip(ClipBounds);
if (ClipToBoundsRadius.IsEmpty)
context.PushClip(ClipBounds);
else
context.PushClip(new RoundedRect(ClipBounds, ClipToBoundsRadius));
}
context.Transform = Transform;

11
src/Avalonia.Visuals/RoundedRect.cs

@ -27,6 +27,10 @@ namespace Avalonia
}
}
public static bool operator ==(RoundedRect left, RoundedRect right) => left.Equals(right);
public static bool operator !=(RoundedRect left, RoundedRect right) => !left.Equals(right);
public Rect Rect { get; }
public Vector RadiiTopLeft { get; }
public Vector RadiiTopRight { get; }
@ -74,6 +78,13 @@ namespace Avalonia
}
public RoundedRect(in Rect bounds, in CornerRadius radius) : this(bounds,
radius.TopLeft, radius.TopRight,
radius.BottomRight, radius.BottomLeft)
{
}
public static implicit operator RoundedRect(Rect r) => new RoundedRect(r);
public bool IsRounded => RadiiTopLeft != default || RadiiTopRight != default || RadiiBottomRight != default ||

15
src/Avalonia.Visuals/VisualTree/IVisualWithRoundRectClip.cs

@ -0,0 +1,15 @@
using System;
namespace Avalonia.VisualTree
{
[Obsolete("Internal API, will be removed in future versions, you've been warned")]
public interface IVisualWithRoundRectClip
{
/// <summary>
/// Gets a value indicating the corner radius of control's clip bounds
/// </summary>
[Obsolete("Internal API, will be removed in future versions, you've been warned")]
CornerRadius ClipToBoundsRadius { get; }
}
}

6
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@ -389,6 +389,12 @@ namespace Avalonia.Skia
Canvas.ClipRect(clip.ToSKRect());
}
public void PushClip(RoundedRect clip)
{
Canvas.Save();
Canvas.ClipRoundRect(clip.ToSKRoundRect());
}
/// <inheritdoc />
public void PopClip()
{

15
src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs

@ -40,6 +40,21 @@ namespace Avalonia.Skia
return new SKRect((float)r.X, (float)r.Y, (float)r.Right, (float)r.Bottom);
}
public static SKRoundRect ToSKRoundRect(this RoundedRect r)
{
var rc = r.Rect.ToSKRect();
var result = new SKRoundRect();
result.SetRectRadii(rc,
new[]
{
r.RadiiTopLeft.ToSKPoint(), r.RadiiTopRight.ToSKPoint(),
r.RadiiBottomRight.ToSKPoint(), r.RadiiBottomLeft.ToSKPoint(),
});
return result;
}
public static Rect ToAvaloniaRect(this SKRect r)
{
return new Rect(r.Left, r.Top, r.Right - r.Left, r.Bottom - r.Top);

6
src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs

@ -359,6 +359,12 @@ namespace Avalonia.Direct2D1.Media
_deviceContext.PushAxisAlignedClip(clip.ToSharpDX(), AntialiasMode.PerPrimitive);
}
public void PushClip(RoundedRect clip)
{
//TODO: radius
_deviceContext.PushAxisAlignedClip(clip.Rect.ToDirect2D(), AntialiasMode.PerPrimitive);
}
public void PopClip()
{
_deviceContext.PopAxisAlignedClip();

Loading…
Cancel
Save