diff --git a/macos/Ghostty.xcodeproj/project.pbxproj b/macos/Ghostty.xcodeproj/project.pbxproj index 5f5b3013c..3f1cddf44 100644 --- a/macos/Ghostty.xcodeproj/project.pbxproj +++ b/macos/Ghostty.xcodeproj/project.pbxproj @@ -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 = ""; }; A50297342DFA0F3300B4E924 /* Double+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Double+Extension.swift"; sourceTree = ""; }; A514C8D52B54A16400493A16 /* Ghostty.Config.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Ghostty.Config.swift; sourceTree = ""; }; + A51544FD2DFB1110009E85D8 /* TabsTitlebarTerminalWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabsTitlebarTerminalWindow.swift; sourceTree = ""; }; + A51544FF2DFB112E009E85D8 /* TerminalTabsTitlebar.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TerminalTabsTitlebar.xib; sourceTree = ""; }; A51B78462AF4B58B00F3EDB9 /* LegacyTerminalWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyTerminalWindow.swift; sourceTree = ""; }; A51BFC1D2B2FB5CE00E92F16 /* About.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = About.xib; sourceTree = ""; }; A51BFC1F2B2FB64F00E92F16 /* AboutController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutController.swift; sourceTree = ""; }; @@ -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 */, diff --git a/macos/Sources/Features/Terminal/TerminalController.swift b/macos/Sources/Features/Terminal/TerminalController.swift index 86b47b9bd..d59f71619 100644 --- a/macos/Sources/Features/Terminal/TerminalController.swift +++ b/macos/Sources/Features/Terminal/TerminalController.swift @@ -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 { diff --git a/macos/Sources/Features/Terminal/Window Styles/LegacyTerminalWindow.swift b/macos/Sources/Features/Terminal/Window Styles/LegacyTerminalWindow.swift index e63681463..89afbf72f 100644 --- a/macos/Sources/Features/Terminal/Window Styles/LegacyTerminalWindow.swift +++ b/macos/Sources/Features/Terminal/Window Styles/LegacyTerminalWindow.swift @@ -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 diff --git a/macos/Sources/Features/Terminal/Window Styles/TabsTitlebarTerminalWindow.swift b/macos/Sources/Features/Terminal/Window Styles/TabsTitlebarTerminalWindow.swift new file mode 100644 index 000000000..858b54829 --- /dev/null +++ b/macos/Sources/Features/Terminal/Window Styles/TabsTitlebarTerminalWindow.swift @@ -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") + } + } +} diff --git a/macos/Sources/Features/Terminal/Window Styles/TerminalHiddenTitlebar.xib b/macos/Sources/Features/Terminal/Window Styles/TerminalHiddenTitlebar.xib index eb4675657..1a2a6c192 100644 --- a/macos/Sources/Features/Terminal/Window Styles/TerminalHiddenTitlebar.xib +++ b/macos/Sources/Features/Terminal/Window Styles/TerminalHiddenTitlebar.xib @@ -17,7 +17,7 @@ - + diff --git a/macos/Sources/Features/Terminal/Window Styles/TerminalTabsTitlebar.xib b/macos/Sources/Features/Terminal/Window Styles/TerminalTabsTitlebar.xib new file mode 100644 index 000000000..779b6e094 --- /dev/null +++ b/macos/Sources/Features/Terminal/Window Styles/TerminalTabsTitlebar.xib @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macos/Sources/Features/Terminal/Window Styles/TerminalWindow.swift b/macos/Sources/Features/Terminal/Window Styles/TerminalWindow.swift index eb4b4a6da..a0e18d283 100644 --- a/macos/Sources/Features/Terminal/Window Styles/TerminalWindow.swift +++ b/macos/Sources/Features/Terminal/Window Styles/TerminalWindow.swift @@ -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,