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:
parent
ad1be14a0c
commit
f1eca64212
@ -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)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user