Compare commits

...

2 commits

Author SHA1 Message Date
maxsoch 20ca403d53 Merge branch 'feature/tips' 2026-07-13 19:04:08 +02:00
maxsoch 19e63f0dcd add a Tips window for the app's less discoverable features
Lists the ⌥ cleanup variant, menu keyboard shortcuts, selective
upgrades, the self-updating cask reinstall behavior and the Logs
reflex. Content is a plain array in TipsWindowController — adding a
tip is one entry. No pbxproj change needed: the project uses Xcode 16
synchronized groups, so new files in BrewBar/ are picked up.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 19:04:08 +02:00
2 changed files with 88 additions and 1 deletions

View file

@ -26,6 +26,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
var logWindow: LogWindowController?
var settingsWindow: SettingsWindowController?
var tipsWindow: TipsWindowController?
var logBuffer: String = ""
var brewPath: String?
@ -105,6 +106,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
menu.addItem(NSMenuItem.separator())
menu.addItem(NSMenuItem(title: "⚙️ Settings…", action: #selector(showSettings), keyEquivalent: ","))
menu.addItem(NSMenuItem(title: "💡 Tips", action: #selector(showTips), keyEquivalent: ""))
versionItem = NSMenuItem(title: "🏷 Brew: ...", action: nil, keyEquivalent: "")
menu.addItem(versionItem)
@ -161,6 +163,13 @@ class AppDelegate: NSObject, NSApplicationDelegate {
presentWindow(of: settingsWindow)
}
@objc func showTips() {
if tipsWindow == nil {
tipsWindow = TipsWindowController()
}
presentWindow(of: tipsWindow)
}
// MARK: - Window presentation
/// Accessory apps never appear in Cmd-Tab and their windows can open
@ -177,7 +186,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
@objc func windowWillClose(_: Notification) {
// async so the closing window's isVisible has flipped to false
DispatchQueue.main.async {
let ourWindows = [self.logWindow?.window, self.settingsWindow?.window]
let ourWindows = [self.logWindow?.window, self.settingsWindow?.window, self.tipsWindow?.window]
let stillOpen = ourWindows.compactMap { $0 }.contains { $0.isVisible }
if !stillOpen {
NSApp.setActivationPolicy(.accessory)

View file

@ -0,0 +1,78 @@
import Cocoa
/// Static "good to know" window for the features a menu can't explain
/// by itself. Content lives in the tips array adding one there is all
/// it takes.
class TipsWindowController: NSWindowController {
let tips: [(title: String, body: String)] = [
("⌥ reveals hidden menu items",
"With the menu open, hold Option (⌥): Cleanup becomes Cleanup (purge cache), which also empties Homebrew's download cache (brew cleanup --prune=all). Downloads are simply re-fetched when next needed."),
("Keyboard shortcuts",
"While the menu is open: R refreshes, U upgrades formulae & casks, L opens the logs, comma (,) opens Settings and Q quits."),
("Upgrade only one kind",
"When both formulae and casks are outdated, an “Upgrade only…” submenu appears under the main upgrade item to handle just one of the two."),
("Self-updating apps",
"Apps that update themselves (browsers, editors…) can drift from what brew installed, and brew then refuses a normal upgrade. BrewBar detects this and automatically reinstalls the cask instead — you may see reinstall --cask --force in the logs; that is expected."),
("When something looks off, read the Logs",
"Every brew command BrewBar runs is in the Logs window with its full output — errors, warnings and all. It is the first place to look."),
]
convenience init() {
let window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 440, height: 300),
styleMask: [.titled, .closable],
backing: .buffered,
defer: false
)
window.title = "BrewBar Tips"
// the controller keeps the window alive; without this, closing the
// window would deallocate it and reopening would crash
window.isReleasedWhenClosed = false
self.init(window: window)
buildUI()
window.center()
}
func buildUI() {
guard let contentView = window?.contentView else { return }
let tipViews: [NSView] = tips.map { tip in
let title = NSTextField(labelWithString: tip.title)
title.font = .boldSystemFont(ofSize: NSFont.systemFontSize)
let body = NSTextField(wrappingLabelWithString: tip.body)
body.font = .systemFont(ofSize: NSFont.smallSystemFontSize)
body.textColor = .secondaryLabelColor
body.preferredMaxLayoutWidth = 400
let tipStack = NSStackView(views: [title, body])
tipStack.orientation = .vertical
tipStack.alignment = .leading
tipStack.spacing = 4
return tipStack
}
let stack = NSStackView(views: tipViews)
stack.orientation = .vertical
stack.alignment = .leading
stack.spacing = 16
stack.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(stack)
NSLayoutConstraint.activate([
stack.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 20),
stack.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
stack.trailingAnchor.constraint(lessThanOrEqualTo: contentView.trailingAnchor, constant: -20),
stack.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -20),
])
contentView.layoutSubtreeIfNeeded()
window?.setContentSize(contentView.fittingSize)
}
}