Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use foreground color in macOS tab titles #3843

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions macos/Sources/Features/Terminal/TerminalController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -245,18 +245,23 @@ class TerminalController: BaseTerminalController {
// If it does, we match the focused surface. If it doesn't, we use the app
// configuration.
let backgroundColor: OSColor
let foregroundColor: OSColor
if let surfaceTree {
if let focusedSurface, surfaceTree.doesBorderTop(view: focusedSurface) {
backgroundColor = OSColor(focusedSurface.backgroundColor ?? surfaceConfig.backgroundColor)
foregroundColor = OSColor(focusedSurface.foregroundColor ?? surfaceConfig.foregroundColor)
} else {
// We don't have a focused surface or our surface doesn't border the
// top. We choose to match the color of the top-left most surface.
backgroundColor = OSColor(surfaceTree.topLeft().backgroundColor ?? derivedConfig.backgroundColor)
foregroundColor = OSColor(surfaceTree.topLeft().foregroundColor ?? derivedConfig.foregroundColor)
}
} else {
backgroundColor = OSColor(self.derivedConfig.backgroundColor)
foregroundColor = OSColor(self.derivedConfig.foregroundColor)
}
window.titlebarColor = backgroundColor.withAlphaComponent(surfaceConfig.backgroundOpacity)
window.titleForegroundColor = foregroundColor

if (window.isOpaque) {
// Bg color is only synced if we have no transparency. This is because
Expand All @@ -268,6 +273,11 @@ class TerminalController: BaseTerminalController {
// so we only call this if we are opaque.
window.updateTabBar()
}

guard let windows = self.window?.tabbedWindows as? [TerminalWindow] else { return }
for (w) in windows {
w.titleForegroundColor = window.titleForegroundColor
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels like this should be moved to a private func and this should be called here and in windowDidBecomeKey instead of a full syncAppearance which is rather expensive. Thoughts?

}

//MARK: - NSWindowController
Expand Down Expand Up @@ -357,6 +367,7 @@ class TerminalController: BaseTerminalController {

// This makes sure our titlebar renders correctly when there is a transparent background
window.titlebarColor = backgroundColor.withAlphaComponent(config.backgroundOpacity)
window.titleForegroundColor = NSColor(config.foregroundColor)
}

// Initialize our content view to the SwiftUI root
Expand Down Expand Up @@ -448,6 +459,10 @@ class TerminalController: BaseTerminalController {
super.windowDidBecomeKey(notification)
self.relabelTabs()
self.fixTabBar()

if let focusedSurface {
self.syncAppearance(focusedSurface.derivedConfig)
}
}

override func windowDidMove(_ notification: Notification) {
Expand Down Expand Up @@ -582,6 +597,9 @@ class TerminalController: BaseTerminalController {
focusedSurface.$backgroundColor
.sink { [weak self, weak focusedSurface] _ in self?.syncAppearanceOnPropertyChange(focusedSurface) }
.store(in: &surfaceAppearanceCancellables)
focusedSurface.$foregroundColor
.sink { [weak self, weak focusedSurface] _ in self?.syncAppearanceOnPropertyChange(focusedSurface) }
.store(in: &surfaceAppearanceCancellables)
}

private func syncAppearanceOnPropertyChange(_ surface: Ghostty.SurfaceView?) {
Expand Down Expand Up @@ -705,15 +723,18 @@ class TerminalController: BaseTerminalController {

private struct DerivedConfig {
let backgroundColor: Color
let foregroundColor: Color
let macosTitlebarStyle: String

init() {
self.backgroundColor = Color(NSColor.windowBackgroundColor)
self.foregroundColor = Color(NSColor.labelColor)
self.macosTitlebarStyle = "system"
}

init(_ config: Ghostty.Config) {
self.backgroundColor = config.backgroundColor
self.foregroundColor = config.foregroundColor
self.macosTitlebarStyle = config.macosTitlebarStyle
}
}
Expand Down
10 changes: 10 additions & 0 deletions macos/Sources/Features/Terminal/TerminalToolbar.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,16 @@ class TerminalToolbar: NSToolbar, NSToolbarDelegate {
}
}

var titleColor: NSColor? {
get {
titleTextField.textColor
}

set {
titleTextField.textColor = newValue
}
}

override init(identifier: NSToolbar.Identifier) {
super.init(identifier: identifier)

Expand Down
41 changes: 28 additions & 13 deletions macos/Sources/Features/Terminal/TerminalWindow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,17 @@ class TerminalWindow: NSWindow {
guard let titlebarContainer else { return }
titlebarContainer.wantsLayer = true
titlebarContainer.layer?.backgroundColor = titlebarColor.cgColor
tab.attributedTitle = attributedTitle
}
}

lazy var titleForegroundColor: NSColor = NSColor.labelColor {
didSet {
tab.attributedTitle = attributedTitle
if let toolbar = toolbar as? TerminalToolbar {
toolbar.titleColor = titleForegroundColor
}
self.updateKeyEquivalentLabel()
}
}

Expand All @@ -28,13 +39,7 @@ class TerminalWindow: NSWindow {
},

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
self?.updateKeyEquivalentLabel()
},
]

Expand Down Expand Up @@ -317,6 +322,17 @@ class TerminalWindow: NSWindow {
}
}

private func updateKeyEquivalentLabel() {
let attributes: [NSAttributedString.Key: Any] = [
.font: NSFont.systemFont(ofSize: 11),
.foregroundColor: self.titleForegroundColor.withAlphaComponent(isKeyWindow ? 1 : 0.4),
]
let attributedString = NSAttributedString(string: " \(keyEquivalent) ", attributes: attributes)

keyEquivalentLabel.attributedStringValue = attributedString
}


// MARK: - Split Zoom Button

@objc dynamic var surfaceIsZoomed: Bool = false
Expand Down Expand Up @@ -403,13 +419,13 @@ class TerminalWindow: NSWindow {
// Used to set the titlebar font.
var titlebarFont: NSFont? {
didSet {
let font = titlebarFont ?? NSFont.titleBarFont(ofSize: NSFont.systemFontSize)
let font = titlebarFont ?? NSFont.titleBarFont(ofSize: 11)

titlebarTextField?.font = font
tab.attributedTitle = attributedTitle

if let toolbar = toolbar as? TerminalToolbar {
toolbar.titleFont = font
toolbar.titleFont = font.withSize(NSFont.systemFontSize)
}
}
}
Expand All @@ -425,12 +441,11 @@ class TerminalWindow: NSWindow {

// 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,
.foregroundColor: titleForegroundColor,
.font: titlebarFont?.withSize(11) ?? NSFont.systemFont(ofSize: 11),
]

return NSAttributedString(string: title, attributes: attributes)
}

Expand Down
20 changes: 20 additions & 0 deletions macos/Sources/Ghostty/Ghostty.Config.swift
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,26 @@ extension Ghostty {
return v;
}

var foregroundColor: Color {
var color: ghostty_config_color_s = .init();
let bg_key = "foreground"
if (!ghostty_config_get(config, &color, bg_key, UInt(bg_key.count))) {
#if os(macOS)
return Color(NSColor.labelColor)
#elseif os(iOS)
return Color(UIColor.label)
#else
#error("unsupported")
#endif
}

return .init(
red: Double(color.r) / 255,
green: Double(color.g) / 255,
blue: Double(color.b) / 255
)
}

var unfocusedSplitOpacity: Double {
guard let config = self.config else { return 1 }
var opacity: Double = 0.85
Expand Down
12 changes: 12 additions & 0 deletions macos/Sources/Ghostty/SurfaceView_AppKit.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ extension Ghostty {
/// dynamically updated. Otherwise, the background color is the default background color.
@Published private(set) var backgroundColor: Color? = nil

/// The foreground color within the color palette of the surface. This is only set if it is
/// dynamically updated. Otherwise, the background color is the default background color.
@Published private(set) var foregroundColor: Color? = nil

// An initial size to request for a window. This will only affect
// then the view is moved to a new window.
var initialSize: NSSize? = nil
Expand Down Expand Up @@ -402,6 +406,11 @@ extension Ghostty {
DispatchQueue.main.async { [weak self] in
self?.backgroundColor = change.color
}
self.backgroundColor = change.color
case .foreground:
DispatchQueue.main.async { [weak self] in
self?.foregroundColor = change.color
}

default:
// We don't do anything for the other colors yet.
Expand Down Expand Up @@ -1126,13 +1135,15 @@ extension Ghostty {
struct DerivedConfig {
let backgroundColor: Color
let backgroundOpacity: Double
let foregroundColor: Color
let macosWindowShadow: Bool
let windowTitleFontFamily: String?
let windowAppearance: NSAppearance?

init() {
self.backgroundColor = Color(NSColor.windowBackgroundColor)
self.backgroundOpacity = 1
self.foregroundColor = Color(NSColor.labelColor)
self.macosWindowShadow = true
self.windowTitleFontFamily = nil
self.windowAppearance = nil
Expand All @@ -1141,6 +1152,7 @@ extension Ghostty {
init(_ config: Ghostty.Config) {
self.backgroundColor = config.backgroundColor
self.backgroundOpacity = config.backgroundOpacity
self.foregroundColor = config.foregroundColor
self.macosWindowShadow = config.macosWindowShadow
self.windowTitleFontFamily = config.windowTitleFontFamily
self.windowAppearance = .init(ghosttyConfig: config)
Expand Down