Update titlebar tabs when config changes

This commit is contained in:
Pete Schaffner
2024-04-05 16:40:04 +02:00
parent 4ede25dd00
commit b947ed0070
2 changed files with 109 additions and 106 deletions

View File

@ -179,7 +179,7 @@ class TerminalController: NSWindowController, NSWindowDelegate,
window.backgroundColor = backgroundColor window.backgroundColor = backgroundColor
window.titlebarColor = backgroundColor.withAlphaComponent(ghostty.config.backgroundOpacity) window.titlebarColor = backgroundColor.withAlphaComponent(ghostty.config.backgroundOpacity)
window.updateToolbar() window.updateTabBar()
} }
/// Update all surfaces with the focus state. This ensures that libghostty has an accurate view about /// Update all surfaces with the focus state. This ensures that libghostty has an accurate view about

View File

@ -1,7 +1,6 @@
import Cocoa import Cocoa
class TerminalWindow: NSWindow { class TerminalWindow: NSWindow {
@objc dynamic var surfaceIsZoomed: Bool = false
@objc dynamic var keyEquivalent: String = "" @objc dynamic var keyEquivalent: String = ""
lazy var titlebarColor: NSColor = backgroundColor { lazy var titlebarColor: NSColor = backgroundColor {
@ -15,32 +14,6 @@ class TerminalWindow: NSWindow {
} }
} }
private lazy var resetZoomToolbarButton: NSButton = generateResetZoomButton()
private lazy var resetZoomTabButton: NSButton = {
let button = generateResetZoomButton()
button.action = #selector(selectTabAndZoom(_:))
return button
}()
private lazy var resetZoomTitlebarAccessoryViewController: NSTitlebarAccessoryViewController? = {
guard let titlebarContainer = contentView?.superview?.subviews.first(where: { $0.className == "NSTitlebarContainerView" }) else { return nil }
let size = NSSize(width: titlebarContainer.bounds.height, height: titlebarContainer.bounds.height)
let view = NSView(frame: NSRect(origin: .zero, size: size))
let button = generateResetZoomButton()
button.frame.origin.x = size.width/2 - button.bounds.width/2
button.frame.origin.y = size.height/2 - button.bounds.height/2
view.addSubview(button)
let titlebarAccessoryViewController = NSTitlebarAccessoryViewController()
titlebarAccessoryViewController.view = view
titlebarAccessoryViewController.layoutAttribute = .right
return titlebarAccessoryViewController
}()
private lazy var keyEquivalentLabel: NSTextField = { private lazy var keyEquivalentLabel: NSTextField = {
let label = NSTextField(labelWithAttributedString: NSAttributedString()) let label = NSTextField(labelWithAttributedString: NSAttributedString())
label.setContentCompressionResistancePriority(.windowSizeStayPut, for: .horizontal) label.setContentCompressionResistancePriority(.windowSizeStayPut, for: .horizontal)
@ -103,6 +76,12 @@ class TerminalWindow: NSWindow {
} }
} }
// 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 becomeKey() { override func becomeKey() {
// This is required because the removeTitlebarAccessoryViewController hook does not // This is required because the removeTitlebarAccessoryViewController hook does not
// catch the creation of a new window by "tearing off" a tab from a tabbed window. // catch the creation of a new window by "tearing off" a tab from a tabbed window.
@ -137,12 +116,6 @@ class TerminalWindow: NSWindow {
updateTabsForVeryDarkBackgrounds() 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()
@ -180,16 +153,36 @@ class TerminalWindow: NSWindow {
updateResetZoomTitlebarButtonVisibility() updateResetZoomTitlebarButtonVisibility()
} }
// MARK: - override func updateConstraintsIfNeeded() {
super.updateConstraintsIfNeeded()
func updateToolbar() { if titlebarTabs {
newTabButtonImageLayer = nil hideTitleBarSeparators()
effectViewIsHidden = false }
}
// MARK: - Tab Bar Styling
var hasVeryDarkBackground: Bool {
backgroundColor.luminance < 0.05
} }
private var newTabButtonImage: NSImage? = nil private var newTabButtonImage: NSImage? = nil
private var newTabButtonImageLayer: VibrantLayer? = nil private var newTabButtonImageLayer: VibrantLayer? = nil
func updateTabBar() {
newTabButtonImageLayer = nil
effectViewIsHidden = false
if titlebarTabs {
guard let tabBarAccessoryViewController = titlebarAccessoryViewControllers.first(where: { $0.identifier == Self.TabBarController}) else { return }
tabBarAccessoryViewController.layoutAttribute = .right
pushTabsToTitlebar(tabBarAccessoryViewController)
}
}
// Since we are coloring the new tab button's image, it doesn't respond to the // 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, // window's key status changes in terms of becoming less prominent visually,
// so we need to do it manually. // so we need to do it manually.
@ -249,10 +242,6 @@ class TerminalWindow: NSWindow {
newTabButtonImageView.frame = newTabButton.bounds newTabButtonImageView.frame = newTabButton.bounds
} }
var hasVeryDarkBackground: Bool {
backgroundColor.luminance < 0.05
}
private func updateTabsForVeryDarkBackgrounds() { private func updateTabsForVeryDarkBackgrounds() {
guard hasVeryDarkBackground else { return } guard hasVeryDarkBackground else { return }
@ -271,7 +260,35 @@ class TerminalWindow: NSWindow {
} }
} }
// MARK: - Split zooming // MARK: - Split Zoom Button
@objc dynamic var surfaceIsZoomed: Bool = false
private lazy var resetZoomToolbarButton: NSButton = generateResetZoomButton()
private lazy var resetZoomTabButton: NSButton = {
let button = generateResetZoomButton()
button.action = #selector(selectTabAndZoom(_:))
return button
}()
private lazy var resetZoomTitlebarAccessoryViewController: NSTitlebarAccessoryViewController? = {
guard let titlebarContainer = contentView?.superview?.subviews.first(where: { $0.className == "NSTitlebarContainerView" }) else { return nil }
let size = NSSize(width: titlebarContainer.bounds.height, height: titlebarContainer.bounds.height)
let view = NSView(frame: NSRect(origin: .zero, size: size))
let button = generateResetZoomButton()
button.frame.origin.x = size.width/2 - button.bounds.width/2
button.frame.origin.y = size.height/2 - button.bounds.height/2
view.addSubview(button)
let titlebarAccessoryViewController = NSTitlebarAccessoryViewController()
titlebarAccessoryViewController.view = view
titlebarAccessoryViewController.layoutAttribute = .right
return titlebarAccessoryViewController
}()
private func updateResetZoomTitlebarButtonVisibility() { private func updateResetZoomTitlebarButtonVisibility() {
guard let tabGroup, let resetZoomTitlebarAccessoryViewController else { return } guard let tabGroup, let resetZoomTitlebarAccessoryViewController else { return }
@ -325,17 +342,7 @@ class TerminalWindow: NSWindow {
windowController.splitZoom(self) windowController.splitZoom(self)
} }
// MARK: - Titlebar Tabs // MARK: - Titlebar Font
// Used by the window controller to enable/disable titlebar tabs.
var titlebarTabs = false {
didSet {
self.titleVisibility = titlebarTabs ? .hidden : .visible
if titlebarTabs {
generateToolbar()
}
}
}
// Used to set the titlebar font. // Used to set the titlebar font.
var titlebarFont: NSFont? { var titlebarFont: NSFont? {
@ -349,24 +356,6 @@ class TerminalWindow: NSWindow {
} }
} }
// We have to regenerate a toolbar when the titlebar tabs setting changes since our
// custom toolbar conditionally generates the items based on this setting. I tried to
// invalidate the toolbar items and force a refresh, but as far as I can tell that
// isn't possible.
func generateToolbar() {
let terminalToolbar = TerminalToolbar(identifier: "Toolbar")
toolbar = terminalToolbar
toolbarStyle = .unifiedCompact
if let resetZoomItem = terminalToolbar.items.first(where: { $0.itemIdentifier == .resetZoom }) {
resetZoomItem.view = resetZoomToolbarButton
resetZoomItem.view!.removeConstraints(resetZoomItem.view!.constraints)
resetZoomItem.view!.widthAnchor.constraint(equalToConstant: 22).isActive = true
resetZoomItem.view!.heightAnchor.constraint(equalToConstant: 20).isActive = true
}
updateResetZoomTitlebarButtonVisibility()
}
// Find the NSTextField responsible for displaying the titlebar's title. // Find the NSTextField responsible for displaying the titlebar's title.
private var titlebarTextField: NSTextField? { private var titlebarTextField: NSTextField? {
guard let titlebarContainer = contentView?.superview?.subviews guard let titlebarContainer = contentView?.superview?.subviews
@ -387,23 +376,46 @@ class TerminalWindow: NSWindow {
return NSAttributedString(string: title, attributes: attributes) return NSAttributedString(string: title, attributes: attributes)
} }
// MARK: - Titlebar Tabs
private var windowButtonsBackdrop: WindowButtonsBackdropView? = nil private var windowButtonsBackdrop: WindowButtonsBackdropView? = nil
private var windowDragHandle: WindowDragView? = nil private var windowDragHandle: WindowDragView? = nil
// The tab bar controller ID from macOS // The tab bar controller ID from macOS
static private let TabBarController = NSUserInterfaceItemIdentifier("_tabBarController") static private let TabBarController = NSUserInterfaceItemIdentifier("_tabBarController")
override func updateConstraintsIfNeeded() { // Used by the window controller to enable/disable titlebar tabs.
super.updateConstraintsIfNeeded() var titlebarTabs = false {
didSet {
self.titleVisibility = titlebarTabs ? .hidden : .visible
if titlebarTabs {
generateToolbar()
}
}
}
hideTitleBarSeparators() // We have to regenerate a toolbar when the titlebar tabs setting changes since our
// custom toolbar conditionally generates the items based on this setting. I tried to
// invalidate the toolbar items and force a refresh, but as far as I can tell that
// isn't possible.
func generateToolbar() {
let terminalToolbar = TerminalToolbar(identifier: "Toolbar")
toolbar = terminalToolbar
toolbarStyle = .unifiedCompact
if let resetZoomItem = terminalToolbar.items.first(where: { $0.itemIdentifier == .resetZoom }) {
resetZoomItem.view = resetZoomToolbarButton
resetZoomItem.view!.removeConstraints(resetZoomItem.view!.constraints)
resetZoomItem.view!.widthAnchor.constraint(equalToConstant: 22).isActive = true
resetZoomItem.view!.heightAnchor.constraint(equalToConstant: 20).isActive = true
}
updateResetZoomTitlebarButtonVisibility()
} }
// For titlebar tabs, we want to hide the separator view so that we get rid // For titlebar tabs, we want to hide the separator view so that we get rid
// of an aesthetically unpleasing shadow. // of an aesthetically unpleasing shadow.
private func hideTitleBarSeparators() { private func hideTitleBarSeparators() {
guard titlebarTabs else { return }
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 }
@ -490,23 +502,14 @@ class TerminalWindow: NSWindow {
// new tabs or expand existing tabs to fill the empty space after one is closed, the centering // new tabs or expand existing tabs to fill the empty space after one is closed, the centering
// of the tab titles can't be properly calculated, so we wait for 0.2 seconds and then mark // of the tab titles can't be properly calculated, so we wait for 0.2 seconds and then mark
// the entire view hierarchy for the tab bar as dirty to fix the positioning... // the entire view hierarchy for the tab bar as dirty to fix the positioning...
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { // DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
self.markHierarchyForLayout(accessoryView) // self.markHierarchyForLayout(accessoryView)
} // }
} }
private func addWindowButtonsBackdrop(titlebarView: NSView, toolbarView: NSView) { private func addWindowButtonsBackdrop(titlebarView: NSView, toolbarView: NSView) {
// If we already made the view, just make sure it's unhidden and correctly placed as a subview. windowButtonsBackdrop?.removeFromSuperview()
if let view = windowButtonsBackdrop { windowButtonsBackdrop = nil
view.removeFromSuperview()
view.isHidden = false
titlebarView.addSubview(view)
view.leftAnchor.constraint(equalTo: toolbarView.leftAnchor).isActive = true
view.rightAnchor.constraint(equalTo: toolbarView.leftAnchor, constant: 78).isActive = true
view.topAnchor.constraint(equalTo: toolbarView.topAnchor).isActive = true
view.heightAnchor.constraint(equalTo: toolbarView.heightAnchor).isActive = true
return
}
let view = WindowButtonsBackdropView(window: self) let view = WindowButtonsBackdropView(window: self)
view.identifier = NSUserInterfaceItemIdentifier("_windowButtonsBackdrop") view.identifier = NSUserInterfaceItemIdentifier("_windowButtonsBackdrop")