Add instrument checkbox selection
This commit is contained in:
@@ -2,6 +2,7 @@ import MusicAssistantCore
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct InstrumentBrowserView: View {
|
struct InstrumentBrowserView: View {
|
||||||
|
@Binding private var project: SongProject
|
||||||
private let catalog: LocalInstrumentCatalog
|
private let catalog: LocalInstrumentCatalog
|
||||||
|
|
||||||
@State private var searchText = ""
|
@State private var searchText = ""
|
||||||
@@ -9,7 +10,11 @@ struct InstrumentBrowserView: View {
|
|||||||
@State private var selectedRegionOrigin: String?
|
@State private var selectedRegionOrigin: String?
|
||||||
@Environment(\.dismiss) private var dismiss
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
|
||||||
init(catalog: LocalInstrumentCatalog = LocalInstrumentCatalog()) {
|
init(
|
||||||
|
project: Binding<SongProject>,
|
||||||
|
catalog: LocalInstrumentCatalog = LocalInstrumentCatalog()
|
||||||
|
) {
|
||||||
|
_project = project
|
||||||
self.catalog = catalog
|
self.catalog = catalog
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,6 +55,7 @@ struct InstrumentBrowserView: View {
|
|||||||
),
|
),
|
||||||
id: \.id
|
id: \.id
|
||||||
) { instrument in
|
) { instrument in
|
||||||
|
Toggle(isOn: selectionBinding(for: instrument)) {
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
Text(instrument.name)
|
Text(instrument.name)
|
||||||
.font(.body.weight(.medium))
|
.font(.body.weight(.medium))
|
||||||
@@ -65,6 +71,8 @@ struct InstrumentBrowserView: View {
|
|||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
.toggleStyle(.checkbox)
|
||||||
.padding(.vertical, 2)
|
.padding(.vertical, 2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -80,8 +88,31 @@ struct InstrumentBrowserView: View {
|
|||||||
}
|
}
|
||||||
.frame(minWidth: 460, minHeight: 520)
|
.frame(minWidth: 460, minHeight: 520)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func selectionBinding(for instrument: InstrumentCatalogItem) -> Binding<Bool> {
|
||||||
|
Binding {
|
||||||
|
project.isInstrumentSelected(id: instrument.id)
|
||||||
|
} set: { isSelected in
|
||||||
|
project.setInstrumentSelected(
|
||||||
|
id: instrument.id,
|
||||||
|
isSelected: isSelected,
|
||||||
|
variant: instrument.name
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#Preview {
|
#Preview {
|
||||||
InstrumentBrowserView()
|
InstrumentBrowserPreview()
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct InstrumentBrowserPreview: View {
|
||||||
|
@State private var project = SongProject(
|
||||||
|
title: "Instrument Browser Preview",
|
||||||
|
idea: "Browse the instrument catalog."
|
||||||
|
)
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
InstrumentBrowserView(project: $project)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ struct ProjectInspectorView: View {
|
|||||||
}
|
}
|
||||||
.background(Color(nsColor: .windowBackgroundColor))
|
.background(Color(nsColor: .windowBackgroundColor))
|
||||||
.sheet(isPresented: $isInstrumentBrowserPresented) {
|
.sheet(isPresented: $isInstrumentBrowserPresented) {
|
||||||
InstrumentBrowserView()
|
InstrumentBrowserView(project: $project)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -312,6 +312,33 @@ public extension SongProject {
|
|||||||
instruments[index].selected = false
|
instruments[index].selected = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mutating func setInstrumentSelected(
|
||||||
|
id instrumentId: String,
|
||||||
|
isSelected: Bool,
|
||||||
|
variant: String? = nil
|
||||||
|
) {
|
||||||
|
guard isSelected else {
|
||||||
|
deselectInstrument(id: instrumentId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if let index = instruments.firstIndex(where: { $0.instrumentId == instrumentId }) {
|
||||||
|
instruments[index].selected = true
|
||||||
|
|
||||||
|
if instruments[index].variant == nil {
|
||||||
|
instruments[index].variant = variant
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
instruments.append(
|
||||||
|
InstrumentTrack(
|
||||||
|
instrumentId: instrumentId,
|
||||||
|
selected: true,
|
||||||
|
variant: variant
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mutating func configureInstrument(
|
mutating func configureInstrument(
|
||||||
id instrumentId: String,
|
id instrumentId: String,
|
||||||
variant: String?,
|
variant: String?,
|
||||||
|
|||||||
@@ -281,6 +281,40 @@ final class SongProjectModelTests: XCTestCase {
|
|||||||
XCTAssertEqual(project.instrumentTrack(for: "oud")?.placements, [introPlacement])
|
XCTAssertEqual(project.instrumentTrack(for: "oud")?.placements, [introPlacement])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testCatalogSelectionCanBeToggledWithoutReplacingExistingArrangement() {
|
||||||
|
let oudPlacement = InstrumentPlacement(sectionId: "intro", direction: "Opening melody")
|
||||||
|
var project = SongProject(
|
||||||
|
id: "catalog-selection-project",
|
||||||
|
title: "Catalog Selection",
|
||||||
|
idea: "Select and deselect instruments",
|
||||||
|
instruments: [
|
||||||
|
InstrumentTrack(
|
||||||
|
instrumentId: "oud",
|
||||||
|
selected: false,
|
||||||
|
variant: "Arabic oud",
|
||||||
|
playingStyle: "tremolo",
|
||||||
|
role: "main hook",
|
||||||
|
autoArrangementEnabled: false,
|
||||||
|
placements: [oudPlacement]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
project.setInstrumentSelected(id: "oud", isSelected: true, variant: "Oud")
|
||||||
|
|
||||||
|
XCTAssertTrue(project.isInstrumentSelected(id: "oud"))
|
||||||
|
XCTAssertEqual(project.instrumentTrack(for: "oud")?.variant, "Arabic oud")
|
||||||
|
XCTAssertEqual(project.instrumentTrack(for: "oud")?.role, "main hook")
|
||||||
|
XCTAssertEqual(project.instrumentTrack(for: "oud")?.placements, [oudPlacement])
|
||||||
|
|
||||||
|
project.setInstrumentSelected(id: "oud", isSelected: false)
|
||||||
|
project.setInstrumentSelected(id: "violin", isSelected: true, variant: "Violin")
|
||||||
|
|
||||||
|
XCTAssertFalse(project.isInstrumentSelected(id: "oud"))
|
||||||
|
XCTAssertTrue(project.isInstrumentSelected(id: "violin"))
|
||||||
|
XCTAssertEqual(project.instrumentTrack(for: "violin")?.variant, "Violin")
|
||||||
|
}
|
||||||
|
|
||||||
func testMultipleVocalistsCanBeAssignedToSections() {
|
func testMultipleVocalistsCanBeAssignedToSections() {
|
||||||
var project = SongProject(
|
var project = SongProject(
|
||||||
id: "project-6",
|
id: "project-6",
|
||||||
|
|||||||
+1
-1
@@ -85,7 +85,7 @@ requirement is missing and blocks implementation, record it in
|
|||||||
- [x] Build searchable instrument browser.
|
- [x] Build searchable instrument browser.
|
||||||
- [x] Add browsing/filtering by family/category.
|
- [x] Add browsing/filtering by family/category.
|
||||||
- [x] Add browsing/filtering by region/origin where useful.
|
- [x] Add browsing/filtering by region/origin where useful.
|
||||||
- [ ] Add checkbox-based multi-select and deselect behavior.
|
- [x] Add checkbox-based multi-select and deselect behavior.
|
||||||
- [ ] Persist selected instruments on the current Song Project.
|
- [ ] Persist selected instruments on the current Song Project.
|
||||||
- [ ] Make selected instruments available to OpenAI/song-generation
|
- [ ] Make selected instruments available to OpenAI/song-generation
|
||||||
logic for arrangement, roles, entry/exit timing, relevant structure
|
logic for arrangement, roles, entry/exit timing, relevant structure
|
||||||
|
|||||||
Reference in New Issue
Block a user