Make things work with really dark backgrounds

This fixes issue #1549
This commit is contained in:
Pete Schaffner
2024-04-02 22:28:34 +02:00
parent bbe35ee02e
commit f086bff651
2 changed files with 133 additions and 65 deletions

View File

@ -80,18 +80,6 @@ class TerminalWindow: NSWindow {
_ = bindings _ = bindings
// By hiding the visual effect view, we allow the window's (or titlebar's in this case)
// background color to show through. If we were to set `titlebarAppearsTransparent` to true
// the selected tab would look fine, but the unselected ones and new tab button backgrounds
// would be an opaque color. When the titlebar isn't transparent, however, the system applies
// a compositing effect to the unselected tab backgrounds, which makes them blend with the
// titlebar's/window's background.
if let titlebarContainer = contentView?.superview?.subviews.first(where: {
$0.className == "NSTitlebarContainerView"
}), let effectView = titlebarContainer.descendants(withClassName: "NSVisualEffectView").first {
effectView.isHidden = true
}
// Create the tab accessory view that houses the key-equivalent label and optional un-zoom button // Create the tab accessory view that houses the key-equivalent label and optional un-zoom button
let stackView = NSStackView(views: [keyEquivalentLabel, resetZoomTabButton]) let stackView = NSStackView(views: [keyEquivalentLabel, resetZoomTabButton])
stackView.setHuggingPriority(.defaultHigh, for: .horizontal) stackView.setHuggingPriority(.defaultHigh, for: .horizontal)
@ -131,25 +119,81 @@ class TerminalWindow: NSWindow {
resetZoomToolbarButton.contentTintColor = .tertiaryLabelColor resetZoomToolbarButton.contentTintColor = .tertiaryLabelColor
} }
override func layoutIfNeeded() {
super.layoutIfNeeded()
guard titlebarTabs else { return }
// We need to be aggressive with this, and it has to be done as well in `update`,
// otherwise things can get out of sync and flickering can occur.
updateTabsForVeryDarkBackgrounds()
}
// We only need to set this once, but need to do it after the window has been created in order
// to determine if the theme is using a very dark background, in which case we don't want to
// remove the effect view if the default tab bar is being used since the effect created in
// `updateTabsForVeryDarkBackgrounds`.
private var effectViewIsHidden = false
override func update() { override func update() {
super.update() super.update()
updateResetZoomTitlebarButtonVisibility()
titlebarSeparatorStyle = tabbedWindows != nil && !titlebarTabs ? .line : .none titlebarSeparatorStyle = tabbedWindows != nil && !titlebarTabs ? .line : .none
if !effectViewIsHidden {
// By hiding the visual effect view, we allow the window's (or titlebar's in this case)
// background color to show through. If we were to set `titlebarAppearsTransparent` to true
// the selected tab would look fine, but the unselected ones and new tab button backgrounds
// would be an opaque color. When the titlebar isn't transparent, however, the system applies
// a compositing effect to the unselected tab backgrounds, which makes them blend with the
// titlebar's/window's background.
if let titlebarContainer = contentView?.superview?.subviews.first(where: {
$0.className == "NSTitlebarContainerView"
}), let effectView = titlebarContainer.descendants(withClassName: "NSVisualEffectView").first {
effectView.isHidden = titlebarTabs || !titlebarTabs && !hasVeryDarkBackground
}
effectViewIsHidden = true
}
if titlebarTabs {
updateTabsForVeryDarkBackgrounds()
// This is called when we open, close, switch, and reorder tabs, at which point we determine if the // This is called when we open, close, switch, and reorder tabs, at which point we determine if the
// first tab in the tab bar is selected. If it is, we make the `windowButtonsBackdrop` color the same // first tab in the tab bar is selected. If it is, we make the `windowButtonsBackdrop` color the same
// as that of the active tab (i.e. the titlebar's background color), otherwise we make it the same // as that of the active tab (i.e. the titlebar's background color), otherwise we make it the same
// color as the background of unselected tabs. // color as the background of unselected tabs.
if let index = windowController?.window?.tabbedWindows?.firstIndex(of: self), titlebarTabs { if let index = windowController?.window?.tabbedWindows?.firstIndex(of: self) {
windowButtonsBackdrop?.isHighlighted = index == 0 windowButtonsBackdrop?.isHighlighted = index == 0
} }
}
updateNewTabButtonOpacity()
updateNewTabButtonImage()
updateResetZoomTitlebarButtonVisibility()
}
// MARK: -
private var newTabButtonImageLayer: VibrantLayer? = nil
// Since we are coloring the new tab button's image, it doesn't respond to the
// window's key status changes in terms of becoming less prominent visually,
// so we need to do it manually.
private func updateNewTabButtonOpacity() {
guard let titlebarContainer = contentView?.superview?.subviews.first(where: {
$0.className == "NSTitlebarContainerView"
}) else { return }
guard let newTabButton: NSButton = titlebarContainer.firstDescendant(withClassName: "NSTabBarNewTabButton") as? NSButton else { return }
guard let newTabButtonImageView: NSImageView = newTabButton.subviews.first(where: {
$0 as? NSImageView != nil
}) as? NSImageView else { return }
newTabButtonImageView.alphaValue = isKeyWindow ? 1 : 0.5
}
// Color the new tab button's image to match the color of the tab title/keyboard shortcut labels, // Color the new tab button's image to match the color of the tab title/keyboard shortcut labels,
// just as it does in the stock tab bar. // just as it does in the stock tab bar.
updateNewTabButtonOpacity() private func updateNewTabButtonImage() {
guard let titlebarContainer = contentView?.superview?.subviews.first(where: { guard let titlebarContainer = contentView?.superview?.subviews.first(where: {
$0.className == "NSTitlebarContainerView" $0.className == "NSTitlebarContainerView"
}) else { return } }) else { return }
@ -186,23 +230,28 @@ class TerminalWindow: NSWindow {
newTabButtonImageView.frame = newTabButton.bounds newTabButtonImageView.frame = newTabButton.bounds
} }
// MARK: - var hasVeryDarkBackground: Bool {
backgroundColor.luminance < 0.05
}
private var newTabButtonImageLayer: VibrantLayer? = nil lazy var backgroundColorWithOpacity: NSColor = backgroundColor.withAlphaComponent(titlebarOpacity)
private func updateTabsForVeryDarkBackgrounds() {
guard hasVeryDarkBackground else { return }
// Since we are coloring the new tab button's image, it doesn't respond to the
// window's key status changes in terms of becoming less prominent visually,
// so we need to do it manually.
private func updateNewTabButtonOpacity() {
guard let titlebarContainer = contentView?.superview?.subviews.first(where: { guard let titlebarContainer = contentView?.superview?.subviews.first(where: {
$0.className == "NSTitlebarContainerView" $0.className == "NSTitlebarContainerView"
}) else { return } }) else { return }
guard let newTabButton: NSButton = titlebarContainer.firstDescendant(withClassName: "NSTabBarNewTabButton") as? NSButton else { return }
guard let newTabButtonImageView: NSImageView = newTabButton.subviews.first(where: {
$0 as? NSImageView != nil
}) as? NSImageView else { return }
newTabButtonImageView.alphaValue = isKeyWindow ? 1 : 0.5 if let tabGroup = tabGroup, tabGroup.isTabBarVisible {
guard let activeTabBackgroundView = titlebarContainer.firstDescendant(withClassName: "NSTabButton")?.superview?.subviews.last?.firstDescendant(withID: "_backgroundView")
else { return }
activeTabBackgroundView.layer?.backgroundColor = backgroundColorWithOpacity.cgColor
titlebarContainer.layer?.backgroundColor = backgroundColorWithOpacity.highlight(withLevel: 0.14)?.cgColor
} else {
titlebarContainer.layer?.backgroundColor = backgroundColorWithOpacity.cgColor
}
} }
private func updateResetZoomTitlebarButtonVisibility() { private func updateResetZoomTitlebarButtonVisibility() {
@ -407,9 +456,7 @@ class TerminalWindow: NSWindow {
return return
} }
let backdropColor = backgroundColor.withAlphaComponent(titlebarOpacity).usingColorSpace(colorSpace!)!.cgColor let view = WindowButtonsBackdropView(window: self)
let view = WindowButtonsBackdropView(backgroundColor: backdropColor)
view.identifier = NSUserInterfaceItemIdentifier("_windowButtonsBackdrop") view.identifier = NSUserInterfaceItemIdentifier("_windowButtonsBackdrop")
titlebarView.addSubview(view) titlebarView.addSubview(view)
@ -491,8 +538,9 @@ fileprivate class WindowDragView: NSView {
// A view that matches the color of selected and unselected tabs in the adjacent tab bar. // A view that matches the color of selected and unselected tabs in the adjacent tab bar.
fileprivate class WindowButtonsBackdropView: NSView { fileprivate class WindowButtonsBackdropView: NSView {
private let overlayLayer = VibrantLayer() private let terminalWindow: TerminalWindow
private let isLightTheme: Bool private let isLightTheme: Bool
private let overlayLayer = VibrantLayer()
var isHighlighted: Bool = true { var isHighlighted: Bool = true {
didSet { didSet {
@ -500,8 +548,14 @@ fileprivate class WindowButtonsBackdropView: NSView {
overlayLayer.isHidden = isHighlighted overlayLayer.isHidden = isHighlighted
layer?.backgroundColor = .clear layer?.backgroundColor = .clear
} else { } else {
let systemOverlayColor = NSColor(cgColor: CGColor(genericGrayGamma2_2Gray: 0.0, alpha: 0.45))!
let titlebarBackgroundColor = terminalWindow.backgroundColorWithOpacity.blended(withFraction: 1, of: systemOverlayColor)
let highlightedColor = terminalWindow.hasVeryDarkBackground ? terminalWindow.backgroundColor : .clear
let backgroundColor = terminalWindow.hasVeryDarkBackground ? titlebarBackgroundColor : systemOverlayColor
overlayLayer.isHidden = true overlayLayer.isHidden = true
layer?.backgroundColor = isHighlighted ? .clear : CGColor(genericGrayGamma2_2Gray: 0.0, alpha: 0.45) layer?.backgroundColor = isHighlighted ? highlightedColor?.cgColor : backgroundColor?.cgColor
} }
} }
} }
@ -510,8 +564,9 @@ fileprivate class WindowButtonsBackdropView: NSView {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
init(backgroundColor: CGColor) { init(window: TerminalWindow) {
self.isLightTheme = NSColor(cgColor: backgroundColor)!.isLightColor self.terminalWindow = window
self.isLightTheme = window.backgroundColor.isLightColor
super.init(frame: .zero) super.init(frame: .zero)

View File

@ -28,4 +28,17 @@ extension NSView {
return result return result
} }
/// Recursively finds and returns the first descendant view that has the given identifier.
func firstDescendant(withID id: String) -> NSView? {
for subview in subviews {
if subview.identifier == NSUserInterfaceItemIdentifier(id) {
return subview
} else if let found = subview.firstDescendant(withID: id) {
return found
}
}
return nil
}
} }