csharpc-sharpdotnetxamlavaloniauicross-platformcross-platform-xamlavaloniaguimulti-platformuser-interfacedotnetcore
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
290 lines
12 KiB
290 lines
12 KiB
using System;
|
|
using Avalonia.Media;
|
|
using Avalonia.Platform;
|
|
using Avalonia.Utilities;
|
|
|
|
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 rect = new Rect(_size);
|
|
if (!MathUtilities.IsZero(borderThickness))
|
|
rect = rect.Deflate(borderThickness * 0.5);
|
|
var rrect = new RoundedRect(rect, _cornerRadius.TopLeft, _cornerRadius.TopRight,
|
|
_cornerRadius.BottomRight, _cornerRadius.BottomLeft);
|
|
|
|
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; }
|
|
}
|
|
}
|
|
}
|
|
|