style: fade playlist chrome into queue background

This commit is contained in:
2026-10-18 21:07:00 +02:00
parent b8275635f0
commit 0084609a00
2 changed files with 28 additions and 1 deletions
+2
View File
@@ -73,6 +73,8 @@ Resizing with the queue open preserves the video's aspect ratio while keeping th
While the sidebar is open, its persistent top-right overlay holds the pin, Cast and settings controls above the list without a surrounding glass container, with matching top and right button insets; these and the bottom queue controls do not fade with the video controls. The native layout check verifies that all three header glyphs actually render. Closing the sidebar returns the pin, Cast and settings controls to the auto-hiding video titlebar with its glass container. The queue toggle stays in the playback controls. Space and configured playback shortcuts continue working with queue-list focus; Tab navigation, deliberately focused buttons/sliders and text entry keep their native keyboard behavior. While the sidebar is open, its persistent top-right overlay holds the pin, Cast and settings controls above the list without a surrounding glass container, with matching top and right button insets; these and the bottom queue controls do not fade with the video controls. The native layout check verifies that all three header glyphs actually render. Closing the sidebar returns the pin, Cast and settings controls to the auto-hiding video titlebar with its glass container. The queue toggle stays in the playback controls. Space and configured playback shortcuts continue working with queue-list focus; Tab navigation, deliberately focused buttons/sliders and text entry keep their native keyboard behavior.
The sidebar, top controls and queue footer share an opaque playlist background. The top backing fades downward into the list over 20 points; the footer backing fades upward by the same amount, keeping scrolled filenames from interfering with the controls without hard edges.
Playback advances automatically and stops at the end, without wrapping. **Previous Queue Item** and **Next Queue Item** are available in the sidebar and Playback menu and are disabled at their respective ends. Reordering or removing another item does not interrupt playback; removing the current item plays its next neighbor, or stops if there is none. A playback error stays on that item rather than skipping it. Playback advances automatically and stops at the end, without wrapping. **Previous Queue Item** and **Next Queue Item** are available in the sidebar and Playback menu and are disabled at their respective ends. Reordering or removing another item does not interrupt playback; removing the current item plays its next neighbor, or stops if there is none. A playback error stays on that item rather than skipping it.
The queue contains local files only and lasts until it is replaced, closed or the app quits; it has no persistence, shuffle, repeat or playlist-file import. Opening a YouTube video or resolved stream clears it. The sidebar is hidden in compact mode; Playback-menu queue navigation remains available. Sidebar appearance, drag/drop interaction and physical-receiver queue transitions still need manual verification. The queue contains local files only and lasts until it is replaced, closed or the app quits; it has no persistence, shuffle, repeat or playlist-file import. Opening a YouTube video or resolved stream clears it. The sidebar is hidden in compact mode; Playback-menu queue navigation remains available. Sidebar appearance, drag/drop interaction and physical-receiver queue transitions still need manual verification.
+26 -1
View File
@@ -2284,6 +2284,27 @@ private struct PlayerContent: View {
) )
} }
private var queueBackground: Color { Color(nsColor: .textBackgroundColor) }
private func queueChromeBackground(top: Bool) -> some View {
VStack(spacing: 0) {
if !top {
LinearGradient(colors: [queueBackground.opacity(0), queueBackground],
startPoint: .top, endPoint: .bottom)
.frame(height: 20)
}
queueBackground
if top {
LinearGradient(colors: [queueBackground, queueBackground.opacity(0)],
startPoint: .top, endPoint: .bottom)
.frame(height: 20)
}
}
.padding(top ? .bottom : .top, -20)
.allowsHitTesting(false)
.accessibilityHidden(true)
}
private var queueSidebar: some View { private var queueSidebar: some View {
VStack(spacing: 0) { VStack(spacing: 0) {
Color.clear.frame(height: 32) Color.clear.frame(height: 32)
@@ -2351,6 +2372,7 @@ private struct PlayerContent: View {
.onMove { model.moveQueueItems(fromOffsets: $0, toOffset: $1) } .onMove { model.moveQueueItems(fromOffsets: $0, toOffset: $1) }
} }
.listStyle(.plain) .listStyle(.plain)
.scrollContentBackground(.hidden)
.contentMargins(.horizontal, 0, for: .scrollContent) .contentMargins(.horizontal, 0, for: .scrollContent)
.overlay { .overlay {
if model.queue.items.isEmpty { if model.queue.items.isEmpty {
@@ -2389,13 +2411,16 @@ private struct PlayerContent: View {
} }
.padding(.horizontal, 12) .padding(.horizontal, 12)
.padding(.vertical, 8) .padding(.vertical, 8)
.background { queueChromeBackground(top: false) }
} }
.frame(width: 260) .frame(width: 260)
.background(PlayerGlass(cornerRadius: 0)) .background(queueBackground)
.overlay(alignment: .topTrailing) { .overlay(alignment: .topTrailing) {
titleControls titleControls
.padding(.trailing, 4) .padding(.trailing, 4)
.frame(height: 32) .frame(height: 32)
.frame(maxWidth: .infinity, alignment: .trailing)
.background { queueChromeBackground(top: true) }
} }
.dropDestination(for: URL.self) { urls, _ in .dropDestination(for: URL.self) { urls, _ in
guard model.canOpen, urls.contains(where: \.isFileURL) else { return false } guard model.canOpen, urls.contains(where: \.isFileURL) else { return false }