diff --git a/README.md b/README.md index cc4f625..5b7c242 100644 --- a/README.md +++ b/README.md @@ -61,15 +61,15 @@ Active playback prevents idle system sleep. Local video also keeps the display a Use the **Queue** icon immediately left of the compact-mode button in the playback controls, or **View → Show Queue**, to show the editable sidebar. The **+** button beside the queue count opens **Add to Queue…**; it and dropping files onto the sidebar append without replacing the queue or interrupting the current item. Drag rows to reorder, remove them with the minus button, and double-click a row to play it. The current item is marked; context menus also offer Play, Remove and Move Up/Down. -The header's **+** is a 20-point rounded-square button with an accent-tinted background and an 8-point gap after the queue count. +The fixed bottom row holds the queue label/count and **+** on the left, with previous/next controls on the right. The **+** is a 20-point rounded-square button with an accent-tinted background and an 8-point gap after the queue count. -The navigation row has extra space above it. Queue rows use equal-width leading and remove-icon slots, with 8-point outer insets matching the gaps between icons and filenames and no additional horizontal scroll-content margin. +Queue rows use equal-width leading and remove-icon slots, with 8-point outer insets matching the gaps between icons and filenames and no additional horizontal scroll-content margin. In a normal window, showing the queue expands the window outward while preserving the video's size; hiding it removes that extra width. The sidebar meets the video directly, without a separate divider gap. The window shifts left when necessary near the screen's right edge. Fullscreen keeps its fixed bounds. Resizing with the queue open preserves the video's aspect ratio while keeping the sidebar at a fixed width; closing the queue retains ordinary aspect-locked video resizing. -While the sidebar is open, its persistent top header holds the queue count, pin, Cast and settings controls without a surrounding glass container, with matching top and right button insets; these do not fade with the video controls. 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 header holds the pin, Cast and settings controls 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. 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. 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. diff --git a/Sources/App/TerebiApp.swift b/Sources/App/TerebiApp.swift index ff080e5..512bad0 100644 --- a/Sources/App/TerebiApp.swift +++ b/Sources/App/TerebiApp.swift @@ -2287,39 +2287,12 @@ private struct PlayerContent: View { private var queueSidebar: some View { VStack(spacing: 0) { HStack(spacing: 4) { - HStack(spacing: 4) { - Text("Queue").fontWeight(.semibold) - Text("\(model.queue.items.count)").foregroundStyle(.secondary) - } - .font(.system(size: 13)) - .accessibilityElement(children: .combine) - Button(action: model.addQueuePanel) { - Image(systemName: "plus") - .frame(width: 20, height: 20) - .background(Color(SunsetPalette.accent).opacity(0.1), in: RoundedRectangle(cornerRadius: 6)) - } - .buttonStyle(.plain) - .foregroundStyle(Color(SunsetPalette.accent)) - .padding(.leading, 4) - .help("Add to Queue…") - .accessibilityLabel("Add to Queue…") - .disabled(!model.canOpen) Spacer(minLength: 4) titleControls } .padding(.leading, 12) .padding(.trailing, 4) .frame(height: 32) - HStack { - iconButton("backward.end.fill", "Previous Queue Item") { model.advanceQueue(-1) } - .disabled(!model.canPreviousQueueItem) - iconButton("forward.end.fill", "Next Queue Item") { model.advanceQueue(1) } - .disabled(!model.canNextQueueItem) - Spacer() - } - .padding(.horizontal, 12) - .padding(.top, 8) - .padding(.bottom, 8) List { ForEach(model.queue.items) { item in HStack(spacing: 8) { @@ -2371,6 +2344,32 @@ private struct PlayerContent: View { .allowsHitTesting(false) } } + HStack(spacing: 4) { + HStack(spacing: 4) { + Text("Queue").fontWeight(.semibold) + Text("\(model.queue.items.count)").foregroundStyle(.secondary) + } + .font(.system(size: 13)) + .accessibilityElement(children: .combine) + Button(action: model.addQueuePanel) { + Image(systemName: "plus") + .frame(width: 20, height: 20) + .background(Color(SunsetPalette.accent).opacity(0.1), in: RoundedRectangle(cornerRadius: 6)) + } + .buttonStyle(.plain) + .foregroundStyle(Color(SunsetPalette.accent)) + .padding(.leading, 4) + .help("Add to Queue…") + .accessibilityLabel("Add to Queue…") + .disabled(!model.canOpen) + Spacer() + iconButton("backward.end.fill", "Previous Queue Item") { model.advanceQueue(-1) } + .disabled(!model.canPreviousQueueItem) + iconButton("forward.end.fill", "Next Queue Item") { model.advanceQueue(1) } + .disabled(!model.canNextQueueItem) + } + .padding(.horizontal, 12) + .padding(.vertical, 8) } .frame(width: 260) .background(PlayerGlass(cornerRadius: 0))