Browse Source
Merge pull request #4081 from AvaloniaUI/feature/rounded-rect-clip
Border Clipping
pull/4140/head
danwalmsley
6 years ago
committed by
GitHub
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with
128 additions and
7 deletions
-
samples/ControlCatalog/Pages/BorderPage.xaml
-
src/Avalonia.Controls/Border.cs
-
src/Avalonia.Visuals/Media/DrawingContext.cs
-
src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs
-
src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs
-
src/Avalonia.Visuals/Rendering/SceneGraph/ClipNode.cs
-
src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
-
src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs
-
src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
-
src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs
-
src/Avalonia.Visuals/RoundedRect.cs
-
src/Avalonia.Visuals/VisualTree/IVisualWithRoundRectClip.cs
-
src/Skia/Avalonia.Skia/DrawingContextImpl.cs
-
src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs
-
src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
|
|
|
@ -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> |
|
|
|
|
|
|
|
@ -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; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@ -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>
|
|
|
|
|
|
|
|
@ -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>
|
|
|
|
|
|
|
|
@ -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()) |
|
|
|
|
|
|
|
@ -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) |
|
|
|
|
|
|
|
@ -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); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -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>
|
|
|
|
|
|
|
|
@ -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; |
|
|
|
|
|
|
|
|
|
|
|
@ -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; |
|
|
|
|
|
|
|
@ -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 || |
|
|
|
|
|
|
|
@ -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; } |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
@ -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() |
|
|
|
{ |
|
|
|
|
|
|
|
@ -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); |
|
|
|
|
|
|
|
@ -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(); |
|
|
|
|