using Avalonia; using Avalonia.Controls; using Avalonia.Media; using System; using System.Globalization; namespace YZWater.Avalonia.Controls; public class GaugeControl : Control { public static readonly StyledProperty ValueProperty = AvaloniaProperty.Register(nameof(Value), 0.0); public static readonly StyledProperty MinValueProperty = AvaloniaProperty.Register(nameof(MinValue), 0.0); public static readonly StyledProperty MaxValueProperty = AvaloniaProperty.Register(nameof(MaxValue), 100.0); public static readonly StyledProperty TitleProperty = AvaloniaProperty.Register(nameof(Title), "Flow"); public static readonly StyledProperty UnitProperty = AvaloniaProperty.Register(nameof(Unit), "m³/h"); public static readonly StyledProperty ValueColorProperty = AvaloniaProperty.Register(nameof(ValueColor)); 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); } static GaugeControl() { AffectsRender(ValueProperty, MinValueProperty, MaxValueProperty, TitleProperty, UnitProperty, ValueColorProperty); } protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { base.OnAttachedToVisualTree(e); ThemeHelper.ThemeChanged += OnThemeChanged; } protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) { ThemeHelper.ThemeChanged -= OnThemeChanged; base.OnDetachedFromVisualTree(e); } private void OnThemeChanged() => InvalidateVisual(); public override void Render(DrawingContext context) { base.Render(context); var bounds = new Rect(Bounds.Size); var cx = bounds.Width / 2; var cy = bounds.Height / 2 + 10; var radius = Math.Min(bounds.Width, bounds.Height) / 2 - 20; var typeface = new Typeface("Microsoft YaHei", FontStyle.Normal, FontWeight.Bold); context.DrawEllipse(ThemeHelper.PanelBg, null, new Rect(cx - radius - 5, cy - radius - 5, (radius + 5) * 2, (radius + 5) * 2)); DrawArc(context, new Pen(ThemeHelper.Border, 8), cx, cy, radius, 135, 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); for (int i = 0; i <= 10; i++) { var angle = 135 + (270.0 * i / 10); var rad = angle * Math.PI / 180; var isMajor = i % 5 == 0; 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 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)); 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 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 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 cx, double cy, double radius, double startAngle, double sweepAngle) { var startRad = startAngle * Math.PI / 180; var endRad = (startAngle + sweepAngle) * Math.PI / 180; var steps = 36; 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 = 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) => new Size(120, 140); }