macos: transparent titlebar needs to be rehidden when tabs change
parent
7d02977482
commit
6ce7f612a6
|
|
@ -1,24 +1,39 @@
|
||||||
import AppKit
|
import AppKit
|
||||||
|
|
||||||
class TransparentTitlebarTerminalWindow: TerminalWindow {
|
class TransparentTitlebarTerminalWindow: TerminalWindow {
|
||||||
private var debugTimer: Timer?
|
// We need to restore our last synced appearance so that we can reapply
|
||||||
|
// the appearance in certain scenarios.
|
||||||
|
private var lastSurfaceConfig: Ghostty.SurfaceView.DerivedConfig?
|
||||||
|
|
||||||
|
// KVO observations
|
||||||
|
private var tabGroupWindowsObservation: NSKeyValueObservation?
|
||||||
|
|
||||||
override func awakeFromNib() {
|
override func awakeFromNib() {
|
||||||
super.awakeFromNib()
|
super.awakeFromNib()
|
||||||
|
|
||||||
// Debug timer to print view hierarchy every second
|
// We need to observe the tab group because we need to redraw on
|
||||||
debugTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
|
// tabbed window changes and there is no notification for that.
|
||||||
print("=== TransparentTitlebarTerminalWindow Debug ===")
|
setupTabGroupObservation()
|
||||||
self?.contentView?.rootView.printViewHierarchy()
|
|
||||||
print("===============================================\n")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
debugTimer?.invalidate()
|
tabGroupWindowsObservation?.invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func becomeMain() {
|
||||||
|
// On macOS Tahoe, the tab bar redraws and restores non-transparency when
|
||||||
|
// switching tabs. To overcome this, we resync the appearance whenever this
|
||||||
|
// window becomes main (focused).
|
||||||
|
if #available(macOS 26.0, *),
|
||||||
|
let lastSurfaceConfig {
|
||||||
|
syncAppearance(lastSurfaceConfig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Appearance
|
||||||
|
|
||||||
override func syncAppearance(_ surfaceConfig: Ghostty.SurfaceView.DerivedConfig) {
|
override func syncAppearance(_ surfaceConfig: Ghostty.SurfaceView.DerivedConfig) {
|
||||||
|
lastSurfaceConfig = surfaceConfig
|
||||||
if #available(macOS 26.0, *) {
|
if #available(macOS 26.0, *) {
|
||||||
syncAppearanceTahoe(surfaceConfig)
|
syncAppearanceTahoe(surfaceConfig)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -47,6 +62,8 @@ class TransparentTitlebarTerminalWindow: TerminalWindow {
|
||||||
titlebarContainer.layer?.backgroundColor = configBgColor.withAlphaComponent(surfaceConfig.backgroundOpacity).cgColor
|
titlebarContainer.layer?.backgroundColor = configBgColor.withAlphaComponent(surfaceConfig.backgroundOpacity).cgColor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: View Finders
|
||||||
|
|
||||||
private var titlebarBackgroundView: NSView? {
|
private var titlebarBackgroundView: NSView? {
|
||||||
titlebarContainer?.firstDescendant(withClassName: "NSTitlebarBackgroundView")
|
titlebarContainer?.firstDescendant(withClassName: "NSTitlebarBackgroundView")
|
||||||
}
|
}
|
||||||
|
|
@ -76,4 +93,33 @@ class TransparentTitlebarTerminalWindow: TerminalWindow {
|
||||||
private var titlebarContainerView: NSView? {
|
private var titlebarContainerView: NSView? {
|
||||||
contentView?.firstViewFromRoot(withClassName: "NSTitlebarContainerView")
|
contentView?.firstViewFromRoot(withClassName: "NSTitlebarContainerView")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: Tab Group Observation
|
||||||
|
|
||||||
|
private func setupTabGroupObservation() {
|
||||||
|
// Remove existing observation if any
|
||||||
|
tabGroupWindowsObservation?.invalidate()
|
||||||
|
tabGroupWindowsObservation = nil
|
||||||
|
|
||||||
|
// Check if tabGroup is available
|
||||||
|
guard let tabGroup else { return }
|
||||||
|
|
||||||
|
// Set up KVO observation for the windows array. Whenever it changes
|
||||||
|
// we resync the appearance because it can cause macOS to redraw the
|
||||||
|
// tab bar.
|
||||||
|
tabGroupWindowsObservation = tabGroup.observe(
|
||||||
|
\.windows,
|
||||||
|
options: [.new]
|
||||||
|
) { [weak self] _, _ in
|
||||||
|
// NOTE: At one point, I guarded this on only if we went from 0 to N
|
||||||
|
// or N to 0 under the assumption that the tab bar would only get
|
||||||
|
// replaced on those cases. This turned out to be false (Tahoe).
|
||||||
|
// It's cheap enough to always redraw this so we should just do it
|
||||||
|
// unconditionally.
|
||||||
|
|
||||||
|
guard let self else { return }
|
||||||
|
guard let lastSurfaceConfig else { return }
|
||||||
|
self.syncAppearance(lastSurfaceConfig)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -118,6 +118,36 @@ extension NSView {
|
||||||
|
|
||||||
// Add frame info
|
// Add frame info
|
||||||
result += " [frame: \(frame)]"
|
result += " [frame: \(frame)]"
|
||||||
|
|
||||||
|
// Add visual properties
|
||||||
|
var properties: [String] = []
|
||||||
|
|
||||||
|
// Hidden status
|
||||||
|
if isHidden {
|
||||||
|
properties.append("hidden")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Opaque status
|
||||||
|
properties.append(isOpaque ? "opaque" : "transparent")
|
||||||
|
|
||||||
|
// Layer backing
|
||||||
|
if wantsLayer {
|
||||||
|
properties.append("layer-backed")
|
||||||
|
if let bgColor = layer?.backgroundColor {
|
||||||
|
let color = NSColor(cgColor: bgColor)
|
||||||
|
if let rgb = color?.usingColorSpace(.deviceRGB) {
|
||||||
|
properties.append(String(format: "bg:rgba(%.0f,%.0f,%.0f,%.2f)",
|
||||||
|
rgb.redComponent * 255,
|
||||||
|
rgb.greenComponent * 255,
|
||||||
|
rgb.blueComponent * 255,
|
||||||
|
rgb.alphaComponent))
|
||||||
|
} else {
|
||||||
|
properties.append("bg:\(bgColor)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result += " [\(properties.joined(separator: ", "))]"
|
||||||
result += "\n"
|
result += "\n"
|
||||||
|
|
||||||
// Process subviews
|
// Process subviews
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue