From f1eca64212de2db461bf813f373a2d59c11c5e0b Mon Sep 17 00:00:00 2001 From: yumoqing Date: Tue, 23 Jun 2026 08:40:01 +0800 Subject: [PATCH] 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 --- Sources/MiniPlayerApp.swift | 110 ++++++++++++++++++------------------ 1 file changed, 54 insertions(+), 56 deletions(-) diff --git a/Sources/MiniPlayerApp.swift b/Sources/MiniPlayerApp.swift index 3ef39d6..e0333ce 100644 --- a/Sources/MiniPlayerApp.swift +++ b/Sources/MiniPlayerApp.swift @@ -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)