Add Sparkle auto-update flow and titlebar update UI
This commit is contained in:
parent
a158c744ea
commit
e3ee246930
19 changed files with 1813 additions and 80 deletions
135
Sources/WindowToolbarController.swift
Normal file
135
Sources/WindowToolbarController.swift
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
import AppKit
|
||||
import SwiftUI
|
||||
|
||||
final class WindowToolbarController: NSObject, NSToolbarDelegate {
|
||||
private let commandItemIdentifier = NSToolbarItem.Identifier("cmux.focusedCommand")
|
||||
private let updateItemIdentifier = NSToolbarItem.Identifier("cmux.updatePill")
|
||||
|
||||
private weak var tabManager: TabManager?
|
||||
private weak var updateViewModel: UpdateViewModel?
|
||||
|
||||
private var commandLabels: [ObjectIdentifier: NSTextField] = [:]
|
||||
private var observers: [NSObjectProtocol] = []
|
||||
|
||||
init(updateViewModel: UpdateViewModel) {
|
||||
self.updateViewModel = updateViewModel
|
||||
super.init()
|
||||
}
|
||||
|
||||
deinit {
|
||||
for observer in observers {
|
||||
NotificationCenter.default.removeObserver(observer)
|
||||
}
|
||||
}
|
||||
|
||||
func start(tabManager: TabManager) {
|
||||
self.tabManager = tabManager
|
||||
attachToExistingWindows()
|
||||
installObservers()
|
||||
updateFocusedCommandText()
|
||||
}
|
||||
|
||||
private func installObservers() {
|
||||
let center = NotificationCenter.default
|
||||
observers.append(center.addObserver(
|
||||
forName: .ghosttyDidSetTitle,
|
||||
object: nil,
|
||||
queue: .main
|
||||
) { [weak self] _ in
|
||||
self?.updateFocusedCommandText()
|
||||
})
|
||||
|
||||
observers.append(center.addObserver(
|
||||
forName: .ghosttyDidFocusTab,
|
||||
object: nil,
|
||||
queue: .main
|
||||
) { [weak self] _ in
|
||||
self?.updateFocusedCommandText()
|
||||
})
|
||||
|
||||
observers.append(center.addObserver(
|
||||
forName: NSWindow.didBecomeMainNotification,
|
||||
object: nil,
|
||||
queue: .main
|
||||
) { [weak self] notification in
|
||||
guard let window = notification.object as? NSWindow else { return }
|
||||
self?.attach(to: window)
|
||||
})
|
||||
}
|
||||
|
||||
private func attachToExistingWindows() {
|
||||
for window in NSApp.windows {
|
||||
attach(to: window)
|
||||
}
|
||||
}
|
||||
|
||||
private func attach(to window: NSWindow) {
|
||||
guard window.toolbar == nil else { return }
|
||||
let toolbar = NSToolbar(identifier: NSToolbar.Identifier("cmux.toolbar"))
|
||||
toolbar.delegate = self
|
||||
toolbar.displayMode = .iconOnly
|
||||
toolbar.allowsUserCustomization = false
|
||||
toolbar.autosavesConfiguration = false
|
||||
toolbar.showsBaselineSeparator = false
|
||||
window.toolbar = toolbar
|
||||
window.toolbarStyle = .unified
|
||||
window.titleVisibility = .visible
|
||||
}
|
||||
|
||||
private func updateFocusedCommandText() {
|
||||
guard let tabManager else { return }
|
||||
let text: String
|
||||
if let selectedId = tabManager.selectedTabId,
|
||||
let tab = tabManager.tabs.first(where: { $0.id == selectedId }) {
|
||||
let title = tab.title.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
text = title.isEmpty ? "Cmd: —" : "Cmd: \(title)"
|
||||
} else {
|
||||
text = "Cmd: —"
|
||||
}
|
||||
|
||||
for label in commandLabels.values {
|
||||
label.stringValue = text
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - NSToolbarDelegate
|
||||
|
||||
func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
|
||||
[commandItemIdentifier, .flexibleSpace, updateItemIdentifier]
|
||||
}
|
||||
|
||||
func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
|
||||
[commandItemIdentifier, .flexibleSpace, updateItemIdentifier]
|
||||
}
|
||||
|
||||
func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? {
|
||||
if itemIdentifier == commandItemIdentifier {
|
||||
let item = NSToolbarItem(itemIdentifier: itemIdentifier)
|
||||
let label = NSTextField(labelWithString: "Cmd: —")
|
||||
label.font = NSFont.systemFont(ofSize: 12, weight: .medium)
|
||||
label.textColor = .secondaryLabelColor
|
||||
label.lineBreakMode = .byTruncatingMiddle
|
||||
label.setContentHuggingPriority(.defaultHigh, for: .horizontal)
|
||||
item.view = label
|
||||
commandLabels[ObjectIdentifier(toolbar)] = label
|
||||
updateFocusedCommandText()
|
||||
return item
|
||||
}
|
||||
|
||||
if itemIdentifier == updateItemIdentifier, let updateViewModel {
|
||||
let item = NSToolbarItem(itemIdentifier: itemIdentifier)
|
||||
let view = NonDraggableHostingView(rootView: UpdatePill(
|
||||
model: updateViewModel,
|
||||
showWhenIdle: true,
|
||||
onIdleTap: {
|
||||
guard let delegate = NSApp.delegate as? AppDelegate else { return }
|
||||
delegate.checkForUpdates(nil)
|
||||
}
|
||||
))
|
||||
item.view = view
|
||||
return item
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue