Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions samples/ControlCatalog/MainView.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@
<TabItem Header="Border">
<pages:BorderPage />
</TabItem>
<TabItem Header="Child Clipping">
<pages:ChildClippingPage />
</TabItem>
<TabItem Header="BitmapCache">
<pages:BitmapCachePage />
</TabItem>
Expand Down
46 changes: 46 additions & 0 deletions samples/ControlCatalog/Pages/BorderPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,52 @@
<Image Source="/Assets/maple-leaf-888807_640.jpg" Stretch="UniformToFill" />
</Border>
<TextBlock Text="Border with Clipping" HorizontalAlignment="Center" />

<StackPanel Spacing="8" Width="360">
<TextBlock Classes="h3">Rounded Border Child Clipping</TextBlock>

<Border Height="72"
Width="160"
Background="Red"
BorderBrush="Green"
BorderThickness="2"
CornerRadius="16">
<Border Width="80"
Height="72"
Background="Blue"
HorizontalAlignment="Right" />
</Border>
<TextBlock Text="Expected: child overlaps rounded corners" HorizontalAlignment="Center" />
<TextBlock Text="No ClipToBounds" HorizontalAlignment="Center" />

<Border Height="72"
Width="160"
Background="Red"
BorderBrush="Green"
BorderThickness="2"
CornerRadius="16"
ClipToBounds="True">
<Border Width="80"
Height="72"
Background="Blue"
HorizontalAlignment="Right" />
</Border>
<TextBlock Text="Expected: child clipped to inner rounded edge" HorizontalAlignment="Center" />
<TextBlock Text="ClipToBounds True" HorizontalAlignment="Center" />

<Border Height="80"
Width="200"
Background="LightGray"
BorderBrush="Black"
BorderThickness="6"
CornerRadius="16"
ClipToBounds="True">
<Border Background="Orange"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" />
</Border>
<TextBlock Text="Thick Border + Rounded Clip" HorizontalAlignment="Center" />
</StackPanel>
</StackPanel>
</StackPanel>
</UserControl>
83 changes: 83 additions & 0 deletions samples/ControlCatalog/Pages/ChildClippingPage.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<UserControl x:Class="ControlCatalog.Pages.ChildClippingPage"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:pages="using:ControlCatalog.Pages"
mc:Ignorable="d"
d:DesignHeight="800"
d:DesignWidth="400">
<ScrollViewer>
<StackPanel Margin="16" Spacing="16">
<TextBlock Classes="h2">Child Clipping</TextBlock>
<TextBlock TextWrapping="Wrap">
Border and ContentPresenter now clip only their children when ClipToBounds is enabled.
The control's own background and border are not clipped. Adorners respect rounded clips too.
</TextBlock>

<StackPanel Spacing="8">
<TextBlock Classes="h3">Border: Rounded Child Clip</TextBlock>
<Border Height="72"
Width="160"
Background="Red"
BorderBrush="Green"
BorderThickness="2"
CornerRadius="16"
ClipToBounds="True">
<Border Width="80"
Height="72"
Background="Blue"
HorizontalAlignment="Right" />
</Border>
<TextBlock Text="Only the child is clipped; the border/background remain intact" />
</StackPanel>

<StackPanel Spacing="8">
<TextBlock Classes="h3">ContentPresenter: Rounded Child Clip</TextBlock>
<ContentPresenter Width="160"
Height="72"
Background="Red"
BorderBrush="Green"
BorderThickness="2"
CornerRadius="16"
ClipToBounds="True"
HorizontalContentAlignment="Right">
<ContentPresenter.Content>
<Border Width="80"
Height="72"
Background="Blue" />
</ContentPresenter.Content>
</ContentPresenter>
<TextBlock Text="Only the child is clipped; the presenter background remains intact" />
</StackPanel>

<StackPanel Spacing="8">
<TextBlock Classes="h3">Adorner: Respects Rounded Clip</TextBlock>
<Border Width="160"
Height="160"
Background="Transparent"
BorderBrush="Black"
BorderThickness="4"
CornerRadius="24"
ClipToBounds="True">
<AdornerLayer.Adorner>
<Border Background="DeepSkyBlue"
Opacity="0.6"
AdornerLayer.IsClipEnabled="True" />
</AdornerLayer.Adorner>
</Border>
<TextBlock Text="Adorner is clipped to the rounded bounds of the adorned control" />
</StackPanel>

<StackPanel Spacing="8">
<TextBlock Classes="h3">Geometry Child Clip (Layout Clip)</TextBlock>
<pages:StarLayoutClipDecorator Width="200"
Height="200"
ClipToBounds="False">
<Border Background="DodgerBlue" />
</pages:StarLayoutClipDecorator>
<TextBlock Text="GetLayoutClip affects only children; ClipToBounds is not required" />
</StackPanel>
</StackPanel>
</ScrollViewer>
</UserControl>
73 changes: 73 additions & 0 deletions samples/ControlCatalog/Pages/ChildClippingPage.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using System;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Media;

namespace ControlCatalog.Pages
{
public partial class ChildClippingPage : UserControl
{
public ChildClippingPage()
{
InitializeComponent();
}
}

public sealed class StarLayoutClipDecorator : Decorator
{
private Geometry? _clipGeometry;
private Size _clipGeometrySize;

protected override Geometry? GetLayoutClip(Size layoutSlotSize)
{
return GetClipGeometry(Bounds.Size);
}

public override void Render(DrawingContext context)
{
context.FillRectangle(Brushes.LightGray, new Rect(Bounds.Size));
base.Render(context);
}

private Geometry GetClipGeometry(Size size)
{
if (_clipGeometry != null && _clipGeometrySize == size)
return _clipGeometry;

_clipGeometrySize = size;
_clipGeometry = BuildStarGeometry(size);
return _clipGeometry;
}

private static Geometry BuildStarGeometry(Size size)
{
var geometry = new StreamGeometry();
var center = new Point(size.Width * 0.5, size.Height * 0.5);
var outerRadius = Math.Min(size.Width, size.Height) * 0.45;
var innerRadius = outerRadius * 0.45;

using (var ctx = geometry.Open())
{
var angleStep = Math.PI / 5.0;
for (var i = 0; i < 10; i++)
{
var radius = (i % 2 == 0) ? outerRadius : innerRadius;
var angle = -Math.PI * 0.5 + angleStep * i;
var point = new Point(
center.X + Math.Cos(angle) * radius,
center.Y + Math.Sin(angle) * radius);

if (i == 0)
ctx.BeginFigure(point, true);
else
ctx.LineTo(point);
}

ctx.EndFigure(true);
}

return geometry;
}
}

}
1 change: 1 addition & 0 deletions src/Avalonia.Base/Layout/Layoutable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,7 @@ public void Arrange(Rect rect)
Logger.TryGet(LogEventLevel.Verbose, LogArea.Layout)?.Log(this, "Arrange to {Rect} ", rect);

IsArrangeValid = true;
SetLayoutSlotSize(rect.Size);
ArrangeCore(rect);
_previousArrange = rect;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Avalonia.Platform;
using Avalonia.Rendering.Composition.Drawing;
using Avalonia.Rendering.Composition.Server;
using Avalonia.Rendering.Composition.Transport;
Expand All @@ -16,6 +17,10 @@ internal class CompositionDrawListVisual : CompositionContainerVisual

private bool _drawListChanged;
private CompositionRenderData? _drawList;
private bool _childClipChanged;
private bool _hasChildClip;
private RoundedRect _childClip;
private IGeometryImpl? _childClipGeometry;

/// <summary>
/// The list of drawing commands
Expand All @@ -36,6 +41,30 @@ public CompositionRenderData? DrawList
}
}

internal void SetChildClip(RoundedRect clip, IGeometryImpl? geometryClip)
{
if (_hasChildClip && _childClip.Equals(clip) && ReferenceEquals(_childClipGeometry, geometryClip))
return;

_hasChildClip = true;
_childClip = clip;
_childClipGeometry = geometryClip;
_childClipChanged = true;
RegisterForSerialization();
}

internal void ClearChildClip()
{
if (!_hasChildClip && _childClipGeometry == null)
return;

_hasChildClip = false;
_childClip = default;
_childClipGeometry = null;
_childClipChanged = true;
RegisterForSerialization();
}

private protected override void SerializeChangesCore(BatchStreamWriter writer)
{
writer.Write((byte)(_drawListChanged ? 1 : 0));
Expand All @@ -44,6 +73,14 @@ private protected override void SerializeChangesCore(BatchStreamWriter writer)
writer.WriteObject(DrawList?.Server);
_drawListChanged = false;
}
writer.Write((byte)(_childClipChanged ? 1 : 0));
if (_childClipChanged)
{
writer.Write(_hasChildClip);
writer.Write(_childClip);
writer.WriteObject(_childClipGeometry);
_childClipChanged = false;
}
base.SerializeChangesCore(writer);
}

Expand Down
20 changes: 16 additions & 4 deletions src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using Avalonia.Media;
using Avalonia.Collections.Pooled;
using Avalonia.VisualTree;

Expand Down Expand Up @@ -71,16 +72,27 @@ void HitTestCore(CompositionVisual visual, Point parentPoint, PooledList<Composi
return;

var point = parentPoint.Transform(invMatrix);

if (visual.ClipToBounds
&& (point.X < 0 || point.Y < 0 || point.X > visual.Size.X || point.Y > visual.Size.Y))
var allowChildren = true;
if (visual is CompositionDrawListVisual { Visual: Visual childClipProvider }
&& childClipProvider.TryGetChildClip(out var childClip, out var childClipGeometry))
{
var clipContains = childClipGeometry == null
? childClip.ContainsExclusive(point)
: childClipGeometry.FillContains(point);
if (!clipContains)
allowChildren = false;
}
else if (visual.ClipToBounds &&
(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];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ internal class ServerCompositionDrawListVisual : ServerCompositionContainerVisua
public readonly Visual UiVisual;
#endif
private ServerCompositionRenderData? _renderCommands;
private bool _hasChildClip;
private RoundedRect _childClip;
private IGeometryImpl? _childClipGeometry;

public ServerCompositionDrawListVisual(ServerCompositor compositor, Visual v) : base(compositor)
{
Expand All @@ -29,6 +32,10 @@ public ServerCompositionDrawListVisual(ServerCompositor compositor, Visual v) :

public override LtrbRect? ComputeOwnContentBounds() => _renderCommands?.Bounds;

public bool HasChildClip => _hasChildClip;
public RoundedRect ChildClip => _childClip;
public IGeometryImpl? ChildClipGeometry => _childClipGeometry;

protected override void DeserializeChangesCore(BatchStreamReader reader, TimeSpan committedAt)
{
if (reader.Read<byte>() == 1)
Expand All @@ -38,6 +45,13 @@ protected override void DeserializeChangesCore(BatchStreamReader reader, TimeSpa
_renderCommands?.AddObserver(this);
InvalidateContent();
}
if (reader.Read<byte>() == 1)
{
_hasChildClip = reader.Read<bool>();
_childClip = reader.Read<RoundedRect>();
_childClipGeometry = reader.ReadObject<IGeometryImpl?>();
InvalidateContent();
}
base.DeserializeChangesCore(reader, committedAt);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ private bool AdornerLayer_WalkAdornerParentClipRecursive(ServerCompositionVisual

if (visual.ClipToBounds)
{
_canvas.PushClip(new Rect(0, 0, visual.Size.X, visual.Size.Y));
visual.PushClipToBounds(_canvas);
_adornerPushedClipStack!.Push((int)Op.PopClip);
}

Expand Down
Loading
Loading