diff --git a/Checks/system-playback.swift b/Checks/system-playback.swift index ff9a1ff..b83a929 100644 --- a/Checks/system-playback.swift +++ b/Checks/system-playback.swift @@ -140,6 +140,15 @@ struct SystemPlaybackChecks { window.isReleasedWhenClosed = false defer { window.close() } model.attachWindow(window) + let videoFrame = window.frame + model.showsQueue = true + require(abs(window.frame.width - videoFrame.width - 261) < 0.01 && + abs(window.frame.height - videoFrame.height) < 0.01, + "opening queue grows window without shrinking video") + model.showsQueue = false + require(abs(window.frame.width - videoFrame.width) < 0.01 && + abs(window.frame.height - videoFrame.height) < 0.01, + "closing queue restores video-only window size") model.open(directory.appendingPathComponent("anamorphic.mp4")) require(model.isBusy && model.perform(.enterCompact) && model.isCompact, "compact entry is available before native media opening acknowledges") diff --git a/README.md b/README.md index 6e017cb..aa8c219 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,8 @@ Active playback prevents idle system sleep. Local video also keeps the display a Use the titlebar's **Queue** icon or **View → Show Queue** to show the editable sidebar. **Add to Queue…** 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. +In a normal window, showing the queue expands the window outward while preserving the video's size; hiding it removes that extra width. The window shifts left when necessary near the screen's right edge. Fullscreen keeps its fixed bounds. + 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. diff --git a/Sources/App/TerebiApp.swift b/Sources/App/TerebiApp.swift index 2f479f6..0473c4b 100644 --- a/Sources/App/TerebiApp.swift +++ b/Sources/App/TerebiApp.swift @@ -32,7 +32,22 @@ final class PlayerModel: ObservableObject { @Published private(set) var fileIcon: NSImage? @Published private(set) var commandError: String? @Published private(set) var queue = PlaybackQueue() - @Published var showsQueue = false + @Published var showsQueue = false { + didSet { + guard showsQueue != oldValue, let window, !isFullScreen, + !isCompact, !compactWindow.isTransitioning else { return } + window.aspectRatio = .zero + var frame = window.frame + frame.size.width += showsQueue ? 261 : -261 + if let screen = window.screen?.visibleFrame { + frame.origin.x = max(screen.minX, min(frame.minX, screen.maxX - frame.width)) + } + window.setFrame(frame, display: true) + if !showsQueue, state.has_video, state.video_aspect_ratio > 0 { + window.aspectRatio = NSSize(width: state.video_aspect_ratio, height: 1) + } + } + } private var queuePlaybackRevision: UInt64? private var queueCompletionHandled = false private var queueCastDevice: CastDevice? @@ -968,14 +983,15 @@ final class PlayerModel: ObservableObject { return } let screen = (window.screen ?? NSScreen.main)?.visibleFrame ?? window.frame - let width = min(window.frame.width, screen.width, screen.height * aspect) - let size = NSSize(width: width, height: width / aspect) + let sidebarWidth: CGFloat = showsQueue ? 261 : 0 + let width = min(window.frame.width - sidebarWidth, screen.width - sidebarWidth, screen.height * aspect) + let size = NSSize(width: width + sidebarWidth, height: width / aspect) // ponytail: extreme ratios retain letterboxing; compact controls if exact fitting is needed. guard size.width >= window.minSize.width, size.height >= window.minSize.height else { window.aspectRatio = .zero return } - window.aspectRatio = NSSize(width: aspect, height: 1) + window.aspectRatio = showsQueue ? .zero : NSSize(width: aspect, height: 1) let frame = NSRect( x: min(max(window.frame.midX - size.width / 2, screen.minX), screen.maxX - size.width), y: min(max(window.frame.midY - size.height / 2, screen.minY), screen.maxY - size.height),