Refactor and Rename to CommandNotch
This commit is contained in:
87
Downterm/CommandNotch/Managers/PopoutWindowController.swift
Normal file
87
Downterm/CommandNotch/Managers/PopoutWindowController.swift
Normal file
@@ -0,0 +1,87 @@
|
||||
import AppKit
|
||||
import SwiftUI
|
||||
import Combine
|
||||
|
||||
/// Manages standalone pop-out terminal windows for detached tabs.
|
||||
/// Each detached tab gets its own resizable window with the terminal view.
|
||||
@MainActor
|
||||
class PopoutWindowController: NSObject, NSWindowDelegate {
|
||||
|
||||
static let shared = PopoutWindowController()
|
||||
|
||||
/// Tracks open pop-out windows so they aren't released prematurely.
|
||||
private var windows: [UUID: NSWindow] = [:]
|
||||
private var sessions: [UUID: TerminalSession] = [:]
|
||||
private var titleObservers: [UUID: AnyCancellable] = [:]
|
||||
|
||||
private override init() {
|
||||
super.init()
|
||||
}
|
||||
|
||||
/// Creates a new standalone window for the given terminal session.
|
||||
func popout(session: TerminalSession) {
|
||||
let windowID = session.id
|
||||
|
||||
if let existingWindow = windows[windowID] {
|
||||
existingWindow.makeKeyAndOrderFront(nil)
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
return
|
||||
}
|
||||
|
||||
let win = NSWindow(
|
||||
contentRect: NSRect(x: 0, y: 0, width: 720, height: 480),
|
||||
styleMask: [.titled, .closable, .resizable, .miniaturizable],
|
||||
backing: .buffered,
|
||||
defer: false
|
||||
)
|
||||
win.title = session.title
|
||||
win.appearance = NSAppearance(named: .darkAqua)
|
||||
win.backgroundColor = .black
|
||||
win.delegate = self
|
||||
win.isReleasedWhenClosed = false
|
||||
|
||||
let hostingView = NSHostingView(
|
||||
rootView: SwiftTermView(session: session)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.background(Color.black)
|
||||
.preferredColorScheme(.dark)
|
||||
)
|
||||
hostingView.frame = NSRect(origin: .zero, size: win.contentRect(forFrameRect: win.frame).size)
|
||||
win.contentView = hostingView
|
||||
|
||||
win.center()
|
||||
win.makeKeyAndOrderFront(nil)
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
|
||||
windows[windowID] = win
|
||||
sessions[windowID] = session
|
||||
|
||||
// Update window title when the terminal title changes
|
||||
titleObservers[windowID] = session.$title
|
||||
.receive(on: RunLoop.main)
|
||||
.sink { [weak win] title in win?.title = title }
|
||||
}
|
||||
|
||||
// MARK: - NSWindowDelegate
|
||||
|
||||
func windowDidBecomeKey(_ notification: Notification) {
|
||||
guard let window = notification.object as? NSWindow,
|
||||
let entry = windows.first(where: { $0.value === window }),
|
||||
let terminalView = sessions[entry.key]?.terminalView,
|
||||
terminalView.window === window else { return }
|
||||
|
||||
window.makeFirstResponder(terminalView)
|
||||
}
|
||||
|
||||
func windowWillClose(_ notification: Notification) {
|
||||
guard let closingWindow = notification.object as? NSWindow else { return }
|
||||
|
||||
// Find which session this window belongs to and clean up
|
||||
if let entry = windows.first(where: { $0.value === closingWindow }) {
|
||||
sessions[entry.key]?.terminate()
|
||||
sessions.removeValue(forKey: entry.key)
|
||||
windows.removeValue(forKey: entry.key)
|
||||
titleObservers.removeValue(forKey: entry.key)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user