using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace TestProject;
/// <summary>
/// 프로세스 링 컨트롤
/// </summary>
public class ProcessRing : Control
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Dependency Property
////////////////////////////////////////////////////////////////////////////////////////// Static
//////////////////////////////////////////////////////////////////////////////// Public
#region 링 너비 속성 - RingWidthProperty
/// <summary>
/// 링 너비 속성
/// </summary>
public static readonly DependencyProperty RingWidthProperty = DependencyProperty.Register
(
"RingWidth",
typeof(double),
typeof(ProcessRing),
new PropertyMetadata(80.0)
);
#endregion
#region 링 높이 속성 - RingHeightProperty
/// <summary>
/// 링 높이 속성
/// </summary>
public static readonly DependencyProperty RingHeightProperty = DependencyProperty.Register
(
"RingHeight",
typeof(double),
typeof(ProcessRing),
new PropertyMetadata(80.0)
);
#endregion
#region 링 시작 각도 속성 - RingStartAngleProperty
/// <summary>
/// 링 시작 각도 속성
/// </summary>
public static readonly DependencyProperty RingStartAngleProperty = DependencyProperty.Register
(
"RingStartAngle",
typeof(double),
typeof(ProcessRing),
new PropertyMetadata(0.0, RingAnglePropertyChangedCallback)
);
#endregion
#region 링 종료 각도 속성 - RingEndAngleProperty
/// <summary>
/// 링 종료 각도 속성
/// </summary>
public static readonly DependencyProperty RingEndAngleProperty = DependencyProperty.Register
(
"RingEndAngle",
typeof(double),
typeof(ProcessRing),
new PropertyMetadata(360.0, RingAnglePropertyChangedCallback)
);
#endregion
#region 링 두께 속성 - RingThicknessProperty
/// <summary>
/// 링 두께 속성
/// </summary>
public static readonly DependencyProperty RingThicknessProperty = DependencyProperty.Register
(
"RingThickness",
typeof(double),
typeof(ProcessRing),
new PropertyMetadata(5.0, RingThicknessPropertyChangedCallback)
);
#endregion
#region 링 브러시 속성 - RingBrushProperty
/// <summary>
/// 링 브러시 속성
/// </summary>
public static readonly DependencyProperty RingBrushProperty = DependencyProperty.Register
(
"RingBrush",
typeof(Brush),
typeof(ProcessRing),
new PropertyMetadata(Brushes.Black)
);
#endregion
#region 처리 여부 속성 - IsProcessingProperty
/// <summary>
/// 처리 여부 속성
/// </summary>
public static readonly DependencyProperty IsProcessingProperty = DependencyProperty.Register
(
"IsProcessing",
typeof(bool),
typeof(ProcessRing),
new PropertyMetadata(false, IsProcessingPropertyChangedCallback)
);
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Field
////////////////////////////////////////////////////////////////////////////////////////// Private
#region Field
/// <summary>
/// 회전 변환
/// </summary>
private RotateTransform rotateTransform;
/// <summary>
/// 링 패스
/// </summary>
private Path ringPath;
/// <summary>
/// 패스 피규어
/// </summary>
private PathFigure pathFigure;
/// <summary>
/// 아크 세그먼트
/// </summary>
private ArcSegment arcSegment;
/// <summary>
/// 배경 링 패스
/// </summary>
private Path backgroundRingPath;
/// <summary>
/// 배경 패스 피규어
/// </summary>
private PathFigure backgroundPathFigure;
/// <summary>
/// 배경 아크 세그먼트
/// </summary>
private ArcSegment backgroundArcSegment;
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Property
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 링 너비 - RingWidth
/// <summary>
/// 링 너비
/// </summary>
public double RingWidth
{
get => (double)GetValue(RingWidthProperty);
set => SetValue(RingWidthProperty, value);
}
#endregion
#region 링 높이 - RingHeight
/// <summary>
/// 링 높이
/// </summary>
public double RingHeight
{
get => (double)GetValue(RingHeightProperty);
set => SetValue(RingHeightProperty, value);
}
#endregion
#region 링 시작 각도 - RingStartAngle
/// <summary>
/// 링 시작 각도
/// </summary>
public double RingStartAngle
{
get => (double)GetValue(RingStartAngleProperty);
set => SetValue(RingStartAngleProperty, value);
}
#endregion
#region 링 종료 각도 - RingEndAngle
/// <summary>
/// 링 종료 각도
/// </summary>
public double RingEndAngle
{
get => (double)GetValue(RingEndAngleProperty);
set => SetValue(RingEndAngleProperty, value);
}
#endregion
#region 링 두께 - RingThickness
/// <summary>
/// 링 두께
/// </summary>
public double RingThickness
{
get => (double)GetValue(RingThicknessProperty);
set => SetValue(RingThicknessProperty, value);
}
#endregion
#region 링 브러시 - RingBrush
/// <summary>
/// 링 브러시
/// </summary>
public Brush RingBrush
{
get => (Brush)GetValue(RingBrushProperty);
set => SetValue(RingBrushProperty, value);
}
#endregion
#region 처리 여부 - IsProcessing
/// <summary>
/// 처리 여부
/// </summary>
public bool IsProcessing
{
get => (bool)GetValue(IsProcessingProperty);
set => SetValue(IsProcessingProperty, value);
}
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
////////////////////////////////////////////////////////////////////////////////////////// Static
#region 생성자 - ProcessRing()
/// <summary>
/// 생성자
/// </summary>
static ProcessRing()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ProcessRing), new FrameworkPropertyMetadata(typeof(ProcessRing)));
}
#endregion
////////////////////////////////////////////////////////////////////////////////////////// Instance
//////////////////////////////////////////////////////////////////////////////// Public
#region 생성자 - ProcessRing()
/// <summary>
/// 생성자
/// </summary>
public ProcessRing()
{
Width = 100;
Height = 100;
RingWidth = 80;
RingHeight = 80;
RingThickness = 5;
Background = Brushes.Transparent;
}
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Method
////////////////////////////////////////////////////////////////////////////////////////// Static
//////////////////////////////////////////////////////////////////////////////// Private
#region 링 각도 속성 변경시 콜백 처리하기 - RingAnglePropertyChangedCallback(d, e)
/// <summary>
/// 링 각도 속성 변경시 콜백 처리하기
/// </summary>
/// <param name="d">이벤트 발생자</param>
/// <param name="e">이벤트 인자</param>
private static void RingAnglePropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ProcessRing processRing = d as ProcessRing;
processRing.UpdateRing();
}
#endregion
#region 링 두께 속성 변경시 콜백 처리하기 - RingThicknessPropertyChangedCallback(d, e)
/// <summary>
/// 링 두께 속성 변경시 콜백 처리하기
/// </summary>
/// <param name="d">이벤트 발생자</param>
/// <param name="e">이벤트 인자</param>
private static void RingThicknessPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ProcessRing processRing = d as ProcessRing;
processRing.RingThickness = Math.Min((double)e.NewValue, Math.Min(processRing.RingWidth, processRing.RingHeight) / 2);
processRing.UpdateRing();
}
#endregion
#region 처리 여부 속성 변경시 콜백 처리하기 - IsProcessingPropertyChangedCallback(d, e)
/// <summary>
/// 처리 여부 속성 변경시 콜백 처리하기
/// </summary>
/// <param name="d">이벤트 발생자</param>
/// <param name="e">이벤트 인자</param>
private static void IsProcessingPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ProcessRing processRing = d as ProcessRing;
processRing.UpdateAnimation();
}
#endregion
////////////////////////////////////////////////////////////////////////////////////////// Instance
//////////////////////////////////////////////////////////////////////////////// Public
#region 템플리트 적용시 처리하기 - OnApplyTemplate()
/// <summary>
/// 템플리트 적용시 처리하기
/// </summary>
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.rotateTransform = GetTemplateChild("PART_RotationTransform" ) as RotateTransform;
this.ringPath = GetTemplateChild("PART_RingPath" ) as Path;
this.pathFigure = GetTemplateChild("PART_PathFigure" ) as PathFigure;
this.arcSegment = GetTemplateChild("PART_ArcSegment" ) as ArcSegment;
this.backgroundRingPath = GetTemplateChild("PART_BackgroundRingPath" ) as Path;
this.backgroundPathFigure = GetTemplateChild("PART_BackgroundPathFigure") as PathFigure;
this.backgroundArcSegment = GetTemplateChild("PART_BackgroundArcSegment") as ArcSegment;
UpdateRing();
UpdateAnimation();
}
#endregion
//////////////////////////////////////////////////////////////////////////////// Private
#region 포인트 회전하기 - RotatePoint(point, angle)
/// <summary>
/// 포인트 회전하기
/// </summary>
/// <param name="point">포인트</param>
/// <param name="angle">각도</param>
/// <returns>회전 포인트</returns>
private Point RotatePoint(Point point, double angle)
{
double radians = angle * (Math.PI / 180);
return new Point
(
point.X * Math.Cos(radians) - point.Y * Math.Sin(radians) + RingThickness / 2,
point.X * Math.Sin(radians) + point.Y * Math.Cos(radians) + RingThickness / 2
);
}
#endregion
#region 링 업데이트하기 - UpdateRing()
/// <summary>
/// 링 업데이트하기
/// </summary>
private void UpdateRing()
{
if(this.pathFigure != null && this.arcSegment != null)
{
double radius = Math.Min(RingWidth, RingHeight) / 2 - RingThickness / 2;
double angleDifference = (RingEndAngle - RingStartAngle + 360) % 360;
#region 링를 설정한다.
Point startPoint = new(radius, 0);
startPoint = RotatePoint(startPoint, RingStartAngle);
this.pathFigure.StartPoint = new Point(startPoint.X + radius, startPoint.Y + radius);
Point endPoint = new Point(radius, 0);
endPoint = RotatePoint(endPoint, RingEndAngle);
this.arcSegment.Point = new Point(endPoint.X + radius, endPoint.Y + radius);
this.arcSegment.Size = new Size(radius, radius);
this.arcSegment.IsLargeArc = angleDifference > 180;
#endregion
#region 배경 링를 설정한다.
Point backgroundStartPoint = new(radius, 0d);
backgroundStartPoint = RotatePoint(backgroundStartPoint, 0d);
this.backgroundPathFigure.StartPoint = new Point(backgroundStartPoint.X + radius, backgroundStartPoint.Y + radius);
Point backgroundEndPoint = new Point(radius, 0);
backgroundEndPoint = RotatePoint(backgroundEndPoint, 359.9);
this.backgroundArcSegment.Point = new Point(backgroundEndPoint.X + radius, backgroundEndPoint.Y + radius);
this.backgroundArcSegment.Size = new Size(radius, radius);
this.backgroundArcSegment.IsLargeArc = angleDifference > 180;
#endregion
}
}
#endregion
#region 애니메이션 업데이트하기 - UpdateAnimation()
/// <summary>
/// 애니메이션 업데이트하기
/// </summary>
private void UpdateAnimation()
{
if(this.rotateTransform != null && this.ringPath != null)
{
if(IsProcessing)
{
DoubleAnimation rotateAnimation = new()
{
RepeatBehavior = RepeatBehavior.Forever,
Duration = TimeSpan.FromSeconds(2),
From = 0,
To = 360
};
this.rotateTransform.BeginAnimation(RotateTransform.AngleProperty, rotateAnimation);
}
else
{
this.rotateTransform.BeginAnimation(RotateTransform.AngleProperty, null);
}
}
}
#endregion
}