Skip to content

Fold the Windows notch to a pill when not in use, as the Mac's Show on hover does - #288

Merged
vinzdg merged 5 commits into
vinzdg:mainfrom
RawJat:feat/windows-show-on-hover
Sep 20, 2026
Merged

vinzdg merged 5 commits into
vinzdg:mainfrom
RawJat:feat/windows-show-on-hover

Conversation

@RawJat

@RawJat RawJat commented Sep 19, 2026 •

Copy link
Copy Markdown
Contributor

The Mac's Show setting has three modes and Windows had two. The one missing was the Mac's default, Show on hover: the notch rests as a small pill at the edge and opens the moment the pointer reaches it.

The fold. Folded, the notch is the Mac's resting pill, NotchLayout.pillWidth × pillHeight — 10 × 79 here, in the same unscaled measures the orb uses. The open pill keeps its whole layout and a clip-path draws it down to that, so everything measured off it (the card's anchor, the handles, which ring is under the pointer) stays true while folded. A thin outline arrives with it, since a black pill on a black wallpaper is otherwise nothing. The rings leave by fading and sliding toward the edge one after another, the Mac's NotchMotion.stagger, and the handles fade with them.

When. It opens on contact and folds 0.45 s after the pointer has gone (foldGrace). What wakes it is the pill's length along the edge and its depth plus the Mac's pillHotZone, 34 px, into the screen. The pointer watchdog reports the crossing from the transition that already switches click-through, because once click-through is back on the page is sent nothing. It never folds mid-carry, mid-drag or under the right-click menu, which all take the pointer off the notch on purpose.

Keep open in the right-click menu is the Mac's own shortcut (onToggleKeepOpen): it switches between Always show and Show on hover, the same setting from another place, and an open Settings window follows it.

Defaults. A fresh install starts on Show on hover, as on the Mac. A config saved before the setting existed keeps the always-open notch it had, so nobody's notch starts folding on an update. Settings gains the third button with the Mac's wording and its ru, zh-Hans, zh-Hant, ja and uk translations; the Mac has no Korean, so those strings are new and worth a native eye.

The rings take the Mac's share of the body, 44 in 70. With the weekly ring set to Outside, the outer ring came within 2.8 px of the screen edge. The Mac keeps it "far enough in that the bezel is never touched", and has room to because its ring is 44 pt in a 70 pt body; ours was 56 in 70. The arcs scale with the ring from their viewBox, the glyphs take the same factor, and the outside ring now sits about 10 px clear, as on the Mac. The percentage labels keep their size.

Testing: cargo test 105 pass, 0 fail, 2 ignored, including a new test that only a fresh install starts on hover. Clippy unchanged. All three pages parse.

Alongside #283 and #239. #283, opened yesterday, reaches for the same thing, and #239's Auto-hide is nearby. The differences are in how closely each follows the Mac: this one folds to the Mac's own resting pill rather than sliding the whole pill off the edge to a strip; it adds the Mac's third mode to the existing Show row rather than a second row beside it; it starts on hover only for a fresh install, so an update does not change a notch someone is already using; it keeps the Mac's guards against folding mid-carry, mid-drag or under the right-click menu, and its Keep open; and it carries the Mac's translations. They will conflict in the same places, so it is one or the other — happy to bring anything across from either, or to fold this into theirs, whichever you would rather keep.

Depends on #276 and #287, and is built on top of both; it should go in after them. #269 moves these files to desktop/, and #274 adds Russian to settings.html.

Preview

Screen.Recording.2026-09-20.003257.mp4

A plain press on the pill plus 4 px of movement dragged the window
anywhere, and letting go snapped it to the nearest edge of whichever
monitor it was over, saving one position that every edge shared. Since
vinzdg#244 a click on a ring refreshes it, so a click that slipped moved the
notch instead (vinzdg#251); and since vinzdg#246 the move handle exists to change
edge, so the drag was doing its job too.

The drag now needs Alt held, and without it every press on the pill is a
click. It follows the pointer along the edge the notch is on and nowhere
else, inside the work area, and never changes edge: the move handle and
the Edge picker in Settings do that. It is the Mac's ⌥-drag
(NotchWindowController.dragged), which only ever moves alongOffset.

Each edge now remembers its own place, as the Mac keeps notchOffset per
edge. A move to another edge lands where the notch was last left on that
edge, centred if it has never been slid along it, and Recentre centres
only the edge it is on. A carry used to land centred on purpose, because
every edge shared one position and a carry dragged the old edge's
fraction along with it; with a place per edge that reason has gone.

The place is kept as a fraction of the edge rather than the Mac's points,
so an existing notch_y becomes the place for the edge it was saved on
without needing a screen to convert it, and a change of resolution keeps
the notch in the same part of the edge. notch_y is read once and never
written again.

One thing is lost: dragging no longer takes the notch to another monitor.
The Screen picker in Settings does, as on the Mac, and its caption no
longer claims the drag can. The drag hint is rewritten in the Mac's own
words, with its ru, zh-Hans, zh-Hant, ja and uk translations taken from
Localizable.xcstrings and ⌥ read as Alt; the Mac has no Korean, so that
sentence is new. The Ukrainian hint had been keyed to an older English
sentence and never showed; it does now.
The move handle only ever offered the edges of one monitor. begin_move
took the notch's screen once, read every target against it, and on
release wrote the edge alone, so a pointer carried onto a second monitor
still picked an edge of the first, and the overlay stayed behind — drawn
there at the wrong size, since its figures belonged to the other screen.

The zones now follow the pointer. Whichever screen it is over, the overlay
moves there, the edge is read against that screen's work area, and
letting go writes the screen as well as the edge; the right edge of
another screen is a move too, though the edge has the same name. Over the
gap a shorter screen leaves beside a taller one, the carry keeps the
screen it was last over rather than flicking between them.

With the drag held to its own edge, this is the one gesture left that
takes the notch to another monitor; the Screen picker in Settings is the
other way. The Mac's handle is single-screen — beginMove takes
currentScreen() once — so this goes past it rather than porting it.

The overlay is pinned in physical pixels, and pinned again if Windows
resizes it for a new scale after the move, as place_notch does for the
notch. On a screen at another scale that resize and its correction would
both play out as the zones shrinking and growing again, so the overlay is
hidden for the crossing and shown a couple of frames after the new zones
are sent. It is also made unfocusable, like the notch: shown again on
every screen crossed, a window that could take focus would take it from
whatever the user was working in.

The silhouette is in logical pixels, so it keeps its size on a screen at
another scale, the size the notch will land at. Screens are told apart by
where they are rather than by name, which the platform is not obliged to
report.
Arriving on a monitor at another scale, Windows resizes the notch by the
ratio of the two scales before place_notch puts it right, and the page
then re-zooms itself for the new pixel ratio a debounce later, which can
bring one more zoom correction from report_dpr. All of it played out on
screen: a notch carried to another monitor jumped sizes as it landed.

Hiding the window for the landing does not help. A hidden WebView2 stops
painting and throttles its timers, so the page only re-laid itself out
once it was shown again, in plain view — however long the delay before
showing it.

So the window stays up and the page empties itself instead: the window
is transparent, so an empty page is an invisible notch, while the WebView
keeps doing its layout. Rust waits for the page to confirm it has painted
empty before moving it, and reveals it on the first report_dpr from the
end of a burst of resizes that needs no further correction — the layout
has stopped changing — rather than after a guessed delay. A fallback
shows it after 700 ms whatever happens, numbered so it can never reveal a
later landing early.

At the same scale nothing is resized on arrival, so nothing is hidden.
Every other placement stays in sight as before.
The Mac's Show setting has three modes and Windows had two. The one it
was missing is the Mac's default, Show on hover: the notch rests as a
small pill at the edge and opens the moment the pointer reaches it.

Folded, the notch is the Mac's resting pill — NotchLayout.pillWidth by
pillHeight, 10 × 79 here in the same unscaled measures the orb uses. The
open pill keeps its whole layout and a clip-path draws it down to that,
so everything measured off it (the card's anchor, the handles, which ring
is under the pointer) stays true; a thin outline arrives with it, since a
black pill on a black wallpaper is otherwise nothing. The rings leave by
fading and sliding toward the edge one after another, NotchMotion's
stagger, and the handles fade with them.

It opens on contact and folds 0.45 s after the pointer has gone
(foldGrace). What wakes it is the pill's length along the edge and its
depth plus the Mac's pillHotZone, 34 px, into the screen — the pill is
small, the place that opens it should not be. The pointer watchdog says
when it crosses on or off, from the transition that already switches
click-through, because once click-through is back on the page is sent
nothing. It never folds mid-carry, mid-drag or under the right-click
menu, all of which take the pointer off the notch on purpose.

Keep open in the right-click menu is the Mac's own shortcut
(onToggleKeepOpen): it switches between Always show and Show on hover,
the same setting from another place, and Settings follows it if open.

A fresh install starts on Show on hover, as on the Mac. A config saved
before the setting existed keeps the always-open notch it had: its owner
never chose a folding notch, and an update is not where they should meet
one. Settings gains the third button with the Mac's wording and its
ru, zh-Hans, zh-Hant, ja and uk translations; the Mac has no Korean, so
those strings are new.
With the weekly ring set to Outside, the outer ring came within 2.8 px of
the screen edge. The Mac places that ring deliberately — "past the track,
into the margin the notch keeps between a ring and its bezel … far enough
in that the bezel is never touched", with NotchLayoutTests pinning both —
and it has room to, because its ring is 44 pt in a 70 pt body. Ours was
56 in 70, which left 7 px of margin in all, so no radius could give the
outside ring any real clearance.

The ring now takes the Mac's ratio. The arcs are drawn in a 56-unit
viewBox and scale on their own; the glyphs are sized in px, so they take
the same factor and the ring shrinks as one piece. The outside ring's
radius was already the Mac's, scaled to the ring, and it now lands about
10 px clear, which is the Mac's clearance too. The percentage labels keep
their size. The pill gets shorter, and the window sizes still hold five
rings, which their tests now measure at 44.
@RawJat
RawJat requested a review from vinzdg as a code owner September 19, 2026 19:01
@vinzdg
vinzdg merged commit faf85b2 into vinzdg:main Sep 20, 2026
4 checks passed
@vinzdg

vinzdg commented Sep 20, 2026

Copy link
Copy Markdown
Owner

Merged as faf85b2. This is the best parity of the three auto-hide attempts: a third mode on the existing Show row rather than a second control, the Mac's foldGrace/pillHotZone/stagger, keep_open in the notch menu, and guards against folding mid-carry, mid-drag and under the menu. keep_open_on_upgrade reading the raw JSON to tell a fresh install from an upgrade is a nice touch — existing notches stay open, which is what I'd want. Thanks @RawJat.

I'd flagged f9d5b27 (rings 56→44 in 70) as unrelated scope and was going to strip it. I checked it before doing so and kept it: Sources/Notch/NotchLayout.swift:52 has ringDiameter = Design.px(117) // 44pt, the design spec's anchor, with sideRingMargin derived from it, so your ratio is the Mac's and the 2.8 px bezel clearance on the outside weekly ring was a real defect. Your reasoning was right and mine was wrong.

Left as-is: the mousemove handler in notch.html sets pointerIn from a DOM event while Rust's notch_pointer is the authority. Harmless today because Rust always emits the false transition, but two owners of one flag is worth collapsing next time you're in there.

vinzdg pushed a commit that referenced this pull request Sep 23, 2026
UK_STATIC was missing the two strings #288 added (Show move handle,
the arc caption) — both now render translated, using the Ukrainian
already authoritative in the Mac catalog. UK_STATIC and KO_STATIC
both carried a dead key from the old Recentre caption, which no
current page text matches; dropped from both rather than re-pointed,
since each already has the new caption's translation.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@RawJat
RawJat deleted the feat/windows-show-on-hover branch September 23, 2026 06:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants