using System.Windows.Controls;
using System.Windows.Shapes;
using Point = System.Windows.Point;
using Size = System.Windows.Size;
// ReSharper disable CheckNamespace
namespace WPFluent.Controls;
///
/// Control that draws a symmetrical arc with rounded edges.
///
///
/// /// <ui:Arc /// EndAngle="359" /// StartAngle="0" /// Stroke="{ui:ThemeResource
/// SystemAccentColorSecondaryBrush}" /// StrokeThickness="2" /// Visibility="Visible" /> ///
///
public class Arc : Shape
{
///
/// Identifies the dependency property.
///
public static readonly DependencyProperty EndAngleProperty = DependencyProperty.Register(
nameof(EndAngle),
typeof(double),
typeof(Arc),
new PropertyMetadata(0.0d, PropertyChangedCallback));
///
/// Identifies the dependency property.
///
public static readonly DependencyProperty StartAngleProperty = DependencyProperty.Register(
nameof(StartAngle),
typeof(double),
typeof(Arc),
new PropertyMetadata(0.0d, PropertyChangedCallback));
///
/// Identifies the dependency property.
///
public static readonly DependencyProperty SweepDirectionProperty = DependencyProperty.Register(
nameof(SweepDirection),
typeof(SweepDirection),
typeof(Arc),
new PropertyMetadata(SweepDirection.Clockwise, PropertyChangedCallback));
private Viewbox _rootLayout;
static Arc()
{
// Modify the metadata of the StrokeStartLineCap dependency property.
StrokeStartLineCapProperty.OverrideMetadata(
typeof(Arc),
new FrameworkPropertyMetadata(PenLineCap.Round, PropertyChangedCallback));
}
private void EnsureRootLayout()
{
if(_rootLayout != null)
{
return;
}
_rootLayout = new Viewbox { SnapsToDevicePixels = true };
AddVisualChild(_rootLayout);
}
protected override Size ArrangeOverride(Size finalSize)
{
EnsureRootLayout();
_rootLayout!.Arrange(new Rect(default, finalSize));
return finalSize;
}
///
/// Get the geometry that defines this shape. Based
/// on Mark Feldman implementation.
///
protected Geometry DefinedGeometry()
{
var geometryStream = new StreamGeometry();
var arcSize = new Size(
Math.Max(0, (RenderSize.Width - StrokeThickness) / 2),
Math.Max(0, (RenderSize.Height - StrokeThickness) / 2));
using var context = geometryStream.Open();
context.BeginFigure(PointAtAngle(Math.Min(StartAngle, EndAngle)), false, false);
context.ArcTo(PointAtAngle(Math.Max(StartAngle, EndAngle)), arcSize, 0, IsLargeArc, SweepDirection, true, false);
geometryStream.Transform = new TranslateTransform(StrokeThickness / 2, StrokeThickness / 2);
return geometryStream;
}
protected override Visual GetVisualChild(int index)
{
if(index != 0)
{
throw new ArgumentOutOfRangeException(nameof(index), "Arc should have only 1 child");
}
EnsureRootLayout();
return _rootLayout;
}
protected override Size MeasureOverride(Size availableSize)
{
EnsureRootLayout();
_rootLayout!.Measure(availableSize);
return _rootLayout.DesiredSize;
}
///
/// Overrides the default OnRender method to draw the element.
///
/// A object that is drawn during the rendering pass of this .
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
Pen pen = new(Stroke, StrokeThickness) { StartLineCap = StrokeStartLineCap, EndLineCap = StrokeStartLineCap, };
drawingContext.DrawGeometry(Stroke, pen, DefinedGeometry());
}
///
/// Draws a point on the coordinates of the given angle. Based on Mark Feldman implementation.
///
/// The angle at which to create the point.
protected Point PointAtAngle(double angle)
{
if(SweepDirection == SweepDirection.Counterclockwise)
{
angle += 90;
angle %= 360;
if(angle < 0)
{
angle += 360;
}
var radAngle = angle * (Math.PI / 180);
var xRadius = (RenderSize.Width - StrokeThickness) / 2;
var yRadius = (RenderSize.Height - StrokeThickness) / 2;
return new Point(xRadius + xRadius * Math.Cos(radAngle), yRadius - yRadius * Math.Sin(radAngle));
}
else
{
angle -= 90;
angle %= 360;
if(angle < 0)
{
angle += 360;
}
var radAngle = angle * (Math.PI / 180);
var xRadius = (RenderSize.Width - StrokeThickness) / 2;
var yRadius = (RenderSize.Height - StrokeThickness) / 2;
return new Point(xRadius + xRadius * Math.Cos(-radAngle), yRadius - yRadius * Math.Sin(-radAngle));
}
}
///
/// Event triggered when one of the key parameters is changed. Forces the geometry to be redrawn.
///
protected static void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if(d is not Arc control)
{
return;
}
control.IsLargeArc = Math.Abs(control.EndAngle - control.StartAngle) > 180;
control.InvalidateVisual();
}
///
protected override Geometry DefiningGeometry => DefinedGeometry();
///
/// Gets or sets the final angle from which the arc will be drawn.
///
public double EndAngle { get => (double)GetValue(EndAngleProperty); set => SetValue(EndAngleProperty, value); }
///
/// Gets a value indicating whether one of the two larger arc sweeps is chosen; otherwise, if is , one of the smaller arc sweeps is chosen.
///
public bool IsLargeArc { get; internal set; } = false;
///
/// Gets or sets the initial angle from which the arc will be drawn.
///
public double StartAngle
{
get => (double)GetValue(StartAngleProperty);
set => SetValue(StartAngleProperty, value);
}
///
/// Gets or sets the direction to where the arc will be drawn.
///
public SweepDirection SweepDirection
{
get => (SweepDirection)GetValue(SweepDirectionProperty);
set => SetValue(SweepDirectionProperty, value);
}
}