|
|
|
@ -136,7 +136,6 @@ namespace Avalonia.Controls |
|
|
|
return new Size(padding.Left + padding.Right, padding.Bottom + padding.Top); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
internal class BorderRenderer |
|
|
|
{ |
|
|
|
private bool _useComplexRendering; |
|
|
|
@ -156,19 +155,28 @@ namespace Avalonia.Controls |
|
|
|
_useComplexRendering = true; |
|
|
|
|
|
|
|
var boundRect = new Rect(finalSize); |
|
|
|
var innerRect = boundRect.Deflate(borderThickness); |
|
|
|
var innerRadii = new Radii(cornerRadius, borderThickness, false); |
|
|
|
var innerRect = new Rect(borderThickness.Left, borderThickness.Top, |
|
|
|
boundRect.Width - borderThickness.Right, boundRect.Height - borderThickness.Bottom); |
|
|
|
|
|
|
|
StreamGeometry backgroundGeometry = null; |
|
|
|
|
|
|
|
// calculate border / background rendering geometry
|
|
|
|
if (!innerRect.Width.Equals(0) && !innerRect.Height.Equals(0)) |
|
|
|
if (!boundRect.Width.Equals(0) && !boundRect.Height.Equals(0)) |
|
|
|
{ |
|
|
|
backgroundGeometry = new StreamGeometry(); |
|
|
|
|
|
|
|
using (var ctx = backgroundGeometry.Open()) |
|
|
|
if (cornerRadius.IsEmpty) |
|
|
|
{ |
|
|
|
GenerateGeometry(ctx, innerRect, innerRadii); |
|
|
|
using (var ctx = backgroundGeometry.Open()) |
|
|
|
{ |
|
|
|
CreateGeometry(ctx, innerRect, cornerRadius); |
|
|
|
} |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
using (var ctx = backgroundGeometry.Open()) |
|
|
|
{ |
|
|
|
CreateGeometry(ctx, innerRect, cornerRadius); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
_backgroundGeometryCache = backgroundGeometry; |
|
|
|
@ -180,16 +188,31 @@ namespace Avalonia.Controls |
|
|
|
|
|
|
|
if (!boundRect.Width.Equals(0) && !boundRect.Height.Equals(0)) |
|
|
|
{ |
|
|
|
var outerRadii = new Radii(cornerRadius, borderThickness, true); |
|
|
|
var borderGeometry = new StreamGeometry(); |
|
|
|
|
|
|
|
using (var ctx = borderGeometry.Open()) |
|
|
|
if (cornerRadius.IsEmpty) |
|
|
|
{ |
|
|
|
GenerateGeometry(ctx, boundRect, outerRadii); |
|
|
|
using (var ctx = borderGeometry.Open()) |
|
|
|
{ |
|
|
|
CreateGeometry(ctx, boundRect, cornerRadius); |
|
|
|
|
|
|
|
if (backgroundGeometry != null) |
|
|
|
if (backgroundGeometry != null) |
|
|
|
{ |
|
|
|
CreateGeometry(ctx, innerRect, cornerRadius); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
using (var ctx = borderGeometry.Open()) |
|
|
|
{ |
|
|
|
GenerateGeometry(ctx, innerRect, innerRadii); |
|
|
|
CreateGeometry(ctx, boundRect, cornerRadius); |
|
|
|
|
|
|
|
if (backgroundGeometry != null) |
|
|
|
{ |
|
|
|
CreateGeometry(ctx, innerRect, cornerRadius); |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@ -202,228 +225,90 @@ namespace Avalonia.Controls |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
public void Render(DrawingContext context, Size size, Thickness borders, CornerRadius radii, IBrush background, IBrush borderBrush) |
|
|
|
private static void CreateGeometry(StreamGeometryContext context, Rect boundRect, CornerRadius cornerRadius) |
|
|
|
{ |
|
|
|
if (_useComplexRendering) |
|
|
|
var topLeft = new Point(boundRect.X + cornerRadius.TopLeft, boundRect.Y); |
|
|
|
var topRight = new Point(boundRect.Width - cornerRadius.TopRight, boundRect.Y); |
|
|
|
var rightTop = new Point(boundRect.Width, boundRect.Y + cornerRadius.TopRight); |
|
|
|
var rightBottom = new Point(boundRect.Width, boundRect.Height - cornerRadius.BottomRight); |
|
|
|
var bottomRight = new Point(boundRect.Width - cornerRadius.BottomRight, boundRect.Height); |
|
|
|
var bottomLeft = new Point(boundRect.X + cornerRadius.BottomLeft, boundRect.Height); |
|
|
|
var leftBottom = new Point(boundRect.X, boundRect.Height - cornerRadius.BottomLeft); |
|
|
|
var leftTop = new Point(boundRect.X, boundRect.Y + cornerRadius.TopLeft); |
|
|
|
|
|
|
|
context.BeginFigure(topLeft, true); |
|
|
|
|
|
|
|
//Top
|
|
|
|
context.LineTo(topRight); |
|
|
|
|
|
|
|
//TopRight corner
|
|
|
|
if (topRight != rightTop) |
|
|
|
{ |
|
|
|
IBrush brush; |
|
|
|
var borderGeometry = _borderGeometryCache; |
|
|
|
if (borderGeometry != null && (brush = borderBrush) != null) |
|
|
|
{ |
|
|
|
context.DrawGeometry(brush, null, borderGeometry); |
|
|
|
} |
|
|
|
|
|
|
|
var backgroundGeometry = _backgroundGeometryCache; |
|
|
|
if (backgroundGeometry != null && (brush = background) != null) |
|
|
|
{ |
|
|
|
context.DrawGeometry(brush, null, backgroundGeometry); |
|
|
|
} |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
var borderThickness = borders.Left; |
|
|
|
var cornerRadius = (float)radii.TopLeft; |
|
|
|
var rect = new Rect(size); |
|
|
|
|
|
|
|
if (background != null) |
|
|
|
{ |
|
|
|
context.FillRectangle(background, rect.Deflate(borders), cornerRadius); |
|
|
|
} |
|
|
|
|
|
|
|
if (borderBrush != null && borderThickness > 0) |
|
|
|
{ |
|
|
|
context.DrawRectangle(new Pen(borderBrush, borderThickness), rect.Deflate(borderThickness), cornerRadius); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private static void GenerateGeometry(StreamGeometryContext ctx, Rect rect, Radii radii) |
|
|
|
{ |
|
|
|
//
|
|
|
|
// Compute the coordinates of the key points
|
|
|
|
//
|
|
|
|
|
|
|
|
var topLeft = new Point(radii.LeftTop, 0); |
|
|
|
var topRight = new Point(rect.Width - radii.RightTop, 0); |
|
|
|
var rightTop = new Point(rect.Width, radii.TopRight); |
|
|
|
var rightBottom = new Point(rect.Width, rect.Height - radii.BottomRight); |
|
|
|
var bottomRight = new Point(rect.Width - radii.RightBottom, rect.Height); |
|
|
|
var bottomLeft = new Point(radii.LeftBottom, rect.Height); |
|
|
|
var leftBottom = new Point(0, rect.Height - radii.BottomLeft); |
|
|
|
var leftTop = new Point(0, radii.TopLeft); |
|
|
|
|
|
|
|
//
|
|
|
|
// Check keypoints 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 x = radii.LeftTop / (radii.LeftTop + radii.RightTop) * rect.Width; |
|
|
|
topLeft += new Point(x, 0); |
|
|
|
topRight += new Point(x, 0); |
|
|
|
context.ArcTo(rightTop, new Size(cornerRadius.TopRight, cornerRadius.TopRight), 0, false, SweepDirection.Clockwise); |
|
|
|
} |
|
|
|
|
|
|
|
// Right edge
|
|
|
|
if (rightTop.Y > rightBottom.Y) |
|
|
|
{ |
|
|
|
var y = radii.TopRight / (radii.TopRight + radii.BottomRight) * rect.Height; |
|
|
|
rightTop += new Point(0, y); |
|
|
|
rightBottom += new Point(0, y); |
|
|
|
} |
|
|
|
//Right
|
|
|
|
context.LineTo(rightBottom); |
|
|
|
|
|
|
|
// Bottom edge
|
|
|
|
if (bottomRight.X < bottomLeft.X) |
|
|
|
//BottomRight corner
|
|
|
|
if (rightBottom != bottomRight) |
|
|
|
{ |
|
|
|
var x = radii.LeftBottom / (radii.LeftBottom + radii.RightBottom) * rect.Width; |
|
|
|
bottomRight += new Point(x, 0); |
|
|
|
bottomLeft += new Point(x, 0); |
|
|
|
context.ArcTo(bottomRight, new Size(cornerRadius.BottomRight, cornerRadius.BottomRight), 0, false, SweepDirection.Clockwise); |
|
|
|
} |
|
|
|
|
|
|
|
// Left edge
|
|
|
|
if (leftBottom.Y < leftTop.Y) |
|
|
|
{ |
|
|
|
var y = radii.TopLeft / (radii.TopLeft + radii.BottomLeft) * rect.Height; |
|
|
|
leftBottom += new Point(0, y); |
|
|
|
leftTop += new Point(0, y); |
|
|
|
} |
|
|
|
//Bottom
|
|
|
|
context.LineTo(bottomLeft); |
|
|
|
|
|
|
|
//
|
|
|
|
// Add on offsets
|
|
|
|
//
|
|
|
|
|
|
|
|
var offset = new Vector(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); |
|
|
|
|
|
|
|
// Top
|
|
|
|
ctx.LineTo(topRight); |
|
|
|
|
|
|
|
// TopRight
|
|
|
|
var radiusX = rect.TopRight.X - topRight.X; |
|
|
|
var radiusY = rightTop.Y - rect.TopRight.Y; |
|
|
|
if (!radiusX.Equals(0) || !radiusY.Equals(0)) |
|
|
|
//BottomLeft corner
|
|
|
|
if (bottomLeft != leftBottom) |
|
|
|
{ |
|
|
|
ctx.ArcTo(rightTop, new Size(radiusX, radiusY), 0, false, SweepDirection.Clockwise); |
|
|
|
context.ArcTo(leftBottom, new Size(cornerRadius.BottomLeft, cornerRadius.BottomLeft), 0, false, SweepDirection.Clockwise); |
|
|
|
} |
|
|
|
|
|
|
|
// Right
|
|
|
|
ctx.LineTo(rightBottom); |
|
|
|
//Left
|
|
|
|
context.LineTo(leftTop); |
|
|
|
|
|
|
|
// BottomRight
|
|
|
|
radiusX = rect.BottomRight.X - bottomRight.X; |
|
|
|
radiusY = rect.BottomRight.Y - rightBottom.Y; |
|
|
|
if (!radiusX.Equals(0) || !radiusY.Equals(0)) |
|
|
|
//TopLeft corner
|
|
|
|
if (leftTop != topLeft) |
|
|
|
{ |
|
|
|
ctx.ArcTo(bottomRight, new Size(radiusX, radiusY), 0, false, SweepDirection.Clockwise); |
|
|
|
context.ArcTo(topLeft, new Size(cornerRadius.TopLeft, cornerRadius.TopLeft), 0, false, SweepDirection.Clockwise); |
|
|
|
} |
|
|
|
|
|
|
|
// Bottom
|
|
|
|
ctx.LineTo(bottomLeft); |
|
|
|
context.EndFigure(true); |
|
|
|
} |
|
|
|
|
|
|
|
// BottomLeft
|
|
|
|
radiusX = bottomLeft.X - rect.BottomLeft.X; |
|
|
|
radiusY = rect.BottomLeft.Y - leftBottom.Y; |
|
|
|
if (!radiusX.Equals(0) || !radiusY.Equals(0)) |
|
|
|
public void Render(DrawingContext context, Size size, Thickness borders, CornerRadius radii, IBrush background, IBrush borderBrush) |
|
|
|
{ |
|
|
|
if (_useComplexRendering) |
|
|
|
{ |
|
|
|
ctx.ArcTo(leftBottom, new Size(radiusX, radiusY), 0, false, SweepDirection.Clockwise); |
|
|
|
} |
|
|
|
|
|
|
|
// Left
|
|
|
|
ctx.LineTo(leftTop); |
|
|
|
var backgroundGeometry = _backgroundGeometryCache; |
|
|
|
if (backgroundGeometry != null) |
|
|
|
{ |
|
|
|
context.DrawGeometry(background, null, backgroundGeometry); |
|
|
|
} |
|
|
|
|
|
|
|
// TopLeft
|
|
|
|
radiusX = topLeft.X - rect.TopLeft.X; |
|
|
|
radiusY = leftTop.Y - rect.TopLeft.Y; |
|
|
|
if (!radiusX.Equals(0) || !radiusY.Equals(0)) |
|
|
|
{ |
|
|
|
ctx.ArcTo(topLeft, new Size(radiusX, radiusY), 0, false, SweepDirection.Clockwise); |
|
|
|
var borderGeometry = _borderGeometryCache; |
|
|
|
if (borderGeometry != null) |
|
|
|
{ |
|
|
|
context.DrawGeometry(borderBrush, null, borderGeometry); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
ctx.EndFigure(true); |
|
|
|
} |
|
|
|
|
|
|
|
private struct Radii |
|
|
|
{ |
|
|
|
internal Radii(CornerRadius radii, Thickness borders, bool outer) |
|
|
|
else |
|
|
|
{ |
|
|
|
var left = 0.5 * borders.Left; |
|
|
|
var top = 0.5 * borders.Top; |
|
|
|
var right = 0.5 * borders.Right; |
|
|
|
var bottom = 0.5 * borders.Bottom; |
|
|
|
var borderThickness = borders.Left; |
|
|
|
var cornerRadius = (float)radii.TopLeft; |
|
|
|
var rect = new Rect(size); |
|
|
|
|
|
|
|
if (outer) |
|
|
|
if (background != null) |
|
|
|
{ |
|
|
|
if (radii.TopLeft.Equals(0)) |
|
|
|
{ |
|
|
|
LeftTop = TopLeft = 0.0; |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
LeftTop = radii.TopLeft + left; |
|
|
|
TopLeft = radii.TopLeft + top; |
|
|
|
} |
|
|
|
if (radii.TopRight.Equals(0)) |
|
|
|
{ |
|
|
|
TopRight = RightTop = 0.0; |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
TopRight = radii.TopRight + top; |
|
|
|
RightTop = radii.TopRight + right; |
|
|
|
} |
|
|
|
if (radii.BottomRight.Equals(0)) |
|
|
|
{ |
|
|
|
RightBottom = BottomRight = 0.0; |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
RightBottom = radii.BottomRight + right; |
|
|
|
BottomRight = radii.BottomRight + bottom; |
|
|
|
} |
|
|
|
if (radii.BottomLeft.Equals(0)) |
|
|
|
{ |
|
|
|
BottomLeft = LeftBottom = 0.0; |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
BottomLeft = radii.BottomLeft + bottom; |
|
|
|
LeftBottom = radii.BottomLeft + left; |
|
|
|
} |
|
|
|
context.FillRectangle(background, rect.Deflate(borders), cornerRadius); |
|
|
|
} |
|
|
|
else |
|
|
|
|
|
|
|
if (borderBrush != null && borderThickness > 0) |
|
|
|
{ |
|
|
|
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); |
|
|
|
context.DrawRectangle(new Pen(borderBrush, borderThickness), rect.Deflate(borderThickness), cornerRadius); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
internal readonly double LeftTop; |
|
|
|
internal readonly double TopLeft; |
|
|
|
internal readonly double TopRight; |
|
|
|
internal readonly double RightTop; |
|
|
|
internal readonly double RightBottom; |
|
|
|
internal readonly double BottomRight; |
|
|
|
internal readonly double BottomLeft; |
|
|
|
internal readonly double LeftBottom; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|