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;
+ }
+ }
+}