diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs index b742f1d44a..d724e14298 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs @@ -48,7 +48,7 @@ namespace Avalonia.Rendering.Composition.Server if (Opacity != 1) canvas.PushOpacity(Opacity); var boundsRect = new Rect(new Size(Size.X, Size.Y)); - if(ClipToBounds) + if (ClipToBounds && !HandlesClipToBounds) canvas.PushClip(Root!.SnapToDevicePixels(boundsRect)); if (Clip != null) canvas.PushGeometryClip(Clip); @@ -65,11 +65,13 @@ namespace Avalonia.Rendering.Composition.Server canvas.PopOpacityMask(); if (Clip != null) canvas.PopGeometryClip(); - if (ClipToBounds) + if (ClipToBounds && !HandlesClipToBounds) canvas.PopClip(); if(Opacity != 1) canvas.PopOpacity(); } + + protected virtual bool HandlesClipToBounds => false; private ReadbackData _readback0, _readback1, _readback2; diff --git a/src/Avalonia.Base/Visual.cs b/src/Avalonia.Base/Visual.cs index d12eb1d138..2bb4a29b3c 100644 --- a/src/Avalonia.Base/Visual.cs +++ b/src/Avalonia.Base/Visual.cs @@ -453,12 +453,15 @@ namespace Avalonia } } + private protected virtual CompositionDrawListVisual CreateCompositionVisual(Compositor compositor) + => new CompositionDrawListVisual(compositor, + new ServerCompositionDrawListVisual(compositor.Server, this), this); + internal CompositionVisual AttachToCompositor(Compositor compositor) { if (CompositionVisual == null || CompositionVisual.Compositor != compositor) { - CompositionVisual = new CompositionDrawListVisual(compositor, - new ServerCompositionDrawListVisual(compositor.Server, this), this); + CompositionVisual = CreateCompositionVisual(compositor); } return CompositionVisual; diff --git a/src/Avalonia.Controls/Border.cs b/src/Avalonia.Controls/Border.cs index bc740c133a..1bb574acd2 100644 --- a/src/Avalonia.Controls/Border.cs +++ b/src/Avalonia.Controls/Border.cs @@ -4,6 +4,7 @@ using Avalonia.Controls.Shapes; using Avalonia.Controls.Utils; using Avalonia.Layout; using Avalonia.Media; +using Avalonia.Rendering.Composition; using Avalonia.Utilities; using Avalonia.VisualTree; @@ -73,6 +74,7 @@ namespace Avalonia.Controls private readonly BorderRenderHelper _borderRenderHelper = new BorderRenderHelper(); private Thickness? _layoutThickness; private double _scale; + private CompositionBorderVisual? _borderVisual; /// /// Initializes static members of the class. @@ -101,6 +103,10 @@ namespace Avalonia.Controls case nameof(BorderThickness): _layoutThickness = null; break; + case nameof(CornerRadius): + if (_borderVisual != null) + _borderVisual.CornerRadius = CornerRadius; + break; } } @@ -245,6 +251,14 @@ namespace Avalonia.Controls return LayoutHelper.ArrangeChild(Child, finalSize, Padding, BorderThickness); } + private protected override CompositionDrawListVisual CreateCompositionVisual(Compositor compositor) + { + return _borderVisual = new CompositionBorderVisual(compositor, this) + { + CornerRadius = CornerRadius + }; + } + public CornerRadius ClipToBoundsRadius => CornerRadius; } } diff --git a/src/Avalonia.Controls/BorderVisual.cs b/src/Avalonia.Controls/BorderVisual.cs new file mode 100644 index 0000000000..7afbf9edcf --- /dev/null +++ b/src/Avalonia.Controls/BorderVisual.cs @@ -0,0 +1,76 @@ +using System; +using Avalonia.Rendering.Composition; +using Avalonia.Rendering.Composition.Server; +using Avalonia.Rendering.Composition.Transport; +using Avalonia.Rendering.SceneGraph; + +namespace Avalonia.Controls; + +class CompositionBorderVisual : CompositionDrawListVisual +{ + private CornerRadius _cornerRadius; + private bool _cornerRadiusChanged; + + public CompositionBorderVisual(Compositor compositor, Visual visual) : base(compositor, + new ServerBorderVisual(compositor.Server, visual), visual) + { + } + + public CornerRadius CornerRadius + { + get => _cornerRadius; + set + { + if (_cornerRadius != value) + { + _cornerRadiusChanged = true; + _cornerRadius = value; + RegisterForSerialization(); + } + } + } + + private protected override void SerializeChangesCore(BatchStreamWriter writer) + { + base.SerializeChangesCore(writer); + writer.Write(_cornerRadiusChanged); + if (_cornerRadiusChanged) + writer.Write(_cornerRadius); + } + + class ServerBorderVisual : ServerCompositionDrawListVisual + { + private CornerRadius _cornerRadius; + public ServerBorderVisual(ServerCompositor compositor, Visual v) : base(compositor, v) + { + } + + protected override void RenderCore(CompositorDrawingContextProxy canvas, Rect currentTransformedClip) + { + if (ClipToBounds) + { + var clipRect = Root!.SnapToDevicePixels(new Rect(new Size(Size.X, Size.Y))); + if (_cornerRadius.IsEmpty) + canvas.PushClip(clipRect); + else + canvas.PushClip(new RoundedRect(clipRect, _cornerRadius)); + } + + base.RenderCore(canvas, currentTransformedClip); + + if(ClipToBounds) + canvas.PopClip(); + + } + + protected override void DeserializeChangesCore(BatchStreamReader reader, TimeSpan commitedAt) + { + base.DeserializeChangesCore(reader, commitedAt); + if (reader.Read()) + _cornerRadius = reader.Read(); + } + + protected override bool HandlesClipToBounds => true; + } + +} \ No newline at end of file