|
@@ -0,0 +1,584 @@
|
|
|
|
|
+using Avalonia;
|
|
|
|
|
+using Avalonia.Controls;
|
|
|
|
|
+using Avalonia.Media;
|
|
|
|
|
+using Avalonia.Threading;
|
|
|
|
|
+using System;
|
|
|
|
|
+using System.Collections.Generic;
|
|
|
|
|
+using System.Globalization;
|
|
|
|
|
+using YZWater.Core.ViewModels;
|
|
|
|
|
+
|
|
|
|
|
+namespace YZWater.Avalonia.Controls;
|
|
|
|
|
+
|
|
|
|
|
+/// <summary>
|
|
|
|
|
+/// 閫氱敤娉电珯 2D 宸ヨ壓娴佺▼鍥炬帶浠 (SCADA 椋庢牸)銆
|
|
|
|
|
+/// 璁捐鐢诲竷 960脳540锛岀瓑姣旂缉鏀鹃傞厤瀹為檯灏哄銆
|
|
|
|
|
+/// </summary>
|
|
|
|
|
+public class PumpStation2DControl : Control
|
|
|
|
|
+{
|
|
|
|
|
+ // 鈺愨晲鈺 Styled Properties锛堟帴鍙d笉鍙橈紝淇濇寔 ViewAView.axaml 缁戝畾鍏煎锛 鈺愨晲鈺
|
|
|
|
|
+
|
|
|
|
|
+ public static readonly StyledProperty<double> Tank1LevelProperty = AvaloniaProperty.Register<PumpStation2DControl, double>(nameof(Tank1Level));
|
|
|
|
|
+ public static readonly StyledProperty<double> Tank2LevelProperty = AvaloniaProperty.Register<PumpStation2DControl, double>(nameof(Tank2Level));
|
|
|
|
|
+ public static readonly StyledProperty<double> Tank3LevelProperty = AvaloniaProperty.Register<PumpStation2DControl, double>(nameof(Tank3Level));
|
|
|
|
|
+ public static readonly StyledProperty<double> Tank4LevelProperty = AvaloniaProperty.Register<PumpStation2DControl, double>(nameof(Tank4Level));
|
|
|
|
|
+ public static readonly StyledProperty<double> InflowRateProperty = AvaloniaProperty.Register<PumpStation2DControl, double>(nameof(InflowRate));
|
|
|
|
|
+ public static readonly StyledProperty<double> OutflowRateProperty = AvaloniaProperty.Register<PumpStation2DControl, double>(nameof(OutflowRate));
|
|
|
|
|
+ public static readonly StyledProperty<bool> Pump1RunningProperty = AvaloniaProperty.Register<PumpStation2DControl, bool>(nameof(Pump1Running));
|
|
|
|
|
+ public static readonly StyledProperty<bool> Pump2RunningProperty = AvaloniaProperty.Register<PumpStation2DControl, bool>(nameof(Pump2Running));
|
|
|
|
|
+ public static readonly StyledProperty<bool> Pump3RunningProperty = AvaloniaProperty.Register<PumpStation2DControl, bool>(nameof(Pump3Running));
|
|
|
|
|
+ public static readonly StyledProperty<bool> Pump4RunningProperty = AvaloniaProperty.Register<PumpStation2DControl, bool>(nameof(Pump4Running));
|
|
|
|
|
+ public static readonly StyledProperty<bool> Pump5RunningProperty = AvaloniaProperty.Register<PumpStation2DControl, bool>(nameof(Pump5Running));
|
|
|
|
|
+ public static readonly StyledProperty<bool> Fan1RunningProperty = AvaloniaProperty.Register<PumpStation2DControl, bool>(nameof(Fan1Running));
|
|
|
|
|
+ public static readonly StyledProperty<bool> Fan2RunningProperty = AvaloniaProperty.Register<PumpStation2DControl, bool>(nameof(Fan2Running));
|
|
|
|
|
+ public static readonly StyledProperty<ValveStatus> Valve1StatusProperty = AvaloniaProperty.Register<PumpStation2DControl, ValveStatus>(nameof(Valve1Status));
|
|
|
|
|
+ public static readonly StyledProperty<ValveStatus> Valve2StatusProperty = AvaloniaProperty.Register<PumpStation2DControl, ValveStatus>(nameof(Valve2Status));
|
|
|
|
|
+ public static readonly StyledProperty<ValveStatus> Valve3StatusProperty = AvaloniaProperty.Register<PumpStation2DControl, ValveStatus>(nameof(Valve3Status));
|
|
|
|
|
+ public static readonly StyledProperty<ValveStatus> Valve4StatusProperty = AvaloniaProperty.Register<PumpStation2DControl, ValveStatus>(nameof(Valve4Status));
|
|
|
|
|
+ public static readonly StyledProperty<bool> IsInflowRunningProperty = AvaloniaProperty.Register<PumpStation2DControl, bool>(nameof(IsInflowRunning));
|
|
|
|
|
+ public static readonly StyledProperty<bool> IsOutflowRunningProperty = AvaloniaProperty.Register<PumpStation2DControl, bool>(nameof(IsOutflowRunning));
|
|
|
|
|
+ public static readonly StyledProperty<bool> HasAlarmProperty = AvaloniaProperty.Register<PumpStation2DControl, bool>(nameof(HasAlarm));
|
|
|
|
|
+ public static readonly StyledProperty<int> AlarmCountProperty = AvaloniaProperty.Register<PumpStation2DControl, int>(nameof(AlarmCount));
|
|
|
|
|
+ public static readonly StyledProperty<int> RunningDeviceCountProperty = AvaloniaProperty.Register<PumpStation2DControl, int>(nameof(RunningDeviceCount));
|
|
|
|
|
+ public static readonly StyledProperty<double> EfficiencyProperty = AvaloniaProperty.Register<PumpStation2DControl, double>(nameof(Efficiency));
|
|
|
|
|
+
|
|
|
|
|
+ public double Tank1Level { get => GetValue(Tank1LevelProperty); set => SetValue(Tank1LevelProperty, value); }
|
|
|
|
|
+ public double Tank2Level { get => GetValue(Tank2LevelProperty); set => SetValue(Tank2LevelProperty, value); }
|
|
|
|
|
+ public double Tank3Level { get => GetValue(Tank3LevelProperty); set => SetValue(Tank3LevelProperty, value); }
|
|
|
|
|
+ public double Tank4Level { get => GetValue(Tank4LevelProperty); set => SetValue(Tank4LevelProperty, value); }
|
|
|
|
|
+ public double InflowRate { get => GetValue(InflowRateProperty); set => SetValue(InflowRateProperty, value); }
|
|
|
|
|
+ public double OutflowRate { get => GetValue(OutflowRateProperty); set => SetValue(OutflowRateProperty, value); }
|
|
|
|
|
+ public bool Pump1Running { get => GetValue(Pump1RunningProperty); set => SetValue(Pump1RunningProperty, value); }
|
|
|
|
|
+ public bool Pump2Running { get => GetValue(Pump2RunningProperty); set => SetValue(Pump2RunningProperty, value); }
|
|
|
|
|
+ public bool Pump3Running { get => GetValue(Pump3RunningProperty); set => SetValue(Pump3RunningProperty, value); }
|
|
|
|
|
+ public bool Pump4Running { get => GetValue(Pump4RunningProperty); set => SetValue(Pump4RunningProperty, value); }
|
|
|
|
|
+ public bool Pump5Running { get => GetValue(Pump5RunningProperty); set => SetValue(Pump5RunningProperty, value); }
|
|
|
|
|
+ public bool Fan1Running { get => GetValue(Fan1RunningProperty); set => SetValue(Fan1RunningProperty, value); }
|
|
|
|
|
+ public bool Fan2Running { get => GetValue(Fan2RunningProperty); set => SetValue(Fan2RunningProperty, value); }
|
|
|
|
|
+ public ValveStatus Valve1Status { get => GetValue(Valve1StatusProperty); set => SetValue(Valve1StatusProperty, value); }
|
|
|
|
|
+ public ValveStatus Valve2Status { get => GetValue(Valve2StatusProperty); set => SetValue(Valve2StatusProperty, value); }
|
|
|
|
|
+ public ValveStatus Valve3Status { get => GetValue(Valve3StatusProperty); set => SetValue(Valve3StatusProperty, value); }
|
|
|
|
|
+ public ValveStatus Valve4Status { get => GetValue(Valve4StatusProperty); set => SetValue(Valve4StatusProperty, value); }
|
|
|
|
|
+ public bool IsInflowRunning { get => GetValue(IsInflowRunningProperty); set => SetValue(IsInflowRunningProperty, value); }
|
|
|
|
|
+ public bool IsOutflowRunning { get => GetValue(IsOutflowRunningProperty); set => SetValue(IsOutflowRunningProperty, value); }
|
|
|
|
|
+ public bool HasAlarm { get => GetValue(HasAlarmProperty); set => SetValue(HasAlarmProperty, value); }
|
|
|
|
|
+ public int AlarmCount { get => GetValue(AlarmCountProperty); set => SetValue(AlarmCountProperty, value); }
|
|
|
|
|
+ public int RunningDeviceCount { get => GetValue(RunningDeviceCountProperty); set => SetValue(RunningDeviceCountProperty, value); }
|
|
|
|
|
+ public double Efficiency { get => GetValue(EfficiencyProperty); set => SetValue(EfficiencyProperty, value); }
|
|
|
|
|
+
|
|
|
|
|
+ // 鈺愨晲鈺 鍔ㄧ敾 鈺愨晲鈺
|
|
|
|
|
+
|
|
|
|
|
+ private double _phase;
|
|
|
|
|
+ private double _pumpAngle;
|
|
|
|
|
+ private double _fanAngle;
|
|
|
|
|
+ private IDisposable? _timer;
|
|
|
|
|
+
|
|
|
|
|
+ static PumpStation2DControl()
|
|
|
|
|
+ {
|
|
|
|
|
+ AffectsRender<PumpStation2DControl>(
|
|
|
|
|
+ Tank1LevelProperty, Tank2LevelProperty, Tank3LevelProperty, Tank4LevelProperty,
|
|
|
|
|
+ InflowRateProperty, OutflowRateProperty,
|
|
|
|
|
+ Pump1RunningProperty, Pump2RunningProperty, Pump3RunningProperty, Pump4RunningProperty, Pump5RunningProperty,
|
|
|
|
|
+ Fan1RunningProperty, Fan2RunningProperty,
|
|
|
|
|
+ Valve1StatusProperty, Valve2StatusProperty, Valve3StatusProperty, Valve4StatusProperty,
|
|
|
|
|
+ IsInflowRunningProperty, IsOutflowRunningProperty, HasAlarmProperty, AlarmCountProperty,
|
|
|
|
|
+ RunningDeviceCountProperty, EfficiencyProperty);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
|
|
|
|
+ {
|
|
|
|
|
+ base.OnAttachedToVisualTree(e);
|
|
|
|
|
+ ThemeHelper.ThemeChanged += OnThemeChanged;
|
|
|
|
|
+ _timer = DispatcherTimer.Run(() =>
|
|
|
|
|
+ {
|
|
|
|
|
+ _phase = (_phase + 1) % 360;
|
|
|
|
|
+ if (AnyPumpRunning) _pumpAngle = (_pumpAngle + 4) % 360;
|
|
|
|
|
+ if (Fan1Running || Fan2Running) _fanAngle = (_fanAngle + 6) % 360;
|
|
|
|
|
+ InvalidateVisual();
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }, TimeSpan.FromMilliseconds(33));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
|
|
|
|
+ {
|
|
|
|
|
+ ThemeHelper.ThemeChanged -= OnThemeChanged;
|
|
|
|
|
+ _timer?.Dispose();
|
|
|
|
|
+ _timer = null;
|
|
|
|
|
+ base.OnDetachedFromVisualTree(e);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void OnThemeChanged() => InvalidateVisual();
|
|
|
|
|
+
|
|
|
|
|
+ private bool AnyPumpRunning => Pump1Running || Pump2Running || Pump3Running || Pump4Running || Pump5Running;
|
|
|
|
|
+
|
|
|
|
|
+ // 鈺愨晲鈺 璁捐鍧愭爣甯搁噺 (960 脳 540) 鈺愨晲鈺
|
|
|
|
|
+
|
|
|
|
|
+ private const double DW = 960, DH = 540;
|
|
|
|
|
+
|
|
|
|
|
+ // 姘寸浣嶇疆: (x, y, width, height)
|
|
|
|
|
+ private static readonly Rect DTank1 = new(55, 130, 120, 140);
|
|
|
|
|
+ private static readonly Rect DTank2 = new(275, 85, 195, 210);
|
|
|
|
|
+ private static readonly Rect DTank3 = new(275, 400, 155, 105);
|
|
|
|
|
+ private static readonly Rect DTank4 = new(720, 130, 120, 140);
|
|
|
|
|
+
|
|
|
|
|
+ // 娉典綅缃 (5 鍙版车鍦ㄩ泦姘翠簳涓嬫柟)
|
|
|
|
|
+ private static readonly Point[] DPumps = { new(300, 340), new(338, 340), new(376, 340), new(414, 340), new(452, 340) };
|
|
|
|
|
+
|
|
|
|
|
+ // 椋庢満浣嶇疆
|
|
|
|
|
+ private static readonly Point DFan1 = new(590, 450);
|
|
|
|
|
+ private static readonly Point DFan2 = new(660, 450);
|
|
|
|
|
+
|
|
|
|
|
+ // 闃闂ㄤ綅缃 (绠¢亾涓婄殑鍏抽敭鑺傜偣)
|
|
|
|
|
+ private static readonly Point DV1 = new(218, 200); // Tank1 鈫 Tank2
|
|
|
|
|
+ private static readonly Point DV2 = new(555, 195); // Header 鈫 Tank4
|
|
|
|
|
+ private static readonly Point DV3 = new(420, 395); // Header 鈫 Tank3 鍒嗘敮
|
|
|
|
|
+ private static readonly Point DV4 = new(230, 450); // Tank2 鈫 Tank3
|
|
|
|
|
+
|
|
|
|
|
+ // 娴侀噺璁′綅缃
|
|
|
|
|
+ private static readonly Point DFM1 = new(22, 195);
|
|
|
|
|
+ private static readonly Point DFM2 = new(870, 195);
|
|
|
|
|
+
|
|
|
|
|
+ // 鈺愨晲鈺 涓绘覆鏌 鈺愨晲鈺
|
|
|
|
|
+
|
|
|
|
|
+ public override void Render(DrawingContext context)
|
|
|
|
|
+ {
|
|
|
|
|
+ base.Render(context);
|
|
|
|
|
+
|
|
|
|
|
+ var bounds = new Rect(Bounds.Size);
|
|
|
|
|
+ if (bounds.Width < 120 || bounds.Height < 120) return;
|
|
|
|
|
+
|
|
|
|
|
+ // 鑳屾櫙
|
|
|
|
|
+ context.DrawRectangle(ThemeHelper.PanelBg, null, bounds);
|
|
|
|
|
+
|
|
|
|
|
+ // 璁$畻缂╂斁锛氳璁″潗鏍 鈫 灞忓箷鍧愭爣
|
|
|
|
|
+ var margin = 8.0;
|
|
|
|
|
+ var scaleX = (bounds.Width - 2 * margin) / DW;
|
|
|
|
|
+ var scaleY = (bounds.Height - 2 * margin) / DH;
|
|
|
|
|
+ var scale = Math.Min(scaleX, scaleY);
|
|
|
|
|
+ var offsetX = margin + (bounds.Width - 2 * margin - DW * scale) / 2;
|
|
|
|
|
+ var offsetY = margin + (bounds.Height - 2 * margin - DH * scale) / 2;
|
|
|
|
|
+
|
|
|
|
|
+ // 鍦伴潰绾
|
|
|
|
|
+ DrawLine(context, offsetX, offsetY, scale, 0, 520, 960, 520, ThemeHelper.Border, 1);
|
|
|
|
|
+
|
|
|
|
|
+ // 鈺愨晲鈺 绠¢亾锛堝厛鐢伙紝杩欐牱璁惧瑕嗙洊鍦ㄤ笂闈級鈺愨晲鈺
|
|
|
|
|
+
|
|
|
|
|
+ // 杩涙按绠 鈫 FM1 鈫 Tank1
|
|
|
|
|
+ DrawPipeH(context, offsetX, offsetY, scale, 0, 200, 55, IsInflowRunning, 10);
|
|
|
|
|
+ DrawFlowMeter(context, offsetX, offsetY, scale, DFM1, "杩涙按", InflowRate, IsInflowRunning);
|
|
|
|
|
+ DrawPipeH(context, offsetX, offsetY, scale, 48, 200, 55, IsInflowRunning, 10);
|
|
|
|
|
+
|
|
|
|
|
+ // Tank1 鈫 V1 鈫 Tank2
|
|
|
|
|
+ DrawPipeH(context, offsetX, offsetY, scale, 175, 200, 195, IsInflowRunning, 8);
|
|
|
|
|
+ DrawValve(context, offsetX, offsetY, scale, DV1, "V1", Valve1Status);
|
|
|
|
|
+ DrawPipeH(context, offsetX, offsetY, scale, 240, 200, 275, IsInflowRunning, 8);
|
|
|
|
|
+
|
|
|
|
|
+ // Tank2 搴曢儴 鈫 娉靛惛鍏ョ
|
|
|
|
|
+ DrawPipeV(context, offsetX, offsetY, scale, 372, 295, 325, AnyPumpRunning, 8);
|
|
|
|
|
+
|
|
|
|
|
+ // 娉垫帓鍑烘荤
|
|
|
|
|
+ DrawPipeH(context, offsetX, offsetY, scale, 285, 365, 470, AnyPumpRunning, 10);
|
|
|
|
|
+
|
|
|
|
|
+ // 鎬荤涓婂集 鈫 V2 鈫 Tank4
|
|
|
|
|
+ DrawPipeV(context, offsetX, offsetY, scale, 500, 365, 195, IsOutflowRunning, 10);
|
|
|
|
|
+ DrawPipeH(context, offsetX, offsetY, scale, 500, 195, 530, IsOutflowRunning, 10);
|
|
|
|
|
+ DrawValve(context, offsetX, offsetY, scale, DV2, "V2", Valve2Status);
|
|
|
|
|
+ DrawPipeH(context, offsetX, offsetY, scale, 580, 195, 720, IsOutflowRunning, 10);
|
|
|
|
|
+
|
|
|
|
|
+ // Tank4 鈫 FM2 鈫 鍑烘按
|
|
|
|
|
+ DrawPipeH(context, offsetX, offsetY, scale, 840, 200, 860, IsOutflowRunning, 10);
|
|
|
|
|
+ DrawFlowMeter(context, offsetX, offsetY, scale, DFM2, "鍑烘按", OutflowRate, IsOutflowRunning);
|
|
|
|
|
+ DrawPipeH(context, offsetX, offsetY, scale, 896, 200, 960, IsOutflowRunning, 10);
|
|
|
|
|
+
|
|
|
|
|
+ // V3 鍒嗘敮锛氭荤 鈫 Tank3
|
|
|
|
|
+ DrawPipeV(context, offsetX, offsetY, scale, 420, 365, 378, false, 8);
|
|
|
|
|
+ DrawValve(context, offsetX, offsetY, scale, DV3, "V3", Valve3Status);
|
|
|
|
|
+ DrawPipeV(context, offsetX, offsetY, scale, 420, 412, 400, false, 8);
|
|
|
|
|
+
|
|
|
|
|
+ // V4 鍒嗘敮锛歍ank2 鈫 Tank3
|
|
|
|
|
+ DrawPipeV(context, offsetX, offsetY, scale, 230, 295, 430, false, 8);
|
|
|
|
|
+ DrawPipeH(context, offsetX, offsetY, scale, 230, 450, 275, false, 8);
|
|
|
|
|
+ DrawValve(context, offsetX, offsetY, scale, DV4, "V4", Valve4Status);
|
|
|
|
|
+
|
|
|
|
|
+ // 鈺愨晲鈺 姘寸 鈺愨晲鈺
|
|
|
|
|
+
|
|
|
|
|
+ DrawTank(context, offsetX, offsetY, scale, DTank1, Tank1Level, "杩涙按姹", "IN",
|
|
|
|
|
+ Color.FromRgb(0, 137, 123)); // Teal
|
|
|
|
|
+ DrawTank(context, offsetX, offsetY, scale, DTank2, Tank2Level, "闆嗘按浜", "SUMP",
|
|
|
|
|
+ Color.FromRgb(33, 150, 243)); // Blue
|
|
|
|
|
+ DrawTank(context, offsetX, offsetY, scale, DTank3, Tank3Level, "璋冭搫姹", "BUF",
|
|
|
|
|
+ Color.FromRgb(126, 87, 194)); // Purple
|
|
|
|
|
+ DrawTank(context, offsetX, offsetY, scale, DTank4, Tank4Level, "鍑烘按姹", "OUT",
|
|
|
|
|
+ Color.FromRgb(0, 150, 136)); // Green
|
|
|
|
|
+
|
|
|
|
|
+ // 鈺愨晲鈺 娉电粍 鈺愨晲鈺
|
|
|
|
|
+
|
|
|
|
|
+ bool[] pumpRunning = { Pump1Running, Pump2Running, Pump3Running, Pump4Running, Pump5Running };
|
|
|
|
|
+ string[] pumpLabels = { "P1", "P2", "P3", "P4", "P5" };
|
|
|
|
|
+ for (int i = 0; i < 5; i++)
|
|
|
|
|
+ DrawPump(context, offsetX, offsetY, scale, DPumps[i], pumpLabels[i], pumpRunning[i]);
|
|
|
|
|
+
|
|
|
|
|
+ // 鈺愨晲鈺 椋庢満 鈺愨晲鈺
|
|
|
|
|
+
|
|
|
|
|
+ DrawFan(context, offsetX, offsetY, scale, DFan1, "F1", Fan1Running);
|
|
|
|
|
+ DrawFan(context, offsetX, offsetY, scale, DFan2, "F2", Fan2Running);
|
|
|
|
|
+
|
|
|
|
|
+ // 椋庢満鍖哄煙妗
|
|
|
|
|
+ var fanAreaX = S(offsetX, scale, 555);
|
|
|
|
|
+ var fanAreaY = S(offsetY, scale, 418);
|
|
|
|
|
+ var fanAreaW = W(scale, 180);
|
|
|
|
|
+ var fanAreaH = H(scale, 75);
|
|
|
|
|
+ context.DrawRectangle(null, new Pen(ThemeHelper.Border, 1),
|
|
|
|
|
+ new Rect(fanAreaX, fanAreaY, fanAreaW, fanAreaH), 3, 3);
|
|
|
|
|
+ DrawLabel(context, offsetX, offsetY, scale, 560, 422, "椋庢満鍖", 10, ThemeHelper.TextTertiary);
|
|
|
|
|
+
|
|
|
|
|
+ // 鈺愨晲鈺 鎶ヨ杈规 鈺愨晲鈺
|
|
|
|
|
+
|
|
|
|
|
+ if (HasAlarm)
|
|
|
|
|
+ {
|
|
|
|
|
+ var sceneRect = new Rect(margin, margin, bounds.Width - 2 * margin, bounds.Height - 2 * margin);
|
|
|
|
|
+ context.DrawRectangle(null, new Pen(ThemeHelper.Danger, 2), sceneRect, 4);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 鈺愨晲鈺 HUD 鐘舵侀潰鏉 鈺愨晲鈺
|
|
|
|
|
+
|
|
|
|
|
+ DrawHud(context, bounds, scale);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 鈺愨晲鈺 鍧愭爣杞崲杈呭姪 鈺愨晲鈺
|
|
|
|
|
+
|
|
|
|
|
+ private static double S(double offset, double scale, double designVal) => offset + designVal * scale;
|
|
|
|
|
+ private static double W(double scale, double designW) => designW * scale;
|
|
|
|
|
+ private static double H(double scale, double designH) => designH * scale;
|
|
|
|
|
+ private static double R(double scale, double designR) => designR * scale;
|
|
|
|
|
+
|
|
|
|
|
+ // 鈺愨晲鈺 姘寸缁樺埗 鈺愨晲鈺
|
|
|
|
|
+
|
|
|
|
|
+ private void DrawTank(DrawingContext context, double ox, double oy, double sc,
|
|
|
|
|
+ Rect designRect, double level, string name, string code, Color waterColor)
|
|
|
|
|
+ {
|
|
|
|
|
+ var x = S(ox, sc, designRect.X);
|
|
|
|
|
+ var y = S(oy, sc, designRect.Y);
|
|
|
|
|
+ var w = W(sc, designRect.Width);
|
|
|
|
|
+ var h = H(sc, designRect.Height);
|
|
|
|
|
+ var rect = new Rect(x, y, w, h);
|
|
|
|
|
+
|
|
|
|
|
+ // 绠变綋
|
|
|
|
|
+ context.DrawRectangle(ThemeHelper.SurfaceBg, new Pen(ThemeHelper.Border, 1.5), rect, 3, 3);
|
|
|
|
|
+
|
|
|
|
|
+ // 姘翠綅濉厖
|
|
|
|
|
+ var clampedLevel = Math.Clamp(level, 0, 100);
|
|
|
|
|
+ if (clampedLevel > 0.5)
|
|
|
|
|
+ {
|
|
|
|
|
+ var waterH = (h - 4) * clampedLevel / 100.0;
|
|
|
|
|
+ var waterRect = new Rect(x + 2, y + h - 2 - waterH, w - 4, waterH);
|
|
|
|
|
+ var waterBrush = new SolidColorBrush(waterColor, 0.65);
|
|
|
|
|
+ context.DrawRectangle(waterBrush, null, waterRect);
|
|
|
|
|
+
|
|
|
|
|
+ // 姘撮潰娉㈢汗
|
|
|
|
|
+ var waveY = waterRect.Y;
|
|
|
|
|
+ var waveBrush = new SolidColorBrush(waterColor, 0.35);
|
|
|
|
|
+ for (int i = 0; i < 3; i++)
|
|
|
|
|
+ {
|
|
|
|
|
+ var wx = waterRect.X + 4 + i * (waterRect.Width / 3.0);
|
|
|
|
|
+ context.DrawLine(new Pen(waveBrush, 1),
|
|
|
|
|
+ new Point(wx, waveY), new Point(wx + waterRect.Width / 6.0, waveY + 2));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 鍚嶇О鏍囩
|
|
|
|
|
+ var fontSize = Math.Max(8, 11 * sc);
|
|
|
|
|
+ DrawLabel(context, ox, oy, sc, designRect.Center.X - 20, designRect.Y + 6, name, 11, ThemeHelper.TextPrimary, FontWeight.Bold);
|
|
|
|
|
+ DrawLabel(context, ox, oy, sc, designRect.Center.X - 14, designRect.Y + 20, code, 9, ThemeHelper.TextTertiary);
|
|
|
|
|
+
|
|
|
|
|
+ // 娑蹭綅鐧惧垎姣
|
|
|
|
|
+ var pctText = $"{clampedLevel:F1}%";
|
|
|
|
|
+ var pctColor = clampedLevel > 80 ? ThemeHelper.Warning :
|
|
|
|
|
+ clampedLevel < 20 ? ThemeHelper.Danger :
|
|
|
|
|
+ new SolidColorBrush(waterColor);
|
|
|
|
|
+ DrawLabel(context, ox, oy, sc, designRect.Center.X - 18, designRect.Center.Y + 8, pctText, 13, pctColor, FontWeight.Bold);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 鈺愨晲鈺 绠¢亾缁樺埗 鈺愨晲鈺
|
|
|
|
|
+
|
|
|
|
|
+ private void DrawPipeH(DrawingContext context, double ox, double oy, double sc,
|
|
|
|
|
+ double x1Design, double yDesign, double x2Design, bool isFlowing, double thicknessDesign)
|
|
|
|
|
+ {
|
|
|
|
|
+ var x1 = S(ox, sc, x1Design);
|
|
|
|
|
+ var y = S(oy, sc, yDesign);
|
|
|
|
|
+ var x2 = S(ox, sc, x2Design);
|
|
|
|
|
+ var thickness = R(sc, thicknessDesign);
|
|
|
|
|
+
|
|
|
|
|
+ // 绠″
|
|
|
|
|
+ var pipeColor = Color.FromRgb(55, 65, 81);
|
|
|
|
|
+ context.DrawLine(new Pen(new SolidColorBrush(pipeColor), thickness),
|
|
|
|
|
+ new Point(x1, y), new Point(x2, y));
|
|
|
|
|
+
|
|
|
|
|
+ // 鍐呯
|
|
|
|
|
+ var innerColor = Color.FromRgb(97, 111, 134);
|
|
|
|
|
+ context.DrawLine(new Pen(new SolidColorBrush(innerColor), thickness * 0.45),
|
|
|
|
|
+ new Point(x1, y), new Point(x2, y));
|
|
|
|
|
+
|
|
|
|
|
+ // 娴佸姩鍔ㄧ敾
|
|
|
|
|
+ if (isFlowing && x2 > x1 + 2)
|
|
|
|
|
+ DrawFlowDots(context, x1, y, x2, y);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void DrawPipeV(DrawingContext context, double ox, double oy, double sc,
|
|
|
|
|
+ double xDesign, double y1Design, double y2Design, bool isFlowing, double thicknessDesign)
|
|
|
|
|
+ {
|
|
|
|
|
+ var x = S(ox, sc, xDesign);
|
|
|
|
|
+ var y1 = S(oy, sc, y1Design);
|
|
|
|
|
+ var y2 = S(oy, sc, y2Design);
|
|
|
|
|
+ var thickness = R(sc, thicknessDesign);
|
|
|
|
|
+
|
|
|
|
|
+ var pipeColor = Color.FromRgb(55, 65, 81);
|
|
|
|
|
+ context.DrawLine(new Pen(new SolidColorBrush(pipeColor), thickness),
|
|
|
|
|
+ new Point(x, y1), new Point(x, y2));
|
|
|
|
|
+
|
|
|
|
|
+ var innerColor = Color.FromRgb(97, 111, 134);
|
|
|
|
|
+ context.DrawLine(new Pen(new SolidColorBrush(innerColor), thickness * 0.45),
|
|
|
|
|
+ new Point(x, y1), new Point(x, y2));
|
|
|
|
|
+
|
|
|
|
|
+ if (isFlowing && Math.Abs(y2 - y1) > 2)
|
|
|
|
|
+ DrawFlowDots(context, x, y1, x, y2);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void DrawFlowDots(DrawingContext context, double x1, double y1, double x2, double y2)
|
|
|
|
|
+ {
|
|
|
|
|
+ var flowBrush = new SolidColorBrush(Color.FromRgb(3, 169, 244), 0.85);
|
|
|
|
|
+ var dx = x2 - x1;
|
|
|
|
|
+ var dy = y2 - y1;
|
|
|
|
|
+ var len = Math.Sqrt(dx * dx + dy * dy);
|
|
|
|
|
+ if (len < 4) return;
|
|
|
|
|
+
|
|
|
|
|
+ var count = Math.Max(2, (int)(len / 18));
|
|
|
|
|
+ for (int i = 0; i < count; i++)
|
|
|
|
|
+ {
|
|
|
|
|
+ var t = ((_phase / 360.0) + (double)i / count) % 1.0;
|
|
|
|
|
+ var px = x1 + dx * t;
|
|
|
|
|
+ var py = y1 + dy * t;
|
|
|
|
|
+ var r = Math.Max(1.5, 3);
|
|
|
|
|
+ context.DrawEllipse(flowBrush, null, new Rect(px - r, py - r, r * 2, r * 2));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 鈺愨晲鈺 娉电粯鍒 鈺愨晲鈺
|
|
|
|
|
+
|
|
|
|
|
+ private void DrawPump(DrawingContext context, double ox, double oy, double sc,
|
|
|
|
|
+ Point designPos, string label, bool running)
|
|
|
|
|
+ {
|
|
|
|
|
+ var cx = S(ox, sc, designPos.X);
|
|
|
|
|
+ var cy = S(oy, sc, designPos.Y);
|
|
|
|
|
+ var r = R(sc, 18);
|
|
|
|
|
+
|
|
|
|
|
+ var statusColor = running ? GetColor(ThemeHelper.Success) : GetColor(ThemeHelper.TextDisabled);
|
|
|
|
|
+ var statusBrush = new SolidColorBrush(statusColor);
|
|
|
|
|
+
|
|
|
|
|
+ // 杩愯鍏夋檿
|
|
|
|
|
+ if (running)
|
|
|
|
|
+ context.DrawEllipse(new SolidColorBrush(statusColor, 0.18), null,
|
|
|
|
|
+ new Rect(cx - r * 1.4, cy - r * 1.4, r * 2.8, r * 2.8));
|
|
|
|
|
+
|
|
|
|
|
+ // 娉典綋鍦
|
|
|
|
|
+ context.DrawEllipse(ThemeHelper.SurfaceBg, new Pen(statusBrush, running ? 2 : 1.2),
|
|
|
|
|
+ new Rect(cx - r, cy - r, r * 2, r * 2));
|
|
|
|
|
+
|
|
|
|
|
+ // 鏃嬭浆鍙剁墖
|
|
|
|
|
+ var bladeCount = 4;
|
|
|
|
|
+ for (int i = 0; i < bladeCount; i++)
|
|
|
|
|
+ {
|
|
|
|
|
+ var angle = (running ? _pumpAngle : 0) + i * (360.0 / bladeCount);
|
|
|
|
|
+ var rad = angle * Math.PI / 180.0;
|
|
|
|
|
+ var bx = cx + Math.Cos(rad) * r * 0.65;
|
|
|
|
|
+ var by = cy + Math.Sin(rad) * r * 0.65;
|
|
|
|
|
+ var bladeBrush = new SolidColorBrush(statusColor, running ? 0.9 : 0.45);
|
|
|
|
|
+ context.DrawLine(new Pen(bladeBrush, Math.Max(1.5, 2.5 * sc)),
|
|
|
|
|
+ new Point(cx, cy), new Point(bx, by));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 涓績鍦
|
|
|
|
|
+ context.DrawEllipse(statusBrush, null,
|
|
|
|
|
+ new Rect(cx - r * 0.22, cy - r * 0.22, r * 0.44, r * 0.44));
|
|
|
|
|
+
|
|
|
|
|
+ // 鏍囩
|
|
|
|
|
+ DrawLabelAt(context, cx, cy + r + 3, label, Math.Max(8, 10 * sc), ThemeHelper.TextPrimary, FontWeight.Bold);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 鈺愨晲鈺 椋庢満缁樺埗 鈺愨晲鈺
|
|
|
|
|
+
|
|
|
|
|
+ private void DrawFan(DrawingContext context, double ox, double oy, double sc,
|
|
|
|
|
+ Point designPos, string label, bool running)
|
|
|
|
|
+ {
|
|
|
|
|
+ var cx = S(ox, sc, designPos.X);
|
|
|
|
|
+ var cy = S(oy, sc, designPos.Y);
|
|
|
|
|
+ var r = R(sc, 20);
|
|
|
|
|
+
|
|
|
|
|
+ var statusColor = running ? GetColor(ThemeHelper.Info) : GetColor(ThemeHelper.TextDisabled);
|
|
|
|
|
+ var statusBrush = new SolidColorBrush(statusColor);
|
|
|
|
|
+
|
|
|
|
|
+ // 澶栧3
|
|
|
|
|
+ context.DrawEllipse(ThemeHelper.SurfaceBg, new Pen(statusBrush, running ? 2 : 1.2),
|
|
|
|
|
+ new Rect(cx - r, cy - r, r * 2, r * 2));
|
|
|
|
|
+
|
|
|
|
|
+ // 鏃嬭浆鍙剁墖 (5 鐗)
|
|
|
|
|
+ var bladeCount = 5;
|
|
|
|
|
+ for (int i = 0; i < bladeCount; i++)
|
|
|
|
|
+ {
|
|
|
|
|
+ var angle = (running ? _fanAngle : 0) + i * (360.0 / bladeCount);
|
|
|
|
|
+ var rad = angle * Math.PI / 180.0;
|
|
|
|
|
+ var bx = cx + Math.Cos(rad) * r * 0.72;
|
|
|
|
|
+ var by = cy + Math.Sin(rad) * r * 0.72;
|
|
|
|
|
+ var bladeBrush = new SolidColorBrush(statusColor, running ? 0.85 : 0.4);
|
|
|
|
|
+ context.DrawLine(new Pen(bladeBrush, Math.Max(2, 3 * sc)),
|
|
|
|
|
+ new Point(cx, cy), new Point(bx, by));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 涓績
|
|
|
|
|
+ context.DrawEllipse(statusBrush, null,
|
|
|
|
|
+ new Rect(cx - r * 0.18, cy - r * 0.18, r * 0.36, r * 0.36));
|
|
|
|
|
+
|
|
|
|
|
+ // 鏍囩
|
|
|
|
|
+ DrawLabelAt(context, cx, cy + r + 4, label, Math.Max(8, 10 * sc), ThemeHelper.TextPrimary, FontWeight.Bold);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 鈺愨晲鈺 闃闂ㄧ粯鍒讹紙铦磋澏闃绗﹀彿锛夆晲鈺愨晲
|
|
|
|
|
+
|
|
|
|
|
+ private void DrawValve(DrawingContext context, double ox, double oy, double sc,
|
|
|
|
|
+ Point designPos, string label, ValveStatus status)
|
|
|
|
|
+ {
|
|
|
|
|
+ var cx = S(ox, sc, designPos.X);
|
|
|
|
|
+ var cy = S(oy, sc, designPos.Y);
|
|
|
|
|
+ var s = R(sc, 12);
|
|
|
|
|
+
|
|
|
|
|
+ var color = status switch
|
|
|
|
|
+ {
|
|
|
|
|
+ ValveStatus.Open => GetColor(ThemeHelper.Success),
|
|
|
|
|
+ ValveStatus.Middle => GetColor(ThemeHelper.Warning),
|
|
|
|
|
+ ValveStatus.Closed => GetColor(ThemeHelper.Danger),
|
|
|
|
|
+ _ => GetColor(ThemeHelper.TextDisabled)
|
|
|
|
|
+ };
|
|
|
|
|
+ var colorBrush = new SolidColorBrush(color);
|
|
|
|
|
+
|
|
|
|
|
+ // 铦磋澏闃锛氫袱涓笁瑙掑舰
|
|
|
|
|
+ var top = new Point(cx, cy - s);
|
|
|
|
|
+ var bottom = new Point(cx, cy + s);
|
|
|
|
|
+ var left = new Point(cx - s * 0.7, cy);
|
|
|
|
|
+ var right = new Point(cx + s * 0.7, cy);
|
|
|
|
|
+
|
|
|
|
|
+ // 涓婁笁瑙
|
|
|
|
|
+ var tri1 = new StreamGeometry();
|
|
|
|
|
+ using (var ctx = tri1.Open())
|
|
|
|
|
+ {
|
|
|
|
|
+ ctx.BeginFigure(left, true);
|
|
|
|
|
+ ctx.LineTo(top);
|
|
|
|
|
+ ctx.LineTo(right);
|
|
|
|
|
+ ctx.EndFigure(true);
|
|
|
|
|
+ }
|
|
|
|
|
+ context.DrawGeometry(new SolidColorBrush(color, 0.7), null, tri1);
|
|
|
|
|
+
|
|
|
|
|
+ // 涓嬩笁瑙掞紙鍊掔疆锛
|
|
|
|
|
+ var tri2 = new StreamGeometry();
|
|
|
|
|
+ using (var ctx = tri2.Open())
|
|
|
|
|
+ {
|
|
|
|
|
+ ctx.BeginFigure(left, true);
|
|
|
|
|
+ ctx.LineTo(bottom);
|
|
|
|
|
+ ctx.LineTo(right);
|
|
|
|
|
+ ctx.EndFigure(true);
|
|
|
|
|
+ }
|
|
|
|
|
+ context.DrawGeometry(new SolidColorBrush(color, 0.7), null, tri2);
|
|
|
|
|
+
|
|
|
|
|
+ // 闃鏉
|
|
|
|
|
+ context.DrawLine(new Pen(colorBrush, Math.Max(1, 1.5 * sc)),
|
|
|
|
|
+ new Point(cx - s * 0.5, cy), new Point(cx + s * 0.5, cy));
|
|
|
|
|
+
|
|
|
|
|
+ // 鐘舵佹寚绀虹偣
|
|
|
|
|
+ var lampR = Math.Max(2, 3 * sc);
|
|
|
|
|
+ context.DrawEllipse(colorBrush, null,
|
|
|
|
|
+ new Rect(cx - lampR, cy - s - lampR * 2.5, lampR * 2, lampR * 2));
|
|
|
|
|
+
|
|
|
|
|
+ // 鏍囩
|
|
|
|
|
+ DrawLabelAt(context, cx, cy + s + 3, label, Math.Max(7, 9 * sc), ThemeHelper.TextSecondary, FontWeight.Bold);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 鈺愨晲鈺 娴侀噺璁$粯鍒 鈺愨晲鈺
|
|
|
|
|
+
|
|
|
|
|
+ private void DrawFlowMeter(DrawingContext context, double ox, double oy, double sc,
|
|
|
|
|
+ Point designPos, string label, double value, bool active)
|
|
|
|
|
+ {
|
|
|
|
|
+ var cx = S(ox, sc, designPos.X);
|
|
|
|
|
+ var cy = S(oy, sc, designPos.Y);
|
|
|
|
|
+ var w = W(sc, 56);
|
|
|
|
|
+ var h = H(sc, 32);
|
|
|
|
|
+
|
|
|
|
|
+ var color = active ? GetColor(ThemeHelper.Success) : GetColor(ThemeHelper.TextDisabled);
|
|
|
|
|
+ var colorBrush = new SolidColorBrush(color);
|
|
|
|
|
+
|
|
|
|
|
+ // 澶栨
|
|
|
|
|
+ var rect = new Rect(cx - w / 2, cy - h / 2, w, h);
|
|
|
|
|
+ context.DrawRectangle(new SolidColorBrush(GetColor(ThemeHelper.SurfaceBg), 0.95),
|
|
|
|
|
+ new Pen(colorBrush, 1.2), rect, 2, 2);
|
|
|
|
|
+
|
|
|
|
|
+ // 鏍囩
|
|
|
|
|
+ DrawLabelAt(context, cx, cy - h * 0.18, label, Math.Max(7, 8 * sc), ThemeHelper.TextTertiary);
|
|
|
|
|
+
|
|
|
|
|
+ // 鏁板
|
|
|
|
|
+ DrawLabelAt(context, cx, cy + h * 0.12, $"{value:F1}", Math.Max(8, 10 * sc), colorBrush, FontWeight.Bold);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 鈺愨晲鈺 HUD 鐘舵侀潰鏉 鈺愨晲鈺
|
|
|
|
|
+
|
|
|
|
|
+ private void DrawHud(DrawingContext context, Rect bounds, double scale)
|
|
|
|
|
+ {
|
|
|
|
|
+ var panelW = Math.Max(120, 185 * Math.Min(scale, 1.5));
|
|
|
|
|
+ var panelH = Math.Max(45, 58 * Math.Min(scale, 1.5));
|
|
|
|
|
+ var panelX = bounds.Right - panelW - 10;
|
|
|
|
|
+ var panelY = bounds.Top + 8;
|
|
|
|
|
+ var panelRect = new Rect(panelX, panelY, panelW, panelH);
|
|
|
|
|
+
|
|
|
|
|
+ context.DrawRectangle(new SolidColorBrush(GetColor(ThemeHelper.SurfaceBg), 0.92),
|
|
|
|
|
+ new Pen(ThemeHelper.Border, 1), panelRect, 3, 3);
|
|
|
|
|
+
|
|
|
|
|
+ var fontSize = Math.Max(8, 10 * Math.Min(scale, 1.5));
|
|
|
|
|
+ var smallFont = Math.Max(7, 9 * Math.Min(scale, 1.5));
|
|
|
|
|
+
|
|
|
|
|
+ DrawLabelAt(context, panelX + 8, panelY + 6, "娉电珯鎬昏", fontSize * 1.15, ThemeHelper.TextPrimary, FontWeight.Bold);
|
|
|
|
|
+ DrawLabelAt(context, panelX + 8, panelY + 6 + fontSize * 1.5,
|
|
|
|
|
+ $"杩愯 {RunningDeviceCount} 鏁堢巼 {Efficiency:F0}%", smallFont, ThemeHelper.TextSecondary);
|
|
|
|
|
+
|
|
|
|
|
+ var alarmColor = HasAlarm ? ThemeHelper.Danger : ThemeHelper.Success;
|
|
|
|
|
+ DrawLabelAt(context, panelX + 8, panelY + 6 + fontSize * 1.5 + smallFont * 1.5,
|
|
|
|
|
+ $"鎶ヨ {AlarmCount}", smallFont, alarmColor, FontWeight.Bold);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 鈺愨晲鈺 鏂囧瓧缁樺埗杈呭姪 鈺愨晲鈺
|
|
|
|
|
+
|
|
|
|
|
+ private void DrawLabel(DrawingContext context, double ox, double oy, double sc,
|
|
|
|
|
+ double designX, double designY, string text, double designFontSize,
|
|
|
|
|
+ IBrush brush, FontWeight weight = FontWeight.Normal)
|
|
|
|
|
+ {
|
|
|
|
|
+ var x = S(ox, sc, designX);
|
|
|
|
|
+ var y = S(oy, sc, designY);
|
|
|
|
|
+ var size = Math.Max(8, designFontSize * sc);
|
|
|
|
|
+ DrawLabelAt(context, x, y, text, size, brush, weight);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static void DrawLabelAt(DrawingContext context, double x, double y, string text,
|
|
|
|
|
+ double size, IBrush brush, FontWeight weight = FontWeight.Normal)
|
|
|
|
|
+ {
|
|
|
|
|
+ var formatted = new FormattedText(text, CultureInfo.CurrentCulture, FlowDirection.LeftToRight,
|
|
|
|
|
+ new Typeface("Microsoft YaHei", FontStyle.Normal, weight), size, brush);
|
|
|
|
|
+ context.DrawText(formatted, new Point(x, y));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static void DrawLine(DrawingContext context, double ox, double oy, double sc,
|
|
|
|
|
+ double x1Design, double y1Design, double x2Design, double y2Design, IBrush brush, double thickness)
|
|
|
|
|
+ {
|
|
|
|
|
+ context.DrawLine(new Pen(brush, thickness),
|
|
|
|
|
+ new Point(S(ox, sc, x1Design), S(oy, sc, y1Design)),
|
|
|
|
|
+ new Point(S(ox, sc, x2Design), S(oy, sc, y2Design)));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static Color GetColor(IBrush brush) => brush is SolidColorBrush solid ? solid.Color : Colors.Gray;
|
|
|
|
|
+
|
|
|
|
|
+ protected override Size MeasureOverride(Size availableSize) => new(760, 420);
|
|
|
|
|
+}
|