diff options
Diffstat (limited to 'menubar/panel.swift')
| -rw-r--r-- | menubar/panel.swift | 160 |
1 files changed, 83 insertions, 77 deletions
diff --git a/menubar/panel.swift b/menubar/panel.swift index e6ff569..2b3cad2 100644 --- a/menubar/panel.swift +++ b/menubar/panel.swift @@ -2,10 +2,10 @@ // // - Never takes focus: a non-activating panel, so typing stays in the // terminal. Visible on every Space and over full-screen apps. -// - Modes: off, on ("Always"), auto ("When agents are waiting"). The global -// shortcut shows or hides it on top of the mode. +// - On or off (the global shortcut and the menu toggle it; off stays off), +// and while on: shown always, or only when agents are waiting. // - Layout: compact (waiting agents in full, working and idle as one summary -// line); in "Always" mode a toggle switches to the full list. +// line); when shown always, a toggle switches to the full list. // - Always dark: herdr's mocha palette with fixed colors on a dark tint, so // text reads the same over any desktop. @@ -13,52 +13,58 @@ import AppKit // MARK: visibility (pure, tested by --selftest) -enum PanelMode: String, CaseIterable { - case off, on, auto +/// What the panel shows while it is on. +enum PanelPolicy: String, CaseIterable { + case always, whenWaiting var label: String { switch self { - case .off: return "Off" - case .on: return "Always" - case .auto: return "When agents are waiting" + case .always: return "Always" + case .whenWaiting: return "Only When Agents Are Waiting" } } } +/// Two persistent settings and one fact: +/// - enabled: the panel is on (the shortcut and the menu toggle it); +/// - policy: while on, show it always or only when agents are waiting; +/// - waiting: whether any agent is blocked or done. +/// Off stays off: nothing shows the panel until it is turned back on. struct PanelVisibility: Equatable { - var mode: PanelMode = .auto - /// Set by the shortcut: true = shown, false = hidden, nil = follow mode. - var forced: Bool? - /// Pane ids of agents currently blocked or done. - var waiting: Set<String> = [] - - var visible: Bool { - forced ?? (mode == .on || (mode == .auto && !waiting.isEmpty)) + static let peekSeconds: TimeInterval = 3 + + var enabled = true + var policy: PanelPolicy = .whenWaiting + var anyWaiting = false + /// Turning the panel on with nothing to show shows it briefly, so the + /// shortcut visibly did something. + var peekUntil: Date? + + func visible(at now: Date) -> Bool { + guard enabled else { return false } + if policy == .always || anyWaiting { return true } + return peekUntil.map { now < $0 } ?? false } - /// The shortcut: flip what is on screen right now. - mutating func toggle() { - forced = !visible + mutating func toggle(at now: Date) { + enabled.toggle() + peekUntil = enabled ? now.addingTimeInterval(Self.peekSeconds) : nil } - mutating func setMode(_ newMode: PanelMode) { - mode = newMode - forced = nil - } - - /// A newly waiting agent cancels a manual hide (it needs you), except in - /// mode off, where nothing shows unless asked for. - mutating func update(waiting newWaiting: Set<String>) { - if forced == false, !newWaiting.subtracting(waiting).isEmpty { - forced = nil + /// Settings from before the on/off split: a three-way mode. + static func migrated(fromMode mode: String) -> PanelVisibility { + switch mode { + case "off": return PanelVisibility(enabled: false, policy: .whenWaiting) + case "on": return PanelVisibility(enabled: true, policy: .always) + default: return PanelVisibility(enabled: true, policy: .whenWaiting) } - waiting = newWaiting } } -/// The full list is offered only in "Always" mode; otherwise compact. -func panelShowsFull(mode: PanelMode, prefersFull: Bool) -> Bool { - mode == .on && prefersFull +/// The full list is offered only when the panel shows always; otherwise +/// compact. +func panelShowsFull(policy: PanelPolicy, prefersFull: Bool) -> Bool { + policy == .always && prefersFull } // MARK: content (pure, printed by --print-panel) @@ -75,7 +81,7 @@ struct PanelContent { let working: Int let idle: Int let full: Bool - /// Whether the panel offers the compact/full toggle ("Always" mode). + /// Whether the panel offers the compact/full toggle (shown always). let canToggle: Bool init(rows all: [Row], full: Bool = false, canToggle: Bool = false) { @@ -451,49 +457,49 @@ func panelSelftest() -> [String] { if !condition { failures.append(message) } } - var auto = PanelVisibility(mode: .auto) - check(!auto.visible, "auto: hidden with nothing waiting") - auto.update(waiting: ["a"]) - check(auto.visible, "auto: shown when an agent waits") - auto.toggle() - check(!auto.visible, "auto: shortcut hides") - auto.update(waiting: ["a"]) - check(!auto.visible, "auto: stays hidden for the same waiting agents") - auto.update(waiting: ["a", "b"]) - check(auto.visible, "auto: a newly waiting agent shows it again") - auto.update(waiting: []) - check(!auto.visible, "auto: hides when nothing waits") - auto.toggle() - check(auto.visible, "auto: shortcut shows it with nothing waiting") - auto.update(waiting: []) - check(auto.visible, "auto: a manual show stays") - auto.toggle() - check(!auto.visible, "auto: shortcut hides again") - - var off = PanelVisibility(mode: .off) - off.update(waiting: ["a"]) - check(!off.visible, "off: hidden even when an agent waits") - off.toggle() - check(off.visible, "off: shortcut shows") - off.toggle() - off.update(waiting: ["a", "b"]) - check(!off.visible, "off: a new waiting agent does not show it") - - var on = PanelVisibility(mode: .on) - check(on.visible, "on: shown with nothing waiting") - on.toggle() - check(!on.visible, "on: shortcut hides") - on.update(waiting: ["a"]) - check(on.visible, "on: a newly waiting agent shows it again") - - var changed = PanelVisibility(mode: .auto, forced: false, waiting: ["a"]) - changed.setMode(.on) - check(changed.visible && changed.forced == nil, "mode change clears the shortcut override") - - check(!panelShowsFull(mode: .auto, prefersFull: true), "layout: compact when agents are waiting") - check(!panelShowsFull(mode: .off, prefersFull: true), "layout: compact when shown by the shortcut in off") - check(panelShowsFull(mode: .on, prefersFull: true), "layout: full in always when chosen") - check(!panelShowsFull(mode: .on, prefersFull: false), "layout: compact in always by default") + let t0 = Date(timeIntervalSince1970: 1_000) + func at(_ seconds: TimeInterval) -> Date { t0.addingTimeInterval(seconds) } + + var panel = PanelVisibility() + check(panel.enabled && panel.policy == .whenWaiting, "defaults: on, only when agents are waiting") + check(!panel.visible(at: t0), "waiting policy: hidden with nothing waiting") + panel.anyWaiting = true + check(panel.visible(at: t0), "waiting policy: shown when an agent waits") + panel.toggle(at: t0) + check(!panel.visible(at: t0), "off: the shortcut hides it") + panel.anyWaiting = false + panel.anyWaiting = true + check(!panel.visible(at: at(60)), "off: stays hidden when another agent starts waiting") + panel.toggle(at: at(60)) + check(panel.visible(at: at(60)), "on again: shown, an agent is waiting") + panel.anyWaiting = false + check(!panel.visible(at: at(70)), "waiting policy: hides when nothing waits") + + var peek = PanelVisibility(enabled: false, policy: .whenWaiting) + peek.toggle(at: t0) + check(peek.visible(at: at(1)), "turned on with nothing waiting: shown briefly") + check(!peek.visible(at: at(PanelVisibility.peekSeconds + 0.1)), "the brief show ends after the peek") + peek.toggle(at: at(10)) + check(!peek.visible(at: at(10)) && peek.peekUntil == nil, "turned off: no peek") + + var always = PanelVisibility(enabled: true, policy: .always) + check(always.visible(at: t0), "always: shown with nothing waiting") + always.toggle(at: t0) + always.anyWaiting = true + check(!always.visible(at: at(5)), "always, off: an agent waiting does not show it") + always.policy = .whenWaiting + check(!always.enabled, "changing what it shows keeps it off") + + check(PanelVisibility.migrated(fromMode: "off") == PanelVisibility(enabled: false, policy: .whenWaiting), + "migration: off") + check(PanelVisibility.migrated(fromMode: "on") == PanelVisibility(enabled: true, policy: .always), + "migration: always") + check(PanelVisibility.migrated(fromMode: "auto") == PanelVisibility(enabled: true, policy: .whenWaiting), + "migration: when waiting") + + check(!panelShowsFull(policy: .whenWaiting, prefersFull: true), "layout: compact when only waiting") + check(panelShowsFull(policy: .always, prefersFull: true), "layout: full in always when chosen") + check(!panelShowsFull(policy: .always, prefersFull: false), "layout: compact in always by default") func row(_ id: String, _ status: Status) -> Row { Row(paneId: id, agent: "pi", name: id, status: status, session: "s", window: id, since: 1) |
