mirror of
https://github.com/yattee/yattee.git
synced 2026-07-20 14:22:02 +00:00
Lock macOS inline player sheet resize to the video aspect ratio
In floating/window mode, interactive resize is aspect-locked via the window's contentAspectRatio, so no black bars appear. The inline sheet had no such lock, allowing free resize with pan bars on the sides. Make applyAspectRatioConstraint static so the sheet path can reuse the exact same lock the standalone window uses, and apply it to the sheet's backing NSWindow from SheetWindowResizer whenever the video ratio is known. Like the standalone window, the resize lock always follows the real video ratio (independent of the playerSheetAutoResize size setting).
This commit is contained in:
@@ -94,6 +94,7 @@ struct ContentView: View {
|
||||
set: { appEnvironment.navigationCoordinator.isPlayerExpanded = $0 }
|
||||
)) {
|
||||
let size = expandedSheetSize(appEnvironment: appEnvironment)
|
||||
let lockAspect = sheetLockAspectRatio(appEnvironment: appEnvironment)
|
||||
ExpandedPlayerSheet()
|
||||
// Floor keeps the content flexible so it tracks the window's
|
||||
// animated resize with no gap — do NOT pin an exact (max) size:
|
||||
@@ -109,8 +110,9 @@ struct ContentView: View {
|
||||
.presentationSizing(.fitted)
|
||||
// `.presentationSizing(.fitted)` only fits the sheet once, at
|
||||
// presentation. Resize the backing window directly when the
|
||||
// aspect-ratio-derived size changes so the sheet tracks the video.
|
||||
.sheetWindowSize(size)
|
||||
// aspect-ratio-derived size changes so the sheet tracks the video,
|
||||
// and lock interactive resize to the video ratio (no black bars).
|
||||
.sheetWindowSize(size, aspectRatio: lockAspect)
|
||||
}
|
||||
#elseif os(tvOS)
|
||||
.fullScreenCover(isPresented: Binding(
|
||||
@@ -142,6 +144,16 @@ struct ContentView: View {
|
||||
}
|
||||
return ExpandedPlayerWindowManager.fittedSheetSize(for: aspect)
|
||||
}
|
||||
|
||||
/// Real video aspect ratio to lock the sheet's interactive resize to, or `0`
|
||||
/// when unknown. Unlike the sheet *size* (which honors `playerSheetAutoResize`),
|
||||
/// the resize lock always follows the actual video ratio — mirroring the
|
||||
/// standalone window, which locks unconditionally. Reading `videoAspectRatio`
|
||||
/// here registers the @Observable dependency that keeps the lock in sync.
|
||||
private func sheetLockAspectRatio(appEnvironment: AppEnvironment) -> Double {
|
||||
let ratio = appEnvironment.playerService.state.videoAspectRatio ?? 0
|
||||
return ratio > 0 ? ratio : 0
|
||||
}
|
||||
#endif
|
||||
|
||||
#if os(macOS)
|
||||
|
||||
@@ -106,7 +106,7 @@ final class ExpandedPlayerWindowManager: NSObject {
|
||||
|
||||
// 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(Self.defaultAspectRatio, to: window)
|
||||
Self.applyAspectRatioConstraint(Self.defaultAspectRatio, to: window)
|
||||
|
||||
// Force the intended size. Assigning `contentViewController` above resizes
|
||||
// the window to the hosting controller's fitting size — and the lightweight
|
||||
@@ -281,7 +281,7 @@ final class ExpandedPlayerWindowManager: NSObject {
|
||||
guard aspectRatio > 0 else { return }
|
||||
|
||||
// Always update the aspect-ratio lock, even if we end up not resizing here.
|
||||
applyAspectRatioConstraint(aspectRatio, to: window)
|
||||
Self.applyAspectRatioConstraint(aspectRatio, to: window)
|
||||
|
||||
// Get screen bounds
|
||||
guard let screen = window.screen ?? NSScreen.main else { return }
|
||||
@@ -311,14 +311,16 @@ final class ExpandedPlayerWindowManager: NSObject {
|
||||
func lockAspectRatio(_ aspectRatio: Double) {
|
||||
guard let window = playerWindow else { return }
|
||||
guard aspectRatio > 0 else { return }
|
||||
applyAspectRatioConstraint(aspectRatio, to: window)
|
||||
Self.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) {
|
||||
/// Static so the inline-sheet path (`SheetWindowResizer`) can lock the sheet's
|
||||
/// backing window to the same ratio the standalone window uses.
|
||||
static func applyAspectRatioConstraint(_ aspectRatio: Double, to window: NSWindow) {
|
||||
guard aspectRatio > 0 else { return }
|
||||
|
||||
// contentAspectRatio is expressed as a ratio; using (aspectRatio, 1) keeps it exact.
|
||||
@@ -517,6 +519,10 @@ private struct ExpandedPlayerWindowRoot: View {
|
||||
/// attached sheet naturally sits.
|
||||
struct SheetWindowResizer: NSViewRepresentable {
|
||||
let targetSize: CGSize
|
||||
/// Real video aspect ratio (width / height) to lock interactive resize to, or
|
||||
/// `0` when unknown. Mirrors the standalone window's `contentAspectRatio` lock
|
||||
/// so dragging the sheet edge keeps the video's ratio instead of adding bars.
|
||||
let aspectRatio: Double
|
||||
|
||||
func makeCoordinator() -> Coordinator { Coordinator() }
|
||||
|
||||
@@ -530,6 +536,7 @@ struct SheetWindowResizer: NSViewRepresentable {
|
||||
|
||||
func updateNSView(_ nsView: NSView, context: Context) {
|
||||
let target = targetSize
|
||||
let aspect = aspectRatio
|
||||
let coordinator = context.coordinator
|
||||
// The window isn't attached during the same runloop tick as the SwiftUI
|
||||
// update, so defer the resize until the view is in a window.
|
||||
@@ -545,6 +552,13 @@ struct SheetWindowResizer: NSViewRepresentable {
|
||||
window.isOpaque = true
|
||||
}
|
||||
|
||||
// Lock interactive resize to the video ratio so dragging the sheet's
|
||||
// edge keeps the aspect (no black bars), matching the standalone
|
||||
// window. Applied every pass so it tracks aspect-ratio changes.
|
||||
if aspect > 0 {
|
||||
ExpandedPlayerWindowManager.applyAspectRatioConstraint(aspect, to: window)
|
||||
}
|
||||
|
||||
let current = window.frame
|
||||
guard abs(current.width - target.width) > 1
|
||||
|| abs(current.height - target.height) > 1 else { return }
|
||||
@@ -567,9 +581,11 @@ struct SheetWindowResizer: NSViewRepresentable {
|
||||
}
|
||||
|
||||
extension View {
|
||||
/// Resizes the hosting sheet window to `size` when it changes (macOS sheets).
|
||||
func sheetWindowSize(_ size: CGSize) -> some View {
|
||||
background(SheetWindowResizer(targetSize: size))
|
||||
/// Resizes the hosting sheet window to `size` when it changes and locks its
|
||||
/// interactive resize to `aspectRatio` (macOS sheets). Pass `aspectRatio == 0`
|
||||
/// to leave resize unconstrained.
|
||||
func sheetWindowSize(_ size: CGSize, aspectRatio: Double) -> some View {
|
||||
background(SheetWindowResizer(targetSize: size, aspectRatio: aspectRatio))
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user