From da580f0373e3ae154c48a64b9b27c48ed5004b26 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sun, 14 Jun 2020 20:24:59 +0300 Subject: [PATCH] Internal API for rounded border clip for usage with Border. --- samples/ControlCatalog/Pages/BorderPage.xaml | 7 +++++++ src/Avalonia.Controls/Border.cs | 5 ++++- .../Rendering/ImmediateRenderer.cs | 6 +++++- .../Rendering/SceneGraph/ClipNode.cs | 14 ++++++++++++-- .../SceneGraph/DeferredDrawingContextImpl.cs | 17 ++++++++++++++++- .../Rendering/SceneGraph/IVisualNode.cs | 2 +- .../Rendering/SceneGraph/SceneBuilder.cs | 5 +++++ .../Rendering/SceneGraph/VisualNode.cs | 8 ++++++-- src/Avalonia.Visuals/RoundedRect.cs | 11 +++++++++++ .../VisualTree/IVisualWithRoundRectClip.cs | 15 +++++++++++++++ .../Media/DrawingContextImpl.cs | 6 ++++++ 11 files changed, 88 insertions(+), 8 deletions(-) create mode 100644 src/Avalonia.Visuals/VisualTree/IVisualWithRoundRectClip.cs diff --git a/samples/ControlCatalog/Pages/BorderPage.xaml b/samples/ControlCatalog/Pages/BorderPage.xaml index c30056d5e5..429db7917e 100644 --- a/samples/ControlCatalog/Pages/BorderPage.xaml +++ b/samples/ControlCatalog/Pages/BorderPage.xaml @@ -29,6 +29,13 @@ Padding="16"> Rounded Corners + + + Clipped 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/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..936f5d30a7 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs @@ -207,7 +207,7 @@ namespace Avalonia.Rendering.SceneGraph { throw new NotSupportedException("Creating layers on a deferred drawing context not supported"); } - + /// public void PopClip() { @@ -282,6 +282,21 @@ namespace Avalonia.Rendering.SceneGraph ++_drawOperationindex; } } + + /// + 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) diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs index 679cf3daa0..6d12b5bca4 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs @@ -29,7 +29,7 @@ namespace Avalonia.Rendering.SceneGraph /// /// Gets the corner radius of visual. Contents are clipped to this radius. /// - CornerRadius CornerRadius { get; } + 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 6276d5c7c3..8fb6b2542a 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs @@ -47,7 +47,7 @@ namespace Avalonia.Rendering.SceneGraph public IVisualNode Parent { get; } /// - public CornerRadius CornerRadius { get; } + public CornerRadius ClipToBoundsRadius { get; set; } /// public Matrix Transform { get; set; } @@ -265,6 +265,7 @@ namespace Avalonia.Rendering.SceneGraph { Transform = Transform, ClipBounds = ClipBounds, + ClipToBoundsRadius = ClipToBoundsRadius, ClipToBounds = ClipToBounds, LayoutBounds = LayoutBounds, GeometryClip = GeometryClip, @@ -304,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/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs index bbb45cf64c..696dc5978e 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();