import MusicAssistantCore import SwiftUI struct MusicalParametersEditorView: View { @Binding var project: SongProject var body: some View { VStack(alignment: .leading, spacing: 12) { ManualAutoIntegerRow( title: "BPM", value: manualAutoIntegerBinding(\.bpm) ) ManualAutoTextRow( title: "Key", value: manualAutoTextBinding(\.key), placeholder: "D" ) ManualAutoTextRow( title: "Scale", value: manualAutoTextBinding(\.scale), placeholder: "minor" ) ManualAutoTextRow( title: "Maqam", value: manualAutoTextBinding(\.maqam), placeholder: "Hijaz" ) } } private func manualAutoIntegerBinding( _ keyPath: WritableKeyPath?> ) -> Binding> { Binding { project[keyPath: keyPath] ?? ManualAutoValue(mode: .auto) } set: { newValue in project[keyPath: keyPath] = newValue } } private func manualAutoTextBinding( _ keyPath: WritableKeyPath?> ) -> Binding> { Binding { project[keyPath: keyPath] ?? ManualAutoValue(mode: .auto) } set: { newValue in project[keyPath: keyPath] = newValue } } } private struct ManualAutoIntegerRow: View { let title: String @Binding var value: ManualAutoValue var body: some View { VStack(alignment: .leading, spacing: 8) { ManualAutoPicker(title: title, mode: modeBinding) if value.mode == .manual { TextField(title, text: integerTextBinding) } } } private var modeBinding: Binding { Binding { value.mode } set: { newMode in value = ManualAutoValue( mode: newMode, value: newMode == .manual ? value.value : nil ) } } private var integerTextBinding: Binding { Binding { value.value.map(String.init) ?? "" } set: { newValue in value.value = Int(newValue.trimmingCharacters(in: .whitespacesAndNewlines)) } } } private struct ManualAutoTextRow: View { let title: String @Binding var value: ManualAutoValue let placeholder: String var body: some View { VStack(alignment: .leading, spacing: 8) { ManualAutoPicker(title: title, mode: modeBinding) if value.mode == .manual { TextField(placeholder, text: textBinding) } } } private var modeBinding: Binding { Binding { value.mode } set: { newMode in value = ManualAutoValue( mode: newMode, value: newMode == .manual ? value.value : nil ) } } private var textBinding: Binding { Binding { value.value ?? "" } set: { newValue in let trimmedValue = newValue.trimmingCharacters(in: .whitespacesAndNewlines) value.value = trimmedValue.isEmpty ? nil : trimmedValue } } } private struct ManualAutoPicker: View { let title: String @Binding var mode: ControlMode var body: some View { Picker(title, selection: $mode) { Text("Auto").tag(ControlMode.auto) Text("Manual").tag(ControlMode.manual) } .pickerStyle(.segmented) } } #Preview { MusicalParametersEditorPreview() } private struct MusicalParametersEditorPreview: View { @State private var project = SongProject( title: "Parameters", idea: "Preview", bpm: ManualAutoValue(mode: .manual, value: 96), key: ManualAutoValue(mode: .auto), scale: ManualAutoValue(mode: .manual, value: "minor"), maqam: ManualAutoValue(mode: .auto) ) var body: some View { Form { MusicalParametersEditorView(project: $project) } .formStyle(.grouped) .frame(width: 340) } }