Reveal tray icons only for the moment a menu is opened
Keeping every icon on the menu bar for as long as the panel was open
looked cluttered. Reveal them just long enough to open the clicked
item's menu, then hide again, so the panel stays clean.
💘 Generated with Crush
Assisted-by: Crush:deepseek-v4.1-flash
This commit is contained in:
@@ -134,12 +134,13 @@ preferred position controls where the hidden/kept boundary lands:
|
|||||||
- Verified with `--diag-hide` across positions 100–400. Below ~200 Wi-Fi gets
|
- Verified with `--diag-hide` across positions 100–400. Below ~200 Wi-Fi gets
|
||||||
hidden too; above ~300 app icons (Now Playing, TextInput) leak back.
|
hidden too; above ~300 app icons (Now Playing, TextInput) leak back.
|
||||||
- `debug.spacerPos` (Double) overrides the seed for tuning without rebuilding.
|
- `debug.spacerPos` (Double) overrides the seed for tuning without rebuilding.
|
||||||
- Pressing a tray tile calls `MenuBarItemManager.press` (`kAXPressAction`, then
|
- Pressing a tray tile calls `MenuBarItemManager.press`. A hidden item sits
|
||||||
`kAXShowMenuAction`). Because a hidden item sits off-screen, its menu would
|
off-screen, and its menu would open off-screen too, so press briefly
|
||||||
open off-screen; `AppDelegate` therefore calls `MenuBarHider.suspend()`
|
`suspend()`s the hider, waits ~0.18s for the layout to settle, performs the
|
||||||
(`popoverWillShow`) to reveal items while the panel is open and `resume()`
|
accessibility action (`kAXPressAction`, then `kAXShowMenuAction`), and
|
||||||
0.6s after `popoverDidClose`. `suspend`/`resume` do not change the user's
|
`resume()`s ~1s later. The reveal is momentary, not for the whole time the
|
||||||
`enabled` preference.
|
panel is open. macOS does not let us place or anchor another app's menu
|
||||||
|
anywhere else, so a momentary reveal is the only way to show it in view.
|
||||||
- **Order matters**: `AppDelegate` must call `setupStatusItem()` *before*
|
- **Order matters**: `AppDelegate` must call `setupStatusItem()` *before*
|
||||||
`MenuBarHider.shared.prepare()`. If the hider contracts the bar first, the
|
`MenuBarHider.shared.prepare()`. If the hider contracts the bar first, the
|
||||||
hamburger is created into the collapsed space and lands off-screen.
|
hamburger is created into the collapsed space and lands off-screen.
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ struct SimpleTopBarApp: App {
|
|||||||
/// full menu bar, a brand new item lands under the notch and is invisible;
|
/// full menu bar, a brand new item lands under the notch and is invisible;
|
||||||
/// writing `NSStatusItem Preferred Position <name>` into defaults puts it in a
|
/// writing `NSStatusItem Preferred Position <name>` into defaults puts it in a
|
||||||
/// visible slot, and the autosave name lets the user's own drag persist.
|
/// visible slot, and the autosave name lets the user's own drag persist.
|
||||||
final class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
|
final class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
private var statusItem: NSStatusItem?
|
private var statusItem: NSStatusItem?
|
||||||
private var popover: NSPopover?
|
private var popover: NSPopover?
|
||||||
|
|
||||||
@@ -103,7 +103,6 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
|
|||||||
let popover = NSPopover()
|
let popover = NSPopover()
|
||||||
popover.behavior = .transient
|
popover.behavior = .transient
|
||||||
popover.animates = false
|
popover.animates = false
|
||||||
popover.delegate = self
|
|
||||||
popover.contentViewController = NSHostingController(
|
popover.contentViewController = NSHostingController(
|
||||||
rootView: TopBarView(
|
rootView: TopBarView(
|
||||||
model: AppListModel.shared,
|
model: AppListModel.shared,
|
||||||
@@ -114,20 +113,6 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
|
|||||||
self.popover = popover
|
self.popover = popover
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reveal the hidden menu bar items while the panel is open so pressing a
|
|
||||||
// tray icon opens that app's menu at its real, on-screen position.
|
|
||||||
func popoverWillShow(_ notification: Notification) {
|
|
||||||
MenuBarHider.shared.suspend()
|
|
||||||
}
|
|
||||||
|
|
||||||
func popoverDidClose(_ notification: Notification) {
|
|
||||||
// Small delay so a menu just opened from a tray tile is fully on screen
|
|
||||||
// before the bar contracts again.
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.6) {
|
|
||||||
MenuBarHider.shared.resume()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc private func togglePopover() {
|
@objc private func togglePopover() {
|
||||||
guard let popover, let button = statusItem?.button else { return }
|
guard let popover, let button = statusItem?.button else { return }
|
||||||
if popover.isShown {
|
if popover.isShown {
|
||||||
|
|||||||
@@ -103,10 +103,30 @@ final class MenuBarItemManager: ObservableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Opens the real menu bar item by pressing its accessibility element, so
|
/// Opens the real menu bar item, so its menu/popover appears.
|
||||||
/// its menu/popover appears at its true screen location.
|
///
|
||||||
@discardableResult
|
/// A hidden item sits off-screen, and its menu would open off-screen too.
|
||||||
func press(_ item: MenuBarItem) -> Bool {
|
/// So briefly reveal the items, let the layout settle, press the item, then
|
||||||
|
/// hide again shortly after. The reveal is momentary rather than lasting the
|
||||||
|
/// whole time the panel is open.
|
||||||
|
func press(_ item: MenuBarItem) {
|
||||||
|
let hider = MenuBarHider.shared
|
||||||
|
let needsReveal = hider.isHiding
|
||||||
|
guard needsReveal else {
|
||||||
|
_ = performPress(item)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
hider.suspend()
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.18) {
|
||||||
|
_ = self.performPress(item)
|
||||||
|
// Let the menu finish appearing before the bar contracts again.
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
|
||||||
|
hider.resume()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func performPress(_ item: MenuBarItem) -> Bool {
|
||||||
if AXUIElementPerformAction(item.element, kAXPressAction as CFString) == .success {
|
if AXUIElementPerformAction(item.element, kAXPressAction as CFString) == .success {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user