Lock macOS player window resize to video aspect ratio

This commit is contained in:
Arkadiusz Fal
2026-05-08 18:32:43 +02:00
parent b7b7c5ac62
commit 9b85ae2b13
2 changed files with 44 additions and 5 deletions

View File

@@ -953,6 +953,11 @@ private struct PlayerMacOSEventHandlersModifier: ViewModifier {
private func handleAspectRatioChange(oldValue: Double?, newValue: Double?) {
guard let newValue, newValue > 0 else { return }
// Always keep the manual-resize aspect lock in sync, regardless of the
// auto-resize setting the user always wants resize to be ratio-locked.
ExpandedPlayerWindowManager.shared.lockAspectRatio(newValue)
guard appEnvironment?.settingsManager.playerSheetAutoResize == true else { return }
let shouldAnimate = oldValue != nil
@@ -982,9 +987,12 @@ private struct PlayerMacOSEventHandlersModifier: ViewModifier {
}
private func handleMacOSAppear() {
if appEnvironment?.settingsManager.playerSheetAutoResize == true,
let aspectRatio = playerState?.videoAspectRatio,
aspectRatio > 0 {
guard let aspectRatio = playerState?.videoAspectRatio, aspectRatio > 0 else { return }
// Lock aspect ratio for manual resize unconditionally.
ExpandedPlayerWindowManager.shared.lockAspectRatio(aspectRatio)
if appEnvironment?.settingsManager.playerSheetAutoResize == true {
ExpandedPlayerWindowManager.shared.resizeToFitAspectRatio(aspectRatio, animated: false)
}
}

View File

@@ -21,9 +21,10 @@ final class ExpandedPlayerWindowManager: NSObject {
// Configuration
private let minWidth: CGFloat = 640
private let minHeight: CGFloat = 480
private let minHeight: CGFloat = 360
private let maxScreenRatio: CGFloat = 0.7
private let targetVideoHeight: CGFloat = 720
private let defaultAspectRatio: Double = 16.0 / 9.0
var isPresented: Bool {
playerWindow != nil
@@ -89,9 +90,12 @@ final class ExpandedPlayerWindowManager: NSObject {
window.titleVisibility = .hidden
window.isMovableByWindowBackground = true
window.backgroundColor = NSColor.windowBackgroundColor
window.minSize = NSSize(width: minWidth, height: minHeight)
window.contentViewController = hostingController
// Lock manual resize to the video aspect ratio. Seeded with 16:9 here;
// updated as soon as the real video aspect ratio is known.
applyAspectRatioConstraint(defaultAspectRatio, to: window)
// Set up window delegate for close handling
// Make ExpandedPlayerWindowManager itself the delegate to avoid lifecycle issues
window.delegate = self
@@ -256,6 +260,9 @@ final class ExpandedPlayerWindowManager: NSObject {
guard let window = playerWindow else { return }
guard aspectRatio > 0 else { return }
// Always update the aspect-ratio lock, even if we end up not resizing here.
applyAspectRatioConstraint(aspectRatio, to: window)
// Get screen bounds
guard let screen = window.screen ?? NSScreen.main else { return }
let screenFrame = screen.visibleFrame
@@ -278,8 +285,32 @@ final class ExpandedPlayerWindowManager: NSObject {
window.setFrame(adjustedFrame, display: true, animate: animated)
}
/// Locks the window's resize behavior to the given aspect ratio without
/// changing the current frame. Use this when the auto-resize setting is
/// disabled but we still want manual resizing to be ratio-locked.
func lockAspectRatio(_ aspectRatio: Double) {
guard let window = playerWindow else { return }
guard aspectRatio > 0 else { return }
applyAspectRatioConstraint(aspectRatio, to: window)
}
// MARK: - Private Helpers
/// Sets `contentAspectRatio` and a ratio-consistent `minSize` on the window
/// so that interactive resize couples width and height proportionally.
private func applyAspectRatioConstraint(_ aspectRatio: Double, to window: NSWindow) {
guard aspectRatio > 0 else { return }
// contentAspectRatio is expressed as a ratio; using (aspectRatio, 1) keeps it exact.
window.contentAspectRatio = NSSize(width: aspectRatio, height: 1)
// Derive a minimum size that lies on the same ratio so the lower bound
// doesn't force the window off-ratio (which would re-introduce bars).
// Anchor on minHeight and scale width by aspect.
let derivedMinWidth = max(minHeight * CGFloat(aspectRatio), 320)
window.minSize = NSSize(width: derivedMinWidth, height: minHeight)
}
private func configureWindowLevel(_ window: NSWindow, floating: Bool) {
if floating {
window.level = .floating