feat: add expandable compact queue and sidebar shortcut
This commit is contained in:
@@ -7,6 +7,7 @@ final class CompactWindowController: NSObject {
|
||||
var normalVideoLayout: (() -> (aspect: Double, sidebarWidth: CGFloat)?)?
|
||||
private(set) var isCompact = false
|
||||
private(set) var isTransitioning = false
|
||||
private(set) var queueHeight: CGFloat = 0
|
||||
static let transitionDuration: TimeInterval = 0.25
|
||||
|
||||
var snappingEnabled: Bool {
|
||||
@@ -59,6 +60,7 @@ final class CompactWindowController: NSObject {
|
||||
private var originatedFullScreen = false
|
||||
private var videoAspect = 16.0 / 9.0
|
||||
private var fittedAspect: Double?
|
||||
private var fittedVideoAspect = 16.0 / 9.0
|
||||
private let reduceMotion: () -> Bool
|
||||
private let defaults: UserDefaults
|
||||
private static let widthKey = "compactWindowWidth"
|
||||
@@ -135,6 +137,34 @@ final class CompactWindowController: NSObject {
|
||||
fitCompactWindow(window, initial: false)
|
||||
}
|
||||
|
||||
// The reveal and native frame share the model's animation clock.
|
||||
func queueLayout(visible: Bool) -> (height: CGFloat, frame: NSRect)? {
|
||||
guard isCompact, !isTransitioning, let window else { return nil }
|
||||
let usable = Self.usableFrame(visibleFrame(for: window))
|
||||
let height = visible ? min(240, max(0, usable.height - 90)) : 0
|
||||
let video = Self.compactSize(aspect: videoAspect, width: window.frame.width,
|
||||
available: NSSize(width: usable.width, height: usable.height - height))
|
||||
let size = NSSize(width: video.width, height: video.height + height)
|
||||
let frame = NSRect(x: min(max(window.frame.minX, usable.minX), usable.maxX - size.width),
|
||||
y: max(usable.minY, min(window.frame.maxY, usable.maxY) - size.height),
|
||||
width: size.width, height: size.height)
|
||||
return (height, frame)
|
||||
}
|
||||
|
||||
func updateQueue(height: CGFloat, frame: NSRect) {
|
||||
guard isCompact, let window else { return }
|
||||
window.resizeIncrements = NSSize(width: 1, height: 1)
|
||||
queueHeight = height
|
||||
fittedVideoAspect = frame.width / (frame.height - height)
|
||||
window.minSize = NSSize(width: 160, height: 90 + height)
|
||||
window.contentMinSize = window.minSize
|
||||
window.setFrame(frame, display: true)
|
||||
if height == 0, Self.canFitAspect(videoAspect, available: Self.usableFrame(visibleFrame(for: window)).size) {
|
||||
window.aspectRatio = NSSize(width: videoAspect, height: 1)
|
||||
}
|
||||
onChange?()
|
||||
}
|
||||
|
||||
func snapAfterDrag() {
|
||||
guard isCompact, !isTransitioning, snappingEnabled, let window else { return }
|
||||
let frame = Self.snappedFrame(window.frame, visibleFrame: visibleFrame(for: window))
|
||||
@@ -229,12 +259,23 @@ final class CompactWindowController: NSObject {
|
||||
|
||||
func windowWillResize(_ sender: NSWindow, to proposedSize: NSSize) -> NSSize {
|
||||
let proposed = original?.windowWillResize?(sender, to: proposedSize) ?? proposedSize
|
||||
guard let owner, !owner.isCompact, !owner.isTransitioning,
|
||||
!sender.styleMask.contains(.fullScreen),
|
||||
let layout = owner.normalVideoLayout?(),
|
||||
layout.aspect.isFinite, layout.aspect > 0 else { return proposed }
|
||||
guard let owner, !owner.isTransitioning,
|
||||
!sender.styleMask.contains(.fullScreen) else { return proposed }
|
||||
let layout: (aspect: Double, sidebarWidth: CGFloat)
|
||||
let extraHeight: CGFloat
|
||||
if owner.isCompact {
|
||||
guard owner.queueHeight > 0 else { return proposed }
|
||||
layout = (owner.fittedVideoAspect, 0)
|
||||
extraHeight = owner.queueHeight
|
||||
} else {
|
||||
guard let normal = owner.normalVideoLayout?(),
|
||||
normal.aspect.isFinite, normal.aspect > 0 else { return proposed }
|
||||
layout = normal
|
||||
extraHeight = 0
|
||||
}
|
||||
// The sidebar stays fixed-width; only the video area is aspect-locked.
|
||||
let chrome = sender.frame.height - sender.contentRect(forFrameRect: sender.frame).height
|
||||
let chrome = owner.isCompact ? extraHeight
|
||||
: sender.frame.height - sender.contentRect(forFrameRect: sender.frame).height
|
||||
let start = resizeStartSize ?? sender.frame.size
|
||||
let widthChange = abs(proposed.width - start.width)
|
||||
let heightChange = abs(proposed.height - start.height) * layout.aspect
|
||||
@@ -291,6 +332,7 @@ final class CompactWindowController: NSObject {
|
||||
return
|
||||
}
|
||||
if let snapshot {
|
||||
queueHeight = 0
|
||||
setState(compact: false, transitioning: animated)
|
||||
window.aspectRatio = .zero
|
||||
window.minSize = .zero
|
||||
@@ -318,6 +360,7 @@ final class CompactWindowController: NSObject {
|
||||
private func enterCompact(_ window: NSWindow, animated: Bool) {
|
||||
// After didExitFullScreen, this is the normal frame, not the fullscreen frame.
|
||||
snapshot = Snapshot(window)
|
||||
queueHeight = 0
|
||||
setState(compact: true, transitioning: animated)
|
||||
window.aspectRatio = .zero
|
||||
window.minSize = .zero
|
||||
@@ -422,18 +465,22 @@ final class CompactWindowController: NSObject {
|
||||
let aspect = videoAspect
|
||||
let savedWidth = initial ? defaults.double(forKey: Self.widthKey) : Double(window.frame.width)
|
||||
let width = savedWidth.isFinite && savedWidth > 0 ? CGFloat(savedWidth) : 320
|
||||
var frame = Self.initialFrame(aspect: videoAspect, visibleFrame: visibleFrame, width: width)
|
||||
let video = Self.compactSize(aspect: videoAspect, width: width,
|
||||
available: NSSize(width: usable.width, height: max(0, usable.height - queueHeight)))
|
||||
var frame = NSRect(x: usable.maxX - video.width, y: usable.maxY - video.height - queueHeight,
|
||||
width: video.width, height: video.height + queueHeight)
|
||||
if !initial {
|
||||
frame.origin.x = min(max(window.frame.minX, usable.minX), usable.maxX - frame.width)
|
||||
frame.origin.y = min(max(window.frame.minY, usable.minY), usable.maxY - frame.height)
|
||||
frame.origin.y = max(usable.minY, min(window.frame.maxY, usable.maxY) - frame.height)
|
||||
}
|
||||
window.resizeIncrements = NSSize(width: 1, height: 1)
|
||||
animateFrame(frame, of: window, animated: animated) { [weak self, weak window] in
|
||||
guard let self, let window else { return }
|
||||
if Self.canFitAspect(aspect, available: usable.size) {
|
||||
if self.queueHeight == 0, Self.canFitAspect(aspect, available: usable.size) {
|
||||
window.aspectRatio = NSSize(width: aspect, height: 1)
|
||||
}
|
||||
self.fittedAspect = aspect
|
||||
self.fittedVideoAspect = video.width / video.height
|
||||
if initial { self.reconcile() }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user