macos: titlebar tabs uses legacy window for now

pull/7588/head
Mitchell Hashimoto 2025-06-12 11:36:38 -07:00
parent ccfd33022f
commit fd785f98bb
No known key found for this signature in database
GPG Key ID: 523D5DC389D273BC
7 changed files with 124 additions and 136 deletions

View File

@ -16,6 +16,8 @@
A514C8D62B54A16400493A16 /* Ghostty.Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = A514C8D52B54A16400493A16 /* Ghostty.Config.swift */; };
A514C8D72B54A16400493A16 /* Ghostty.Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = A514C8D52B54A16400493A16 /* Ghostty.Config.swift */; };
A514C8D82B54DC6800493A16 /* Ghostty.App.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53D0C992B543F3B00305CE6 /* Ghostty.App.swift */; };
A51544FE2DFB111C009E85D8 /* TabsTitlebarTerminalWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51544FD2DFB1110009E85D8 /* TabsTitlebarTerminalWindow.swift */; };
A51545002DFB112E009E85D8 /* TerminalTabsTitlebar.xib in Resources */ = {isa = PBXBuildFile; fileRef = A51544FF2DFB112E009E85D8 /* TerminalTabsTitlebar.xib */; };
A51B78472AF4B58B00F3EDB9 /* LegacyTerminalWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51B78462AF4B58B00F3EDB9 /* LegacyTerminalWindow.swift */; };
A51BFC1E2B2FB5CE00E92F16 /* About.xib in Resources */ = {isa = PBXBuildFile; fileRef = A51BFC1D2B2FB5CE00E92F16 /* About.xib */; };
A51BFC202B2FB64F00E92F16 /* AboutController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51BFC1F2B2FB64F00E92F16 /* AboutController.swift */; };
@ -137,6 +139,8 @@
9351BE8E2D22937F003B3499 /* nvim */ = {isa = PBXFileReference; lastKnownFileType = folder; name = nvim; path = "../zig-out/share/nvim"; sourceTree = "<group>"; };
A50297342DFA0F3300B4E924 /* Double+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Double+Extension.swift"; sourceTree = "<group>"; };
A514C8D52B54A16400493A16 /* Ghostty.Config.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Ghostty.Config.swift; sourceTree = "<group>"; };
A51544FD2DFB1110009E85D8 /* TabsTitlebarTerminalWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabsTitlebarTerminalWindow.swift; sourceTree = "<group>"; };
A51544FF2DFB112E009E85D8 /* TerminalTabsTitlebar.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TerminalTabsTitlebar.xib; sourceTree = "<group>"; };
A51B78462AF4B58B00F3EDB9 /* LegacyTerminalWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyTerminalWindow.swift; sourceTree = "<group>"; };
A51BFC1D2B2FB5CE00E92F16 /* About.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = About.xib; sourceTree = "<group>"; };
A51BFC1F2B2FB64F00E92F16 /* AboutController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutController.swift; sourceTree = "<group>"; };
@ -404,10 +408,12 @@
A59630992AEE1C6400D64628 /* Terminal.xib */,
A5593FE22DF8D78600B47B10 /* TerminalHiddenTitlebar.xib */,
A5593FE42DF8DE3000B47B10 /* TerminalLegacy.xib */,
A51544FF2DFB112E009E85D8 /* TerminalTabsTitlebar.xib */,
A5593FE82DF927DF00B47B10 /* TerminalTransparentTitlebar.xib */,
A5593FDE2DF8D57100B47B10 /* TerminalWindow.swift */,
A5593FE02DF8D73400B47B10 /* HiddenTitlebarTerminalWindow.swift */,
A51B78462AF4B58B00F3EDB9 /* LegacyTerminalWindow.swift */,
A51544FD2DFB1110009E85D8 /* TabsTitlebarTerminalWindow.swift */,
A5593FE62DF927CC00B47B10 /* TransparentTitlebarTerminalWindow.swift */,
);
path = "Window Styles";
@ -694,6 +700,7 @@
A5CDF1912AAF9A5800513312 /* ConfigurationErrors.xib in Resources */,
857F63812A5E64F200CA4815 /* MainMenu.xib in Resources */,
A596309A2AEE1C6400D64628 /* Terminal.xib in Resources */,
A51545002DFB112E009E85D8 /* TerminalTabsTitlebar.xib in Resources */,
A5CBD05C2CA0C5C70017A1AE /* QuickTerminal.xib in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -761,6 +768,7 @@
A5333E1C2B5A1CE3008AEFF7 /* CrossKit.swift in Sources */,
A5874D992DAD751B00E83852 /* CGS.swift in Sources */,
A586366B2DF0A98C00E04A10 /* Array+Extension.swift in Sources */,
A51544FE2DFB111C009E85D8 /* TabsTitlebarTerminalWindow.swift in Sources */,
A59444F729A2ED5200725BBA /* SettingsView.swift in Sources */,
A56D58862ACDDB4100508D2C /* Ghostty.Shell.swift in Sources */,
A5985CD72C320C4500C57AD3 /* String+Extension.swift in Sources */,

View File

@ -13,6 +13,7 @@ class TerminalController: BaseTerminalController {
let config = appDelegate.ghostty.config
let nib = switch config.macosTitlebarStyle {
case "native": "Terminal"
//case "tabs": "TerminalTabsTitlebar"
case "tabs": "TerminalLegacy"
case "hidden": "TerminalHiddenTitlebar"
case "transparent": "TerminalTransparentTitlebar"
@ -128,11 +129,8 @@ class TerminalController: BaseTerminalController {
invalidateRestorableState()
// Update our zoom state
if let window = window as? LegacyTerminalWindow {
window.surfaceIsZoomed = to.zoomed != nil
}
if let window = window as? TerminalWindow {
window.surfaceIsZoomed2 = to.zoomed != nil
window.surfaceIsZoomed = to.zoomed != nil
}
// If our surface tree is now nil then we close our window.
@ -418,25 +416,6 @@ class TerminalController: BaseTerminalController {
}
}
}
// Legacy
if let windows = self.window?.tabbedWindows as? [LegacyTerminalWindow] {
for (tab, window) in zip(1..., windows) {
// We need to clear any windows beyond this because they have had
// a keyEquivalent set previously.
guard tab <= 9 else {
window.keyEquivalent = ""
continue
}
let action = "goto_tab:\(tab)"
if let equiv = ghostty.config.keyboardShortcut(for: action) {
window.keyEquivalent = "\(equiv)"
} else {
window.keyEquivalent = ""
}
}
}
}
private func fixTabBar() {
@ -470,13 +449,13 @@ class TerminalController: BaseTerminalController {
// Let our window handle its own appearance
if let window = window as? TerminalWindow {
// Sync our zoom state for splits
window.surfaceIsZoomed2 = surfaceTree.zoomed != nil
window.surfaceIsZoomed = surfaceTree.zoomed != nil
// Set the font for the window and tab titles.
if let titleFontName = surfaceConfig.windowTitleFontFamily {
window.titlebarFont2 = NSFont(name: titleFontName, size: NSFont.systemFontSize)
window.titlebarFont = NSFont(name: titleFontName, size: NSFont.systemFontSize)
} else {
window.titlebarFont2 = nil
window.titlebarFont = nil
}
// Call this last in case it uses any of the properties above.
@ -488,16 +467,6 @@ class TerminalController: BaseTerminalController {
if let window = window as? LegacyTerminalWindow {
// Update our window light/darkness based on our updated background color
window.isLightTheme = OSColor(surfaceConfig.backgroundColor).isLightColor
// Sync our zoom state for splits
window.surfaceIsZoomed = surfaceTree.zoomed != nil
// Set the font for the window and tab titles.
if let titleFontName = surfaceConfig.windowTitleFontFamily {
window.titlebarFont = NSFont(name: titleFontName, size: NSFont.systemFontSize)
} else {
window.titlebarFont = nil
}
}
// If our window is not visible, then we do nothing. Some things such as blurring
@ -916,18 +885,15 @@ class TerminalController: BaseTerminalController {
}
// TODO: remove
if let window = window as? LegacyTerminalWindow {
if let window = window as? LegacyTerminalWindow,
config.macosTitlebarStyle == "tabs" {
// Handle titlebar tabs config option. Something about what we do while setting up the
// titlebar tabs interferes with the window restore process unless window.tabbingMode
// is set to .preferred, so we set it, and switch back to automatic as soon as we can.
if (config.macosTitlebarStyle == "tabs") {
window.tabbingMode = .preferred
window.titlebarTabs = true
DispatchQueue.main.async {
window.tabbingMode = .automatic
}
} else if (config.macosTitlebarStyle == "transparent") {
window.transparentTabs = true
window.tabbingMode = .preferred
window.titlebarTabs = true
DispatchQueue.main.async {
window.tabbingMode = .automatic
}
if window.hasStyledTabs {

View File

@ -3,12 +3,16 @@ import Cocoa
/// The terminal window that we originally had in Ghostty for a long time. Kind of a soupy mess
/// of styling.
class LegacyTerminalWindow: TerminalWindow {
@objc dynamic var keyEquivalent: String = ""
/// This is used to determine if certain elements should be drawn light or dark and should
/// be updated whenever the window background color or surrounding elements changes.
var isLightTheme: Bool = false
override var surfaceIsZoomed: Bool {
didSet {
updateResetZoomTitlebarButtonVisibility()
}
}
lazy var titlebarColor: NSColor = backgroundColor {
didSet {
guard let titlebarContainer else { return }
@ -17,33 +21,6 @@ class LegacyTerminalWindow: TerminalWindow {
}
}
private lazy var keyEquivalentLabel: NSTextField = {
let label = NSTextField(labelWithAttributedString: NSAttributedString())
label.setContentCompressionResistancePriority(.windowSizeStayPut, for: .horizontal)
label.postsFrameChangedNotifications = true
return label
}()
private lazy var bindings = [
observe(\.surfaceIsZoomed, options: [.initial, .new]) { [weak self] window, _ in
guard let tabGroup = self?.tabGroup else { return }
self?.resetZoomTabButton.isHidden = !window.surfaceIsZoomed
self?.updateResetZoomTitlebarButtonVisibility()
},
observe(\.keyEquivalent, options: [.initial, .new]) { [weak self] window, _ in
let attributes: [NSAttributedString.Key: Any] = [
.font: NSFont.systemFont(ofSize: NSFont.smallSystemFontSize),
.foregroundColor: window.isKeyWindow ? NSColor.labelColor : NSColor.secondaryLabelColor,
]
let attributedString = NSAttributedString(string: " \(window.keyEquivalent) ", attributes: attributes)
self?.keyEquivalentLabel.attributedStringValue = attributedString
},
]
// false if all three traffic lights are missing/hidden, otherwise true
private var hasWindowButtons: Bool {
get {
@ -60,31 +37,13 @@ class LegacyTerminalWindow: TerminalWindow {
override func awakeFromNib() {
super.awakeFromNib()
_ = bindings
// Create the tab accessory view that houses the key-equivalent label and optional un-zoom button
let stackView = NSStackView(views: [keyEquivalentLabel, resetZoomTabButton])
stackView.setHuggingPriority(.defaultHigh, for: .horizontal)
stackView.spacing = 3
tab.accessoryView = stackView
if titlebarTabs {
generateToolbar()
}
}
deinit {
bindings.forEach() { $0.invalidate() }
}
// MARK: - NSWindow
override var title: String {
didSet {
tab.attributedTitle = attributedTitle
}
}
// We only need to set this once, but need to do it after the window has been created in order
// to determine if the theme is using a very dark background, in which case we don't want to
// remove the effect view if the default tab bar is being used since the effect created in
@ -101,7 +60,6 @@ class LegacyTerminalWindow: TerminalWindow {
super.becomeKey()
updateNewTabButtonOpacity()
resetZoomTabButton.contentTintColor = .controlAccentColor
resetZoomToolbarButton.contentTintColor = .controlAccentColor
tab.attributedTitle = attributedTitle
}
@ -110,7 +68,6 @@ class LegacyTerminalWindow: TerminalWindow {
super.resignKey()
updateNewTabButtonOpacity()
resetZoomTabButton.contentTintColor = .secondaryLabelColor
resetZoomToolbarButton.contentTintColor = .tertiaryLabelColor
tab.attributedTitle = attributedTitle
}
@ -284,16 +241,8 @@ class LegacyTerminalWindow: TerminalWindow {
// MARK: - Split Zoom Button
@objc dynamic var surfaceIsZoomed: Bool = false
private lazy var resetZoomToolbarButton: NSButton = generateResetZoomButton()
private lazy var resetZoomTabButton: NSButton = {
let button = generateResetZoomButton()
button.action = #selector(selectTabAndZoom(_:))
return button
}()
private lazy var resetZoomTitlebarAccessoryViewController: NSTitlebarAccessoryViewController? = {
guard let titlebarContainer else { return nil }
let size = NSSize(width: titlebarContainer.bounds.height, height: titlebarContainer.bounds.height)
@ -356,37 +305,13 @@ class LegacyTerminalWindow: TerminalWindow {
// MARK: - Titlebar Font
// Used to set the titlebar font.
var titlebarFont: NSFont? {
override var titlebarFont: NSFont? {
didSet {
let font = titlebarFont ?? NSFont.titleBarFont(ofSize: NSFont.systemFontSize)
titlebarTextField?.font = font
tab.attributedTitle = attributedTitle
if let toolbar = toolbar as? TerminalToolbar {
toolbar.titleFont = font
}
guard let toolbar = toolbar as? TerminalToolbar else { return }
toolbar.titleFont = titlebarFont ?? .titleBarFont(ofSize: NSFont.systemFontSize)
}
}
// Find the NSTextField responsible for displaying the titlebar's title.
private var titlebarTextField: NSTextField? {
guard let titlebarView = titlebarContainer?.subviews
.first(where: { $0.className == "NSTitlebarView" }) else { return nil }
return titlebarView.subviews.first(where: { $0 is NSTextField }) as? NSTextField
}
// Return a styled representation of our title property.
private var attributedTitle: NSAttributedString? {
guard let titlebarFont else { return nil }
let attributes: [NSAttributedString.Key: Any] = [
.font: titlebarFont,
.foregroundColor: isKeyWindow ? NSColor.labelColor : NSColor.secondaryLabelColor,
]
return NSAttributedString(string: title, attributes: attributes)
}
// MARK: - Titlebar Tabs
private var windowButtonsBackdrop: WindowButtonsBackdropView? = nil

View File

@ -0,0 +1,58 @@
import AppKit
import SwiftUI
class TabsTitlebarTerminalWindow: TerminalWindow, NSToolbarDelegate {
override func awakeFromNib() {
super.awakeFromNib()
// We must hide the title since we're going to be moving tabs into
// the titlebar which have their own title.
titleVisibility = .hidden
// Create a toolbar
let toolbar = NSToolbar(identifier: "TerminalToolbar")
toolbar.delegate = self
toolbar.centeredItemIdentifiers.insert(.title)
self.toolbar = toolbar
//toolbarStyle = .unifiedCompact
}
// MARK: NSToolbarDelegate
func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
return [.title, .flexibleSpace, .space]
}
func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
return [.flexibleSpace, .title, .flexibleSpace]
}
func toolbar(_ toolbar: NSToolbar,
itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier,
willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? {
switch itemIdentifier {
case .title:
let item = NSToolbarItem(itemIdentifier: .title)
item.view = NSHostingView(rootView: TitleItem())
item.visibilityPriority = .user
item.isEnabled = true
return item
default:
return NSToolbarItem(itemIdentifier: itemIdentifier)
}
}
}
extension NSToolbarItem.Identifier {
/// Displays the title of the window
static let title = NSToolbarItem.Identifier("Title")
}
extension TabsTitlebarTerminalWindow {
struct TitleItem: View {
var body: some View {
Text("HELLO THIS IS A PRETTY LONG TITLE")
}
}
}

View File

@ -17,7 +17,7 @@
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="0.0" y="0.0" width="800" height="600"/>
<rect key="screenRect" x="0.0" y="0.0" width="3008" height="1661"/>
<rect key="screenRect" x="0.0" y="0.0" width="1512" height="948"/>
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
<rect key="frame" x="0.0" y="0.0" width="800" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="24093.7" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="24093.7"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="TerminalController" customModule="Ghostty" customModuleProvider="target">
<connections>
<outlet property="window" destination="QvC-M9-y7g" id="cg9-Ep-qHg"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window title="👻 Ghostty" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="QvC-M9-y7g" customClass="TabsTitlebarTerminalWindow" customModule="Ghostty" customModuleProvider="target">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="0.0" y="0.0" width="800" height="600"/>
<rect key="screenRect" x="0.0" y="0.0" width="1512" height="948"/>
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
<rect key="frame" x="0.0" y="0.0" width="800" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
</view>
<connections>
<outlet property="delegate" destination="-2" id="tG2-b7-nb8"/>
</connections>
<point key="canvasLocation" x="132" y="-82"/>
</window>
</objects>
</document>

View File

@ -111,11 +111,11 @@ class TerminalWindow: NSWindow {
// MARK: Surface Zoom
/// Set to true if a surface is currently zoomed to show the reset zoom button.
var surfaceIsZoomed2: Bool = false {
var surfaceIsZoomed: Bool = false {
didSet {
// Show/hide our reset zoom button depending on if we're zoomed.
// We want to show it if we are zoomed.
resetZoomTabButton.isHidden = !surfaceIsZoomed2
resetZoomTabButton.isHidden = !surfaceIsZoomed
}
}
@ -150,9 +150,9 @@ class TerminalWindow: NSWindow {
}
// Used to set the titlebar font.
var titlebarFont2: NSFont? {
var titlebarFont: NSFont? {
didSet {
let font = titlebarFont2 ?? NSFont.titleBarFont(ofSize: NSFont.systemFontSize)
let font = titlebarFont ?? NSFont.titleBarFont(ofSize: NSFont.systemFontSize)
titlebarTextField?.font = font
tab.attributedTitle = attributedTitle
@ -167,8 +167,8 @@ class TerminalWindow: NSWindow {
}
// Return a styled representation of our title property.
private var attributedTitle: NSAttributedString? {
guard let titlebarFont = titlebarFont2 else { return nil }
var attributedTitle: NSAttributedString? {
guard let titlebarFont = titlebarFont else { return nil }
let attributes: [NSAttributedString.Key: Any] = [
.font: titlebarFont,