diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs b/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs
index f00eef0d10..f93ce6c933 100644
--- a/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs
+++ b/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs
@@ -97,15 +97,28 @@ namespace Avalonia.Rendering.Composition
if (!TryTransformTo(visual, globalPoint, out var point))
return;
- if (visual.ClipToBounds
- && (point.X < 0 || point.Y < 0 || point.X > visual.Size.X || point.Y > visual.Size.Y))
- return;
+ var allowChildren = true;
+ if (visual.ClipToBounds)
+ {
+ if (visual is CompositionDrawListVisual { Visual: IVisualWithChildClip childClipProvider }
+ && childClipProvider.TryGetChildClip(out var childClip))
+ {
+ if (point.X < 0 || point.Y < 0 || point.X > visual.Size.X || point.Y > visual.Size.Y)
+ return;
+ if (!childClip.ContainsExclusive(point))
+ allowChildren = false;
+ }
+ else if (point.X < 0 || point.Y < 0 || point.X > visual.Size.X || point.Y > visual.Size.Y)
+ {
+ return;
+ }
+ }
if (visual.Clip?.FillContains(point) == false)
return;
// Inspect children
- if (visual is CompositionContainerVisual cv)
+ if (allowChildren && visual is CompositionContainerVisual cv)
for (var c = cv.Children.Count - 1; c >= 0; c--)
{
var ch = cv.Children[c];
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs
index ba9b042ad3..7d8e751a71 100644
--- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs
@@ -40,16 +40,25 @@ internal class ServerCompositionDrawListVisual : ServerCompositionContainerVisua
base.DeserializeChangesCore(reader, committedAt);
}
- protected override void RenderCore(ServerVisualRenderContext context, LtrbRect currentTransformedClip)
+ protected void RenderOwnContent(ServerVisualRenderContext context, LtrbRect currentTransformedClip)
{
- if (_renderCommands != null
+ if (_renderCommands != null
&& context.ShouldRenderOwnContent(this, currentTransformedClip))
{
_renderCommands.Render(context.Canvas);
}
+ }
+ protected void RenderChildren(ServerVisualRenderContext context, LtrbRect currentTransformedClip)
+ {
base.RenderCore(context, currentTransformedClip);
}
+
+ protected override void RenderCore(ServerVisualRenderContext context, LtrbRect currentTransformedClip)
+ {
+ RenderOwnContent(context, currentTransformedClip);
+ RenderChildren(context, currentTransformedClip);
+ }
public void DependencyQueuedInvalidate(IServerRenderResource sender) => ValuesInvalidated();
diff --git a/src/Avalonia.Base/Rendering/ImmediateRenderer.cs b/src/Avalonia.Base/Rendering/ImmediateRenderer.cs
index f054e77db8..ebc94dc8ff 100644
--- a/src/Avalonia.Base/Rendering/ImmediateRenderer.cs
+++ b/src/Avalonia.Base/Rendering/ImmediateRenderer.cs
@@ -50,17 +50,21 @@ internal class ImmediateRenderer
transform = Matrix.CreateTranslation(bounds.Position);
}
+ var childClipProvider = visual.ClipToBounds ? visual as IVisualWithChildClip : null;
+ RoundedRect childClip = default;
+ var hasChildClip = childClipProvider != null && childClipProvider.TryGetChildClip(out childClip);
+
using (visual.TextOptions != default ? context.PushTextOptions(visual.TextOptions) : default(DrawingContext.PushedState?))
using (visual.RenderOptions != default ? context.PushRenderOptions(visual.RenderOptions) : default(DrawingContext.PushedState?))
using (context.PushTransform(transform))
using (visual.HasMirrorTransform ? context.PushTransform(new Matrix(-1.0, 0.0, 0.0, 1.0, visual.Bounds.Width, 0)) : default(DrawingContext.PushedState?))
using (context.PushOpacity(opacity))
- using (visual switch
+ using (!hasChildClip ? visual switch
{
{ ClipToBounds: true } and IVisualWithRoundRectClip roundClipVisual => context.PushClip(new RoundedRect(rect, roundClipVisual.ClipToBoundsRadius)),
{ ClipToBounds: true } => context.PushClip(rect),
_ => default(DrawingContext.PushedState?)
- })
+ } : default(DrawingContext.PushedState?))
using (visual.Clip is { } clip ? context.PushGeometryClip(clip) : default(DrawingContext.PushedState?))
using (visual.OpacityMask is { } opctMask ? context.PushOpacityMask(opctMask, rect) : default(DrawingContext.PushedState?))
{
@@ -79,12 +83,15 @@ internal class ImmediateRenderer
if (visual.ClipToBounds)
{
totalTransform = Matrix.Identity;
- clipRect = rect;
+ clipRect = hasChildClip ? childClip.Rect : rect;
}
- foreach (var child in childrenEnumerable)
+ using (hasChildClip ? context.PushClip(childClip) : default(DrawingContext.PushedState?))
{
- Render(context, child, child.Bounds, totalTransform, clipRect);
+ foreach (var child in childrenEnumerable)
+ {
+ Render(context, child, child.Bounds, totalTransform, clipRect);
+ }
}
}
}
diff --git a/src/Avalonia.Base/VisualTree/IVisualWithChildClip.cs b/src/Avalonia.Base/VisualTree/IVisualWithChildClip.cs
new file mode 100644
index 0000000000..fd67cb67fc
--- /dev/null
+++ b/src/Avalonia.Base/VisualTree/IVisualWithChildClip.cs
@@ -0,0 +1,15 @@
+using Avalonia.Media;
+
+namespace Avalonia.VisualTree
+{
+ ///
+ /// Provides a clip that should be applied to a visual's children only.
+ ///
+ internal interface IVisualWithChildClip
+ {
+ ///
+ /// Attempts to get a clip for the visual's children in local coordinates.
+ ///
+ bool TryGetChildClip(out RoundedRect clip);
+ }
+}
diff --git a/src/Avalonia.Controls/Border.cs b/src/Avalonia.Controls/Border.cs
index b816858632..960ef85d81 100644
--- a/src/Avalonia.Controls/Border.cs
+++ b/src/Avalonia.Controls/Border.cs
@@ -11,7 +11,7 @@ namespace Avalonia.Controls
/// A control which decorates a child with a border and background.
///
#pragma warning disable CS0618 // Type or member is obsolete
- public partial class Border : Decorator, IVisualWithRoundRectClip
+ public partial class Border : Decorator, IVisualWithRoundRectClip, IVisualWithChildClip
#pragma warning restore CS0618 // Type or member is obsolete
{
///
@@ -178,6 +178,8 @@ namespace Avalonia.Controls
/// The drawing context.
public sealed override void Render(DrawingContext context)
{
+ if (_borderVisual != null)
+ _borderVisual.BorderThickness = LayoutThickness;
_borderRenderHelper.Render(
context,
Bounds.Size,
@@ -218,5 +220,17 @@ namespace Avalonia.Controls
}
public CornerRadius ClipToBoundsRadius => CornerRadius;
+
+ bool IVisualWithChildClip.TryGetChildClip(out RoundedRect clip)
+ {
+ var bounds = new Rect(Bounds.Size);
+ var keypoints = GeometryBuilder.CalculateRoundedCornersRectangleWinUI(
+ bounds,
+ LayoutThickness,
+ CornerRadius,
+ BackgroundSizing.InnerBorderEdge);
+ clip = keypoints.ToRoundedRect();
+ return true;
+ }
}
}
diff --git a/src/Avalonia.Controls/BorderVisual.cs b/src/Avalonia.Controls/BorderVisual.cs
index 3046e7e875..f51cccbe7c 100644
--- a/src/Avalonia.Controls/BorderVisual.cs
+++ b/src/Avalonia.Controls/BorderVisual.cs
@@ -1,4 +1,6 @@
using System;
+using Avalonia;
+using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering.Composition;
using Avalonia.Rendering.Composition.Server;
@@ -11,6 +13,8 @@ class CompositionBorderVisual : CompositionDrawListVisual
{
private CornerRadius _cornerRadius;
private bool _cornerRadiusChanged;
+ private Thickness _borderThickness;
+ private bool _borderThicknessChanged;
public CompositionBorderVisual(Compositor compositor, Visual visual) : base(compositor,
new ServerBorderVisual(compositor.Server, visual), visual)
@@ -31,17 +35,35 @@ class CompositionBorderVisual : CompositionDrawListVisual
}
}
+ public Thickness BorderThickness
+ {
+ get => _borderThickness;
+ set
+ {
+ if (_borderThickness != value)
+ {
+ _borderThicknessChanged = true;
+ _borderThickness = value;
+ RegisterForSerialization();
+ }
+ }
+ }
+
private protected override void SerializeChangesCore(BatchStreamWriter writer)
{
base.SerializeChangesCore(writer);
writer.Write(_cornerRadiusChanged);
if (_cornerRadiusChanged)
writer.Write(_cornerRadius);
+ writer.Write(_borderThicknessChanged);
+ if (_borderThicknessChanged)
+ writer.Write(_borderThickness);
}
class ServerBorderVisual : ServerCompositionDrawListVisual
{
private CornerRadius _cornerRadius;
+ private Thickness _borderThickness;
public ServerBorderVisual(ServerCompositor compositor, Visual v) : base(compositor, v)
{
}
@@ -49,18 +71,22 @@ class CompositionBorderVisual : CompositionDrawListVisual
protected override void RenderCore(ServerVisualRenderContext ctx, LtrbRect currentTransformedClip)
{
var canvas = ctx.Canvas;
+ RenderOwnContent(ctx, currentTransformedClip);
+
if (ClipToBounds)
{
var clipRect = Root!.SnapToDevicePixels(new Rect(new Size(Size.X, Size.Y)));
- if (_cornerRadius == default)
- canvas.PushClip(clipRect);
- else
- canvas.PushClip(new RoundedRect(clipRect, _cornerRadius));
+ var keypoints = GeometryBuilder.CalculateRoundedCornersRectangleWinUI(
+ clipRect,
+ _borderThickness,
+ _cornerRadius,
+ BackgroundSizing.InnerBorderEdge);
+ canvas.PushClip(keypoints.ToRoundedRect());
}
- base.RenderCore(ctx, currentTransformedClip);
-
- if(ClipToBounds)
+ RenderChildren(ctx, currentTransformedClip);
+
+ if (ClipToBounds)
canvas.PopClip();
}
@@ -70,6 +96,8 @@ class CompositionBorderVisual : CompositionDrawListVisual
base.DeserializeChangesCore(reader, committedAt);
if (reader.Read())
_cornerRadius = reader.Read();
+ if (reader.Read())
+ _borderThickness = reader.Read();
}
protected override bool HandlesClipToBounds => true;