using Avalonia;
using Avalonia.Media;
using System;
using System.Collections.Generic;
using System.Globalization;
namespace YZWater.Avalonia.Controls.Renderers;
///
/// 水箱渲染器。
///
/// - :简单矩形水箱(兼容模式)
/// - :剖面池(SCADA 拟真)— 池壁质感 + 液位填充 +
/// 正弦波浪动画 + 液位大字 + 左侧刻度尺 + 彩色阈值线(启泵/停泵/保护液位)
///
///
public static class TankRenderer
{
/// 阈值线定义(液位为与 waterLevel 同单位的原始值)
public record ThresholdLine(double Level, Color Color, string Label);
///
/// 绘制一个简单矩形水箱(兼容模式,保留原样式)。
///
public static void Draw(
DrawingContext context,
Rect bounds,
double waterLevel,
Color waterColor,
string name,
string code,
IBrush bgBrush,
IBrush borderBrush,
IBrush textPrimaryBrush,
IBrush textTertiaryBrush,
IBrush warningBrush,
IBrush dangerBrush)
{
var x = bounds.X;
var y = bounds.Y;
var w = bounds.Width;
var h = bounds.Height;
// 箱体
context.DrawRectangle(bgBrush, new Pen(borderBrush, 1.5), bounds, 3, 3);
// 水位填充
var clampedLevel = Math.Clamp(waterLevel, 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 centerX = x + w / 2;
var centerY = y + h / 2;
// 名称标签
var nameFontSize = Math.Max(8, Math.Min(w, h) * 0.077);
DrawTextAt(context, centerX - 20, y + 6, name, nameFontSize, textPrimaryBrush, FontWeight.Bold);
var codeFontSize = Math.Max(8, Math.Min(w, h) * 0.063);
DrawTextAt(context, centerX - 14, y + 20, code, codeFontSize, textTertiaryBrush);
// 液位百分比
var pctText = $"{clampedLevel:F1}%";
IBrush pctColor = clampedLevel > 80 ? warningBrush :
clampedLevel < 20 ? dangerBrush :
new SolidColorBrush(waterColor);
var pctFontSize = Math.Max(8, Math.Min(w, h) * 0.092);
DrawTextAt(context, centerX - 18, centerY + 8, pctText, pctFontSize, pctColor, FontWeight.Bold);
}
// ════════════════════════════════════════════════════════
// 剖面池(拟真模式)
// ════════════════════════════════════════════════════════
///
/// 绘制剖面式水池:池壁(混凝土质感)+ 液位填充 + 正弦波浪动画 +
/// 液位大字 + 左侧刻度尺 + 彩色阈值横线。
///
/// 绘图上下文
/// 池体矩形(屏幕坐标,含池壁)
/// 当前液位(原始值,如米)
/// 满量程液位(原始值)
/// 水体颜色
/// 池名称
/// 波浪动画相位(0~360)
/// 阈值线集合(按原始液位值)
/// 主题画刷
public static void DrawSection(
DrawingContext context,
Rect bounds,
double waterLevel,
double maxLevel,
Color waterColor,
string name,
double wavePhase,
IReadOnlyList thresholds,
SectionPalette palette)
{
var wall = Math.Max(4, Math.Min(bounds.Width, bounds.Height) * 0.06); // 池壁厚度
var inner = new Rect(bounds.X + wall, bounds.Y + wall * 0.6,
bounds.Width - wall * 2, bounds.Height - wall * 0.6 - wall * 0.5);
// ─── 池壁(外框,混凝土质感 + U 形实体壁)───
// 池底
var wallBrush = new SolidColorBrush(Color.FromArgb(180, 90, 98, 112));
context.DrawRectangle(wallBrush, new Pen(ThemeHelper.Border, 1.5), bounds, 2, 2);
// U 形内壁(左/右/底)
var innerWall = new SolidColorBrush(Color.FromArgb(140, 55, 62, 74));
// 左壁
context.DrawRectangle(innerWall, null,
new Rect(inner.X - wall, inner.Y, wall, inner.Height));
// 右壁
context.DrawRectangle(innerWall, null,
new Rect(inner.Right, inner.Y, wall, inner.Height));
// 池底
context.DrawRectangle(innerWall, null,
new Rect(inner.X, inner.Bottom, inner.Width, wall));
// 内壁阴影线(增强立体感)
context.DrawLine(new Pen(new SolidColorBrush(Color.FromArgb(100, 40, 45, 55)), 1.5),
new Point(inner.X, inner.Y), new Point(inner.X, inner.Bottom));
context.DrawLine(new Pen(new SolidColorBrush(Color.FromArgb(100, 40, 45, 55)), 1.5),
new Point(inner.Right, inner.Y), new Point(inner.Right, inner.Bottom));
context.DrawLine(new Pen(new SolidColorBrush(Color.FromArgb(100, 40, 45, 55)), 1.5),
new Point(inner.X, inner.Bottom), new Point(inner.Right, inner.Bottom));
// ─── 内腔背景 ───
context.DrawRectangle(palette.CavityBrush, null, inner);
// ── 液位填充 + 正弦波浪 ──
if (maxLevel <= 0) maxLevel = 1;
var levelPct = Math.Clamp(waterLevel / maxLevel, 0, 1);
var waterTop = inner.Bottom - inner.Height * levelPct;
// 波浪幅度随池高缩放(比旧版更明显)
var amp1 = Math.Max(4, inner.Height * 0.045);
if (levelPct > 0.01)
{
var waterRect = new Rect(inner.X, waterTop, inner.Width, inner.Bottom - waterTop);
var waterBrush = new LinearGradientBrush
{
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
EndPoint = new RelativePoint(0, 1, RelativeUnit.Relative),
GradientStops =
{
new GradientStop(Color.FromArgb(210, waterColor.R, waterColor.G, waterColor.B), 0),
new GradientStop(Color.FromArgb(120, waterColor.R, waterColor.G, waterColor.B), 1),
}
};
context.DrawRectangle(waterBrush, null, waterRect);
// 水面亮线(液面与空气交界处的高光)
context.DrawLine(new Pen(new SolidColorBrush(Color.FromArgb(200, 255, 255, 255)), 1.8),
new Point(inner.X + 1, waterTop), new Point(inner.Right - 1, waterTop));
// 正弦波浪(3 层相位错开,半透明叠加)
DrawWave(context, waterTop, inner, waterColor, wavePhase, 0.55, amp1, 1.0);
DrawWave(context, waterTop + 2, inner, waterColor, wavePhase * 1.3 + 120, 0.32, amp1 * 1.3, 0.7);
DrawWave(context, waterTop + 4, inner, waterColor, wavePhase * 0.8 + 240, 0.20, amp1 * 1.6, 0.5);
// 池内气泡上升动画(6 个气泡,相位不同,持续上升)
DrawBubbles(context, inner, waterRect, waterColor, wavePhase, levelPct);
}
else
{
// 空池:池底积水带 + 沉积示意,避免"死白框"
var siltRect = new Rect(inner.X, inner.Bottom - inner.Height * 0.04, inner.Width, inner.Height * 0.04);
context.DrawRectangle(new SolidColorBrush(Color.FromArgb(90, 110, 120, 100)), null, siltRect);
var residRect = new Rect(inner.X, inner.Bottom - inner.Height * 0.02, inner.Width, inner.Height * 0.02);
context.DrawRectangle(new SolidColorBrush(Color.FromArgb(140, waterColor.R, waterColor.G, waterColor.B)), null, residRect);
}
// ─── 阈值线(画在水位之上,一眼看出距离)───
if (thresholds != null)
{
foreach (var th in thresholds)
{
if (th.Level <= 0 || th.Level > maxLevel) continue;
var ty = inner.Bottom - inner.Height * Math.Clamp(th.Level / maxLevel, 0, 1);
var pen = new Pen(new SolidColorBrush(th.Color, 0.9), 1.5, DashStyle.Dash);
context.DrawLine(pen, new Point(inner.X, ty), new Point(inner.Right, ty));
// 阈值标签(左侧小字,紧贴线上方)
var label = $"{th.Label} {th.Level:F2}";
var ft = new FormattedText(label, CultureInfo.CurrentCulture, FlowDirection.LeftToRight,
new Typeface("Microsoft YaHei", FontStyle.Normal, FontWeight.Bold),
Math.Max(8, inner.Height * 0.055),
new SolidColorBrush(th.Color));
context.DrawText(ft, new Point(inner.X + 2, ty - ft.Height - 1));
}
}
// ─── 当前液位值(气泡浮标:跟随水面高度,贴池右侧)───
var valueText = $"{waterLevel:F2} m";
var valueSize = Math.Max(11, inner.Height * 0.085);
var valueFt = new FormattedText(valueText, CultureInfo.CurrentCulture, FlowDirection.LeftToRight,
new Typeface("Consolas", FontStyle.Normal, FontWeight.Bold), valueSize, palette.TextPrimaryBrush);
var badgeW = valueFt.Width + 14;
var badgeH = valueFt.Height + 8;
var badgeX = inner.Right - badgeW - 6;
var badgeY = Math.Clamp(waterTop - badgeH - 8, inner.Y + 2, inner.Bottom - badgeH - 2);
// 浮标牌 + 指向水面的指针
var badgeRect = new Rect(badgeX, badgeY, badgeW, badgeH);
context.DrawRectangle(palette.CavityBrush, new Pen(palette.BorderBrush, 1), badgeRect, 4, 4);
context.DrawLine(new Pen(palette.BorderBrush, 1.2),
new Point(badgeX + badgeW / 2, badgeY + badgeH),
new Point(badgeX + badgeW / 2, waterTop));
context.DrawEllipse(new SolidColorBrush(waterColor), null,
new Rect(badgeX + badgeW / 2 - 3, waterTop - 3, 6, 6));
context.DrawText(valueFt,
new Point(badgeRect.Center.X - valueFt.Width / 2, badgeRect.Center.Y - valueFt.Height / 2));
// ─── 池名称(顶部)───
var nameSize = Math.Max(9, bounds.Height * 0.07);
var nameFt = new FormattedText(name, CultureInfo.CurrentCulture, FlowDirection.LeftToRight,
new Typeface("Microsoft YaHei", FontStyle.Normal, FontWeight.Bold), nameSize, palette.TextPrimaryBrush);
context.DrawText(nameFt, new Point(bounds.X + (bounds.Width - nameFt.Width) / 2, bounds.Y - nameFt.Height - 2));
// ─── 水面亮线(液面与空气交界处的高光)───
if (levelPct > 0.01)
{
var waterRect = new Rect(inner.X, waterTop, inner.Width, inner.Bottom - waterTop);
context.DrawLine(new Pen(new SolidColorBrush(Color.FromArgb(200, 255, 255, 255)), 1.8),
new Point(inner.X + 1, waterTop), new Point(inner.Right - 1, waterTop));
}
// ─── 左侧刻度尺 ───
DrawRuler(context, bounds, inner, maxLevel, palette);
}
/// 池内气泡上升动画(6 个气泡,不同相位,从池底持续升起破裂)
private static void DrawBubbles(DrawingContext context, Rect inner, Rect waterRect,
Color waterColor, double phase, double levelPct)
{
const int bubbleCount = 6;
var bubbleBrush = new SolidColorBrush(Color.FromArgb(120, waterColor.R, waterColor.G, waterColor.B));
for (int i = 0; i < bubbleCount; i++)
{
// 每个气泡的相位偏移不同
var p = (phase + i * 67) % 360;
var t = p / 360.0; // 0→1 从池底到水面
var bx = inner.X + inner.Width * (0.15 + (i * 0.14 + Math.Sin(p * 0.05) * 0.08));
var by = inner.Bottom - inner.Height * levelPct * t - (inner.Height * 0.05); // 到水面以下
// 气泡大小随上升略缩小
var bSize = Math.Max(1.5, 3.5 * (1 - t * 0.3));
var bubbleAlpha = (byte)(140 * (1 - t * 0.6));
// 只在液面以下绘制
if (by >= inner.Y && by <= waterRect.Bottom)
{
context.DrawEllipse(new SolidColorBrush(Color.FromArgb(bubbleAlpha,
waterColor.R, waterColor.G, waterColor.B)), null,
new Rect(bx - bSize, by - bSize, bSize * 2, bSize * 2));
}
}
}
/// 绘制单层正弦波浪(填充至池底,裁剪在内腔范围内)
private static void DrawWave(DrawingContext context, double topY, Rect inner,
Color waterColor, double phase, double alpha, double amplitude, double wavelengthScale)
{
if (topY < inner.Y - amplitude || topY > inner.Bottom) return;
const int steps = 24;
var geometry = new StreamGeometry();
using (var ctx = geometry.Open())
{
ctx.BeginFigure(new Point(inner.X, inner.Bottom), isFilled: true);
// 从左到右沿正弦曲线采样
for (int i = 0; i <= steps; i++)
{
var t = (double)i / steps;
var px = inner.X + inner.Width * t;
var py = topY + Math.Sin((t * Math.PI * 2 * wavelengthScale) + (phase * Math.PI / 180)) * amplitude;
ctx.LineTo(new Point(px, py), isStroked: false);
}
// 闭合到池底
ctx.EndFigure(true);
}
var brush = new SolidColorBrush(waterColor, alpha);
context.DrawGeometry(brush, null, geometry);
}
/// 左侧刻度尺(0~maxLevel,按 5 段标注)
private static void DrawRuler(DrawingContext context, Rect bounds, Rect inner,
double maxLevel, SectionPalette palette)
{
const int segments = 5;
var tickLen = Math.Max(3, bounds.Width * 0.02);
var fontSize = Math.Max(7, bounds.Height * 0.045);
var pen = new Pen(palette.TextTertiaryBrush, 1);
for (int i = 0; i <= segments; i++)
{
var value = maxLevel * i / segments;
var y = inner.Bottom - inner.Height * ((double)i / segments);
context.DrawLine(pen, new Point(bounds.X - tickLen, y), new Point(bounds.X - 1, y));
var label = value.ToString("F1", CultureInfo.CurrentCulture);
var ft = new FormattedText(label, CultureInfo.CurrentCulture, FlowDirection.LeftToRight,
new Typeface("Consolas"), fontSize, palette.TextTertiaryBrush);
context.DrawText(ft, new Point(bounds.X - tickLen - ft.Width - 2, y - ft.Height / 2));
}
}
private static void DrawTextAt(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));
}
}
/// 剖面池主题画刷集(从 ThemeHelper 取色后传入,控件不用 DynamicResource)
public record SectionPalette(
IBrush WallBrush, // 池壁(混凝土)
IBrush WallShadowBrush, // 池壁内侧阴影线
IBrush CavityBrush, // 内腔背景
IBrush BorderBrush, // 边框
IBrush TextPrimaryBrush, // 主文字
IBrush TextTertiaryBrush // 辅助文字(刻度)
);