diff --git a/agent/BACKLOG.md b/agent/BACKLOG.md index d6342ae..6cb79d7 100644 --- a/agent/BACKLOG.md +++ b/agent/BACKLOG.md @@ -177,129 +177,6 @@ migration (state.json created + git-staged, legacy `git rm`'d). Proven by hand 2026-07-15 (drvPaths identical across the rename) — this just keeps it proven. Delete together with the shim + the `nomarchy-theme-sync` alias. -### 145. Show the keyboard layout on the LUKS prompt (Fedora does this) - -Bernardo 2026-07-16: the splash's password screen should say which layout is -loaded. It is the right ask for this machine specifically — a VT has **one** -keymap and no concept of per-device layouts, so `settings.keyboard.devices` -(his external board remembered as `us`) does **not** apply at the passphrase -box; it follows the session layout via `services.xserver.xkb.layout` → -`console.useXkbConfig` (`modules/nixos/default.nix:96-102`). That is the same -structural gap as **#114** (tuigreet), one step earlier in the boot, and with -worse stakes: three wrong tries on a disk you cannot read yet, with no -indication that the keyboard is not the one you think. - -**Fedora's mechanism does not port, and the reason matters.** Its label is -`ply_keymap_icon` — a **C widget in the two-step plugin** -(`plugins/splash/two-step/plugin.c:239`, `libply-splash-graphics/ply-keymap-icon.c`); -the **script** plugin has no keyboard API at all (nothing in -`script-lib-plymouth.c`). And it is fed from **`XKBLAYOUT` in -`/etc/vconsole.conf`**, which **NixOS does not write** — this box has exactly -`KEYMAP=/nix/store/…-xkb-console-keymap` and nothing else, so plymouth's own -trace says `XKBLAYOUT: (null)`. Switching to a C-plugin theme *and* teaching -NixOS to emit XKBLAYOUT to get a stock feature is a bad trade for a text label. - -**So bake it, the way the palette already is.** The theme derivation seds -`@BG_R@/@BG_G@/@BG_B@` into `nomarchy.script` at build time -(`modules/nixos/plymouth.nix`); add `@LAYOUT@` from -`config.nomarchy.keyboard.layout` and draw it under the entry with -`Image.Text` (already used by `display_message_callback`), in the muted/subtext -colour, only inside `display_password_callback`. It stays honest because the -label and the console keymap are baked from the same option in the same -rebuild. - -To settle while building: (a) `keyboard.layout` may be a comma list (`us,gb`) — -the VT loads the **first**, so print that one, not the raw string, or the label -lies in exactly the situation it exists for; (b) wording — Fedora says -"keyboard layout: us"; (c) whether to render it at all when there is only one -layout and no remembered devices (an argument for always: the point is to tell -you what the *console* does, which is not what your session does). - -**Do #137 first, and land this with it:** the label is one more sprite that has -to move when the canvas resizes, so it belongs in that item's `layout()`, and -both are proven by the same `tools/plymouth-preview.sh` run — which renders -`ask-for-password`, i.e. exactly this screen. - -### 137. Plymouth splash draws on head 0's geometry, so it is off-centre when docked - -Bernardo 2026-07-16 (T14s, docked): the boot **and** shutdown logo is not -centred on the external monitor. - -**The first diagnosis here was wrong — do not act on it.** It claimed -`Window.GetWidth()` returns head 0 and that the fix was per-head sprites. Both -are false, and per-head sprites would have been a *regression* (duplicate -logos). Read from plymouth's own source (`plymouth.src`, the build nixpkgs -ships) rather than from a summary: - -- `Window.GetWidth()` **without an index returns `data->max_width`** - (`script-lib-sprite.c:227`) — the widest head, not head 0. -- The script plugin builds a virtual canvas of `max_width × max_height` and - **centres every display inside it**: `display->x = (max_width - width) / 2` - (`:536`). Sprites are drawn per display at `sprite->x - display->x` (`:452`). -- Therefore a canvas-centred sprite **is** centred on every head, and today's - `Window.GetWidth()/2 - w/2` is *correct at the instant it runs*. Note the DRM - renderer sets every head's `area.x = 0` (`renderers/drm/plugin.c:612`) — the - origins that matter are the plugin's computed ones, not the crtc's. - -**The real bug is time, not geometry.** `nomarchy.script` computes every -position as a **top-level statement at parse time** (`:9`, `:15–16`, `:129`, -`:210`, `:219`), i.e. once, against whatever canvas existed at that moment. -When a display arrives or leaves *during* the splash — which is exactly -"booting up / shutting down **with** an external" — `max_width/max_height` -change, the plugin re-centres each display (`:532-537`), but the sprite's frozen -canvas coordinate does not move with it. Every element then sits off by -`(new_max - old_max) / 2` **on every head**. A single monitor never changes the -canvas, which is precisely why this has never been seen undocked. - -**Fix shape:** hoist the position math into a `reposition()` and call it from -the existing `refresh_callback` (already running at 50 fps) whenever -`Window.GetWidth()/GetHeight()` differ from the last canvas — logo (scale -included), entry, lock, any live bullets, progress box/bar. Keep it a -canvas-change check, not an every-frame recompute. - -**The rig works, needs no root, and is in `scratchpad/ply-rig.sh`** (recipe -below — rebuild it, it is ~20 lines). Rendered on the dev box 2026-07-16: - -- `unshare -rm` gives a user + mount namespace: root *in there* only, every - mount invisible to the real system and gone when it exits. -- `/run/plymouth` is BOTH the daemon's socket dir **and** its compiled-in - plugin/theme path (`strings plymouthd` → `/run/plymouth/plugins/`). Bind a - writable dir over it **and recreate its three symlinks** (`plugins`, - `themes`, `plymouthd.defaults` → `/etc/plymouth/…`) or plymouth cannot find - `x11.so`, silently skips X11 and reaches for a tty. -- **Do not pass `--tty`**: `main.c:2133` only takes the X11 path while - `default_tty` is unset. Bind an empty dir over `/dev/dri` too — with X11 - first in the renderer order (`ply-renderer.c:283`) DRM is never used, and the - mask makes that a guarantee rather than a hope. -- `PLY_CREATE_FAKE_MULTI_HEAD_SETUP=1` → 800×600 + 640×480 windows. - `plymouth show-splash`, then `plymouth ask-for-password` renders the LUKS - dialog. Debug: `--kernel-command-line="plymouth.debug=stream:/path splash"` - (**`stream:`**, not `file:` — `file:` buffers and you get nothing). -- Fast loop: copy the theme into the rig's `themes/` and sed its `.plymouth` - `ScriptFile=`/`ImageDir=` to the copy — then editing the script is a 10s - render instead of a 3min `nixos-rebuild`. - -**What the rig already proved (2026-07-16):** with a *stable* two-head canvas -the **current** theme centres correctly on both heads — logo and progress bar -each in their own head's middle. So the geometry is not the bug, which is the -evidence for the timing diagnosis above. The fake path builds both heads up -front, so it cannot reproduce a canvas *change*: for that, plug/unplug while -the splash is up, or compare a boot with and without the external. - -**Attempt 1 failed, and the rig caught it — that is the point.** Hoisting the -positions into a `layout()` called from `refresh_callback` rendered the -background on both heads and **nothing else**: no logo, no progress bar. The -script plugin logs `starting boot animation` and **no parse error**, so it -fails *silently* — reverted rather than shipped (that script draws the LUKS box -on an encrypted machine). Next session: bisect with the fast loop. Prime -suspect is scope — plymouth script assigns into `local` unless the name already -exists, so a `logo.image = …` inside a function may not be the `logo` the rest -of the script sees, even though the same pattern works in `update_progress_bar`. -Test that hypothesis *first*, in isolation, before rewriting anything. - -Pass = with the rig, a two-head render centred on both **plus the password -dialog visible**; then a real docked boot + shutdown, undocked unchanged. - ### 143. Rofi keeps the selected *row number* when the search changes, so the highlight lands on an unrelated entry Bernardo 2026-07-16, and his repro is exact: open the app menu, type `ca`, move diff --git a/agent/HARDWARE-QUEUE.md b/agent/HARDWARE-QUEUE.md index 1813bd3..d0d9f83 100644 --- a/agent/HARDWARE-QUEUE.md +++ b/agent/HARDWARE-QUEUE.md @@ -509,6 +509,17 @@ Everything else below stays open; order is convenience, not a gate. active and PPD switching governors; opt-ins: ROCm (`rocminfo`, a GPU PyTorch/Ollama smoke) and the XDNA NPU driver loading. - [ ] **Fingerprint** — `fprintd-enroll` + (opt-in PAM) login/sudo. +- [ ] **#137/#145 splash on a real docked boot** — the one thing the render rig + cannot do: its fake heads exist from the start, so the canvas never + resizes, which *is* the bug. **Docked**, reboot: pass = the logo is centred + on **both** panels at boot **and** at shutdown (it was off-centre on the + external), and the LUKS prompt shows the padlock, the entry, and beneath it + a keyboard icon + your layout (`us`). Then reboot **undocked** — unchanged. + Rendering is already proven (`tools/plymouth-preview.sh`, both heads + + `ask-for-password`), so what is unproven is only a canvas that changes + under a real DRM boot. If the splash is ever blank, the passphrase prompt + is invisible but still live — type it blind, or pick the previous + generation in the boot menu, which carries the old theme. - [ ] **#142 dock mode survives a rebuild** — **relogin first** (the watcher is `exec-once`; `nomarchy-home` alone leaves the old one running and you would be testing nothing — round 6's lesson). Then, docked: run diff --git a/agent/JOURNAL.md b/agent/JOURNAL.md index 8f02290..a9545e4 100644 --- a/agent/JOURNAL.md +++ b/agent/JOURNAL.md @@ -19,6 +19,35 @@ Template: --- +## 2026-07-16 — #137 + #145: the splash follows the canvas, and says your layout +- **Task:** #137 (splash off-centre docked) + #145 (keyboard layout on the LUKS + prompt, with Fedora's icon). +- **Did:** one `layout()`, re-run from the existing refresh callback on canvas + change; `@LAYOUT@` + `@FG_*@` baked by plymouth.nix like the palette already + is; plymouth's own `keyboard.png` copied at build time and recoloured. +- **The lesson, and it is the whole entry:** I was wrong twice, and only the + render rig caught it. Not head-0 geometry (`Window.GetWidth()` = `max_width`); + not per-head sprites (would have duplicated the logo — the plugin already + centres each display in the canvas). The bug was **time**: positions computed + once at parse time vs a canvas that resizes when a head arrives. And attempt 1 + of the fix died on a plymouth-script trap: **a bare assignment inside a + function writes the global if that name exists globally**, so + `canvas_width = Window.GetWidth()` updated the very global its guard compared + against — always false, nothing drawn, **no error logged**. Isolated with a + 20-line probe (`[after assign: global.canvas_width=800][guard SKIPPED]`). + Both facts are now comments in the script; do not re-derive them. +- **Verified:** V0 + V2 by render — `tools/plymouth-preview.sh` with the real + built theme: both heads centred, and the password dialog showing padlock, + entry, and ⌨ + `us`. Also caught, only because I read the build log: copying a + file from the store keeps it **444**, so `recolor` failed, the phase aborted, + and **every later sed silently did not run** — the theme shipped with `@BG_R@` + literals. `chmod +w`. Check build logs, not just exit codes. +- **Pending:** V3 queued — a real docked boot/shutdown is the only way to resize + a canvas (the rig's fake heads exist from the start). Recovery if the splash + is ever blank: the prompt is invisible but live (type blind), or boot the + previous generation. +- **Next suggestion:** #129/#130 (GTK contrast) or #133 (the #107 shim check). + ## 2026-07-16 — "fix the rest": #140, #142, #144 shipped; #137 re-diagnosed - **Task:** the T14s round's remainder. Bernardo picked configurable-provider + drop-the-CLI for #140 (and asked for chatgpt/grok in the list). diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 3553a7e..d89392d 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -449,6 +449,49 @@ Design/decision records and a running log of shipped work (items marked (#131). Those windows therefore carry float/center rules and deliberately no `size` rule. Measured after: calendar 1536×936 = exactly 60%×65%, doctor 1408×1008 = 55%×70%, both centred in the Waybar work area. +- ✓ **Splash: positions follow the canvas, + a keyboard hint on the LUKS prompt + (#137/#145, 2026-07-16):** Bernardo's logo was off-centre on the external when + booting/shutting down docked. **The first two diagnoses were both wrong**, and + the record matters more than the fix: it is *not* that `Window.GetWidth()` + returns head 0 (it returns `max_width`), and *not* that per-head sprites were + needed (they would have duplicated the logo). The script plugin builds one + canvas of `max_width × max_height`, **centres every display inside it** + (`display->x = (max_width - width)/2`, `script-lib-sprite.c:536`) and draws + each sprite at `sprite.x - display.x` — so a canvas-centred sprite is centred + on **every** head and the theme's arithmetic was right all along. The bug was + **time**: every position was a top-level statement evaluated once at parse + time, so a head arriving or leaving mid-splash resized the canvas, the plugin + re-centred the displays, and the frozen sprites ended up off by + `(new_max - old_max)/2` on every head. One monitor never resizes the canvas — + hence "only when docked". Fix: one `layout()`, re-run from the existing + refresh callback on canvas change. + **The trap that cost two attempts, now a comment in the script:** in plymouth + script a bare assignment inside a function writes the **global** if that name + already exists globally (`global.foo` and `foo` are one variable). So + `canvas_width = Window.GetWidth()` updated `global.canvas_width` *before* the + guard compared against it — always false, body never ran, whole splash a bare + background, **no error logged**. Proven with a 20-line probe: + `[after assign: global.canvas_width=800][guard SKIPPED]` vs a distinctly named + local firing correctly. Hence `cw`/`ch`. + **#145** rides along: a VT loads exactly one keymap and knows nothing of + per-device layouts, so the passphrase box types with the console layout — + worth stating before three wrong tries on a disk nobody can read yet (the same + gap as #114, one step earlier in the boot). Fedora's own mechanism does not + port: its label is `ply_keymap_icon`, a **C widget** in the two-step plugin + (the script plugin has no keyboard API), fed from `XKBLAYOUT` in + `/etc/vconsole.conf` — which **NixOS never writes** (this box has only + `KEYMAP=`; plymouth's trace literally says `XKBLAYOUT: (null)`). + So `@LAYOUT@` is baked from `services.xserver.xkb.layout` exactly like the + palette already is — **first** of a comma list, because that is what the VT + loads and the raw string would lie in the multi-layout case the label exists + for. The icon is plymouth's own `keyboard.png` (literally Federa's glyph), + copied at build time so no GPL bytes enter this repo, recoloured to subtext. + `chmod +w` after that copy: store files are 444 and `recolor` rewrites in + place — without it magick fails, the phase aborts, **every later sed silently + does not run**, and the theme ships with `@BG_R@` in it. + V2 by render, both heads + the password dialog, via the new + `tools/plymouth-preview.sh`. V3 (a real docked boot/shutdown) is queued: the + fake heads exist from the start, so the rig cannot resize a canvas. - ✓ **Dock mode survives a rebuild (#142, 2026-07-16):** Bernardo: every rebuild while docked re-lit the laptop panel and threw a workspace onto it, and he re-picked Dock mode by hand. It was not forgetfulness — **a rebuild performed diff --git a/modules/nixos/plymouth.nix b/modules/nixos/plymouth.nix index 9d8c5a1..42e87e3 100644 --- a/modules/nixos/plymouth.nix +++ b/modules/nixos/plymouth.nix @@ -24,8 +24,20 @@ let text = colorOf "text" "#D3DAE0"; surface = colorOf "surface" "#303A46"; accent = colorOf "accent" "#B79BE8"; + # The keyboard hint is a footnote, not a headline: subtext, like the rest of + # the palette's secondary text (#145). + subtext = colorOf "subtext" "#97A3B2"; magick = lib.getExe' pkgs.imagemagick "magick"; + # What the passphrase prompt actually types with. `services.xserver.xkb.layout` + # is this distro's single source for the layout, bridged to the console (and + # so to the initrd prompt) by console.useXkbConfig — see modules/nixos/default.nix. + # A VT loads exactly ONE keymap, so a comma list ("us,gb") means the FIRST: + # printing the raw string would lie in precisely the multi-layout case the + # label exists for. + kbdLayout = lib.head + (lib.splitString "," (config.services.xserver.xkb.layout or "us")); + # Plymouth's Window.SetBackgroundTopColor takes three floats in # 0.0–1.0; the .plymouth metadata's ConsoleLogBackgroundColor takes a # 0xRRGGBB hex. Nix has no float math: multiply, integer-divide, pad. @@ -38,7 +50,9 @@ let else if lib.stringLength s == 2 then "0${s}" else s; in "0.${padded}"; - channel = off: byteToFloat (lib.fromHexString (lib.substring off 2 base)); + channelOf = hex: off: byteToFloat (lib.fromHexString (lib.substring off 2 hex)); + channel = channelOf base; # Window.SetBackground* (the splash base) + fgChannel = channelOf subtext; # Image.Text needs the same 0.0-1.0 floats nomarchy-plymouth = pkgs.stdenv.mkDerivation { pname = "nomarchy-plymouth"; @@ -53,6 +67,17 @@ let mkdir -p "$themedir" cp * "$themedir/" + # The keyboard glyph for the passphrase hint (#145) is plymouth's own — + # literally the icon Fedora shows, since its keymap widget loads this same + # asset. Copied at build time rather than vendored: the bytes stay in + # nixpkgs' (GPL) plymouth and never enter this repo, and it cannot drift + # from the plymouth we actually run. + # chmod: store files are read-only (444) and `recolor` rewrites in place. + # unpackPhase makes the *source* writable, which is why the art below + # needs no such thing — a file copied straight from the store does. + cp ${pkgs.plymouth}/share/plymouth/themes/spinner/keyboard.png "$themedir/keyboard.png" + chmod +w "$themedir/keyboard.png" + # Recolor the splash art from the palette (flat fill, alpha kept) so # it reads on any base instead of the shipped fixed navy. recolor() { ${magick} "$themedir/$1" -fill "#$2" -colorize 100 "$themedir/$1"; } @@ -62,6 +87,7 @@ let recolor entry.png ${surface} # password field box recolor progress_box.png ${surface} # progress track recolor progress_bar.png ${accent} # progress fill + recolor keyboard.png ${subtext} # passphrase keyboard-layout hint # Point the .plymouth metadata into the store sed -i "s|/usr/share/plymouth/themes/nomarchy|$themedir|g" \ @@ -73,6 +99,10 @@ let -e 's|@BG_R@|${channel 0}|g' \ -e 's|@BG_G@|${channel 2}|g' \ -e 's|@BG_B@|${channel 4}|g' \ + -e 's|@FG_R@|${fgChannel 0}|g' \ + -e 's|@FG_G@|${fgChannel 2}|g' \ + -e 's|@FG_B@|${fgChannel 4}|g' \ + -e 's|@LAYOUT@|${kbdLayout}|g' \ "$themedir/nomarchy.script" sed -i 's|@BG_HEX@|${base}|g' \ "$themedir/nomarchy.plymouth" diff --git a/modules/nixos/plymouth/nomarchy.script b/modules/nixos/plymouth/nomarchy.script index 216dac9..a5be68f 100644 --- a/modules/nixos/plymouth/nomarchy.script +++ b/modules/nixos/plymouth/nomarchy.script @@ -3,18 +3,125 @@ Window.SetBackgroundTopColor(@BG_R@, @BG_G@, @BG_B@); Window.SetBackgroundBottomColor(@BG_R@, @BG_G@, @BG_B@); -logo.image = Image("logo.png"); +# Everything is placed by layout(), which runs on every canvas change — never +# once at parse time (#137). +# +# How the canvas works, because it is not obvious and the old code read as +# correct: the script plugin lays all heads out in ONE virtual canvas of +# max_width x max_height (the largest head), centres each display inside it +# (display->x = (max_width - width) / 2) and draws each sprite at +# (sprite.x - display.x). So Window.GetWidth() is the CANVAS width — the +# widest head, NOT head 0 — and a canvas-centred sprite lands centred on every +# head. The arithmetic below is unchanged from the original and it was always +# right. What it was not is permanent: when a head arrives or leaves the +# canvas resizes, the plugin re-centres each display, and sprites keep the +# coordinates they were given — so positions frozen at parse time end up off by +# (new_max - old_max) / 2 on EVERY head. Booting or shutting down with an +# external attached is exactly that. One monitor never resizes the canvas, +# which is why this only ever showed docked. +# +# TRAP — read before editing layout(). In plymouth script a bare assignment +# inside a function writes the GLOBAL if that name already exists globally +# (`global.foo` and a bare `foo` are the same variable; it only becomes a local +# when no global of that name exists). So a local named after the global it +# guards on silently updates that global *before* the comparison, the guard is +# then always false, and the body never runs — the whole splash renders as a +# bare background, with no error logged anywhere. Hence `cw`/`ch` below, and +# never `canvas_width = Window.GetWidth()`. +logo.original_image = Image("logo.png"); +lock.image = Image("lock.png"); +entry.image = Image("entry.png"); +bullet.image = Image("bullet.png"); +# The keyboard hint for the passphrase prompt (#145): a VT loads ONE keymap and +# knows nothing of per-device layouts, so what you type here is the console +# layout — which is worth saying out loud before three wrong tries on a disk +# nobody can read yet. Both are baked at build time by plymouth.nix. +kbd.icon_image = Image("keyboard.png"); +kbd.text_image = Image.Text("@LAYOUT@", @FG_R@, @FG_G@, @FG_B@); -# Calculate scale factor to make logo ~15% of screen height -logo_scale_factor = (Window.GetHeight() * 0.15) / logo.image.GetHeight(); -logo_width = logo.image.GetWidth() * logo_scale_factor; -logo_height = logo.image.GetHeight() * logo_scale_factor; -logo.image = logo.image.Scale(logo_width, logo_height); - -logo.sprite = Sprite(logo.image); -logo.sprite.SetX (Window.GetWidth() / 2 - logo.image.GetWidth() / 2); -logo.sprite.SetY (Window.GetHeight() / 2 - logo.image.GetHeight() / 2); +logo.sprite = Sprite(); logo.sprite.SetOpacity (1); +entry.sprite = Sprite(entry.image); +entry.sprite.SetOpacity (0); +lock.sprite = Sprite(); +lock.sprite.SetOpacity (0); +kbd.icon_sprite = Sprite(); +kbd.icon_sprite.SetOpacity (0); +kbd.text_sprite = Sprite(); +kbd.text_sprite.SetOpacity (0); + +global.canvas_width = 0; +global.canvas_height = 0; +global.bullet_size = 7; +global.bullet_gap = 5; + +fun layout () + { + cw = Window.GetWidth(); + ch = Window.GetHeight(); + + if (cw != global.canvas_width || ch != global.canvas_height) + { + global.canvas_width = cw; + global.canvas_height = ch; + + # Logo: ~15% of canvas height, centred. + logo_scale = (ch * 0.15) / logo.original_image.GetHeight(); + logo.image = logo.original_image.Scale( + logo.original_image.GetWidth() * logo_scale, + logo.original_image.GetHeight() * logo_scale); + logo.sprite.SetImage(logo.image); + logo.sprite.SetX(cw / 2 - logo.image.GetWidth() / 2); + logo.sprite.SetY(ch / 2 - logo.image.GetHeight() / 2); + + # Password entry, under the logo. + entry.x = cw / 2 - entry.image.GetWidth() / 2; + entry.y = logo.sprite.GetY() + logo.image.GetHeight() + 40; + entry.sprite.SetPosition(entry.x, entry.y, 10001); + + # Lock, slightly shorter than the entry, to its left. + # (source lock.png is 84x96) + lock_h = entry.image.GetHeight() * 0.8; + lock_w = 84 * (lock_h / 96); + lock.sprite.SetImage(lock.image.Scale(lock_w, lock_h)); + lock.sprite.SetPosition(entry.x - lock_w - 15, + entry.y + entry.image.GetHeight() / 2 - lock_h / 2, + 10001); + + # Keyboard hint, centred as one icon+text group under the entry. + kbd_gap = 8; + kbd_x = cw / 2 - (kbd.icon_image.GetWidth() + kbd_gap + + kbd.text_image.GetWidth()) / 2; + kbd_y = entry.y + entry.image.GetHeight() + 18; + kbd.icon_sprite.SetImage(kbd.icon_image); + kbd.icon_sprite.SetPosition(kbd_x, kbd_y, 10001); + kbd.text_sprite.SetImage(kbd.text_image); + kbd.text_sprite.SetPosition( + kbd_x + kbd.icon_image.GetWidth() + kbd_gap, + kbd_y + kbd.icon_image.GetHeight() / 2 - kbd.text_image.GetHeight() / 2, + 10001); + + # Bullets already on screen belong to the old canvas. + for (index = 0; bullet.sprites[index]; index++) + { + bullet.sprites[index].SetPosition( + entry.x + 20 + index * (global.bullet_size + global.bullet_gap), + entry.y + entry.image.GetHeight() / 2 - global.bullet_size / 2, + 10002); + } + + # Progress box + bar share the entry's line. + progress_box.sprite.SetPosition( + cw / 2 - progress_box.image.GetWidth() / 2, + entry.y + entry.image.GetHeight() / 2 - progress_box.image.GetHeight() / 2, + 0); + progress_bar.sprite.SetPosition( + cw / 2 - progress_bar.original_image.GetWidth() / 2, + entry.y + entry.image.GetHeight() / 2 + - progress_bar.original_image.GetHeight() / 2, + 1); + } + } # Use these to adjust the progress bar timing global.fake_progress_limit = 0.7; # Target percentage for fake progress (0.0 to 1.0) @@ -31,6 +138,10 @@ global.max_progress = 0.0; # Track the maximum progress reached to prevent back fun refresh_callback () { + # Cheap: two Window.Get*() reads; layout() returns at once unless the canvas + # actually resized (a head arrived or left). + layout(); + global.animation_frame++; # Animate fake progress to limit over time with easing @@ -91,12 +202,18 @@ fun show_password_dialog() { lock.sprite.SetOpacity(1); entry.sprite.SetOpacity(1); + # The keyboard hint belongs to the prompt: it is only ever the answer to + # "what am I typing with?", so it appears and leaves with the box (#145). + kbd.icon_sprite.SetOpacity(1); + kbd.text_sprite.SetOpacity(1); } fun hide_password_dialog() { lock.sprite.SetOpacity(0); entry.sprite.SetOpacity(0); + kbd.icon_sprite.SetOpacity(0); + kbd.text_sprite.SetOpacity(0); for (index = 0; bullet.sprites[index]; index++) bullet.sprites[index].SetOpacity(0); } @@ -121,30 +238,7 @@ fun stop_fake_progress() #----------------------------------------- Dialogue -------------------------------- -lock.image = Image("lock.png"); -entry.image = Image("entry.png"); -bullet.image = Image("bullet.png"); - -entry.sprite = Sprite(entry.image); -entry.x = Window.GetWidth()/2 - entry.image.GetWidth() / 2; -entry.y = logo.sprite.GetY() + logo.image.GetHeight() + 40; -entry.sprite.SetPosition(entry.x, entry.y, 10001); -entry.sprite.SetOpacity(0); - -# Scale lock to be slightly shorter than entry field height -# Original lock is 84x96, entry height determines scale -lock_height = entry.image.GetHeight() * 0.8; -lock_scale = lock_height / 96; -lock_width = 84 * lock_scale; - -scaled_lock = lock.image.Scale(lock_width, lock_height); -lock.sprite = Sprite(scaled_lock); -lock.x = entry.x - lock_width - 15; -lock.y = entry.y + entry.image.GetHeight()/2 - lock_height/2; -lock.sprite.SetPosition(lock.x, lock.y, 10001); -lock.sprite.SetOpacity(0); - -# Bullet array +# Images and sprites are created at the top; every position lives in layout(). bullet.sprites = []; fun display_normal_callback () @@ -206,21 +300,18 @@ Plymouth.SetDisplayPasswordFunction(display_password_callback); progress_box.image = Image("progress_box.png"); progress_box.sprite = Sprite(progress_box.image); - -progress_box.x = Window.GetWidth() / 2 - progress_box.image.GetWidth() / 2; -progress_box.y = entry.y + entry.image.GetHeight() / 2 - progress_box.image.GetHeight() / 2; -progress_box.sprite.SetPosition(progress_box.x, progress_box.y, 0); progress_box.sprite.SetOpacity(0); progress_bar.original_image = Image("progress_bar.png"); progress_bar.sprite = Sprite(); progress_bar.image = progress_bar.original_image.Scale(1, progress_bar.original_image.GetHeight()); - -progress_bar.x = Window.GetWidth() / 2 - progress_bar.original_image.GetWidth() / 2; -progress_bar.y = progress_box.y + (progress_box.image.GetHeight() - progress_bar.original_image.GetHeight()) / 2; -progress_bar.sprite.SetPosition(progress_bar.x, progress_bar.y, 1); progress_bar.sprite.SetOpacity(0); +# First placement: everything layout() reads now exists. The refresh callback +# re-runs it, so a head arriving or leaving mid-splash moves the splash with it +# instead of stranding it against a canvas that is gone. +layout(); + fun progress_callback (duration, progress) { global.real_progress = progress;