Add a hover badge and switcher for stacked windows#1795
Draft
MyronKoch wants to merge 9 commits into
Draft
Conversation
When multiple windows are stacked at the same grid position, resting the cursor on that position's top-left corner shows a small count badge and a list of the stacked window names. Clicking a name brings that window forward. Off by default (stackBadge), with a checkbox in the additional settings popover. Detection is stateless: a 200ms timer reads the mouse position, and only a 250ms dwell on a computed grid corner triggers one fresh window-server query (CGWindowList, which cannot block on unresponsive apps). AX is touched only to read the titles of matched windows, off the main thread with a messaging timeout, and discarded if the cursor has moved on. No window state is cached. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Window gaps place a cell's window up to a full gap away from the geometric grid corner, and the cascade offset - applied in AppKit coordinates - converts to an upward move in AX space, so the original corner-anchored, downward-only match never saw a complete stack. Candidates are now gathered from a gap-widened box, clustered around the leftmost origin with a y-symmetric cascade range (extracted into StackBadgeGeometry for testability), and the badge anchors on the stack's visual top-left extremity. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Bump the request generation on every dismiss so an in-flight title fetch can't resurrect the badge after the feature is disabled or the screens change - Resolve click-to-focus by pid on the background queue with AX messaging timeouts, instead of going through the unsynchronized shared window-list cache - Recompute corner points per dwell instead of caching them: adjustedVisibleFrame depends on inputs (Todo mode, Stage Manager, edge-gap defaults) that don't post screen-change notifications - Pick the densest cascade cluster instead of anchoring on the leftmost candidate, so an unrelated left neighbor can't mask a stack - Fetch AX titles once per application rather than once per window - Restart or stop the badge timer when a config import changes the stackBadge default - Clamp the name list to the screen so every row stays clickable - Align the settings checkbox with its neighbors Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Enlarge the hover catch zone and shorten the dwell for a snappier, easier-to-hit trigger - Show each stacked window's app icon beside its name, and drop the now redundant app-name prefix from the title - Highlight rows with the system selection color and white text, like a native menu - Enlarge the count badge and size it to its contents - Drop the badge and list below the title-bar band so the front window's traffic lights stay clickable - Add a keep-alive corridor from the trigger down to the list so moving onto it doesn't dismiss it Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Multi-model review (GPT adversarial + Opus architecture) findings: - Route row clicks reliably: override hitTest so clicks on the icon and title subviews reach the row instead of being swallowed, and override acceptsFirstMouse so the first click on the background non-activating panel focuses the window instead of being eaten as an activation click. - Bound the keep-alive corridor to the gap between the peek and the list's top edge, so a list clamped to the screen bottom no longer turns the corridor into a tall sticky column that keeps the overlay alive far from the actual UI. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Make the badge read as a native element per macOS HIG: - The count badge is now a vibrancy capsule using the same .hudWindow material as the list, with an SF Symbol stack glyph (guarded for macOS 11+ like the menu icons) instead of a Unicode character. - Continuous (squircle) corner curves on the badge, list, and row highlights; SF Pro throughout. - Count and symbol use semantic labelColor so they stay legible in both Light and Dark mode (the .hudWindow material follows the appearance). - Open the list below the badge with a real gap so they never overlap, and increase the title-bar clearance so both clear the traffic lights. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A tall stack pinned near the screen bottom clamped the list's origin upward, which could push its top back over the badge. Pin the list top below the badge and cap its height to the space above the screen bottom so it only ever grows downward. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Bigger pill (28pt), count (15pt), and stack symbol (14pt) so the badge fills the space above the list and reads as a deliberate anchor rather than floating low, and clears the extra title-bar clearance cleanly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Clicking a row focuses that window but no longer dismisses the list, so you can click through the stacked windows one after another without re-opening it. The list still dismisses when the cursor leaves the overlay; clicks keep working while another app is frontmost thanks to the rows' first-mouse handling and the non-activating panel. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Owner
|
This looks awesome! Looking forward to testing this out! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-on to #1762 (grid-position overlap offset). On that PR you mentioned looking forward to the hover count badge idea as a follow-on — here it is.
Dark mode
Light mode
What it does
When the overlap offset stacks multiple windows at the same grid position, resting the cursor on the stack's top-left corner shows a small count badge and a list of the stacked windows — each with its app icon and title. Click a name to bring that window forward.
It turns overlap from something you can't see into a lightweight switcher for that grid slot. On a laptop or small screen where you can't spread windows out, a stack of tiles plus this becomes a de-facto tab bar for the spot.
Opt-in and unobtrusive
stackBadgehidden default, with a checkbox ("Show stacked window badge on hover") in the General-tab additional-settings popover next to the overlap-offset toggle, and an entry inTerminalCommands.md.How it works (stateless)
No window state is cached, so nothing can go stale across window moves, closes, Spaces, Stage Manager, or sleep/wake. A lightweight timer reads the cursor position; only a brief dwell on a grid corner (computed from screen geometry alone) triggers a single window-server query via
CGWindowListCopyWindowInfo, which can't block on an unresponsive app. Accessibility is touched only to read the matched windows' titles, on a background queue with a messaging timeout.Testing
Screenshots
Light mode
(drag stack-badge-light.png here)
Dark mode
(drag stack-badge-dark.png here)