mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-01-01 03:02:15 +00:00
macos: keep glass titlebar inset in sync when tab bar appears (#10105)
Discussion: https://github.com/ghostty-org/ghostty/discussions/10104 Summary: When background blur uses macOS glass styles, the titlebar becomes fully transparent after opening a new tab. This updates the glass effect view’s top inset during layout so the glass layer continues to cover the titlebar area. Root Cause: The glass effect view’s top constraint is computed once using the theme frame’s safe-area top inset. On macOS 26, opening a new tab changes the titlebar height/safe-area, but the constraint is never refreshed. That leaves an uncovered strip at the top, which appears fully transparent. Fix: Track the glass view’s top constraint and update its constant on layout. This keeps the glass layer aligned with the current safe-area inset while avoiding unnecessary reconfiguration. Tests: - Manual: `zig build run`, open a new tab with `background-opacity < 1` and `background-blur = macos-glass-regular` and confirm titlebar is no longer fully transparent. - Note: automated tests not added; UI behavior is hard to exercise in existing test suite. Window position question: Opening a new tab seems to reset the window position / trigger a maximize-like behavior on my system. Is this intended (feature) or a bug? I did not change this behavior in this PR. AI Assistance: This change was implemented with AI assistance (Codex) and reviewed by me.
This commit is contained in:
@@ -8,6 +8,7 @@ class TerminalViewContainer<ViewModel: TerminalViewModel>: NSView {
|
||||
|
||||
/// Glass effect view for liquid glass background when transparency is enabled
|
||||
private var glassEffectView: NSView?
|
||||
private var glassTopConstraint: NSLayoutConstraint?
|
||||
private var derivedConfig: DerivedConfig
|
||||
|
||||
init(ghostty: Ghostty.App, viewModel: ViewModel, delegate: (any TerminalViewDelegate)? = nil) {
|
||||
@@ -54,6 +55,12 @@ class TerminalViewContainer<ViewModel: TerminalViewModel>: NSView {
|
||||
override func viewDidMoveToWindow() {
|
||||
super.viewDidMoveToWindow()
|
||||
updateGlassEffectIfNeeded()
|
||||
updateGlassEffectTopInsetIfNeeded()
|
||||
}
|
||||
|
||||
override func layout() {
|
||||
super.layout()
|
||||
updateGlassEffectTopInsetIfNeeded()
|
||||
}
|
||||
|
||||
@objc private func ghosttyConfigDidChange(_ notification: Notification) {
|
||||
@@ -74,6 +81,7 @@ private extension TerminalViewContainer {
|
||||
@available(macOS 26.0, *)
|
||||
func addGlassEffectViewIfNeeded() -> NSGlassEffectView? {
|
||||
if let existed = glassEffectView as? NSGlassEffectView {
|
||||
updateGlassEffectTopInsetIfNeeded()
|
||||
return existed
|
||||
}
|
||||
guard let themeFrameView = window?.contentView?.superview else {
|
||||
@@ -82,12 +90,18 @@ private extension TerminalViewContainer {
|
||||
let effectView = NSGlassEffectView()
|
||||
addSubview(effectView, positioned: .below, relativeTo: terminalView)
|
||||
effectView.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
effectView.topAnchor.constraint(equalTo: topAnchor, constant: -themeFrameView.safeAreaInsets.top),
|
||||
effectView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||
effectView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
||||
effectView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||
])
|
||||
glassTopConstraint = effectView.topAnchor.constraint(
|
||||
equalTo: topAnchor,
|
||||
constant: -themeFrameView.safeAreaInsets.top
|
||||
)
|
||||
if let glassTopConstraint {
|
||||
NSLayoutConstraint.activate([
|
||||
glassTopConstraint,
|
||||
effectView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||
effectView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
||||
effectView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||
])
|
||||
}
|
||||
glassEffectView = effectView
|
||||
return effectView
|
||||
}
|
||||
@@ -98,6 +112,7 @@ private extension TerminalViewContainer {
|
||||
guard #available(macOS 26.0, *), derivedConfig.backgroundBlur.isGlassStyle else {
|
||||
glassEffectView?.removeFromSuperview()
|
||||
glassEffectView = nil
|
||||
glassTopConstraint = nil
|
||||
return
|
||||
}
|
||||
guard let effectView = addGlassEffectViewIfNeeded() else {
|
||||
@@ -120,6 +135,17 @@ private extension TerminalViewContainer {
|
||||
#endif // compiler(>=6.2)
|
||||
}
|
||||
|
||||
func updateGlassEffectTopInsetIfNeeded() {
|
||||
#if compiler(>=6.2)
|
||||
guard #available(macOS 26.0, *), derivedConfig.backgroundBlur.isGlassStyle else {
|
||||
return
|
||||
}
|
||||
guard glassEffectView != nil else { return }
|
||||
guard let themeFrameView = window?.contentView?.superview else { return }
|
||||
glassTopConstraint?.constant = -themeFrameView.safeAreaInsets.top
|
||||
#endif // compiler(>=6.2)
|
||||
}
|
||||
|
||||
struct DerivedConfig: Equatable {
|
||||
var backgroundOpacity: Double = 0
|
||||
var backgroundBlur: Ghostty.Config.BackgroundBlur
|
||||
|
||||
Reference in New Issue
Block a user