From 567e557968d5b41849a36a2b5456498daf9ca806 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 10 Nov 2026 17:30:45 -0800 Subject: [PATCH] iOS: show stem level inside the fader track Co-Authored-By: Claude Opus 5.5 --- ios/Stemify/UI/Screens/NowPlayingScreen.swift | 31 +++++++++---------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/ios/Stemify/UI/Screens/NowPlayingScreen.swift b/ios/Stemify/UI/Screens/NowPlayingScreen.swift index 0394b05..3efeaa5 100644 --- a/ios/Stemify/UI/Screens/NowPlayingScreen.swift +++ b/ios/Stemify/UI/Screens/NowPlayingScreen.swift @@ -806,20 +806,10 @@ private struct StemFader: View { var body: some View { let color = Color(hex: Stems.color(name)) FaderRow(dot: setting.muted ? p.label3 : color, label: Stems.label(name), percent: Int(stemTaper(setting.level) * 100), dim: setting.muted, height: height, stacked: roomy) { - VStack(spacing: 0) { - FaderSlider(value: setting.level, fill: setting.muted ? p.label3 : color, detent: StemSetting.unity, enabled: enabled, thumb: roomy ? 26 : 24) { - pm.setStemLevel(name, $0) - } - // Live level under the fader, -48..0 dBFS. - GeometryReader { geo in - let f = CGFloat(min(1, max(0, (20 * log10(level + 1e-5) + 48) / 48))) - ZStack(alignment: .leading) { - Capsule().fill(p.fill) - Capsule().fill(color).frame(width: geo.size.width * f) - } - } - .frame(height: 3) - .padding(.horizontal, 12) + // Live level, -48..0 dBFS, drawn as a light band inside the fader's fill. + let f = CGFloat(min(1, max(0, (20 * log10(level + 1e-5) + 48) / 48))) + FaderSlider(value: setting.level, fill: setting.muted ? p.label3 : color, detent: StemSetting.unity, enabled: enabled, thumb: roomy ? 26 : 24, meter: enabled && !setting.muted ? f : nil) { + pm.setStemLevel(name, $0) } } trailing: { if enabled { @@ -905,17 +895,19 @@ struct FaderSlider: View { var detent: Float? var enabled = true var thumb: CGFloat = 24 + /// 0...1 live level, shown as a lighter band over the filled part of the track. + var meter: CGFloat? let onChange: (Float) -> Void @Environment(\.palette) private var p @State private var start: Float? @State private var caught = false - init(value: Float, fill: Color, detent: Float? = nil, enabled: Bool = true, thumb: CGFloat = 24, onChange: @escaping (Float) -> Void) { - self.value = value; self.fill = fill; self.detent = detent; self.enabled = enabled; self.thumb = thumb; self.onChange = onChange + init(value: Float, fill: Color, detent: Float? = nil, enabled: Bool = true, thumb: CGFloat = 24, meter: CGFloat? = nil, onChange: @escaping (Float) -> Void) { + self.value = value; self.fill = fill; self.detent = detent; self.enabled = enabled; self.thumb = thumb; self.meter = meter; self.onChange = onChange } - private var bar: CGFloat { thumb >= 26 ? 5 : 4 } + private var bar: CGFloat { meter == nil ? (thumb >= 26 ? 5 : 4) : 6 } var body: some View { GeometryReader { geo in @@ -924,6 +916,11 @@ struct FaderSlider: View { ZStack(alignment: .leading) { Capsule().fill(p.fillStrong).frame(height: bar).padding(.horizontal, thumb / 2) Capsule().fill(fill).frame(width: x + thumb / 2, height: bar).padding(.leading, thumb / 2 - 0.01) + if let meter, meter > 0.01 { + Capsule().fill(.white.opacity(0.5)).frame(width: meter * (x + thumb / 2), height: bar) + .padding(.leading, thumb / 2 - 0.01) + .animation(.linear(duration: 1 / 30), value: meter) + } if let detent { p.label3.frame(width: 2, height: 10).offset(x: CGFloat(detent) * track + thumb / 2 - 1) }