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), "风扇"); 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); if (IsRunning) StartAnim(); } protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) { base.OnDetachedFromVisualTree(e); StopAnim(); } 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)); // 叶片 var bladeCount = 4; var bladeLen = radius * 0.8; var bladeW = radius * 0.25; for (int i = 0; i < bladeCount; i++) { var angle = _angle + (360.0 / bladeCount) * i; var bx = cx + Math.Cos(angle * Math.PI / 180) * bladeLen * 0.4; var by = cy + Math.Sin(angle * Math.PI / 180) * bladeLen * 0.4; context.DrawRectangle(fanColor, null, new Rect(bx - bladeW / 2, by - bladeLen / 2, bladeW, bladeLen)); } // 中心 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); }