123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220 |
- using Avalonia;
- using Avalonia.Controls;
- using Avalonia.Media;
- using Avalonia.Threading;
- using System.Windows.Input;
- using InABox.Core;
- namespace InABox.Avalonia.Components;
- public class CircularCountdownTimer : Control
- {
- private readonly DispatcherTimer _timer;
- private DateTime? _startTime;
-
- public static readonly StyledProperty<IImage?> ImageProperty =
- AvaloniaProperty.Register<CircularCountdownTimer, IImage?>(nameof(Image));
- public IImage? Image
- {
- get => GetValue(ImageProperty);
- set => SetValue(ImageProperty, value);
- }
-
- public static readonly StyledProperty<bool> IsActiveProperty =
- AvaloniaProperty.Register<CircularCountdownTimer, bool>(nameof(IsActive));
- public bool IsActive
- {
- get => GetValue(IsActiveProperty);
- set => SetValue(IsActiveProperty, value);
- }
-
- public static readonly StyledProperty<ICommand?> StartedProperty =
- AvaloniaProperty.Register<CircularCountdownTimer, ICommand?>(nameof(Started));
- public ICommand? Started
- {
- get => GetValue(StartedProperty);
- set => SetValue(StartedProperty, value);
- }
-
- public static readonly StyledProperty<ICommand?> StoppedProperty =
- AvaloniaProperty.Register<CircularCountdownTimer, ICommand?>(nameof(Stopped));
- public ICommand? Stopped
- {
- get => GetValue(StoppedProperty);
- set => SetValue(StoppedProperty, value);
- }
-
-
- public static readonly StyledProperty<IBrush> BackgroundProperty =
- AvaloniaProperty.Register<CircularCountdownTimer, IBrush>(nameof(Background), Brushes.Transparent);
- public IBrush Background
- {
- get => GetValue(BackgroundProperty);
- set => SetValue(BackgroundProperty, value);
- }
-
- public static readonly StyledProperty<double> StrokeThicknessProperty =
- AvaloniaProperty.Register<CircularCountdownTimer, double>(nameof(StrokeThickness), 10.0);
- public double StrokeThickness
- {
- get => GetValue(StrokeThicknessProperty);
- set => SetValue(StrokeThicknessProperty, value);
- }
-
- public static readonly StyledProperty<IBrush> ProgressBackgroundProperty =
- AvaloniaProperty.Register<CircularCountdownTimer, IBrush>(nameof(ProgressBackground), Brushes.Gray);
- public IBrush ProgressBackground
- {
- get => GetValue(ProgressBackgroundProperty);
- set => SetValue(ProgressBackgroundProperty, value);
- }
-
- public static readonly StyledProperty<IBrush> ProgressForegroundProperty =
- AvaloniaProperty.Register<CircularCountdownTimer, IBrush>(nameof(ProgressForeground), Brushes.WhiteSmoke);
- public IBrush ProgressForeground
- {
- get => GetValue(ProgressForegroundProperty);
- set => SetValue(ProgressForegroundProperty, value);
- }
-
- protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
- {
- base.OnPropertyChanged(change);
- if (change.Property == IsActiveProperty)
- {
- if (Equals(change.NewValue,true))
- Start();
- else
- Stop();
- }
- }
- public static readonly StyledProperty<double> DurationProperty =
- AvaloniaProperty.Register<CircularCountdownTimer, double>(nameof(Duration), 15.0);
- public double Duration
- {
- get => GetValue(DurationProperty);
- set
- {
- SetValue(DurationProperty, value);
- InvalidateVisual();
- }
- }
-
- public CircularCountdownTimer()
- {
- _timer = new DispatcherTimer(DispatcherPriority.MaxValue)
- {
- Interval = TimeSpan.FromMilliseconds(200),
- };
- _timer.Tick += TimerTick;
- }
- private void Start()
- {
- _startTime = DateTime.Now;
- _timer.Start();
- Started?.Execute(DataContext);
- InvalidateVisual();
- }
-
- private void Stop()
- {
- _timer.Stop();
- _startTime = null;
- Stopped?.Execute(DataContext);
- InvalidateVisual();
- }
-
- private TimeSpan RemainingTime()
- {
- if (!_startTime.HasValue)
- return TimeSpan.Zero;
-
- var _nowTime = DateTime.Now;
- var _endTime = _startTime.Value.AddSeconds(Duration);
- return _endTime > _nowTime
- ? _endTime - _nowTime
- : TimeSpan.Zero;
- }
-
- private void TimerTick(object? sender, EventArgs e)
- {
- if (RemainingTime() <= TimeSpan.Zero)
- IsActive = false;
- else
- InvalidateVisual();
- }
-
- public override void Render(DrawingContext context)
- {
- var boundsRect = new Rect(Bounds.Left+Margin.Left, Bounds.Top+Margin.Top, Bounds.Width-(Margin.Left+Margin.Right), Bounds.Height-(Margin.Top+Margin.Bottom));
-
- double centerX = boundsRect.Width / 2;
- double centerY = boundsRect.Height / 2;
- double radius = Math.Min(centerX, centerY) - StrokeThickness;
- var backgroundPen = new Pen(ProgressBackground, StrokeThickness);
- var progressPen = new Pen(ProgressForeground, StrokeThickness)
- {
- LineCap = PenLineCap.Flat // Smooth arc edges
- };
- context.FillRectangle(Background, boundsRect);
- // Draw background circle
- context.DrawEllipse(null, backgroundPen, new Point(centerX, centerY), radius, radius);
- if (Image != null)
- {
- var boxwidth = Math.Sqrt(2) * radius;
- Rect rect = new Rect(centerX - (boxwidth/2), centerY - (boxwidth/2), boxwidth, boxwidth);
- context.DrawImage(Image, rect);
- }
- double _remainingTime = RemainingTime().TotalSeconds;
- if (_remainingTime.IsEffectivelyGreaterThan(0.0))
- {
- // Calculate the sweep angle
- double sweepAngle = (_remainingTime / Duration) * 360;
- double startAngle = -90;
- double endAngle = startAngle + sweepAngle;
- // Convert angles to radians
- double startRad = Math.PI * startAngle / 180.0;
- double endRad = Math.PI * endAngle / 180.0;
- // Calculate points on the circle
- var startPoint = new Point(
- centerX + radius * Math.Cos(startRad),
- centerY + radius * Math.Sin(startRad)
- );
- var endPoint = new Point(
- centerX + radius * Math.Cos(endRad),
- centerY + radius * Math.Sin(endRad)
- );
- // Create an arc geometry
- var geometry = new StreamGeometry();
- using (var ctx = geometry.Open())
- {
- ctx.BeginFigure(startPoint, false);
- ctx.ArcTo(endPoint, new Size(radius, radius), 0, sweepAngle > 180, SweepDirection.Clockwise);
- }
- // Draw the progress arc
- context.DrawGeometry(null, progressPen, geometry);
- }
- }
- }
|