From 4aa4afd292ad699b62d4971f46e69cb6d5ba12f9 Mon Sep 17 00:00:00 2001
From: NaBian <836904362@qq.com>
Date: Thu, 1 Nov 2018 17:45:45 +0800
Subject: [PATCH] add CircleProgressBar
thanks to @ghost1372 suggestion
---
HandyControl/Controls/CircleProgressBar.cs | 81 ++
HandyControl/Controls/Growl.cs | 12 +-
.../SliderCanvas.cs => InternalCanvas.cs} | 8 +-
.../Drawing/BezierCurveFlattener.cs | 357 +++++++++
.../Expression/Drawing/CommonExtensions.cs | 115 +++
.../Drawing/ExceptionStringTable.cs | 39 +
.../Expression/Drawing/GeometryHelper.cs | 221 +++++
.../Expression/Drawing/MarchLocation.cs | 50 ++
.../Expression/Drawing/MarchStopReason.cs | 9 +
HandyControl/Expression/Drawing/MathHelper.cs | 65 ++
.../Expression/Drawing/PathFigureHelper.cs | 115 +++
.../Expression/Drawing/PathGeometryHelper.cs | 25 +
.../Expression/Drawing/PathSegmentData.cs | 17 +
.../Expression/Drawing/PathSegmentHelper.cs | 755 ++++++++++++++++++
.../Expression/Drawing/PolylineData.cs | 152 ++++
.../Expression/Drawing/PolylineHelper.cs | 116 +++
.../Expression/Drawing/RandomEngine.cs | 45 ++
.../Expression/Drawing/SimpleSegment.cs | 76 ++
.../Expression/Media/ArcGeometrySource.cs | 470 +++++++++++
.../Media/DrawingPropertyChangedEventArgs.cs | 11 +
.../Media/DrawingPropertyMetadata.cs | 77 ++
.../Media/DrawingPropertyMetadataOptions.cs | 12 +
.../Expression/Media/GeometryEffect.cs | 172 ++++
.../Media/GeometryEffectConverter.cs | 59 ++
.../Expression/Media/GeometrySource!1.cs | 88 ++
.../Media/IArcGeometrySourceParameters.cs | 13 +
.../Expression/Media/IGeometrySource.cs | 17 +
.../Media/IGeometrySourceExtensions.cs | 31 +
.../Media/IGeometrySourceParameters.cs | 13 +
HandyControl/Expression/Media/IShape.cs | 25 +
.../Media/InvalidateGeometryReasons.cs | 14 +
.../Expression/Media/SketchGeometryEffect.cs | 135 ++++
HandyControl/Expression/Media/UnitType.cs | 8 +
HandyControl/Expression/Shapes/Arc.cs | 64 ++
.../Expression/Shapes/PrimitiveShape.cs | 79 ++
HandyControl/HandyControl.csproj | 35 +-
.../Styles/Base/ProgressBarBaseStyle.xaml | 55 +-
HandyControl/Themes/Styles/ProgressBar.xaml | 23 +-
HandyControl/Themes/Styles/Slider.xaml | 16 +-
HandyControlDemo/Data/MessageToken.cs | 2 +
HandyControlDemo/Data/MessageToken.tt | 3 +-
HandyControlDemo/HandyControlDemo.csproj | 8 +
.../Properties/Langs/Lang.Designer.cs | 9 +
.../Properties/Langs/Lang.en.resx | 3 +
HandyControlDemo/Properties/Langs/Lang.resx | 3 +
.../Img/LeftMainContent/ColorWheel_16x.png | Bin 0 -> 553 bytes
.../Controls/CircleProgressBarDemoCtl.xaml | 16 +
.../Controls/CircleProgressBarDemoCtl.xaml.cs | 11 +
.../UserControl/Main/LeftMainContent.xaml | 7 +-
49 files changed, 3697 insertions(+), 40 deletions(-)
create mode 100644 HandyControl/Controls/CircleProgressBar.cs
rename HandyControl/Controls/{Slider/SliderCanvas.cs => InternalCanvas.cs} (89%)
create mode 100644 HandyControl/Expression/Drawing/BezierCurveFlattener.cs
create mode 100644 HandyControl/Expression/Drawing/CommonExtensions.cs
create mode 100644 HandyControl/Expression/Drawing/ExceptionStringTable.cs
create mode 100644 HandyControl/Expression/Drawing/GeometryHelper.cs
create mode 100644 HandyControl/Expression/Drawing/MarchLocation.cs
create mode 100644 HandyControl/Expression/Drawing/MarchStopReason.cs
create mode 100644 HandyControl/Expression/Drawing/MathHelper.cs
create mode 100644 HandyControl/Expression/Drawing/PathFigureHelper.cs
create mode 100644 HandyControl/Expression/Drawing/PathGeometryHelper.cs
create mode 100644 HandyControl/Expression/Drawing/PathSegmentData.cs
create mode 100644 HandyControl/Expression/Drawing/PathSegmentHelper.cs
create mode 100644 HandyControl/Expression/Drawing/PolylineData.cs
create mode 100644 HandyControl/Expression/Drawing/PolylineHelper.cs
create mode 100644 HandyControl/Expression/Drawing/RandomEngine.cs
create mode 100644 HandyControl/Expression/Drawing/SimpleSegment.cs
create mode 100644 HandyControl/Expression/Media/ArcGeometrySource.cs
create mode 100644 HandyControl/Expression/Media/DrawingPropertyChangedEventArgs.cs
create mode 100644 HandyControl/Expression/Media/DrawingPropertyMetadata.cs
create mode 100644 HandyControl/Expression/Media/DrawingPropertyMetadataOptions.cs
create mode 100644 HandyControl/Expression/Media/GeometryEffect.cs
create mode 100644 HandyControl/Expression/Media/GeometryEffectConverter.cs
create mode 100644 HandyControl/Expression/Media/GeometrySource!1.cs
create mode 100644 HandyControl/Expression/Media/IArcGeometrySourceParameters.cs
create mode 100644 HandyControl/Expression/Media/IGeometrySource.cs
create mode 100644 HandyControl/Expression/Media/IGeometrySourceExtensions.cs
create mode 100644 HandyControl/Expression/Media/IGeometrySourceParameters.cs
create mode 100644 HandyControl/Expression/Media/IShape.cs
create mode 100644 HandyControl/Expression/Media/InvalidateGeometryReasons.cs
create mode 100644 HandyControl/Expression/Media/SketchGeometryEffect.cs
create mode 100644 HandyControl/Expression/Media/UnitType.cs
create mode 100644 HandyControl/Expression/Shapes/Arc.cs
create mode 100644 HandyControl/Expression/Shapes/PrimitiveShape.cs
create mode 100644 HandyControlDemo/Resources/Img/LeftMainContent/ColorWheel_16x.png
create mode 100644 HandyControlDemo/UserControl/Controls/CircleProgressBarDemoCtl.xaml
create mode 100644 HandyControlDemo/UserControl/Controls/CircleProgressBarDemoCtl.xaml.cs
diff --git a/HandyControl/Controls/CircleProgressBar.cs b/HandyControl/Controls/CircleProgressBar.cs
new file mode 100644
index 00000000..4f4fa5d0
--- /dev/null
+++ b/HandyControl/Controls/CircleProgressBar.cs
@@ -0,0 +1,81 @@
+using System.Windows;
+using System.Windows.Controls.Primitives;
+using HandyControl.Data;
+using HandyControl.Expression.Shapes;
+
+namespace HandyControl.Controls
+{
+ [TemplatePart(Name = IndicatorTemplateName, Type = typeof(Arc))]
+ public class CircleProgressBar : RangeBase
+ {
+ private const string IndicatorTemplateName = "PART_Indicator";
+
+ public static readonly DependencyProperty ArcThicknessProperty = DependencyProperty.Register(
+ "ArcThickness", typeof(double), typeof(CircleProgressBar), new PropertyMetadata(default(double)));
+
+ public static readonly DependencyProperty ShowTextProperty = DependencyProperty.Register(
+ "ShowText", typeof(bool), typeof(CircleProgressBar), new PropertyMetadata(true));
+
+ private Arc _indicator;
+
+ static CircleProgressBar()
+ {
+ FocusableProperty.OverrideMetadata(typeof(CircleProgressBar),
+ new FrameworkPropertyMetadata(BooleanBoxes.FalseBox));
+ MaximumProperty.OverrideMetadata(typeof(CircleProgressBar), new FrameworkPropertyMetadata(100.0));
+ }
+
+ public bool ShowText
+ {
+ get => (bool) GetValue(ShowTextProperty);
+ set => SetValue(ShowTextProperty, value);
+ }
+
+ public double ArcThickness
+ {
+ get => (double) GetValue(ArcThicknessProperty);
+ set => SetValue(ArcThicknessProperty, value);
+ }
+
+ private void SetProgressBarIndicatorAngle()
+ {
+ if (_indicator == null) return;
+ var minimum = Minimum;
+ var maximum = Maximum;
+ var num = Value;
+ _indicator.EndAngle = (maximum <= minimum ? 0 : (num - minimum) / (maximum - minimum)) * 360;
+ }
+
+ public override void OnApplyTemplate()
+ {
+ base.OnApplyTemplate();
+
+ _indicator = GetTemplateChild(IndicatorTemplateName) as Arc;
+ if (_indicator != null)
+ {
+ _indicator.StartAngle = 0;
+ _indicator.EndAngle = 0;
+ }
+
+ SetProgressBarIndicatorAngle();
+ }
+
+ protected override void OnMinimumChanged(double oldMinimum, double newMinimum)
+ {
+ base.OnMinimumChanged(oldMinimum, newMinimum);
+ SetProgressBarIndicatorAngle();
+ }
+
+ protected override void OnMaximumChanged(double oldMaximum, double newMaximum)
+ {
+ base.OnMaximumChanged(oldMaximum, newMaximum);
+ SetProgressBarIndicatorAngle();
+ }
+
+ protected override void OnValueChanged(double oldValue, double newValue)
+ {
+ base.OnValueChanged(oldValue, newValue);
+ SetProgressBarIndicatorAngle();
+ }
+ }
+}
\ No newline at end of file
diff --git a/HandyControl/Controls/Growl.cs b/HandyControl/Controls/Growl.cs
index b56772d7..1da287b4 100644
--- a/HandyControl/Controls/Growl.cs
+++ b/HandyControl/Controls/Growl.cs
@@ -284,7 +284,7 @@ namespace HandyControl.Controls
///
public static void Success(GrowlInfo growlInfo)
{
- if (growlInfo == null) throw new NullReferenceException("growlInfo is null");
+ if (growlInfo == null) throw new ArgumentNullException(nameof(growlInfo));
growlInfo.IconKey = ResourceToken.SuccessGeometry;
growlInfo.IconBrushKey = ResourceToken.SuccessBrush;
growlInfo.Type = InfoType.Success;
@@ -306,7 +306,7 @@ namespace HandyControl.Controls
///
public static void Info(GrowlInfo growlInfo)
{
- if (growlInfo == null) throw new NullReferenceException("growlInfo is null");
+ if (growlInfo == null) throw new ArgumentNullException(nameof(growlInfo));
growlInfo.IconKey = ResourceToken.InfoGeometry;
growlInfo.IconBrushKey = ResourceToken.InfoBrush;
growlInfo.Type = InfoType.Info;
@@ -328,7 +328,7 @@ namespace HandyControl.Controls
///
public static void Warning(GrowlInfo growlInfo)
{
- if (growlInfo == null) throw new NullReferenceException("growlInfo is null");
+ if (growlInfo == null) throw new ArgumentNullException(nameof(growlInfo));
growlInfo.IconKey = ResourceToken.WarningGeometry;
growlInfo.IconBrushKey = ResourceToken.WarningBrush;
growlInfo.Type = InfoType.Warning;
@@ -350,7 +350,7 @@ namespace HandyControl.Controls
///
public static void Error(GrowlInfo growlInfo)
{
- if (growlInfo == null) throw new NullReferenceException("growlInfo is null");
+ if (growlInfo == null) throw new ArgumentNullException(nameof(growlInfo));
growlInfo.IconKey = ResourceToken.ErrorGeometry;
growlInfo.IconBrushKey = ResourceToken.DangerBrush;
growlInfo.StaysOpen = true;
@@ -373,7 +373,7 @@ namespace HandyControl.Controls
///
public static void Fatal(GrowlInfo growlInfo)
{
- if (growlInfo == null) throw new NullReferenceException("growlInfo is null");
+ if (growlInfo == null) throw new ArgumentNullException(nameof(growlInfo));
growlInfo.IconKey = ResourceToken.FatalGeometry;
growlInfo.IconBrushKey = ResourceToken.PrimaryTextBrush;
growlInfo.StaysOpen = true;
@@ -401,7 +401,7 @@ namespace HandyControl.Controls
///
public static void Ask(GrowlInfo growlInfo)
{
- if (growlInfo == null) throw new NullReferenceException("growlInfo is null");
+ if (growlInfo == null) throw new ArgumentNullException(nameof(growlInfo));
growlInfo.IconKey = ResourceToken.AskGeometry;
growlInfo.IconBrushKey = ResourceToken.AccentBrush;
growlInfo.StaysOpen = true;
diff --git a/HandyControl/Controls/Slider/SliderCanvas.cs b/HandyControl/Controls/InternalCanvas.cs
similarity index 89%
rename from HandyControl/Controls/Slider/SliderCanvas.cs
rename to HandyControl/Controls/InternalCanvas.cs
index ee4bade3..b406724c 100644
--- a/HandyControl/Controls/Slider/SliderCanvas.cs
+++ b/HandyControl/Controls/InternalCanvas.cs
@@ -1,14 +1,12 @@
-using System;
-using System.Windows;
+using System.Windows;
using System.Windows.Controls;
-// ReSharper disable once CheckNamespace
namespace HandyControl.Controls
{
- internal class SliderCanvas : Canvas
+ internal class InternalCanvas : Canvas
{
internal static readonly DependencyProperty OrientationProperty = DependencyProperty.Register(
- "Orientation", typeof(Orientation), typeof(SliderCanvas), new PropertyMetadata(default(Orientation)));
+ "Orientation", typeof(Orientation), typeof(InternalCanvas), new PropertyMetadata(default(Orientation)));
internal Orientation Orientation
{
diff --git a/HandyControl/Expression/Drawing/BezierCurveFlattener.cs b/HandyControl/Expression/Drawing/BezierCurveFlattener.cs
new file mode 100644
index 00000000..e26ee670
--- /dev/null
+++ b/HandyControl/Expression/Drawing/BezierCurveFlattener.cs
@@ -0,0 +1,357 @@
+using System;
+using System.Collections.Generic;
+using System.Windows;
+
+namespace HandyControl.Expression.Drawing
+{
+ internal static class BezierCurveFlattener
+ {
+ public const double StandardFlatteningTolerance = 0.25;
+
+ private static void DoCubicForwardDifferencing(Point[] controlPoints, double leftParameter, double rightParameter, double inverseErrorTolerance, ICollection resultPolyline, ICollection resultParameters)
+ {
+ double num14;
+ var num = controlPoints[1].X - controlPoints[0].X;
+ var num2 = controlPoints[1].Y - controlPoints[0].Y;
+ var num3 = controlPoints[2].X - controlPoints[1].X;
+ var num4 = controlPoints[2].Y - controlPoints[1].Y;
+ var num5 = controlPoints[3].X - controlPoints[2].X;
+ var num6 = controlPoints[3].Y - controlPoints[2].Y;
+ var num7 = num3 - num;
+ var num8 = num4 - num2;
+ var num9 = num5 - num3;
+ var num10 = num6 - num4;
+ var num11 = num9 - num7;
+ var num12 = num10 - num8;
+ var vector = controlPoints[3].Subtract(controlPoints[0]);
+ var length = vector.Length;
+ if (!MathHelper.IsVerySmall(length))
+ num14 = Math.Max(0.0,
+ Math.Max(Math.Abs((num7 * vector.Y - num8 * vector.X) / length),
+ Math.Abs((num9 * vector.Y - num10 * vector.X) / length)));
+ else
+ num14 = Math.Max(0.0,
+ Math.Max(GeometryHelper.Distance(controlPoints[1], controlPoints[0]),
+ GeometryHelper.Distance(controlPoints[2], controlPoints[0])));
+ uint num15 = 0;
+ if (num14 > 0.0)
+ {
+ var d = num14 * inverseErrorTolerance;
+ num15 = d < 2147483647.0 ? Log4UnsignedInt32((uint) (d + 0.5)) : Log4Double(d);
+ }
+
+ var exp = (int) -num15;
+ var num18 = exp + exp;
+ var num19 = num18 + exp;
+ var num20 = MathHelper.DoubleFromMantissaAndExponent(3.0 * num7, num18);
+ var num21 = MathHelper.DoubleFromMantissaAndExponent(3.0 * num8, num18);
+ var num22 = MathHelper.DoubleFromMantissaAndExponent(6.0 * num11, num19);
+ var num23 = MathHelper.DoubleFromMantissaAndExponent(6.0 * num12, num19);
+ var num24 = MathHelper.DoubleFromMantissaAndExponent(3.0 * num, exp) + num20 +
+ 0.16666666666666666 * num22;
+ var num25 = MathHelper.DoubleFromMantissaAndExponent(3.0 * num2, exp) + num21 +
+ 0.16666666666666666 * num23;
+ var num26 = 2.0 * num20 + num22;
+ var num27 = 2.0 * num21 + num23;
+ var x = controlPoints[0].X;
+ var y = controlPoints[0].Y;
+ var item = new Point(0.0, 0.0);
+ var num30 = 1 << (int) num15;
+ var num31 = num30 > 0 ? (rightParameter - leftParameter) / num30 : 0.0;
+ var num32 = leftParameter;
+ for (var i = 1; i < num30; i++)
+ {
+ x += num24;
+ y += num25;
+ item.X = x;
+ item.Y = y;
+ resultPolyline.Add(item);
+ num32 += num31;
+ resultParameters?.Add(num32);
+ num24 += num26;
+ num25 += num27;
+ num26 += num22;
+ num27 += num23;
+ }
+ }
+
+ private static void DoCubicMidpointSubdivision(Point[] controlPoints, uint depth, double leftParameter, double rightParameter, double inverseErrorTolerance, ICollection resultPolyline, ICollection resultParameters)
+ {
+ Point[] pointArray = { controlPoints[0], controlPoints[1], controlPoints[2], controlPoints[3] };
+ var pointArray2 = new Point[4];
+ pointArray2[3] = pointArray[3];
+ pointArray[3] = GeometryHelper.Midpoint(pointArray[3], pointArray[2]);
+ pointArray[2] = GeometryHelper.Midpoint(pointArray[2], pointArray[1]);
+ pointArray[1] = GeometryHelper.Midpoint(pointArray[1], pointArray[0]);
+ pointArray2[2] = pointArray[3];
+ pointArray[3] = GeometryHelper.Midpoint(pointArray[3], pointArray[2]);
+ pointArray[2] = GeometryHelper.Midpoint(pointArray[2], pointArray[1]);
+ pointArray2[1] = pointArray[3];
+ pointArray[3] = GeometryHelper.Midpoint(pointArray[3], pointArray[2]);
+ pointArray2[0] = pointArray[3];
+ depth--;
+ var num = (leftParameter + rightParameter) * 0.5;
+ if (depth > 0)
+ {
+ DoCubicMidpointSubdivision(pointArray, depth, leftParameter, num, inverseErrorTolerance,
+ resultPolyline, resultParameters);
+ resultPolyline.Add(pointArray2[0]);
+ resultParameters?.Add(num);
+ DoCubicMidpointSubdivision(pointArray2, depth, num, rightParameter, inverseErrorTolerance,
+ resultPolyline, resultParameters);
+ }
+ else
+ {
+ DoCubicForwardDifferencing(pointArray, leftParameter, num, inverseErrorTolerance,
+ resultPolyline, resultParameters);
+ resultPolyline.Add(pointArray2[0]);
+ resultParameters?.Add(num);
+ DoCubicForwardDifferencing(pointArray2, num, rightParameter, inverseErrorTolerance,
+ resultPolyline, resultParameters);
+ }
+ }
+
+ private static void EnsureErrorTolerance(ref double errorTolerance)
+ {
+ if (errorTolerance <= 0.0)
+ {
+ errorTolerance = 0.25;
+ }
+ }
+
+ public static void FlattenCubic(Point[] controlPoints, double errorTolerance, ICollection resultPolyline, bool skipFirstPoint, ICollection resultParameters = null)
+ {
+ if (resultPolyline == null) throw new ArgumentNullException(nameof(resultPolyline));
+ if (controlPoints == null) throw new ArgumentNullException(nameof(controlPoints));
+ if (controlPoints.Length != 4) throw new ArgumentOutOfRangeException(nameof(controlPoints));
+ EnsureErrorTolerance(ref errorTolerance);
+ if (!skipFirstPoint)
+ {
+ resultPolyline.Add(controlPoints[0]);
+ resultParameters?.Add(0.0);
+ }
+
+ if (IsCubicChordMonotone(controlPoints, errorTolerance * errorTolerance))
+ {
+ var flattener = new AdaptiveForwardDifferencingCubicFlattener(controlPoints, errorTolerance,
+ errorTolerance, true);
+ var p = new Point();
+ var u = 0.0;
+ while (flattener.Next(ref p, ref u))
+ {
+ resultPolyline.Add(p);
+ resultParameters?.Add(u);
+ }
+ }
+ else
+ {
+ var x = controlPoints[3].X - controlPoints[2].X + controlPoints[1].X - controlPoints[0].X;
+ var y = controlPoints[3].Y - controlPoints[2].Y + controlPoints[1].Y - controlPoints[0].Y;
+ var num4 = 1.0 / errorTolerance;
+ var depth = Log8UnsignedInt32((uint) (MathHelper.Hypotenuse(x, y) * num4 + 0.5));
+ if (depth > 0) depth--;
+ if (depth > 0)
+ DoCubicMidpointSubdivision(controlPoints, depth, 0.0, 1.0, 0.75 * num4, resultPolyline,
+ resultParameters);
+ else
+ DoCubicForwardDifferencing(controlPoints, 0.0, 1.0, 0.75 * num4, resultPolyline,
+ resultParameters);
+ }
+
+ resultPolyline.Add(controlPoints[3]);
+ resultParameters?.Add(1.0);
+ }
+
+ public static void FlattenQuadratic(Point[] controlPoints, double errorTolerance, ICollection resultPolyline, bool skipFirstPoint, ICollection resultParameters = null)
+ {
+ if (resultPolyline == null)
+ {
+ throw new ArgumentNullException(nameof(resultPolyline));
+ }
+ if (controlPoints == null)
+ {
+ throw new ArgumentNullException(nameof(controlPoints));
+ }
+ if (controlPoints.Length != 3)
+ {
+ throw new ArgumentOutOfRangeException(nameof(controlPoints));
+ }
+ EnsureErrorTolerance(ref errorTolerance);
+ Point[] pointArray = { controlPoints[0], GeometryHelper.Lerp(controlPoints[0], controlPoints[1], 0.66666666666666663), GeometryHelper.Lerp(controlPoints[1], controlPoints[2], 0.33333333333333331), controlPoints[2] };
+ FlattenCubic(pointArray, errorTolerance, resultPolyline, skipFirstPoint, resultParameters);
+ }
+
+ private static bool IsCubicChordMonotone(Point[] controlPoints, double squaredTolerance)
+ {
+ double num = GeometryHelper.SquaredDistance(controlPoints[0], controlPoints[3]);
+ if (num <= squaredTolerance)
+ {
+ return false;
+ }
+ Vector lhs = controlPoints[3].Subtract(controlPoints[0]);
+ Vector rhs = controlPoints[1].Subtract(controlPoints[0]);
+ double num2 = GeometryHelper.Dot(lhs, rhs);
+ if ((num2 < 0.0) || (num2 > num))
+ {
+ return false;
+ }
+ Vector vector3 = controlPoints[2].Subtract(controlPoints[0]);
+ double num3 = GeometryHelper.Dot(lhs, vector3);
+ if ((num3 < 0.0) || (num3 > num))
+ {
+ return false;
+ }
+ if (num2 > num3)
+ {
+ return false;
+ }
+ return true;
+ }
+
+ private static uint Log4Double(double d)
+ {
+ uint num = 0;
+ while (d > 1.0)
+ {
+ d *= 0.25;
+ num++;
+ }
+ return num;
+ }
+
+ private static uint Log4UnsignedInt32(uint i)
+ {
+ uint num = 0;
+ while (i > 0)
+ {
+ i = i >> 2;
+ num++;
+ }
+ return num;
+ }
+
+ private static uint Log8UnsignedInt32(uint i)
+ {
+ uint num = 0;
+ while (i > 0)
+ {
+ i = i >> 3;
+ num++;
+ }
+ return num;
+ }
+
+ private class AdaptiveForwardDifferencingCubicFlattener
+ {
+ private double _aX;
+
+ private double _aY;
+
+ private double _bX;
+
+ private double _bY;
+
+ private double _cX;
+
+ private double _cY;
+
+ private readonly double _distanceTolerance;
+
+ private readonly bool _doParameters;
+
+ private double _dParameter;
+
+ private double _dX;
+
+ private double _dY;
+
+ private readonly double _flatnessTolerance;
+
+ private int _numSteps;
+
+ private double _parameter;
+
+ internal AdaptiveForwardDifferencingCubicFlattener(Point[] controlPoints,
+ double flatnessTolerance, double distanceTolerance, bool doParameters)
+ {
+ _numSteps = 1;
+ _dParameter = 1.0;
+ _flatnessTolerance = 3.0 * flatnessTolerance;
+ _distanceTolerance = distanceTolerance;
+ _doParameters = doParameters;
+ _aX = -controlPoints[0].X + 3.0 * (controlPoints[1].X - controlPoints[2].X) +
+ controlPoints[3].X;
+ _aY = -controlPoints[0].Y + 3.0 * (controlPoints[1].Y - controlPoints[2].Y) +
+ controlPoints[3].Y;
+ _bX = 3.0 * (controlPoints[0].X - 2.0 * controlPoints[1].X + controlPoints[2].X);
+ _bY = 3.0 * (controlPoints[0].Y - 2.0 * controlPoints[1].Y + controlPoints[2].Y);
+ _cX = 3.0 * (-controlPoints[0].X + controlPoints[1].X);
+ _cY = 3.0 * (-controlPoints[0].Y + controlPoints[1].Y);
+ _dX = controlPoints[0].X;
+ _dY = controlPoints[0].Y;
+ }
+
+ private void DoubleStepSize()
+ {
+ _aX *= 8.0;
+ _aY *= 8.0;
+ _bX *= 4.0;
+ _bY *= 4.0;
+ _cX += _cX;
+ _cY += _cY;
+ if (_doParameters) _dParameter *= 2.0;
+ _numSteps = _numSteps >> 1;
+ }
+
+ private void HalveStepSize()
+ {
+ _aX *= 0.125;
+ _aY *= 0.125;
+ _bX *= 0.25;
+ _bY *= 0.25;
+ _cX *= 0.5;
+ _cY *= 0.5;
+ if (_doParameters) _dParameter *= 0.5;
+ _numSteps = _numSteps << 1;
+ }
+
+ private void IncrementDifferencesAndParameter()
+ {
+ _dX = _aX + _bX + _cX + _dX;
+ _dY = _aY + _bY + _cY + _dY;
+ _cX = _aX + _aX + _aX + _bX + _bX + _cX;
+ _cY = _aY + _aY + _aY + _bY + _bY + _cY;
+ _bX = _aX + _aX + _aX + _bX;
+ _bY = _aY + _aY + _aY + _bY;
+ _numSteps--;
+ _parameter += _dParameter;
+ }
+
+ private bool MustSubdivide(double flatnessTolerance)
+ {
+ var num = -(_aY + _bY + _cY);
+ var num2 = _aX + _bX + _cX;
+ var num3 = Math.Abs(num) + Math.Abs(num2);
+ if (num3 <= _distanceTolerance) return false;
+ num3 *= flatnessTolerance;
+ if (Math.Abs(_cX * num + _cY * num2) <= num3 &&
+ Math.Abs((_bX + _cX + _cX) * num + (_bY + _cY + _cY) * num2) <= num3) return false;
+ return true;
+ }
+
+ // ReSharper disable once RedundantAssignment
+ internal bool Next(ref Point p, ref double u)
+ {
+ while (MustSubdivide(_flatnessTolerance)) HalveStepSize();
+ if ((_numSteps & 1) == 0)
+ while (_numSteps > 1 && !MustSubdivide(_flatnessTolerance * 0.25))
+ DoubleStepSize();
+ IncrementDifferencesAndParameter();
+ p.X = _dX;
+ p.Y = _dY;
+ u = _parameter;
+ return _numSteps != 0;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/HandyControl/Expression/Drawing/CommonExtensions.cs b/HandyControl/Expression/Drawing/CommonExtensions.cs
new file mode 100644
index 00000000..a3e22a5a
--- /dev/null
+++ b/HandyControl/Expression/Drawing/CommonExtensions.cs
@@ -0,0 +1,115 @@
+using System;
+using System.Collections.Generic;
+using System.Windows;
+
+namespace HandyControl.Expression.Drawing
+{
+ internal static class CommonExtensions
+ {
+ public static void AddRange(this ICollection collection, IEnumerable newItems)
+ {
+ if (collection == null)
+ {
+ throw new ArgumentNullException(nameof(collection));
+ }
+
+ if (collection is List list)
+ {
+ list.AddRange(newItems);
+ }
+ else
+ {
+ foreach (var local in newItems)
+ {
+ collection.Add(local);
+ }
+ }
+ }
+
+ public static bool SetIfDifferent(this DependencyObject dependencyObject, DependencyProperty dependencyProperty, object value)
+ {
+ if (!Equals(dependencyObject.GetValue(dependencyProperty), value))
+ {
+ dependencyObject.SetValue(dependencyProperty, value);
+ return true;
+ }
+ return false;
+ }
+
+ public static bool EnsureListCount(this IList list, int count, Func factory = null)
+ {
+ if (list == null)
+ {
+ throw new ArgumentNullException(nameof(list));
+ }
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count));
+ }
+ if (!list.EnsureListCountAtLeast(count, factory))
+ {
+ if (list.Count <= count)
+ {
+ return false;
+ }
+
+ if (list is List list2)
+ {
+ list2.RemoveRange(count, list.Count - count);
+ }
+ else
+ {
+ for (int i = list.Count - 1; i >= count; i--)
+ {
+ list.RemoveAt(i);
+ }
+ }
+ }
+ return true;
+ }
+
+ public static bool EnsureListCountAtLeast(this IList list, int count, Func factory = null)
+ {
+ if (list == null)
+ {
+ throw new ArgumentNullException(nameof(list));
+ }
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count));
+ }
+ if (list.Count >= count)
+ {
+ return false;
+ }
+
+ if ((list is List list2) && (factory == null))
+ {
+ list2.AddRange(new T[count - list.Count]);
+ }
+ else
+ {
+ for (int i = list.Count; i < count; i++)
+ {
+ list.Add((factory == null) ? default(T) : factory());
+ }
+ }
+ return true;
+ }
+
+ public static bool ClearIfSet(this DependencyObject dependencyObject, DependencyProperty dependencyProperty)
+ {
+ if (dependencyObject.ReadLocalValue(dependencyProperty) != DependencyProperty.UnsetValue)
+ {
+ dependencyObject.ClearValue(dependencyProperty);
+ return true;
+ }
+ return false;
+ }
+
+ public static void RemoveLast(this IList list)
+ {
+ list.RemoveAt(list.Count - 1);
+ }
+ }
+}
\ No newline at end of file
diff --git a/HandyControl/Expression/Drawing/ExceptionStringTable.cs b/HandyControl/Expression/Drawing/ExceptionStringTable.cs
new file mode 100644
index 00000000..d2deda04
--- /dev/null
+++ b/HandyControl/Expression/Drawing/ExceptionStringTable.cs
@@ -0,0 +1,39 @@
+using System.CodeDom.Compiler;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Globalization;
+using System.Resources;
+using System.Runtime.CompilerServices;
+
+namespace HandyControl.Expression.Drawing
+{
+ [CompilerGenerated]
+ [GeneratedCode("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [DebuggerNonUserCode]
+ internal class ExceptionStringTable
+ {
+ private static ResourceManager ResourceMan;
+
+ [EditorBrowsable(EditorBrowsableState.Advanced)]
+ internal static CultureInfo Culture { get; set; }
+
+ [EditorBrowsable(EditorBrowsableState.Advanced)]
+ internal static ResourceManager ResourceManager
+ {
+ get
+ {
+ if (ReferenceEquals(ResourceMan, null))
+ {
+ var manager = new ResourceManager("Microsoft.Expression.Drawing.ExceptionStringTable",
+ typeof(ExceptionStringTable).Assembly);
+ ResourceMan = manager;
+ }
+
+ return ResourceMan;
+ }
+ }
+
+ internal static string TypeNotSupported =>
+ ResourceManager.GetString("TypeNotSupported", Culture);
+ }
+}
\ No newline at end of file
diff --git a/HandyControl/Expression/Drawing/GeometryHelper.cs b/HandyControl/Expression/Drawing/GeometryHelper.cs
new file mode 100644
index 00000000..8fdc12d9
--- /dev/null
+++ b/HandyControl/Expression/Drawing/GeometryHelper.cs
@@ -0,0 +1,221 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using System.Windows;
+using System.Windows.Media;
+
+namespace HandyControl.Expression.Drawing
+{
+ public static class GeometryHelper
+ {
+ internal static void ApplyTransform(this IList points, GeneralTransform transform)
+ {
+ for (var i = 0; i < points.Count; i++) points[i] = transform.Transform(points[i]);
+ }
+
+ internal static Rect Bounds(this Size size)
+ {
+ return new Rect(0.0, 0.0, size.Width, size.Height);
+ }
+
+ internal static Point Center(this Rect rect)
+ {
+ return new Point(rect.X + rect.Width / 2.0, rect.Y + rect.Height / 2.0);
+ }
+
+ internal static Vector Subtract(this Point lhs, Point rhs)
+ {
+ return new Vector(lhs.X - rhs.X, lhs.Y - rhs.Y);
+ }
+
+ internal static Thickness Subtract(this Rect lhs, Rect rhs)
+ {
+ return new Thickness(rhs.Left - lhs.Left,
+ rhs.Top - lhs.Top, lhs.Right - rhs.Right,
+ lhs.Bottom - rhs.Bottom);
+ }
+
+ internal static Point Lerp(Point pointA, Point pointB, double alpha)
+ {
+ return new Point(
+ MathHelper.Lerp(pointA.X, pointB.X, alpha),
+ MathHelper.Lerp(pointA.Y, pointB.Y, alpha));
+ }
+
+ internal static Vector Lerp(Vector vectorA, Vector vectorB, double alpha)
+ {
+ return new Vector(MathHelper.Lerp(vectorA.X, vectorB.X, alpha),
+ MathHelper.Lerp(vectorA.Y, vectorB.Y, alpha));
+ }
+
+ internal static double Distance(Point lhs, Point rhs)
+ {
+ var num = lhs.X - rhs.X;
+ var num2 = lhs.Y - rhs.Y;
+ return Math.Sqrt(num * num + num2 * num2);
+ }
+
+ internal static Rect Resize(this Rect rect, double ratio)
+ {
+ return rect.Resize(ratio, ratio);
+ }
+
+ internal static Rect Resize(this Rect rect, double ratioX, double ratioY)
+ {
+ var point = rect.Center();
+ var width = rect.Width * ratioX;
+ var height = rect.Height * ratioY;
+ return new Rect(point.X - width / 2.0, point.Y - height / 2.0, width, height);
+ }
+
+ internal static Rect GetStretchBound(Rect logicalBound, Stretch stretch, Size aspectRatio)
+ {
+ if (stretch == Stretch.None) stretch = Stretch.Fill;
+ if (stretch == Stretch.Fill || !aspectRatio.HasValidArea()) return logicalBound;
+ var point = logicalBound.Center();
+ if (stretch == Stretch.Uniform)
+ {
+ if (aspectRatio.Width * logicalBound.Height < logicalBound.Width * aspectRatio.Height)
+ logicalBound.Width = logicalBound.Height * aspectRatio.Width / aspectRatio.Height;
+ else
+ logicalBound.Height = logicalBound.Width * aspectRatio.Height / aspectRatio.Width;
+ }
+ else if (stretch == Stretch.UniformToFill)
+ {
+ if (aspectRatio.Width * logicalBound.Height < logicalBound.Width * aspectRatio.Height)
+ logicalBound.Height = logicalBound.Width * aspectRatio.Height / aspectRatio.Width;
+ else
+ logicalBound.Width = logicalBound.Height * aspectRatio.Width / aspectRatio.Height;
+ }
+
+ return new Rect(point.X - logicalBound.Width / 2.0, point.Y - logicalBound.Height / 2.0,
+ logicalBound.Width, logicalBound.Height);
+ }
+
+ internal static Point GetArcPoint(double degree)
+ {
+ var a = degree * 3.1415926535897931 / 180.0;
+ return new Point(0.5 + 0.5 * Math.Sin(a), 0.5 - 0.5 * Math.Cos(a));
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ internal static Point GetArcPoint(double degree, Rect bound)
+ {
+ var arcPoint = GetArcPoint(degree);
+ return RelativeToAbsolutePoint(bound, arcPoint);
+ }
+
+ internal static Point RelativeToAbsolutePoint(Rect bound, Point relative)
+ {
+ return new Point(bound.X + relative.X * bound.Width, bound.Y + relative.Y * bound.Height);
+ }
+
+ internal static double SquaredDistance(Point lhs, Point rhs)
+ {
+ var num = lhs.X - rhs.X;
+ var num2 = lhs.Y - rhs.Y;
+ return num * num + num2 * num2;
+ }
+
+ internal static Point Midpoint(Point lhs, Point rhs)
+ {
+ return new Point((lhs.X + rhs.X) / 2.0, (lhs.Y + rhs.Y) / 2.0);
+ }
+
+ internal static bool HasValidArea(this Size size)
+ {
+ return size.Width > 0.0 && size.Height > 0.0 && !double.IsInfinity(size.Width) &&
+ !double.IsInfinity(size.Height);
+ }
+
+ internal static Rect Inflate(Rect rect, double offset)
+ {
+ return Inflate(rect, new Thickness(offset));
+ }
+
+ internal static Rect Inflate(Rect rect, Thickness thickness)
+ {
+ var width = rect.Width + thickness.Left + thickness.Right;
+ var height = rect.Height + thickness.Top + thickness.Bottom;
+ var x = rect.X - thickness.Left;
+ if (width < 0.0)
+ {
+ x += width / 2.0;
+ width = 0.0;
+ }
+
+ var y = rect.Y - thickness.Top;
+ if (height < 0.0)
+ {
+ y += height / 2.0;
+ height = 0.0;
+ }
+
+ return new Rect(x, y, width, height);
+ }
+
+ internal static double Dot(Point lhs, Point rhs)
+ {
+ return lhs.X * rhs.X + lhs.Y * rhs.Y;
+ }
+
+ internal static double Dot(Vector lhs, Vector rhs)
+ {
+ return lhs.X * rhs.X + lhs.Y * rhs.Y;
+ }
+
+ internal static Point Plus(this Point lhs, Point rhs)
+ {
+ return new Point(lhs.X + rhs.X, lhs.Y + rhs.Y);
+ }
+
+ internal static Point Minus(this Point lhs, Point rhs)
+ {
+ return new Point(lhs.X - rhs.X, lhs.Y - rhs.Y);
+ }
+
+ internal static Vector Normal(Point lhs, Point rhs)
+ {
+ return new Vector(lhs.Y - rhs.Y, rhs.X - lhs.X).Normalized();
+ }
+
+ internal static Vector Normalized(this Vector vector)
+ {
+ var vector2 = new Vector(vector.X, vector.Y);
+ var length = vector2.Length;
+ if (MathHelper.IsVerySmall(length)) return new Vector(0.0, 1.0);
+ return vector2 / length;
+ }
+
+ internal static double Determinant(Point lhs, Point rhs)
+ {
+ return lhs.X * rhs.Y - lhs.Y * rhs.X;
+ }
+
+ internal static bool EnsureSegmentType(out T result, IList list, int index,
+ Func factory) where T : PathSegment
+ {
+ result = list[index] as T;
+ if (result == null)
+ {
+ list[index] = result = factory();
+ return true;
+ }
+
+ return false;
+ }
+
+ internal static bool EnsureGeometryType(out T result, ref Geometry value, Func factory)
+ where T : Geometry
+ {
+ result = value as T;
+ if (result == null)
+ {
+ value = result = factory();
+ return true;
+ }
+
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/HandyControl/Expression/Drawing/MarchLocation.cs b/HandyControl/Expression/Drawing/MarchLocation.cs
new file mode 100644
index 00000000..420104a2
--- /dev/null
+++ b/HandyControl/Expression/Drawing/MarchLocation.cs
@@ -0,0 +1,50 @@
+using System.Collections.Generic;
+using System.Windows;
+
+namespace HandyControl.Expression.Drawing
+{
+ internal class MarchLocation
+ {
+ public double After { get; private set; }
+
+ public double Before { get; private set; }
+
+ public int Index { get; private set; }
+
+ public double Ratio { get; private set; }
+
+ public MarchStopReason Reason { get; private set; }
+
+ public double Remain { get; private set; }
+
+ public static MarchLocation Create(MarchStopReason reason, int index, double before, double after,
+ double remain)
+ {
+ var num = before + after;
+ return new MarchLocation
+ {
+ Reason = reason,
+ Index = index,
+ Remain = remain,
+ Before = MathHelper.EnsureRange(before, 0.0, num),
+ After = MathHelper.EnsureRange(after, 0.0, num),
+ Ratio = MathHelper.EnsureRange(MathHelper.SafeDivide(before, num, 0.0), 0.0, 1.0)
+ };
+ }
+
+ public double GetArcLength(IList accumulatedLengths)
+ {
+ return MathHelper.Lerp(accumulatedLengths[Index], accumulatedLengths[Index + 1], Ratio);
+ }
+
+ public Vector GetNormal(PolylineData polyline, double cornerRadius = 0.0)
+ {
+ return polyline.SmoothNormal(Index, Ratio, cornerRadius);
+ }
+
+ public Point GetPoint(IList points)
+ {
+ return GeometryHelper.Lerp(points[Index], points[Index + 1], Ratio);
+ }
+ }
+}
\ No newline at end of file
diff --git a/HandyControl/Expression/Drawing/MarchStopReason.cs b/HandyControl/Expression/Drawing/MarchStopReason.cs
new file mode 100644
index 00000000..7a5a53f7
--- /dev/null
+++ b/HandyControl/Expression/Drawing/MarchStopReason.cs
@@ -0,0 +1,9 @@
+namespace HandyControl.Expression.Drawing
+{
+ internal enum MarchStopReason
+ {
+ CompleteStep,
+ CompletePolyline,
+ CornerPoint
+ }
+}
\ No newline at end of file
diff --git a/HandyControl/Expression/Drawing/MathHelper.cs b/HandyControl/Expression/Drawing/MathHelper.cs
new file mode 100644
index 00000000..2db4f04e
--- /dev/null
+++ b/HandyControl/Expression/Drawing/MathHelper.cs
@@ -0,0 +1,65 @@
+using System;
+
+namespace HandyControl.Expression.Drawing
+{
+ internal static class MathHelper
+ {
+ public static bool AreClose(double value1, double value2) =>
+ // ReSharper disable once CompareOfFloatsByEqualityOperator
+ value1 == value2 || IsVerySmall(value1 - value2);
+
+ public static double Lerp(double x, double y, double alpha) => x * (1.0 - alpha) + y * alpha;
+
+ public static bool IsVerySmall(double value) => Math.Abs(value) < 1E-06;
+
+ public static bool IsFiniteDouble(double x) => !double.IsInfinity(x) && !double.IsNaN(x);
+
+ public static double DoubleFromMantissaAndExponent(double x, int exp) => x * Math.Pow(2.0, exp);
+
+ public static bool GreaterThan(double value1, double value2) => value1 > value2 && !AreClose(value1, value2);
+
+ public static bool GreaterThanOrClose(double value1, double value2)
+ {
+ if (value1 <= value2)
+ {
+ return AreClose(value1, value2);
+ }
+ return true;
+ }
+
+ public static double Hypotenuse(double x, double y) => Math.Sqrt(x * x + y * y);
+
+ public static bool LessThan(double value1, double value2) => value1 < value2 && !AreClose(value1, value2);
+
+ public static bool LessThanOrClose(double value1, double value2)
+ {
+ if (value1 >= value2)
+ {
+ return AreClose(value1, value2);
+ }
+ return true;
+ }
+
+ public static double EnsureRange(double value, double? min, double? max)
+ {
+ if (min.HasValue && value < min.Value)
+ {
+ return min.Value;
+ }
+ if (max.HasValue && value > max.Value)
+ {
+ return max.Value;
+ }
+ return value;
+ }
+
+ public static double SafeDivide(double lhs, double rhs, double fallback)
+ {
+ if (!IsVerySmall(rhs))
+ {
+ return lhs / rhs;
+ }
+ return fallback;
+ }
+ }
+}
\ No newline at end of file
diff --git a/HandyControl/Expression/Drawing/PathFigureHelper.cs b/HandyControl/Expression/Drawing/PathFigureHelper.cs
new file mode 100644
index 00000000..40c906ce
--- /dev/null
+++ b/HandyControl/Expression/Drawing/PathFigureHelper.cs
@@ -0,0 +1,115 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows;
+using System.Windows.Media;
+
+namespace HandyControl.Expression.Drawing
+{
+ internal static class PathFigureHelper
+ {
+ public static IEnumerable AllSegments(this PathFigure figure)
+ {
+ if (figure != null && figure.Segments.Count > 0)
+ {
+ var startPoint = figure.StartPoint;
+ // ReSharper disable once GenericEnumeratorNotDisposed
+ var enumerator = figure.Segments.GetEnumerator();
+ while (enumerator.MoveNext())
+ {
+ var current = enumerator.Current;
+ var lastPoint = current.GetLastPoint();
+ yield return new PathSegmentData(startPoint, current);
+ startPoint = lastPoint;
+ }
+ }
+ }
+
+ internal static void ApplyTransform(this PathFigure figure, GeneralTransform transform)
+ {
+ figure.StartPoint = transform.Transform(figure.StartPoint);
+ for (var i = 0; i < figure.Segments.Count; i++)
+ {
+ var segment =
+ PathSegmentHelper.ApplyTransform(figure.Segments[i], figure.StartPoint, transform);
+ if (!Equals(figure.Segments[i], segment)) figure.Segments[i] = segment;
+ }
+ }
+
+ internal static void FlattenFigure(PathFigure figure, IList points, double tolerance,
+ bool removeRepeat)
+ {
+ if (figure == null) throw new ArgumentNullException(nameof(figure));
+ if (points == null) throw new ArgumentNullException(nameof(points));
+ if (tolerance < 0.0) throw new ArgumentOutOfRangeException(nameof(tolerance));
+ var list = removeRepeat ? new List() : points;
+ list.Add(figure.StartPoint);
+ foreach (var data in figure.AllSegments())
+ data.PathSegment.FlattenSegment(list, data.StartPoint, tolerance);
+ if (figure.IsClosed) list.Add(figure.StartPoint);
+ if (removeRepeat && list.Count > 0)
+ {
+ points.Add(list[0]);
+ for (var i = 1; i < list.Count; i++)
+ if (!MathHelper.IsVerySmall(GeometryHelper.SquaredDistance(points.Last(), list[i])))
+ points.Add(list[i]);
+ }
+ }
+
+ internal static bool SyncEllipseFigure(PathFigure figure, Rect bounds, SweepDirection sweepDirection,
+ bool isFilled = true)
+ {
+ var flag = false;
+ var pointArray = new Point[2];
+ var size = new Size(bounds.Width / 2.0, bounds.Height / 2.0);
+ var point = bounds.Center();
+ if (size.Width > size.Height)
+ {
+ pointArray[0] = new Point(bounds.Left, point.Y);
+ pointArray[1] = new Point(bounds.Right, point.Y);
+ }
+ else
+ {
+ pointArray[0] = new Point(point.X, bounds.Top);
+ pointArray[1] = new Point(point.X, bounds.Bottom);
+ }
+
+ flag |= figure.SetIfDifferent(PathFigure.IsClosedProperty, true);
+ flag |= figure.SetIfDifferent(PathFigure.IsFilledProperty, isFilled);
+ flag |= figure.SetIfDifferent(PathFigure.StartPointProperty, pointArray[0]);
+ flag |= figure.Segments.EnsureListCount(2, () => new ArcSegment());
+ flag |= GeometryHelper.EnsureSegmentType(out var segment, figure.Segments, 0, () => new ArcSegment());
+ flag |= segment.SetIfDifferent(ArcSegment.PointProperty, pointArray[1]);
+ flag |= segment.SetIfDifferent(ArcSegment.SizeProperty, size);
+ flag |= segment.SetIfDifferent(ArcSegment.IsLargeArcProperty, false);
+ flag |= segment.SetIfDifferent(ArcSegment.SweepDirectionProperty, sweepDirection);
+ flag |= GeometryHelper.EnsureSegmentType(out segment, figure.Segments, 1, () => new ArcSegment());
+ flag |= segment.SetIfDifferent(ArcSegment.PointProperty, pointArray[0]);
+ flag |= segment.SetIfDifferent(ArcSegment.SizeProperty, size);
+ flag |= segment.SetIfDifferent(ArcSegment.IsLargeArcProperty, false);
+ return flag | segment.SetIfDifferent(ArcSegment.SweepDirectionProperty, sweepDirection);
+ }
+
+ internal static bool SyncPolylineFigure(PathFigure figure, IList points, bool isClosed,
+ bool isFilled = true)
+ {
+ if (figure == null) throw new ArgumentNullException(nameof(figure));
+ var flag = false;
+ if (points == null || points.Count == 0)
+ {
+ flag |= figure.ClearIfSet(PathFigure.StartPointProperty);
+ flag |= figure.Segments.EnsureListCount(0);
+ }
+ else
+ {
+ flag |= figure.SetIfDifferent(PathFigure.StartPointProperty, points[0]);
+ flag |= figure.Segments.EnsureListCount(1, () => new PolyLineSegment());
+ flag |= PathSegmentHelper.SyncPolylineSegment(figure.Segments, 0, points, 1,
+ points.Count - 1);
+ }
+
+ flag |= figure.SetIfDifferent(PathFigure.IsClosedProperty, isClosed);
+ return flag | figure.SetIfDifferent(PathFigure.IsFilledProperty, isFilled);
+ }
+ }
+}
\ No newline at end of file
diff --git a/HandyControl/Expression/Drawing/PathGeometryHelper.cs b/HandyControl/Expression/Drawing/PathGeometryHelper.cs
new file mode 100644
index 00000000..d9307403
--- /dev/null
+++ b/HandyControl/Expression/Drawing/PathGeometryHelper.cs
@@ -0,0 +1,25 @@
+using System.Windows.Media;
+
+namespace HandyControl.Expression.Drawing
+{
+ internal static class PathGeometryHelper
+ {
+ public static bool IsStroked(this PathSegment pathSegment) => pathSegment.IsStroked;
+
+ public static PathGeometry AsPathGeometry(this Geometry original)
+ {
+ if (original == null)
+ {
+ return null;
+ }
+
+ if (!(original is PathGeometry geometry))
+ {
+ return PathGeometry.CreateFromGeometry(original);
+ }
+ return geometry;
+ }
+
+ internal static Geometry FixPathGeometryBoundary(Geometry geometry) => geometry;
+ }
+}
\ No newline at end of file
diff --git a/HandyControl/Expression/Drawing/PathSegmentData.cs b/HandyControl/Expression/Drawing/PathSegmentData.cs
new file mode 100644
index 00000000..6e7bb7b2
--- /dev/null
+++ b/HandyControl/Expression/Drawing/PathSegmentData.cs
@@ -0,0 +1,17 @@
+using System.Windows;
+
+namespace HandyControl.Expression.Drawing
+{
+ internal sealed class PathSegmentData
+ {
+ public PathSegmentData(Point startPoint, System.Windows.Media.PathSegment pathSegment)
+ {
+ PathSegment = pathSegment;
+ StartPoint = startPoint;
+ }
+
+ public System.Windows.Media.PathSegment PathSegment { get; }
+
+ public Point StartPoint { get; }
+ }
+}
\ No newline at end of file
diff --git a/HandyControl/Expression/Drawing/PathSegmentHelper.cs b/HandyControl/Expression/Drawing/PathSegmentHelper.cs
new file mode 100644
index 00000000..e64fce79
--- /dev/null
+++ b/HandyControl/Expression/Drawing/PathSegmentHelper.cs
@@ -0,0 +1,755 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Windows;
+using System.Windows.Media;
+
+namespace HandyControl.Expression.Drawing
+{
+ internal static class PathSegmentHelper
+ {
+ public static PathSegment ApplyTransform(PathSegment segment, Point start, GeneralTransform transform)
+ {
+ return PathSegmentImplementation.Create(segment, start).ApplyTransform(transform);
+ }
+
+ public static PathSegment ArcToBezierSegments(ArcSegment arcSegment, Point startPoint)
+ {
+ var isStroked = arcSegment.IsStroked();
+ ArcToBezierHelper.ArcToBezier(startPoint.X, startPoint.Y, arcSegment.Size.Width,
+ arcSegment.Size.Height, arcSegment.RotationAngle, arcSegment.IsLargeArc,
+ arcSegment.SweepDirection == SweepDirection.Clockwise, arcSegment.Point.X, arcSegment.Point.Y,
+ out var pointArray, out var num);
+ switch (num)
+ {
+ case -1:
+ return null;
+
+ case 0:
+ return CreateLineSegment(arcSegment.Point, isStroked);
+
+ case 1:
+ return CreateBezierSegment(pointArray[0], pointArray[1], pointArray[2], isStroked);
+ }
+
+ return CreatePolyBezierSegment(pointArray, 0, num * 3, isStroked);
+ }
+
+ public static ArcSegment CreateArcSegment(Point point, Size size, bool isLargeArc, bool clockwise,
+ double rotationAngle = 0.0, bool isStroked = true)
+ {
+ var dependencyObject = new ArcSegment();
+ dependencyObject.SetIfDifferent(ArcSegment.PointProperty, point);
+ dependencyObject.SetIfDifferent(ArcSegment.SizeProperty, size);
+ dependencyObject.SetIfDifferent(ArcSegment.IsLargeArcProperty, isLargeArc);
+ dependencyObject.SetIfDifferent(ArcSegment.SweepDirectionProperty,
+ clockwise ? SweepDirection.Clockwise : SweepDirection.Counterclockwise);
+ dependencyObject.SetIfDifferent(ArcSegment.RotationAngleProperty, rotationAngle);
+ dependencyObject.SetIsStroked(isStroked);
+ return dependencyObject;
+ }
+
+ public static BezierSegment CreateBezierSegment(Point point1, Point point2, Point point3,
+ bool isStroked = true)
+ {
+ var segment = new BezierSegment
+ {
+ Point1 = point1,
+ Point2 = point2,
+ Point3 = point3
+ };
+ segment.SetIsStroked(isStroked);
+ return segment;
+ }
+
+ public static LineSegment CreateLineSegment(Point point, bool isStroked = true)
+ {
+ var segment = new LineSegment
+ {
+ Point = point
+ };
+ segment.SetIsStroked(isStroked);
+ return segment;
+ }
+
+ public static PolyBezierSegment CreatePolyBezierSegment(IList points, int start, int count,
+ bool isStroked = true)
+ {
+ if (points == null) throw new ArgumentNullException(nameof(points));
+ count = count / 3 * 3;
+ if (count < 0 || points.Count < start + count) throw new ArgumentOutOfRangeException(nameof(count));
+ var segment = new PolyBezierSegment
+ {
+ Points = new PointCollection()
+ };
+ for (var i = 0; i < count; i++) segment.Points.Add(points[start + i]);
+ segment.SetIsStroked(isStroked);
+ return segment;
+ }
+
+ public static PolyLineSegment CreatePolylineSegment(IList points, int start, int count,
+ bool isStroked = true)
+ {
+ if (count < 0 || points.Count < start + count) throw new ArgumentOutOfRangeException(nameof(count));
+ var segment = new PolyLineSegment
+ {
+ Points = new PointCollection()
+ };
+ for (var i = 0; i < count; i++) segment.Points.Add(points[start + i]);
+ segment.SetIsStroked(isStroked);
+ return segment;
+ }
+
+ public static PolyQuadraticBezierSegment CreatePolyQuadraticBezierSegment(IList points,
+ int start, int count, bool isStroked = true)
+ {
+ if (points == null) throw new ArgumentNullException(nameof(points));
+ count = count / 2 * 2;
+ if (count < 0 || points.Count < start + count) throw new ArgumentOutOfRangeException(nameof(count));
+ var segment = new PolyQuadraticBezierSegment
+ {
+ Points = new PointCollection()
+ };
+ for (var i = 0; i < count; i++) segment.Points.Add(points[start + i]);
+ segment.SetIsStroked(isStroked);
+ return segment;
+ }
+
+ public static QuadraticBezierSegment CreateQuadraticBezierSegment(Point point1, Point point2,
+ bool isStroked = true)
+ {
+ var segment = new QuadraticBezierSegment
+ {
+ Point1 = point1,
+ Point2 = point2
+ };
+ segment.SetIsStroked(isStroked);
+ return segment;
+ }
+
+ public static void FlattenSegment(this PathSegment segment, IList points, Point start,
+ double tolerance)
+ {
+ PathSegmentImplementation.Create(segment, start).Flatten(points, tolerance);
+ }
+
+ public static Point GetLastPoint(this PathSegment segment)
+ {
+ return segment.GetPoint(-1);
+ }
+
+ public static Point GetPoint(this PathSegment segment, int index)
+ {
+ return PathSegmentImplementation.Create(segment).GetPoint(index);
+ }
+
+ public static int GetPointCount(this PathSegment segment)
+ {
+ if (segment is ArcSegment) return 1;
+ if (segment is LineSegment) return 1;
+ if (segment is QuadraticBezierSegment) return 2;
+ if (segment is BezierSegment) return 3;
+ if (segment is PolyLineSegment segment2) return segment2.Points.Count;
+ if (segment is PolyQuadraticBezierSegment segment3) return segment3.Points.Count / 2 * 2;
+ if (segment is PolyBezierSegment segment4) return segment4.Points.Count / 3 * 3;
+ return 0;
+ }
+
+ public static IEnumerable GetSimpleSegments(this PathSegment segment, Point start)
+ {
+ return PathSegmentImplementation.Create(segment, start).GetSimpleSegments();
+ }
+
+ public static bool IsEmpty(this PathSegment segment)
+ {
+ return segment.GetPointCount() == 0;
+ }
+
+ private static void SetIsStroked(this PathSegment segment, bool isStroked)
+ {
+ if (segment.IsStroked != isStroked) segment.IsStroked = isStroked;
+ }
+
+ public static bool SyncPolyBezierSegment(PathSegmentCollection collection, int index,
+ IList points, int start, int count)
+ {
+ if (collection == null) throw new ArgumentNullException(nameof(collection));
+ if (index < 0 || index >= collection.Count) throw new ArgumentOutOfRangeException(nameof(index));
+ if (points == null) throw new ArgumentNullException(nameof(points));
+ if (start < 0) throw new ArgumentOutOfRangeException(nameof(start));
+ if (count < 0) throw new ArgumentOutOfRangeException(nameof(count));
+ if (points.Count < start + count) throw new ArgumentOutOfRangeException(nameof(count));
+ var flag = false;
+ count = count / 3 * 3;
+ var segment = collection[index] as PolyBezierSegment;
+ if (segment == null)
+ {
+ collection[index] = segment = new PolyBezierSegment();
+ flag = true;
+ }
+
+ segment.Points.EnsureListCount(count);
+ for (var i = 0; i < count; i++)
+ if (segment.Points[i] != points[i + start])
+ {
+ segment.Points[i] = points[i + start];
+ flag = true;
+ }
+
+ return flag;
+ }
+
+ public static bool SyncPolylineSegment(PathSegmentCollection collection, int index,
+ IList points, int start, int count)
+ {
+ if (collection == null) throw new ArgumentNullException(nameof(collection));
+ if (index < 0 || index >= collection.Count) throw new ArgumentOutOfRangeException(nameof(index));
+ if (points == null) throw new ArgumentNullException(nameof(points));
+ if (start < 0) throw new ArgumentOutOfRangeException(nameof(start));
+ if (count < 0) throw new ArgumentOutOfRangeException(nameof(count));
+ if (points.Count < start + count) throw new ArgumentOutOfRangeException(nameof(count));
+ var flag = false;
+ var segment = collection[index] as PolyLineSegment;
+ if (segment == null)
+ {
+ collection[index] = segment = new PolyLineSegment();
+ flag = true;
+ }
+
+ flag |= segment.Points.EnsureListCount(count);
+ for (var i = 0; i < count; i++)
+ if (segment.Points[i] != points[i + start])
+ {
+ segment.Points[i] = points[i + start];
+ flag = true;
+ }
+
+ return flag;
+ }
+
+
+ private class ArcSegmentImplementation : PathSegmentImplementation
+ {
+ private ArcSegment _segment;
+
+ public override PathSegment ApplyTransform(GeneralTransform transform)
+ {
+ var segment = ArcToBezierSegments(_segment, Start);
+ if (segment != null) return PathSegmentHelper.ApplyTransform(segment, Start, transform);
+ _segment.Point = transform.Transform(_segment.Point);
+ return _segment;
+ }
+
+ public static PathSegmentImplementation Create(ArcSegment source)
+ {
+ if (source != null) return new ArcSegmentImplementation {_segment = source};
+ return null;
+ }
+
+ public override void Flatten(IList points, double tolerance)
+ {
+ var segment = ArcToBezierSegments(_segment, Start);
+ segment?.FlattenSegment(points, Start, tolerance);
+ }
+
+ public override Point GetPoint(int index)
+ {
+ if (index < -1 || index > 0) throw new ArgumentOutOfRangeException(nameof(index));
+ return _segment.Point;
+ }
+
+ public override IEnumerable GetSimpleSegments()
+ {
+ var segment = ArcToBezierSegments(_segment, Start);
+ if (segment != null) return segment.GetSimpleSegments(Start);
+ return Enumerable.Empty();
+ }
+ }
+
+
+ private static class ArcToBezierHelper
+ {
+ private static bool AcceptRadius(double rHalfChord2, double rFuzz2, ref double rRadius)
+ {
+ var flag = rRadius * rRadius > rHalfChord2 * rFuzz2;
+ if (flag && rRadius < 0.0) rRadius = -rRadius;
+ return flag;
+ }
+
+ public static void ArcToBezier(double xStart, double yStart, double xRadius, double yRadius,
+ double rRotation, bool fLargeArc, bool fSweepUp, double xEnd, double yEnd, out Point[] pPt,
+ out int cPieces)
+ {
+ var num = 1E-06;
+ pPt = new Point[12];
+ var num12 = num * num;
+ var flag = false;
+ cPieces = -1;
+ var num2 = 0.5 * (xEnd - xStart);
+ var num3 = 0.5 * (yEnd - yStart);
+ var num4 = num2 * num2 + num3 * num3;
+ if (num4 >= num12)
+ {
+ if (!AcceptRadius(num4, num12, ref xRadius) || !AcceptRadius(num4, num12, ref yRadius))
+ {
+ cPieces = 0;
+ }
+ else
+ {
+ double num5;
+ double num6;
+ // ReSharper disable once InlineOutVariableDeclaration
+ double num7;
+ // ReSharper disable once InlineOutVariableDeclaration
+ double num8;
+ double num9;
+ double num10;
+ Point point4;
+ if (Math.Abs(rRotation) < num)
+ {
+ num5 = 1.0;
+ num6 = 0.0;
+ }
+ else
+ {
+ rRotation = -rRotation * 3.1415926535897931 / 180.0;
+ num5 = Math.Cos(rRotation);
+ num6 = Math.Sin(rRotation);
+ var num15 = num2 * num5 - num3 * num6;
+ num3 = num2 * num6 + num3 * num5;
+ num2 = num15;
+ }
+
+ num2 /= xRadius;
+ num3 /= yRadius;
+ num4 = num2 * num2 + num3 * num3;
+ if (num4 > 1.0)
+ {
+ var num16 = Math.Sqrt(num4);
+ xRadius *= num16;
+ yRadius *= num16;
+ num9 = num10 = 0.0;
+ flag = true;
+ num2 /= num16;
+ num3 /= num16;
+ }
+ else
+ {
+ var num17 = Math.Sqrt((1.0 - num4) / num4);
+ if (fLargeArc != fSweepUp)
+ {
+ num9 = -num17 * num3;
+ num10 = num17 * num2;
+ }
+ else
+ {
+ num9 = num17 * num3;
+ num10 = -num17 * num2;
+ }
+ }
+
+ var ptStart = new Point(-num2 - num9, -num3 - num10);
+ var ptEnd = new Point(num2 - num9, num3 - num10);
+ var matrix = new Matrix(num5 * xRadius, -num6 * xRadius, num6 * yRadius,
+ num5 * yRadius, 0.5 * (xEnd + xStart), 0.5 * (yEnd + yStart));
+ if (!flag)
+ {
+ matrix.OffsetX += matrix.M11 * num9 + matrix.M21 * num10;
+ matrix.OffsetY += matrix.M12 * num9 + matrix.M22 * num10;
+ }
+
+ GetArcAngle(ptStart, ptEnd, fLargeArc, fSweepUp, out num7, out num8, out cPieces);
+ var bezierDistance = GetBezierDistance(num7);
+ if (!fSweepUp) bezierDistance = -bezierDistance;
+ var rhs = new Point(-bezierDistance * ptStart.Y, bezierDistance * ptStart.X);
+ var index = 0;
+ pPt = new Point[cPieces * 3];
+ for (var i = 1; i < cPieces; i++)
+ {
+ var lhs = new Point(ptStart.X * num7 - ptStart.Y * num8,
+ ptStart.X * num8 + ptStart.Y * num7);
+ point4 = new Point(-bezierDistance * lhs.Y, bezierDistance * lhs.X);
+ pPt[index++] = matrix.Transform(ptStart.Plus(rhs));
+ pPt[index++] = matrix.Transform(lhs.Minus(point4));
+ pPt[index++] = matrix.Transform(lhs);
+ ptStart = lhs;
+ rhs = point4;
+ }
+
+ point4 = new Point(-bezierDistance * ptEnd.Y, bezierDistance * ptEnd.X);
+ pPt[index++] = matrix.Transform(ptStart.Plus(rhs));
+ pPt[index++] = matrix.Transform(ptEnd.Minus(point4));
+ pPt[index] = new Point(xEnd, yEnd);
+ }
+ }
+ }
+
+ private static void GetArcAngle(Point ptStart, Point ptEnd, bool fLargeArc, bool fSweepUp,
+ out double rCosArcAngle, out double rSinArcAngle, out int cPieces)
+ {
+ rCosArcAngle = GeometryHelper.Dot(ptStart, ptEnd);
+ rSinArcAngle = GeometryHelper.Determinant(ptStart, ptEnd);
+ if (rCosArcAngle >= 0.0)
+ {
+ if (!fLargeArc)
+ {
+ cPieces = 1;
+ return;
+ }
+
+ cPieces = 4;
+ }
+ else if (fLargeArc)
+ {
+ cPieces = 3;
+ }
+ else
+ {
+ cPieces = 2;
+ }
+
+ var d = Math.Atan2(rSinArcAngle, rCosArcAngle);
+ if (fSweepUp)
+ {
+ if (d < 0.0) d += 6.2831853071795862;
+ }
+ else if (d > 0.0)
+ {
+ d -= 6.2831853071795862;
+ }
+
+ d /= cPieces;
+ rCosArcAngle = Math.Cos(d);
+ rSinArcAngle = Math.Sin(d);
+ }
+
+ private static double GetBezierDistance(double rDot, double rRadius = 1.0)
+ {
+ var num = rRadius * rRadius;
+ var num5 = 0.0;
+ var d = 0.5 * (num + rDot);
+ if (d < 0.0) return num5;
+ var num3 = num - d;
+ if (num3 <= 0.0) return num5;
+ var num4 = Math.Sqrt(num3);
+ var num2 = 4.0 * (rRadius - Math.Sqrt(d)) / 3.0;
+ if (num2 <= num4 * 1E-06) return 0.0;
+ return num2 / num4;
+ }
+ }
+
+
+ private class BezierSegmentImplementation : PathSegmentImplementation
+ {
+ private BezierSegment _segment;
+
+ public override PathSegment ApplyTransform(GeneralTransform transform)
+ {
+ _segment.Point1 = transform.Transform(_segment.Point1);
+ _segment.Point2 = transform.Transform(_segment.Point2);
+ _segment.Point3 = transform.Transform(_segment.Point3);
+ return _segment;
+ }
+
+ public static PathSegmentImplementation Create(BezierSegment source)
+ {
+ if (source != null) return new BezierSegmentImplementation {_segment = source};
+ return null;
+ }
+
+ public override void Flatten(IList points, double tolerance)
+ {
+ Point[] controlPoints = {Start, _segment.Point1, _segment.Point2, _segment.Point3};
+ var resultPolyline = new List();
+ BezierCurveFlattener.FlattenCubic(controlPoints, tolerance, resultPolyline, true);
+ points.AddRange(resultPolyline);
+ }
+
+ public override Point GetPoint(int index)
+ {
+ if (index < -1 || index > 2) throw new ArgumentOutOfRangeException(nameof(index));
+ if (index == 0) return _segment.Point1;
+ if (index == 1) return _segment.Point2;
+ return _segment.Point3;
+ }
+
+ public override IEnumerable GetSimpleSegments()
+ {
+ yield return SimpleSegment.Create(Start, _segment.Point1, _segment.Point2, _segment.Point3);
+ }
+ }
+
+
+ private class LineSegmentImplementation : PathSegmentImplementation
+ {
+ private LineSegment _segment;
+
+ public override PathSegment ApplyTransform(GeneralTransform transform)
+ {
+ _segment.Point = transform.Transform(_segment.Point);
+ return _segment;
+ }
+
+ public static PathSegmentImplementation Create(LineSegment source)
+ {
+ if (source != null) return new LineSegmentImplementation {_segment = source};
+ return null;
+ }
+
+ public override void Flatten(IList points, double tolerance)
+ {
+ points.Add(_segment.Point);
+ }
+
+ public override Point GetPoint(int index)
+ {
+ if (index < -1 || index > 0) throw new ArgumentOutOfRangeException(nameof(index));
+ return _segment.Point;
+ }
+
+ public override IEnumerable GetSimpleSegments()
+ {
+ yield return SimpleSegment.Create(Start, _segment.Point);
+ }
+ }
+
+
+ private abstract class PathSegmentImplementation
+ {
+ protected Point Start { get; private set; }
+
+ public abstract PathSegment ApplyTransform(GeneralTransform transform);
+
+ public static PathSegmentImplementation Create(PathSegment segment)
+ {
+ PathSegmentImplementation implementation;
+ if ((implementation = BezierSegmentImplementation.Create(segment as BezierSegment)) == null &&
+ (implementation = LineSegmentImplementation.Create(segment as LineSegment)) == null &&
+ (implementation = ArcSegmentImplementation.Create(segment as ArcSegment)) == null &&
+ (implementation = PolyLineSegmentImplementation.Create(segment as PolyLineSegment)) ==
+ null &&
+ (implementation = PolyBezierSegmentImplementation.Create(segment as PolyBezierSegment)) ==
+ null &&
+ (implementation =
+ QuadraticBezierSegmentImplementation.Create(segment as QuadraticBezierSegment)) ==
+ null &&
+ (implementation =
+ PolyQuadraticBezierSegmentImplementation.Create(segment as PolyQuadraticBezierSegment)
+ ) == null)
+ throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture,
+ ExceptionStringTable.TypeNotSupported, new object[] {segment.GetType().FullName}));
+ return implementation;
+ }
+
+ public static PathSegmentImplementation Create(PathSegment segment, Point start)
+ {
+ var implementation = Create(segment);
+ implementation.Start = start;
+ return implementation;
+ }
+
+ public abstract void Flatten(IList points, double tolerance);
+
+ public abstract Point GetPoint(int index);
+
+ public abstract IEnumerable GetSimpleSegments();
+ }
+
+
+ private class PolyBezierSegmentImplementation : PathSegmentImplementation
+ {
+ private PolyBezierSegment _segment;
+
+ public override PathSegment ApplyTransform(GeneralTransform transform)
+ {
+ _segment.Points.ApplyTransform(transform);
+ return _segment;
+ }
+
+ public static PathSegmentImplementation Create(PolyBezierSegment source)
+ {
+ if (source != null) return new PolyBezierSegmentImplementation {_segment = source};
+ return null;
+ }
+
+ public override void Flatten(IList points, double tolerance)
+ {
+ var start = Start;
+ var num = _segment.Points.Count / 3 * 3;
+ for (var i = 0; i < num; i += 3)
+ {
+ Point[] controlPoints = {start, _segment.Points[i], _segment.Points[i + 1], _segment.Points[i + 2]};
+ var resultPolyline = new List();
+ BezierCurveFlattener.FlattenCubic(controlPoints, tolerance, resultPolyline, true);
+ points.AddRange(resultPolyline);
+ start = _segment.Points[i + 2];
+ }
+ }
+
+ public override Point GetPoint(int index)
+ {
+ var num = _segment.Points.Count / 3 * 3;
+ if (index < -1 || index > num - 1) throw new ArgumentOutOfRangeException(nameof(index));
+ if (index != -1) return _segment.Points[index];
+ return _segment.Points[num - 1];
+ }
+
+ public override IEnumerable GetSimpleSegments()
+ {
+ var start = Start;
+ IList points = _segment.Points;
+ var iteratorVariable2 = _segment.Points.Count / 3;
+ var iteratorVariable3 = 0;
+ while (true)
+ {
+ if (iteratorVariable3 >= iteratorVariable2) yield break;
+ var iteratorVariable4 = iteratorVariable3 * 3;
+ yield return SimpleSegment.Create(start, points[iteratorVariable4],
+ points[iteratorVariable4 + 1], points[iteratorVariable4 + 2]);
+ start = points[iteratorVariable4 + 2];
+ iteratorVariable3++;
+ }
+ }
+ }
+
+
+ private class PolyLineSegmentImplementation : PathSegmentImplementation
+ {
+ private PolyLineSegment _segment;
+
+ public override PathSegment ApplyTransform(GeneralTransform transform)
+ {
+ _segment.Points.ApplyTransform(transform);
+ return _segment;
+ }
+
+ public static PathSegmentImplementation Create(PolyLineSegment source)
+ {
+ if (source != null) return new PolyLineSegmentImplementation {_segment = source};
+ return null;
+ }
+
+ public override void Flatten(IList points, double tolerance)
+ {
+ points.AddRange(_segment.Points);
+ }
+
+ public override Point GetPoint(int index)
+ {
+ if (index < -1 || index > _segment.Points.Count - 1)
+ throw new ArgumentOutOfRangeException(nameof(index));
+ if (index != -1) return _segment.Points[index];
+ return _segment.Points.Last();
+ }
+
+ public override IEnumerable GetSimpleSegments()
+ {
+ var start = Start;
+ // ReSharper disable once GenericEnumeratorNotDisposed
+ var enumerator = _segment.Points.GetEnumerator();
+ while (enumerator.MoveNext())
+ {
+ var current = enumerator.Current;
+ yield return SimpleSegment.Create(start, current);
+ start = current;
+ }
+ }
+ }
+
+
+ private class PolyQuadraticBezierSegmentImplementation : PathSegmentImplementation
+ {
+ private PolyQuadraticBezierSegment _segment;
+
+ public override PathSegment ApplyTransform(GeneralTransform transform)
+ {
+ _segment.Points.ApplyTransform(transform);
+ return _segment;
+ }
+
+ public static PathSegmentImplementation Create(PolyQuadraticBezierSegment source)
+ {
+ if (source != null) return new PolyQuadraticBezierSegmentImplementation {_segment = source};
+ return null;
+ }
+
+ public override void Flatten(IList points, double tolerance)
+ {
+ var start = Start;
+ var num = _segment.Points.Count / 2 * 2;
+ for (var i = 0; i < num; i += 2)
+ {
+ Point[] controlPoints = {start, _segment.Points[i], _segment.Points[i + 1]};
+ var resultPolyline = new List();
+ BezierCurveFlattener.FlattenQuadratic(controlPoints, tolerance, resultPolyline, true);
+ points.AddRange(resultPolyline);
+ start = _segment.Points[i + 1];
+ }
+ }
+
+ public override Point GetPoint(int index)
+ {
+ var num = _segment.Points.Count / 2 * 2;
+ if (index < -1 || index > num - 1) throw new ArgumentOutOfRangeException(nameof(index));
+ if (index != -1) return _segment.Points[index];
+ return _segment.Points[num - 1];
+ }
+
+ public override IEnumerable GetSimpleSegments()
+ {
+ var start = Start;
+ IList points = _segment.Points;
+ var iteratorVariable2 = _segment.Points.Count / 2;
+ var iteratorVariable3 = 0;
+ while (true)
+ {
+ if (iteratorVariable3 >= iteratorVariable2) yield break;
+ var iteratorVariable4 = iteratorVariable3 * 2;
+ yield return SimpleSegment.Create(start, points[iteratorVariable4],
+ points[iteratorVariable4 + 1]);
+ start = points[iteratorVariable4 + 1];
+ iteratorVariable3++;
+ }
+ }
+ }
+
+
+ private class QuadraticBezierSegmentImplementation : PathSegmentImplementation
+ {
+ private QuadraticBezierSegment _segment;
+
+ public override PathSegment ApplyTransform(GeneralTransform transform)
+ {
+ _segment.Point1 = transform.Transform(_segment.Point1);
+ _segment.Point2 = transform.Transform(_segment.Point2);
+ return _segment;
+ }
+
+ public static PathSegmentImplementation Create(QuadraticBezierSegment source)
+ {
+ if (source != null) return new QuadraticBezierSegmentImplementation {_segment = source};
+ return null;
+ }
+
+ public override void Flatten(IList points, double tolerance)
+ {
+ Point[] controlPoints = {Start, _segment.Point1, _segment.Point2};
+ var resultPolyline = new List();
+ BezierCurveFlattener.FlattenQuadratic(controlPoints, tolerance, resultPolyline, true);
+ points.AddRange(resultPolyline);
+ }
+
+ public override Point GetPoint(int index)
+ {
+ if (index < -1 || index > 1) throw new ArgumentOutOfRangeException(nameof(index));
+ return index == 0 ? _segment.Point1 : _segment.Point2;
+ }
+
+ public override IEnumerable GetSimpleSegments()
+ {
+ yield return SimpleSegment.Create(Start, _segment.Point1, _segment.Point2);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/HandyControl/Expression/Drawing/PolylineData.cs b/HandyControl/Expression/Drawing/PolylineData.cs
new file mode 100644
index 00000000..7520e267
--- /dev/null
+++ b/HandyControl/Expression/Drawing/PolylineData.cs
@@ -0,0 +1,152 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows;
+
+namespace HandyControl.Expression.Drawing
+{
+ internal class PolylineData
+ {
+ private IList _accumulates;
+
+ private IList _angles;
+
+ private IList _lengths;
+
+ private IList _normals;
+
+ private double? _totalLength;
+
+ public PolylineData(IList points)
+ {
+ if (points == null) throw new ArgumentNullException(nameof(points));
+ if (points.Count <= 1) throw new ArgumentOutOfRangeException(nameof(points));
+ Points = points;
+ }
+
+ public IList AccumulatedLength =>
+ _accumulates ?? ComputeAccumulatedLength();
+
+ public IList Angles =>
+ _angles ?? ComputeAngles();
+
+ public int Count =>
+ Points.Count;
+
+ public bool IsClosed =>
+ Points[0] == Points.Last();
+
+ public IList Lengths =>
+ _lengths ?? ComputeLengths();
+
+ public IList Normals =>
+ _normals ?? ComputeNormals();
+
+ public IList Points { get; }
+
+ public double TotalLength
+ {
+ get
+ {
+ var length = _totalLength;
+ return length ?? ComputeTotalLength();
+ }
+ }
+
+ private IList ComputeAccumulatedLength()
+ {
+ _accumulates = new double[Count];
+ _accumulates[0] = 0.0;
+ for (var i = 1; i < Count; i++) _accumulates[i] = _accumulates[i - 1] + Lengths[i - 1];
+ _totalLength = _accumulates.Last();
+ return _accumulates;
+ }
+
+ private IList ComputeAngles()
+ {
+ _angles = new double[Count];
+ for (var i = 1; i < Count - 1; i++) _angles[i] = -GeometryHelper.Dot(Normals[i - 1], Normals[i]);
+ if (IsClosed)
+ {
+ var num2 = -GeometryHelper.Dot(Normals[0], Normals[Count - 2]);
+ _angles[0] = _angles[Count - 1] = num2;
+ }
+ else
+ {
+ _angles[0] = _angles[Count - 1] = 1.0;
+ }
+
+ return _angles;
+ }
+
+ private IList ComputeLengths()
+ {
+ _lengths = new double[Count];
+ for (var i = 0; i < Count; i++) _lengths[i] = Difference(i).Length;
+ return _lengths;
+ }
+
+ private IList ComputeNormals()
+ {
+ _normals = new Vector[Points.Count];
+ for (var i = 0; i < Count - 1; i++) _normals[i] = GeometryHelper.Normal(Points[i], Points[i + 1]);
+ _normals[Count - 1] = _normals[Count - 2];
+ return _normals;
+ }
+
+ private double ComputeTotalLength()
+ {
+ ComputeAccumulatedLength();
+ // ReSharper disable once PossibleInvalidOperationException
+ return _totalLength.Value;
+ }
+
+ public Vector Difference(int index)
+ {
+ var num = (index + 1) % Count;
+ return Points[num].Subtract(Points[index]);
+ }
+
+ public Vector SmoothNormal(int index, double fraction, double cornerRadius)
+ {
+ if (cornerRadius > 0.0)
+ {
+ var num = Lengths[index];
+ if (MathHelper.IsVerySmall(num))
+ {
+ var num2 = index - 1;
+ if (num2 < 0 && IsClosed) num2 = Count - 1;
+ var num3 = index + 1;
+ if (IsClosed && num3 >= Count - 1) num3 = 0;
+ if (num2 >= 0 && num3 < Count)
+ return GeometryHelper.Lerp(Normals[num3], Normals[num2], 0.5).Normalized();
+ return Normals[index];
+ }
+
+ var num4 = Math.Min(cornerRadius / num, 0.5);
+ if (fraction <= num4)
+ {
+ var num5 = index - 1;
+ if (IsClosed && num5 == -1) num5 = Count - 1;
+ if (num5 >= 0)
+ {
+ var alpha = (num4 - fraction) / (2.0 * num4);
+ return GeometryHelper.Lerp(Normals[index], Normals[num5], alpha).Normalized();
+ }
+ }
+ else if (fraction >= 1.0 - num4)
+ {
+ var num7 = index + 1;
+ if (IsClosed && num7 >= Count - 1) num7 = 0;
+ if (num7 < Count)
+ {
+ var num8 = (fraction + num4 - 1.0) / (2.0 * num4);
+ return GeometryHelper.Lerp(Normals[index], Normals[num7], num8).Normalized();
+ }
+ }
+ }
+
+ return Normals[index];
+ }
+ }
+}
\ No newline at end of file
diff --git a/HandyControl/Expression/Drawing/PolylineHelper.cs b/HandyControl/Expression/Drawing/PolylineHelper.cs
new file mode 100644
index 00000000..b82d8928
--- /dev/null
+++ b/HandyControl/Expression/Drawing/PolylineHelper.cs
@@ -0,0 +1,116 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace HandyControl.Expression.Drawing
+{
+ internal static class PolylineHelper
+ {
+ public static IEnumerable GetWrappedPolylines(IList lines,
+ ref double startArcLength)
+ {
+ var count = 0;
+ for (var i = 0; i < lines.Count; i++)
+ {
+ count = i;
+ startArcLength -= lines[i].TotalLength;
+ if (MathHelper.LessThanOrClose(startArcLength, 0.0)) break;
+ }
+
+ if (!MathHelper.LessThanOrClose(startArcLength, 0.0))
+ throw new ArgumentOutOfRangeException(nameof(startArcLength));
+ startArcLength += lines[count].TotalLength;
+ return lines.Skip(count).Concat(lines.Take(count + 1));
+ }
+
+ public static void PathMarch(PolylineData polyline, double startArcLength, double cornerThreshold,
+ Func stopCallback)
+ {
+ if (polyline == null) throw new ArgumentNullException(nameof(polyline));
+ var count = polyline.Count;
+ if (count <= 1) throw new ArgumentOutOfRangeException(nameof(polyline));
+ var flag = false;
+ var x = startArcLength;
+ var before = 0.0;
+ var index = 0;
+ var num5 = Math.Cos(cornerThreshold * 3.1415926535897931 / 180.0);
+ while (true)
+ {
+ var num6 = polyline.Lengths[index];
+ if (!MathHelper.IsFiniteDouble(x)) return;
+ if (MathHelper.IsVerySmall(x))
+ {
+ x = stopCallback(MarchLocation.Create(MarchStopReason.CompleteStep, index, before,
+ num6 - before, x));
+ flag = true;
+ }
+ else if (MathHelper.GreaterThan(x, 0.0))
+ {
+ if (MathHelper.LessThanOrClose(x + before, num6))
+ {
+ before += x;
+ x = stopCallback(MarchLocation.Create(MarchStopReason.CompleteStep, index, before,
+ num6 - before, 0.0));
+ flag = true;
+ }
+ else if (index < count - 2)
+ {
+ index++;
+ var num7 = num6 - before;
+ x -= num7;
+ before = 0.0;
+ // ReSharper disable once CompareOfFloatsByEqualityOperator
+ if (flag && num5 != 1.0 && polyline.Angles[index] > num5)
+ {
+ num6 = polyline.Lengths[index];
+ x = stopCallback(MarchLocation.Create(MarchStopReason.CornerPoint, index, before,
+ num6 - before, x));
+ }
+ }
+ else
+ {
+ var num8 = num6 - before;
+ x -= num8;
+ num6 = polyline.Lengths[index];
+ before = polyline.Lengths[index];
+ x = stopCallback(MarchLocation.Create(MarchStopReason.CompletePolyline, index, before,
+ num6 - before, x));
+ flag = true;
+ }
+ }
+ else if (MathHelper.LessThan(x, 0.0))
+ {
+ if (MathHelper.GreaterThanOrClose(x + before, 0.0))
+ {
+ before += x;
+ x = stopCallback(MarchLocation.Create(MarchStopReason.CompleteStep, index, before,
+ num6 - before, 0.0));
+ flag = true;
+ }
+ else if (index > 0)
+ {
+ index--;
+ x += before;
+ before = polyline.Lengths[index];
+ // ReSharper disable once CompareOfFloatsByEqualityOperator
+ if (flag && num5 != 1.0 && polyline.Angles[index + 1] > num5)
+ {
+ num6 = polyline.Lengths[index];
+ x = stopCallback(MarchLocation.Create(MarchStopReason.CornerPoint, index, before,
+ num6 - before, x));
+ }
+ }
+ else
+ {
+ x += before;
+ num6 = polyline.Lengths[index];
+ before = 0.0;
+ x = stopCallback(MarchLocation.Create(MarchStopReason.CompletePolyline, index, before,
+ num6 - before, x));
+ flag = true;
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/HandyControl/Expression/Drawing/RandomEngine.cs b/HandyControl/Expression/Drawing/RandomEngine.cs
new file mode 100644
index 00000000..f370bd0d
--- /dev/null
+++ b/HandyControl/Expression/Drawing/RandomEngine.cs
@@ -0,0 +1,45 @@
+using System;
+
+namespace HandyControl.Expression.Drawing
+{
+ internal class RandomEngine
+ {
+ private double? _anotherSample;
+
+ private Random _random;
+
+ public RandomEngine(long seed) => Initialize(seed);
+
+ private double Gaussian()
+ {
+ double num;
+ double num2;
+ double num3;
+ if (_anotherSample.HasValue)
+ {
+ var num4 = _anotherSample.Value;
+ _anotherSample = null;
+ return num4;
+ }
+
+ do
+ {
+ num2 = 2.0 * Uniform() - 1.0;
+ num3 = 2.0 * Uniform() - 1.0;
+ num = num2 * num2 + num3 * num3;
+ } while (num >= 1.0);
+
+ var num5 = Math.Sqrt(-2.0 * Math.Log(num) / num);
+ _anotherSample = num2 * num5;
+ return num3 * num5;
+ }
+
+ private void Initialize(long seed) => _random = new Random((int) seed);
+
+ public double NextGaussian(double mean, double variance) => Gaussian() * variance + mean;
+
+ public double NextUniform(double min, double max) => Uniform() * (max - min) + min;
+
+ private double Uniform() => _random.NextDouble();
+ }
+}
\ No newline at end of file
diff --git a/HandyControl/Expression/Drawing/SimpleSegment.cs b/HandyControl/Expression/Drawing/SimpleSegment.cs
new file mode 100644
index 00000000..0c117f0f
--- /dev/null
+++ b/HandyControl/Expression/Drawing/SimpleSegment.cs
@@ -0,0 +1,76 @@
+using System.Collections.Generic;
+using System.Windows;
+
+namespace HandyControl.Expression.Drawing
+{
+ internal class SimpleSegment
+ {
+ public enum SegmentType
+ {
+ Line,
+
+ CubicBeizer
+ }
+
+
+ private SimpleSegment()
+ {
+ }
+
+ public Point[] Points { get; private set; }
+
+ public SegmentType Type { get; private set; }
+
+ public static SimpleSegment Create(Point point0, Point point1)
+ {
+ var segment = new SimpleSegment
+ {
+ Type = SegmentType.Line,
+ Points = new[] {point0, point1}
+ };
+ return segment;
+ }
+
+ public static SimpleSegment Create(Point point0, Point point1, Point point2)
+ {
+ var point = GeometryHelper.Lerp(point0, point1, 0.66666666666666663);
+ var point3 = GeometryHelper.Lerp(point1, point2, 0.33333333333333331);
+ var segment = new SimpleSegment
+ {
+ Type = SegmentType.CubicBeizer,
+ Points = new[] {point0, point, point3, point2}
+ };
+ return segment;
+ }
+
+ public static SimpleSegment Create(Point point0, Point point1, Point point2, Point point3)
+ {
+ var segment = new SimpleSegment
+ {
+ Type = SegmentType.CubicBeizer,
+ Points = new[] {point0, point1, point2, point3}
+ };
+ return segment;
+ }
+
+ public void Flatten(IList resultPolyline, double tolerance, IList resultParameters)
+ {
+ switch (Type)
+ {
+ case SegmentType.Line:
+ resultPolyline.Add(Points[1]);
+ if (resultParameters == null) break;
+ resultParameters.Add(1.0);
+ return;
+
+ case SegmentType.CubicBeizer:
+ BezierCurveFlattener.FlattenCubic(Points, tolerance, resultPolyline, true,
+ resultParameters);
+ break;
+
+ default:
+ return;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/HandyControl/Expression/Media/ArcGeometrySource.cs b/HandyControl/Expression/Media/ArcGeometrySource.cs
new file mode 100644
index 00000000..8270314a
--- /dev/null
+++ b/HandyControl/Expression/Media/ArcGeometrySource.cs
@@ -0,0 +1,470 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+using System.Windows;
+using System.Windows.Media;
+using HandyControl.Expression.Drawing;
+
+namespace HandyControl.Expression.Media
+{
+ internal class ArcGeometrySource : GeometrySource
+ {
+ private double _absoluteThickness;
+
+ private double _relativeThickness;
+
+ private static bool AreCloseEnough(double angleA, double angleB)
+ {
+ return Math.Abs(angleA - angleB) < 0.001;
+ }
+
+ internal static double[] ComputeAngleRanges(double radiusX, double radiusY, double intersect,
+ double start, double end)
+ {
+ var values = new List
+ {
+ start,
+ end,
+ intersect,
+ 180.0 - intersect,
+ 180.0 + intersect,
+ 360.0 - intersect,
+ 360.0 + intersect,
+ 540.0 - intersect,
+ 540.0 + intersect,
+ 720.0 - intersect
+ };
+ values.Sort();
+ var index = values.IndexOf(start);
+ var num2 = values.IndexOf(end);
+ if (num2 == index)
+ {
+ num2++;
+ }
+ else if (start < end)
+ {
+ IncreaseDuplicatedIndex(values, ref index);
+ DecreaseDuplicatedIndex(values, ref num2);
+ }
+ else if (start > end)
+ {
+ DecreaseDuplicatedIndex(values, ref index);
+ IncreaseDuplicatedIndex(values, ref num2);
+ }
+
+ var list = new List();
+ if (index < num2)
+ for (var i = index; i <= num2; i++)
+ list.Add(values[i]);
+ else
+ for (var j = index; j >= num2; j--)
+ list.Add(values[j]);
+ var num5 = EnsureFirstQuadrant((list[0] + list[1]) / 2.0);
+ if (radiusX < radiusY && num5 < intersect || radiusX > radiusY && num5 > intersect)
+ list.RemoveAt(0);
+ if (list.Count % 2 == 1) list.RemoveLast();
+ if (list.Count == 0)
+ {
+ var num6 = Math.Min(index, num2) - 1;
+ if (num6 < 0) num6 = Math.Max(index, num2) + 1;
+ list.Add(values[num6]);
+ list.Add(values[num6]);
+ }
+
+ return list.ToArray();
+ }
+
+ protected override Rect ComputeLogicalBounds(Rect layoutBounds, IGeometrySourceParameters parameters)
+ {
+ return GeometryHelper.GetStretchBound(base.ComputeLogicalBounds(layoutBounds, parameters),
+ parameters.Stretch, new Size(1.0, 1.0));
+ }
+
+ private static IList ComputeOneInnerCurve(double start, double end, Rect bounds, double offset)
+ {
+ var num = bounds.Width / 2.0;
+ var num2 = bounds.Height / 2.0;
+ var point = bounds.Center();
+ start = start * 3.1415926535897931 / 180.0;
+ end = end * 3.1415926535897931 / 180.0;
+ var num3 = 0.17453292519943295;
+ var num4 = (int) Math.Ceiling(Math.Abs(end - start) / num3);
+ num4 = Math.Max(2, num4);
+ var list = new List(num4);
+ var list2 = new List(num4);
+ var lhs = new Point();
+ var item = new Point();
+ var vector = new Vector();
+ var vector2 = new Vector();
+ var vector3 = new Vector();
+ var vector4 = new Vector();
+ for (var i = 0; i < num4; i++)
+ {
+ var a = MathHelper.Lerp(start, end, i / (double) (num4 - 1));
+ var num10 = Math.Sin(a);
+ var num11 = Math.Cos(a);
+ lhs.X = point.X + num * num10;
+ lhs.Y = point.Y - num2 * num11;
+ vector.X = num * num11;
+ vector.Y = num2 * num10;
+ vector2.X = -num2 * num10;
+ vector2.Y = num * num11;
+ var d = num2 * num2 * num10 * num10 + num * num * num11 * num11;
+ var num6 = Math.Sqrt(d);
+ var num7 = 2.0 * num10 * num11 * (num2 * num2 - num * num);
+ vector3.X = -num2 * num11;
+ vector3.Y = -num * num10;
+ item.X = lhs.X + offset * vector2.X / num6;
+ item.Y = lhs.Y + offset * vector2.Y / num6;
+ vector4.X = vector.X + offset / num6 * (vector3.X - 0.5 * vector2.X / d * num7);
+ vector4.Y = vector.Y + offset / num6 * (vector3.Y - 0.5 * vector2.Y / d * num7);
+ list.Add(item);
+ list2.Add(-vector4.Normalized());
+ }
+
+ var list3 = new List(num4 * 3 + 1)
+ {
+ list[0]
+ };
+ for (var j = 1; j < num4; j++)
+ {
+ lhs = list[j - 1];
+ item = list[j];
+ var num13 = GeometryHelper.Distance(lhs, item) / 3.0;
+ list3.Add(lhs + list2[j - 1] * num13);
+ list3.Add(item - list2[j] * num13);
+ list3.Add(item);
+ }
+
+ return list3;
+ }
+
+ private static void DecreaseDuplicatedIndex(IList values, ref int index)
+ {
+ // ReSharper disable once CompareOfFloatsByEqualityOperator
+ while (index > 0 && values[index] == values[index - 1]) index--;
+ }
+
+ internal static double EnsureFirstQuadrant(double angle)
+ {
+ angle = Math.Abs(angle % 180.0);
+ if (angle <= 90.0) return angle;
+ return 180.0 - angle;
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static Size GetArcSize(Rect bound)
+ {
+ return new Size(bound.Width / 2.0, bound.Height / 2.0);
+ }
+
+ private static void IncreaseDuplicatedIndex(IList values, ref int index)
+ {
+ // ReSharper disable once CompareOfFloatsByEqualityOperator
+ while (index < values.Count - 1 && values[index] == values[index + 1]) index++;
+ }
+
+ internal static double InnerCurveSelfIntersect(double radiusX, double radiusY, double thickness)
+ {
+ var angleA = 0.0;
+ var angleB = 1.5707963267948966;
+ var flag = radiusX <= radiusY;
+ var vector = new Vector();
+ while (!AreCloseEnough(angleA, angleB))
+ {
+ var d = (angleA + angleB) / 2.0;
+ var num4 = Math.Cos(d);
+ var num5 = Math.Sin(d);
+ vector.X = radiusY * num5;
+ vector.Y = radiusX * num4;
+ vector.Normalize();
+ if (flag)
+ {
+ var num6 = radiusX * num5 - vector.X * thickness;
+ if (num6 > 0.0)
+ angleB = d;
+ else if (num6 < 0.0) angleA = d;
+ }
+ else
+ {
+ var num7 = radiusY * num4 - vector.Y * thickness;
+ if (num7 < 0.0)
+ angleB = d;
+ else if (num7 > 0.0) angleA = d;
+ }
+ }
+
+ var num8 = (angleA + angleB) / 2.0;
+ if (AreCloseEnough(num8, 0.0)) return 0.0;
+ if (!AreCloseEnough(num8, 1.5707963267948966)) return num8 * 180.0 / 3.1415926535897931;
+ return 90.0;
+ }
+
+ private static double NormalizeAngle(double degree)
+ {
+ if (degree < 0.0 || degree > 360.0)
+ {
+ degree = degree % 360.0;
+ if (degree < 0.0) degree += 360.0;
+ }
+
+ return degree;
+ }
+
+ private void NormalizeThickness(IArcGeometrySourceParameters parameters)
+ {
+ var num = LogicalBounds.Width / 2.0;
+ var num2 = LogicalBounds.Height / 2.0;
+ var rhs = Math.Min(num, num2);
+ var arcThickness = parameters.ArcThickness;
+ if (parameters.ArcThicknessUnit == UnitType.Pixel)
+ arcThickness = MathHelper.SafeDivide(arcThickness, rhs, 0.0);
+ _relativeThickness = MathHelper.EnsureRange(arcThickness, 0.0, 1.0);
+ _absoluteThickness = rhs * _relativeThickness;
+ }
+
+ private bool SyncPieceWiseInnerCurves(PathFigure figure, int index, ref Point firstPoint,
+ params double[] angles)
+ {
+ var flag = false;
+ var length = angles.Length;
+ var logicalBounds = LogicalBounds;
+ var absoluteThickness = _absoluteThickness;
+ flag |= figure.Segments.EnsureListCount(index + length / 2, () => new PolyBezierSegment());
+ for (var i = 0; i < length / 2; i++)
+ {
+ var points = ComputeOneInnerCurve(angles[i * 2], angles[i * 2 + 1], logicalBounds,
+ absoluteThickness);
+ if (i == 0) firstPoint = points[0];
+ flag |= PathSegmentHelper.SyncPolyBezierSegment(figure.Segments, index + i, points, 1,
+ points.Count - 1);
+ }
+
+ return flag;
+ }
+
+ [SuppressMessage("ReSharper", "ConditionIsAlwaysTrueOrFalse")]
+ [SuppressMessage("ReSharper", "CompareOfFloatsByEqualityOperator")]
+ protected override bool UpdateCachedGeometry(IArcGeometrySourceParameters parameters)
+ {
+ var flag = false;
+ NormalizeThickness(parameters);
+ var relativeMode = parameters.ArcThicknessUnit == UnitType.Percent;
+ var flag3 = MathHelper.AreClose(parameters.StartAngle, parameters.EndAngle);
+ var angle = NormalizeAngle(parameters.StartAngle);
+ var end = NormalizeAngle(parameters.EndAngle);
+ if (end < angle) end += 360.0;
+ var isFilled = _relativeThickness == 1.0;
+ var flag5 = _relativeThickness == 0.0;
+ if (flag3) return flag | UpdateZeroAngleGeometry(relativeMode, angle);
+ if (MathHelper.IsVerySmall((end - angle) % 360.0))
+ {
+ if (flag5 || isFilled) return flag | UpdateEllipseGeometry(isFilled);
+ return flag | UpdateFullRingGeometry(relativeMode);
+ }
+
+ if (isFilled) return flag | UpdatePieGeometry(angle, end);
+ if (flag5) return flag | UpdateOpenArcGeometry(angle, end);
+ return flag | UpdateRingArcGeometry(relativeMode, angle, end);
+ }
+
+ private bool UpdateEllipseGeometry(bool isFilled)
+ {
+ var flag = false;
+ var y = MathHelper.Lerp(LogicalBounds.Top, LogicalBounds.Bottom, 0.5);
+ var point = new Point(LogicalBounds.Left, y);
+ var point2 = new Point(LogicalBounds.Right, y);
+ flag |= GeometryHelper.EnsureGeometryType(out var geometry, ref CachedGeometry,
+ () => new PathGeometry());
+ flag |= geometry.Figures.EnsureListCount(1, () => new PathFigure());
+ var dependencyObject = geometry.Figures[0];
+ flag |= dependencyObject.SetIfDifferent(PathFigure.IsClosedProperty, true);
+ flag |= dependencyObject.SetIfDifferent(PathFigure.IsFilledProperty, isFilled);
+ flag |= dependencyObject.Segments.EnsureListCount(2, () => new ArcSegment());
+ flag |= dependencyObject.SetIfDifferent(PathFigure.StartPointProperty, point);
+ flag |= GeometryHelper.EnsureSegmentType(out var segment, dependencyObject.Segments, 0,
+ () => new ArcSegment());
+ flag |= GeometryHelper.EnsureSegmentType(out var segment2, dependencyObject.Segments, 1,
+ () => new ArcSegment());
+ var size = new Size(LogicalBounds.Width / 2.0, LogicalBounds.Height / 2.0);
+ flag |= segment.SetIfDifferent(ArcSegment.IsLargeArcProperty, false);
+ flag |= segment.SetIfDifferent(ArcSegment.SizeProperty, size);
+ flag |= segment.SetIfDifferent(ArcSegment.SweepDirectionProperty, SweepDirection.Clockwise);
+ flag |= segment.SetIfDifferent(ArcSegment.PointProperty, point2);
+ flag |= segment2.SetIfDifferent(ArcSegment.IsLargeArcProperty, false);
+ flag |= segment2.SetIfDifferent(ArcSegment.SizeProperty, size);
+ flag |= segment2.SetIfDifferent(ArcSegment.SweepDirectionProperty, SweepDirection.Clockwise);
+ return flag | segment2.SetIfDifferent(ArcSegment.PointProperty, point);
+ }
+
+ private bool UpdateFullRingGeometry(bool relativeMode)
+ {
+ var flag = false;
+ flag |= GeometryHelper.EnsureGeometryType(out var geometry, ref CachedGeometry,
+ () => new PathGeometry());
+ flag |= geometry.SetIfDifferent(PathGeometry.FillRuleProperty, FillRule.EvenOdd);
+ flag |= geometry.Figures.EnsureListCount(2, () => new PathFigure());
+ flag |= PathFigureHelper.SyncEllipseFigure(geometry.Figures[0], LogicalBounds,
+ SweepDirection.Clockwise);
+ var logicalBounds = LogicalBounds;
+ var num = logicalBounds.Width / 2.0;
+ var num2 = logicalBounds.Height / 2.0;
+ if (relativeMode || MathHelper.AreClose(num, num2))
+ {
+ var bounds = LogicalBounds.Resize(1.0 - _relativeThickness);
+ return flag | PathFigureHelper.SyncEllipseFigure(geometry.Figures[1], bounds,
+ SweepDirection.Counterclockwise);
+ }
+
+ flag |= geometry.Figures[1].SetIfDifferent(PathFigure.IsClosedProperty, true);
+ flag |= geometry.Figures[1].SetIfDifferent(PathFigure.IsFilledProperty, true);
+ var firstPoint = new Point();
+ var intersect = InnerCurveSelfIntersect(num, num2, _absoluteThickness);
+ var angles = ComputeAngleRanges(num, num2, intersect, 360.0, 0.0);
+ flag |= SyncPieceWiseInnerCurves(geometry.Figures[1], 0, ref firstPoint, angles);
+ return flag | geometry.Figures[1].SetIfDifferent(PathFigure.StartPointProperty, firstPoint);
+ }
+
+ private bool UpdateOpenArcGeometry(double start, double end)
+ {
+ PathFigure figure;
+ ArcSegment segment;
+ var flag = false;
+ var cachedGeometry = CachedGeometry as PathGeometry;
+ if (cachedGeometry == null || cachedGeometry.Figures.Count != 1 ||
+ (figure = cachedGeometry.Figures[0]).Segments.Count != 1 ||
+ (segment = figure.Segments[0] as ArcSegment) == null)
+ {
+ figure = new PathFigure();
+ CachedGeometry = new PathGeometry
+ {
+ Figures = {figure}
+ };
+ figure.Segments.Add(segment = new ArcSegment());
+ figure.IsClosed = false;
+ segment.SweepDirection = SweepDirection.Clockwise;
+ flag = true;
+ }
+
+ flag |= figure.SetIfDifferent(PathFigure.StartPointProperty,
+ GeometryHelper.GetArcPoint(start, LogicalBounds));
+ flag |= figure.SetIfDifferent(PathFigure.IsFilledProperty, false);
+ flag |= segment.SetIfDifferent(ArcSegment.PointProperty,
+ GeometryHelper.GetArcPoint(end, LogicalBounds));
+ flag |= segment.SetIfDifferent(ArcSegment.SizeProperty, GetArcSize(LogicalBounds));
+ return flag | segment.SetIfDifferent(ArcSegment.IsLargeArcProperty, end - start > 180.0);
+ }
+
+ private bool UpdatePieGeometry(double start, double end)
+ {
+ PathFigure figure;
+ ArcSegment segment;
+ LineSegment segment2;
+ var flag = false;
+ var cachedGeometry = CachedGeometry as PathGeometry;
+ if (cachedGeometry == null || cachedGeometry.Figures.Count != 1 ||
+ (figure = cachedGeometry.Figures[0]).Segments.Count != 2 ||
+ (segment = figure.Segments[0] as ArcSegment) == null ||
+ (segment2 = figure.Segments[1] as LineSegment) == null)
+ {
+ figure = new PathFigure();
+ CachedGeometry = new PathGeometry
+ {
+ Figures = {figure}
+ };
+ figure.Segments.Add(segment = new ArcSegment());
+ figure.Segments.Add(segment2 = new LineSegment());
+ figure.IsClosed = true;
+ segment.SweepDirection = SweepDirection.Clockwise;
+ flag = true;
+ }
+
+ flag |= figure.SetIfDifferent(PathFigure.StartPointProperty,
+ GeometryHelper.GetArcPoint(start, LogicalBounds));
+ flag |= segment.SetIfDifferent(ArcSegment.PointProperty,
+ GeometryHelper.GetArcPoint(end, LogicalBounds));
+ flag |= segment.SetIfDifferent(ArcSegment.SizeProperty, GetArcSize(LogicalBounds));
+ flag |= segment.SetIfDifferent(ArcSegment.IsLargeArcProperty, end - start > 180.0);
+ return flag | segment2.SetIfDifferent(LineSegment.PointProperty, LogicalBounds.Center());
+ }
+
+ private bool UpdateRingArcGeometry(bool relativeMode, double start, double end)
+ {
+ var flag = false;
+ flag |= GeometryHelper.EnsureGeometryType(out var geometry, ref CachedGeometry,
+ () => new PathGeometry());
+ flag |= geometry.SetIfDifferent(PathGeometry.FillRuleProperty, FillRule.Nonzero);
+ flag |= geometry.Figures.EnsureListCount(1, () => new PathFigure());
+ var dependencyObject = geometry.Figures[0];
+ flag |= dependencyObject.SetIfDifferent(PathFigure.IsClosedProperty, true);
+ flag |= dependencyObject.SetIfDifferent(PathFigure.IsFilledProperty, true);
+ flag |= dependencyObject.SetIfDifferent(PathFigure.StartPointProperty,
+ GeometryHelper.GetArcPoint(start, LogicalBounds));
+ flag |= dependencyObject.Segments.EnsureListCountAtLeast(3, () => new ArcSegment());
+ flag |= GeometryHelper.EnsureSegmentType(out var segment, dependencyObject.Segments, 0,
+ () => new ArcSegment());
+ flag |= segment.SetIfDifferent(ArcSegment.PointProperty,
+ GeometryHelper.GetArcPoint(end, LogicalBounds));
+ flag |= segment.SetIfDifferent(ArcSegment.SizeProperty,
+ new Size(LogicalBounds.Width / 2.0, LogicalBounds.Height / 2.0));
+ flag |= segment.SetIfDifferent(ArcSegment.IsLargeArcProperty, end - start > 180.0);
+ flag |= segment.SetIfDifferent(ArcSegment.SweepDirectionProperty, SweepDirection.Clockwise);
+ flag |= GeometryHelper.EnsureSegmentType(out var segment2, dependencyObject.Segments, 1,
+ () => new LineSegment());
+ var logicalBounds = LogicalBounds;
+ var num = logicalBounds.Width / 2.0;
+ var num2 = logicalBounds.Height / 2.0;
+ if (relativeMode || MathHelper.AreClose(num, num2))
+ {
+ var bound = LogicalBounds.Resize(1.0 - _relativeThickness);
+ flag |= segment2.SetIfDifferent(LineSegment.PointProperty,
+ GeometryHelper.GetArcPoint(end, bound));
+ flag |= dependencyObject.Segments.EnsureListCount(3, () => new ArcSegment());
+ flag |= GeometryHelper.EnsureSegmentType(out var segment3, dependencyObject.Segments, 2,
+ () => new ArcSegment());
+ flag |= segment3.SetIfDifferent(ArcSegment.PointProperty,
+ GeometryHelper.GetArcPoint(start, bound));
+ flag |= segment3.SetIfDifferent(ArcSegment.SizeProperty, GetArcSize(bound));
+ flag |= segment3.SetIfDifferent(ArcSegment.IsLargeArcProperty, end - start > 180.0);
+ return flag | segment3.SetIfDifferent(ArcSegment.SweepDirectionProperty,
+ SweepDirection.Counterclockwise);
+ }
+
+ var firstPoint = new Point();
+ var intersect = InnerCurveSelfIntersect(num, num2, _absoluteThickness);
+ var angles = ComputeAngleRanges(num, num2, intersect, end, start);
+ flag |= SyncPieceWiseInnerCurves(dependencyObject, 2, ref firstPoint, angles);
+ return flag | segment2.SetIfDifferent(LineSegment.PointProperty, firstPoint);
+ }
+
+ private bool UpdateZeroAngleGeometry(bool relativeMode, double angle)
+ {
+ Point point2;
+ var flag = false;
+ var arcPoint = GeometryHelper.GetArcPoint(angle, LogicalBounds);
+ var logicalBounds = LogicalBounds;
+ var num = logicalBounds.Width / 2.0;
+ var num2 = logicalBounds.Height / 2.0;
+ if (relativeMode || MathHelper.AreClose(num, num2))
+ {
+ var bound = LogicalBounds.Resize(1.0 - _relativeThickness);
+ point2 = GeometryHelper.GetArcPoint(angle, bound);
+ }
+ else
+ {
+ var intersect = InnerCurveSelfIntersect(num, num2, _absoluteThickness);
+ var numArray = ComputeAngleRanges(num, num2, intersect, angle, angle);
+ var a = numArray[0] * 3.1415926535897931 / 180.0;
+ var vector = new Vector(num2 * Math.Sin(a), -num * Math.Cos(a));
+ point2 = GeometryHelper.GetArcPoint(numArray[0], LogicalBounds) -
+ vector.Normalized() * _absoluteThickness;
+ }
+
+ flag |= GeometryHelper.EnsureGeometryType(out var geometry, ref CachedGeometry,
+ () => new LineGeometry());
+ flag |= geometry.SetIfDifferent(LineGeometry.StartPointProperty, arcPoint);
+ return flag | geometry.SetIfDifferent(LineGeometry.EndPointProperty, point2);
+ }
+ }
+}
\ No newline at end of file
diff --git a/HandyControl/Expression/Media/DrawingPropertyChangedEventArgs.cs b/HandyControl/Expression/Media/DrawingPropertyChangedEventArgs.cs
new file mode 100644
index 00000000..7bcb3406
--- /dev/null
+++ b/HandyControl/Expression/Media/DrawingPropertyChangedEventArgs.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace HandyControl.Expression.Media
+{
+ internal class DrawingPropertyChangedEventArgs : EventArgs
+ {
+ public bool IsAnimated { get; set; }
+
+ public DrawingPropertyMetadata Metadata { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/HandyControl/Expression/Media/DrawingPropertyMetadata.cs b/HandyControl/Expression/Media/DrawingPropertyMetadata.cs
new file mode 100644
index 00000000..f3fb8776
--- /dev/null
+++ b/HandyControl/Expression/Media/DrawingPropertyMetadata.cs
@@ -0,0 +1,77 @@
+using System;
+using System.Windows;
+
+namespace HandyControl.Expression.Media
+{
+ internal class DrawingPropertyMetadata : FrameworkPropertyMetadata
+ {
+ private DrawingPropertyMetadataOptions options;
+
+ private PropertyChangedCallback propertyChangedCallback;
+
+ public static event EventHandler DrawingPropertyChanged;
+
+ static DrawingPropertyMetadata()
+ {
+ DrawingPropertyChanged += delegate (object sender, DrawingPropertyChangedEventArgs args) {
+ IShape shape = sender as IShape;
+ if ((shape != null) && args.Metadata.AffectsRender)
+ {
+ InvalidateGeometryReasons reasons = InvalidateGeometryReasons.PropertyChanged;
+ if (args.IsAnimated)
+ {
+ reasons |= InvalidateGeometryReasons.IsAnimated;
+ }
+ shape.InvalidateGeometry(reasons);
+ }
+ };
+ }
+
+ public DrawingPropertyMetadata(object defaultValue) : this(defaultValue, DrawingPropertyMetadataOptions.None, null)
+ {
+ }
+
+ public DrawingPropertyMetadata(PropertyChangedCallback propertyChangedCallback) : this(DependencyProperty.UnsetValue, DrawingPropertyMetadataOptions.None, propertyChangedCallback)
+ {
+ }
+
+ private DrawingPropertyMetadata(DrawingPropertyMetadataOptions options, object defaultValue) : base(defaultValue, (FrameworkPropertyMetadataOptions)options)
+ {
+ }
+
+ public DrawingPropertyMetadata(object defaultValue, DrawingPropertyMetadataOptions options) : this(defaultValue, options, null)
+ {
+ }
+
+ public DrawingPropertyMetadata(object defaultValue, DrawingPropertyMetadataOptions options, PropertyChangedCallback propertyChangedCallback) : base(defaultValue, (FrameworkPropertyMetadataOptions)options, AttachCallback(defaultValue, options, propertyChangedCallback))
+ {
+ }
+
+ private static PropertyChangedCallback AttachCallback(object defaultValue, DrawingPropertyMetadataOptions options, PropertyChangedCallback propertyChangedCallback)
+ {
+ DrawingPropertyMetadata metadata = new DrawingPropertyMetadata(options, defaultValue)
+ {
+ options = options,
+ propertyChangedCallback = propertyChangedCallback
+ };
+ return new PropertyChangedCallback(metadata.InternalCallback);
+ }
+
+ private void InternalCallback(DependencyObject sender, DependencyPropertyChangedEventArgs e)
+ {
+ if (DrawingPropertyChanged != null)
+ {
+ DrawingPropertyChangedEventArgs args = new DrawingPropertyChangedEventArgs
+ {
+ Metadata = this,
+ IsAnimated = DependencyPropertyHelper.GetValueSource(sender, e.Property).IsAnimated
+ };
+ DrawingPropertyChanged(sender, args);
+ }
+ if (this.propertyChangedCallback != null)
+ {
+ this.propertyChangedCallback(sender, e);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/HandyControl/Expression/Media/DrawingPropertyMetadataOptions.cs b/HandyControl/Expression/Media/DrawingPropertyMetadataOptions.cs
new file mode 100644
index 00000000..82114f86
--- /dev/null
+++ b/HandyControl/Expression/Media/DrawingPropertyMetadataOptions.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace HandyControl.Expression.Media
+{
+ [Flags]
+ internal enum DrawingPropertyMetadataOptions
+ {
+ AffectsMeasure = 1,
+ AffectsRender = 0x10,
+ None = 0
+ }
+}
\ No newline at end of file
diff --git a/HandyControl/Expression/Media/GeometryEffect.cs b/HandyControl/Expression/Media/GeometryEffect.cs
new file mode 100644
index 00000000..84804c13
--- /dev/null
+++ b/HandyControl/Expression/Media/GeometryEffect.cs
@@ -0,0 +1,172 @@
+using System;
+using System.ComponentModel;
+using System.Windows;
+using System.Windows.Media;
+using System.Windows.Threading;
+
+namespace HandyControl.Expression.Media
+{
+ [TypeConverter(typeof(GeometryEffectConverter))]
+ public abstract class GeometryEffect : Freezable
+ {
+ // ReSharper disable once InconsistentNaming
+ private static GeometryEffect defaultGeometryEffect;
+
+ public static readonly DependencyProperty GeometryEffectProperty =
+ DependencyProperty.RegisterAttached("GeometryEffect", typeof(GeometryEffect),
+ typeof(GeometryEffect),
+ new DrawingPropertyMetadata(DefaultGeometryEffect,
+ DrawingPropertyMetadataOptions.AffectsRender, OnGeometryEffectChanged));
+
+ protected Geometry CachedGeometry;
+
+ private bool _effectInvalidated;
+
+ static GeometryEffect()
+ {
+ DrawingPropertyMetadata.DrawingPropertyChanged +=
+ delegate(object sender, DrawingPropertyChangedEventArgs args)
+ {
+ if (sender is GeometryEffect effect && args.Metadata.AffectsRender)
+ effect.InvalidateGeometry(InvalidateGeometryReasons.PropertyChanged);
+ };
+ }
+
+ public static GeometryEffect DefaultGeometryEffect =>
+ defaultGeometryEffect ?? (defaultGeometryEffect = new NoGeometryEffect());
+
+ public Geometry OutputGeometry =>
+ CachedGeometry;
+
+ protected internal DependencyObject Parent { get; private set; }
+
+ protected internal virtual void Attach(DependencyObject obj)
+ {
+ if (Parent != null) Detach();
+ _effectInvalidated = true;
+ CachedGeometry = null;
+ if (InvalidateParent(obj)) Parent = obj;
+ }
+
+ public new GeometryEffect CloneCurrentValue()
+ {
+ return (GeometryEffect) base.CloneCurrentValue();
+ }
+
+ protected override Freezable CreateInstanceCore()
+ {
+ return (Freezable) Activator.CreateInstance(GetType());
+ }
+
+ protected abstract GeometryEffect DeepCopy();
+
+ protected internal virtual void Detach()
+ {
+ _effectInvalidated = true;
+ CachedGeometry = null;
+ if (Parent != null)
+ {
+ InvalidateParent(Parent);
+ Parent = null;
+ }
+ }
+
+ public abstract bool Equals(GeometryEffect geometryEffect);
+
+ public static GeometryEffect GetGeometryEffect(DependencyObject obj)
+ {
+ return (GeometryEffect) obj.GetValue(GeometryEffectProperty);
+ }
+
+ public bool InvalidateGeometry(InvalidateGeometryReasons reasons)
+ {
+ if (_effectInvalidated) return false;
+ _effectInvalidated = true;
+ if (reasons != InvalidateGeometryReasons.ParentInvalidated) InvalidateParent(Parent);
+ return true;
+ }
+
+ private static bool InvalidateParent(DependencyObject parent)
+ {
+ if (parent is IShape shape)
+ {
+ shape.InvalidateGeometry(InvalidateGeometryReasons.ChildInvalidated);
+ return true;
+ }
+
+ if (parent is GeometryEffect effect)
+ {
+ effect.InvalidateGeometry(InvalidateGeometryReasons.ChildInvalidated);
+ return true;
+ }
+
+ return false;
+ }
+
+ private static void OnGeometryEffectChanged(DependencyObject obj,
+ DependencyPropertyChangedEventArgs e)
+ {
+ var oldValue = e.OldValue as GeometryEffect;
+ var newValue = e.NewValue as GeometryEffect;
+ if (!Equals(oldValue, newValue))
+ {
+ if (oldValue != null && obj.Equals(oldValue.Parent)) oldValue.Detach();
+ if (newValue != null)
+ {
+ if (newValue.Parent != null)
+ {
+ obj.Dispatcher.BeginInvoke(new Action(() =>
+ {
+ var effect = newValue.CloneCurrentValue();
+ obj.SetValue(GeometryEffectProperty, effect);
+ }), DispatcherPriority.Send, null);
+ }
+ else
+ {
+ newValue.Attach(obj);
+ }
+ }
+ }
+ }
+
+ public bool ProcessGeometry(Geometry input)
+ {
+ var flag = false;
+ if (_effectInvalidated)
+ {
+ flag |= UpdateCachedGeometry(input);
+ _effectInvalidated = false;
+ }
+
+ return flag;
+ }
+
+ public static void SetGeometryEffect(DependencyObject obj, GeometryEffect value)
+ {
+ obj.SetValue(GeometryEffectProperty, value);
+ }
+
+ protected abstract bool UpdateCachedGeometry(Geometry input);
+
+
+ private class NoGeometryEffect : GeometryEffect
+ {
+ protected override GeometryEffect DeepCopy()
+ {
+ return new NoGeometryEffect();
+ }
+
+ public override bool Equals(GeometryEffect geometryEffect)
+ {
+ if (geometryEffect != null) return geometryEffect is NoGeometryEffect;
+ return true;
+ }
+
+ protected override bool UpdateCachedGeometry(Geometry input)
+ {
+ CachedGeometry = input;
+ return false;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/HandyControl/Expression/Media/GeometryEffectConverter.cs b/HandyControl/Expression/Media/GeometryEffectConverter.cs
new file mode 100644
index 00000000..6dbfa57a
--- /dev/null
+++ b/HandyControl/Expression/Media/GeometryEffectConverter.cs
@@ -0,0 +1,59 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Globalization;
+
+namespace HandyControl.Expression.Media
+{
+ public sealed class GeometryEffectConverter : TypeConverter
+ {
+ private static readonly Dictionary RegisteredEffects;
+
+ static GeometryEffectConverter()
+ {
+ var dictionary = new Dictionary
+ {
+ {
+ "None",
+ GeometryEffect.DefaultGeometryEffect
+ },
+ {
+ "Sketch",
+ new SketchGeometryEffect()
+ }
+ };
+ RegisteredEffects = dictionary;
+ }
+
+ public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
+ {
+ return typeof(string).IsAssignableFrom(sourceType);
+ }
+
+ public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
+ {
+ return typeof(string).IsAssignableFrom(destinationType);
+ }
+
+ public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
+ {
+ if (value is string key && RegisteredEffects.TryGetValue(key, out var effect))
+ return effect.CloneCurrentValue();
+ return null;
+ }
+
+ public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value,
+ Type destinationType)
+ {
+ if (typeof(string).IsAssignableFrom(destinationType))
+ {
+ if (value is string) return value;
+ foreach (var pair in RegisteredEffects)
+ if (pair.Value?.Equals(value as GeometryEffect) ?? value == null)
+ return pair.Key;
+ }
+
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/HandyControl/Expression/Media/GeometrySource!1.cs b/HandyControl/Expression/Media/GeometrySource!1.cs
new file mode 100644
index 00000000..0edc7fe1
--- /dev/null
+++ b/HandyControl/Expression/Media/GeometrySource!1.cs
@@ -0,0 +1,88 @@
+using System.Windows;
+using System.Windows.Media;
+using HandyControl.Expression.Drawing;
+
+namespace HandyControl.Expression.Media
+{
+ public abstract class GeometrySource : IGeometrySource
+ where TParameters : IGeometrySourceParameters
+ {
+ private bool _geometryInvalidated;
+
+ protected Geometry CachedGeometry;
+
+ public bool InvalidateGeometry(InvalidateGeometryReasons reasons)
+ {
+ if ((reasons & InvalidateGeometryReasons.TemplateChanged) != 0) CachedGeometry = null;
+ if (!_geometryInvalidated)
+ {
+ _geometryInvalidated = true;
+ return true;
+ }
+
+ return false;
+ }
+
+ public bool UpdateGeometry(IGeometrySourceParameters parameters, Rect layoutBounds)
+ {
+ var force = false;
+ if (parameters is TParameters parameters1)
+ {
+ var rect = ComputeLogicalBounds(layoutBounds, parameters1);
+ force |= LayoutBounds != layoutBounds || LogicalBounds != rect;
+ if (_geometryInvalidated || force)
+ {
+ LayoutBounds = layoutBounds;
+ LogicalBounds = rect;
+ force |= UpdateCachedGeometry(parameters1);
+ force |= ApplyGeometryEffect(parameters1, force);
+ }
+ }
+
+ _geometryInvalidated = false;
+ return force;
+ }
+
+ public Geometry Geometry { get; private set; }
+
+ public Rect LayoutBounds { get; private set; }
+
+ public Rect LogicalBounds { get; private set; }
+
+ private bool ApplyGeometryEffect(IGeometrySourceParameters parameters, bool force)
+ {
+ var flag = false;
+ var cachedGeometry = CachedGeometry;
+ var geometryEffect = parameters.GetGeometryEffect();
+ if (geometryEffect != null)
+ {
+ if (force)
+ {
+ flag = true;
+ geometryEffect.InvalidateGeometry(InvalidateGeometryReasons.ParentInvalidated);
+ }
+
+ if (geometryEffect.ProcessGeometry(CachedGeometry))
+ {
+ flag = true;
+ cachedGeometry = geometryEffect.OutputGeometry;
+ }
+ }
+
+ if (!Equals(Geometry, cachedGeometry))
+ {
+ flag = true;
+ Geometry = cachedGeometry;
+ }
+
+ return flag;
+ }
+
+ protected virtual Rect ComputeLogicalBounds(Rect layoutBounds, IGeometrySourceParameters parameters)
+ {
+ return GeometryHelper.Inflate(layoutBounds, -parameters.GetHalfStrokeThickness());
+ }
+
+ protected abstract bool UpdateCachedGeometry(TParameters parameters);
+ }
+}
\ No newline at end of file
diff --git a/HandyControl/Expression/Media/IArcGeometrySourceParameters.cs b/HandyControl/Expression/Media/IArcGeometrySourceParameters.cs
new file mode 100644
index 00000000..f4536306
--- /dev/null
+++ b/HandyControl/Expression/Media/IArcGeometrySourceParameters.cs
@@ -0,0 +1,13 @@
+namespace HandyControl.Expression.Media
+{
+ public interface IArcGeometrySourceParameters : IGeometrySourceParameters
+ {
+ double ArcThickness { get; }
+
+ UnitType ArcThicknessUnit { get; }
+
+ double EndAngle { get; }
+
+ double StartAngle { get; }
+ }
+}
\ No newline at end of file
diff --git a/HandyControl/Expression/Media/IGeometrySource.cs b/HandyControl/Expression/Media/IGeometrySource.cs
new file mode 100644
index 00000000..faaa3e8d
--- /dev/null
+++ b/HandyControl/Expression/Media/IGeometrySource.cs
@@ -0,0 +1,17 @@
+using System.Windows;
+
+namespace HandyControl.Expression.Media
+{
+ public interface IGeometrySource
+ {
+ bool InvalidateGeometry(InvalidateGeometryReasons reasons);
+
+ bool UpdateGeometry(IGeometrySourceParameters parameters, Rect layoutBounds);
+
+ System.Windows.Media.Geometry Geometry { get; }
+
+ Rect LayoutBounds { get; }
+
+ Rect LogicalBounds { get; }
+ }
+}
\ No newline at end of file
diff --git a/HandyControl/Expression/Media/IGeometrySourceExtensions.cs b/HandyControl/Expression/Media/IGeometrySourceExtensions.cs
new file mode 100644
index 00000000..1e616211
--- /dev/null
+++ b/HandyControl/Expression/Media/IGeometrySourceExtensions.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Windows;
+
+namespace HandyControl.Expression.Media
+{
+ internal static class GeometrySourceExtensions
+ {
+ public static GeometryEffect GetGeometryEffect(this IGeometrySourceParameters parameters)
+ {
+ if (parameters is DependencyObject obj2)
+ {
+ var geometryEffect = GeometryEffect.GetGeometryEffect(obj2);
+ if (geometryEffect != null && obj2.Equals(geometryEffect.Parent)) return geometryEffect;
+ }
+
+ return null;
+ }
+
+ public static double GetHalfStrokeThickness(this IGeometrySourceParameters parameter)
+ {
+ if (parameter.Stroke != null)
+ {
+ var strokeThickness = parameter.StrokeThickness;
+ if (!double.IsNaN(strokeThickness) && !double.IsInfinity(strokeThickness))
+ return Math.Abs(strokeThickness) / 2.0;
+ }
+
+ return 0.0;
+ }
+ }
+}
\ No newline at end of file
diff --git a/HandyControl/Expression/Media/IGeometrySourceParameters.cs b/HandyControl/Expression/Media/IGeometrySourceParameters.cs
new file mode 100644
index 00000000..5354b688
--- /dev/null
+++ b/HandyControl/Expression/Media/IGeometrySourceParameters.cs
@@ -0,0 +1,13 @@
+using System.Windows.Media;
+
+namespace HandyControl.Expression.Media
+{
+ public interface IGeometrySourceParameters
+ {
+ Stretch Stretch { get; }
+
+ Brush Stroke { get; }
+
+ double StrokeThickness { get; }
+ }
+}
\ No newline at end of file
diff --git a/HandyControl/Expression/Media/IShape.cs b/HandyControl/Expression/Media/IShape.cs
new file mode 100644
index 00000000..1f0fd37a
--- /dev/null
+++ b/HandyControl/Expression/Media/IShape.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Windows;
+using System.Windows.Media;
+
+namespace HandyControl.Expression.Media
+{
+ public interface IShape
+ {
+ event EventHandler RenderedGeometryChanged;
+
+ void InvalidateGeometry(InvalidateGeometryReasons reasons);
+
+ Brush Fill { get; set; }
+
+ Thickness GeometryMargin { get; }
+
+ Geometry RenderedGeometry { get; }
+
+ Stretch Stretch { get; set; }
+
+ Brush Stroke { get; set; }
+
+ double StrokeThickness { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/HandyControl/Expression/Media/InvalidateGeometryReasons.cs b/HandyControl/Expression/Media/InvalidateGeometryReasons.cs
new file mode 100644
index 00000000..32799de5
--- /dev/null
+++ b/HandyControl/Expression/Media/InvalidateGeometryReasons.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace HandyControl.Expression.Media
+{
+ [Flags]
+ public enum InvalidateGeometryReasons
+ {
+ ChildInvalidated = 4,
+ IsAnimated = 2,
+ ParentInvalidated = 8,
+ PropertyChanged = 1,
+ TemplateChanged = 0x10
+ }
+}
\ No newline at end of file
diff --git a/HandyControl/Expression/Media/SketchGeometryEffect.cs b/HandyControl/Expression/Media/SketchGeometryEffect.cs
new file mode 100644
index 00000000..0969ca1a
--- /dev/null
+++ b/HandyControl/Expression/Media/SketchGeometryEffect.cs
@@ -0,0 +1,135 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Windows;
+using System.Windows.Media;
+using HandyControl.Expression.Drawing;
+
+namespace HandyControl.Expression.Media
+{
+ public sealed class SketchGeometryEffect : GeometryEffect
+ {
+ private readonly long _randomSeed = DateTime.Now.Ticks;
+
+ protected override GeometryEffect DeepCopy()
+ {
+ return new SketchGeometryEffect();
+ }
+
+ private static void DisturbPoints(RandomEngine random, double scale, IList points,
+ IList normals)
+ {
+ var count = points.Count;
+ for (var i = 1; i < count; i++)
+ {
+ var num3 = random.NextGaussian(0.0, 1.0 * scale);
+ var num4 = random.NextUniform(-0.5, 0.5) * scale;
+ var point = points[i];
+ var vector = normals[i];
+ var vector2 = normals[i];
+ var point2 = points[i];
+ var vector3 = normals[i];
+ var vector4 = normals[i];
+ points[i] = new Point(point.X + vector.X * num4 - vector2.Y * num3,
+ point2.Y + vector3.X * num3 + vector4.Y * num4);
+ }
+ }
+
+ public override bool Equals(GeometryEffect geometryEffect)
+ {
+ return geometryEffect is SketchGeometryEffect;
+ }
+
+ private IEnumerable GetEffectiveSegments(PathFigure pathFigure)
+ {
+ var startPoint = pathFigure.StartPoint;
+ foreach (var iteratorVariable1 in pathFigure.AllSegments())
+ foreach (var iteratorVariable2 in iteratorVariable1.PathSegment.GetSimpleSegments(
+ iteratorVariable1.StartPoint))
+ {
+ yield return iteratorVariable2;
+ startPoint = iteratorVariable2.Points.Last();
+ }
+
+ if (pathFigure.IsClosed) yield return SimpleSegment.Create(startPoint, pathFigure.StartPoint);
+ }
+
+ [SuppressMessage("ReSharper", "ConditionIsAlwaysTrueOrFalse")]
+ protected override bool UpdateCachedGeometry(Geometry input)
+ {
+ var flag = false;
+ var inputPath = input.AsPathGeometry();
+ if (inputPath != null) return flag | UpdateSketchGeometry(inputPath);
+ CachedGeometry = input;
+ return flag;
+ }
+
+ private bool UpdateSketchGeometry(PathGeometry inputPath)
+ {
+ var flag = false;
+ flag |= GeometryHelper.EnsureGeometryType(out var geometry, ref CachedGeometry,
+ () => new PathGeometry());
+ flag |= geometry.Figures.EnsureListCount(inputPath.Figures.Count, () => new PathFigure());
+ var random = new RandomEngine(_randomSeed);
+ for (var i = 0; i < inputPath.Figures.Count; i++)
+ {
+ var pathFigure = inputPath.Figures[i];
+ var isClosed = pathFigure.IsClosed;
+ var isFilled = pathFigure.IsFilled;
+ if (pathFigure.Segments.Count == 0)
+ {
+ flag |= geometry.Figures[i]
+ .SetIfDifferent(PathFigure.StartPointProperty, pathFigure.StartPoint);
+ flag |= geometry.Figures[i].Segments.EnsureListCount(0);
+ }
+ else
+ {
+ var list = new List(pathFigure.Segments.Count * 3);
+ foreach (var segment in GetEffectiveSegments(pathFigure))
+ {
+ var resultPolyline = new List
+ {
+ segment.Points[0]
+ };
+ segment.Flatten(resultPolyline, 0.0, null);
+ var polyline = new PolylineData(resultPolyline);
+ if (resultPolyline.Count > 1 && polyline.TotalLength > 4.0)
+ {
+ var a = polyline.TotalLength / 8.0;
+ var sampleCount = (int) Math.Max(2.0, Math.Ceiling(a));
+ var interval = polyline.TotalLength / sampleCount;
+ var scale = interval / 8.0;
+ var samplePoints = new List(sampleCount);
+ var sampleNormals = new List(sampleCount);
+ var sampleIndex = 0;
+ PolylineHelper.PathMarch(polyline, 0.0, 0.0, delegate(MarchLocation location)
+ {
+ if (location.Reason == MarchStopReason.CompletePolyline) return double.NaN;
+ if (location.Reason != MarchStopReason.CompleteStep) return location.Remain;
+ if (sampleIndex++ == sampleCount) return double.NaN;
+ samplePoints.Add(location.GetPoint(polyline.Points));
+ sampleNormals.Add(location.GetNormal(polyline));
+ return interval;
+ });
+ DisturbPoints(random, scale, samplePoints, sampleNormals);
+ list.AddRange(samplePoints);
+ }
+ else
+ {
+ list.AddRange(resultPolyline);
+ list.RemoveLast();
+ }
+ }
+
+ if (!isClosed) list.Add(pathFigure.Segments.Last().GetLastPoint());
+ flag |= PathFigureHelper.SyncPolylineFigure(geometry.Figures[i], list, isClosed,
+ isFilled);
+ }
+ }
+
+ if (flag) CachedGeometry = PathGeometryHelper.FixPathGeometryBoundary(CachedGeometry);
+ return flag;
+ }
+ }
+}
\ No newline at end of file
diff --git a/HandyControl/Expression/Media/UnitType.cs b/HandyControl/Expression/Media/UnitType.cs
new file mode 100644
index 00000000..4aa8d7da
--- /dev/null
+++ b/HandyControl/Expression/Media/UnitType.cs
@@ -0,0 +1,8 @@
+namespace HandyControl.Expression.Media
+{
+ public enum UnitType
+ {
+ Pixel,
+ Percent
+ }
+}
\ No newline at end of file
diff --git a/HandyControl/Expression/Shapes/Arc.cs b/HandyControl/Expression/Shapes/Arc.cs
new file mode 100644
index 00000000..7f34b945
--- /dev/null
+++ b/HandyControl/Expression/Shapes/Arc.cs
@@ -0,0 +1,64 @@
+using System.Windows;
+using System.Windows.Media;
+using HandyControl.Expression.Media;
+
+namespace HandyControl.Expression.Shapes
+{
+ public sealed class Arc : PrimitiveShape, IArcGeometrySourceParameters
+ {
+ public static readonly DependencyProperty ArcThicknessProperty =
+ DependencyProperty.Register("ArcThickness", typeof(double), typeof(Arc),
+ new DrawingPropertyMetadata(0.0, DrawingPropertyMetadataOptions.AffectsRender));
+
+ public static readonly DependencyProperty ArcThicknessUnitProperty =
+ DependencyProperty.Register("ArcThicknessUnit", typeof(UnitType), typeof(Arc),
+ new DrawingPropertyMetadata(UnitType.Pixel, DrawingPropertyMetadataOptions.AffectsRender));
+
+ public static readonly DependencyProperty EndAngleProperty = DependencyProperty.Register("EndAngle",
+ typeof(double), typeof(Arc),
+ new DrawingPropertyMetadata(90.0, DrawingPropertyMetadataOptions.AffectsRender));
+
+ public static readonly DependencyProperty StartAngleProperty =
+ DependencyProperty.Register("StartAngle", typeof(double), typeof(Arc),
+ new DrawingPropertyMetadata(0.0, DrawingPropertyMetadataOptions.AffectsRender));
+
+ public double ArcThickness
+ {
+ get =>
+ (double) GetValue(ArcThicknessProperty);
+ set => SetValue(ArcThicknessProperty, value);
+ }
+
+ public UnitType ArcThicknessUnit
+ {
+ get =>
+ (UnitType) GetValue(ArcThicknessUnitProperty);
+ set => SetValue(ArcThicknessUnitProperty, value);
+ }
+
+ public double EndAngle
+ {
+ get =>
+ (double) GetValue(EndAngleProperty);
+ set => SetValue(EndAngleProperty, value);
+ }
+
+ Stretch IGeometrySourceParameters.Stretch => Stretch;
+
+ Brush IGeometrySourceParameters.Stroke => Stroke;
+
+ double IGeometrySourceParameters.StrokeThickness => StrokeThickness;
+
+ public double StartAngle
+ {
+ get =>
+ (double) GetValue(StartAngleProperty);
+ set => SetValue(StartAngleProperty, value);
+ }
+
+ protected override IGeometrySource CreateGeometrySource()
+ {
+ return new ArcGeometrySource();
+ }
+ }
+}
\ No newline at end of file
diff --git a/HandyControl/Expression/Shapes/PrimitiveShape.cs b/HandyControl/Expression/Shapes/PrimitiveShape.cs
new file mode 100644
index 00000000..fd217485
--- /dev/null
+++ b/HandyControl/Expression/Shapes/PrimitiveShape.cs
@@ -0,0 +1,79 @@
+using System;
+using System.Windows;
+using System.Windows.Media;
+using System.Windows.Shapes;
+using HandyControl.Expression.Drawing;
+using HandyControl.Expression.Media;
+
+namespace HandyControl.Expression.Shapes
+{
+ public abstract class PrimitiveShape : Shape, IGeometrySourceParameters, IShape
+ {
+ private IGeometrySource _geometrySource;
+
+ static PrimitiveShape()
+ {
+ StretchProperty.OverrideMetadata(typeof(PrimitiveShape),
+ new DrawingPropertyMetadata(Stretch.Fill, DrawingPropertyMetadataOptions.AffectsRender));
+ StrokeThicknessProperty.OverrideMetadata(typeof(PrimitiveShape),
+ new DrawingPropertyMetadata(1.0, DrawingPropertyMetadataOptions.AffectsRender));
+ }
+
+ protected sealed override Geometry DefiningGeometry =>
+ GeometrySource.Geometry ?? Geometry.Empty;
+
+ private IGeometrySource GeometrySource => _geometrySource ?? (_geometrySource = CreateGeometrySource());
+
+ Stretch IGeometrySourceParameters.Stretch => Stretch;
+
+ Brush IGeometrySourceParameters.Stroke => Stroke;
+
+ double IGeometrySourceParameters.StrokeThickness => StrokeThickness;
+
+ public event EventHandler RenderedGeometryChanged;
+
+ public void InvalidateGeometry(InvalidateGeometryReasons reasons)
+ {
+ if (GeometrySource.InvalidateGeometry(reasons)) InvalidateArrange();
+ }
+
+ public Thickness GeometryMargin => GeometrySource.LogicalBounds.Subtract(RenderedGeometry.Bounds);
+
+ Brush IShape.Fill
+ {
+ get => Fill;
+ set => Fill = value;
+ }
+
+ Stretch IShape.Stretch
+ {
+ get => Stretch;
+ set => Stretch = value;
+ }
+
+ Brush IShape.Stroke
+ {
+ get => Stroke;
+ set => Stroke = value;
+ }
+
+ double IShape.StrokeThickness
+ {
+ get => StrokeThickness;
+ set => StrokeThickness = value;
+ }
+
+ protected override Size ArrangeOverride(Size finalSize)
+ {
+ if (GeometrySource.UpdateGeometry(this, finalSize.Bounds())) RealizeGeometry();
+ base.ArrangeOverride(finalSize);
+ return finalSize;
+ }
+
+ protected abstract IGeometrySource CreateGeometrySource();
+
+ protected override Size MeasureOverride(Size availableSize) => new Size(base.StrokeThickness, base.StrokeThickness);
+
+ private void RealizeGeometry() => RenderedGeometryChanged?.Invoke(this, EventArgs.Empty);
+ }
+}
\ No newline at end of file
diff --git a/HandyControl/HandyControl.csproj b/HandyControl/HandyControl.csproj
index 3a4967d2..38f500c3 100644
--- a/HandyControl/HandyControl.csproj
+++ b/HandyControl/HandyControl.csproj
@@ -47,6 +47,7 @@
+
@@ -61,7 +62,7 @@
-
+
@@ -71,6 +72,38 @@
True
ResourceToken.tt
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/HandyControl/Themes/Styles/Base/ProgressBarBaseStyle.xaml b/HandyControl/Themes/Styles/Base/ProgressBarBaseStyle.xaml
index 5f1655f7..51997587 100644
--- a/HandyControl/Themes/Styles/Base/ProgressBarBaseStyle.xaml
+++ b/HandyControl/Themes/Styles/Base/ProgressBarBaseStyle.xaml
@@ -1,6 +1,11 @@
+ xmlns:controls="clr-namespace:HandyControl.Controls"
+ xmlns:shapes="clr-namespace:HandyControl.Expression.Shapes">
+
+
+
+
@@ -18,18 +23,16 @@
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
@@ -51,11 +54,9 @@
-
+
-
-
-
+
@@ -68,4 +69,24 @@
+
+
\ No newline at end of file
diff --git a/HandyControl/Themes/Styles/ProgressBar.xaml b/HandyControl/Themes/Styles/ProgressBar.xaml
index 3b0c40e5..f6916e85 100644
--- a/HandyControl/Themes/Styles/ProgressBar.xaml
+++ b/HandyControl/Themes/Styles/ProgressBar.xaml
@@ -1,5 +1,6 @@
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:controls="clr-namespace:HandyControl.Controls">
@@ -175,6 +176,26 @@
+
+
+
+
+
+
+
+
+
+