removed unwanted bar now with waveforms, major updates and enhancements
This commit is contained in:
+114
-74
@@ -1024,7 +1024,10 @@ enum DJLibraryBrowser {
|
||||
case .missingArtist:return song.artist.trimmingCharacters(in:.whitespacesAndNewlines).isEmpty
|
||||
}
|
||||
}
|
||||
guard sort != .source else { return ascending ? filtered : Array(filtered.reversed()) }
|
||||
guard sort != .source else {
|
||||
let ordered = playlist == nil ? filtered.sorted{$0.id < $1.id} : filtered
|
||||
return ascending ? ordered : Array(ordered.reversed())
|
||||
}
|
||||
return filtered.sorted { lhs,rhs in
|
||||
let comparison: ComparisonResult
|
||||
switch sort {
|
||||
@@ -1055,6 +1058,10 @@ struct DJConsole: View {
|
||||
@State private var ascending = true
|
||||
@State private var collectionCounts = [DJCollection:Int]()
|
||||
@AppStorage("DJStudio.librarySidebar") private var sidebarVisible = true
|
||||
@AppStorage("DJStudio.performanceHeight") private var performanceHeight = 454.0
|
||||
@AppStorage("DJStudio.sidebarWidth") private var sidebarWidth = 184.0
|
||||
@State private var performanceDragStart: Double?
|
||||
@State private var sidebarDragStart: Double?
|
||||
@State private var queueVisible = false
|
||||
@State private var query = ""
|
||||
@State private var playlist: Int64?
|
||||
@@ -1097,9 +1104,19 @@ struct DJConsole: View {
|
||||
var body: some View {
|
||||
VStack(spacing:10) {
|
||||
header
|
||||
VSplitView {
|
||||
DJContinuousConsole(system:system,loadA:{loadSelected("A")},loadB:{loadSelected("B")}).frame(minHeight:408,idealHeight:438,maxHeight:560)
|
||||
DJLibrarySnapshot(revision:libraryRevision,selection:selection,selected:selected,page:page,busy:system.generatingWaveforms || system.importing,font:system.settings.libraryFont,height:system.settings.libraryRowHeight) { browser }.equatable().frame(minHeight:210,maxHeight:.infinity)
|
||||
GeometryReader { geometry in
|
||||
VStack(spacing:0) {
|
||||
DJContinuousConsole(system:system,loadA:{loadSelected("A")},loadB:{loadSelected("B")}).frame(height:performanceSize(geometry.size.height))
|
||||
RoundedRectangle(cornerRadius:2).fill(Color.white.opacity(0.16)).frame(width:44,height:3).frame(maxWidth:.infinity).frame(height:10).contentShape(Rectangle())
|
||||
.help("Drag to resize the performance area; double-click to reset")
|
||||
.onTapGesture(count:2) { performanceHeight = 454 }
|
||||
.gesture(DragGesture(coordinateSpace:.global).onChanged { value in
|
||||
if performanceDragStart == nil { performanceDragStart = performanceSize(geometry.size.height) }
|
||||
performanceHeight = min(max(418,(performanceDragStart ?? 454)+value.translation.height),max(418,geometry.size.height-220))
|
||||
}.onEnded { _ in performanceDragStart = nil })
|
||||
.accessibilityLabel("Resize DJ performance area")
|
||||
DJLibrarySnapshot(revision:libraryRevision,selection:selection,selected:selected,page:page,busy:system.generatingWaveforms || system.importing,font:system.settings.libraryFont,height:system.settings.libraryRowHeight) { browser }.equatable().frame(maxWidth:.infinity,maxHeight:.infinity)
|
||||
}
|
||||
}
|
||||
if let progress = system.automaticWaveformProgress {
|
||||
BackgroundWaveformStatus(progress:progress,cancel:{system.automaticWaveforms.cancel()},dismiss:{system.automaticWaveformProgress = nil})
|
||||
@@ -1112,7 +1129,7 @@ struct DJConsole: View {
|
||||
else { Button("View Log") { system.importLogVisible = true };Button("Dismiss") { system.importProgress = nil } }
|
||||
}.font(.caption).controlSize(.mini)
|
||||
}
|
||||
}.padding(14).frame(maxWidth:.infinity,maxHeight:.infinity,alignment:.top)
|
||||
}.padding(14).frame(minWidth:900,maxWidth:.infinity,minHeight:706,maxHeight:.infinity,alignment:.top)
|
||||
.background(DJStyle.background).foregroundStyle(.white)
|
||||
.preferredColorScheme(.dark)
|
||||
.alert("Replace playing track?",isPresented:Binding(get:{system.pendingReplacement != nil},set:{if !$0 { system.pendingReplacement = nil }}),presenting:system.pendingReplacement) { request in
|
||||
@@ -1174,13 +1191,16 @@ struct DJConsole: View {
|
||||
.onStudioChange(of:librarySort) { _ in updateFilteredTracks();page = 0 }
|
||||
.onStudioChange(of:ascending) { _ in updateFilteredTracks();page = 0 }
|
||||
.onStudioChange(of:sidebarVisible) { _ in libraryRevision = UUID() }
|
||||
.onStudioChange(of:sidebarWidth) { _ in libraryRevision = UUID() }
|
||||
.onStudioChange(of:system.favourites) { _ in updateFilteredTracks() }
|
||||
}
|
||||
private func performanceSize(_ available: Double) -> Double { max(0,min(max(418,performanceHeight),min(580,max(0,available-220)))) }
|
||||
private var header: some View {
|
||||
HStack(spacing:14) {
|
||||
ZStack { RoundedRectangle(cornerRadius:10).fill(DJStyle.a.opacity(0.14));Image(systemName:"waveform.path").font(.system(size:20,weight:.semibold)).foregroundStyle(DJStyle.a) }.frame(width:40,height:40)
|
||||
VStack(alignment:.leading,spacing:3) { Text("DJ STUDIO").font(.system(size:15,weight:.bold,design:.rounded)).tracking(2);Text("PERFORMANCE WORKSPACE").font(.system(size:8,weight:.medium)).tracking(1.8).foregroundStyle(.secondary) }
|
||||
Spacer()
|
||||
HStack(spacing:8) { Text("MASTER").font(.system(size:9,weight:.semibold)).foregroundStyle(.secondary);DJHardwareFader(value:$system.master,label:"DJ master level").frame(width:95,height:28) }
|
||||
Button { sidebarVisible.toggle() } label: { Image(systemName:"sidebar.left") }.help("Show or hide collections").buttonStyle(LibraryActionButtonStyle(tint:DJStyle.a))
|
||||
Button { graphicEQVisible = true } label: { Label("Equaliser",systemImage:"slider.vertical.3") }.buttonStyle(LibraryActionButtonStyle(tint:system.graphicEQ.enabled ? DJStyle.a : .gray))
|
||||
DJQueueButton(automix:system.automix,open:{queueVisible = true})
|
||||
@@ -1190,13 +1210,13 @@ struct DJConsole: View {
|
||||
private var libraryTitle: String { playlist.flatMap { id in system.playlists.first{$0.id == id}?.name } ?? collection.rawValue }
|
||||
private func chooseCollection(_ value: DJCollection) { playlist = nil;collection = value;librarySort = .title;ascending = true;refreshPlaylist();page = 0 }
|
||||
private var collectionSidebar: some View {
|
||||
ScrollView {
|
||||
VStack(alignment:.leading,spacing:0) {
|
||||
Text("COLLECTION").font(.system(size:9,weight:.bold)).tracking(1.5).foregroundStyle(.secondary).padding(.horizontal,16).padding(.top,18).padding(.bottom,10)
|
||||
ForEach(DJCollection.allCases) { scope in
|
||||
collectionRow(scope.rawValue,icon:scope.icon,count:collectionCounts[scope] ?? 0,active:playlist == nil && collection == scope) { chooseCollection(scope) }
|
||||
}
|
||||
HStack { Text("PLAYLISTS").font(.system(size:9,weight:.bold)).tracking(1.5).foregroundStyle(.secondary);Spacer();Button { renaming = false;playlistName = "";playlistEditor = true } label: { Image(systemName:"plus") }.buttonStyle(.plain).help("Create playlist") }.padding(.horizontal,16).padding(.top,24).padding(.bottom,8)
|
||||
ScrollView {
|
||||
VStack(spacing:2) {
|
||||
ForEach(system.playlists) { list in
|
||||
collectionRow(list.name,icon:"music.note",count:nil,active:playlist == list.id) { collection = .all;playlist = list.id;librarySort = .source;ascending = true;refreshPlaylist();page = 0 }
|
||||
@@ -1206,19 +1226,30 @@ struct DJConsole: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if system.playlists.isEmpty { Text("Build your next set with playlists.").font(.caption).foregroundStyle(.secondary).padding(.horizontal,16).padding(.bottom,18) }
|
||||
Spacer(minLength:0)
|
||||
}.frame(minWidth:156,idealWidth:184,maxWidth:250,maxHeight:.infinity).background(DJStyle.background)
|
||||
}.padding(.bottom,12)
|
||||
}.frame(minWidth:156,idealWidth:176,maxWidth:210,maxHeight:.infinity).background(DJStyle.background)
|
||||
}
|
||||
private func collectionRow(_ name: String,icon: String,count: Int?,active: Bool,action: @escaping () -> Void) -> some View {
|
||||
Button(action:action) {
|
||||
HStack(spacing:9) { Image(systemName:icon).frame(width:16).foregroundStyle(active ? DJStyle.a : .secondary);Text(name).lineLimit(1);Spacer(minLength:2);if let count { Text(count.formatted()).font(.system(size:10).monospacedDigit()).foregroundStyle(.secondary) } .font(.system(size:11,weight:active ? .semibold : .regular)).padding(.horizontal,12).padding(.vertical,9).background(active ? DJStyle.a.opacity(0.12) : .clear,in:RoundedRectangle(cornerRadius:7))
|
||||
HStack(spacing:9) { Image(systemName:icon).frame(width:16).foregroundStyle(active ? DJStyle.a : .secondary);Text(name).lineLimit(1);Spacer(minLength:2);if let count { Text(count.formatted()).font(.system(size:10).monospacedDigit()).foregroundStyle(.secondary) } }.font(.system(size:11,weight:active ? .semibold : .regular)).padding(.horizontal,12).padding(.vertical,9).background(active ? DJStyle.a.opacity(0.12) : .clear,in:RoundedRectangle(cornerRadius:7))
|
||||
}.buttonStyle(.plain).padding(.horizontal,6).accessibilityValue(active ? "Selected" : "")
|
||||
}
|
||||
private var browser: some View {
|
||||
HSplitView {
|
||||
if sidebarVisible { collectionSidebar }
|
||||
GeometryReader { geometry in
|
||||
HStack(spacing:0) {
|
||||
if sidebarVisible {
|
||||
collectionSidebar.frame(width:min(max(156,sidebarWidth),210))
|
||||
Rectangle().fill(Color.white.opacity(0.07)).frame(width:6).contentShape(Rectangle())
|
||||
.help("Drag to resize collections; double-click to reset")
|
||||
.onTapGesture(count:2) { sidebarWidth = 184 }
|
||||
.gesture(DragGesture(coordinateSpace:.global).onChanged { value in
|
||||
if sidebarDragStart == nil { sidebarDragStart = sidebarWidth }
|
||||
sidebarWidth = min(210,max(156,(sidebarDragStart ?? 184)+value.translation.width))
|
||||
}.onEnded { _ in sidebarDragStart = nil })
|
||||
.accessibilityLabel("Resize DJ collection sidebar")
|
||||
}
|
||||
VStack(spacing:0) {
|
||||
HStack(spacing:12) {
|
||||
VStack(alignment:.leading,spacing:3) { Text(libraryTitle).font(.system(size:17,weight:.semibold));Text("\(songs.count.formatted()) tracks").font(.caption).foregroundStyle(.secondary) }
|
||||
@@ -1241,25 +1272,19 @@ struct DJConsole: View {
|
||||
Divider().overlay(Color.white.opacity(0.05))
|
||||
ScrollViewReader { reader in
|
||||
Table(visible,selection:$selection) {
|
||||
TableColumn("Favourite") { song in
|
||||
TableColumn("Fav") { song in
|
||||
Button { system.toggleFavourite(song.id) } label: { GoldStar(filled:system.favourites.contains(song.id),size:15) }.buttonStyle(.plain).accessibilityLabel(system.favourites.contains(song.id) ? "Remove \(song.title) from favourites" : "Favourite \(song.title)")
|
||||
}.width(55)
|
||||
}.width(38)
|
||||
TableColumn("Title") { song in
|
||||
DJLibraryDragTitle(title:song.title,reference:system.dragReference(song.id),fontSize:system.settings.libraryFont,select:{selectTrack(song.id)},doubleClick:{system.loadFromLibraryDoubleClick(song)},selectAll:{selectAllTracks()})
|
||||
.frame(maxWidth:.infinity,alignment:.leading).frame(minHeight:system.settings.libraryRowHeight)
|
||||
}.width(min:180,ideal:400,max:1600)
|
||||
TableColumn("Artist") { song in Text(song.artist).lineLimit(1).help(song.artist) }.width(min:100,ideal:200,max:800)
|
||||
TableColumn("Album") { song in Text(song.album).lineLimit(1).foregroundStyle(.secondary).help(song.album) }.width(min:90,ideal:180,max:800)
|
||||
}.width(min:150,ideal:260,max:1200)
|
||||
TableColumn("Artist") { song in Text(song.artist).lineLimit(1).help(song.artist) }.width(min:90,ideal:150,max:800)
|
||||
TableColumn("Album") { song in Text(song.album).lineLimit(1).foregroundStyle(.secondary).help(song.album) }.width(min:80,ideal:120,max:800)
|
||||
TableColumn("Time") { song in Text(DJStyle.stamp(song.duration)).monospacedDigit().foregroundStyle(.secondary) }.width(min:45,ideal:60,max:100)
|
||||
TableColumn("Rating") { song in
|
||||
GoldRating(rating:Binding(get:{song.rating},set:{value in system.perform { try system.library?.rate(song.id,value);try system.refresh() } }))
|
||||
}.width(min:95,ideal:105,max:140)
|
||||
TableColumn("Load") { song in
|
||||
HStack(spacing:7) {
|
||||
Button("A") { system.load(song,into:"A") }.tint(DJStyle.a).accessibilityLabel("Load \(song.title) into A")
|
||||
Button("B") { system.load(song,into:"B") }.tint(DJStyle.b).accessibilityLabel("Load \(song.title) into B")
|
||||
}.buttonStyle(.bordered).controlSize(.small)
|
||||
}.width(90)
|
||||
}.font(.system(size:system.settings.libraryFont))
|
||||
.contextMenu(forSelectionType:Int64.self) { ids in
|
||||
if let song = songs.first(where:{ids.contains($0.id)}) { playlistMenu(song) }
|
||||
@@ -1284,6 +1309,7 @@ struct DJConsole: View {
|
||||
}.padding(10).background(DJStyle.background)
|
||||
}
|
||||
}.frame(minWidth:530,maxWidth:.infinity,maxHeight:.infinity).background(DJStyle.panel)
|
||||
}
|
||||
}.clipShape(RoundedRectangle(cornerRadius:12)).overlay(RoundedRectangle(cornerRadius:12).stroke(.white.opacity(0.07)))
|
||||
}
|
||||
@ViewBuilder private func playlistMenu(_ song: Song) -> some View {
|
||||
@@ -1372,11 +1398,10 @@ private struct DJDeckSurfaces {
|
||||
@ObservedObject var system: DJSystem
|
||||
private var color: Color { DJStyle.color(deck.letter) }
|
||||
var tempoStrip: some View {
|
||||
VStack(spacing:5) {
|
||||
VStack(spacing:3) {
|
||||
Button("SYNC") { system.sync(deck) }.foregroundStyle(system.syncActive ? color : Color.secondary).font(.system(size:11,weight:.bold)) .buttonStyle(DJConsoleButtonStyle(system.syncActive ? color : nil,active:system.syncActive)).accessibilityValue(system.syncActive ? "On" : "Off").help("Match the other deck to this deck’s BPM; click again to restore both original tempos").accessibilityIdentifier("dj.\(deck.letter).sync")
|
||||
Text(deck.bpmConfirmed ? String(format:"%.1f",deck.bpm*player.tempo) : "—").font(.system(size:11,weight:.semibold).monospacedDigit())
|
||||
Button(String(format:"%+.1f%%",(player.tempo-1)*100)) { player.tempo = 1 }.font(.system(size:9).monospacedDigit()).buttonStyle(.plain).foregroundStyle(.secondary).help("Reset tempo")
|
||||
DJHardwareFader(value:Binding(get:{system.settings.position(for:player.tempo)},set:{player.tempo = system.settings.tempo(at:$0)}),label:"Deck \(deck.letter) tempo",vertical:true).frame(width:52,height:94)
|
||||
DJHardwareFader(value:Binding(get:{system.settings.position(for:player.tempo)},set:{player.tempo = system.settings.tempo(at:$0)}),label:"Deck \(deck.letter) tempo",vertical:true).frame(width:44,height:74)
|
||||
HStack(spacing:16) { Button("−") { player.tempo = max(1-system.settings.tempoRange/100,player.tempo-0.001) };Button("+") { player.tempo = min(1+system.settings.tempoRange/100,player.tempo+0.001) } }.buttonStyle(.plain).font(.system(size:15,weight:.medium))
|
||||
}.frame(width:62).disabled(!player.canJog)
|
||||
}
|
||||
@@ -1387,13 +1412,13 @@ private struct DJDeckSurfaces {
|
||||
DJKnob(label:"MID",value:$player.midGain,range:-12...12,reset:0,color:color)
|
||||
DJKnob(label:"LOW",value:$player.lowGain,range:-12...12,reset:0,color:color)
|
||||
}
|
||||
VStack(spacing:12) {
|
||||
VStack(spacing:8) {
|
||||
DJKnob(label:"FILTER",value:$player.filter,range:-1...1,reset:0,color:color,format:{String(format:"%+.0f%%",$0*100)})
|
||||
HStack(spacing:3) {
|
||||
VStack(spacing:5) {
|
||||
ForEach(0..<8) { i in RoundedRectangle(cornerRadius:1).fill(Double(max(player.leftLevel,player.rightLevel))*8 > Double(7-i) ? color : Color.white.opacity(0.07)).frame(width:5,height:5) }
|
||||
}.accessibilityLabel("Deck output meter")
|
||||
DJHardwareFader(value:$deck.level,label:"Deck \(deck.letter) level",vertical:true).frame(width:44,height:94)
|
||||
DJHardwareFader(value:$deck.level,label:"Deck \(deck.letter) level",vertical:true).frame(width:44,height:70)
|
||||
}
|
||||
Text("LEVEL").font(.system(size:8,weight:.semibold)).foregroundStyle(.secondary)
|
||||
Button { player.vocalReductionEnabled.toggle() } label: { Image(systemName:player.vocalReductionEnabled ? "mic.slash.fill" : "mic.fill") }.buttonStyle(.plain).foregroundStyle(player.vocalReductionEnabled ? color : .secondary).help("Centre vocal reduction: reduces centred stereo audio, including some instruments; mono tracks pass through").accessibilityLabel("Deck \(deck.letter) centre vocal reduction").accessibilityValue(player.vocalReductionEnabled ? "On" : "Off")
|
||||
@@ -1639,35 +1664,50 @@ struct DJContinuousConsole: View {
|
||||
let loadA: () -> Void
|
||||
let loadB: () -> Void
|
||||
var body: some View {
|
||||
VStack(spacing:0) {
|
||||
HStack(spacing:0) {
|
||||
DJDeckDropZone(system:system,letter:"A") { DJTrackHeader(deck:system.a,player:system.a.player,system:system,load:loadA) }
|
||||
Divider()
|
||||
DJDeckDropZone(system:system,letter:"B") { DJTrackHeader(deck:system.b,player:system.b.player,system:system,load:loadB) }
|
||||
}.frame(height:92).background(Color.black.opacity(0.4))
|
||||
Divider()
|
||||
HStack(spacing:0) {
|
||||
DJDeckDropZone(system:system,letter:"A") { DJConsoleWing(deck:system.a,player:system.a.player,system:system) }.frame(width:344)
|
||||
Divider()
|
||||
VStack(spacing:1) {
|
||||
DJDeckDropZone(system:system,letter:"A") { DJCentreWaveform(deck:system.a,player:system.a.player,letter:"A",color:DJStyle.a,settings:system.settings) }
|
||||
DJDeckDropZone(system:system,letter:"B") { DJCentreWaveform(deck:system.b,player:system.b.player,letter:"B",color:DJStyle.b,settings:system.settings) }
|
||||
}.frame(maxWidth:.infinity).background(.black)
|
||||
Divider()
|
||||
DJDeckDropZone(system:system,letter:"B") { DJConsoleWing(deck:system.b,player:system.b.player,system:system) }.frame(width:344)
|
||||
}.frame(height:198).background(Color(white:0.12))
|
||||
Divider()
|
||||
HStack(spacing:12) {
|
||||
DJConsoleTransport(deck:system.a,player:system.a.player,system:system)
|
||||
VStack(spacing:0) {
|
||||
DJHardwareFader(value:$system.crossfader,label:"DJ crossfader").frame(height:36).disabled(system.routing.mode == "External")
|
||||
HStack { Text("A").foregroundStyle(DJStyle.a);Spacer();Button("Centre") { system.crossfader = 0.5 }.buttonStyle(.plain);Spacer();Text("B").foregroundStyle(DJStyle.b) }.font(.system(size:9))
|
||||
}.frame(width:195)
|
||||
DJConsoleTransport(deck:system.b,player:system.b.player,system:system)
|
||||
}.padding(.horizontal,12).padding(.vertical,10).background(Color(white:0.14))
|
||||
}.overlay(Rectangle().stroke(.white.opacity(0.09)))
|
||||
VStack(spacing:8) {
|
||||
HStack(spacing:10) {
|
||||
DJDeckDropZone(system:system,letter:"A") { DJTrackHeader(deck:system.a,player:system.a.player,system:system,load:loadA) }.background(DJStyle.panel,in:RoundedRectangle(cornerRadius:10))
|
||||
DJDeckDropZone(system:system,letter:"B") { DJTrackHeader(deck:system.b,player:system.b.player,system:system,load:loadB) }.background(DJStyle.panel,in:RoundedRectangle(cornerRadius:10))
|
||||
}.frame(height:82)
|
||||
VStack(spacing:2) {
|
||||
DJDeckDropZone(system:system,letter:"A") { DJCentreWaveform(deck:system.a,player:system.a.player,letter:"A",color:DJStyle.a,settings:system.settings) }
|
||||
DJDeckDropZone(system:system,letter:"B") { DJCentreWaveform(deck:system.b,player:system.b.player,letter:"B",color:DJStyle.b,settings:system.settings) }
|
||||
}.frame(minHeight:64,maxHeight:.infinity).background(.black).clipShape(RoundedRectangle(cornerRadius:8)).clipped()
|
||||
HStack(alignment:.top,spacing:10) {
|
||||
performance(system.a)
|
||||
mixer.frame(width:292)
|
||||
performance(system.b)
|
||||
}.frame(height:252)
|
||||
}.accessibilityIdentifier("dj.performance.workspace")
|
||||
}
|
||||
private func performance(_ deck: DJChannel) -> some View {
|
||||
VStack(spacing:6) {
|
||||
HStack { Text("DECK \(deck.letter)").foregroundStyle(DJStyle.color(deck.letter));Spacer();Text("PERFORMANCE").foregroundStyle(.secondary) }.font(.system(size:9,weight:.bold)).tracking(1.2)
|
||||
DJDeckDropZone(system:system,letter:deck.letter) { DJConsoleWing(deck:deck,player:deck.player,system:system) }
|
||||
Divider().overlay(.white.opacity(0.04))
|
||||
DJConsoleTransport(deck:deck,player:deck.player,system:system)
|
||||
}.padding(12).frame(maxWidth:.infinity,maxHeight:.infinity,alignment:.top)
|
||||
.background(DJStyle.panel,in:RoundedRectangle(cornerRadius:12))
|
||||
.overlay(alignment:.top) { RoundedRectangle(cornerRadius:2).fill(DJStyle.color(deck.letter)).frame(height:2).padding(.horizontal,14) }
|
||||
}
|
||||
private var mixer: some View {
|
||||
VStack(spacing:8) {
|
||||
HStack { Text("CHANNEL MIXER");Spacer();Text(system.routing.mode.uppercased()).foregroundStyle(.secondary) }.font(.system(size:8,weight:.bold)).tracking(1)
|
||||
HStack(alignment:.top,spacing:12) {
|
||||
VStack(spacing:4) { Text("A").foregroundStyle(DJStyle.a);DJDeckSurfaces(deck:system.a,player:system.a.player,system:system).tonePanel }
|
||||
Divider().frame(height:152)
|
||||
VStack(spacing:4) { Text("B").foregroundStyle(DJStyle.b);DJDeckSurfaces(deck:system.b,player:system.b.player,system:system).tonePanel }
|
||||
}.font(.system(size:9,weight:.bold))
|
||||
HStack(spacing:8) {
|
||||
Text("A").foregroundStyle(DJStyle.a)
|
||||
DJHardwareFader(value:$system.crossfader,label:"DJ crossfader").frame(height:30).disabled(system.routing.mode == "External")
|
||||
Text("B").foregroundStyle(DJStyle.b)
|
||||
Button { system.crossfader = 0.5 } label: { Image(systemName:"scope") }.buttonStyle(.plain).help("Centre crossfader")
|
||||
}.font(.system(size:10,weight:.semibold))
|
||||
}.padding(12).frame(maxHeight:.infinity,alignment:.top).background(DJStyle.panel,in:RoundedRectangle(cornerRadius:12))
|
||||
}
|
||||
}
|
||||
|
||||
private struct DJTrackHeader: View {
|
||||
@ObservedObject var deck: DJChannel
|
||||
@ObservedObject var player: Player
|
||||
@@ -1712,25 +1752,21 @@ private struct DJConsoleWing: View {
|
||||
@ObservedObject var system: DJSystem
|
||||
var body: some View {
|
||||
let face = DJDeckSurfaces(deck:deck,player:player,system:system)
|
||||
HStack(spacing:10) {
|
||||
if deck.letter == "B" { face.tonePanel }
|
||||
if deck.letter == "A" { face.tempoStrip }
|
||||
HStack(spacing:18) {
|
||||
face.tempoStrip
|
||||
Spacer(minLength:0)
|
||||
ZStack {
|
||||
DJPlatter(player:player,letter:deck.letter,color:DJStyle.color(deck.letter),markerEnabled:system.settings.platterMarker).frame(width:134,height:134)
|
||||
DJPlatter(player:player,letter:deck.letter,color:DJStyle.color(deck.letter),markerEnabled:system.settings.platterMarker).frame(width:112,height:112)
|
||||
VStack(spacing:3) {
|
||||
Text(String(format:"%+.1f%%",(player.tempo-1)*100)).font(.system(size:9)).foregroundStyle(.secondary)
|
||||
Text(deck.bpmConfirmed ? String(format:"%.1f",deck.bpm*player.tempo) : deck.letter).font(.system(size:21,weight:.semibold).monospacedDigit()).foregroundStyle(DJStyle.color(deck.letter))
|
||||
Divider().frame(width:62)
|
||||
Text(deck.bpmConfirmed ? String(format:"%.1f",deck.bpm*player.tempo) : deck.letter).font(.system(size:19,weight:.semibold).monospacedDigit()).foregroundStyle(DJStyle.color(deck.letter))
|
||||
Text(DJStyle.stamp(player.time)).font(.system(size:10).monospacedDigit()).foregroundStyle(.secondary)
|
||||
Text("−"+DJStyle.stamp(player.duration-player.time)).font(.system(size:11).monospacedDigit())
|
||||
}.frame(width:95,height:95).background(Color(white:0.095),in:Circle()).allowsHitTesting(false)
|
||||
}.frame(width:76,height:76).background(DJStyle.background,in:Circle()).allowsHitTesting(false)
|
||||
}
|
||||
if deck.letter == "A" { face.tonePanel }
|
||||
if deck.letter == "B" { face.tempoStrip }
|
||||
}.padding(.horizontal,9).frame(width:344).opacity(system.settings.dimInactiveDeck && !player.playing ? 0.55 : 1)
|
||||
|
||||
Spacer(minLength:0)
|
||||
}.frame(maxWidth:.infinity).frame(height:130).opacity(system.settings.dimInactiveDeck && !player.playing ? 0.55 : 1)
|
||||
}
|
||||
}
|
||||
|
||||
private struct DJConsoleTransport: View {
|
||||
@ObservedObject var deck: DJChannel
|
||||
@ObservedObject var player: Player
|
||||
@@ -1750,15 +1786,19 @@ private struct DJConsoleTransport: View {
|
||||
var body: some View {
|
||||
VStack(spacing:6) {
|
||||
HStack(spacing:8) {
|
||||
if deck.letter == "A" { playButton;cueButton;loopButtons;headphones;Spacer(minLength:0) }
|
||||
else { Spacer(minLength:0);headphones;loopButtons;cueButton;playButton }
|
||||
playButton;cueButton;loopButtons;headphones;Spacer(minLength:0)
|
||||
|
||||
}.controlSize(.regular)
|
||||
HStack(spacing:6) {
|
||||
if deck.letter == "B" { Spacer(minLength:0) }
|
||||
HStack(spacing:4) {
|
||||
ForEach(0..<4) { index in Button("\(index+1)") { system.hotCue(index,deck:deck) }.foregroundStyle(player.hotCues[index] == nil ? Color.secondary : DJStyle.color(deck.letter)).contextMenu { Button("Clear hot cue") { player.hotCue(index,clear:true) } } }
|
||||
Button("Cue \(DJStyle.stamp(player.cuePosition)) ✎") { system.editCue(deck) };Button("IN") { player.markLoopIn() };Button("OUT") { player.markLoopOut() };Button("Loop ✎") { system.loopEditor = deck.letter }
|
||||
Menu {
|
||||
Button("Edit Cue · \(DJStyle.stamp(player.cuePosition))") { system.editCue(deck) }
|
||||
Button("Set Cue Here") { player.setCue() }
|
||||
Button("Edit Loop…") { system.loopEditor = deck.letter }
|
||||
} label: { Image(systemName:"flag") }
|
||||
Button("IN") { player.markLoopIn() };Button("OUT") { player.markLoopOut() }
|
||||
Menu { Button("Stop") { player.stopDeck() };Button("Exit loop") { player.clearLoop() };Button("Nudge back") { player.jog(-0.05) };Button("Nudge forward") { player.jog(0.05) } } label: { Image(systemName:"ellipsis") }
|
||||
if deck.letter == "A" { Spacer(minLength:0) }
|
||||
Spacer(minLength:0)
|
||||
}.controlSize(.mini)
|
||||
}.buttonStyle(DJConsoleButtonStyle()).frame(maxWidth:.infinity).disabled(!player.canJog)
|
||||
}
|
||||
@@ -1935,7 +1975,7 @@ private struct DJConsoleButtonStyle: ButtonStyle {
|
||||
@Environment(\.isEnabled) private var enabled
|
||||
init(_ accent: Color? = nil,active: Bool = false) { self.accent = accent;self.active = active }
|
||||
func makeBody(configuration: Configuration) -> some View {
|
||||
configuration.label.padding(.horizontal,size == .mini ? 6 : 12).padding(.vertical,size == .mini ? 3 : 7)
|
||||
configuration.label.padding(.horizontal,size == .mini ? 5 : 7).padding(.vertical,size == .mini ? 3 : 6)
|
||||
.background(active ? (accent ?? .blue).opacity(0.45) : Color(white:configuration.isPressed ? 0.20 : 0.12),in:RoundedRectangle(cornerRadius:5))
|
||||
.overlay(RoundedRectangle(cornerRadius:5).stroke(accent ?? .black.opacity(0.8),lineWidth:accent == nil ? 1 : 2))
|
||||
.opacity(enabled ? 1 : 0.35)
|
||||
|
||||
Reference in New Issue
Block a user