| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290 |
- using System;
- using Avalonia.Media;
- using Avalonia.Platform;
- namespace Avalonia.Controls.Utils
- {
- internal class BorderRenderHelper
- {
- private bool _useComplexRendering;
- private bool? _backendSupportsIndividualCorners;
- private StreamGeometry _backgroundGeometryCache;
- private StreamGeometry _borderGeometryCache;
- private Size _size;
- private Thickness _borderThickness;
- private CornerRadius _cornerRadius;
- private bool _initialized;
- void Update(Size finalSize, Thickness borderThickness, CornerRadius cornerRadius)
- {
- _backendSupportsIndividualCorners ??= AvaloniaLocator.Current.GetService<IPlatformRenderInterface>()
- .SupportsIndividualRoundRects;
- _size = finalSize;
- _borderThickness = borderThickness;
- _cornerRadius = cornerRadius;
- _initialized = true;
- if (borderThickness.IsUniform && (cornerRadius.IsUniform || _backendSupportsIndividualCorners == true))
- {
- _backgroundGeometryCache = null;
- _borderGeometryCache = null;
- _useComplexRendering = false;
- }
- else
- {
- _useComplexRendering = true;
- var boundRect = new Rect(finalSize);
- var innerRect = boundRect.Deflate(borderThickness);
- BorderGeometryKeypoints backgroundKeypoints = null;
- StreamGeometry backgroundGeometry = null;
- if (innerRect.Width != 0 && innerRect.Height != 0)
- {
- backgroundGeometry = new StreamGeometry();
- backgroundKeypoints = new BorderGeometryKeypoints(innerRect, borderThickness, cornerRadius, true);
- using (var ctx = backgroundGeometry.Open())
- {
- CreateGeometry(ctx, innerRect, backgroundKeypoints);
- }
- _backgroundGeometryCache = backgroundGeometry;
- }
- else
- {
- _backgroundGeometryCache = null;
- }
- if (boundRect.Width != 0 && innerRect.Height != 0)
- {
- var borderGeometryKeypoints = new BorderGeometryKeypoints(boundRect, borderThickness, cornerRadius, false);
- var borderGeometry = new StreamGeometry();
- using (var ctx = borderGeometry.Open())
- {
- CreateGeometry(ctx, boundRect, borderGeometryKeypoints);
- if (backgroundGeometry != null)
- {
- CreateGeometry(ctx, innerRect, backgroundKeypoints);
- }
- }
- _borderGeometryCache = borderGeometry;
- }
- else
- {
- _borderGeometryCache = null;
- }
- }
- }
- public void Render(DrawingContext context,
- Size finalSize, Thickness borderThickness, CornerRadius cornerRadius,
- IBrush background, IBrush borderBrush, BoxShadows boxShadows)
- {
- if (_size != finalSize
- || _borderThickness != borderThickness
- || _cornerRadius != cornerRadius
- || !_initialized)
- Update(finalSize, borderThickness, cornerRadius);
- RenderCore(context, background, borderBrush, boxShadows);
- }
- void RenderCore(DrawingContext context, IBrush background, IBrush borderBrush, BoxShadows boxShadows)
- {
- if (_useComplexRendering)
- {
- var backgroundGeometry = _backgroundGeometryCache;
- if (backgroundGeometry != null)
- {
- context.DrawGeometry(background, null, backgroundGeometry);
- }
- var borderGeometry = _borderGeometryCache;
- if (borderGeometry != null)
- {
- context.DrawGeometry(borderBrush, null, borderGeometry);
- }
- }
- else
- {
- var borderThickness = _borderThickness.Top;
- IPen pen = null;
- if (borderThickness > 0)
- {
- pen = new Pen(borderBrush, borderThickness);
- }
- var rrect = new RoundedRect(new Rect(_size), _cornerRadius.TopLeft, _cornerRadius.TopRight,
- _cornerRadius.BottomRight, _cornerRadius.BottomLeft);
- if (Math.Abs(borderThickness) > double.Epsilon)
- {
- rrect = rrect.Deflate(borderThickness * 0.5, borderThickness * 0.5);
- }
- context.PlatformImpl.DrawRectangle(background, pen, rrect, boxShadows);
- }
- }
- private static void CreateGeometry(StreamGeometryContext context, Rect boundRect, BorderGeometryKeypoints keypoints)
- {
- context.BeginFigure(keypoints.TopLeft, true);
- // Top
- context.LineTo(keypoints.TopRight);
- // TopRight corner
- var radiusX = boundRect.TopRight.X - keypoints.TopRight.X;
- var radiusY = keypoints.RightTop.Y - boundRect.TopRight.Y;
- if (radiusX != 0 || radiusY != 0)
- {
- context.ArcTo(keypoints.RightTop, new Size(radiusY, radiusY), 0, false, SweepDirection.Clockwise);
- }
- // Right
- context.LineTo(keypoints.RightBottom);
- // BottomRight corner
- radiusX = boundRect.BottomRight.X - keypoints.BottomRight.X;
- radiusY = boundRect.BottomRight.Y - keypoints.RightBottom.Y;
- if (radiusX != 0 || radiusY != 0)
- {
- context.ArcTo(keypoints.BottomRight, new Size(radiusX, radiusY), 0, false, SweepDirection.Clockwise);
- }
- // Bottom
- context.LineTo(keypoints.BottomLeft);
- // BottomLeft corner
- radiusX = keypoints.BottomLeft.X - boundRect.BottomLeft.X;
- radiusY = boundRect.BottomLeft.Y - keypoints.LeftBottom.Y;
- if (radiusX != 0 || radiusY != 0)
- {
- context.ArcTo(keypoints.LeftBottom, new Size(radiusX, radiusY), 0, false, SweepDirection.Clockwise);
- }
- // Left
- context.LineTo(keypoints.LeftTop);
- // TopLeft corner
- radiusX = keypoints.TopLeft.X - boundRect.TopLeft.X;
- radiusY = keypoints.LeftTop.Y - boundRect.TopLeft.Y;
- if (radiusX != 0 || radiusY != 0)
- {
- context.ArcTo(keypoints.TopLeft, new Size(radiusX, radiusY), 0, false, SweepDirection.Clockwise);
- }
- context.EndFigure(true);
- }
- private class BorderGeometryKeypoints
- {
- internal BorderGeometryKeypoints(Rect boundRect, Thickness borderThickness, CornerRadius cornerRadius, bool inner)
- {
- var left = 0.5 * borderThickness.Left;
- var top = 0.5 * borderThickness.Top;
- var right = 0.5 * borderThickness.Right;
- var bottom = 0.5 * borderThickness.Bottom;
- double leftTopY;
- double topLeftX;
- double topRightX;
- double rightTopY;
- double rightBottomY;
- double bottomRightX;
- double bottomLeftX;
- double leftBottomY;
- if (inner)
- {
- leftTopY = Math.Max(0, cornerRadius.TopLeft - top) + boundRect.TopLeft.Y;
- topLeftX = Math.Max(0, cornerRadius.TopLeft - left) + boundRect.TopLeft.X;
- topRightX = boundRect.Width - Math.Max(0, cornerRadius.TopRight - top) + boundRect.TopLeft.X;
- rightTopY = Math.Max(0, cornerRadius.TopRight - right) + boundRect.TopLeft.Y;
- rightBottomY = boundRect.Height - Math.Max(0, cornerRadius.BottomRight - bottom) + boundRect.TopLeft.Y;
- bottomRightX = boundRect.Width - Math.Max(0, cornerRadius.BottomRight - right) + boundRect.TopLeft.X;
- bottomLeftX = Math.Max(0, cornerRadius.BottomLeft - left) + boundRect.TopLeft.X;
- leftBottomY = boundRect.Height - Math.Max(0, cornerRadius.BottomLeft - bottom) + boundRect.TopLeft.Y;
- }
- else
- {
- leftTopY = cornerRadius.TopLeft + top + boundRect.TopLeft.Y;
- topLeftX = cornerRadius.TopLeft + left + boundRect.TopLeft.X;
- topRightX = boundRect.Width - (cornerRadius.TopRight + right) + boundRect.TopLeft.X;
- rightTopY = cornerRadius.TopRight + top + boundRect.TopLeft.Y;
- rightBottomY = boundRect.Height - (cornerRadius.BottomRight + bottom) + boundRect.TopLeft.Y;
- bottomRightX = boundRect.Width - (cornerRadius.BottomRight + right) + boundRect.TopLeft.X;
- bottomLeftX = cornerRadius.BottomLeft + left + boundRect.TopLeft.X;
- leftBottomY = boundRect.Height - (cornerRadius.BottomLeft + bottom) + boundRect.TopLeft.Y;
- }
- var leftTopX = boundRect.TopLeft.X;
- var topLeftY = boundRect.TopLeft.Y;
- var topRightY = boundRect.TopLeft.Y;
- var rightTopX = boundRect.Width + boundRect.TopLeft.X;
- var rightBottomX = boundRect.Width + boundRect.TopLeft.X;
- var bottomRightY = boundRect.Height + boundRect.TopLeft.Y;
- var bottomLeftY = boundRect.Height + boundRect.TopLeft.Y;
- var leftBottomX = boundRect.TopLeft.X;
- LeftTop = new Point(leftTopX, leftTopY);
- TopLeft = new Point(topLeftX, topLeftY);
- TopRight = new Point(topRightX, topRightY);
- RightTop = new Point(rightTopX, rightTopY);
- RightBottom = new Point(rightBottomX, rightBottomY);
- BottomRight = new Point(bottomRightX, bottomRightY);
- BottomLeft = new Point(bottomLeftX, bottomLeftY);
- LeftBottom = new Point(leftBottomX, leftBottomY);
- // Fix overlap
- if (TopLeft.X > TopRight.X)
- {
- var scaledX = topLeftX / (topLeftX + topRightX) * boundRect.Width;
- TopLeft = new Point(scaledX, TopLeft.Y);
- TopRight = new Point(scaledX, TopRight.Y);
- }
- if (RightTop.Y > RightBottom.Y)
- {
- var scaledY = rightBottomY / (rightTopY + rightBottomY) * boundRect.Height;
- RightTop = new Point(RightTop.X, scaledY);
- RightBottom = new Point(RightBottom.X, scaledY);
- }
- if (BottomRight.X < BottomLeft.X)
- {
- var scaledX = bottomLeftX / (bottomLeftX + bottomRightX) * boundRect.Width;
- BottomRight = new Point(scaledX, BottomRight.Y);
- BottomLeft = new Point(scaledX, BottomLeft.Y);
- }
- if (LeftBottom.Y < LeftTop.Y)
- {
- var scaledY = leftTopY / (leftTopY + leftBottomY) * boundRect.Height;
- LeftBottom = new Point(LeftBottom.X, scaledY);
- LeftTop = new Point(LeftTop.X, scaledY);
- }
- }
- internal Point LeftTop { get; }
- internal Point TopLeft { get; }
- internal Point TopRight { get; }
- internal Point RightTop { get; }
- internal Point RightBottom { get; }
- internal Point BottomRight { get; }
- internal Point BottomLeft { get; }
- internal Point LeftBottom { get; }
- }
- }
- }
|