Modify terminal tab bar location in titlebar
The user is asking about changing TitlebarTabsTahoeTerminalWindow.swift to make the tab bar a top accessory view instead of a bottom one, which would move the tabs into the titlebar.
Let me first read the file to see how it's currently implemented, then I can explain what changes would be needed.
Looking at the code, changing it to use a top accessory view instead of the current approach wouldn't work well. The current implementation doesn't use layoutAttribute = .bottom - it uses .right (line 59) and then manually repositions the tab bar into the toolbar using constraints.
To move tabs into the titlebar above the toolbar, you'd want:
- Change
layoutAttribute = .rightto.top(line 59) - Remove the constraint-based repositioning logic (lines 123-153)
- Adjust
fullSizeContentView = trueif not already set - Handle the window button overlap differently
However, this approach has tradeoffs:
- Top accessory: Tabs sit above the titlebar (macOS default), creating a separate visual layer
- Current approach: Tabs are embedded within the titlebar itself, creating a unified appearance like Safari
The current implementation gives you the integrated titlebar tabs look. A top accessory would feel more like a traditional tabbed window. Which style are you aiming for?
The user wants to try changing the tab bar to use a top accessory view instead of the current bottom/right approach. Let me make the changes they mentioned.
Looking at the code:
- Line 59: Change
layoutAttribute = .rightto.top - Remove or simplify the constraint-based repositioning logic in setupTabBar()
- Potentially need to adjust other settings
Let me think about what needs to change:
- Line 59:
.right->.top - The setupTabBar() function does a lot of constraint manipulation to reposition the tab bar into the toolbar. If we're using a top accessory, we probably don't need most of this.
- The tabBarObserver and frame change notification might still be needed or might not
- The removeTabBar() function should still work
Let me create a modified version that uses a top accessory approach.
@@ -55,9 +55,9 @@
// Some setup needs to happen BEFORE it is added, such as layout. If
// we don't do this before the call below, we'll trigger an AppKit
// assertion.
childViewController.layoutAttribute = .right
childViewController.layoutAttribute = .top
super.addTitlebarAccessoryViewController(childViewController)
// Setup the tab bar to go into the titlebar.
@@ -117,65 +117,8 @@
// View model updates must happen on their own ticks.
DispatchQueue.main.async {
self.viewModel.hasTabBar = true
}
// Find our clip view
guard let clipView = tabBar.firstSuperview(withClassName: "NSTitlebarAccessoryClipView") else { return }
guard let accessoryView = clipView.subviews[safe: 0] else { return }
guard let titlebarView = clipView.firstSuperview(withClassName: "NSTitlebarView") else { return }
guard let toolbarView = titlebarView.firstDescendant(withClassName: "NSToolbarView") else { return }
// The container is the view that we'll constrain our tab bar within.
let container = toolbarView
// The padding for the tab bar. If we're showing window buttons then
// we need to offset the window buttons.
let leftPadding: CGFloat = switch(self.derivedConfig.macosWindowButtons) {
case .hidden: 0
case .visible: 70
}
// Constrain the accessory clip view (the parent of the accessory view
// usually that clips the children) to the container view.
clipView.translatesAutoresizingMaskIntoConstraints = false
accessoryView.translatesAutoresizingMaskIntoConstraints = false
// Setup all our constraints
NSLayoutConstraint.activate([
clipView.leftAnchor.constraint(equalTo: container.leftAnchor, constant: leftPadding),
clipView.rightAnchor.constraint(equalTo: container.rightAnchor),
clipView.topAnchor.constraint(equalTo: container.topAnchor, constant: 2),
clipView.heightAnchor.constraint(equalTo: container.heightAnchor),
accessoryView.leftAnchor.constraint(equalTo: clipView.leftAnchor),
accessoryView.rightAnchor.constraint(equalTo: clipView.rightAnchor),
accessoryView.topAnchor.constraint(equalTo: clipView.topAnchor),
accessoryView.heightAnchor.constraint(equalTo: clipView.heightAnchor),
])
clipView.needsLayout = true
accessoryView.needsLayout = true
// Setup an observer for the NSTabBar frame. When system appearance changes or
// other events occur, the tab bar can resize and clear our constraints. When this
// happens, we need to remove our custom constraints and re-apply them once the
// tab bar has proper dimensions again to avoid constraint conflicts.
tabBar.postsFrameChangedNotifications = true
tabBarObserver = NotificationCenter.default.addObserver(
forName: NSView.frameDidChangeNotification,
object: tabBar,
queue: .main
) { [weak self] _ in
guard let self else { return }
// Remove the observer so we can call setup again.
self.tabBarObserver = nil
// Wait a tick to let the new tab bars appear and then set them up.
DispatchQueue.main.async {
self.setupTabBar()
}
}
}
func removeTabBar() {
// View model needs to be updated on another tick because it
Done! Changed to use .top layout and removed the constraint manipulation that was embedding tabs into the toolbar. The tabs will now appear as a standard top accessory view above the titlebar.
Thread
Reference Thread
T-2b5aecbe-7a48-4a4e-b5ad-c355b751042b