123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669 |
- 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 DashedBorder : 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 = (DashedBorder) d;
- border.LeftPenCache = null;
- border.RightPenCache = null;
- border.TopPenCache = null;
- border.BottomPenCache = null;
- border.GeometryPenCache = null;
- }
- public static readonly DependencyProperty BorderThicknessProperty = DependencyProperty.Register(
- nameof(BorderThickness), typeof(Thickness), typeof(DashedBorder), 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(
- nameof(BorderDashThickness), typeof(double), typeof(DashedBorder), 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(
- nameof(Padding), typeof(Thickness), typeof(DashedBorder), 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(
- nameof(CornerRadius), typeof(CornerRadius), typeof(DashedBorder), 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(
- nameof(BorderBrush), typeof(Brush), typeof(DashedBorder), 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(
- nameof(Background), typeof(Brush), typeof(DashedBorder), 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(
- nameof(BorderDashArray), typeof(DoubleCollection), typeof(DashedBorder), 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(
- nameof(BorderDashCap), typeof(PenLineCap), typeof(DashedBorder), 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(
- nameof(BorderDashOffset), typeof(double), typeof(DashedBorder), 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(th.Left + th.Right, th.Top + th.Bottom);
- private static Rect DeflateRect(Rect rt, Thickness thick) => new(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;
- }
- }
|