in src/Avalonia.Base/Media/GeometryBuilder.cs [206:429]
public static RoundedRectKeypoints CalculateRoundedCornersRectangleWinUI(
Rect outerBounds,
Thickness borderThickness,
CornerRadius cornerRadius,
BackgroundSizing sizing)
{
// This was initially derived from WinUI:
// - CGeometryBuilder::CalculateRoundedCornersRectangle
// https://github.com/microsoft/microsoft-ui-xaml/blob/93742a178db8f625ba9299f62c21f656e0b195ad/dxaml/xcp/core/core/elements/geometry.cpp#L862-L869
//
// It has been modified to accept a BackgroundSizing parameter directly as well
// as to support BackgroundSizing.CenterBorder.
//
// Keep in mind:
// > In Xaml, the corner radius is defined to be the middle of the stroke
// > (i.e. half the border thickness extends to either side).
bool fOuter;
Rect boundRect = outerBounds;
if (sizing == BackgroundSizing.InnerBorderEdge)
{
boundRect = outerBounds.Deflate(borderThickness);
fOuter = false;
}
else if (sizing == BackgroundSizing.OuterBorderEdge)
{
fOuter = true;
}
else // CenterBorder
{
// This is a trick to support a 3rd state (CenterBorder) using the same WinUI-based algorithm.
// The WinUI algorithm only supports the fOuter = True|False parameter.
boundRect = outerBounds.Deflate(borderThickness * 0.5);
fOuter = false;
}
// Start of WinUI converted code
// WinUI's Point struct fields can be modified directly, Avalonia's Point is read-only.
// Therefore, we will use doubles for calculation so multiple Point structs aren't
// required during calculations -- everything can be done with these double variables.
double fLeftTop;
double fLeftBottom;
double fTopLeft;
double fTopRight;
double fRightTop;
double fRightBottom;
double fBottomLeft;
double fBottomRight;
double left;
double right;
double top;
double bottom;
// If the caller wants to take the border into account
// initialize the borders variables
if (borderThickness != default)
{
left = 0.5 * borderThickness.Left;
right = 0.5 * borderThickness.Right;
top = 0.5 * borderThickness.Top;
bottom = 0.5 * borderThickness.Bottom;
}
else
{
left = 0.0;
right = 0.0;
top = 0.0;
bottom = 0.0;
}
// The following if/else block initializes the variables
// of which the points of the path will be created
// In case of outer, add the border - if any.
// Otherwise (inner rectangle) subtract the border - if any
if (fOuter)
{
if (MathUtilities.AreClose(cornerRadius.TopLeft, 0.0, Epsilon))
{
fLeftTop = 0.0;
fTopLeft = 0.0;
}
else
{
fLeftTop = cornerRadius.TopLeft + left;
fTopLeft = cornerRadius.TopLeft + top;
}
if (MathUtilities.AreClose(cornerRadius.TopRight, 0.0, Epsilon))
{
fTopRight = 0.0;
fRightTop = 0.0;
}
else
{
fTopRight = cornerRadius.TopRight + top;
fRightTop = cornerRadius.TopRight + right;
}
if (MathUtilities.AreClose(cornerRadius.BottomRight, 0.0, Epsilon))
{
fRightBottom = 0.0;
fBottomRight = 0.0;
}
else
{
fRightBottom = cornerRadius.BottomRight + right;
fBottomRight = cornerRadius.BottomRight + bottom;
}
if (MathUtilities.AreClose(cornerRadius.BottomLeft, 0.0, Epsilon))
{
fBottomLeft = 0.0;
fLeftBottom = 0.0;
}
else
{
fBottomLeft = cornerRadius.BottomLeft + bottom;
fLeftBottom = cornerRadius.BottomLeft + left;
}
}
else
{
fLeftTop = Math.Max(0.0, cornerRadius.TopLeft - left);
fTopLeft = Math.Max(0.0, cornerRadius.TopLeft - top);
fTopRight = Math.Max(0.0, cornerRadius.TopRight - top);
fRightTop = Math.Max(0.0, cornerRadius.TopRight - right);
fRightBottom = Math.Max(0.0, cornerRadius.BottomRight - right);
fBottomRight = Math.Max(0.0, cornerRadius.BottomRight - bottom);
fBottomLeft = Math.Max(0.0, cornerRadius.BottomLeft - bottom);
fLeftBottom = Math.Max(0.0, cornerRadius.BottomLeft - left);
}
double topLeftX = fLeftTop;
double topLeftY = 0;
double topRightX = boundRect.Width - fRightTop;
double topRightY = 0;
double rightTopX = boundRect.Width;
double rightTopY = fTopRight;
double rightBottomX = boundRect.Width;
double rightBottomY = boundRect.Height - fBottomRight;
double bottomRightX = boundRect.Width - fRightBottom;
double bottomRightY = boundRect.Height;
double bottomLeftX = fLeftBottom;
double bottomLeftY = boundRect.Height;
double leftBottomX = 0;
double leftBottomY = boundRect.Height - fBottomLeft;
double leftTopX = 0;
double leftTopY = fTopLeft;
// check keypoints for overlap and resolve by partitioning radii according to
// the percentage of each one.
// top edge
if (topLeftX > topRightX)
{
double v = (fLeftTop) / (fLeftTop + fRightTop) * boundRect.Width;
topLeftX = v;
topRightX = v;
}
// right edge
if (rightTopY > rightBottomY)
{
double v = (fTopRight) / (fTopRight + fBottomRight) * boundRect.Height;
rightTopY = v;
rightBottomY = v;
}
// bottom edge
if (bottomRightX < bottomLeftX)
{
double v = (fLeftBottom) / (fLeftBottom + fRightBottom) * boundRect.Width;
bottomRightX = v;
bottomLeftX = v;
}
// left edge
if (leftBottomY < leftTopY)
{
double v = (fTopLeft) / (fTopLeft + fBottomLeft) * boundRect.Height;
leftBottomY = v;
leftTopY = v;
}
// The above code does all calculations without taking into consideration X/Y absolute position.
// In WinUI, this is compensated for in DrawRoundedCornersRectangle(); however, we do this here directly
// when the final keypoints are being created.
var keypoints = new RoundedRectKeypoints();
keypoints.TopLeft = new Point(
boundRect.X + topLeftX,
boundRect.Y + topLeftY);
keypoints.TopRight = new Point(
boundRect.X + topRightX,
boundRect.Y + topRightY);
keypoints.RightTop = new Point(
boundRect.X + rightTopX,
boundRect.Y + rightTopY);
keypoints.RightBottom = new Point(
boundRect.X + rightBottomX,
boundRect.Y + rightBottomY);
keypoints.BottomRight = new Point(
boundRect.X + bottomRightX,
boundRect.Y + bottomRightY);
keypoints.BottomLeft = new Point(
boundRect.X + bottomLeftX,
boundRect.Y + bottomLeftY);
keypoints.LeftBottom = new Point(
boundRect.X + leftBottomX,
boundRect.Y + leftBottomY);
keypoints.LeftTop = new Point(
boundRect.X + leftTopX,
boundRect.Y + leftTopY);
return keypoints;
}