removed unwanted bar now with waveforms, major updates and enhancements

This commit is contained in:
Mark Gregory
2026-11-07 10:23:45 +00:00
parent 0b0061681b
commit a1c25cdb23
3 changed files with 176 additions and 75 deletions
+114 -74
View File
@@ -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)