mirror of
https://gitee.com/handyorg/HandyControl.git
synced 2024-11-29 18:38:30 +08:00
chore: add avalonia DashedBorder.
This commit is contained in:
parent
d704338680
commit
a6c4f7b2f3
@ -0,0 +1,607 @@
|
|||||||
|
using System;
|
||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Collections;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Layout;
|
||||||
|
using Avalonia.Media;
|
||||||
|
using HandyControl.Tools;
|
||||||
|
using HandyControl.Tools.Extension;
|
||||||
|
|
||||||
|
namespace HandyControl.Controls;
|
||||||
|
|
||||||
|
public class DashedBorder : Decorator
|
||||||
|
{
|
||||||
|
public static readonly StyledProperty<double> BorderDashThicknessProperty =
|
||||||
|
AvaloniaProperty.Register<DashedBorder, double>(nameof(BorderDashThickness));
|
||||||
|
|
||||||
|
public static readonly StyledProperty<AvaloniaList<double>?> BorderDashArrayProperty =
|
||||||
|
AvaloniaProperty.Register<DashedBorder, AvaloniaList<double>?>(nameof(BorderDashArray));
|
||||||
|
|
||||||
|
public static readonly StyledProperty<PenLineCap> BorderDashCapProperty =
|
||||||
|
AvaloniaProperty.Register<DashedBorder, PenLineCap>(nameof(BorderDashCap));
|
||||||
|
|
||||||
|
public static readonly StyledProperty<double> BorderDashOffsetProperty =
|
||||||
|
AvaloniaProperty.Register<DashedBorder, double>(nameof(BorderDashOffset));
|
||||||
|
|
||||||
|
public static readonly StyledProperty<Thickness> BorderThicknessProperty =
|
||||||
|
Border.BorderThicknessProperty.AddOwner<DashedBorder>();
|
||||||
|
|
||||||
|
public static readonly StyledProperty<CornerRadius> CornerRadiusProperty =
|
||||||
|
Border.CornerRadiusProperty.AddOwner<DashedBorder>();
|
||||||
|
|
||||||
|
public static readonly StyledProperty<IBrush?> BorderBrushProperty =
|
||||||
|
Border.BorderBrushProperty.AddOwner<DashedBorder>();
|
||||||
|
|
||||||
|
public static readonly StyledProperty<IBrush?> BackgroundProperty =
|
||||||
|
Border.BackgroundProperty.AddOwner<DashedBorder>();
|
||||||
|
|
||||||
|
private Thickness? _layoutThickness;
|
||||||
|
private double _scale;
|
||||||
|
private bool _useComplexRendering;
|
||||||
|
private StreamGeometry? _backgroundGeometryCache;
|
||||||
|
private StreamGeometry? _borderGeometryCache;
|
||||||
|
private Pen? _leftPenCache;
|
||||||
|
private Pen? _rightPenCache;
|
||||||
|
private Pen? _topPenCache;
|
||||||
|
private Pen? _bottomPenCache;
|
||||||
|
private Pen? _geometryPenCache;
|
||||||
|
|
||||||
|
static DashedBorder()
|
||||||
|
{
|
||||||
|
AffectsMeasure<DashedBorder>(
|
||||||
|
BorderDashThicknessProperty,
|
||||||
|
BorderThicknessProperty
|
||||||
|
);
|
||||||
|
AffectsRender<DashedBorder>(
|
||||||
|
BorderDashArrayProperty,
|
||||||
|
BorderDashCapProperty,
|
||||||
|
BorderDashOffsetProperty,
|
||||||
|
CornerRadiusProperty,
|
||||||
|
BorderBrushProperty,
|
||||||
|
BackgroundProperty
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double BorderDashThickness
|
||||||
|
{
|
||||||
|
get => GetValue(BorderDashThicknessProperty);
|
||||||
|
set => SetValue(BorderDashThicknessProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AvaloniaList<double>? BorderDashArray
|
||||||
|
{
|
||||||
|
get => GetValue(BorderDashArrayProperty);
|
||||||
|
set => SetValue(BorderDashArrayProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PenLineCap BorderDashCap
|
||||||
|
{
|
||||||
|
get => GetValue(BorderDashCapProperty);
|
||||||
|
set => SetValue(BorderDashCapProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double BorderDashOffset
|
||||||
|
{
|
||||||
|
get => GetValue(BorderDashOffsetProperty);
|
||||||
|
set => SetValue(BorderDashOffsetProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Thickness BorderThickness
|
||||||
|
{
|
||||||
|
get => GetValue(BorderThicknessProperty);
|
||||||
|
set => SetValue(BorderThicknessProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CornerRadius CornerRadius
|
||||||
|
{
|
||||||
|
get => GetValue(CornerRadiusProperty);
|
||||||
|
set => SetValue(CornerRadiusProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IBrush? BorderBrush
|
||||||
|
{
|
||||||
|
get => GetValue(BorderBrushProperty);
|
||||||
|
set => SetValue(BorderBrushProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IBrush? Background
|
||||||
|
{
|
||||||
|
get => GetValue(BackgroundProperty);
|
||||||
|
set => SetValue(BackgroundProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Thickness LayoutThickness
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
VerifyScale();
|
||||||
|
|
||||||
|
if (_layoutThickness.HasValue)
|
||||||
|
{
|
||||||
|
return _layoutThickness.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
Thickness thickness = BorderThickness;
|
||||||
|
if (UseLayoutRounding)
|
||||||
|
{
|
||||||
|
thickness = LayoutHelper.RoundLayoutThickness(thickness, _scale, _scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
_layoutThickness = thickness;
|
||||||
|
|
||||||
|
return _layoutThickness.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void VerifyScale()
|
||||||
|
{
|
||||||
|
double layoutScale = LayoutHelper.GetLayoutScale(this);
|
||||||
|
if (MathHelper.AreClose(layoutScale, this._scale))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_scale = layoutScale;
|
||||||
|
_layoutThickness = new Thickness?();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
|
||||||
|
{
|
||||||
|
base.OnPropertyChanged(change);
|
||||||
|
|
||||||
|
string propertyName = change.Property.Name;
|
||||||
|
if (string.Equals(propertyName, nameof(BorderThickness)) ||
|
||||||
|
string.Equals(propertyName, nameof(BorderDashThickness)) ||
|
||||||
|
string.Equals(propertyName, nameof(BorderBrush)) ||
|
||||||
|
string.Equals(propertyName, nameof(Background)) ||
|
||||||
|
string.Equals(propertyName, nameof(BorderDashArray)) ||
|
||||||
|
string.Equals(propertyName, nameof(BorderDashCap)) ||
|
||||||
|
string.Equals(propertyName, nameof(BorderDashOffset)))
|
||||||
|
{
|
||||||
|
ClearCache();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ClearCache()
|
||||||
|
{
|
||||||
|
_leftPenCache = null;
|
||||||
|
_rightPenCache = null;
|
||||||
|
_topPenCache = null;
|
||||||
|
_bottomPenCache = null;
|
||||||
|
_geometryPenCache = null;
|
||||||
|
_backgroundGeometryCache = null;
|
||||||
|
_borderGeometryCache = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Render(DrawingContext context)
|
||||||
|
{
|
||||||
|
var radii = CornerRadius;
|
||||||
|
var borderBrush = BorderBrush;
|
||||||
|
var background = Background;
|
||||||
|
var borderThickness = BorderThickness;
|
||||||
|
bool useLayoutRounding = UseLayoutRounding;
|
||||||
|
bool uniformCorners = AreUniformCorners(radii);
|
||||||
|
|
||||||
|
_useComplexRendering = !uniformCorners;
|
||||||
|
if (!_useComplexRendering && borderBrush != null)
|
||||||
|
{
|
||||||
|
_useComplexRendering = !MathHelper.IsZero(radii.TopLeft) && !borderThickness.IsUniform;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_useComplexRendering)
|
||||||
|
{
|
||||||
|
var boundRect = new Rect(Bounds.Size);
|
||||||
|
var innerRect = boundRect.Deflate(borderThickness);
|
||||||
|
var innerRadii = new Radii(radii, borderThickness, false);
|
||||||
|
|
||||||
|
if (_backgroundGeometryCache == null &&
|
||||||
|
!MathHelper.IsZero(innerRect.Width) &&
|
||||||
|
!MathHelper.IsZero(innerRect.Height))
|
||||||
|
{
|
||||||
|
var backgroundGeometry = new StreamGeometry();
|
||||||
|
|
||||||
|
using (var ctx = backgroundGeometry.Open())
|
||||||
|
{
|
||||||
|
GenerateGeometry(ctx, innerRect, innerRadii);
|
||||||
|
}
|
||||||
|
|
||||||
|
_backgroundGeometryCache = backgroundGeometry;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_backgroundGeometryCache = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_borderGeometryCache == null &&
|
||||||
|
!MathHelper.IsZero(boundRect.Width) &&
|
||||||
|
!MathHelper.IsZero(boundRect.Height))
|
||||||
|
{
|
||||||
|
var outerRadii = new Radii(radii, borderThickness, true);
|
||||||
|
var borderGeometry = new StreamGeometry();
|
||||||
|
|
||||||
|
using (var ctx = borderGeometry.Open())
|
||||||
|
{
|
||||||
|
GenerateGeometry(ctx, boundRect, outerRadii);
|
||||||
|
}
|
||||||
|
|
||||||
|
_borderGeometryCache = borderGeometry;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_borderGeometryCache = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_backgroundGeometryCache = null;
|
||||||
|
_borderGeometryCache = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_useComplexRendering)
|
||||||
|
{
|
||||||
|
if (_borderGeometryCache != null && borderBrush != null)
|
||||||
|
{
|
||||||
|
_geometryPenCache ??= new Pen
|
||||||
|
{
|
||||||
|
Brush = borderBrush,
|
||||||
|
Thickness = BorderDashThickness,
|
||||||
|
LineCap = BorderDashCap,
|
||||||
|
DashStyle = new DashStyle(BorderDashArray, BorderDashOffset)
|
||||||
|
};
|
||||||
|
|
||||||
|
context.DrawGeometry(null, _geometryPenCache, _borderGeometryCache);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_backgroundGeometryCache != null && background != null)
|
||||||
|
{
|
||||||
|
context.DrawGeometry(background, null, _backgroundGeometryCache);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var cornerRadius = CornerRadius;
|
||||||
|
double outerCornerRadius = cornerRadius.TopLeft;
|
||||||
|
bool roundedCorners = !MathHelper.IsZero(outerCornerRadius);
|
||||||
|
|
||||||
|
if (!borderThickness.IsZero() && borderBrush != null)
|
||||||
|
{
|
||||||
|
_leftPenCache ??= new Pen
|
||||||
|
{
|
||||||
|
Brush = borderBrush,
|
||||||
|
Thickness = LayoutThickness.Left,
|
||||||
|
LineCap = BorderDashCap,
|
||||||
|
DashStyle = new DashStyle(BorderDashArray, BorderDashOffset)
|
||||||
|
};
|
||||||
|
|
||||||
|
double halfThickness;
|
||||||
|
var renderSize = Bounds.Size;
|
||||||
|
|
||||||
|
if (borderThickness.IsUniform())
|
||||||
|
{
|
||||||
|
halfThickness = _leftPenCache.Thickness * 0.5;
|
||||||
|
var rect = new Rect(
|
||||||
|
new Point(halfThickness, halfThickness),
|
||||||
|
new Point(renderSize.Width - halfThickness, renderSize.Height - halfThickness)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (roundedCorners)
|
||||||
|
{
|
||||||
|
context.DrawRectangle(null, _leftPenCache, rect, outerCornerRadius, outerCornerRadius);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
context.DrawRectangle(null, _leftPenCache, rect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (MathHelper.GreaterThan(borderThickness.Left, 0))
|
||||||
|
{
|
||||||
|
halfThickness = _leftPenCache.Thickness * 0.5;
|
||||||
|
context.DrawLine(
|
||||||
|
_leftPenCache,
|
||||||
|
new Point(halfThickness, 0),
|
||||||
|
new Point(halfThickness, renderSize.Height)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MathHelper.GreaterThan(borderThickness.Right, 0))
|
||||||
|
{
|
||||||
|
_rightPenCache ??= new Pen
|
||||||
|
{
|
||||||
|
Brush = borderBrush,
|
||||||
|
Thickness = LayoutThickness.Right,
|
||||||
|
LineCap = BorderDashCap,
|
||||||
|
DashStyle = new DashStyle(BorderDashArray, BorderDashOffset)
|
||||||
|
};
|
||||||
|
|
||||||
|
halfThickness = _rightPenCache.Thickness * 0.5;
|
||||||
|
context.DrawLine(
|
||||||
|
_rightPenCache,
|
||||||
|
new Point(renderSize.Width - halfThickness, 0),
|
||||||
|
new Point(renderSize.Width - halfThickness, renderSize.Height)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MathHelper.GreaterThan(borderThickness.Top, 0))
|
||||||
|
{
|
||||||
|
_topPenCache ??= new Pen
|
||||||
|
{
|
||||||
|
Brush = borderBrush,
|
||||||
|
Thickness = LayoutThickness.Top,
|
||||||
|
LineCap = BorderDashCap,
|
||||||
|
DashStyle = new DashStyle(BorderDashArray, BorderDashOffset)
|
||||||
|
};
|
||||||
|
|
||||||
|
halfThickness = _topPenCache.Thickness * 0.5;
|
||||||
|
context.DrawLine(
|
||||||
|
_topPenCache,
|
||||||
|
new Point(0, halfThickness),
|
||||||
|
new Point(renderSize.Width, halfThickness)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MathHelper.GreaterThan(borderThickness.Bottom, 0))
|
||||||
|
{
|
||||||
|
_bottomPenCache ??= new Pen
|
||||||
|
{
|
||||||
|
Brush = borderBrush,
|
||||||
|
Thickness = LayoutThickness.Bottom,
|
||||||
|
LineCap = BorderDashCap,
|
||||||
|
DashStyle = new DashStyle(BorderDashArray, BorderDashOffset)
|
||||||
|
};
|
||||||
|
|
||||||
|
halfThickness = _bottomPenCache.Thickness * 0.5;
|
||||||
|
context.DrawLine(
|
||||||
|
_bottomPenCache,
|
||||||
|
new Point(0, renderSize.Height - halfThickness),
|
||||||
|
new Point(renderSize.Width, renderSize.Height - halfThickness)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (background != null)
|
||||||
|
{
|
||||||
|
Point pointLeftTop, pointRightBottom;
|
||||||
|
|
||||||
|
if (useLayoutRounding)
|
||||||
|
{
|
||||||
|
pointLeftTop = new Point(LayoutThickness.Left, LayoutThickness.Top);
|
||||||
|
pointRightBottom = new Point(
|
||||||
|
Bounds.Size.Width - LayoutThickness.Right,
|
||||||
|
Bounds.Size.Height - LayoutThickness.Bottom
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pointLeftTop = new Point(borderThickness.Left, borderThickness.Top);
|
||||||
|
pointRightBottom = new Point(
|
||||||
|
Bounds.Size.Width - borderThickness.Right,
|
||||||
|
Bounds.Size.Height - borderThickness.Bottom
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pointRightBottom.X > pointLeftTop.X && pointRightBottom.Y > pointLeftTop.Y)
|
||||||
|
{
|
||||||
|
if (roundedCorners)
|
||||||
|
{
|
||||||
|
var innerRadii = new Radii(cornerRadius, borderThickness, false);
|
||||||
|
double innerCornerRadius = innerRadii._topLeft;
|
||||||
|
context.DrawRectangle(
|
||||||
|
brush: background,
|
||||||
|
pen: null,
|
||||||
|
rect: new Rect(pointLeftTop, pointRightBottom),
|
||||||
|
radiusX: innerCornerRadius,
|
||||||
|
radiusY: innerCornerRadius
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
context.DrawRectangle(
|
||||||
|
brush: background,
|
||||||
|
pen: null,
|
||||||
|
rect: new Rect(pointLeftTop, pointRightBottom)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Size MeasureOverride(Size availableSize)
|
||||||
|
{
|
||||||
|
return LayoutHelper.MeasureChild(Child, availableSize, Padding, BorderThickness);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Size ArrangeOverride(Size finalSize)
|
||||||
|
{
|
||||||
|
return LayoutHelper.ArrangeChild(Child, finalSize, Padding, BorderThickness);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool AreUniformCorners(CornerRadius borderRadii)
|
||||||
|
{
|
||||||
|
double topLeft = borderRadii.TopLeft;
|
||||||
|
return MathHelper.AreClose(topLeft, borderRadii.TopRight) &&
|
||||||
|
MathHelper.AreClose(topLeft, borderRadii.BottomLeft) &&
|
||||||
|
MathHelper.AreClose(topLeft, borderRadii.BottomRight);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void GenerateGeometry(StreamGeometryContext ctx, Rect rect, Radii radii)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
|
||||||
|
// top edge
|
||||||
|
if (topLeft.X > topRight.X)
|
||||||
|
{
|
||||||
|
double v = radii._leftTop / (radii._leftTop + radii._rightTop) * rect.Width;
|
||||||
|
topLeft = topLeft.WithX(v);
|
||||||
|
topRight = topRight.WithX(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
// right edge
|
||||||
|
if (rightTop.Y > rightBottom.Y)
|
||||||
|
{
|
||||||
|
double v = radii._topRight / (radii._topRight + radii._bottomRight) * rect.Height;
|
||||||
|
rightTop = rightTop.WithY(v);
|
||||||
|
rightBottom = rightBottom.WithY(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
// bottom edge
|
||||||
|
if (bottomRight.X < bottomLeft.X)
|
||||||
|
{
|
||||||
|
double v = radii._leftBottom / (radii._leftBottom + radii._rightBottom) * rect.Width;
|
||||||
|
bottomRight = bottomRight.WithX(v);
|
||||||
|
bottomLeft = bottomLeft.WithX(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
// left edge
|
||||||
|
if (leftBottom.Y < leftTop.Y)
|
||||||
|
{
|
||||||
|
double v = radii._topLeft / (radii._topLeft + radii._bottomLeft) * rect.Height;
|
||||||
|
leftBottom = leftBottom.WithY(v);
|
||||||
|
leftTop = leftTop.WithY(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 line
|
||||||
|
ctx.LineTo(topRight, true);
|
||||||
|
|
||||||
|
// Upper-right corner
|
||||||
|
double radiusX = rect.TopRight.X - topRight.X;
|
||||||
|
double 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Right line
|
||||||
|
ctx.LineTo(rightBottom, true);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bottom line
|
||||||
|
ctx.LineTo(bottomLeft, true);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Left line
|
||||||
|
ctx.LineTo(leftTop, true);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.EndFigure(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly struct Radii
|
||||||
|
{
|
||||||
|
internal Radii(CornerRadius radii, Thickness borders, bool outer)
|
||||||
|
{
|
||||||
|
double left = 0.5 * borders.Left;
|
||||||
|
double top = 0.5 * borders.Top;
|
||||||
|
double right = 0.5 * borders.Right;
|
||||||
|
double 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 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;
|
||||||
|
}
|
||||||
|
}
|
@ -44,21 +44,23 @@ public class UniformSpacingPanel : Panel
|
|||||||
AvaloniaProperty.Register<UniformSpacingPanel, VerticalAlignment?>(nameof(ItemVerticalAlignment),
|
AvaloniaProperty.Register<UniformSpacingPanel, VerticalAlignment?>(nameof(ItemVerticalAlignment),
|
||||||
defaultValue: VerticalAlignment.Stretch);
|
defaultValue: VerticalAlignment.Stretch);
|
||||||
|
|
||||||
|
private Orientation _orientation;
|
||||||
|
|
||||||
static UniformSpacingPanel()
|
static UniformSpacingPanel()
|
||||||
{
|
{
|
||||||
AffectsMeasure<StackPanel>(OrientationProperty);
|
AffectsMeasure<StackPanel>(
|
||||||
AffectsMeasure<StackPanel>(ChildWrappingProperty);
|
OrientationProperty,
|
||||||
AffectsMeasure<StackPanel>(SpacingProperty);
|
ChildWrappingProperty,
|
||||||
AffectsMeasure<StackPanel>(HorizontalSpacingProperty);
|
SpacingProperty,
|
||||||
AffectsMeasure<StackPanel>(VerticalSpacingProperty);
|
HorizontalSpacingProperty,
|
||||||
AffectsMeasure<StackPanel>(ItemWidthProperty);
|
VerticalSpacingProperty,
|
||||||
AffectsMeasure<StackPanel>(ItemHeightProperty);
|
ItemWidthProperty,
|
||||||
AffectsMeasure<StackPanel>(ItemHorizontalAlignmentProperty);
|
ItemHeightProperty,
|
||||||
AffectsMeasure<StackPanel>(ItemVerticalAlignmentProperty);
|
ItemHorizontalAlignmentProperty,
|
||||||
|
ItemVerticalAlignmentProperty
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static double CoerceLength(AvaloniaObject _, double length) => length < 0 ? 0 : length;
|
|
||||||
|
|
||||||
public Orientation Orientation
|
public Orientation Orientation
|
||||||
{
|
{
|
||||||
get => GetValue(OrientationProperty);
|
get => GetValue(OrientationProperty);
|
||||||
@ -113,12 +115,23 @@ public class UniformSpacingPanel : Panel
|
|||||||
set => SetValue(ItemVerticalAlignmentProperty, value);
|
set => SetValue(ItemVerticalAlignmentProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static double CoerceLength(AvaloniaObject _, double length) => length < 0 ? 0 : length;
|
||||||
|
|
||||||
|
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
|
||||||
|
{
|
||||||
|
base.OnPropertyChanged(change);
|
||||||
|
|
||||||
|
if (string.Equals(change.Property.Name, nameof(Orientation)))
|
||||||
|
{
|
||||||
|
_orientation = change.GetNewValue<Orientation>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected override Size MeasureOverride(Size availableSize)
|
protected override Size MeasureOverride(Size availableSize)
|
||||||
{
|
{
|
||||||
var orientation = Orientation;
|
var curLineSize = new PanelUvSize(_orientation);
|
||||||
var curLineSize = new PanelUvSize(orientation);
|
var panelSize = new PanelUvSize(_orientation);
|
||||||
var panelSize = new PanelUvSize(orientation);
|
var uvConstraint = new PanelUvSize(_orientation, availableSize);
|
||||||
var uvConstraint = new PanelUvSize(orientation, availableSize);
|
|
||||||
double itemWidth = ItemWidth;
|
double itemWidth = ItemWidth;
|
||||||
double itemHeight = ItemHeight;
|
double itemHeight = ItemHeight;
|
||||||
bool itemWidthSet = !double.IsNaN(itemWidth);
|
bool itemWidthSet = !double.IsNaN(itemWidth);
|
||||||
@ -138,9 +151,9 @@ public class UniformSpacingPanel : Panel
|
|||||||
|
|
||||||
if (childWrapping == VisualWrapping.NoWrap)
|
if (childWrapping == VisualWrapping.NoWrap)
|
||||||
{
|
{
|
||||||
var layoutSlotSize = new PanelUvSize(orientation, availableSize);
|
var layoutSlotSize = new PanelUvSize(_orientation, availableSize);
|
||||||
|
|
||||||
if (orientation == Orientation.Horizontal)
|
if (_orientation == Orientation.Horizontal)
|
||||||
{
|
{
|
||||||
layoutSlotSize.V = double.PositiveInfinity;
|
layoutSlotSize.V = double.PositiveInfinity;
|
||||||
}
|
}
|
||||||
@ -166,7 +179,7 @@ public class UniformSpacingPanel : Panel
|
|||||||
child.Measure(new Size(layoutSlotSize.Width, layoutSlotSize.Height));
|
child.Measure(new Size(layoutSlotSize.Width, layoutSlotSize.Height));
|
||||||
|
|
||||||
var sz = new PanelUvSize(
|
var sz = new PanelUvSize(
|
||||||
orientation,
|
_orientation,
|
||||||
itemWidthSet ? itemWidth : child.DesiredSize.Width,
|
itemWidthSet ? itemWidth : child.DesiredSize.Width,
|
||||||
itemHeightSet ? itemHeight : child.DesiredSize.Height);
|
itemHeightSet ? itemHeight : child.DesiredSize.Height);
|
||||||
|
|
||||||
@ -195,7 +208,7 @@ public class UniformSpacingPanel : Panel
|
|||||||
child.Measure(childConstraint);
|
child.Measure(childConstraint);
|
||||||
|
|
||||||
var sz = new PanelUvSize(
|
var sz = new PanelUvSize(
|
||||||
orientation,
|
_orientation,
|
||||||
itemWidthSet ? itemWidth : child.DesiredSize.Width,
|
itemWidthSet ? itemWidth : child.DesiredSize.Width,
|
||||||
itemHeightSet ? itemHeight : child.DesiredSize.Height);
|
itemHeightSet ? itemHeight : child.DesiredSize.Height);
|
||||||
|
|
||||||
@ -223,17 +236,16 @@ public class UniformSpacingPanel : Panel
|
|||||||
|
|
||||||
protected override Size ArrangeOverride(Size finalSize)
|
protected override Size ArrangeOverride(Size finalSize)
|
||||||
{
|
{
|
||||||
var orientation = Orientation;
|
|
||||||
int firstInLine = 0;
|
int firstInLine = 0;
|
||||||
double itemWidth = ItemWidth;
|
double itemWidth = ItemWidth;
|
||||||
double itemHeight = ItemHeight;
|
double itemHeight = ItemHeight;
|
||||||
double accumulatedV = 0;
|
double accumulatedV = 0;
|
||||||
double itemU = orientation == Orientation.Horizontal ? itemWidth : itemHeight;
|
double itemU = _orientation == Orientation.Horizontal ? itemWidth : itemHeight;
|
||||||
var curLineSize = new PanelUvSize(orientation);
|
var curLineSize = new PanelUvSize(_orientation);
|
||||||
var uvFinalSize = new PanelUvSize(orientation, finalSize);
|
var uvFinalSize = new PanelUvSize(_orientation, finalSize);
|
||||||
bool itemWidthSet = !double.IsNaN(itemWidth);
|
bool itemWidthSet = !double.IsNaN(itemWidth);
|
||||||
bool itemHeightSet = !double.IsNaN(itemHeight);
|
bool itemHeightSet = !double.IsNaN(itemHeight);
|
||||||
bool useItemU = orientation == Orientation.Horizontal ? itemWidthSet : itemHeightSet;
|
bool useItemU = _orientation == Orientation.Horizontal ? itemWidthSet : itemHeightSet;
|
||||||
var childWrapping = ChildWrapping;
|
var childWrapping = ChildWrapping;
|
||||||
var spacingSize = GetSpacingSize();
|
var spacingSize = GetSpacingSize();
|
||||||
|
|
||||||
@ -250,14 +262,13 @@ public class UniformSpacingPanel : Panel
|
|||||||
var child = Children[i];
|
var child = Children[i];
|
||||||
|
|
||||||
var sz = new PanelUvSize(
|
var sz = new PanelUvSize(
|
||||||
orientation,
|
_orientation,
|
||||||
itemWidthSet ? itemWidth : child.DesiredSize.Width,
|
itemWidthSet ? itemWidth : child.DesiredSize.Width,
|
||||||
itemHeightSet ? itemHeight : child.DesiredSize.Height);
|
itemHeightSet ? itemHeight : child.DesiredSize.Height);
|
||||||
|
|
||||||
if (!isFirst && MathHelper.GreaterThan(curLineSize.U + sz.U + spacingSize.U, uvFinalSize.U))
|
if (!isFirst && MathHelper.GreaterThan(curLineSize.U + sz.U + spacingSize.U, uvFinalSize.U))
|
||||||
{
|
{
|
||||||
ArrangeWrapLine(orientation, accumulatedV, curLineSize.V, firstInLine, i, useItemU, itemU,
|
ArrangeWrapLine(accumulatedV, curLineSize.V, firstInLine, i, useItemU, itemU, spacingSize.U);
|
||||||
spacingSize.U);
|
|
||||||
|
|
||||||
accumulatedV += curLineSize.V + spacingSize.V;
|
accumulatedV += curLineSize.V + spacingSize.V;
|
||||||
curLineSize = sz;
|
curLineSize = sz;
|
||||||
@ -275,7 +286,7 @@ public class UniformSpacingPanel : Panel
|
|||||||
|
|
||||||
if (firstInLine < Children.Count)
|
if (firstInLine < Children.Count)
|
||||||
{
|
{
|
||||||
ArrangeWrapLine(orientation, accumulatedV, curLineSize.V, firstInLine, Children.Count, useItemU, itemU,
|
ArrangeWrapLine(accumulatedV, curLineSize.V, firstInLine, Children.Count, useItemU, itemU,
|
||||||
spacingSize.U);
|
spacingSize.U);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -331,7 +342,6 @@ public class UniformSpacingPanel : Panel
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void ArrangeWrapLine(
|
private void ArrangeWrapLine(
|
||||||
Orientation orientation,
|
|
||||||
double v,
|
double v,
|
||||||
double lineV,
|
double lineV,
|
||||||
int start,
|
int start,
|
||||||
@ -341,13 +351,13 @@ public class UniformSpacingPanel : Panel
|
|||||||
double spacing)
|
double spacing)
|
||||||
{
|
{
|
||||||
double u = 0;
|
double u = 0;
|
||||||
bool isHorizontal = orientation == Orientation.Horizontal;
|
bool isHorizontal = _orientation == Orientation.Horizontal;
|
||||||
|
|
||||||
for (int i = start; i < end; i++)
|
for (int i = start; i < end; i++)
|
||||||
{
|
{
|
||||||
var child = Children[i];
|
var child = Children[i];
|
||||||
|
|
||||||
var childSize = new PanelUvSize(orientation, child.DesiredSize);
|
var childSize = new PanelUvSize(_orientation, child.DesiredSize);
|
||||||
double layoutSlotU = useItemU ? itemU : childSize.U;
|
double layoutSlotU = useItemU ? itemU : childSize.U;
|
||||||
|
|
||||||
child.Arrange(isHorizontal ? new Rect(u, v, layoutSlotU, lineV) : new Rect(v, u, lineV, layoutSlotU));
|
child.Arrange(isHorizontal ? new Rect(u, v, layoutSlotU, lineV) : new Rect(v, u, lineV, layoutSlotU));
|
||||||
|
@ -0,0 +1,22 @@
|
|||||||
|
using Avalonia;
|
||||||
|
|
||||||
|
namespace HandyControl.Tools.Extension;
|
||||||
|
|
||||||
|
internal static class ValueExtension
|
||||||
|
{
|
||||||
|
public static Thickness Add(this Thickness a, Thickness b) =>
|
||||||
|
new(a.Left + b.Left, a.Top + b.Top, a.Right + b.Right, a.Bottom + b.Bottom);
|
||||||
|
|
||||||
|
public static bool IsZero(this Thickness thickness) =>
|
||||||
|
MathHelper.IsZero(thickness.Left)
|
||||||
|
&& MathHelper.IsZero(thickness.Top)
|
||||||
|
&& MathHelper.IsZero(thickness.Right)
|
||||||
|
&& MathHelper.IsZero(thickness.Bottom);
|
||||||
|
|
||||||
|
public static bool IsUniform(this Thickness thickness) =>
|
||||||
|
MathHelper.AreClose(thickness.Left, thickness.Top)
|
||||||
|
&& MathHelper.AreClose(thickness.Left, thickness.Right)
|
||||||
|
&& MathHelper.AreClose(thickness.Left, thickness.Bottom);
|
||||||
|
|
||||||
|
public static bool IsNaN(this double value) => double.IsNaN(value);
|
||||||
|
}
|
@ -6,6 +6,8 @@ internal static class MathHelper
|
|||||||
{
|
{
|
||||||
public const double Epsilon = 2.2204460492503131e-016;
|
public const double Epsilon = 2.2204460492503131e-016;
|
||||||
|
|
||||||
|
public static bool IsZero(double value) => Math.Abs(value) < 10.0 * Epsilon;
|
||||||
|
|
||||||
public static bool AreClose(double value1, double value2) =>
|
public static bool AreClose(double value1, double value2) =>
|
||||||
// ReSharper disable once CompareOfFloatsByEqualityOperator
|
// ReSharper disable once CompareOfFloatsByEqualityOperator
|
||||||
value1 == value2 || IsVerySmall(value1 - value2);
|
value1 == value2 || IsVerySmall(value1 - value2);
|
||||||
|
@ -359,11 +359,6 @@ public class DashedBorder : Decorator
|
|||||||
using (var ctx = borderGeometry.Open())
|
using (var ctx = borderGeometry.Open())
|
||||||
{
|
{
|
||||||
GenerateGeometry(ctx, boundRect, outerRadii);
|
GenerateGeometry(ctx, boundRect, outerRadii);
|
||||||
|
|
||||||
if (backgroundGeometry != null)
|
|
||||||
{
|
|
||||||
GenerateGeometry(ctx, innerRect, innerRadii);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
borderGeometry.Freeze();
|
borderGeometry.Freeze();
|
||||||
|
Loading…
Reference in New Issue
Block a user