From 63e56d0402a04696fbaff2eb7e5dbbf8f6257740 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 11 Jun 2025 15:08:12 -0700 Subject: [PATCH] macos: titlebar fonts work with new terminal window --- .../Terminal/TerminalController.swift | 11 ++++ .../Window Styles/LegacyTerminalWindow.swift | 42 ------------- .../Window Styles/TerminalWindow.swift | 62 ++++++++++++++++++- .../TransparentTitlebarTerminalWindow.swift | 26 -------- 4 files changed, 72 insertions(+), 69 deletions(-) diff --git a/macos/Sources/Features/Terminal/TerminalController.swift b/macos/Sources/Features/Terminal/TerminalController.swift index 082a3c806..cf771d556 100644 --- a/macos/Sources/Features/Terminal/TerminalController.swift +++ b/macos/Sources/Features/Terminal/TerminalController.swift @@ -471,6 +471,17 @@ class TerminalController: BaseTerminalController { private func syncAppearance(_ surfaceConfig: Ghostty.SurfaceView.DerivedConfig) { // Let our window handle its own appearance if let window = window as? TerminalWindow { + // Sync our zoom state for splits + window.surfaceIsZoomed2 = 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) + } else { + window.titlebarFont2 = nil + } + + // Call this last in case it uses any of the properties above. window.syncAppearance(surfaceConfig) } diff --git a/macos/Sources/Features/Terminal/Window Styles/LegacyTerminalWindow.swift b/macos/Sources/Features/Terminal/Window Styles/LegacyTerminalWindow.swift index 208e86343..e63681463 100644 --- a/macos/Sources/Features/Terminal/Window Styles/LegacyTerminalWindow.swift +++ b/macos/Sources/Features/Terminal/Window Styles/LegacyTerminalWindow.swift @@ -77,48 +77,6 @@ class LegacyTerminalWindow: TerminalWindow { bindings.forEach() { $0.invalidate() } } - // MARK: Titlebar Helpers - // These helpers are generic to what we're trying to achieve (i.e. titlebar - // style tabs, titlebar styling, etc.). They're just here to make it easier. - - private var titlebarContainer: NSView? { - // If we aren't fullscreen then the titlebar container is part of our window. - if !styleMask.contains(.fullScreen) { - guard let view = contentView?.superview ?? contentView else { return nil } - return titlebarContainerView(in: view) - } - - // If we are fullscreen, the titlebar container view is part of a separate - // "fullscreen window", we need to find the window and then get the view. - for window in NSApplication.shared.windows { - // This is the private window class that contains the toolbar - guard window.className == "NSToolbarFullScreenWindow" else { continue } - - // The parent will match our window. This is used to filter the correct - // fullscreen window if we have multiple. - guard window.parent == self else { continue } - - guard let view = window.contentView else { continue } - return titlebarContainerView(in: view) - } - - return nil - } - - private func titlebarContainerView(in view: NSView) -> NSView? { - if view.className == "NSTitlebarContainerView" { - return view - } - - for subview in view.subviews { - if let found = titlebarContainerView(in: subview) { - return found - } - } - - return nil - } - // MARK: - NSWindow override var title: String { diff --git a/macos/Sources/Features/Terminal/Window Styles/TerminalWindow.swift b/macos/Sources/Features/Terminal/Window Styles/TerminalWindow.swift index 9fac08c4b..1e9ca1b01 100644 --- a/macos/Sources/Features/Terminal/Window Styles/TerminalWindow.swift +++ b/macos/Sources/Features/Terminal/Window Styles/TerminalWindow.swift @@ -72,7 +72,7 @@ class TerminalWindow: NSWindow { override func mergeAllWindows(_ sender: Any?) { super.mergeAllWindows(sender) - + // It takes an event loop cycle to merge all the windows so we set a // short timer to relabel the tabs (issue #1902) DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in @@ -139,6 +139,66 @@ class TerminalWindow: NSWindow { return button } + // MARK: Title Text + + override var title: String { + didSet { + // Whenever we change the window title we must also update our + // tab title if we're using custom fonts. + tab.attributedTitle = attributedTitle + } + } + + // Used to set the titlebar font. + var titlebarFont2: NSFont? { + didSet { + let font = titlebarFont2 ?? NSFont.titleBarFont(ofSize: NSFont.systemFontSize) + + titlebarTextField?.font = font + tab.attributedTitle = attributedTitle + } + } + + // Find the NSTextField responsible for displaying the titlebar's title. + private var titlebarTextField: NSTextField? { + titlebarContainer? + .firstDescendant(withClassName: "NSTitlebarView")? + .firstDescendant(withClassName: "NSTextField") as? NSTextField + } + + // Return a styled representation of our title property. + private var attributedTitle: NSAttributedString? { + guard let titlebarFont = titlebarFont2 else { return nil } + + let attributes: [NSAttributedString.Key: Any] = [ + .font: titlebarFont, + .foregroundColor: isKeyWindow ? NSColor.labelColor : NSColor.secondaryLabelColor, + ] + return NSAttributedString(string: title, attributes: attributes) + } + + var titlebarContainer: NSView? { + // If we aren't fullscreen then the titlebar container is part of our window. + if !styleMask.contains(.fullScreen) { + return contentView?.firstViewFromRoot(withClassName: "NSTitlebarContainerView") + } + + // If we are fullscreen, the titlebar container view is part of a separate + // "fullscreen window", we need to find the window and then get the view. + for window in NSApplication.shared.windows { + // This is the private window class that contains the toolbar + guard window.className == "NSToolbarFullScreenWindow" else { continue } + + // The parent will match our window. This is used to filter the correct + // fullscreen window if we have multiple. + guard window.parent == self else { continue } + + return window.contentView?.firstViewFromRoot(withClassName: "NSTitlebarContainerView") + } + + return nil + } + // MARK: Positioning And Styling /// This is called by the controller when there is a need to reset the window apperance. diff --git a/macos/Sources/Features/Terminal/Window Styles/TransparentTitlebarTerminalWindow.swift b/macos/Sources/Features/Terminal/Window Styles/TransparentTitlebarTerminalWindow.swift index ada84ff12..f949b6094 100644 --- a/macos/Sources/Features/Terminal/Window Styles/TransparentTitlebarTerminalWindow.swift +++ b/macos/Sources/Features/Terminal/Window Styles/TransparentTitlebarTerminalWindow.swift @@ -94,32 +94,6 @@ class TransparentTitlebarTerminalWindow: TerminalWindow { titlebarContainer?.firstDescendant(withClassName: "NSTitlebarBackgroundView") } - private var titlebarContainer: NSView? { - // If we aren't fullscreen then the titlebar container is part of our window. - if !styleMask.contains(.fullScreen) { - return titlebarContainerView - } - - // If we are fullscreen, the titlebar container view is part of a separate - // "fullscreen window", we need to find the window and then get the view. - for window in NSApplication.shared.windows { - // This is the private window class that contains the toolbar - guard window.className == "NSToolbarFullScreenWindow" else { continue } - - // The parent will match our window. This is used to filter the correct - // fullscreen window if we have multiple. - guard window.parent == self else { continue } - - return titlebarContainerView - } - - return nil - } - - private var titlebarContainerView: NSView? { - contentView?.firstViewFromRoot(withClassName: "NSTitlebarContainerView") - } - // MARK: Tab Group Observation private func setupKVO() {