diff --git a/samples/ControlCatalog/Pages/BorderPage.xaml b/samples/ControlCatalog/Pages/BorderPage.xaml
index c30056d5e5..8133d0e408 100644
--- a/samples/ControlCatalog/Pages/BorderPage.xaml
+++ b/samples/ControlCatalog/Pages/BorderPage.xaml
@@ -29,6 +29,13 @@
Padding="16">
Rounded Corners
+
+
+
+
diff --git a/src/Avalonia.Controls/Border.cs b/src/Avalonia.Controls/Border.cs
index b355310244..c8337df99c 100644
--- a/src/Avalonia.Controls/Border.cs
+++ b/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
{
///
/// A control which decorates a child with a border and background.
///
- public partial class Border : Decorator
+ public partial class Border : Decorator, IVisualWithRoundRectClip
{
///
/// Defines the property.
@@ -129,5 +130,7 @@ namespace Avalonia.Controls
{
return LayoutHelper.ArrangeChild(Child, finalSize, Padding, BorderThickness);
}
+
+ public CornerRadius ClipToBoundsRadius => CornerRadius;
}
}
diff --git a/src/Avalonia.Visuals/Media/DrawingContext.cs b/src/Avalonia.Visuals/Media/DrawingContext.cs
index b1cf1aecc9..ba7191d7a6 100644
--- a/src/Avalonia.Visuals/Media/DrawingContext.cs
+++ b/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);
+ }
+
///
/// Pushes a clip rectangle.
///
diff --git a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs
index 660d10c088..c87946b3ea 100644
--- a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs
+++ b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs
@@ -107,6 +107,12 @@ namespace Avalonia.Platform
/// The clip rectangle.
void PushClip(Rect clip);
+ ///
+ /// Pushes a clip rounded rectangle.
+ ///
+ /// The clip rounded rectangle
+ void PushClip(RoundedRect clip);
+
///
/// Pops the latest pushed clip rectangle.
///
diff --git a/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs b/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs
index c1c4b6bc99..b1f51b941a 100644
--- a/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs
+++ b/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())
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/ClipNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/ClipNode.cs
index 34f042e334..ada04bfefd 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/ClipNode.cs
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/ClipNode.cs
@@ -16,6 +16,16 @@ namespace Avalonia.Rendering.SceneGraph
{
Clip = clip;
}
+
+ ///
+ /// Initializes a new instance of the class that represents a
+ /// clip push.
+ ///
+ /// The clip to push.
+ public ClipNode(RoundedRect clip)
+ {
+ Clip = clip;
+ }
///
/// Initializes a new instance of the class that represents a
@@ -31,7 +41,7 @@ namespace Avalonia.Rendering.SceneGraph
///
/// Gets the clip to be pushed or null if the operation represents a pop.
///
- public Rect? Clip { get; }
+ public RoundedRect? Clip { get; }
///
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.
///
- public bool Equals(Rect? clip) => Clip == clip;
+ public bool Equals(RoundedRect? clip) => Clip == clip;
///
public void Render(IDrawingContextImpl context)
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
index 6ad71ac111..dfb21a0289 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
@@ -163,7 +163,7 @@ namespace Avalonia.Rendering.SceneGraph
++_drawOperationindex;
}
}
-
+
public void Custom(ICustomDrawOperation custom)
{
var next = NextDrawAs();
@@ -283,6 +283,21 @@ namespace Avalonia.Rendering.SceneGraph
}
}
+ ///
+ public void PushClip(RoundedRect clip)
+ {
+ var next = NextDrawAs();
+
+ if (next == null || !next.Item.Equals(clip))
+ {
+ Add(new ClipNode(clip));
+ }
+ else
+ {
+ ++_drawOperationindex;
+ }
+ }
+
///
public void PushGeometryClip(IGeometryImpl clip)
{
@@ -368,7 +383,7 @@ namespace Avalonia.Rendering.SceneGraph
{
using (var refCounted = RefCountable.Create(node))
{
- Add(refCounted);
+ Add(refCounted);
}
}
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs
index 8b77c37c1f..6d12b5bca4 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs
@@ -26,6 +26,11 @@ namespace Avalonia.Rendering.SceneGraph
///
Matrix Transform { get; }
+ ///
+ /// Gets the corner radius of visual. Contents are clipped to this radius.
+ ///
+ CornerRadius ClipToBoundsRadius { get; }
+
///
/// Gets the bounds of the node's geometry in global coordinates.
///
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
index 263a491933..5da44c5943 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
+++ b/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;
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs
index 6f566ff6d6..8fb6b2542a 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs
@@ -46,6 +46,9 @@ namespace Avalonia.Rendering.SceneGraph
///
public IVisualNode Parent { get; }
+ ///
+ public CornerRadius ClipToBoundsRadius { get; set; }
+
///
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;
diff --git a/src/Avalonia.Visuals/RoundedRect.cs b/src/Avalonia.Visuals/RoundedRect.cs
index ad860240f2..3452bc1ff8 100644
--- a/src/Avalonia.Visuals/RoundedRect.cs
+++ b/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 ||
diff --git a/src/Avalonia.Visuals/VisualTree/IVisualWithRoundRectClip.cs b/src/Avalonia.Visuals/VisualTree/IVisualWithRoundRectClip.cs
new file mode 100644
index 0000000000..9ace215d03
--- /dev/null
+++ b/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
+ {
+ ///
+ /// Gets a value indicating the corner radius of control's clip bounds
+ ///
+ [Obsolete("Internal API, will be removed in future versions, you've been warned")]
+ CornerRadius ClipToBoundsRadius { get; }
+
+ }
+}
diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs
index 26fdb08a4b..ae756f4eab 100644
--- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs
+++ b/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());
+ }
+
///
public void PopClip()
{
diff --git a/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs b/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs
index 6375f74c59..ec7e0a67ed 100644
--- a/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs
+++ b/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);
diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
index 9b7ba4844a..e0de40525f 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
+++ b/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();