226 lines
7.3 KiB
C#
226 lines
7.3 KiB
C#
namespace NeoUI.Controls;
|
|
|
|
public class Border : System.Windows.Controls.Border
|
|
{
|
|
protected override Geometry? GetLayoutClip(Size layoutSlotSize)
|
|
{
|
|
if (!ClipToBounds)
|
|
{
|
|
return base.GetLayoutClip(layoutSlotSize);
|
|
}
|
|
|
|
var borderThickness = BorderThickness;
|
|
var cornerRadius = CornerRadius;
|
|
var renderSize = RenderSize;
|
|
|
|
if (renderSize.Width <= 0 || renderSize.Height <= 0)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var rect = new Rect(0, 0, renderSize.Width, renderSize.Height);
|
|
var radii = new Radii(cornerRadius, borderThickness, true);
|
|
|
|
var layoutGeometry = new StreamGeometry();
|
|
using var ctx = layoutGeometry.Open();
|
|
GenerateGeometry(ctx, rect, radii);
|
|
|
|
layoutGeometry.Freeze();
|
|
return layoutGeometry;
|
|
}
|
|
|
|
internal static void GenerateGeometry(StreamGeometryContext ctx, Rect rect, Radii radii)
|
|
{
|
|
//
|
|
// compute the coordinates of the key points
|
|
//
|
|
|
|
Point topLeft = new(radii.LeftTop, 0);
|
|
Point topRight = new(rect.Width - radii.RightTop, 0);
|
|
Point rightTop = new(rect.Width, radii.TopRight);
|
|
Point rightBottom = new(rect.Width, rect.Height - radii.BottomRight);
|
|
Point bottomRight = new(rect.Width - radii.RightBottom, rect.Height);
|
|
Point bottomLeft = new(radii.LeftBottom, rect.Height);
|
|
Point leftBottom = new(0, rect.Height - radii.BottomLeft);
|
|
Point leftTop = new(0, radii.TopLeft);
|
|
|
|
//
|
|
// check key points for overlap and resolve by partitioning radii according to
|
|
// the percentage of each one.
|
|
//
|
|
|
|
// top edge is handled here
|
|
if (topLeft.X > topRight.X)
|
|
{
|
|
var v = radii.LeftTop / (radii.LeftTop + radii.RightTop) * rect.Width;
|
|
topLeft.X = v;
|
|
topRight.X = v;
|
|
}
|
|
|
|
// right edge
|
|
if (rightTop.Y > rightBottom.Y)
|
|
{
|
|
var v = radii.TopRight / (radii.TopRight + radii.BottomRight) * rect.Height;
|
|
rightTop.Y = v;
|
|
rightBottom.Y = v;
|
|
}
|
|
|
|
// bottom edge
|
|
if (bottomRight.X < bottomLeft.X)
|
|
{
|
|
var v = radii.LeftBottom / (radii.LeftBottom + radii.RightBottom) * rect.Width;
|
|
bottomRight.X = v;
|
|
bottomLeft.X = v;
|
|
}
|
|
|
|
// left edge
|
|
if (leftBottom.Y < leftTop.Y)
|
|
{
|
|
var v = radii.TopLeft / (radii.TopLeft + radii.BottomLeft) * rect.Height;
|
|
leftBottom.Y = v;
|
|
leftTop.Y = v;
|
|
}
|
|
|
|
//
|
|
// add on offsets
|
|
//
|
|
|
|
Vector offset = new(rect.TopLeft.X, rect.TopLeft.Y);
|
|
topLeft += offset;
|
|
topRight += offset;
|
|
rightTop += offset;
|
|
rightBottom += offset;
|
|
bottomRight += offset;
|
|
bottomLeft += offset;
|
|
leftBottom += offset;
|
|
leftTop += offset;
|
|
|
|
//
|
|
// create the border geometry
|
|
//
|
|
ctx.BeginFigure(topLeft, true /* is filled */, true /* is closed */);
|
|
|
|
// Top line
|
|
ctx.LineTo(topRight, true /* is stroked */, false /* is smooth join */);
|
|
|
|
// Upper-right corner
|
|
var radiusX = rect.TopRight.X - topRight.X;
|
|
var radiusY = rightTop.Y - rect.TopRight.Y;
|
|
if (!MathHelper.IsZero(radiusX) || !MathHelper.IsZero(radiusY))
|
|
{
|
|
ctx.ArcTo(rightTop, new Size(radiusX, radiusY), 0, false, SweepDirection.Clockwise, true, false);
|
|
}
|
|
|
|
// Right line
|
|
ctx.LineTo(rightBottom, true /* is stroked */, false /* is smooth join */);
|
|
|
|
// Lower-right corner
|
|
radiusX = rect.BottomRight.X - bottomRight.X;
|
|
radiusY = rect.BottomRight.Y - rightBottom.Y;
|
|
if (!MathHelper.IsZero(radiusX) || !MathHelper.IsZero(radiusY))
|
|
{
|
|
ctx.ArcTo(bottomRight, new Size(radiusX, radiusY), 0, false, SweepDirection.Clockwise, true, false);
|
|
}
|
|
|
|
// Bottom line
|
|
ctx.LineTo(bottomLeft, true /* is stroked */, false /* is smooth join */);
|
|
|
|
// Lower-left corner
|
|
radiusX = bottomLeft.X - rect.BottomLeft.X;
|
|
radiusY = rect.BottomLeft.Y - leftBottom.Y;
|
|
if (!MathHelper.IsZero(radiusX) || !MathHelper.IsZero(radiusY))
|
|
{
|
|
ctx.ArcTo(leftBottom, new Size(radiusX, radiusY), 0, false, SweepDirection.Clockwise, true, false);
|
|
}
|
|
|
|
// Left line
|
|
ctx.LineTo(leftTop, true /* is stroked */, false /* is smooth join */);
|
|
|
|
// Upper-left corner
|
|
radiusX = topLeft.X - rect.TopLeft.X;
|
|
radiusY = leftTop.Y - rect.TopLeft.Y;
|
|
if (!MathHelper.IsZero(radiusX) || !MathHelper.IsZero(radiusY))
|
|
{
|
|
ctx.ArcTo(topLeft, new Size(radiusX, radiusY), 0, false, SweepDirection.Clockwise, true, false);
|
|
}
|
|
}
|
|
|
|
internal struct Radii
|
|
{
|
|
internal Radii(CornerRadius radii, Thickness borders, bool outer)
|
|
{
|
|
var left = 0.5 * borders.Left;
|
|
var top = 0.5 * borders.Top;
|
|
var right = 0.5 * borders.Right;
|
|
var bottom = 0.5 * borders.Bottom;
|
|
|
|
if (outer)
|
|
{
|
|
if (MathHelper.IsZero(radii.TopLeft))
|
|
{
|
|
LeftTop = TopLeft = 0.0;
|
|
}
|
|
else
|
|
{
|
|
LeftTop = radii.TopLeft + left;
|
|
TopLeft = radii.TopLeft + top;
|
|
}
|
|
if (MathHelper.IsZero(radii.TopRight))
|
|
{
|
|
TopRight = RightTop = 0.0;
|
|
}
|
|
else
|
|
{
|
|
TopRight = radii.TopRight + top;
|
|
RightTop = radii.TopRight + right;
|
|
}
|
|
if (MathHelper.IsZero(radii.BottomRight))
|
|
{
|
|
RightBottom = BottomRight = 0.0;
|
|
}
|
|
else
|
|
{
|
|
RightBottom = radii.BottomRight + right;
|
|
BottomRight = radii.BottomRight + bottom;
|
|
}
|
|
if (MathHelper.IsZero(radii.BottomLeft))
|
|
{
|
|
BottomLeft = LeftBottom = 0.0;
|
|
}
|
|
else
|
|
{
|
|
BottomLeft = radii.BottomLeft + bottom;
|
|
LeftBottom = radii.BottomLeft + left;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LeftTop = Math.Max(0.0, radii.TopLeft - left);
|
|
TopLeft = Math.Max(0.0, radii.TopLeft - top);
|
|
TopRight = Math.Max(0.0, radii.TopRight - top);
|
|
RightTop = Math.Max(0.0, radii.TopRight - right);
|
|
RightBottom = Math.Max(0.0, radii.BottomRight - right);
|
|
BottomRight = Math.Max(0.0, radii.BottomRight - bottom);
|
|
BottomLeft = Math.Max(0.0, radii.BottomLeft - bottom);
|
|
LeftBottom = Math.Max(0.0, radii.BottomLeft - left);
|
|
}
|
|
}
|
|
|
|
internal double LeftTop;
|
|
internal double TopLeft;
|
|
internal double TopRight;
|
|
internal double RightTop;
|
|
internal double RightBottom;
|
|
internal double BottomRight;
|
|
internal double BottomLeft;
|
|
internal double LeftBottom;
|
|
}
|
|
}
|
|
|
|
file static class MathHelper
|
|
{
|
|
internal const double DBL_EPSILON = 2.2204460492503131e-016;
|
|
|
|
public static bool IsZero(double value) => Math.Abs(value) < 10.0 * DBL_EPSILON;
|
|
}
|