mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
Use foreground color in macOS tab titles
Currently tab titles will use the default macOS tab foreground color, but the color schemes background color. This can result in tabs with unreadable text. This change updates the macOS UI tabs to use the foreground color defined in the config, or the overridden value when set dynamically. This change: - Tracks `foregroundColor` in the SurfaceView which is pre-populated with the config foreground color along with support for color change update events. - Adds a new `titleForegroundColor` property to use in `TerminalWindow` titles. - Updates each tab window foreground color when `syncAppearance` is called. - Calls `syncAppearance` when a window becomes the key window.
This commit is contained in:
@ -253,20 +253,25 @@ 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) {
|
||||
// Similar to above, an alpha component of "0" causes compositor issues, so
|
||||
// we use 0.001. See: https://github.com/ghostty-org/ghostty/pull/4308
|
||||
backgroundColor = OSColor(focusedSurface.backgroundColor ?? surfaceConfig.backgroundColor).withAlphaComponent(0.001)
|
||||
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
|
||||
@ -278,6 +283,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
|
||||
}
|
||||
}
|
||||
|
||||
private func setInitialWindowPosition(x: Int16?, y: Int16?, windowDecorations: Bool) {
|
||||
@ -315,28 +325,28 @@ class TerminalController: BaseTerminalController {
|
||||
window.styleMask = [
|
||||
// We need `titled` in the mask to get the normal window frame
|
||||
.titled,
|
||||
|
||||
|
||||
// Full size content view so we can extend
|
||||
// content in to the hidden titlebar's area
|
||||
.fullSizeContentView,
|
||||
|
||||
|
||||
.resizable,
|
||||
.closable,
|
||||
.miniaturizable,
|
||||
]
|
||||
|
||||
|
||||
// Hide the title
|
||||
window.titleVisibility = .hidden
|
||||
window.titlebarAppearsTransparent = true
|
||||
|
||||
|
||||
// Hide the traffic lights (window control buttons)
|
||||
window.standardWindowButton(.closeButton)?.isHidden = true
|
||||
window.standardWindowButton(.miniaturizeButton)?.isHidden = true
|
||||
window.standardWindowButton(.zoomButton)?.isHidden = true
|
||||
|
||||
|
||||
// Disallow tabbing if the titlebar is hidden, since that will (should) also hide the tab bar.
|
||||
window.tabbingMode = .disallowed
|
||||
|
||||
|
||||
// Nuke it from orbit -- hide the titlebar container entirely, just in case. There are
|
||||
// some operations that appear to bring back the titlebar visibility so this ensures
|
||||
// it is gone forever.
|
||||
@ -345,7 +355,7 @@ class TerminalController: BaseTerminalController {
|
||||
titleBarContainer.isHidden = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override func windowDidLoad() {
|
||||
super.windowDidLoad()
|
||||
guard let window = window as? TerminalWindow else { return }
|
||||
@ -429,6 +439,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
|
||||
@ -487,6 +498,10 @@ class TerminalController: BaseTerminalController {
|
||||
super.windowDidBecomeKey(notification)
|
||||
self.relabelTabs()
|
||||
self.fixTabBar()
|
||||
|
||||
if let focusedSurface {
|
||||
self.syncAppearance(focusedSurface.derivedConfig)
|
||||
}
|
||||
}
|
||||
|
||||
override func windowDidMove(_ notification: Notification) {
|
||||
@ -651,6 +666,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?) {
|
||||
@ -780,15 +798,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
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@ -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()
|
||||
},
|
||||
]
|
||||
|
||||
@ -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
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -423,12 +439,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)
|
||||
}
|
||||
|
||||
|
@ -350,6 +350,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
|
||||
|
@ -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
|
||||
@ -443,6 +447,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.
|
||||
@ -1209,6 +1218,7 @@ extension Ghostty {
|
||||
struct DerivedConfig {
|
||||
let backgroundColor: Color
|
||||
let backgroundOpacity: Double
|
||||
let foregroundColor: Color
|
||||
let macosWindowShadow: Bool
|
||||
let windowTitleFontFamily: String?
|
||||
let windowAppearance: NSAppearance?
|
||||
@ -1216,6 +1226,7 @@ extension Ghostty {
|
||||
init() {
|
||||
self.backgroundColor = Color(NSColor.windowBackgroundColor)
|
||||
self.backgroundOpacity = 1
|
||||
self.foregroundColor = Color(NSColor.labelColor)
|
||||
self.macosWindowShadow = true
|
||||
self.windowTitleFontFamily = nil
|
||||
self.windowAppearance = nil
|
||||
@ -1224,6 +1235,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)
|
||||
|
Reference in New Issue
Block a user