#if !os(tvOS)
import SwiftUI
/// One slider for any scalar ParamDescriptor. Tap the value → numeric entry;
/// double-tap the row → reset to the preset default; tap the label (with its ⓘ)
/// → a help popover. macOS also shows the help on hover; VoiceOver reads it as a hint.
struct ParamSlider: View {
@Bindable var state: AppState
let descriptor: ParamDescriptor
/// Friendly label override (e.g. the parameter matrix uses short names); the
/// help tooltip still comes from `descriptor`. `nil` falls back to `descriptor.label`.
var displayLabel: String? = nil
@State private var editingText: String?
private var value: Double { descriptor.get(state.edited) }
var body: some View {
VStack(alignment: .leading, spacing: Theme.Space.s1) {
HStack {
labelView
Spacer()
if state.isModified(descriptor) {
Button { state.pushUndo(); state.reset(descriptor) } label: {
Image(systemName: "arrow.uturn.backward").font(.system(size: 11))
.foregroundStyle(Theme.Colors.fgTertiary)
}
.buttonStyle(.plain)
.accessibilityLabel("Reset \(descriptor.label) to preset")
}
LockButton(state: state, id: descriptor.id)
.accessibilityLabel(state.isLocked(descriptor.id) ? "Unlock \(descriptor.label)" : "Lock \(descriptor.label)")
.accessibilityHint("Keep randomize and mutate from changing this")
.accessibilityValue(A11y.locked(state.isLocked(descriptor.id)))
valueLabel
}
Slider(value: Binding(get: { value }, set: { commit($0) }),
in: descriptor.range,
onEditingChanged: { editing in if editing { state.pushUndo() } })
.tint(Theme.Colors.accent)
.controlSize(.small)
.accessibilityLabel(descriptor.label)
.accessibilityValue(formatted)
.accessibilityHint(descriptor.tooltip)
.precisionControls(value: Binding(get: { value }, set: { commit($0) }),
range: descriptor.range,
baseStep: descriptor.isInt ? 1 : nil,
onEditStart: { state.pushUndo() })
}
.contentShape(Rectangle())
.onTapGesture(count: 2) { state.pushUndo(); state.reset(descriptor) }
}
private var labelView: some View {
HelpLabel(displayLabel ?? descriptor.label, help: descriptor.tooltip)
}
@ViewBuilder private var valueLabel: some View {
if let t = editingText {
TextField("", text: Binding(get: { t }, set: { editingText = $0 }))
.frame(width: 64)
.font(Theme.Fonts.value)
.textFieldStyle(.roundedBorder)
.onSubmit { commitText() }
} else {
Text(formatted)
.font(Theme.Fonts.value)
.foregroundStyle(Theme.Colors.fgPrimary)
.onTapGesture { editingText = formatted }
}
}
private var formatted: String {
descriptor.isInt ? String(Int(value.rounded())) : String(format: "%.3g", value)
}
private func commit(_ v: Double) {
var p = state.edited
descriptor.set(&p, clamp(v))
state.edited = p
}
private func commitText() {
if let t = editingText, let v = Double(t) { state.pushUndo(); commit(v) }
editingText = nil
}
private func clamp(_ v: Double) -> Double {
min(max(v, descriptor.range.lowerBound), descriptor.range.upperBound)
}
}
#endif