fix: compact toolbar with all controls (open/volume/track/playlist)

- Replaced text buttons with SF Symbol icon buttons (28x28)
- All features: folder/prev/play/next/volume+slider/track/repeat/fullscreen/playlist
- Removed Spacer() to prevent buttons from being clipped
- Single compact row layout, no overflow
This commit is contained in:
yumoqing 2026-06-23 08:40:01 +08:00
parent ad1be14a0c
commit f1eca64212

View File

@ -240,98 +240,96 @@ struct ControlToolbar: View {
//
HStack(spacing: 8) {
Text(bridge.currentTimeText)
.font(.system(size: 12, design: .monospaced))
.font(.system(size: 11, design: .monospaced))
.foregroundColor(.white)
.frame(width: 50, alignment: .trailing)
.frame(width: 45, alignment: .trailing)
ProgressSlider(player: bridge.player, bridge: bridge)
.frame(height: 20)
Text(bridge.totalTimeText)
.font(.system(size: 12, design: .monospaced))
.font(.system(size: 11, design: .monospaced))
.foregroundColor(.white)
.frame(width: 50, alignment: .leading)
.frame(width: 45, alignment: .leading)
}
//
HStack(spacing: 10) {
#if os(macOS)
ToolbarButton(label: "📂 \(L.open)") { bridge.openFileDialog(); onTouch() }
#endif
ToolbarButton(label: "") { bridge.playPrev(); onTouch() }
ToolbarButton(label: bridge.isPlaying ? "" : "▶️") { bridge.togglePlayPause(); onTouch() }
ToolbarButton(label: "") { bridge.playNext(); onTouch() }
//
HStack(spacing: 4) {
TB(icon: "folder") { bridge.openFileDialog(); onTouch() }
TB(icon: "backward.fill") { bridge.playPrev(); onTouch() }
TB(icon: bridge.isPlaying ? "pause.fill" : "play.fill") { bridge.togglePlayPause(); onTouch() }
TB(icon: "forward.fill") { bridge.playNext(); onTouch() }
Spacer()
Divider().frame(height: 20).padding(.horizontal, 4)
//
ToolbarButton(label: "🔈") { bridge.adjustVolume(by: -0.1); onTouch() }
//
//
TB(icon: volumeIcon()) { bridge.adjustVolume(by: 0.1); onTouch() }
GeometryReader { geo in
ZStack(alignment: .leading) {
RoundedRectangle(cornerRadius: 2)
.fill(Color.white.opacity(0.2))
.frame(height: 4)
RoundedRectangle(cornerRadius: 2)
.fill(Color.accentColor)
.frame(width: geo.size.width * CGFloat(bridge.volume), height: 4)
RoundedRectangle(cornerRadius: 2).fill(Color.white.opacity(0.2)).frame(height: 4)
RoundedRectangle(cornerRadius: 2).fill(Color.accentColor).frame(width: geo.size.width * CGFloat(bridge.volume), height: 4)
}
.contentShape(Rectangle())
.gesture(
DragGesture(minimumDistance: 0)
.onChanged { value in
let ratio = max(0, min(1, value.location.x / geo.size.width))
bridge.setVolume(Float(ratio))
onTouch()
}
DragGesture(minimumDistance: 0).onChanged { value in
bridge.setVolume(max(0, min(1, Float(value.location.x / geo.size.width))))
onTouch()
}
)
}
.frame(width: 80, height: 20)
.frame(width: 60, height: 20)
Text("\(Int(bridge.volume * 100))")
.font(.system(size: 11, design: .monospaced))
.font(.system(size: 10, design: .monospaced))
.foregroundColor(.white.opacity(0.7))
.frame(width: 30)
.frame(width: 25)
ToolbarButton(label: "🔊") { bridge.adjustVolume(by: 0.1); onTouch() }
Divider().frame(height: 20).padding(.horizontal, 4)
Spacer()
//
TB(icon: "music.note") { bridge.showTrackDialog = true; onTouch() }
Text(bridge.currentTrackLabel)
.font(.system(size: 11))
.foregroundColor(.white.opacity(0.7))
//
TB(icon: repeatIcon()) { bridge.cycleRepeatMode(); onTouch() }
ToolbarButton(label: L.audioTrack) { bridge.showTrackDialog = true; onTouch() }
ToolbarButton(label: "\(bridge.repeatMode.icon) \(bridge.repeatMode.displayName)") { bridge.cycleRepeatMode(); onTouch() }
ToolbarButton(label: "") { bridge.toggleFullscreen(); onTouch() }
ToolbarButton(label: "📋 \(L.list) (\(bridge.queue.count))") { bridge.togglePlaylistWindow(); onTouch() }
//
TB(icon: "arrow.up.left.and.arrow.down.right") { bridge.toggleFullscreen(); onTouch() }
//
TB(icon: "list.bullet") { bridge.togglePlaylistWindow(); onTouch() }
}
}
.padding(.horizontal, 16)
.padding(.vertical, 8)
.background(.black.opacity(0.55))
.onHover { hovering in
isHovering = hovering
if hovering { onTouch() }
.padding(.horizontal, 12)
.padding(.vertical, 6)
}
private func volumeIcon() -> String {
if bridge.volume <= 0 { return "speaker.slash.fill" }
if bridge.volume < 0.5 { return "speaker.wave.1.fill" }
return "speaker.wave.3.fill"
}
private func repeatIcon() -> String {
switch bridge.repeatMode {
case .none: return "repeat"
case .single: return "repeat.1"
case .all: return "repeat"
}
}
}
// MARK: -
struct ToolbarButton: View {
let label: String
//
struct TB: View {
let icon: String
let action: () -> Void
@State private var hovering = false
var body: some View {
Button(action: action) {
Text(label)
.font(.system(size: 13))
Image(systemName: icon)
.font(.system(size: 14))
.foregroundColor(.white)
.padding(.horizontal, 8)
.padding(.vertical, 4)
.background(hovering ? .white.opacity(0.15) : .clear)
.frame(width: 28, height: 28)
.background(hovering ? Color.white.opacity(0.2) : .clear)
.cornerRadius(4)
}
.buttonStyle(.plain)