FanControl.cs 3.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980
  1. using Avalonia;
  2. using Avalonia.Controls;
  3. using Avalonia.Media;
  4. using Avalonia.Threading;
  5. using System;
  6. using System.Globalization;
  7. namespace YZWater.Avalonia.Controls;
  8. public class FanControl : Control
  9. {
  10. public static readonly StyledProperty<bool> IsRunningProperty = AvaloniaProperty.Register<FanControl, bool>(nameof(IsRunning), false);
  11. public static readonly StyledProperty<double> SpeedProperty = AvaloniaProperty.Register<FanControl, double>(nameof(Speed), 1.0);
  12. public static readonly StyledProperty<string> TextProperty = AvaloniaProperty.Register<FanControl, string>(nameof(Text), "Fan");
  13. public bool IsRunning { get => GetValue(IsRunningProperty); set => SetValue(IsRunningProperty, value); }
  14. public double Speed { get => GetValue(SpeedProperty); set => SetValue(SpeedProperty, value); }
  15. public string Text { get => GetValue(TextProperty); set => SetValue(TextProperty, value); }
  16. private double _angle;
  17. private IDisposable? _timer;
  18. static FanControl() { AffectsRender<FanControl>(IsRunningProperty, SpeedProperty, TextProperty); }
  19. protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
  20. {
  21. base.OnAttachedToVisualTree(e);
  22. ThemeHelper.ThemeChanged += OnThemeChanged;
  23. if (IsRunning) StartAnim();
  24. }
  25. protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
  26. {
  27. ThemeHelper.ThemeChanged -= OnThemeChanged;
  28. StopAnim();
  29. base.OnDetachedFromVisualTree(e);
  30. }
  31. private void OnThemeChanged() => InvalidateVisual();
  32. protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
  33. {
  34. base.OnPropertyChanged(change);
  35. if (change.Property == IsRunningProperty) { if (IsRunning) StartAnim(); else { StopAnim(); _angle = 0; } }
  36. }
  37. private void StartAnim() { StopAnim(); _timer = DispatcherTimer.Run(() => { _angle += Speed * 2; if (_angle >= 360) _angle -= 360; InvalidateVisual(); return true; }, TimeSpan.FromMilliseconds(50)); }
  38. private void StopAnim() { _timer?.Dispose(); _timer = null; }
  39. public override void Render(DrawingContext context)
  40. {
  41. base.Render(context);
  42. var bounds = new Rect(Bounds.Size);
  43. var cx = bounds.Width / 2; var cy = bounds.Height / 2;
  44. var radius = Math.Min(bounds.Width, bounds.Height) / 2 - 5;
  45. var typeface = new Typeface("Microsoft YaHei", FontStyle.Normal, FontWeight.Bold);
  46. var fanColor = IsRunning ? ThemeHelper.Success : ThemeHelper.TextDisabled;
  47. context.DrawRectangle(null, new Pen(ThemeHelper.Border, 2), new Rect(cx - radius, cy - radius, radius * 2, radius * 2));
  48. for (int i = 0; i < 4; i++)
  49. {
  50. var angle = _angle + 90.0 * i;
  51. var bx = cx + Math.Cos(angle * Math.PI / 180) * radius * 0.8 * 0.4;
  52. var by = cy + Math.Sin(angle * Math.PI / 180) * radius * 0.8 * 0.4;
  53. context.DrawRectangle(fanColor, null, new Rect(bx - radius * 0.125, by - radius * 0.4, radius * 0.25, radius * 0.8));
  54. }
  55. var cr = radius * 0.15;
  56. context.DrawEllipse(fanColor, null, new Rect(cx - cr, cy - cr, cr * 2, cr * 2));
  57. if (!string.IsNullOrEmpty(Text))
  58. {
  59. var t = new FormattedText(Text, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, typeface, 10, ThemeHelper.TextPrimary);
  60. context.DrawText(t, new Point(cx - t.Width / 2, bounds.Bottom - t.Height - 2));
  61. }
  62. }
  63. protected override Size MeasureOverride(Size availableSize) => new Size(50, 50);
  64. }