diff --git a/Checks/compact-window.swift b/Checks/compact-window.swift index 5f5140c..a248b4c 100644 --- a/Checks/compact-window.swift +++ b/Checks/compact-window.swift @@ -69,10 +69,10 @@ struct CompactWindowChecks { let usableScreen = NSRect(x: -1_200, y: 50, width: 1_200, height: 800) let landscape = CompactWindowController.initialFrame(aspect: 16.0 / 9.0, visibleFrame: usableScreen) - try require(sameFrame(landscape, NSRect(x: -336, y: 66, width: 320, height: 180)), - "16:9 enters at bottom right with 320pt width and 16pt inset") + try require(sameFrame(landscape, NSRect(x: -336, y: 654, width: 320, height: 180)), + "16:9 enters at top right with 320pt width and 16pt inset") let portrait = CompactWindowController.initialFrame(aspect: 9.0 / 16.0, visibleFrame: usableScreen) - try require(sameFrame(portrait, NSRect(x: -336, y: 66, width: 320, height: 5_120.0 / 9.0)), + try require(sameFrame(portrait, NSRect(x: -336, y: 834 - 5_120.0 / 9.0, width: 320, height: 5_120.0 / 9.0)), "portrait preserves exact aspect when it fits") for aspect in [16.0 / 9.0, 9.0 / 16.0, 100.0, 0.01] { let frame = CompactWindowController.initialFrame(aspect: aspect, visibleFrame: usableScreen) @@ -84,12 +84,12 @@ struct CompactWindowChecks { try require(frame.minX >= usableScreen.minX + 16 && frame.minY >= usableScreen.minY + 16 && frame.maxX <= usableScreen.maxX - 16 && frame.maxY <= usableScreen.maxY - 16, "aspect \(aspect) stays inside inset usable screen") - try require(close(frame.maxX, usableScreen.maxX - 16) && close(frame.minY, usableScreen.minY + 16), - "aspect \(aspect) stays anchored bottom right") + try require(close(frame.maxX, usableScreen.maxX - 16) && close(frame.maxY, usableScreen.maxY - 16), + "aspect \(aspect) stays anchored top right") } let smallScreen = NSRect(x: 0, y: 0, width: 300, height: 400) let bounded = CompactWindowController.initialFrame(aspect: 16.0 / 9.0, visibleFrame: smallScreen) - try require(sameFrame(bounded, NSRect(x: 16, y: 16, width: 268, height: 150.75)), + try require(sameFrame(bounded, NSRect(x: 16, y: 233.25, width: 268, height: 150.75)), "target width shrinks to usable bounds without distorting ordinary aspect") } @@ -171,9 +171,9 @@ struct CompactWindowChecks { let visible = (window.screen ?? screen).visibleFrame try require(close(window.frame.maxX, visible.maxX - 16) - && close(window.frame.minY, visible.minY + 16) + && close(window.frame.maxY, visible.maxY - 16) && close(window.frame.width / window.frame.height, 16.0 / 9.0), - "actual compact window enters at inset bottom right with playback aspect") + "actual compact window enters at inset top right with playback aspect") let nearEdge = NSRect(x: visible.minX + 26, y: visible.minY + 27, width: window.frame.width, height: window.frame.height) window.setFrame(nearEdge, display: false) diff --git a/README.md b/README.md index 905f1b6..ae4bf11 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ The layout follows [IINA's video-first window](https://iina.io/img/screenshot.pn Opening video fits the window to its display aspect ratio, including non-square pixels, and constrains subsequent window resizing. Fullscreen retains the normal letterboxed renderer; audio-only playback and close release the aspect constraint. Extreme ratios that cannot fit both the screen and usable controls keep a freely resizable letterboxed window. -**Compact mode** uses the same window, Metal view, and playback session—not a second window or macOS system PiP. Use the PiP-shaped button in the normal playback controls, **⌃⌘P**, or **Playback Actions → Enter Compact Mode**. The compact-mode button replaces the fullscreen button; fullscreen remains available through its menu, keyboard shortcuts, and double-clicking normal-mode video. Compact mode starts at the current screen's bottom-right corner with a 16-point inset and a preferred width of 320 points, preserving display aspect where usable screen bounds and the 160 × 90-point minimum allow; extreme ratios retain letterboxing. Compact mode floats above ordinary windows, joins all Spaces, and is configured to join eligible other-app fullscreen spaces and Stage Manager sets. Small hover controls provide play/pause, restore, and Playback Actions; keyboard playback controls remain available. +**Compact mode** uses the same window, Metal view, and playback session—not a second window or macOS system PiP. Use the PiP-shaped button in the normal playback controls, **⌃⌘P**, or **Playback Actions → Enter Compact Mode**. The compact-mode button replaces the fullscreen button; fullscreen remains available through its menu, keyboard shortcuts, and double-clicking normal-mode video. Compact mode starts at the current screen's top-right corner with a 16-point inset and a preferred width of 320 points, preserving display aspect where usable screen bounds and the 160 × 90-point minimum allow; extreme ratios retain letterboxing. Compact mode floats above ordinary windows, joins all Spaces, and is configured to join eligible other-app fullscreen spaces and Stage Manager sets. Small hover controls provide play/pause, restore, and Playback Actions; keyboard playback controls remain available. Drag the compact video to move the window; on release, each axis snaps independently when within 24 points of an inset usable-screen edge. **Snap to Screen Edges** is enabled by default and remembers its toggle across launches. Disabling snapping permits free placement. Exiting compact mode restores the previous normal geometry and window behavior, including keep-on-top; entry from fullscreen waits for the native exit animation, and restoring that session returns to fullscreen. Double-clicking compact video also restores the previous mode. Video replacement updates the compact aspect; audio-only replacement, playback errors, and closing the file leave compact mode. Closing the window still quits the app. diff --git a/Sources/App/CompactWindow.swift b/Sources/App/CompactWindow.swift index 1e7d18c..a6aa7f5 100644 --- a/Sources/App/CompactWindow.swift +++ b/Sources/App/CompactWindow.swift @@ -333,7 +333,7 @@ final class CompactWindowController: NSObject { nonisolated static func initialFrame(aspect: Double, visibleFrame: NSRect) -> NSRect { let usable = usableFrame(visibleFrame) let size = compactSize(aspect: aspect, width: 320, available: usable.size) - return NSRect(x: usable.maxX - size.width, y: usable.minY, width: size.width, height: size.height) + return NSRect(x: usable.maxX - size.width, y: usable.maxY - size.height, width: size.width, height: size.height) } nonisolated static func snappedFrame(_ frame: NSRect, visibleFrame: NSRect) -> NSRect {