summaryrefslogtreecommitdiff
path: root/macos/Sources/Features/Terminal/Window Styles/TerminalWindow.swift
diff options
context:
space:
mode:
Diffstat (limited to 'macos/Sources/Features/Terminal/Window Styles/TerminalWindow.swift')
-rw-r--r--macos/Sources/Features/Terminal/Window Styles/TerminalWindow.swift77
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)
+ }
+ }
+
}