using Avalonia; using Avalonia.Controls; using Avalonia.Media; using Avalonia.Threading; using System; using System.Globalization; namespace YZWater.Avalonia.Controls; public class FanControl : Control { public static readonly StyledProperty IsRunningProperty = AvaloniaProperty.Register(nameof(IsRunning), false); public static readonly StyledProperty SpeedProperty = AvaloniaProperty.Register(nameof(Speed), 1.0); public static readonly StyledProperty TextProperty = AvaloniaProperty.Register(nameof(Text), "Fan"); public bool IsRunning { get => GetValue(IsRunningProperty); set => SetValue(IsRunningProperty, value); } public double Speed { get => GetValue(SpeedProperty); set => SetValue(SpeedProperty, value); } public string Text { get => GetValue(TextProperty); set => SetValue(TextProperty, value); } private double _angle; private IDisposable? _timer; static FanControl() { AffectsRender(IsRunningProperty, SpeedProperty, TextProperty); } protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { base.OnAttachedToVisualTree(e); ThemeHelper.ThemeChanged += OnThemeChanged; if (IsRunning) StartAnim(); } protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) { ThemeHelper.ThemeChanged -= OnThemeChanged; StopAnim(); base.OnDetachedFromVisualTree(e); } private void OnThemeChanged() => InvalidateVisual(); protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); if (change.Property == IsRunningProperty) { if (IsRunning) StartAnim(); else { StopAnim(); _angle = 0; } } } private void StartAnim() { StopAnim(); _timer = DispatcherTimer.Run(() => { _angle += Speed * 2; if (_angle >= 360) _angle -= 360; InvalidateVisual(); return true; }, TimeSpan.FromMilliseconds(50)); } private void StopAnim() { _timer?.Dispose(); _timer = null; } public override void Render(DrawingContext context) { base.Render(context); var bounds = new Rect(Bounds.Size); var cx = bounds.Width / 2; var cy = bounds.Height / 2; var radius = Math.Min(bounds.Width, bounds.Height) / 2 - 5; var typeface = new Typeface("Microsoft YaHei", FontStyle.Normal, FontWeight.Bold); var fanColor = IsRunning ? ThemeHelper.Success : ThemeHelper.TextDisabled; context.DrawRectangle(null, new Pen(ThemeHelper.Border, 2), new Rect(cx - radius, cy - radius, radius * 2, radius * 2)); for (int i = 0; i < 4; i++) { var angle = _angle + 90.0 * i; var bx = cx + Math.Cos(angle * Math.PI / 180) * radius * 0.8 * 0.4; var by = cy + Math.Sin(angle * Math.PI / 180) * radius * 0.8 * 0.4; context.DrawRectangle(fanColor, null, new Rect(bx - radius * 0.125, by - radius * 0.4, radius * 0.25, radius * 0.8)); } var cr = radius * 0.15; context.DrawEllipse(fanColor, null, new Rect(cx - cr, cy - cr, cr * 2, cr * 2)); if (!string.IsNullOrEmpty(Text)) { var t = new FormattedText(Text, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, typeface, 10, ThemeHelper.TextPrimary); context.DrawText(t, new Point(cx - t.Width / 2, bounds.Bottom - t.Height - 2)); } } protected override Size MeasureOverride(Size availableSize) => new Size(50, 50); }