Skip to content

Windows/Linux: the island hops between screens instead of jumping - #899

Open
ZouaouiB wants to merge 1 commit into
Louis-CFM:mainfrom
ZouaouiB:feat/island-screen-hop
Open

ZouaouiB wants to merge 1 commit into
Louis-CFM:mainfrom
ZouaouiB:feat/island-screen-hop

Conversation

@ZouaouiB

@ZouaouiB ZouaouiB commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

When the island changes display (Island lives on: the screen under the cursor, a display plugged in or out, Settings), the window jumps across in one frame. This makes it hop like a notch would.

Changes

  • island/screenHop.ts (new): the island tucks up into the top edge of the display it leaves (190 ms), the window moves while it is out of sight, waits for the new display's pixel ratio to settle, and drops down from the top edge of the next one with a few px of give (560 ms).
  • Changes that come in during a hop run while the island is still away: one visible hop, ending on the latest display. A change during the drop takes over from where the island is, without a snap. Settings moves the window from Rust before the event: the island then only drops in. Hidden island or reduced motion: it just moves, as before.
  • The motion is on #root (#island keeps its own transform), with no fill: nothing is left once it has landed.
  • main.ts: the screen-changed handler runs through hopScreens().

Validation

  • npm test: 466 passed. tsc --noEmit, vite build: passed. git diff --check: passed.
  • In the browser, with a stand-in for the window move: a single hop sampled frame by frame (out of sight at −174 px, back to 0 within ~0.8 s), a second change during the tuck, during the move and during the drop, and the hidden path.
  • The real move between monitors was not recorded; I couldn't capture a GIF from my setup.

#892 (Mochi cut off after a monitor change) edits the same screen-changed handler: whichever lands second needs a one-line merge.

I know CLAUDE.md says existing views stay as they ship unless you ask, so this one is entirely your call — happy to adjust it, or to close it if it isn't the direction you want.

When the island changes display ("Island lives on: the screen under the
cursor", a display plugged in or out, Settings), the window jumped across
in one frame. The island now tucks up into the top edge of the display it
leaves (190 ms), the window moves while it is out of sight, waits for the
new display's pixel ratio to settle, and drops down from the top edge of the
next one with a few px of give (560 ms).

- Changes that come in during a hop run while the island is still away:
  one visible hop, ending on the latest display.
- A change that comes in during the drop takes over from where the island is.
- Settings moves the window from Rust before the event: the island then only
  drops in.
- Hidden island or reduced motion: it just moves, as before.

The motion is on #root (#island keeps its own transform), and nothing is
left on it once it has landed.
@afpruivo

Copy link
Copy Markdown
Contributor

Tested on Windows 11 Pro 26H2 with two monitors at different scales (3840×2160 at 150 % main, 1440×2560 portrait at 100 %), Island lives on: the screen under the cursor. Debug build of this PR merged on top of #892 (the one-line conflict in the screen-changed handler you mentioned: kept the hop and added island.ensureRunning() inside it). npm test: 466 passed.

One problem: moving the mouse quickly back and forth between the screens can leave the island stuck on the wrong one. It stays there even after the mouse stops (clicking doesn't help), until the cursor changes screens again. Seen mostly with the compact island.

I logged the hop to see why (timestamps in ms):

102613 event         window on portrait
102613 hop start, tuck
102808 tucked
102813 move done     still on portrait   ← screen-changed fired, but the window didn't move
...
108223 event         window on portrait
108420 tucked
108425 move done     still on portrait   ← and the cursor is back on the main screen now

hopScreens never overlaps (no event ever came in mid-hop), so it isn't the queue. I think it's this:

  1. The Rust poll samples the screen under the cursor, sees it changed, sets last_screen to it and emits screen-changed.
  2. The hop waits ~190 ms for the tuck, then Bridge.reposition() reads the cursor again. If the cursor already went back, the window is placed on the other screen than the one Rust remembered.
  3. Rust's last_screen now says the island is where the cursor is, so it never emits again, and the island stays on the wrong screen.

Without the hop, reposition runs right after the event, so that gap is almost nil (I haven't tried the same fast moves on main). A fix could be on the Rust side: compare the target screen with the monitor the window is actually on (instead of the last emitted key), so a mismatch triggers another screen-changed. Or the hop could call reposition once more after landing and hop again if the display changed.

This branch has not been deployed

No deployments
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