75 lines
3.0 KiB
Swift
75 lines
3.0 KiB
Swift
import SstData
|
|
import SstUI
|
|
import SwiftUI
|
|
|
|
/// The transport deck: VU meter, counter, LED bar and the REC/STOP keys. Always visible at the top.
|
|
struct DeckView: View {
|
|
@EnvironmentObject private var model: AppModel
|
|
|
|
var body: some View {
|
|
HStack(alignment: .top, spacing: Layout.panelGap) {
|
|
instruments
|
|
Spacer(minLength: 0)
|
|
controls
|
|
}
|
|
.padding(Layout.panelPadding)
|
|
.background(PanelBackground())
|
|
}
|
|
|
|
private var instruments: some View {
|
|
TimelineView(.animation(minimumInterval: 1 / Motion.needleUpdateHz, paused: !model.isRecording)) { context in
|
|
let dbfs = Double(model.levelSource.dbfs)
|
|
HStack(alignment: .top, spacing: Layout.panelGap) {
|
|
VUMeterView(levelDbfs: dbfs, isActive: model.isRecording, brand: AppIdentity.brandCaption)
|
|
.frame(width: MeterTokens.vuWidth)
|
|
VStack(alignment: .leading, spacing: Layout.panelGap) {
|
|
SilkLabel(Strings.time, style: Typography.labelSmall)
|
|
SegmentCounterView(StopwatchFormat.string(elapsed(at: context.date)))
|
|
SilkLabel(Strings.level, style: Typography.labelSmall)
|
|
LEDBarView(fraction: model.isRecording ? VUScale.fraction(forDbfs: dbfs) : 0)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private func elapsed(at date: Date) -> TimeInterval {
|
|
if let started = model.recordingStartedAt { return date.timeIntervalSince(started) }
|
|
return model.lastDuration ?? 0
|
|
}
|
|
|
|
private var controls: some View {
|
|
VStack(alignment: .trailing, spacing: Layout.panelGap) {
|
|
HStack(spacing: Layout.panelGap) {
|
|
Button { model.toggleWindowRecording() } label: {
|
|
HStack(spacing: Space.s3) {
|
|
LampView(isOn: model.isRecording)
|
|
Text(Strings.record)
|
|
}
|
|
}
|
|
.buttonStyle(KeyButtonStyle(kind: .record, latched: model.isRecording))
|
|
.disabled(!model.isRecording && model.windowRecordingBlocker != nil)
|
|
.help(model.windowRecordingBlocker ?? "")
|
|
|
|
Button(Strings.stop) { model.stopWindowRecording() }
|
|
.buttonStyle(KeyButtonStyle())
|
|
.disabled(!model.isRecording)
|
|
}
|
|
if let error = model.lastError {
|
|
HStack(spacing: Space.s3) {
|
|
LampView(isOn: true, tint: .amber)
|
|
Text(error)
|
|
.textStyle(Typography.meta)
|
|
.foregroundStyle(Palette.inkSecondary)
|
|
.multilineTextAlignment(.trailing)
|
|
}
|
|
} else {
|
|
Text(model.statusLine)
|
|
.textStyle(Typography.meta)
|
|
.foregroundStyle(Palette.inkSecondary)
|
|
.multilineTextAlignment(.trailing)
|
|
}
|
|
}
|
|
.frame(maxWidth: Layout.sidebarWidth, alignment: .trailing)
|
|
}
|
|
}
|