add DashBorder

This commit is contained in:
NaBian committed 2020-06-21 23:21:08 +08:00
1 parent 096fa70ecb
commit f367263379
2 files changed
+671

No files matched your search

@@ -0,0 +1,670 @@
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using HandyControl.Data;
using HandyControl.Expression.Drawing;
using HandyControl.Tools;
using HandyControl.Tools.Extension;
namespace HandyControl.Controls
{
public class DashBorder : Decorator
{
private bool _useComplexRenderCodePath;
private Pen GeometryPenCache { get; set; }
private Pen LeftPenCache { get; set; }
private Pen RightPenCache { get; set; }
private Pen TopPenCache { get; set; }
private Pen BottomPenCache { get; set; }
private StreamGeometry BackgroundGeometryCache { get; set; }
private StreamGeometry BorderGeometryCache { get; set; }
private static void OnClearPenCache(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var border = (DashBorder)d;
border.LeftPenCache = null;
border.RightPenCache = null;
border.TopPenCache = null;
border.BottomPenCache = null;
border.GeometryPenCache = null;
}
public static readonly DependencyProperty BorderThicknessProperty = DependencyProperty.Register(
"BorderThickness", typeof(Thickness), typeof(DashBorder), new FrameworkPropertyMetadata(default(Thickness),
FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender,
OnClearPenCache));
public Thickness BorderThickness
{
get => (Thickness) GetValue(BorderThicknessProperty);
set => SetValue(BorderThicknessProperty, value);
}
public static readonly DependencyProperty BorderDashThicknessProperty = DependencyProperty.Register(
"BorderDashThickness", typeof(double), typeof(DashBorder), new FrameworkPropertyMetadata(ValueBoxes.Double0Box,
FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender,
OnClearPenCache));
public double BorderDashThickness
{
get => (double) GetValue(BorderDashThicknessProperty);
set => SetValue(BorderDashThicknessProperty, value);
}
public static readonly DependencyProperty PaddingProperty = DependencyProperty.Register(
"Padding", typeof(Thickness), typeof(DashBorder), new FrameworkPropertyMetadata(default(Thickness),
FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender));
public Thickness Padding
{
get => (Thickness) GetValue(PaddingProperty);
set => SetValue(PaddingProperty, value);
}
public static readonly DependencyProperty CornerRadiusProperty = DependencyProperty.Register(
"CornerRadius", typeof(CornerRadius), typeof(DashBorder), new FrameworkPropertyMetadata(default(CornerRadius),
FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender));
public CornerRadius CornerRadius
{
get => (CornerRadius) GetValue(CornerRadiusProperty);
set => SetValue(CornerRadiusProperty, value);
}
public static readonly DependencyProperty BorderBrushProperty = DependencyProperty.Register(
"BorderBrush", typeof(Brush), typeof(DashBorder), new FrameworkPropertyMetadata(default(Brush),
FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.SubPropertiesDoNotAffectRender,
OnClearPenCache));
public Brush BorderBrush
{
get => (Brush) GetValue(BorderBrushProperty);
set => SetValue(BorderBrushProperty, value);
}
public static readonly DependencyProperty BackgroundProperty = DependencyProperty.Register(
"Background", typeof(Brush), typeof(DashBorder), new FrameworkPropertyMetadata(default(Brush),
FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.SubPropertiesDoNotAffectRender,
OnClearPenCache));
public Brush Background
{
get => (Brush) GetValue(BackgroundProperty);
set => SetValue(BackgroundProperty, value);
}
public static readonly DependencyProperty BorderDashArrayProperty = DependencyProperty.Register(
"BorderDashArray", typeof(DoubleCollection), typeof(DashBorder), new FrameworkPropertyMetadata(default(DoubleCollection),
FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender,
OnClearPenCache));
public DoubleCollection BorderDashArray
{
get => (DoubleCollection) GetValue(BorderDashArrayProperty);
set => SetValue(BorderDashArrayProperty, value);
}
public static readonly DependencyProperty BorderDashCapProperty = DependencyProperty.Register(
"BorderDashCap", typeof(PenLineCap), typeof(DashBorder), new FrameworkPropertyMetadata(default(PenLineCap),
FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender,
OnClearPenCache));
public PenLineCap BorderDashCap
{
get => (PenLineCap) GetValue(BorderDashCapProperty);
set => SetValue(BorderDashCapProperty, value);
}
public static readonly DependencyProperty BorderDashOffsetProperty = DependencyProperty.Register(
"BorderDashOffset", typeof(double), typeof(DashBorder), new FrameworkPropertyMetadata(ValueBoxes.Double0Box,
FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender,
OnClearPenCache));
public double BorderDashOffset
{
get => (double) GetValue(BorderDashOffsetProperty);
set => SetValue(BorderDashOffsetProperty, value);
}
private static Size ConvertThickness2Size(Thickness th) => new Size(th.Left + th.Right, th.Top + th.Bottom);
private static Rect DeflateRect(Rect rt, Thickness thick) => new Rect(rt.Left + thick.Left,
rt.Top + thick.Top,
Math.Max(0.0, rt.Width - thick.Left - thick.Right),
Math.Max(0.0, rt.Height - thick.Top - thick.Bottom));
private static bool AreUniformCorners(CornerRadius borderRadii)
{
var 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, in 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)
{
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
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, true);
// Top line
ctx.LineTo(topRight, true, false);
// 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, false);
// 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, false);
// 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, false);
// 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);
}
}
protected override Size MeasureOverride(Size constraint)
{
var child = Child;
var borderThickness = BorderThickness;
var padding = Padding;
if (UseLayoutRounding)
{
var dpiScaleX = DpiHelper.DeviceDpiX;
var dpiScaleY = DpiHelper.DeviceDpiY;
borderThickness = new Thickness(
DpiHelper.RoundLayoutValue(borderThickness.Left, dpiScaleX),
DpiHelper.RoundLayoutValue(borderThickness.Top, dpiScaleY),
DpiHelper.RoundLayoutValue(borderThickness.Right, dpiScaleX),
DpiHelper.RoundLayoutValue(borderThickness.Bottom, dpiScaleY));
}
var borderSize = ConvertThickness2Size(borderThickness);
var paddingSize = ConvertThickness2Size(padding);
var mySize = new Size();
if (child != null)
{
var combined = new Size(borderSize.Width + paddingSize.Width, borderSize.Height + paddingSize.Height);
var childConstraint = new Size(Math.Max(0.0, constraint.Width - combined.Width), Math.Max(0.0, constraint.Height - combined.Height));
child.Measure(childConstraint);
var childSize = child.DesiredSize;
mySize.Width = childSize.Width + combined.Width;
mySize.Height = childSize.Height + combined.Height;
}
else
{
mySize = new Size(borderSize.Width + paddingSize.Width, borderSize.Height + paddingSize.Height);
}
return mySize;
}
protected override Size ArrangeOverride(Size arrangeSize)
{
var borderThickness = BorderThickness;
if (UseLayoutRounding)
{
var dpiScaleX = DpiHelper.DeviceDpiX;
var dpiScaleY = DpiHelper.DeviceDpiY;
borderThickness = new Thickness(
DpiHelper.RoundLayoutValue(borderThickness.Left, dpiScaleX),
DpiHelper.RoundLayoutValue(borderThickness.Top, dpiScaleY),
DpiHelper.RoundLayoutValue(borderThickness.Right, dpiScaleX),
DpiHelper.RoundLayoutValue(borderThickness.Bottom, dpiScaleY));
}
var boundRect = new Rect(arrangeSize);
var innerRect = DeflateRect(boundRect, borderThickness);
var child = Child;
if (child != null)
{
var padding = Padding;
var childRect = DeflateRect(innerRect, padding);
child.Arrange(childRect);
}
var radii = CornerRadius;
var borderBrush = BorderBrush;
var uniformCorners = AreUniformCorners(radii);
_useComplexRenderCodePath = !uniformCorners;
if (!_useComplexRenderCodePath && borderBrush != null)
{
_useComplexRenderCodePath = !MathHelper.IsZero(radii.TopLeft) && !borderThickness.IsUniform();
}
if (_useComplexRenderCodePath)
{
var innerRadii = new Radii(radii, borderThickness, false);
StreamGeometry backgroundGeometry = null;
if (!MathHelper.IsZero(innerRect.Width) && !MathHelper.IsZero(innerRect.Height))
{
backgroundGeometry = new StreamGeometry();
using (var ctx = backgroundGeometry.Open())
{
GenerateGeometry(ctx, innerRect, innerRadii);
}
backgroundGeometry.Freeze();
BackgroundGeometryCache = backgroundGeometry;
}
else
{
BackgroundGeometryCache = null;
}
if (!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);
if (backgroundGeometry != null)
{
GenerateGeometry(ctx, innerRect, innerRadii);
}
}
borderGeometry.Freeze();
BorderGeometryCache = borderGeometry;
}
else
{
BorderGeometryCache = null;
}
}
else
{
BackgroundGeometryCache = null;
BorderGeometryCache = null;
}
return arrangeSize;
}
protected override void OnRender(DrawingContext drawingContext)
{
var background = Background;
var borderBrush = BorderBrush;
var useLayoutRounding = UseLayoutRounding;
if (_useComplexRenderCodePath)
{
var borderGeometry = BorderGeometryCache;
if (borderGeometry != null && borderBrush != null)
{
var pen = GeometryPenCache;
if (pen == null)
{
pen = new Pen
{
Brush = borderBrush,
Thickness = BorderDashThickness,
DashCap = BorderDashCap,
DashStyle = new DashStyle(BorderDashArray, BorderDashOffset)
};
if (borderBrush.IsFrozen)
{
pen.Freeze();
}
GeometryPenCache = pen;
}
drawingContext.DrawGeometry(null, pen, borderGeometry);
}
var backgroundGeometry = BackgroundGeometryCache;
if (backgroundGeometry != null && background != null)
{
drawingContext.DrawGeometry(background, null, backgroundGeometry);
}
}
else
{
var dpiScaleX = DpiHelper.DeviceDpiX;
var dpiScaleY = DpiHelper.DeviceDpiY;
var borderThickness = BorderThickness;
var cornerRadius = CornerRadius;
var outerCornerRadius = cornerRadius.TopLeft;
var roundedCorners = !MathHelper.IsZero(outerCornerRadius);
if (!borderThickness.IsZero() && borderBrush != null)
{
var pen = LeftPenCache;
if (pen == null)
{
pen = new Pen
{
Brush = borderBrush,
Thickness = useLayoutRounding
? DpiHelper.RoundLayoutValue(borderThickness.Left, dpiScaleX)
: borderThickness.Left,
DashCap = BorderDashCap,
DashStyle = new DashStyle(BorderDashArray, BorderDashOffset)
};
if (borderBrush.IsFrozen)
{
pen.Freeze();
}
LeftPenCache = pen;
}
double halfThickness;
var renderSize = RenderSize;
if (borderThickness.IsUniform())
{
halfThickness = pen.Thickness * 0.5;
var rect = new Rect(new Point(halfThickness, halfThickness), new Point(renderSize.Width - halfThickness, renderSize.Height - halfThickness));
if (roundedCorners)
{
drawingContext.DrawRoundedRectangle(null, pen, rect, outerCornerRadius, outerCornerRadius);
}
else
{
drawingContext.DrawRectangle(null, pen, rect);
}
}
else
{
if (MathHelper.GreaterThan(borderThickness.Left, 0))
{
halfThickness = pen.Thickness * 0.5;
drawingContext.DrawLine(pen, new Point(halfThickness, 0), new Point(halfThickness, renderSize.Height));
}
if (MathHelper.GreaterThan(borderThickness.Right, 0))
{
pen = RightPenCache;
if (pen == null)
{
pen = new Pen
{
Brush = borderBrush,
Thickness = useLayoutRounding
? DpiHelper.RoundLayoutValue(borderThickness.Right, dpiScaleX)
: borderThickness.Right,
DashCap = BorderDashCap,
DashStyle = new DashStyle(BorderDashArray, BorderDashOffset)
};
if (borderBrush.IsFrozen)
{
pen.Freeze();
}
RightPenCache = pen;
}
halfThickness = pen.Thickness * 0.5;
drawingContext.DrawLine(pen, new Point(renderSize.Width - halfThickness, 0), new Point(renderSize.Width - halfThickness, renderSize.Height));
}
if (MathHelper.GreaterThan(borderThickness.Top, 0))
{
pen = TopPenCache;
if (pen == null)
{
pen = new Pen
{
Brush = borderBrush,
Thickness = useLayoutRounding
? DpiHelper.RoundLayoutValue(borderThickness.Top, dpiScaleY)
: borderThickness.Top,
DashCap = BorderDashCap,
DashStyle = new DashStyle(BorderDashArray, BorderDashOffset)
};
if (borderBrush.IsFrozen)
{
pen.Freeze();
}
TopPenCache = pen;
}
halfThickness = pen.Thickness * 0.5;
drawingContext.DrawLine(pen, new Point(0, halfThickness), new Point(renderSize.Width, halfThickness));
}
if (MathHelper.GreaterThan(borderThickness.Bottom, 0))
{
pen = BottomPenCache;
if (pen == null)
{
pen = new Pen
{
Brush = borderBrush,
Thickness = useLayoutRounding
? DpiHelper.RoundLayoutValue(borderThickness.Bottom, dpiScaleY)
: borderThickness.Bottom,
DashCap = BorderDashCap,
DashStyle = new DashStyle(BorderDashArray, BorderDashOffset)
};
if (borderBrush.IsFrozen)
{
pen.Freeze();
}
BottomPenCache = pen;
}
halfThickness = pen.Thickness * 0.5;
drawingContext.DrawLine(pen, new Point(0, renderSize.Height - halfThickness), new Point(renderSize.Width, renderSize.Height - halfThickness));
}
}
}
if (background != null)
{
Point ptTL, ptBR;
if (useLayoutRounding)
{
ptTL = new Point(DpiHelper.RoundLayoutValue(borderThickness.Left, dpiScaleX), DpiHelper.RoundLayoutValue(borderThickness.Top, dpiScaleY));
ptBR = new Point(RenderSize.Width - DpiHelper.RoundLayoutValue(borderThickness.Right, dpiScaleX),
RenderSize.Height - DpiHelper.RoundLayoutValue(borderThickness.Bottom, dpiScaleY));
}
else
{
ptTL = new Point(borderThickness.Left, borderThickness.Top);
ptBR = new Point(RenderSize.Width - borderThickness.Right, RenderSize.Height - borderThickness.Bottom);
}
if (ptBR.X > ptTL.X && ptBR.Y > ptTL.Y)
{
if (roundedCorners)
{
var innerRadii = new Radii(cornerRadius, borderThickness, false);
var innerCornerRadius = innerRadii.TopLeft;
drawingContext.DrawRoundedRectangle(background, null, new Rect(ptTL, ptBR), innerCornerRadius, innerCornerRadius);
}
else
{
drawingContext.DrawRectangle(background, null, new Rect(ptTL, ptBR));
}
}
}
}
}
private readonly 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 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;
}
}
}
@@ -33,6 +33,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Controls\Image\ImageBlock.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Controls\Input\ImageSelector.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Controls\Input\PinBox.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Controls\Other\DashBorder.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Controls\Other\Divider.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Controls\Other\Drawer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Controls\Other\Magnifier.cs" />