fix: use a nonactivating compact panel across fullscreen spaces
This commit is contained in:
@@ -241,7 +241,9 @@ final class PlayerModel: ObservableObject {
|
||||
let videoView: NSView?
|
||||
private var handle: OpaquePointer?
|
||||
private var timer: Timer?
|
||||
private weak var window: NSWindow?
|
||||
private weak var attachedWindow: NSWindow?
|
||||
weak var hostedPlayerView: NSView?
|
||||
var window: NSWindow? { compactWindow.presentationWindow ?? attachedWindow }
|
||||
private var hideDeadline: ContinuousClock.Instant?
|
||||
private var pointerInsideWindow = true
|
||||
private var keyboardNavigation = false
|
||||
@@ -329,6 +331,7 @@ final class PlayerModel: ObservableObject {
|
||||
self.compactIntent = nil
|
||||
self.fullScreenIntent = nil
|
||||
}
|
||||
self.updateWindowState()
|
||||
self.objectWillChange.send()
|
||||
}
|
||||
timer = Timer.scheduledTimer(withTimeInterval: 0.05, repeats: true) { [weak self] _ in
|
||||
@@ -1423,17 +1426,17 @@ final class PlayerModel: ObservableObject {
|
||||
}
|
||||
|
||||
func attachWindow(_ window: NSWindow) {
|
||||
guard self.window !== window else { return }
|
||||
guard !window.styleMask.contains(.nonactivatingPanel), attachedWindow !== window else { return }
|
||||
startContentHeight = 400 - (window.frame.height - window.contentLayoutRect.height)
|
||||
finishQueueTransition()
|
||||
self.window = window
|
||||
attachedWindow = window
|
||||
window.title = fileName ?? "Terebi"
|
||||
window.contentMinSize = NSSize(width: 400, height: 240)
|
||||
compactWindow.normalVideoLayout = { [weak self] in
|
||||
guard let self, self.showsQueue, self.state.has_video else { return nil }
|
||||
return (self.state.video_aspect_ratio, 260)
|
||||
}
|
||||
compactWindow.attach(window)
|
||||
compactWindow.attach(window, contentView: hostedPlayerView)
|
||||
updateWindowState()
|
||||
if fileName == nil { configureStartWindow(showing: true) }
|
||||
fitVideoWindow()
|
||||
@@ -4233,7 +4236,9 @@ private struct WindowChrome: NSViewRepresentable {
|
||||
window.isOpaque = false
|
||||
window.backgroundColor = .clear
|
||||
window.acceptsMouseMovedEvents = true
|
||||
window.collectionBehavior.insert(.fullScreenPrimary)
|
||||
if !window.styleMask.contains(.nonactivatingPanel) {
|
||||
window.collectionBehavior.insert(.fullScreenPrimary)
|
||||
}
|
||||
window.appearance = NSAppearance(named: .darkAqua)
|
||||
for name in [NSWindow.willEnterFullScreenNotification, NSWindow.willStartLiveResizeNotification,
|
||||
NSWindow.willCloseNotification] {
|
||||
@@ -4280,8 +4285,9 @@ private struct WindowChrome: NSViewRepresentable {
|
||||
return consumed ? nil : event
|
||||
}
|
||||
Task { @MainActor [weak self, weak window] in
|
||||
guard let self, let window else { return }
|
||||
guard let self, let window, self.configuredWindow === window, self.window === window else { return }
|
||||
self.model.attachWindow(window)
|
||||
self.model.updateWindowState()
|
||||
self.updateButtons()
|
||||
window.makeFirstResponder(nil)
|
||||
}
|
||||
@@ -4359,6 +4365,14 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
|
||||
let model = PlayerModel.shared
|
||||
guard model.isCompact || model.compactWindow.isTransitioning,
|
||||
let window = model.window else { return true }
|
||||
window.makeKeyAndOrderFront(nil)
|
||||
return false
|
||||
}
|
||||
|
||||
// Let SwiftUI create the initial window for a Finder/document launch.
|
||||
func application(_ application: NSApplication, open urls: [URL]) {
|
||||
if urls.contains(where: { $0.scheme == "terebi-enqueue" }) {
|
||||
@@ -4368,13 +4382,36 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
else if let url = urls.first { PlayerModel.shared.open(url) }
|
||||
}
|
||||
|
||||
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { true }
|
||||
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||
// AppKit doesn't count the compact panel as a normal application window.
|
||||
let compact = PlayerModel.shared.compactWindow
|
||||
return !compact.isCompact && !compact.isTransitioning
|
||||
}
|
||||
|
||||
func applicationWillTerminate(_ notification: Notification) {
|
||||
PlayerModel.shared.shutdown()
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private struct PlayerSceneContent: NSViewRepresentable {
|
||||
let model: PlayerModel
|
||||
|
||||
func makeNSView(context: Context) -> NSView {
|
||||
// Keep SwiftUI's scene host in place; only this app-owned player host moves.
|
||||
let container = NSView()
|
||||
let player = NSHostingView(rootView: PlayerContent(model: model))
|
||||
player.sizingOptions = []
|
||||
player.frame = container.bounds
|
||||
player.autoresizingMask = [.width, .height]
|
||||
container.addSubview(player)
|
||||
model.hostedPlayerView = player
|
||||
return container
|
||||
}
|
||||
|
||||
func updateNSView(_ view: NSView, context: Context) {}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
struct TerebiApp: App {
|
||||
@NSApplicationDelegateAdaptor(AppDelegate.self) private var delegate
|
||||
@@ -4382,13 +4419,14 @@ struct TerebiApp: App {
|
||||
|
||||
var body: some Scene {
|
||||
Window("\(model.fileName ?? "Terebi")", id: "terebi") {
|
||||
PlayerContent(model: model)
|
||||
PlayerSceneContent(model: model)
|
||||
.ignoresSafeArea()
|
||||
.focusedSceneObject(model)
|
||||
}
|
||||
.defaultSize(width: 640, height: 400)
|
||||
.windowResizability(.contentSize)
|
||||
.windowStyle(.hiddenTitleBar)
|
||||
.commands { PlayerCommands() }
|
||||
.commands { PlayerCommands(model: model) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user