Browse Source

Add child-only clip support for Border

pull/20647/head
Wiesław Šoltés 1 month ago
parent
commit
9fcdacd063
  1. 21
      src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs
  2. 13
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs
  3. 17
      src/Avalonia.Base/Rendering/ImmediateRenderer.cs
  4. 15
      src/Avalonia.Base/VisualTree/IVisualWithChildClip.cs
  5. 16
      src/Avalonia.Controls/Border.cs
  6. 42
      src/Avalonia.Controls/BorderVisual.cs

21
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];

13
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();

17
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);
}
}
}
}

15
src/Avalonia.Base/VisualTree/IVisualWithChildClip.cs

@ -0,0 +1,15 @@
using Avalonia.Media;
namespace Avalonia.VisualTree
{
/// <summary>
/// Provides a clip that should be applied to a visual's children only.
/// </summary>
internal interface IVisualWithChildClip
{
/// <summary>
/// Attempts to get a clip for the visual's children in local coordinates.
/// </summary>
bool TryGetChildClip(out RoundedRect clip);
}
}

16
src/Avalonia.Controls/Border.cs

@ -11,7 +11,7 @@ namespace Avalonia.Controls
/// A control which decorates a child with a border and background.
/// </summary>
#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
{
/// <summary>
@ -178,6 +178,8 @@ namespace Avalonia.Controls
/// <param name="context">The drawing context.</param>
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;
}
}
}

42
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<bool>())
_cornerRadius = reader.Read<CornerRadius>();
if (reader.Read<bool>())
_borderThickness = reader.Read<Thickness>();
}
protected override bool HandlesClipToBounds => true;

Loading…
Cancel
Save