mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
Update titlebar tabs when config changes
This commit is contained in:
@ -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
|
||||||
|
@ -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")
|
||||||
|
Reference in New Issue
Block a user