diff options
Diffstat (limited to 'macos/Sources/Features/Terminal/Window Styles/TerminalWindow.swift')
| -rw-r--r-- | macos/Sources/Features/Terminal/Window Styles/TerminalWindow.swift | 77 |
1 files changed, 64 insertions, 13 deletions
diff --git a/macos/Sources/Features/Terminal/Window Styles/TerminalWindow.swift b/macos/Sources/Features/Terminal/Window Styles/TerminalWindow.swift index 3ab6293dc..661c89121 100644 --- a/macos/Sources/Features/Terminal/Window Styles/TerminalWindow.swift +++ b/macos/Sources/Features/Terminal/Window Styles/TerminalWindow.swift @@ -5,6 +5,12 @@ import GhosttyKit /// The base class for all standalone, "normal" terminal windows. This sets the basic /// style and configuration of the window based on the app configuration. class TerminalWindow: NSWindow { + /// Posted when a terminal window awakes from nib. + static let terminalDidAwake = Notification.Name("TerminalWindowDidAwake") + + /// Posted when a terminal window will close + static let terminalWillCloseNotification = Notification.Name("TerminalWindowWillClose") + /// This is the key in UserDefaults to use for the default `level` value. This is /// used by the manual float on top menu item feature. static let defaultLevelKey: String = "TerminalDefaultLevel" @@ -14,15 +20,25 @@ class TerminalWindow: NSWindow { /// Reset split zoom button in titlebar private let resetZoomAccessory = NSTitlebarAccessoryViewController() + + /// Update notification UI in titlebar + private let updateAccessory = NSTitlebarAccessoryViewController() /// The configuration derived from the Ghostty config so we don't need to rely on references. private(set) var derivedConfig: DerivedConfig = .init() + + /// Whether this window supports the update accessory. If this is false, then views within this + /// window should determine how to show update notifications. + var supportsUpdateAccessory: Bool { + // Native window supports it. + true + } /// Gets the terminal controller from the window controller. var terminalController: TerminalController? { windowController as? TerminalController } - + // MARK: NSWindow Overrides override var toolbar: NSToolbar? { @@ -35,6 +51,9 @@ class TerminalWindow: NSWindow { } override func awakeFromNib() { + // Notify that this terminal window has loaded + NotificationCenter.default.post(name: Self.terminalDidAwake, object: self) + // This is required so that window restoration properly creates our tabs // again. I'm not sure why this is required. If you don't do this, then // tabs restore as separate windows. @@ -85,6 +104,17 @@ class TerminalWindow: NSWindow { })) addTitlebarAccessoryViewController(resetZoomAccessory) resetZoomAccessory.view.translatesAutoresizingMaskIntoConstraints = false + + // Create update notification accessory + if supportsUpdateAccessory { + updateAccessory.layoutAttribute = .right + updateAccessory.view = NSHostingView(rootView: UpdateAccessoryView( + viewModel: viewModel, + model: appDelegate.updateViewModel + )) + addTitlebarAccessoryViewController(updateAccessory) + updateAccessory.view.translatesAutoresizingMaskIntoConstraints = false + } } // Setup the accessory view for tabs that shows our keyboard shortcuts, @@ -103,6 +133,11 @@ class TerminalWindow: NSWindow { // still become key/main and receive events. override var canBecomeKey: Bool { return true } override var canBecomeMain: Bool { return true } + + override func close() { + NotificationCenter.default.post(name: Self.terminalWillCloseNotification, object: self) + super.close() + } override func becomeKey() { super.becomeKey() @@ -198,6 +233,9 @@ class TerminalWindow: NSWindow { if let idx = titlebarAccessoryViewControllers.firstIndex(of: resetZoomAccessory) { removeTitlebarAccessoryViewController(at: idx) } + + // We don't need to do this with the update accessory. I don't know why but + // everything works fine. } private func tabBarDidDisappear() { @@ -436,7 +474,7 @@ class TerminalWindow: NSWindow { standardWindowButton(.miniaturizeButton)?.isHidden = true standardWindowButton(.zoomButton)?.isHidden = true } - + // MARK: Config struct DerivedConfig { @@ -467,21 +505,20 @@ extension TerminalWindow { class ViewModel: ObservableObject { @Published var isSurfaceZoomed: Bool = false @Published var hasToolbar: Bool = false - } - - struct ResetZoomAccessoryView: View { - @ObservedObject var viewModel: ViewModel - let action: () -> Void - // The padding from the top that the view appears. This was all just manually - // measured based on the OS. - var topPadding: CGFloat { + /// Calculates the top padding based on toolbar visibility and macOS version + fileprivate var accessoryTopPadding: CGFloat { if #available(macOS 26.0, *) { - return viewModel.hasToolbar ? 10 : 5 + return hasToolbar ? 10 : 5 } else { - return viewModel.hasToolbar ? 9 : 4 + return hasToolbar ? 9 : 4 } } + } + + struct ResetZoomAccessoryView: View { + @ObservedObject var viewModel: ViewModel + let action: () -> Void var body: some View { if viewModel.isSurfaceZoomed { @@ -497,10 +534,24 @@ extension TerminalWindow { } // With a toolbar, the window title is taller, so we need more padding // to properly align. - .padding(.top, topPadding) + .padding(.top, viewModel.accessoryTopPadding) // We always need space at the end of the titlebar .padding(.trailing, 10) } } } + + /// A pill-shaped button that displays update status and provides access to update actions. + struct UpdateAccessoryView: View { + @ObservedObject var viewModel: ViewModel + @ObservedObject var model: UpdateViewModel + + var body: some View { + // We use the same top/trailing padding so that it hugs the same. + UpdatePill(model: model) + .padding(.top, viewModel.accessoryTopPadding) + .padding(.trailing, viewModel.accessoryTopPadding) + } + } + } |
