add CircleProgressBar
thanks to @ghost1372 suggestion
This commit is contained in:
1 parent
fb71862464
commit
4aa4afd292
49 files changed
+3697
-40
No files matched your search
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -284,7 +284,7 @@ namespace HandyControl.Controls
|
||||
/// <param name="growlInfo"></param>
|
||||
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
|
||||
/// <param name="growlInfo"></param>
|
||||
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
|
||||
/// <param name="growlInfo"></param>
|
||||
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
|
||||
/// <param name="growlInfo"></param>
|
||||
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
|
||||
/// <param name="growlInfo"></param>
|
||||
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
|
||||
/// <param name="growlInfo"></param>
|
||||
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;
|
||||
|
||||
+3
-5
@@ -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
|
||||
{
|
||||
@@ -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<Point> resultPolyline, ICollection<double> 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<Point> resultPolyline, ICollection<double> 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<Point> resultPolyline, bool skipFirstPoint, ICollection<double> 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<Point> resultPolyline, bool skipFirstPoint, ICollection<double> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<T>(this ICollection<T> collection, IEnumerable<T> newItems)
|
||||
{
|
||||
if (collection == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(collection));
|
||||
}
|
||||
|
||||
if (collection is List<T> 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<T>(this IList<T> list, int count, Func<T> 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<T> 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<T>(this IList<T> list, int count, Func<T> 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<T> 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<T>(this IList<T> list)
|
||||
{
|
||||
list.RemoveAt(list.Count - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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<Point> 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<T>(out T result, IList<PathSegment> list, int index,
|
||||
Func<T> factory) where T : PathSegment
|
||||
{
|
||||
result = list[index] as T;
|
||||
if (result == null)
|
||||
{
|
||||
list[index] = result = factory();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static bool EnsureGeometryType<T>(out T result, ref Geometry value, Func<T> factory)
|
||||
where T : Geometry
|
||||
{
|
||||
result = value as T;
|
||||
if (result == null)
|
||||
{
|
||||
value = result = factory();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<double> 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<Point> points)
|
||||
{
|
||||
return GeometryHelper.Lerp(points[Index], points[Index + 1], Ratio);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace HandyControl.Expression.Drawing
|
||||
{
|
||||
internal enum MarchStopReason
|
||||
{
|
||||
CompleteStep,
|
||||
CompletePolyline,
|
||||
CornerPoint
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<PathSegmentData> 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<Point> 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<Point>() : 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<Point> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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<Point> 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<Point> 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<Point> 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<Point> 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<SimpleSegment> 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<Point> 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<Point> 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<Point> 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<SimpleSegment> GetSimpleSegments()
|
||||
{
|
||||
var segment = ArcToBezierSegments(_segment, Start);
|
||||
if (segment != null) return segment.GetSimpleSegments(Start);
|
||||
return Enumerable.Empty<SimpleSegment>();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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<Point> points, double tolerance)
|
||||
{
|
||||
Point[] controlPoints = {Start, _segment.Point1, _segment.Point2, _segment.Point3};
|
||||
var resultPolyline = new List<Point>();
|
||||
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<SimpleSegment> 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<Point> 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<SimpleSegment> 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<Point> points, double tolerance);
|
||||
|
||||
public abstract Point GetPoint(int index);
|
||||
|
||||
public abstract IEnumerable<SimpleSegment> 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<Point> 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<Point>();
|
||||
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<SimpleSegment> GetSimpleSegments()
|
||||
{
|
||||
var start = Start;
|
||||
IList<Point> 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<Point> 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<SimpleSegment> 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<Point> 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<Point>();
|
||||
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<SimpleSegment> GetSimpleSegments()
|
||||
{
|
||||
var start = Start;
|
||||
IList<Point> 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<Point> points, double tolerance)
|
||||
{
|
||||
Point[] controlPoints = {Start, _segment.Point1, _segment.Point2};
|
||||
var resultPolyline = new List<Point>();
|
||||
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<SimpleSegment> GetSimpleSegments()
|
||||
{
|
||||
yield return SimpleSegment.Create(Start, _segment.Point1, _segment.Point2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<double> _accumulates;
|
||||
|
||||
private IList<double> _angles;
|
||||
|
||||
private IList<double> _lengths;
|
||||
|
||||
private IList<Vector> _normals;
|
||||
|
||||
private double? _totalLength;
|
||||
|
||||
public PolylineData(IList<Point> points)
|
||||
{
|
||||
if (points == null) throw new ArgumentNullException(nameof(points));
|
||||
if (points.Count <= 1) throw new ArgumentOutOfRangeException(nameof(points));
|
||||
Points = points;
|
||||
}
|
||||
|
||||
public IList<double> AccumulatedLength =>
|
||||
_accumulates ?? ComputeAccumulatedLength();
|
||||
|
||||
public IList<double> Angles =>
|
||||
_angles ?? ComputeAngles();
|
||||
|
||||
public int Count =>
|
||||
Points.Count;
|
||||
|
||||
public bool IsClosed =>
|
||||
Points[0] == Points.Last();
|
||||
|
||||
public IList<double> Lengths =>
|
||||
_lengths ?? ComputeLengths();
|
||||
|
||||
public IList<Vector> Normals =>
|
||||
_normals ?? ComputeNormals();
|
||||
|
||||
public IList<Point> Points { get; }
|
||||
|
||||
public double TotalLength
|
||||
{
|
||||
get
|
||||
{
|
||||
var length = _totalLength;
|
||||
return length ?? ComputeTotalLength();
|
||||
}
|
||||
}
|
||||
|
||||
private IList<double> 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<double> 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<double> ComputeLengths()
|
||||
{
|
||||
_lengths = new double[Count];
|
||||
for (var i = 0; i < Count; i++) _lengths[i] = Difference(i).Length;
|
||||
return _lengths;
|
||||
}
|
||||
|
||||
private IList<Vector> 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];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace HandyControl.Expression.Drawing
|
||||
{
|
||||
internal static class PolylineHelper
|
||||
{
|
||||
public static IEnumerable<PolylineData> GetWrappedPolylines(IList<PolylineData> 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<MarchLocation, double> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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<Point> resultPolyline, double tolerance, IList<double> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<IArcGeometrySourceParameters>
|
||||
{
|
||||
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<double>
|
||||
{
|
||||
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<double>();
|
||||
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<Point> 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<Point>(num4);
|
||||
var list2 = new List<Vector>(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<Point>(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<double> 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<double> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace HandyControl.Expression.Media
|
||||
{
|
||||
internal class DrawingPropertyChangedEventArgs : EventArgs
|
||||
{
|
||||
public bool IsAnimated { get; set; }
|
||||
|
||||
public DrawingPropertyMetadata Metadata { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -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<DrawingPropertyChangedEventArgs> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
|
||||
namespace HandyControl.Expression.Media
|
||||
{
|
||||
[Flags]
|
||||
internal enum DrawingPropertyMetadataOptions
|
||||
{
|
||||
AffectsMeasure = 1,
|
||||
AffectsRender = 0x10,
|
||||
None = 0
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<string, GeometryEffect> RegisteredEffects;
|
||||
|
||||
static GeometryEffectConverter()
|
||||
{
|
||||
var dictionary = new Dictionary<string, GeometryEffect>
|
||||
{
|
||||
{
|
||||
"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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using HandyControl.Expression.Drawing;
|
||||
|
||||
namespace HandyControl.Expression.Media
|
||||
{
|
||||
public abstract class GeometrySource<TParameters> : 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace HandyControl.Expression.Media
|
||||
{
|
||||
public interface IArcGeometrySourceParameters : IGeometrySourceParameters
|
||||
{
|
||||
double ArcThickness { get; }
|
||||
|
||||
UnitType ArcThicknessUnit { get; }
|
||||
|
||||
double EndAngle { get; }
|
||||
|
||||
double StartAngle { get; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace HandyControl.Expression.Media
|
||||
{
|
||||
public interface IGeometrySourceParameters
|
||||
{
|
||||
Stretch Stretch { get; }
|
||||
|
||||
Brush Stroke { get; }
|
||||
|
||||
double StrokeThickness { get; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
|
||||
namespace HandyControl.Expression.Media
|
||||
{
|
||||
[Flags]
|
||||
public enum InvalidateGeometryReasons
|
||||
{
|
||||
ChildInvalidated = 4,
|
||||
IsAnimated = 2,
|
||||
ParentInvalidated = 8,
|
||||
PropertyChanged = 1,
|
||||
TemplateChanged = 0x10
|
||||
}
|
||||
}
|
||||
@@ -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<Point> points,
|
||||
IList<Vector> 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<SimpleSegment> 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<Point>(pathFigure.Segments.Count * 3);
|
||||
foreach (var segment in GetEffectiveSegments(pathFigure))
|
||||
{
|
||||
var resultPolyline = new List<Point>
|
||||
{
|
||||
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<Point>(sampleCount);
|
||||
var sampleNormals = new List<Vector>(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace HandyControl.Expression.Media
|
||||
{
|
||||
public enum UnitType
|
||||
{
|
||||
Pixel,
|
||||
Percent
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -47,6 +47,7 @@
|
||||
<Compile Include="Controls\Button\ContextMenuToggleButton.cs" />
|
||||
<Compile Include="Controls\Carousel.cs" />
|
||||
<Compile Include="Controls\CirclePanel.cs" />
|
||||
<Compile Include="Controls\CircleProgressBar.cs" />
|
||||
<Compile Include="Controls\Clock\Clock.cs" />
|
||||
<Compile Include="Controls\Clock\ClockRadioButton.cs" />
|
||||
<Compile Include="Controls\Growl.cs" />
|
||||
@@ -61,7 +62,7 @@
|
||||
<Compile Include="Controls\Loading\LoadingBase.cs" />
|
||||
<Compile Include="Controls\Loading\LoadingCircle.cs" />
|
||||
<Compile Include="Controls\Loading\LoadingLine.cs" />
|
||||
<Compile Include="Controls\Slider\SliderCanvas.cs" />
|
||||
<Compile Include="Controls\InternalCanvas.cs" />
|
||||
<Compile Include="Data\Enum\InfoType.cs" />
|
||||
<Compile Include="Data\Enum\SkinType.cs" />
|
||||
<Compile Include="Data\Enum\TipPlacement.cs" />
|
||||
@@ -71,6 +72,38 @@
|
||||
<DesignTime>True</DesignTime>
|
||||
<DependentUpon>ResourceToken.tt</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Expression\Drawing\BezierCurveFlattener.cs" />
|
||||
<Compile Include="Expression\Drawing\CommonExtensions.cs" />
|
||||
<Compile Include="Expression\Drawing\ExceptionStringTable.cs" />
|
||||
<Compile Include="Expression\Drawing\GeometryHelper.cs" />
|
||||
<Compile Include="Expression\Drawing\MarchLocation.cs" />
|
||||
<Compile Include="Expression\Drawing\MarchStopReason.cs" />
|
||||
<Compile Include="Expression\Drawing\MathHelper.cs" />
|
||||
<Compile Include="Expression\Drawing\PathFigureHelper.cs" />
|
||||
<Compile Include="Expression\Drawing\PathGeometryHelper.cs" />
|
||||
<Compile Include="Expression\Drawing\PathSegmentData.cs" />
|
||||
<Compile Include="Expression\Drawing\PathSegmentHelper.cs" />
|
||||
<Compile Include="Expression\Drawing\PolylineData.cs" />
|
||||
<Compile Include="Expression\Drawing\PolylineHelper.cs" />
|
||||
<Compile Include="Expression\Drawing\RandomEngine.cs" />
|
||||
<Compile Include="Expression\Drawing\SimpleSegment.cs" />
|
||||
<Compile Include="Expression\Media\ArcGeometrySource.cs" />
|
||||
<Compile Include="Expression\Media\DrawingPropertyChangedEventArgs.cs" />
|
||||
<Compile Include="Expression\Media\DrawingPropertyMetadata.cs" />
|
||||
<Compile Include="Expression\Media\DrawingPropertyMetadataOptions.cs" />
|
||||
<Compile Include="Expression\Media\GeometryEffect.cs" />
|
||||
<Compile Include="Expression\Media\GeometryEffectConverter.cs" />
|
||||
<Compile Include="Expression\Media\GeometrySource!1.cs" />
|
||||
<Compile Include="Expression\Media\IArcGeometrySourceParameters.cs" />
|
||||
<Compile Include="Expression\Media\IGeometrySource.cs" />
|
||||
<Compile Include="Expression\Media\IGeometrySourceExtensions.cs" />
|
||||
<Compile Include="Expression\Media\IGeometrySourceParameters.cs" />
|
||||
<Compile Include="Expression\Media\InvalidateGeometryReasons.cs" />
|
||||
<Compile Include="Expression\Media\IShape.cs" />
|
||||
<Compile Include="Expression\Media\SketchGeometryEffect.cs" />
|
||||
<Compile Include="Expression\Media\UnitType.cs" />
|
||||
<Compile Include="Expression\Shapes\Arc.cs" />
|
||||
<Compile Include="Expression\Shapes\PrimitiveShape.cs" />
|
||||
<Compile Include="Tools\SingleOpenHelper.cs" />
|
||||
<Compile Include="Controls\Steps\StepBar.cs" />
|
||||
<Compile Include="Controls\Steps\StepItem.cs" />
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="clr-namespace:HandyControl.Controls">
|
||||
xmlns:controls="clr-namespace:HandyControl.Controls"
|
||||
xmlns:shapes="clr-namespace:HandyControl.Expression.Shapes">
|
||||
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="../../Basic/Converters.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<LinearGradientBrush x:Key="ProgressBarIndicatorAnimatedFill" StartPoint="0,0" EndPoint="1,0">
|
||||
<GradientStop Color="#00FFFFFF" Offset="0"/>
|
||||
@@ -18,18 +23,16 @@
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ProgressBar">
|
||||
<Grid>
|
||||
<Border x:Name="TemplateRoot" CornerRadius="{Binding Path=(controls:BorderElement.CornerRadius),RelativeSource={RelativeSource TemplatedParent}}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}"/>
|
||||
<Grid Margin="{TemplateBinding BorderThickness}">
|
||||
<Rectangle x:Name="PART_Track"/>
|
||||
<Grid HorizontalAlignment="Left">
|
||||
<Decorator x:Name="PART_Indicator" HorizontalAlignment="Left">
|
||||
<Border CornerRadius="{Binding Path=(controls:BorderElement.CornerRadius),RelativeSource={RelativeSource TemplatedParent}}" x:Name="Animation" ClipToBounds="true" Background="{TemplateBinding Foreground}">
|
||||
<Rectangle x:Name="PART_GlowRect" Fill="{StaticResource ProgressBarIndicatorAnimatedFill}" HorizontalAlignment="Left" Margin="-100,0,0,0" Width="100"/>
|
||||
</Border>
|
||||
</Decorator>
|
||||
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="{DynamicResource TextIconBrush}" Text="{Binding Value,RelativeSource={RelativeSource TemplatedParent},StringFormat={}{0} %}"/>
|
||||
</Grid>
|
||||
<Grid x:Name="TemplateRoot">
|
||||
<Border CornerRadius="{Binding Path=(controls:BorderElement.CornerRadius),RelativeSource={RelativeSource TemplatedParent}}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}"/>
|
||||
<Rectangle x:Name="PART_Track"/>
|
||||
<Grid HorizontalAlignment="Left">
|
||||
<Decorator x:Name="PART_Indicator" HorizontalAlignment="Left">
|
||||
<Border CornerRadius="{Binding Path=(controls:BorderElement.CornerRadius),RelativeSource={RelativeSource TemplatedParent}}" x:Name="Animation" ClipToBounds="true" Background="{TemplateBinding Foreground}">
|
||||
<Rectangle x:Name="PART_GlowRect" Fill="{StaticResource ProgressBarIndicatorAnimatedFill}" HorizontalAlignment="Left" Margin="-100,0,0,0" Width="100"/>
|
||||
</Border>
|
||||
</Decorator>
|
||||
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="{DynamicResource TextIconBrush}" Text="{Binding Value,RelativeSource={RelativeSource TemplatedParent},StringFormat={}{0} %}"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
@@ -51,11 +54,9 @@
|
||||
</ControlTemplate.Resources>
|
||||
<Grid x:Name="TemplateRoot" SnapsToDevicePixels="true">
|
||||
<Border CornerRadius="{Binding Path=(controls:BorderElement.CornerRadius),RelativeSource={RelativeSource TemplatedParent}}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}"/>
|
||||
<Rectangle x:Name="PART_Track" Margin="1"/>
|
||||
<Rectangle x:Name="PART_Track"/>
|
||||
<Decorator x:Name="PART_Indicator" HorizontalAlignment="Left" Margin="1">
|
||||
<Grid x:Name="Foreground">
|
||||
<Border x:Name="Indicator" CornerRadius="{Binding Path=(controls:BorderElement.CornerRadius),RelativeSource={RelativeSource TemplatedParent}}" Background="{TemplateBinding Foreground}"/>
|
||||
</Grid>
|
||||
<Border x:Name="Indicator" CornerRadius="{Binding Path=(controls:BorderElement.CornerRadius),RelativeSource={RelativeSource TemplatedParent}}" Background="{TemplateBinding Foreground}"/>
|
||||
</Decorator>
|
||||
</Grid>
|
||||
<ControlTemplate.Triggers>
|
||||
@@ -68,4 +69,24 @@
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="ProgressBarCircleBaseStyle" TargetType="controls:CircleProgressBar">
|
||||
<Setter Property="controls:BorderElement.CornerRadius" Value="4"/>
|
||||
<Setter Property="Background" Value="{DynamicResource SecondaryRegionBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource BorderBrush}"/>
|
||||
<Setter Property="Height" Value="100"/>
|
||||
<Setter Property="Width" Value="100"/>
|
||||
<Setter Property="ArcThickness" Value="4"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="controls:CircleProgressBar">
|
||||
<Grid x:Name="TemplateRoot">
|
||||
<shapes:Arc ArcThickness="{TemplateBinding ArcThickness}" Stretch="None" EndAngle="360" Width="{TemplateBinding ActualWidth}" Height="{TemplateBinding ActualHeight}" Stroke="{TemplateBinding BorderBrush}" Fill="{TemplateBinding Background}" StrokeThickness="0"/>
|
||||
<shapes:Arc ArcThickness="{TemplateBinding ArcThickness}" Stretch="None" Width="{TemplateBinding ActualWidth}" StrokeThickness="0" Height="{TemplateBinding ActualHeight}" x:Name="PART_Indicator" Fill="{TemplateBinding Foreground}"/>
|
||||
<TextBlock Visibility="{Binding ShowText,RelativeSource={RelativeSource TemplatedParent},Converter={StaticResource Boolean2VisibilityConverter}}" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="{DynamicResource PrimaruTextBrush}" Text="{Binding Value,RelativeSource={RelativeSource TemplatedParent},StringFormat={}{0} %}"/>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
</ResourceDictionary>
|
||||
@@ -1,5 +1,6 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="clr-namespace:HandyControl.Controls">
|
||||
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="Base/ProgressBarBaseStyle.xaml"/>
|
||||
@@ -175,6 +176,26 @@
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style BasedOn="{StaticResource ProgressBarCircleBaseStyle}" TargetType="controls:CircleProgressBar">
|
||||
<Setter Property="Foreground" Value="{DynamicResource PrimaryBrush}"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="ProgressBarSuccessCircle" BasedOn="{StaticResource ProgressBarCircleBaseStyle}" TargetType="controls:CircleProgressBar">
|
||||
<Setter Property="Foreground" Value="{DynamicResource SuccessBrush}"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="ProgressBarInfoCircle" BasedOn="{StaticResource ProgressBarCircleBaseStyle}" TargetType="controls:CircleProgressBar">
|
||||
<Setter Property="Foreground" Value="{DynamicResource InfoBrush}"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="ProgressBarWarningCircle" BasedOn="{StaticResource ProgressBarCircleBaseStyle}" TargetType="controls:CircleProgressBar">
|
||||
<Setter Property="Foreground" Value="{DynamicResource WarningBrush}"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="ProgressBarDangerCircle" BasedOn="{StaticResource ProgressBarCircleBaseStyle}" TargetType="controls:CircleProgressBar">
|
||||
<Setter Property="Foreground" Value="{DynamicResource DangerBrush}"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="ProgressBarFlat" TargetType="ProgressBar">
|
||||
<Setter Property="Height" Value="4"/>
|
||||
<Setter Property="Background" Value="{DynamicResource SecondaryRegionBrush}"/>
|
||||
|
||||
@@ -503,10 +503,10 @@
|
||||
</ControlTemplate>
|
||||
|
||||
<ControlTemplate x:Key="TipSliderThumbHorizontalTop" TargetType="Thumb">
|
||||
<controls:SliderCanvas>
|
||||
<controls:InternalCanvas>
|
||||
<Label Name="LabelValue" Opacity="0" Canvas.Bottom="28" Style="{StaticResource LabelPrimary}" Content="{Binding Value,RelativeSource={RelativeSource FindAncestor,AncestorType=Slider}}"/>
|
||||
<Border Canvas.Bottom="0" Effect="{StaticResource EffectShadow1}" Name="BorderDot" BorderThickness="0" BorderBrush="{DynamicResource PrimaryBrush}" Width="18" Height="18" Background="White" CornerRadius="9"/>
|
||||
</controls:SliderCanvas>
|
||||
</controls:InternalCanvas>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Effect" Value="{StaticResource EffectShadow2}" TargetName="BorderDot"/>
|
||||
@@ -534,10 +534,10 @@
|
||||
</ControlTemplate>
|
||||
|
||||
<ControlTemplate x:Key="TipSliderThumbHorizontalBottom" TargetType="Thumb">
|
||||
<controls:SliderCanvas>
|
||||
<controls:InternalCanvas>
|
||||
<Label Name="LabelValue" Opacity="0" Canvas.Top="28" Style="{StaticResource LabelPrimary}" Content="{Binding Value,RelativeSource={RelativeSource FindAncestor,AncestorType=Slider}}"/>
|
||||
<Border Canvas.Bottom="0" Effect="{StaticResource EffectShadow1}" Name="BorderDot" BorderThickness="0" BorderBrush="{DynamicResource PrimaryBrush}" Width="18" Height="18" Background="White" CornerRadius="9"/>
|
||||
</controls:SliderCanvas>
|
||||
</controls:InternalCanvas>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Effect" Value="{StaticResource EffectShadow2}" TargetName="BorderDot"/>
|
||||
@@ -565,10 +565,10 @@
|
||||
</ControlTemplate>
|
||||
|
||||
<ControlTemplate x:Key="TipSliderThumbVerticalLeft" TargetType="Thumb">
|
||||
<controls:SliderCanvas Orientation="Vertical">
|
||||
<controls:InternalCanvas Orientation="Vertical">
|
||||
<Label Name="LabelValue" Opacity="0" Canvas.Right="28" Style="{StaticResource LabelPrimary}" Content="{Binding Value,RelativeSource={RelativeSource FindAncestor,AncestorType=Slider}}"/>
|
||||
<Border Canvas.Right="0" Effect="{StaticResource EffectShadow1}" Name="BorderDot" BorderThickness="0" BorderBrush="{DynamicResource PrimaryBrush}" Width="18" Height="18" Background="White" CornerRadius="9"/>
|
||||
</controls:SliderCanvas>
|
||||
</controls:InternalCanvas>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Effect" Value="{StaticResource EffectShadow2}" TargetName="BorderDot"/>
|
||||
@@ -596,10 +596,10 @@
|
||||
</ControlTemplate>
|
||||
|
||||
<ControlTemplate x:Key="TipSliderThumbVerticalRight" TargetType="Thumb">
|
||||
<controls:SliderCanvas Orientation="Vertical">
|
||||
<controls:InternalCanvas Orientation="Vertical">
|
||||
<Label Name="LabelValue" Opacity="0" Canvas.Left="28" Style="{StaticResource LabelPrimary}" Content="{Binding Value,RelativeSource={RelativeSource FindAncestor,AncestorType=Slider}}"/>
|
||||
<Border Canvas.Right="0" Effect="{StaticResource EffectShadow1}" Name="BorderDot" BorderThickness="0" BorderBrush="{DynamicResource PrimaryBrush}" Width="18" Height="18" Background="White" CornerRadius="9"/>
|
||||
</controls:SliderCanvas>
|
||||
</controls:InternalCanvas>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Effect" Value="{StaticResource EffectShadow2}" TargetName="BorderDot"/>
|
||||
|
||||
@@ -36,6 +36,8 @@
|
||||
|
||||
public static readonly string PreviewSliderDemoCtl = nameof(PreviewSliderDemoCtl);
|
||||
|
||||
public static readonly string CircleProgressBarDemoCtl = nameof(CircleProgressBarDemoCtl);
|
||||
|
||||
public static readonly string ButtonDemoCtl = nameof(ButtonDemoCtl);
|
||||
|
||||
public static readonly string ToggleButtonDemoCtl = nameof(ToggleButtonDemoCtl);
|
||||
|
||||
@@ -20,7 +20,8 @@ var controlList = new List<string>
|
||||
"NumericUpDownDemoCtl",
|
||||
"WindowDemoCtl",
|
||||
"ScrollViewerDemoCtl",
|
||||
"PreviewSliderDemoCtl"
|
||||
"PreviewSliderDemoCtl",
|
||||
"CircleProgressBarDemoCtl"
|
||||
};
|
||||
var styleList = new List<string>
|
||||
{
|
||||
|
||||
@@ -104,6 +104,9 @@
|
||||
<Compile Include="UserControl\Controls\CirclePanelDemoCtl.xaml.cs">
|
||||
<DependentUpon>CirclePanelDemoCtl.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="UserControl\Controls\CircleProgressBarDemoCtl.xaml.cs">
|
||||
<DependentUpon>CircleProgressBarDemoCtl.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="UserControl\Controls\ClockDemoCtl.xaml.cs">
|
||||
<DependentUpon>ClockDemoCtl.xaml</DependentUpon>
|
||||
</Compile>
|
||||
@@ -309,6 +312,10 @@
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="UserControl\Controls\CircleProgressBarDemoCtl.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="UserControl\Controls\ClockDemoCtl.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
@@ -505,6 +512,7 @@
|
||||
<Generator>TextTemplatingFileGenerator</Generator>
|
||||
<LastGenOutput>MessageToken.cs</LastGenOutput>
|
||||
</Content>
|
||||
<Resource Include="Resources\Img\LeftMainContent\ColorWheel_16x.png" />
|
||||
<Resource Include="Resources\Img\LeftMainContent\Slider_16x.png" />
|
||||
<Resource Include="Resources\Img\LeftMainContent\HoverMenu_16x.png" />
|
||||
<Resource Include="Resources\Img\icon.ico" />
|
||||
|
||||
@@ -195,6 +195,15 @@ namespace HandyControlDemo.Properties.Langs {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 圆形进度条 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string CircleProgressBar {
|
||||
get {
|
||||
return ResourceManager.GetString("CircleProgressBar", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 清空 的本地化字符串。
|
||||
/// </summary>
|
||||
|
||||
@@ -387,4 +387,7 @@
|
||||
<data name="Slider" xml:space="preserve">
|
||||
<value>Slider</value>
|
||||
</data>
|
||||
<data name="CircleProgressBar" xml:space="preserve">
|
||||
<value>CircleProgressBar</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -367,4 +367,7 @@
|
||||
<data name="Slider" xml:space="preserve">
|
||||
<value>滑块</value>
|
||||
</data>
|
||||
<data name="CircleProgressBar" xml:space="preserve">
|
||||
<value>圆形进度条</value>
|
||||
</data>
|
||||
</root>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 553 B |
@@ -0,0 +1,16 @@
|
||||
<UserControl x:Class="HandyControlDemo.UserControl.CircleProgressBarDemoCtl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="clr-namespace:HandyControl.Controls;assembly=HandyControl"
|
||||
Background="{DynamicResource RegionBrush}">
|
||||
<controls:TransitioningContentControl>
|
||||
<StackPanel Orientation="Horizontal" Margin="32">
|
||||
<controls:CircleProgressBar Value="50"/>
|
||||
<controls:CircleProgressBar Value="80" FontSize="30" Margin="16,0,0,0"/>
|
||||
<controls:CircleProgressBar Value="10" Margin="16,0,0,0" ShowText="False" Width="20" Height="20" ArcThickness="2" Style="{StaticResource ProgressBarSuccessCircle}"/>
|
||||
<controls:CircleProgressBar Value="20" Margin="16,0,0,0" ShowText="False" Width="30" Height="30" ArcThickness="6" Style="{StaticResource ProgressBarInfoCircle}"/>
|
||||
<controls:CircleProgressBar Value="40" Margin="16,0,0,0" ShowText="False" Width="40" Height="40" ArcThickness="10" Style="{StaticResource ProgressBarWarningCircle}"/>
|
||||
<controls:CircleProgressBar Value="80" Margin="16,0,0,0" Width="50" Height="50" Style="{StaticResource ProgressBarDangerCircle}"/>
|
||||
</StackPanel>
|
||||
</controls:TransitioningContentControl>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,11 @@
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace HandyControlDemo.UserControl
|
||||
{
|
||||
public partial class CircleProgressBarDemoCtl
|
||||
{
|
||||
public CircleProgressBarDemoCtl()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -130,6 +130,11 @@
|
||||
</controls:EdgeElement.LeftContent>
|
||||
</ListBoxItem>
|
||||
<ListBoxItem Tag="{x:Static data:MessageToken.CirclePanelDemoCtl}" Content="{x:Static langs:Lang.CirclePanel}">
|
||||
<controls:EdgeElement.LeftContent>
|
||||
<Image Source="../../Resources/Img/LeftMainContent/ColorWheel_16x.png"/>
|
||||
</controls:EdgeElement.LeftContent>
|
||||
</ListBoxItem>
|
||||
<ListBoxItem Style="{StaticResource ListBoxItemNew}" Tag="{x:Static data:MessageToken.CircleProgressBarDemoCtl}" Content="{x:Static langs:Lang.CircleProgressBar}">
|
||||
<controls:EdgeElement.LeftContent>
|
||||
<Image Source="../../Resources/Img/LeftMainContent/ShapeArcRing_16x.png"/>
|
||||
</controls:EdgeElement.LeftContent>
|
||||
@@ -179,7 +184,7 @@
|
||||
<Image Source="../../Resources/Img/LeftMainContent/SplitterControl_16x.png"/>
|
||||
</controls:EdgeElement.LeftContent>
|
||||
</ListBoxItem>
|
||||
<ListBoxItem Style="{StaticResource ListBoxItemNew}" Tag="{x:Static data:MessageToken.PreviewSliderDemoCtl}" Content="{x:Static langs:Lang.PreviewSlider}">
|
||||
<ListBoxItem Tag="{x:Static data:MessageToken.PreviewSliderDemoCtl}" Content="{x:Static langs:Lang.PreviewSlider}">
|
||||
<controls:EdgeElement.LeftContent>
|
||||
<Image Source="../../Resources/Img/LeftMainContent/HoverMenu_16x.png"/>
|
||||
</controls:EdgeElement.LeftContent>
|
||||
|
||||
Reference in new issue
Block a user