using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using ytLive.Helpers;
namespace ytLive.Controls;
/// A segmented level ladder for the audio dock.
///
/// Drawn in ONE pass. Twenty-four Rectangles per meter would
/// mean hundreds of elements updating on the UI thread; a single drawing call does not. The
/// consumer updates at ~20 Hz — music-level smoothness is not needed and the
/// paint cost should never compete with the compositor.
///
/// Level is a 0…1 display fraction (see ), never a gain setting: the
/// ladder shows what was measured.
public sealed class SegmentMeter : FrameworkElement
{
public static readonly DependencyProperty LevelProperty = DependencyProperty.Register(
nameof(Level), typeof(double), typeof(SegmentMeter),
new FrameworkPropertyMetadata(0d, FrameworkPropertyMetadataOptions.AffectsRender));
/// Peak-hold mark, 0…1. Falls back slowly so a clip stays visible long enough to see.
public static readonly DependencyProperty PeakProperty = DependencyProperty.Register(
nameof(Peak), typeof(double), typeof(SegmentMeter),
new FrameworkPropertyMetadata(0d, FrameworkPropertyMetadataOptions.AffectsRender));
public static readonly DependencyProperty SegmentsProperty = DependencyProperty.Register(
nameof(Segments), typeof(int), typeof(SegmentMeter),
new FrameworkPropertyMetadata(24, FrameworkPropertyMetadataOptions.AffectsRender));
public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register(
nameof(Orientation), typeof(Orientation), typeof(SegmentMeter),
new FrameworkPropertyMetadata(Orientation.Vertical, FrameworkPropertyMetadataOptions.AffectsRender));
public double Level { get => (double)GetValue(LevelProperty); set => SetValue(LevelProperty, value); }
public double Peak { get => (double)GetValue(PeakProperty); set => SetValue(PeakProperty, value); }
public int Segments { get => (int)GetValue(SegmentsProperty); set => SetValue(SegmentsProperty, value); }
public Orientation Orientation { get => (Orientation)GetValue(OrientationProperty); set => SetValue(OrientationProperty, value); }
public SegmentMeter()
{
// Token-driven, not literal: the ladder cannot drift from DESIGN.md.
SetResourceReference(NominalBrushProperty, "Brush.Meter.Nominal");
SetResourceReference(CautionBrushProperty, "Brush.Meter.Caution");
SetResourceReference(PeakBrushProperty, "Brush.Meter.Peak");
SetResourceReference(DimBrushProperty, "Brush.SurfaceContainerHighest");
}
private static readonly DependencyProperty NominalBrushProperty = DependencyProperty.Register(
"NominalBrush", typeof(Brush), typeof(SegmentMeter), new FrameworkPropertyMetadata(Brushes.Cyan, FrameworkPropertyMetadataOptions.AffectsRender));
private static readonly DependencyProperty CautionBrushProperty = DependencyProperty.Register(
"CautionBrush", typeof(Brush), typeof(SegmentMeter), new FrameworkPropertyMetadata(Brushes.Orange, FrameworkPropertyMetadataOptions.AffectsRender));
private static readonly DependencyProperty PeakBrushProperty = DependencyProperty.Register(
"PeakBrush", typeof(Brush), typeof(SegmentMeter), new FrameworkPropertyMetadata(Brushes.Red, FrameworkPropertyMetadataOptions.AffectsRender));
private static readonly DependencyProperty DimBrushProperty = DependencyProperty.Register(
"DimBrush", typeof(Brush), typeof(SegmentMeter), new FrameworkPropertyMetadata(Brushes.DimGray, FrameworkPropertyMetadataOptions.AffectsRender));
private Brush NominalBrush => (Brush)GetValue(NominalBrushProperty);
private Brush CautionBrush => (Brush)GetValue(CautionBrushProperty);
private Brush PeakBrush => (Brush)GetValue(PeakBrushProperty);
private Brush DimBrush => (Brush)GetValue(DimBrushProperty);
private const double Gap = 2;
protected override void OnRender(DrawingContext dc)
{
var count = Math.Max(1, Segments);
var vertical = Orientation == Orientation.Vertical;
var extent = vertical ? ActualHeight : ActualWidth;
var thickness = vertical ? ActualWidth : ActualHeight;
if (extent <= 0 || thickness <= 0) return;
var segmentSize = (extent - Gap * (count - 1)) / count;
if (segmentSize <= 0) return;
var lit = Math.Clamp(Level, 0, 1) * count;
for (var i = 0; i < count; i++)
{
// Segment 0 sits at the bottom (vertical) or the left (horizontal).
var fraction = (i + 1) / (double)count;
var brush = fraction >= MeterScale.ToFraction(MeterScale.PeakDb) ? PeakBrush
: fraction >= MeterScale.ToFraction(MeterScale.CautionDb) ? CautionBrush
: NominalBrush;
if (i >= lit) brush = DimBrush;
var offset = i * (segmentSize + Gap);
var rect = vertical
? new Rect(0, extent - offset - segmentSize, thickness, segmentSize)
: new Rect(offset, 0, segmentSize, thickness);
dc.DrawRectangle(brush, null, rect);
}
// Peak-hold: a 2px marker at the held level, drawn above the ladder.
var peak = Math.Clamp(Peak, 0, 1);
if (peak <= 0) return;
var line = vertical
? new Rect(0, extent - peak * extent - 1, thickness, 2)
: new Rect(peak * extent - 1, 0, 2, thickness);
dc.DrawRectangle(PeakBrush, null, line);
}
}