macos: remove split zoom accessory when tabs appear

pull/7588/head
Mitchell Hashimoto 2025-06-13 13:36:03 -07:00
parent f7f0514b9f
commit a7df90ee55
No known key found for this signature in database
GPG Key ID: 523D5DC389D273BC
1 changed files with 45 additions and 5 deletions

View File

@ -12,6 +12,9 @@ class TerminalWindow: NSWindow {
/// The view model for SwiftUI views /// The view model for SwiftUI views
private var viewModel = ViewModel() private var viewModel = ViewModel()
/// Reset split zoom button in titlebar
private let resetZoomAccessory = NSTitlebarAccessoryViewController()
/// The configuration derived from the Ghostty config so we don't need to rely on references. /// The configuration derived from the Ghostty config so we don't need to rely on references.
private(set) var derivedConfig: DerivedConfig = .init() private(set) var derivedConfig: DerivedConfig = .init()
@ -56,7 +59,6 @@ class TerminalWindow: NSWindow {
} }
// Create our reset zoom titlebar accessory. // Create our reset zoom titlebar accessory.
let resetZoomAccessory = NSTitlebarAccessoryViewController()
resetZoomAccessory.layoutAttribute = .right resetZoomAccessory.layoutAttribute = .right
resetZoomAccessory.view = NSHostingView(rootView: ResetZoomAccessoryView( resetZoomAccessory.view = NSHostingView(rootView: ResetZoomAccessoryView(
viewModel: viewModel, viewModel: viewModel,
@ -94,6 +96,18 @@ class TerminalWindow: NSWindow {
resetZoomTabButton.contentTintColor = .secondaryLabelColor resetZoomTabButton.contentTintColor = .secondaryLabelColor
} }
override func becomeMain() {
super.becomeMain()
// Its possible we miss the accessory titlebar call so we check again
// whenever the window becomes main. Both of these are idempotent.
if hasTabBar {
tabBarDidAppear()
} else {
tabBarDidDisappear()
}
}
override func mergeAllWindows(_ sender: Any?) { override func mergeAllWindows(_ sender: Any?) {
super.mergeAllWindows(sender) super.mergeAllWindows(sender)
@ -112,13 +126,13 @@ class TerminalWindow: NSWindow {
// it. This has been verified to work on macOS 12 to 26 // it. This has been verified to work on macOS 12 to 26
if isTabBar(childViewController) { if isTabBar(childViewController) {
childViewController.identifier = Self.tabBarIdentifier childViewController.identifier = Self.tabBarIdentifier
viewModel.hasTabBar = true tabBarDidAppear()
} }
} }
override func removeTitlebarAccessoryViewController(at index: Int) { override func removeTitlebarAccessoryViewController(at index: Int) {
if let childViewController = titlebarAccessoryViewControllers[safe: index], isTabBar(childViewController) { if let childViewController = titlebarAccessoryViewControllers[safe: index], isTabBar(childViewController) {
viewModel.hasTabBar = false tabBarDidDisappear()
} }
super.removeTitlebarAccessoryViewController(at: index) super.removeTitlebarAccessoryViewController(at: index)
@ -130,6 +144,11 @@ class TerminalWindow: NSWindow {
/// added. /// added.
private static let tabBarIdentifier: NSUserInterfaceItemIdentifier = .init("_ghosttyTabBar") private static let tabBarIdentifier: NSUserInterfaceItemIdentifier = .init("_ghosttyTabBar")
/// Returns true if there is a tab bar visible on this window.
var hasTabBar: Bool {
contentView?.firstViewFromRoot(withClassName: "NSTabBar") != nil
}
func isTabBar(_ childViewController: NSTitlebarAccessoryViewController) -> Bool { func isTabBar(_ childViewController: NSTitlebarAccessoryViewController) -> Bool {
if childViewController.identifier == nil { if childViewController.identifier == nil {
// The good case // The good case
@ -154,6 +173,28 @@ class TerminalWindow: NSWindow {
return childViewController.identifier == Self.tabBarIdentifier return childViewController.identifier == Self.tabBarIdentifier
} }
/// Ensures we only run didAppear/didDisappear once per state.
private var tabBarDidAppearRan = false
private func tabBarDidAppear() {
guard !tabBarDidAppearRan else { return }
tabBarDidAppearRan = true
// Remove our reset zoom accessory. For some reason having a SwiftUI
// titlebar accessory causes our content view scaling to be wrong.
// Removing it fixes it, we just need to remember to add it again later.
if let idx = titlebarAccessoryViewControllers.firstIndex(of: resetZoomAccessory) {
removeTitlebarAccessoryViewController(at: idx)
}
}
private func tabBarDidDisappear() {
guard tabBarDidAppearRan else { return }
tabBarDidAppearRan = false
addTitlebarAccessoryViewController(resetZoomAccessory)
}
// MARK: Tab Key Equivalents // MARK: Tab Key Equivalents
var keyEquivalent: String? = nil { var keyEquivalent: String? = nil {
@ -402,7 +443,6 @@ extension TerminalWindow {
class ViewModel: ObservableObject { class ViewModel: ObservableObject {
@Published var isSurfaceZoomed: Bool = false @Published var isSurfaceZoomed: Bool = false
@Published var hasToolbar: Bool = false @Published var hasToolbar: Bool = false
@Published var hasTabBar: Bool = false
} }
struct ResetZoomAccessoryView: View { struct ResetZoomAccessoryView: View {
@ -410,7 +450,7 @@ extension TerminalWindow {
let action: () -> Void let action: () -> Void
var body: some View { var body: some View {
if viewModel.isSurfaceZoomed && !viewModel.hasTabBar { if viewModel.isSurfaceZoomed {
VStack { VStack {
Button(action: action) { Button(action: action) {
Image("ResetZoom") Image("ResetZoom")