macOS: fix title bar clipping custom font

The #9168 fix is no longer needed, since the frame is now higher than the actual glyph.
This commit is contained in:
Lukas
2026-09-12 08:15:57 +08:00
parent 9bbb9b2468
commit 74e47a706f
2 changed files with 85 additions and 24 deletions

View File

@@ -1278,6 +1278,13 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
// Whenever we resize save our last position and size for the next start.
LastWindowPosition.shared.save(window)
if let window = self.window as? TerminalWindow {
// Expand the title frame to new width.
// This is needed because when the new window size becomes bigger,
// window's title will be clipped again.
window.syncWindowTitleAppearance()
}
}
func windowDidBecomeMain(_ notification: Notification) {

View File

@@ -1,4 +1,5 @@
import AppKit
import Combine
import SwiftUI
import GhosttyKit
@@ -277,12 +278,6 @@ class TerminalWindow: NSWindow {
/// added.
static let tabBarIdentifier: NSUserInterfaceItemIdentifier = .init("_ghosttyTabBar")
var hasMoreThanOneTabs: Bool {
/// accessing ``tabGroup?.windows`` here
/// will cause other edge cases, be careful
(tabbedWindows?.count ?? 0) > 1
}
func isTabBar(_ childViewController: NSTitlebarAccessoryViewController) -> Bool {
if childViewController.identifier == nil {
// The good case
@@ -396,12 +391,9 @@ class TerminalWindow: NSWindow {
// Whenever we change the window title we must also update our
// tab title if we're using custom fonts.
tab.attributedTitle = attributedTitle
/// We also needs to update this here, just in case
/// the value is not what we want
///
/// Check ``titlebarFont`` down below
/// to see why we need to check `hasMoreThanOneTabs` here
titlebarTextField?.usesSingleLineMode = !hasMoreThanOneTabs
guard title != oldValue else { return }
syncWindowTitleAppearance()
}
}
@@ -409,25 +401,28 @@ class TerminalWindow: NSWindow {
var titlebarFont: NSFont? {
didSet {
let font = titlebarFont ?? NSFont.titleBarFont(ofSize: NSFont.systemFontSize)
titlebarTextField?.font = font
/// We check `hasMoreThanOneTabs` here because the system
/// may copy this setting to the tabs text field at some point(e.g. entering/exiting fullscreen),
/// which can cause the title to be vertically misaligned (shifted downward).
///
/// This behaviour is the opposite of what happens in the title bars text field, which is quite odd...
titlebarTextField?.usesSingleLineMode = !hasMoreThanOneTabs
tab.attributedTitle = attributedTitle
// We need to call this every time the font is set,
// after entering or exiting fullscreen, or other cases,
// AppKit will reset the font.
syncWindowTitleAppearance(font: font)
}
}
// Find the NSTextField responsible for displaying the titlebar's title.
private var titlebarTextField: NSTextField? {
titlebarContainer?
// Find the array of NSTextField responsible for displaying the titlebar's title.
// In fullscreen mode, there'll be two of them.
private var titlebarTextFields: [NSTextField] {
(titlebarContainer?
.firstDescendant(withClassName: "NSTitlebarView")?
.firstDescendant(withClassName: "NSTextField") as? NSTextField
.descendants(withClassName: "NSTextField")
.compactMap { $0 as? NSTextField } ?? [])
.filter({ $0.superview?.className == "NSTitlebarView" })
}
// Cancellables for the frame change of the text fields in the titlebar.
private var titlebarTextFieldFrameCancellables = Set<AnyCancellable>()
// Return a styled representation of our title property.
var attributedTitle: NSAttributedString? {
guard let titlebarFont = titlebarFont else { return nil }
@@ -463,6 +458,65 @@ class TerminalWindow: NSWindow {
// MARK: Positioning And Styling
/// Update titlebarTextField's size and font
func syncWindowTitleAppearance(font: NSFont? = nil) {
titlebarTextFields.forEach { field in
if let font {
field.font = font
}
}
titlebarTextFieldFrameCancellables.removeAll()
// macOS 15 doesn't seem to need to adjust the frame.
//
// When using custom font, we always expand the frame to
// show the text properly.
//
// AppKit will relayout the frame when the font changes, that's
// why we need this hack in the first place.
guard #available(macOS 26.0, *), titlebarFont != nil else {
return
}
titlebarTextFields.forEach { field in
setWindowTitleFrameSize(field)
}
// We need to observe changes to make the frame correct in some cases:
//
// 1. Entering fullscreen mode.
// > Updating the frame when it's hidden doesn't seem to work.
// 2. Exiting fullscreen mode.
// 3. Resizing the window.
// 3. Maybe more...
titlebarTextFields.forEach { field in
field.postsFrameChangedNotifications = true
NotificationCenter.default
.publisher(for: NSView.frameDidChangeNotification, object: field)
.compactMap { $0.object as? NSTextField }
.sink { [weak self] in
self?.setWindowTitleFrameSize($0)
}
.store(in: &titlebarTextFieldFrameCancellables)
}
}
private func setWindowTitleFrameSize(_ textfield: NSTextField) {
guard let superview = textfield.superview else { return }
// Button group size estimate: 92.
// Make the available space a little bit smaller.
let fittingSize = textfield.sizeThatFits(superview.bounds.insetBy(dx: 92/2 + 6, dy: 0).size)
// We make the frame a little bit higher so fixed-width fonts can
// align center vertically as well.
let properSize = CGSize(width: fittingSize.width, height: fittingSize.height * 1.1)
guard textfield.frame.size != properSize else { return }
textfield.frame.size = properSize
}
/// This is called by the controller when there is a need to reset the window appearance.
func syncAppearance(_ surfaceConfig: Ghostty.SurfaceView.DerivedConfig) {
// If our window is not visible, then we do nothing. Some things such as blurring