mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
Move un-zoom button into the tab/toolbar
This commit is contained in:
@ -3,6 +3,31 @@ import Cocoa
|
||||
import SwiftUI
|
||||
import GhosttyKit
|
||||
|
||||
fileprivate class ZoomButtonView: NSView {
|
||||
let target: Any
|
||||
let action: Selector
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
init(frame frameRect: NSRect, target: Any, selector: Selector) {
|
||||
self.target = target
|
||||
self.action = selector
|
||||
|
||||
super.init(frame: frameRect)
|
||||
|
||||
let zoomButton = NSButton(image: NSImage(systemSymbolName: "arrow.down.right.and.arrow.up.left.square.fill", accessibilityDescription: nil)!, target: target, action: selector)
|
||||
|
||||
zoomButton.frame = bounds
|
||||
zoomButton.isBordered = false
|
||||
zoomButton.contentTintColor = .systemBlue
|
||||
zoomButton.state = .on
|
||||
zoomButton.imageScaling = .scaleProportionallyUpOrDown
|
||||
addSubview(zoomButton)
|
||||
}
|
||||
}
|
||||
|
||||
/// The terminal controller is an NSWindowController that maps 1:1 to a terminal window.
|
||||
class TerminalController: NSWindowController, NSWindowDelegate,
|
||||
TerminalViewDelegate, TerminalViewModel,
|
||||
@ -54,8 +79,10 @@ class TerminalController: NSWindowController, NSWindowDelegate,
|
||||
/// This is set to false by init if the window managed by this controller should not be restorable.
|
||||
/// For example, terminals executing custom scripts are not restorable.
|
||||
private var restorable: Bool = true
|
||||
|
||||
init(_ ghostty: Ghostty.App,
|
||||
|
||||
private var surfaceIsZoomed: Bool = false
|
||||
|
||||
init(_ ghostty: Ghostty.App,
|
||||
withBaseConfig base: Ghostty.SurfaceConfiguration? = nil,
|
||||
withSurfaceTree tree: Ghostty.SplitNode? = nil
|
||||
) {
|
||||
@ -120,13 +147,13 @@ class TerminalController: NSWindowController, NSWindowDelegate,
|
||||
func relabelTabs() {
|
||||
// Reset this to false. It'll be set back to true later.
|
||||
tabListenForFrame = false
|
||||
|
||||
|
||||
guard let windows = self.window?.tabbedWindows else { return }
|
||||
|
||||
// We only listen for frame changes if we have more than 1 window,
|
||||
// otherwise the accessory view doesn't matter.
|
||||
tabListenForFrame = windows.count > 1
|
||||
|
||||
|
||||
for (index, window) in windows.enumerated().prefix(9) {
|
||||
let action = "goto_tab:\(index + 1)"
|
||||
guard let equiv = ghostty.config.keyEquivalent(for: action) else {
|
||||
@ -141,10 +168,25 @@ class TerminalController: NSWindowController, NSWindowDelegate,
|
||||
let text = NSTextField(labelWithAttributedString: attributedString)
|
||||
text.setContentCompressionResistancePriority(.windowSizeStayPut, for: .horizontal)
|
||||
text.postsFrameChangedNotifications = true
|
||||
window.tab.accessoryView = text
|
||||
|
||||
let stackView = NSStackView(views: [text])
|
||||
// stackView.setHuggingPriority(.defaultHigh, for: .horizontal)
|
||||
|
||||
window.tab.accessoryView = stackView
|
||||
}
|
||||
|
||||
if surfaceIsZoomed {
|
||||
guard let stackView = window?.tabGroup?.selectedWindow?.tab.accessoryView as? NSStackView else { return }
|
||||
|
||||
var zoomButton: ZoomButtonView = ZoomButtonView(frame: NSRect(x: 0, y: 0, width: 20, height: 20), target: self, selector: #selector(splitZoom(_:)))
|
||||
|
||||
zoomButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
zoomButton.widthAnchor.constraint(equalToConstant: 20).isActive = true
|
||||
zoomButton.heightAnchor.constraint(equalToConstant: 20).isActive = true
|
||||
stackView.addArrangedSubview(zoomButton)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func fixTabBar() {
|
||||
// We do this to make sure that the tab bar will always re-composite. If we don't,
|
||||
// then the it will "drag" pieces of the background with it when a transparent
|
||||
@ -200,7 +242,17 @@ class TerminalController: NSWindowController, NSWindowDelegate,
|
||||
leaf.surface.focusDidChange(focused)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func windowDidUpdate(_ notification: Notification) {
|
||||
updateToolbarZoomButton()
|
||||
}
|
||||
|
||||
private func updateToolbarZoomButton() {
|
||||
guard let itemView = window?.toolbar?.items.last?.view as? ZoomButtonView else { return }
|
||||
|
||||
itemView.alphaValue = surfaceIsZoomed ? 1 : 0
|
||||
}
|
||||
|
||||
//MARK: - NSWindowController
|
||||
|
||||
override func windowWillLoad() {
|
||||
@ -305,6 +357,11 @@ class TerminalController: NSWindowController, NSWindowDelegate,
|
||||
window.tabGroup?.removeWindow(window)
|
||||
}
|
||||
}
|
||||
|
||||
guard let toolbarZoomItem = window.toolbar?.items.last else { return }
|
||||
var zoomButton: ZoomButtonView = ZoomButtonView(frame: NSRect(x: 0, y: 0, width: 20, height: 20), target: self, selector: #selector(splitZoom(_:)))
|
||||
|
||||
toolbarZoomItem.view = zoomButton
|
||||
}
|
||||
|
||||
// Shows the "+" button in the tab bar, responds to that click.
|
||||
@ -594,7 +651,13 @@ class TerminalController: NSWindowController, NSWindowDelegate,
|
||||
// we want to invalidate our state.
|
||||
invalidateRestorableState()
|
||||
}
|
||||
|
||||
|
||||
func zoomStateDidChange(to: Bool) {
|
||||
self.surfaceIsZoomed = to
|
||||
updateToolbarZoomButton()
|
||||
relabelTabs()
|
||||
}
|
||||
|
||||
//MARK: - Clipboard Confirmation
|
||||
|
||||
func clipboardConfirmationComplete(_ action: ClipboardConfirmationView.Action, _ request: Ghostty.ClipboardRequest) {
|
||||
|
@ -1,5 +1,9 @@
|
||||
import Cocoa
|
||||
|
||||
fileprivate extension NSToolbarItem.Identifier {
|
||||
static let zoom = NSToolbarItem.Identifier("zoom")
|
||||
}
|
||||
|
||||
// Custom NSToolbar subclass that displays a centered window title,
|
||||
// in order to accommodate the titlebar tabs feature.
|
||||
class TerminalToolbar: NSToolbar, NSToolbarDelegate {
|
||||
@ -31,32 +35,37 @@ class TerminalToolbar: NSToolbar, NSToolbarDelegate {
|
||||
func toolbar(_ toolbar: NSToolbar,
|
||||
itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier,
|
||||
willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? {
|
||||
guard itemIdentifier == Self.identifier else {
|
||||
return NSToolbarItem(itemIdentifier: itemIdentifier)
|
||||
var item: NSToolbarItem
|
||||
|
||||
switch itemIdentifier {
|
||||
case Self.identifier:
|
||||
item = NSToolbarItem(itemIdentifier: itemIdentifier)
|
||||
item.view = self.titleTextField
|
||||
item.visibilityPriority = .user
|
||||
|
||||
// NSToolbarItem.minSize and NSToolbarItem.maxSize are deprecated, and make big ugly
|
||||
// warnings in Xcode when you use them, but I cannot for the life of me figure out
|
||||
// how to get this to work with constraints. The behavior isn't the same, instead of
|
||||
// shrinking the item and clipping the subview, it hides the item as soon as the
|
||||
// intrinsic size of the subview gets too big for the toolbar width, regardless of
|
||||
// whether I have constraints set on its width, height, or both :/
|
||||
//
|
||||
// If someone can fix this so we don't have to use deprecated properties: Please do.
|
||||
item.minSize = NSSize(width: 32, height: 1)
|
||||
item.maxSize = NSSize(width: 1024, height: self.titleTextField.intrinsicContentSize.height)
|
||||
|
||||
item.isEnabled = true
|
||||
case .zoom:
|
||||
item = NSToolbarItem(itemIdentifier: NSToolbarItem.Identifier("zoom"))
|
||||
default:
|
||||
item = NSToolbarItem(itemIdentifier: itemIdentifier)
|
||||
}
|
||||
|
||||
let toolbarItem = NSToolbarItem(itemIdentifier: itemIdentifier)
|
||||
toolbarItem.view = self.titleTextField
|
||||
toolbarItem.visibilityPriority = .user
|
||||
|
||||
// NSToolbarItem.minSize and NSToolbarItem.maxSize are deprecated, and make big ugly
|
||||
// warnings in Xcode when you use them, but I cannot for the life of me figure out
|
||||
// how to get this to work with constraints. The behavior isn't the same, instead of
|
||||
// shrinking the item and clipping the subview, it hides the item as soon as the
|
||||
// intrinsic size of the subview gets too big for the toolbar width, regardless of
|
||||
// whether I have constraints set on its width, height, or both :/
|
||||
//
|
||||
// If someone can fix this so we don't have to use deprecated properties: Please do.
|
||||
toolbarItem.minSize = NSSize(width: 32, height: 1)
|
||||
toolbarItem.maxSize = NSSize(width: 1024, height: self.titleTextField.intrinsicContentSize.height)
|
||||
|
||||
toolbarItem.isEnabled = true
|
||||
|
||||
return toolbarItem
|
||||
|
||||
return item
|
||||
}
|
||||
|
||||
func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
|
||||
return [Self.identifier, .space]
|
||||
return [Self.identifier, .space, .zoom]
|
||||
}
|
||||
|
||||
func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
|
||||
@ -64,7 +73,7 @@ class TerminalToolbar: NSToolbar, NSToolbarDelegate {
|
||||
// getting smaller than the max size so starts clipping. Lucky for us, three of the
|
||||
// built-in spacers seems to exactly match the space on the left that's reserved for
|
||||
// the window buttons.
|
||||
return [Self.identifier, .space, .space, .space]
|
||||
return [Self.identifier, .space, .space, .zoom]
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,8 @@ protocol TerminalViewDelegate: AnyObject {
|
||||
/// The surface tree did change in some way, i.e. a split was added, removed, etc. This is
|
||||
/// not called initially.
|
||||
func surfaceTreeDidChange()
|
||||
|
||||
func zoomStateDidChange(to: Bool)
|
||||
}
|
||||
|
||||
// Default all the functions so they're optional
|
||||
@ -24,6 +26,7 @@ extension TerminalViewDelegate {
|
||||
func focusedSurfaceDidChange(to: Ghostty.SurfaceView?) {}
|
||||
func titleDidChange(to: String) {}
|
||||
func cellSizeDidChange(to: NSSize) {}
|
||||
func zoomStateDidChange(to: Bool) {}
|
||||
}
|
||||
|
||||
/// The view model is a required implementation for TerminalView callers. This contains
|
||||
@ -64,12 +67,6 @@ struct TerminalView<ViewModel: TerminalViewModel>: View {
|
||||
}
|
||||
}
|
||||
|
||||
if let zoomedSplit = zoomedSplit {
|
||||
if zoomedSplit {
|
||||
title = "🔍 " + title
|
||||
}
|
||||
}
|
||||
|
||||
return title
|
||||
}
|
||||
|
||||
@ -107,6 +104,9 @@ struct TerminalView<ViewModel: TerminalViewModel>: View {
|
||||
// in the hash value.
|
||||
self.delegate?.surfaceTreeDidChange()
|
||||
}
|
||||
.onChange(of: zoomedSplit) { newValue in
|
||||
self.delegate?.zoomStateDidChange(to: newValue ?? false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user