diff --git a/src/Avalonia.Base/Utilities/MathUtilities.cs b/src/Avalonia.Base/Utilities/MathUtilities.cs index d381979c1e..3c48c3469e 100644 --- a/src/Avalonia.Base/Utilities/MathUtilities.cs +++ b/src/Avalonia.Base/Utilities/MathUtilities.cs @@ -324,6 +324,41 @@ namespace Avalonia.Utilities return angle * 2 * Math.PI; } + /// + /// Calculates the point of an angle on an ellipse. + /// + /// The centre point of the ellipse. + /// The x radius of the ellipse. + /// The y radius of the ellipse. + /// The angle in radians. + /// A point on the ellipse. + public static Point GetEllipsePoint(Point centre, double radiusX, double radiusY, double angle) + { + return new Point(radiusX * Math.Cos(angle) + centre.X, radiusY * Math.Sin(angle) + centre.Y); + } + + /// + /// Gets the minimum and maximum from the specified numbers. + /// + /// The first number. + /// The second number. + /// A tuple containing the minimum and maximum of the two specified numbers. + public static (double min, double max) GetMinMax(double a, double b) + { + return a < b ? (a, b) : (b, a); + } + + /// + /// Gets the minimum and maximum from the specified number and the difference with that number. + /// + /// The initial value to use. + /// The difference for . + /// A tuple containing the minimum and maximum of the specified number and the difference with that number. + public static (double min, double max) GetMinMaxFromDelta(double initialValue, double delta) + { + return GetMinMax(initialValue, initialValue + delta); + } + private static void ThrowCannotBeGreaterThanException(T min, T max) { throw new ArgumentException($"{min} cannot be greater than {max}."); diff --git a/src/Avalonia.Controls/Shapes/Sector.cs b/src/Avalonia.Controls/Shapes/Sector.cs new file mode 100644 index 0000000000..5d2f6701a7 --- /dev/null +++ b/src/Avalonia.Controls/Shapes/Sector.cs @@ -0,0 +1,65 @@ +using System; +using Avalonia.Media; +using Avalonia.Utilities; + +namespace Avalonia.Controls.Shapes +{ + public class Sector : Shape + { + public static readonly StyledProperty StartAngleProperty = AvaloniaProperty.Register(nameof(StartAngle), 0.0d); + public static readonly StyledProperty AngleProperty = AvaloniaProperty.Register(nameof(Angle), 0.0d); + + public double StartAngle + { + get => GetValue(StartAngleProperty); + set => SetValue(StartAngleProperty, value); + } + + public double Angle + { + get => GetValue(AngleProperty); + set => SetValue(AngleProperty, value); + } + + static Sector() + { + StrokeThicknessProperty.OverrideDefaultValue(1.0d); + AffectsGeometry(BoundsProperty, StrokeThicknessProperty, StartAngleProperty, AngleProperty); + } + + protected override Geometry? CreateDefiningGeometry() + { + Rect rect = new Rect(Bounds.Size); + Rect deflatedRect = rect.Deflate(StrokeThickness * 0.5d); + + if (Angle >= 360.0d || Angle <= -360.0d) + { + return new EllipseGeometry(deflatedRect); + } + + if (Angle == 0.0d) + { + return new StreamGeometry(); + } + + (double startAngle, double endAngle) = MathUtilities.GetMinMaxFromDelta(MathUtilities.Deg2Rad(StartAngle), MathUtilities.Deg2Rad(Angle)); + + Point centre = new Point(rect.Width * 0.5d, rect.Height * 0.5d); + double radiusX = deflatedRect.Width * 0.5d; + double radiusY = deflatedRect.Height * 0.5d; + Point startCurvePoint = MathUtilities.GetEllipsePoint(centre, radiusX, radiusY, startAngle); + Point endCurvePoint = MathUtilities.GetEllipsePoint(centre, radiusX, radiusY, endAngle); + Size size = new Size(radiusX, radiusY); + + StreamGeometry streamGeometry = new StreamGeometry(); + using StreamGeometryContext streamGeometryContext = streamGeometry.Open(); + + streamGeometryContext.BeginFigure(startCurvePoint, false); + streamGeometryContext.ArcTo(endCurvePoint, size, 0.0d, Math.Abs(Angle) > 180.0d, SweepDirection.Clockwise); + streamGeometryContext.LineTo(centre); + streamGeometryContext.EndFigure(true); + + return streamGeometry; + } + } +}