Explorar o c贸digo

涓婚缁熶竴 - 鎵鏈夎嚜瀹氫箟鎺т欢鏀圭敤ThemeHelper璇诲彇涓婚璧勬簮

纾 鏇 hai 1 semana
pai
achega
00fa49db1e

+ 30 - 129
src/YZWater.Avalonia/Controls/FanControl.cs

@@ -7,167 +7,68 @@ using System.Globalization;
 
 namespace YZWater.Avalonia.Controls;
 
-/// <summary>
-/// 椋庢墖鎺т欢 - 妯℃嫙 HslCoolFan
-/// </summary>
 public class FanControl : Control
 {
     public static readonly StyledProperty<bool> IsRunningProperty =
         AvaloniaProperty.Register<FanControl, bool>(nameof(IsRunning), false);
-
     public static readonly StyledProperty<double> SpeedProperty =
         AvaloniaProperty.Register<FanControl, double>(nameof(Speed), 1.0);
-
-    public static readonly StyledProperty<IBrush> FanColorProperty =
-        AvaloniaProperty.Register<FanControl, IBrush>(nameof(FanColor), new SolidColorBrush(Color.Parse("#607D8B")));
-
     public static readonly StyledProperty<string> TextProperty =
         AvaloniaProperty.Register<FanControl, string>(nameof(Text), "椋庢墖");
 
-    public bool IsRunning
-    {
-        get => GetValue(IsRunningProperty);
-        set => SetValue(IsRunningProperty, value);
-    }
+    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); }
 
-    public double Speed
-    {
-        get => GetValue(SpeedProperty);
-        set => SetValue(SpeedProperty, value);
-    }
-
-    public IBrush FanColor
-    {
-        get => GetValue(FanColorProperty);
-        set => SetValue(FanColorProperty, value);
-    }
-
-    public string Text
-    {
-        get => GetValue(TextProperty);
-        set => SetValue(TextProperty, value);
-    }
-
-    private double _rotationAngle = 0;
-    private IDisposable? _timerSubscription;
-
-    static FanControl()
-    {
-        AffectsRender<FanControl>(IsRunningProperty, SpeedProperty, FanColorProperty, TextProperty);
-    }
-
-    protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
-    {
-        base.OnAttachedToVisualTree(e);
-        if (IsRunning)
-        {
-            StartAnimation();
-        }
-    }
-
-    protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
-    {
-        base.OnDetachedFromVisualTree(e);
-        StopAnimation();
-    }
+    private double _angle;
+    private IDisposable? _timer;
 
+    static FanControl() { AffectsRender<FanControl>(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)
-            {
-                StartAnimation();
-            }
-            else
-            {
-                StopAnimation();
-                _rotationAngle = 0;
-            }
-        }
-    }
-
-    private void StartAnimation()
-    {
-        StopAnimation();
-        _timerSubscription = DispatcherTimer.Run(() =>
-        {
-            _rotationAngle += Speed * 2;
-            if (_rotationAngle >= 360) _rotationAngle -= 360;
-            InvalidateVisual();
-            return true;
-        }, TimeSpan.FromMilliseconds(50));
-    }
-
-    private void StopAnimation()
-    {
-        _timerSubscription?.Dispose();
-        _timerSubscription = null;
+        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 centerX = bounds.Width / 2;
-        var centerY = bounds.Height / 2;
+        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");
+        var typeface = new Typeface("Microsoft YaHei", FontStyle.Normal, FontWeight.Bold);
+        var fanColor = IsRunning ? ThemeHelper.Success() : ThemeHelper.TextDisabled();
 
-        // 缁樺埗澶栨
-        var outerPen = new Pen(FanColor, 2);
-        context.DrawRectangle(null, outerPen, new Rect(
-            centerX - radius,
-            centerY - radius,
-            radius * 2,
-            radius * 2));
+        // 澶栨
+        context.DrawRectangle(null, new Pen(ThemeHelper.Border(), 2), new Rect(cx - radius, cy - radius, radius * 2, radius * 2));
 
-        // 缁樺埗椋庢墖鍙剁墖
+        // 鍙剁墖
         var bladeCount = 4;
-        var bladeLength = radius * 0.8;
-        var bladeWidth = radius * 0.25;
-
+        var bladeLen = radius * 0.8;
+        var bladeW = radius * 0.25;
         for (int i = 0; i < bladeCount; i++)
         {
-            var angle = _rotationAngle + (360.0 / bladeCount) * i;
-            var radians = angle * Math.PI / 180;
-
-            var bladeX = centerX + Math.Cos(radians) * bladeLength * 0.4;
-            var bladeY = centerY + Math.Sin(radians) * bladeLength * 0.4;
-
-            var bladeRect = new Rect(
-                bladeX - bladeWidth / 2,
-                bladeY - bladeLength / 2,
-                bladeWidth,
-                bladeLength);
-
-            context.DrawRectangle(FanColor, null, bladeRect);
+            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 centerRadius = radius * 0.15;
-        context.DrawEllipse(FanColor, null, new Rect(
-            centerX - centerRadius,
-            centerY - centerRadius,
-            centerRadius * 2,
-            centerRadius * 2));
+        // 涓績
+        var cr = radius * 0.15;
+        context.DrawEllipse(fanColor, null, new Rect(cx - cr, cy - cr, cr * 2, cr * 2));
 
-        // 缁樺埗鏂囧瓧
+        // 鏂囧瓧
         if (!string.IsNullOrEmpty(Text))
         {
-            var foreground = new SolidColorBrush(Colors.Black);
-            var text = new FormattedText(Text, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, typeface, 10, foreground);
-            var textPoint = new Point(
-                centerX - text.Width / 2,
-                bounds.Bottom - text.Height - 2);
-            context.DrawText(text, textPoint);
+            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)
-    {
-        return new Size(50, 50);
-    }
+    protected override Size MeasureOverride(Size availableSize) => new Size(50, 50);
 }

+ 52 - 130
src/YZWater.Avalonia/Controls/GaugeControl.cs

@@ -6,177 +6,99 @@ using System.Globalization;
 
 namespace YZWater.Avalonia.Controls;
 
-/// <summary>
-/// 浠〃鐩樻帶浠讹紙绠鍖栫増锛
-/// </summary>
 public class GaugeControl : Control
 {
     public static readonly StyledProperty<double> ValueProperty =
         AvaloniaProperty.Register<GaugeControl, double>(nameof(Value), 0.0);
-
     public static readonly StyledProperty<double> MinValueProperty =
         AvaloniaProperty.Register<GaugeControl, double>(nameof(MinValue), 0.0);
-
     public static readonly StyledProperty<double> MaxValueProperty =
         AvaloniaProperty.Register<GaugeControl, double>(nameof(MaxValue), 100.0);
-
     public static readonly StyledProperty<string> TitleProperty =
         AvaloniaProperty.Register<GaugeControl, string>(nameof(Title), "娴侀噺");
-
     public static readonly StyledProperty<string> UnitProperty =
         AvaloniaProperty.Register<GaugeControl, string>(nameof(Unit), "m鲁/h");
-
     public static readonly StyledProperty<IBrush> ValueColorProperty =
-        AvaloniaProperty.Register<GaugeControl, IBrush>(nameof(ValueColor), new SolidColorBrush(Color.Parse("#00BCD4")));
-
-    public double Value
-    {
-        get => GetValue(ValueProperty);
-        set => SetValue(ValueProperty, value);
-    }
-
-    public double MinValue
-    {
-        get => GetValue(MinValueProperty);
-        set => SetValue(MinValueProperty, value);
-    }
+        AvaloniaProperty.Register<GaugeControl, IBrush>(nameof(ValueColor));
 
-    public double MaxValue
-    {
-        get => GetValue(MaxValueProperty);
-        set => SetValue(MaxValueProperty, value);
-    }
+    public double Value { get => GetValue(ValueProperty); set => SetValue(ValueProperty, value); }
+    public double MinValue { get => GetValue(MinValueProperty); set => SetValue(MinValueProperty, value); }
+    public double MaxValue { get => GetValue(MaxValueProperty); set => SetValue(MaxValueProperty, value); }
+    public string Title { get => GetValue(TitleProperty); set => SetValue(TitleProperty, value); }
+    public string Unit { get => GetValue(UnitProperty); set => SetValue(UnitProperty, value); }
+    public IBrush ValueColor { get => GetValue(ValueColorProperty); set => SetValue(ValueColorProperty, value); }
 
-    public string Title
-    {
-        get => GetValue(TitleProperty);
-        set => SetValue(TitleProperty, value);
-    }
-
-    public string Unit
-    {
-        get => GetValue(UnitProperty);
-        set => SetValue(UnitProperty, value);
-    }
-
-    public IBrush ValueColor
-    {
-        get => GetValue(ValueColorProperty);
-        set => SetValue(ValueColorProperty, value);
-    }
-
-    static GaugeControl()
-    {
-        AffectsRender<GaugeControl>(ValueProperty, MinValueProperty, MaxValueProperty,
-            TitleProperty, UnitProperty, ValueColorProperty);
-    }
+    static GaugeControl() { AffectsRender<GaugeControl>(ValueProperty, MinValueProperty, MaxValueProperty, TitleProperty, UnitProperty, ValueColorProperty); }
 
     public override void Render(DrawingContext context)
     {
         base.Render(context);
-
         var bounds = new Rect(Bounds.Size);
-        var centerX = bounds.Width / 2;
-        var centerY = bounds.Height / 2 + 10;
+        var cx = bounds.Width / 2; var cy = bounds.Height / 2 + 10;
         var radius = Math.Min(bounds.Width, bounds.Height) / 2 - 20;
         var typeface = new Typeface("Microsoft YaHei", FontStyle.Normal, FontWeight.Bold);
 
-        // 缁樺埗鑳屾櫙鍦
-        var bgBrush = new SolidColorBrush(Color.Parse("#1E272E"));
-        context.DrawEllipse(bgBrush, null, new Rect(
-            centerX - radius - 5,
-            centerY - radius - 5,
-            (radius + 5) * 2,
-            (radius + 5) * 2));
+        // 鑳屾櫙
+        context.DrawEllipse(ThemeHelper.PanelBg(), null, new Rect(cx - radius - 5, cy - radius - 5, (radius + 5) * 2, (radius + 5) * 2));
 
-        // 缁樺埗鍒诲害寮х嚎鑳屾櫙
-        var arcPenBg = new Pen(new SolidColorBrush(Color.Parse("#37474F")), 8);
-        DrawArc(context, arcPenBg, centerX, centerY, radius, 135, 270);
+        // 寮х嚎鑳屾櫙
+        var arcBg = new Pen(ThemeHelper.Border(), 8);
+        DrawArc(context, arcBg, cx, cy, radius, 135, 270);
 
-        // 璁$畻褰撳墠鍊煎搴旂殑瑙掑害
-        var normalizedValue = (Value - MinValue) / (MaxValue - MinValue);
-        normalizedValue = Math.Max(0, Math.Min(1, normalizedValue));
-        var valueAngle = 135 + normalizedValue * 270;
+        // 鍊煎姬绾
+        var normalized = Math.Max(0, Math.Min(1, (Value - MinValue) / (MaxValue - MinValue)));
+        var valueAngle = 135 + normalized * 270;
+        var valueColor = ValueColor ?? ThemeHelper.Success();
+        DrawArc(context, new Pen(valueColor, 8), cx, cy, radius, 135, valueAngle - 135);
 
-        // 缁樺埗褰撳墠鍊煎姬绾
-        var valueColor = ValueColor as SolidColorBrush ?? new SolidColorBrush(Color.Parse("#00BCD4"));
-        var arcPen = new Pen(valueColor, 8);
-        DrawArc(context, arcPen, centerX, centerY, radius, 135, valueAngle - 135);
-
-        // 缁樺埗鍒诲害绾
-        var tickCount = 10;
-        for (int i = 0; i <= tickCount; i++)
+        // 鍒诲害
+        for (int i = 0; i <= 10; i++)
         {
-            var angle = 135 + (270.0 * i / tickCount);
-            var radians = angle * Math.PI / 180;
+            var angle = 135 + (270.0 * i / 10);
+            var rad = angle * Math.PI / 180;
             var isMajor = i % 5 == 0;
-            var tickLength = isMajor ? 12 : 6;
-
-            var innerX = centerX + Math.Cos(radians) * (radius - tickLength);
-            var innerY = centerY + Math.Sin(radians) * (radius - tickLength);
-            var outerX = centerX + Math.Cos(radians) * radius;
-            var outerY = centerY + Math.Sin(radians) * radius;
-
-            var tickPen = new Pen(new SolidColorBrush(isMajor ? Colors.White : Colors.Gray), isMajor ? 2 : 1);
-            context.DrawLine(tickPen, new Point(innerX, innerY), new Point(outerX, outerY));
+            var tickLen = isMajor ? 12 : 6;
+            context.DrawLine(new Pen(isMajor ? ThemeHelper.TextPrimary() : ThemeHelper.TextDisabled(), isMajor ? 2 : 1),
+                new Point(cx + Math.Cos(rad) * (radius - tickLen), cy + Math.Sin(rad) * (radius - tickLen)),
+                new Point(cx + Math.Cos(rad) * radius, cy + Math.Sin(rad) * radius));
         }
 
-        // 缁樺埗鎸囬拡
-        var pointerAngle = valueAngle * Math.PI / 180;
-        var pointerLength = radius * 0.7;
-        var pointerX = centerX + Math.Cos(pointerAngle) * pointerLength;
-        var pointerY = centerY + Math.Sin(pointerAngle) * pointerLength;
-
-        var pointerPen = new Pen(valueColor, 3);
-        context.DrawLine(pointerPen, new Point(centerX, centerY), new Point(pointerX, pointerY));
+        // 鎸囬拡
+        var pAngle = valueAngle * Math.PI / 180;
+        var pLen = radius * 0.7;
+        context.DrawLine(new Pen(valueColor, 3), new Point(cx, cy), new Point(cx + Math.Cos(pAngle) * pLen, cy + Math.Sin(pAngle) * pLen));
+        context.DrawEllipse(valueColor, null, new Rect(cx - 6, cy - 6, 12, 12));
 
-        // 缁樺埗涓績鍦
-        context.DrawEllipse(valueColor, null, new Rect(centerX - 6, centerY - 6, 12, 12));
+        // 鏍囬
+        var titleText = new FormattedText(Title, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, typeface, 12, ThemeHelper.TextPrimary());
+        context.DrawText(titleText, new Point(cx - titleText.Width / 2, bounds.Y + 5));
 
-        // 缁樺埗鏍囬
-        var titleText = new FormattedText(Title, CultureInfo.CurrentCulture,
-            FlowDirection.LeftToRight, typeface, 12, new SolidColorBrush(Colors.White));
-        context.DrawText(titleText, new Point(centerX - titleText.Width / 2, bounds.Y + 5));
+        // 鏁板
+        var valText = new FormattedText($"{Value:F1}", CultureInfo.CurrentCulture, FlowDirection.LeftToRight, new Typeface("Microsoft YaHei", FontStyle.Normal, FontWeight.Bold), 20, ThemeHelper.TextPrimary());
+        context.DrawText(valText, new Point(cx - valText.Width / 2, cy + radius * 0.3));
 
-        // 缁樺埗鏁板
-        var valueStr = $"{Value:F1}";
-        var valueText = new FormattedText(valueStr, CultureInfo.CurrentCulture,
-            FlowDirection.LeftToRight, new Typeface("Microsoft YaHei", FontStyle.Normal, FontWeight.Bold),
-            20, new SolidColorBrush(Colors.White));
-        context.DrawText(valueText, new Point(centerX - valueText.Width / 2, centerY + radius * 0.3));
-
-        // 缁樺埗鍗曚綅
-        var unitText = new FormattedText(Unit, CultureInfo.CurrentCulture,
-            FlowDirection.LeftToRight, new Typeface("Microsoft YaHei"), 10,
-            new SolidColorBrush(Colors.Gray));
-        context.DrawText(unitText, new Point(centerX - unitText.Width / 2, centerY + radius * 0.3 + valueText.Height + 2));
+        // 鍗曚綅
+        var unitText = new FormattedText(Unit, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, new Typeface("Microsoft YaHei"), 10, ThemeHelper.TextDisabled());
+        context.DrawText(unitText, new Point(cx - unitText.Width / 2, cy + radius * 0.3 + valText.Height + 2));
     }
 
-    private void DrawArc(DrawingContext context, IPen pen, double centerX, double centerY, double radius, double startAngle, double sweepAngle)
+    private void DrawArc(DrawingContext context, IPen pen, double cx, double cy, double radius, double startAngle, double sweepAngle)
     {
-        // 绠鍖栫粯鍒讹細浣跨敤澶氫釜灏忕嚎娈佃繎浼煎姬绾
-        var startRadians = startAngle * Math.PI / 180;
-        var endRadians = (startAngle + sweepAngle) * Math.PI / 180;
+        var startRad = startAngle * Math.PI / 180;
+        var endRad = (startAngle + sweepAngle) * Math.PI / 180;
         var steps = 36;
-
-        var prevX = centerX + Math.Cos(startRadians) * radius;
-        var prevY = centerY + Math.Sin(startRadians) * radius;
-
+        var px = cx + Math.Cos(startRad) * radius;
+        var py = cy + Math.Sin(startRad) * radius;
         for (int i = 1; i <= steps; i++)
         {
             var t = (double)i / steps;
-            var angle = startRadians + (endRadians - startRadians) * t;
-            var x = centerX + Math.Cos(angle) * radius;
-            var y = centerY + Math.Sin(angle) * radius;
-
-            context.DrawLine(pen, new Point(prevX, prevY), new Point(x, y));
-            prevX = x;
-            prevY = y;
+            var angle = startRad + (endRad - startRad) * t;
+            var x = cx + Math.Cos(angle) * radius;
+            var y = cy + Math.Sin(angle) * radius;
+            context.DrawLine(pen, new Point(px, py), new Point(x, y));
+            px = x; py = y;
         }
     }
 
-    protected override Size MeasureOverride(Size availableSize)
-    {
-        return new Size(120, 140);
-    }
+    protected override Size MeasureOverride(Size availableSize) => new Size(120, 140);
 }

+ 26 - 119
src/YZWater.Avalonia/Controls/PipeLineControl.cs

@@ -6,162 +6,69 @@ using System;
 
 namespace YZWater.Avalonia.Controls;
 
-/// <summary>
-/// 绠¢亾鎺т欢 - 妯℃嫙 PipeLine
-/// </summary>
 public class PipeLineControl : Control
 {
     public static readonly StyledProperty<bool> IsFlowProperty =
         AvaloniaProperty.Register<PipeLineControl, bool>(nameof(IsFlow), false);
-
     public static readonly StyledProperty<double> FlowSpeedProperty =
         AvaloniaProperty.Register<PipeLineControl, double>(nameof(FlowSpeed), 1.0);
-
     public static readonly StyledProperty<IBrush> PipeColorProperty =
-        AvaloniaProperty.Register<PipeLineControl, IBrush>(nameof(PipeColor), new SolidColorBrush(Color.Parse("#4CAF50")));
-
+        AvaloniaProperty.Register<PipeLineControl, IBrush>(nameof(PipeColor));
     public static readonly StyledProperty<IBrush> WaterColorProperty =
-        AvaloniaProperty.Register<PipeLineControl, IBrush>(nameof(WaterColor), new SolidColorBrush(Color.Parse("#2196F3")));
-
+        AvaloniaProperty.Register<PipeLineControl, IBrush>(nameof(WaterColor));
     public static readonly StyledProperty<double> PipeWidthProperty =
         AvaloniaProperty.Register<PipeLineControl, double>(nameof(PipeWidth), 20.0);
-
     public static readonly StyledProperty<bool> IsHorizontalProperty =
         AvaloniaProperty.Register<PipeLineControl, bool>(nameof(IsHorizontal), true);
 
-    public bool IsFlow
-    {
-        get => GetValue(IsFlowProperty);
-        set => SetValue(IsFlowProperty, value);
-    }
-
-    public double FlowSpeed
-    {
-        get => GetValue(FlowSpeedProperty);
-        set => SetValue(FlowSpeedProperty, value);
-    }
-
-    public IBrush PipeColor
-    {
-        get => GetValue(PipeColorProperty);
-        set => SetValue(PipeColorProperty, value);
-    }
-
-    public IBrush WaterColor
-    {
-        get => GetValue(WaterColorProperty);
-        set => SetValue(WaterColorProperty, value);
-    }
-
-    public double PipeWidth
-    {
-        get => GetValue(PipeWidthProperty);
-        set => SetValue(PipeWidthProperty, value);
-    }
-
-    public bool IsHorizontal
-    {
-        get => GetValue(IsHorizontalProperty);
-        set => SetValue(IsHorizontalProperty, value);
-    }
-
-    private double _flowOffset = 0;
-    private IDisposable? _timerSubscription;
-
-    static PipeLineControl()
-    {
-        AffectsRender<PipeLineControl>(IsFlowProperty, FlowSpeedProperty, PipeColorProperty, WaterColorProperty, PipeWidthProperty, IsHorizontalProperty);
-    }
-
-    protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
-    {
-        base.OnAttachedToVisualTree(e);
-        if (IsFlow)
-        {
-            StartAnimation();
-        }
-    }
+    public bool IsFlow { get => GetValue(IsFlowProperty); set => SetValue(IsFlowProperty, value); }
+    public double FlowSpeed { get => GetValue(FlowSpeedProperty); set => SetValue(FlowSpeedProperty, value); }
+    public IBrush PipeColor { get => GetValue(PipeColorProperty); set => SetValue(PipeColorProperty, value); }
+    public IBrush WaterColor { get => GetValue(WaterColorProperty); set => SetValue(WaterColorProperty, value); }
+    public double PipeWidth { get => GetValue(PipeWidthProperty); set => SetValue(PipeWidthProperty, value); }
+    public bool IsHorizontal { get => GetValue(IsHorizontalProperty); set => SetValue(IsHorizontalProperty, value); }
 
-    protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
-    {
-        base.OnDetachedFromVisualTree(e);
-        StopAnimation();
-    }
+    private double _flowOffset;
+    private IDisposable? _timer;
 
+    static PipeLineControl() { AffectsRender<PipeLineControl>(IsFlowProperty, FlowSpeedProperty, PipeColorProperty, WaterColorProperty, PipeWidthProperty, IsHorizontalProperty); }
+    protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { base.OnAttachedToVisualTree(e); if (IsFlow) StartAnim(); }
+    protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) { base.OnDetachedFromVisualTree(e); StopAnim(); }
     protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
     {
         base.OnPropertyChanged(change);
-        if (change.Property == IsFlowProperty)
-        {
-            if (IsFlow)
-            {
-                StartAnimation();
-            }
-            else
-            {
-                StopAnimation();
-                _flowOffset = 0;
-            }
-        }
-    }
-
-    private void StartAnimation()
-    {
-        StopAnimation();
-        _timerSubscription = DispatcherTimer.Run(() =>
-        {
-            _flowOffset += FlowSpeed;
-            if (_flowOffset >= 20) _flowOffset -= 20;
-            InvalidateVisual();
-            return true; // 缁х画杩愯
-        }, TimeSpan.FromMilliseconds(50));
-    }
-
-    private void StopAnimation()
-    {
-        _timerSubscription?.Dispose();
-        _timerSubscription = null;
+        if (change.Property == IsFlowProperty) { if (IsFlow) StartAnim(); else { StopAnim(); _flowOffset = 0; } }
     }
+    private void StartAnim() { StopAnim(); _timer = DispatcherTimer.Run(() => { _flowOffset += FlowSpeed; if (_flowOffset >= 20) _flowOffset -= 20; 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 pipeWidth = PipeWidth;
+        var pw = PipeWidth;
+        var pipeColor = PipeColor ?? ThemeHelper.Border();
+        var waterColor = WaterColor ?? ThemeHelper.Success();
 
         if (IsHorizontal)
         {
-            // 缁樺埗姘村钩绠¢亾
-            var pipeRect = new Rect(0, (bounds.Height - pipeWidth) / 2, bounds.Width, pipeWidth);
-            context.DrawRectangle(PipeColor, null, pipeRect);
-
-            // 缁樺埗娴佸姩鏁堟灉
+            context.DrawRectangle(pipeColor, null, new Rect(0, (bounds.Height - pw) / 2, bounds.Width, pw));
             if (IsFlow)
             {
-                var dashPen = new Pen(WaterColor, pipeWidth * 0.6, new DashStyle(new double[] { 8, 12 }, _flowOffset));
-                var y = bounds.Height / 2;
-                context.DrawLine(dashPen, new Point(0, y), new Point(bounds.Width, y));
+                var dashPen = new Pen(waterColor, pw * 0.6, new DashStyle(new double[] { 8, 12 }, _flowOffset));
+                context.DrawLine(dashPen, new Point(0, bounds.Height / 2), new Point(bounds.Width, bounds.Height / 2));
             }
         }
         else
         {
-            // 缁樺埗鍨傜洿绠¢亾
-            var pipeRect = new Rect((bounds.Width - pipeWidth) / 2, 0, pipeWidth, bounds.Height);
-            context.DrawRectangle(PipeColor, null, pipeRect);
-
-            // 缁樺埗娴佸姩鏁堟灉
+            context.DrawRectangle(pipeColor, null, new Rect((bounds.Width - pw) / 2, 0, pw, bounds.Height));
             if (IsFlow)
             {
-                var dashPen = new Pen(WaterColor, pipeWidth * 0.6, new DashStyle(new double[] { 8, 12 }, _flowOffset));
-                var x = bounds.Width / 2;
-                context.DrawLine(dashPen, new Point(x, 0), new Point(x, bounds.Height));
+                var dashPen = new Pen(waterColor, pw * 0.6, new DashStyle(new double[] { 8, 12 }, _flowOffset));
+                context.DrawLine(dashPen, new Point(bounds.Width / 2, 0), new Point(bounds.Width / 2, bounds.Height));
             }
         }
     }
 
-    protected override Size MeasureOverride(Size availableSize)
-    {
-        return IsHorizontal ? new Size(80, 30) : new Size(30, 80);
-    }
+    protected override Size MeasureOverride(Size availableSize) => IsHorizontal ? new Size(80, 30) : new Size(30, 80);
 }

+ 50 - 216
src/YZWater.Avalonia/Controls/PumpControl.cs

@@ -7,274 +7,108 @@ using System.Globalization;
 
 namespace YZWater.Avalonia.Controls;
 
-/// <summary>
-/// 娉垫帶浠 - 妯℃嫙 HslPumpOne锛堜紭鍖栫増锛
-/// </summary>
 public class PumpControl : Control
 {
     public static readonly StyledProperty<bool> IsRunningProperty =
         AvaloniaProperty.Register<PumpControl, bool>(nameof(IsRunning), false);
-
     public static readonly StyledProperty<double> SpeedProperty =
         AvaloniaProperty.Register<PumpControl, double>(nameof(Speed), 0.0);
-
     public static readonly StyledProperty<double> FrequencyProperty =
         AvaloniaProperty.Register<PumpControl, double>(nameof(Frequency), 50.0);
-
     public static readonly StyledProperty<string> TextProperty =
         AvaloniaProperty.Register<PumpControl, string>(nameof(Text), string.Empty);
-
     public static readonly StyledProperty<IBrush> RunningColorProperty =
-        AvaloniaProperty.Register<PumpControl, IBrush>(nameof(RunningColor), new SolidColorBrush(Color.Parse("#4CAF50")));
-
+        AvaloniaProperty.Register<PumpControl, IBrush>(nameof(RunningColor));
     public static readonly StyledProperty<IBrush> StoppedColorProperty =
-        AvaloniaProperty.Register<PumpControl, IBrush>(nameof(StoppedColor), new SolidColorBrush(Color.Parse("#607D8B")));
-
-    public static readonly StyledProperty<IBrush> FaultColorProperty =
-        AvaloniaProperty.Register<PumpControl, IBrush>(nameof(FaultColor), new SolidColorBrush(Color.Parse("#F44336")));
-
-    public bool IsRunning
-    {
-        get => GetValue(IsRunningProperty);
-        set => SetValue(IsRunningProperty, value);
-    }
-
-    public double Speed
-    {
-        get => GetValue(SpeedProperty);
-        set => SetValue(SpeedProperty, value);
-    }
-
-    public double Frequency
-    {
-        get => GetValue(FrequencyProperty);
-        set => SetValue(FrequencyProperty, value);
-    }
+        AvaloniaProperty.Register<PumpControl, IBrush>(nameof(StoppedColor));
 
-    public string Text
-    {
-        get => GetValue(TextProperty);
-        set => SetValue(TextProperty, value);
-    }
-
-    public IBrush RunningColor
-    {
-        get => GetValue(RunningColorProperty);
-        set => SetValue(RunningColorProperty, value);
-    }
+    public bool IsRunning { get => GetValue(IsRunningProperty); set => SetValue(IsRunningProperty, value); }
+    public double Speed { get => GetValue(SpeedProperty); set => SetValue(SpeedProperty, value); }
+    public double Frequency { get => GetValue(FrequencyProperty); set => SetValue(FrequencyProperty, value); }
+    public string Text { get => GetValue(TextProperty); set => SetValue(TextProperty, value); }
+    public IBrush RunningColor { get => GetValue(RunningColorProperty); set => SetValue(RunningColorProperty, value); }
+    public IBrush StoppedColor { get => GetValue(StoppedColorProperty); set => SetValue(StoppedColorProperty, value); }
 
-    public IBrush StoppedColor
-    {
-        get => GetValue(StoppedColorProperty);
-        set => SetValue(StoppedColorProperty, value);
-    }
-
-    public IBrush FaultColor
-    {
-        get => GetValue(FaultColorProperty);
-        set => SetValue(FaultColorProperty, value);
-    }
-
-    private double _rotationAngle = 0;
-    private IDisposable? _timerSubscription;
+    private double _rotationAngle;
+    private IDisposable? _timer;
 
     static PumpControl()
     {
-        AffectsRender<PumpControl>(IsRunningProperty, SpeedProperty, FrequencyProperty,
-            TextProperty, RunningColorProperty, StoppedColorProperty, FaultColorProperty);
-    }
-
-    protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
-    {
-        base.OnAttachedToVisualTree(e);
-        if (IsRunning)
-        {
-            StartAnimation();
-        }
-    }
-
-    protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
-    {
-        base.OnDetachedFromVisualTree(e);
-        StopAnimation();
+        AffectsRender<PumpControl>(IsRunningProperty, SpeedProperty, FrequencyProperty, TextProperty, RunningColorProperty, StoppedColorProperty);
     }
 
+    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)
-            {
-                StartAnimation();
-            }
-            else
-            {
-                StopAnimation();
-                _rotationAngle = 0;
-            }
-            InvalidateVisual();
-        }
+        if (change.Property == IsRunningProperty) { if (IsRunning) StartAnim(); else { StopAnim(); _rotationAngle = 0; } }
     }
 
-    private void StartAnimation()
-    {
-        StopAnimation();
-        _timerSubscription = DispatcherTimer.Run(() =>
-        {
-            _rotationAngle += Frequency / 20.0;
-            if (_rotationAngle >= 360) _rotationAngle -= 360;
-            InvalidateVisual();
-            return true;
-        }, TimeSpan.FromMilliseconds(30));
-    }
-
-    private void StopAnimation()
-    {
-        _timerSubscription?.Dispose();
-        _timerSubscription = null;
-    }
+    private void StartAnim() { StopAnim(); _timer = DispatcherTimer.Run(() => { _rotationAngle += Frequency / 20; if (_rotationAngle >= 360) _rotationAngle -= 360; InvalidateVisual(); return true; }, TimeSpan.FromMilliseconds(30)); }
+    private void StopAnim() { _timer?.Dispose(); _timer = null; }
 
     public override void Render(DrawingContext context)
     {
         base.Render(context);
-
         var bounds = new Rect(Bounds.Size);
-        var centerX = bounds.Width / 2;
-        var centerY = bounds.Height / 2 - 5;
+        var cx = bounds.Width / 2;
+        var cy = bounds.Height / 2 - 5;
         var radius = Math.Min(bounds.Width, bounds.Height) / 2 - 15;
         var typeface = new Typeface("Microsoft YaHei", FontStyle.Normal, FontWeight.Bold);
 
-        // 纭畾鐘舵侀鑹
-        var statusColor = IsRunning ? RunningColor : StoppedColor;
-        var statusBrush = statusColor as SolidColorBrush ?? new SolidColorBrush(Colors.Gray);
+        var statusColor = (IsRunning ? RunningColor : StoppedColor) ?? (IsRunning ? ThemeHelper.Success() : ThemeHelper.TextDisabled());
+        var statusBrush = statusColor as SolidColorBrush ?? ThemeHelper.Success();
 
-        // 缁樺埗澶栧湀鍙戝厜鏁堟灉锛堣繍琛屾椂锛
+        // 鍙戝厜
         if (IsRunning)
-        {
-            var glowBrush = new SolidColorBrush(statusBrush.Color, 0.2);
-            context.DrawEllipse(glowBrush, null, new Rect(
-                centerX - radius - 8,
-                centerY - radius - 8,
-                (radius + 8) * 2,
-                (radius + 8) * 2));
-        }
-
-        // 缁樺埗澶栧3
-        var outerPen = new Pen(new SolidColorBrush(Color.Parse("#455A64")), 3);
-        context.DrawEllipse(null, outerPen, new Rect(
-            centerX - radius,
-            centerY - radius,
-            radius * 2,
-            radius * 2));
+            context.DrawEllipse(new SolidColorBrush((statusBrush as SolidColorBrush)?.Color ?? Colors.Gray, 0.2), null, new Rect(cx - radius - 8, cy - radius - 8, (radius + 8) * 2, (radius + 8) * 2));
 
-        // 缁樺埗鍐呴儴鑳屾櫙
-        var innerBg = new SolidColorBrush(Color.Parse("#1E272E"));
-        context.DrawEllipse(innerBg, null, new Rect(
-            centerX - radius + 3,
-            centerY - radius + 3,
-            (radius - 3) * 2,
-            (radius - 3) * 2));
+        // 澶栧3
+        context.DrawEllipse(null, new Pen(ThemeHelper.Border(), 3), new Rect(cx - radius, cy - radius, radius * 2, radius * 2));
+        context.DrawEllipse(ThemeHelper.PanelBg(), null, new Rect(cx - radius + 3, cy - radius + 3, (radius - 3) * 2, (radius - 3) * 2));
 
-        // 缁樺埗娉鍙剁墖
+        // 鍙剁墖
         var bladeCount = 6;
-        var bladeLength = radius * 0.65;
-        var bladeWidth = radius * 0.18;
-
+        var bladeLen = radius * 0.65;
+        var bladeW = radius * 0.18;
         for (int i = 0; i < bladeCount; i++)
         {
             var angle = _rotationAngle + (360.0 / bladeCount) * i;
-            var radians = angle * Math.PI / 180;
-
-            var bladeX = centerX + Math.Cos(radians) * bladeLength * 0.5;
-            var bladeY = centerY + Math.Sin(radians) * bladeLength * 0.5;
-
-            // 缁樺埗鍙剁墖锛堝甫娓愬彉鏁堟灉锛
-            var bladeBrush = new SolidColorBrush(IsRunning
-                ? Color.FromArgb(200, statusBrush.Color.R, statusBrush.Color.G, statusBrush.Color.B)
-                : Color.Parse("#546E7A"));
-
-            var bladePath = new StreamGeometry();
-            using (var ctx = bladePath.Open())
-            {
-                ctx.BeginFigure(new Point(bladeX - bladeWidth / 2, bladeY - bladeLength / 2), true);
-                ctx.LineTo(new Point(bladeX + bladeWidth / 2, bladeY - bladeLength / 2));
-                ctx.LineTo(new Point(bladeX + bladeWidth / 2, bladeY + bladeLength / 2));
-                ctx.LineTo(new Point(bladeX - bladeWidth / 2, bladeY + bladeLength / 2));
-                ctx.EndFigure(true);
-            }
-
-            // 鏃嬭浆鍙剁墖 - 浣跨敤鐭╅樀鍙樻崲
-            var rotationRadians = angle * Math.PI / 180;
-            var cos = Math.Cos(rotationRadians);
-            var sin = Math.Sin(rotationRadians);
-            var transform = new Matrix(cos, sin, -sin, cos, bladeX - bladeX * cos + bladeY * sin, bladeY - bladeX * sin - bladeY * cos);
+            var bx = cx + Math.Cos(angle * Math.PI / 180) * bladeLen * 0.5;
+            var by = cy + Math.Sin(angle * Math.PI / 180) * bladeLen * 0.5;
+            var bladeBrush = IsRunning ? new SolidColorBrush((statusBrush as SolidColorBrush)?.Color ?? Colors.Gray, 200) : ThemeHelper.TextDisabled();
+            var rad = angle * Math.PI / 180;
+            var cos = Math.Cos(rad); var sin = Math.Sin(rad);
+            var transform = new Matrix(cos, sin, -sin, cos, bx - bx * cos + by * sin, by - bx * sin - by * cos);
             using (context.PushTransform(transform))
-            {
-                context.DrawGeometry(bladeBrush, null, bladePath);
-            }
+                context.DrawRectangle(bladeBrush, null, new Rect(bx - bladeW / 2, by - bladeLen / 2, bladeW, bladeLen));
         }
 
-        // 缁樺埗涓績鍦嗭紙甯︽笎鍙橈級
-        var centerRadius = radius * 0.25;
-        var centerBrush = new SolidColorBrush(IsRunning ? statusBrush.Color : Color.Parse("#455A64"));
-        context.DrawEllipse(centerBrush, null, new Rect(
-            centerX - centerRadius,
-            centerY - centerRadius,
-            centerRadius * 2,
-            centerRadius * 2));
-
-        // 涓績楂樺厜
-        var highlightBrush = new SolidColorBrush(Colors.White, 0.3);
-        context.DrawEllipse(highlightBrush, null, new Rect(
-            centerX - centerRadius * 0.5,
-            centerY - centerRadius * 0.5,
-            centerRadius,
-            centerRadius));
+        // 涓績
+        var cr = radius * 0.25;
+        context.DrawEllipse(IsRunning ? statusColor : ThemeHelper.Border(), null, new Rect(cx - cr, cy - cr, cr * 2, cr * 2));
+        context.DrawEllipse(new SolidColorBrush(Colors.White, 0.3), null, new Rect(cx - cr * 0.5, cy - cr * 0.5, cr, cr));
 
-        // 缁樺埗鐘舵佹寚绀虹伅
-        var indicatorSize = 8;
-        var indicatorX = bounds.Width - 20;
-        var indicatorY = 10;
-        var indicatorBrush = IsRunning
-            ? new SolidColorBrush(Color.Parse("#4CAF50"))
-            : new SolidColorBrush(Color.Parse("#78909C"));
+        // 鎸囩ず鐏
+        var indColor = IsRunning ? ThemeHelper.Success() : ThemeHelper.TextDisabled();
+        if (IsRunning) context.DrawEllipse(new SolidColorBrush((statusBrush as SolidColorBrush)?.Color ?? Colors.Gray, 0.3), null, new Rect(bounds.Width - 23, 7, 16, 16));
+        context.DrawEllipse(indColor, null, new Rect(bounds.Width - 20, 10, 10, 10));
 
-        // 鎸囩ず鐏彂鍏
-        if (IsRunning)
-        {
-            context.DrawEllipse(new SolidColorBrush(indicatorBrush.Color, 0.3), null,
-                new Rect(indicatorX - 4, indicatorY - 4, 16, 16));
-        }
-
-        context.DrawEllipse(indicatorBrush, null,
-            new Rect(indicatorX, indicatorY, indicatorSize, indicatorSize));
-
-        // 缁樺埗鏂囧瓧鏍囩
+        // 鏂囧瓧
         if (!string.IsNullOrEmpty(Text))
         {
-            var textBrush = new SolidColorBrush(Colors.White);
-            var text = new FormattedText(Text, CultureInfo.CurrentCulture,
-                FlowDirection.LeftToRight, typeface, 11, textBrush);
-            var textPoint = new Point(
-                centerX - text.Width / 2,
-                bounds.Bottom - text.Height - 5);
-            context.DrawText(text, textPoint);
+            var t = new FormattedText(Text, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, typeface, 11, ThemeHelper.TextPrimary());
+            context.DrawText(t, new Point(cx - t.Width / 2, bounds.Bottom - t.Height - 5));
         }
 
-        // 缁樺埗棰戠巼鏄剧ず
+        // 棰戠巼
         if (IsRunning)
         {
-            var freqStr = $"{Frequency:F0}Hz";
-            var freqText = new FormattedText(freqStr, CultureInfo.CurrentCulture,
-                FlowDirection.LeftToRight, new Typeface("Microsoft YaHei"), 9,
-                new SolidColorBrush(statusBrush.Color));
-            context.DrawText(freqText, new Point(centerX - freqText.Width / 2, centerY + radius + 5));
+            var f = new FormattedText($"{Frequency:F0}Hz", CultureInfo.CurrentCulture, FlowDirection.LeftToRight, new Typeface("Microsoft YaHei"), 9, statusColor);
+            context.DrawText(f, new Point(cx - f.Width / 2, cy + radius + 5));
         }
     }
 
-    protected override Size MeasureOverride(Size availableSize)
-    {
-        return new Size(70, 80);
-    }
+    protected override Size MeasureOverride(Size availableSize) => new Size(70, 80);
 }

+ 15 - 46
src/YZWater.Avalonia/Controls/StatusCard.cs

@@ -5,28 +5,20 @@ using System.Globalization;
 
 namespace YZWater.Avalonia.Controls;
 
-/// <summary>
-/// 鐘舵佸崱鐗囨帶浠 - 宸ヤ笟椋庢牸
-/// </summary>
 public class StatusCard : Control
 {
     public static readonly StyledProperty<string> TitleProperty =
         AvaloniaProperty.Register<StatusCard, string>(nameof(Title), "璁惧");
-
     public static readonly StyledProperty<string> StatusProperty =
         AvaloniaProperty.Register<StatusCard, string>(nameof(Status), "RUNNING");
-
     public static readonly StyledProperty<string> IconProperty =
         AvaloniaProperty.Register<StatusCard, string>(nameof(Icon), "P1");
-
     public static readonly StyledProperty<bool> IsActiveProperty =
         AvaloniaProperty.Register<StatusCard, bool>(nameof(IsActive), false);
-
     public static readonly StyledProperty<IBrush> ActiveColorProperty =
-        AvaloniaProperty.Register<StatusCard, IBrush>(nameof(ActiveColor), new SolidColorBrush(Color.Parse("#00897B")));
-
+        AvaloniaProperty.Register<StatusCard, IBrush>(nameof(ActiveColor));
     public static readonly StyledProperty<IBrush> InactiveColorProperty =
-        AvaloniaProperty.Register<StatusCard, IBrush>(nameof(InactiveColor), new SolidColorBrush(Color.Parse("#4B5563")));
+        AvaloniaProperty.Register<StatusCard, IBrush>(nameof(InactiveColor));
 
     public string Title { get => GetValue(TitleProperty); set => SetValue(TitleProperty, value); }
     public string Status { get => GetValue(StatusProperty); set => SetValue(StatusProperty, value); }
@@ -35,58 +27,35 @@ public class StatusCard : Control
     public IBrush ActiveColor { get => GetValue(ActiveColorProperty); set => SetValue(ActiveColorProperty, value); }
     public IBrush InactiveColor { get => GetValue(InactiveColorProperty); set => SetValue(InactiveColorProperty, value); }
 
-    static StatusCard()
-    {
-        AffectsRender<StatusCard>(TitleProperty, StatusProperty, IconProperty,
-            IsActiveProperty, ActiveColorProperty, InactiveColorProperty);
-    }
+    static StatusCard() { AffectsRender<StatusCard>(TitleProperty, StatusProperty, IconProperty, IsActiveProperty, ActiveColorProperty, InactiveColorProperty); }
 
     public override void Render(DrawingContext context)
     {
         base.Render(context);
-
         var bounds = new Rect(Bounds.Size);
-        var statusColor = IsActive ? ActiveColor : InactiveColor;
-        var statusBrush = statusColor as SolidColorBrush ?? new SolidColorBrush(Colors.Gray);
+        var statusColor = IsActive ? (ActiveColor ?? ThemeHelper.Success()) : (InactiveColor ?? ThemeHelper.TextDisabled());
+        var statusBrush = statusColor as SolidColorBrush ?? ThemeHelper.Success();
         var monoTypeface = new Typeface("Consolas, monospace");
         var labelTypeface = new Typeface("Microsoft YaHei");
 
         // 鍗$墖鑳屾櫙
-        context.DrawRectangle(new SolidColorBrush(Color.Parse("#0D1117")), null, bounds);
-
+        context.DrawRectangle(ThemeHelper.PanelBg(), null, bounds);
         // 宸︿晶鐘舵佹潯
         context.DrawRectangle(statusColor, null, new Rect(0, 0, 3, bounds.Height));
-
-        // LED 鎸囩ず鐏
-        var ledX = 12.0;
-        var ledY = bounds.Height / 2 - 5;
-        if (IsActive)
-        {
-            context.DrawEllipse(new SolidColorBrush(statusBrush.Color, 0.3), null,
-                new Rect(ledX - 3, ledY - 3, 16, 16));
-        }
+        // LED
+        var ledX = 12.0; var ledY = bounds.Height / 2 - 5;
+        if (IsActive) context.DrawEllipse(new SolidColorBrush((statusBrush as SolidColorBrush)?.Color ?? Colors.Gray, 0.3), null, new Rect(ledX - 3, ledY - 3, 16, 16));
         context.DrawEllipse(statusColor, null, new Rect(ledX, ledY, 10, 10));
-
-        // 鍥炬爣鏂囧瓧
-        var iconText = new FormattedText(Icon, CultureInfo.CurrentCulture,
-            FlowDirection.LeftToRight, monoTypeface, 11, new SolidColorBrush(Colors.White));
+        // 鍥炬爣
+        var iconText = new FormattedText(Icon, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, monoTypeface, 11, ThemeHelper.TextPrimary());
         context.DrawText(iconText, new Point(28, 6));
-
         // 鏍囬
-        var titleText = new FormattedText(Title, CultureInfo.CurrentCulture,
-            FlowDirection.LeftToRight, labelTypeface, 10, new SolidColorBrush(Color.Parse("#9CA3AF")));
+        var titleText = new FormattedText(Title, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, labelTypeface, 10, ThemeHelper.TextSecondary());
         context.DrawText(titleText, new Point(28, 22));
-
-        // 鐘舵佹枃瀛
-        var statusTextColor = IsActive ? "#00897B" : "#6B7280";
-        var statusText = new FormattedText(Status, CultureInfo.CurrentCulture,
-            FlowDirection.LeftToRight, monoTypeface, 9,
-            new SolidColorBrush(Color.Parse(statusTextColor)));
+        // 鐘舵
+        var statusText = new FormattedText(Status, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, monoTypeface, 9, statusColor);
         context.DrawText(statusText, new Point(bounds.Width - statusText.Width - 10, bounds.Height / 2 - statusText.Height / 2));
     }
 
-    protected override Size MeasureOverride(Size availableSize)
-    {
-        return new Size(230, 42);
-    }
+    protected override Size MeasureOverride(Size availableSize) => new Size(230, 42);
 }

+ 39 - 0
src/YZWater.Avalonia/Controls/ThemeHelper.cs

@@ -0,0 +1,39 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Media;
+
+namespace YZWater.Avalonia.Controls;
+
+/// <summary>
+/// 涓婚璧勬簮杈呭姪鏂规硶
+/// </summary>
+internal static class ThemeHelper
+{
+    /// <summary>
+    /// 浠庡簲鐢ㄨ祫婧愯幏鍙栫敾鍒凤紝鎵句笉鍒板垯杩斿洖榛樿鍊
+    /// </summary>
+    public static IBrush GetBrush(string key, string fallbackHex)
+    {
+        try
+        {
+            var resources = Application.Current?.Resources;
+            if (resources != null && resources.TryGetResource(key, null, out var value) && value is IBrush brush)
+                return brush;
+        }
+        catch { }
+        return new SolidColorBrush(Color.Parse(fallbackHex));
+    }
+
+    // 甯哥敤蹇嵎鏂规硶
+    public static IBrush SurfaceBg() => GetBrush("SurfaceBgBrush", "#111820");
+    public static IBrush PanelBg() => GetBrush("PanelBgBrush", "#0D1117");
+    public static IBrush Border() => GetBrush("BorderBrush", "#1C2333");
+    public static IBrush TextPrimary() => GetBrush("TextPrimaryBrush", "#E6EDF3");
+    public static IBrush TextSecondary() => GetBrush("TextSecondaryBrush", "#9CA3AF");
+    public static IBrush TextTertiary() => GetBrush("TextTertiaryBrush", "#6B7280");
+    public static IBrush TextDisabled() => GetBrush("TextDisabledBrush", "#4B5563");
+    public static IBrush Success() => GetBrush("SuccessBrush", "#00897B");
+    public static IBrush Warning() => GetBrush("WarningBrush", "#F59E0B");
+    public static IBrush Danger() => GetBrush("DangerBrush", "#EF4444");
+    public static IBrush Info() => GetBrush("InfoBrush", "#3B82F6");
+}

+ 29 - 90
src/YZWater.Avalonia/Controls/ValveControl.cs

@@ -7,121 +7,60 @@ using YZWater.Core.ViewModels;
 
 namespace YZWater.Avalonia.Controls;
 
-/// <summary>
-/// 闃闂ㄦ帶浠 - 妯℃嫙 HslSwitch
-/// </summary>
 public class ValveControl : Control
 {
     public static readonly StyledProperty<ValveStatus> StatusProperty =
         AvaloniaProperty.Register<ValveControl, ValveStatus>(nameof(Status), ValveStatus.Middle);
-
     public static readonly StyledProperty<string> TextProperty =
         AvaloniaProperty.Register<ValveControl, string>(nameof(Text), "闃闂");
-
     public static readonly StyledProperty<IBrush> ValveColorProperty =
-        AvaloniaProperty.Register<ValveControl, IBrush>(nameof(ValveColor), new SolidColorBrush(Color.Parse("#FF5722")));
-
+        AvaloniaProperty.Register<ValveControl, IBrush>(nameof(ValveColor));
     public static readonly StyledProperty<IBrush> BackgroundColorProperty =
-        AvaloniaProperty.Register<ValveControl, IBrush>(nameof(BackgroundColor), new SolidColorBrush(Color.Parse("#333333")));
+        AvaloniaProperty.Register<ValveControl, IBrush>(nameof(BackgroundColor));
 
-    public ValveStatus Status
-    {
-        get => GetValue(StatusProperty);
-        set => SetValue(StatusProperty, value);
-    }
+    public ValveStatus Status { get => GetValue(StatusProperty); set => SetValue(StatusProperty, value); }
+    public string Text { get => GetValue(TextProperty); set => SetValue(TextProperty, value); }
+    public IBrush ValveColor { get => GetValue(ValveColorProperty); set => SetValue(ValveColorProperty, value); }
+    public IBrush BackgroundColor { get => GetValue(BackgroundColorProperty); set => SetValue(BackgroundColorProperty, value); }
 
-    public string Text
-    {
-        get => GetValue(TextProperty);
-        set => SetValue(TextProperty, value);
-    }
-
-    public IBrush ValveColor
-    {
-        get => GetValue(ValveColorProperty);
-        set => SetValue(ValveColorProperty, value);
-    }
-
-    public IBrush BackgroundColor
-    {
-        get => GetValue(BackgroundColorProperty);
-        set => SetValue(BackgroundColorProperty, value);
-    }
-
-    static ValveControl()
-    {
-        AffectsRender<ValveControl>(StatusProperty, TextProperty, ValveColorProperty, BackgroundColorProperty);
-    }
+    static ValveControl() { AffectsRender<ValveControl>(StatusProperty, TextProperty, ValveColorProperty, BackgroundColorProperty); }
 
     public override void Render(DrawingContext context)
     {
         base.Render(context);
-
         var bounds = new Rect(Bounds.Size);
-        var centerX = bounds.Width / 2;
-        var centerY = bounds.Height / 2;
+        var cx = bounds.Width / 2; var cy = bounds.Height / 2;
         var size = Math.Min(bounds.Width, bounds.Height) * 0.8;
-        var typeface = new Typeface("Microsoft YaHei");
-
-        // 缁樺埗鑳屾櫙鍦
-        var backgroundRadius = size / 2;
-        context.DrawEllipse(BackgroundColor, null, new Rect(
-            centerX - backgroundRadius,
-            centerY - backgroundRadius,
-            backgroundRadius * 2,
-            backgroundRadius * 2));
-
-        // 缁樺埗闃闂ㄦ墜鏌
-        var handleLength = size * 0.35;
-        var handleWidth = size * 0.15;
+        var typeface = new Typeface("Microsoft YaHei", FontStyle.Normal, FontWeight.Bold);
 
-        var angle = Status switch
-        {
-            ValveStatus.Open => 0,
-            ValveStatus.Middle => 45,
-            ValveStatus.Closed => 90,
-            _ => 45
-        };
-
-        // 绠鍖栫粯鍒 - 涓嶄娇鐢ㄦ棆杞紝鐩存帴缁樺埗鐭╁舰
-        var handleRect = new Rect(
-            centerX - handleWidth / 2,
-            centerY - handleLength,
-            handleWidth,
-            handleLength * 2);
+        // 鑳屾櫙鍦
+        var bg = BackgroundColor ?? ThemeHelper.Border();
+        context.DrawEllipse(bg, null, new Rect(cx - size / 2, cy - size / 2, size, size));
 
-        context.DrawRectangle(ValveColor, null, handleRect);
+        // 鎵嬫焺
+        var handleLen = size * 0.35;
+        var handleW = size * 0.15;
+        var vc = ValveColor ?? ThemeHelper.Warning();
+        context.DrawRectangle(vc, null, new Rect(cx - handleW / 2, cy - handleLen, handleW, handleLen * 2));
 
-        // 缁樺埗鐘舵佹寚绀哄櫒
-        var indicatorSize = size * 0.15;
-        var indicatorColor = Status switch
+        // 鐘舵佹寚绀虹伅
+        var indSize = size * 0.15;
+        var indColor = Status switch
         {
-            ValveStatus.Open => new SolidColorBrush(Colors.Green),
-            ValveStatus.Middle => new SolidColorBrush(Colors.Yellow),
-            ValveStatus.Closed => new SolidColorBrush(Colors.Red),
-            _ => new SolidColorBrush(Colors.Gray)
+            ValveStatus.Open => ThemeHelper.Success(),
+            ValveStatus.Middle => ThemeHelper.Warning(),
+            ValveStatus.Closed => ThemeHelper.Danger(),
+            _ => ThemeHelper.TextDisabled()
         };
+        context.DrawEllipse(indColor, null, new Rect(cx - indSize / 2, cy - size / 2 - indSize - 5, indSize, indSize));
 
-        context.DrawEllipse(indicatorColor, null, new Rect(
-            centerX - indicatorSize / 2,
-            centerY - backgroundRadius - indicatorSize - 5,
-            indicatorSize,
-            indicatorSize));
-
-        // 缁樺埗鏂囧瓧
+        // 鏂囧瓧
         if (!string.IsNullOrEmpty(Text))
         {
-            var foreground = new SolidColorBrush(Colors.White);
-            var text = new FormattedText(Text, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, typeface, 10, foreground);
-            var textPoint = new Point(
-                centerX - text.Width / 2,
-                bounds.Bottom - text.Height - 2);
-            context.DrawText(text, textPoint);
+            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)
-    {
-        return new Size(50, 70);
-    }
+    protected override Size MeasureOverride(Size availableSize) => new Size(50, 70);
 }

+ 24 - 79
src/YZWater.Avalonia/Controls/WaterTankControl.cs

@@ -6,46 +6,21 @@ using System.Globalization;
 
 namespace YZWater.Avalonia.Controls;
 
-/// <summary>
-/// 姘寸鎺т欢锛堢畝鍖栫増锛
-/// </summary>
 public class WaterTankControl : Control
 {
     public static readonly StyledProperty<double> WaterLevelProperty =
         AvaloniaProperty.Register<WaterTankControl, double>(nameof(WaterLevel), 50.0);
-
     public static readonly StyledProperty<string> TextProperty =
         AvaloniaProperty.Register<WaterTankControl, string>(nameof(Text), string.Empty);
-
     public static readonly StyledProperty<IBrush> WaterColorProperty =
-        AvaloniaProperty.Register<WaterTankControl, IBrush>(nameof(WaterColor), new SolidColorBrush(Color.Parse("#00BCD4")));
-
+        AvaloniaProperty.Register<WaterTankControl, IBrush>(nameof(WaterColor));
     public static readonly StyledProperty<IBrush> BorderColorProperty =
-        AvaloniaProperty.Register<WaterTankControl, IBrush>(nameof(BorderColor), new SolidColorBrush(Color.Parse("#37474F")));
-
-    public double WaterLevel
-    {
-        get => GetValue(WaterLevelProperty);
-        set => SetValue(WaterLevelProperty, value);
-    }
-
-    public string Text
-    {
-        get => GetValue(TextProperty);
-        set => SetValue(TextProperty, value);
-    }
-
-    public IBrush WaterColor
-    {
-        get => GetValue(WaterColorProperty);
-        set => SetValue(WaterColorProperty, value);
-    }
+        AvaloniaProperty.Register<WaterTankControl, IBrush>(nameof(BorderColor));
 
-    public IBrush BorderColor
-    {
-        get => GetValue(BorderColorProperty);
-        set => SetValue(BorderColorProperty, value);
-    }
+    public double WaterLevel { get => GetValue(WaterLevelProperty); set => SetValue(WaterLevelProperty, value); }
+    public string Text { get => GetValue(TextProperty); set => SetValue(TextProperty, value); }
+    public IBrush WaterColor { get => GetValue(WaterColorProperty); set => SetValue(WaterColorProperty, value); }
+    public IBrush BorderColor { get => GetValue(BorderColorProperty); set => SetValue(BorderColorProperty, value); }
 
     static WaterTankControl()
     {
@@ -55,68 +30,38 @@ public class WaterTankControl : Control
     public override void Render(DrawingContext context)
     {
         base.Render(context);
-
         var bounds = new Rect(Bounds.Size);
         var typeface = new Typeface("Microsoft YaHei", FontStyle.Normal, FontWeight.Bold);
         var edgeWidth = 3.0;
 
-        // 缁樺埗鑳屾櫙
-        var bgBrush = new SolidColorBrush(Color.Parse("#263238"));
-        context.DrawRectangle(bgBrush, null, bounds);
-
-        // 缁樺埗杈规
-        var borderPen = new Pen(BorderColor, edgeWidth);
-        context.DrawRectangle(null, borderPen, bounds);
+        // 鑳屾櫙
+        context.DrawRectangle(ThemeHelper.PanelBg(), null, bounds);
+        // 杈规
+        var borderColor = BorderColor ?? ThemeHelper.Border();
+        context.DrawRectangle(null, new Pen(borderColor, edgeWidth), bounds);
 
-        // 璁$畻姘翠綅楂樺害
+        // 姘翠綅
         var waterHeight = (bounds.Height - edgeWidth * 2) * Math.Max(0, Math.Min(100, WaterLevel)) / 100.0;
-        var waterTop = bounds.Y + bounds.Height - edgeWidth - waterHeight;
-
-        // 缁樺埗姘
         if (waterHeight > 0)
         {
-            var waterColor = WaterColor as SolidColorBrush;
-            var waterBrush = waterColor != null
-                ? new SolidColorBrush(waterColor.Color, 0.8)
-                : WaterColor;
-
-            var waterRect = new Rect(
-                bounds.X + edgeWidth,
-                waterTop,
-                bounds.Width - edgeWidth * 2,
-                waterHeight);
-            context.DrawRectangle(waterBrush, null, waterRect);
+            var waterColor = WaterColor ?? ThemeHelper.Success();
+            var waterBrush = waterColor is SolidColorBrush sc ? new SolidColorBrush(sc.Color, 0.8) : waterColor;
+            context.DrawRectangle(waterBrush, null, new Rect(bounds.X + edgeWidth, bounds.Y + bounds.Height - edgeWidth - waterHeight, bounds.Width - edgeWidth * 2, waterHeight));
         }
 
-        // 缁樺埗鏍囬
+        // 鏍囬
         if (!string.IsNullOrEmpty(Text))
         {
-            var titleBrush = new SolidColorBrush(Colors.White);
-            var titleText = new FormattedText(Text, CultureInfo.CurrentCulture,
-                FlowDirection.LeftToRight, typeface, 12, titleBrush);
-            var titlePoint = new Point(
-                bounds.X + (bounds.Width - titleText.Width) / 2,
-                bounds.Y + edgeWidth + 5);
-            context.DrawText(titleText, titlePoint);
+            var titleText = new FormattedText(Text, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, typeface, 12, ThemeHelper.TextPrimary());
+            context.DrawText(titleText, new Point(bounds.X + (bounds.Width - titleText.Width) / 2, bounds.Y + edgeWidth + 5));
         }
 
-        // 缁樺埗娑蹭綅鐧惧垎姣
-        var levelStr = $"{WaterLevel:F1}%";
-        var levelText = new FormattedText(levelStr, CultureInfo.CurrentCulture,
-            FlowDirection.LeftToRight, typeface, 14, new SolidColorBrush(Colors.White));
-
-        var levelBgRect = new Rect(
-            bounds.X + (bounds.Width - levelText.Width - 10) / 2,
-            bounds.Y + bounds.Height - edgeWidth - levelText.Height - 10,
-            levelText.Width + 10,
-            levelText.Height + 6);
-
-        context.DrawRectangle(new SolidColorBrush(Colors.Black, 0.5), null, levelBgRect);
-        context.DrawText(levelText, new Point(levelBgRect.X + 5, levelBgRect.Y + 3));
+        // 鐧惧垎姣
+        var levelText = new FormattedText($"{WaterLevel:F1}%", CultureInfo.CurrentCulture, FlowDirection.LeftToRight, typeface, 14, ThemeHelper.TextPrimary());
+        var bgRect = new Rect(bounds.X + (bounds.Width - levelText.Width - 10) / 2, bounds.Y + bounds.Height - edgeWidth - levelText.Height - 10, levelText.Width + 10, levelText.Height + 6);
+        context.DrawRectangle(new SolidColorBrush(Colors.Black, 0.5), null, bgRect);
+        context.DrawText(levelText, new Point(bgRect.X + 5, bgRect.Y + 3));
     }
 
-    protected override Size MeasureOverride(Size availableSize)
-    {
-        return new Size(120, 160);
-    }
+    protected override Size MeasureOverride(Size availableSize) => new Size(120, 160);
 }