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
|
// If it does, we match the focused surface. If it doesn't, we use the app
|
||||||
// configuration.
|
// configuration.
|
||||||
let backgroundColor: OSColor
|
let backgroundColor: OSColor
|
||||||
|
let foregroundColor: OSColor
|
||||||
if let surfaceTree {
|
if let surfaceTree {
|
||||||
if let focusedSurface, surfaceTree.doesBorderTop(view: focusedSurface) {
|
if let focusedSurface, surfaceTree.doesBorderTop(view: focusedSurface) {
|
||||||
// Similar to above, an alpha component of "0" causes compositor issues, so
|
// 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
|
// we use 0.001. See: https://github.com/ghostty-org/ghostty/pull/4308
|
||||||
backgroundColor = OSColor(focusedSurface.backgroundColor ?? surfaceConfig.backgroundColor).withAlphaComponent(0.001)
|
backgroundColor = OSColor(focusedSurface.backgroundColor ?? surfaceConfig.backgroundColor).withAlphaComponent(0.001)
|
||||||
|
foregroundColor = OSColor(focusedSurface.foregroundColor ?? surfaceConfig.foregroundColor)
|
||||||
} else {
|
} else {
|
||||||
// We don't have a focused surface or our surface doesn't border the
|
// 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.
|
// top. We choose to match the color of the top-left most surface.
|
||||||
backgroundColor = OSColor(surfaceTree.topLeft().backgroundColor ?? derivedConfig.backgroundColor)
|
backgroundColor = OSColor(surfaceTree.topLeft().backgroundColor ?? derivedConfig.backgroundColor)
|
||||||
|
foregroundColor = OSColor(surfaceTree.topLeft().foregroundColor ?? derivedConfig.foregroundColor)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
backgroundColor = OSColor(self.derivedConfig.backgroundColor)
|
backgroundColor = OSColor(self.derivedConfig.backgroundColor)
|
||||||
|
foregroundColor = OSColor(self.derivedConfig.foregroundColor)
|
||||||
}
|
}
|
||||||
window.titlebarColor = backgroundColor.withAlphaComponent(surfaceConfig.backgroundOpacity)
|
window.titlebarColor = backgroundColor.withAlphaComponent(surfaceConfig.backgroundOpacity)
|
||||||
|
window.titleForegroundColor = foregroundColor
|
||||||
|
|
||||||
if (window.isOpaque) {
|
if (window.isOpaque) {
|
||||||
// Bg color is only synced if we have no transparency. This is because
|
// 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.
|
// so we only call this if we are opaque.
|
||||||
window.updateTabBar()
|
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) {
|
private func setInitialWindowPosition(x: Int16?, y: Int16?, windowDecorations: Bool) {
|
||||||
@ -315,28 +325,28 @@ class TerminalController: BaseTerminalController {
|
|||||||
window.styleMask = [
|
window.styleMask = [
|
||||||
// We need `titled` in the mask to get the normal window frame
|
// We need `titled` in the mask to get the normal window frame
|
||||||
.titled,
|
.titled,
|
||||||
|
|
||||||
// Full size content view so we can extend
|
// Full size content view so we can extend
|
||||||
// content in to the hidden titlebar's area
|
// content in to the hidden titlebar's area
|
||||||
.fullSizeContentView,
|
.fullSizeContentView,
|
||||||
|
|
||||||
.resizable,
|
.resizable,
|
||||||
.closable,
|
.closable,
|
||||||
.miniaturizable,
|
.miniaturizable,
|
||||||
]
|
]
|
||||||
|
|
||||||
// Hide the title
|
// Hide the title
|
||||||
window.titleVisibility = .hidden
|
window.titleVisibility = .hidden
|
||||||
window.titlebarAppearsTransparent = true
|
window.titlebarAppearsTransparent = true
|
||||||
|
|
||||||
// Hide the traffic lights (window control buttons)
|
// Hide the traffic lights (window control buttons)
|
||||||
window.standardWindowButton(.closeButton)?.isHidden = true
|
window.standardWindowButton(.closeButton)?.isHidden = true
|
||||||
window.standardWindowButton(.miniaturizeButton)?.isHidden = true
|
window.standardWindowButton(.miniaturizeButton)?.isHidden = true
|
||||||
window.standardWindowButton(.zoomButton)?.isHidden = true
|
window.standardWindowButton(.zoomButton)?.isHidden = true
|
||||||
|
|
||||||
// Disallow tabbing if the titlebar is hidden, since that will (should) also hide the tab bar.
|
// Disallow tabbing if the titlebar is hidden, since that will (should) also hide the tab bar.
|
||||||
window.tabbingMode = .disallowed
|
window.tabbingMode = .disallowed
|
||||||
|
|
||||||
// Nuke it from orbit -- hide the titlebar container entirely, just in case. There are
|
// 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
|
// some operations that appear to bring back the titlebar visibility so this ensures
|
||||||
// it is gone forever.
|
// it is gone forever.
|
||||||
@ -345,7 +355,7 @@ class TerminalController: BaseTerminalController {
|
|||||||
titleBarContainer.isHidden = true
|
titleBarContainer.isHidden = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func windowDidLoad() {
|
override func windowDidLoad() {
|
||||||
super.windowDidLoad()
|
super.windowDidLoad()
|
||||||
guard let window = window as? TerminalWindow else { return }
|
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
|
// This makes sure our titlebar renders correctly when there is a transparent background
|
||||||
window.titlebarColor = backgroundColor.withAlphaComponent(config.backgroundOpacity)
|
window.titlebarColor = backgroundColor.withAlphaComponent(config.backgroundOpacity)
|
||||||
|
window.titleForegroundColor = NSColor(config.foregroundColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize our content view to the SwiftUI root
|
// Initialize our content view to the SwiftUI root
|
||||||
@ -487,6 +498,10 @@ class TerminalController: BaseTerminalController {
|
|||||||
super.windowDidBecomeKey(notification)
|
super.windowDidBecomeKey(notification)
|
||||||
self.relabelTabs()
|
self.relabelTabs()
|
||||||
self.fixTabBar()
|
self.fixTabBar()
|
||||||
|
|
||||||
|
if let focusedSurface {
|
||||||
|
self.syncAppearance(focusedSurface.derivedConfig)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func windowDidMove(_ notification: Notification) {
|
override func windowDidMove(_ notification: Notification) {
|
||||||
@ -651,6 +666,9 @@ class TerminalController: BaseTerminalController {
|
|||||||
focusedSurface.$backgroundColor
|
focusedSurface.$backgroundColor
|
||||||
.sink { [weak self, weak focusedSurface] _ in self?.syncAppearanceOnPropertyChange(focusedSurface) }
|
.sink { [weak self, weak focusedSurface] _ in self?.syncAppearanceOnPropertyChange(focusedSurface) }
|
||||||
.store(in: &surfaceAppearanceCancellables)
|
.store(in: &surfaceAppearanceCancellables)
|
||||||
|
focusedSurface.$foregroundColor
|
||||||
|
.sink { [weak self, weak focusedSurface] _ in self?.syncAppearanceOnPropertyChange(focusedSurface) }
|
||||||
|
.store(in: &surfaceAppearanceCancellables)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func syncAppearanceOnPropertyChange(_ surface: Ghostty.SurfaceView?) {
|
private func syncAppearanceOnPropertyChange(_ surface: Ghostty.SurfaceView?) {
|
||||||
@ -780,15 +798,18 @@ class TerminalController: BaseTerminalController {
|
|||||||
|
|
||||||
private struct DerivedConfig {
|
private struct DerivedConfig {
|
||||||
let backgroundColor: Color
|
let backgroundColor: Color
|
||||||
|
let foregroundColor: Color
|
||||||
let macosTitlebarStyle: String
|
let macosTitlebarStyle: String
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
self.backgroundColor = Color(NSColor.windowBackgroundColor)
|
self.backgroundColor = Color(NSColor.windowBackgroundColor)
|
||||||
|
self.foregroundColor = Color(NSColor.labelColor)
|
||||||
self.macosTitlebarStyle = "system"
|
self.macosTitlebarStyle = "system"
|
||||||
}
|
}
|
||||||
|
|
||||||
init(_ config: Ghostty.Config) {
|
init(_ config: Ghostty.Config) {
|
||||||
self.backgroundColor = config.backgroundColor
|
self.backgroundColor = config.backgroundColor
|
||||||
|
self.foregroundColor = config.foregroundColor
|
||||||
self.macosTitlebarStyle = config.macosTitlebarStyle
|
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) {
|
override init(identifier: NSToolbar.Identifier) {
|
||||||
super.init(identifier: identifier)
|
super.init(identifier: identifier)
|
||||||
|
|
||||||
|
@ -8,6 +8,17 @@ class TerminalWindow: NSWindow {
|
|||||||
guard let titlebarContainer else { return }
|
guard let titlebarContainer else { return }
|
||||||
titlebarContainer.wantsLayer = true
|
titlebarContainer.wantsLayer = true
|
||||||
titlebarContainer.layer?.backgroundColor = titlebarColor.cgColor
|
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
|
observe(\.keyEquivalent, options: [.initial, .new]) { [weak self] window, _ in
|
||||||
let attributes: [NSAttributedString.Key: Any] = [
|
self?.updateKeyEquivalentLabel()
|
||||||
.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
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -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
|
// MARK: - Split Zoom Button
|
||||||
|
|
||||||
@objc dynamic var surfaceIsZoomed: Bool = false
|
@objc dynamic var surfaceIsZoomed: Bool = false
|
||||||
@ -403,13 +419,13 @@ class TerminalWindow: NSWindow {
|
|||||||
// Used to set the titlebar font.
|
// Used to set the titlebar font.
|
||||||
var titlebarFont: NSFont? {
|
var titlebarFont: NSFont? {
|
||||||
didSet {
|
didSet {
|
||||||
let font = titlebarFont ?? NSFont.titleBarFont(ofSize: NSFont.systemFontSize)
|
let font = titlebarFont ?? NSFont.titleBarFont(ofSize: 11)
|
||||||
|
|
||||||
titlebarTextField?.font = font
|
titlebarTextField?.font = font
|
||||||
tab.attributedTitle = attributedTitle
|
tab.attributedTitle = attributedTitle
|
||||||
|
|
||||||
if let toolbar = toolbar as? TerminalToolbar {
|
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.
|
// Return a styled representation of our title property.
|
||||||
private var attributedTitle: NSAttributedString? {
|
private var attributedTitle: NSAttributedString? {
|
||||||
guard let titlebarFont else { return nil }
|
|
||||||
|
|
||||||
let attributes: [NSAttributedString.Key: Any] = [
|
let attributes: [NSAttributedString.Key: Any] = [
|
||||||
.font: titlebarFont,
|
.foregroundColor: titleForegroundColor,
|
||||||
.foregroundColor: isKeyWindow ? NSColor.labelColor : NSColor.secondaryLabelColor,
|
.font: titlebarFont?.withSize(11) ?? NSFont.systemFont(ofSize: 11),
|
||||||
]
|
]
|
||||||
|
|
||||||
return NSAttributedString(string: title, attributes: attributes)
|
return NSAttributedString(string: title, attributes: attributes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -350,6 +350,26 @@ extension Ghostty {
|
|||||||
return v;
|
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 {
|
var unfocusedSplitOpacity: Double {
|
||||||
guard let config = self.config else { return 1 }
|
guard let config = self.config else { return 1 }
|
||||||
var opacity: Double = 0.85
|
var opacity: Double = 0.85
|
||||||
|
@ -55,6 +55,10 @@ extension Ghostty {
|
|||||||
/// dynamically updated. Otherwise, the background color is the default background color.
|
/// dynamically updated. Otherwise, the background color is the default background color.
|
||||||
@Published private(set) var backgroundColor: Color? = nil
|
@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
|
// An initial size to request for a window. This will only affect
|
||||||
// then the view is moved to a new window.
|
// then the view is moved to a new window.
|
||||||
var initialSize: NSSize? = nil
|
var initialSize: NSSize? = nil
|
||||||
@ -443,6 +447,11 @@ extension Ghostty {
|
|||||||
DispatchQueue.main.async { [weak self] in
|
DispatchQueue.main.async { [weak self] in
|
||||||
self?.backgroundColor = change.color
|
self?.backgroundColor = change.color
|
||||||
}
|
}
|
||||||
|
self.backgroundColor = change.color
|
||||||
|
case .foreground:
|
||||||
|
DispatchQueue.main.async { [weak self] in
|
||||||
|
self?.foregroundColor = change.color
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// We don't do anything for the other colors yet.
|
// We don't do anything for the other colors yet.
|
||||||
@ -1209,6 +1218,7 @@ extension Ghostty {
|
|||||||
struct DerivedConfig {
|
struct DerivedConfig {
|
||||||
let backgroundColor: Color
|
let backgroundColor: Color
|
||||||
let backgroundOpacity: Double
|
let backgroundOpacity: Double
|
||||||
|
let foregroundColor: Color
|
||||||
let macosWindowShadow: Bool
|
let macosWindowShadow: Bool
|
||||||
let windowTitleFontFamily: String?
|
let windowTitleFontFamily: String?
|
||||||
let windowAppearance: NSAppearance?
|
let windowAppearance: NSAppearance?
|
||||||
@ -1216,6 +1226,7 @@ extension Ghostty {
|
|||||||
init() {
|
init() {
|
||||||
self.backgroundColor = Color(NSColor.windowBackgroundColor)
|
self.backgroundColor = Color(NSColor.windowBackgroundColor)
|
||||||
self.backgroundOpacity = 1
|
self.backgroundOpacity = 1
|
||||||
|
self.foregroundColor = Color(NSColor.labelColor)
|
||||||
self.macosWindowShadow = true
|
self.macosWindowShadow = true
|
||||||
self.windowTitleFontFamily = nil
|
self.windowTitleFontFamily = nil
|
||||||
self.windowAppearance = nil
|
self.windowAppearance = nil
|
||||||
@ -1224,6 +1235,7 @@ extension Ghostty {
|
|||||||
init(_ config: Ghostty.Config) {
|
init(_ config: Ghostty.Config) {
|
||||||
self.backgroundColor = config.backgroundColor
|
self.backgroundColor = config.backgroundColor
|
||||||
self.backgroundOpacity = config.backgroundOpacity
|
self.backgroundOpacity = config.backgroundOpacity
|
||||||
|
self.foregroundColor = config.foregroundColor
|
||||||
self.macosWindowShadow = config.macosWindowShadow
|
self.macosWindowShadow = config.macosWindowShadow
|
||||||
self.windowTitleFontFamily = config.windowTitleFontFamily
|
self.windowTitleFontFamily = config.windowTitleFontFamily
|
||||||
self.windowAppearance = .init(ghosttyConfig: config)
|
self.windowAppearance = .init(ghosttyConfig: config)
|
||||||
|
Reference in New Issue
Block a user