Skip to content

pinToTop: element-level pin-to-top overlays - #26

Merged
daedeloth merged 7 commits into
masterfrom
flung-emojis-qr
Jul 14, 2026
Merged

pinToTop: element-level pin-to-top overlays#26
daedeloth merged 7 commits into
masterfrom
flung-emojis-qr

Conversation

@daedeloth

Copy link
Copy Markdown
Member

Adds an element-level pin-to-top primitive so a display object can render above a chosen layer while still tracking its original placeholder's position/scale/animation.

API

  • easelbone.pinToTop(obj) / easelbone.unpinFromTop(obj)
  • RootView.setPinContainer(container) — designate the layer pinned objects render into
  • stage._pinsHidden — hide all pinned overlays (e.g. while a modal is open)

How it works

Pins reparent obj into a per-pin wrapper on the pin container; the wrapper carries the anchor's bounds (so bounds-sizing Fill/Background objects keep their size) and is re-synced to the anchor's global transform every paint. State lives on the shared stage object so separate easelbone bundles on one stage cooperate. Includes idempotent re-pin, auto-teardown when the anchor leaves the stage, deferred pinning until the object is on a stage, and a Playwright regression test (tools/pin-test.js).

Consumed by catlab-remote-library # and client.quizwitz.com # to keep connect/portal QR codes above the flung-emoji layer. Release v1.2.8.

🤖 Generated with Claude Code

daedeloth and others added 7 commits July 14, 2026 14:49
pinToTop reparents an object onto a designated pin container and re-syncs
its transform from its original anchor each paint, so it renders on top
while following the anchor's position/scale/animation. State lives on the
stage object so separate easelbone bundles on one stage cooperate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
pin() previously re-derived the anchor from obj.parent, which is wrong
for an already-pinned object (parent is by then the pin container),
causing a leaked never-torn-down record plus redundant per-frame
matrix work on every re-pin of the same instance. Guard pin() to no-op
when the object is already tracked.

Also mark RootView dirty on successful pin/unpin via DirtyFlag, since
reparenting/removing a child doesn't itself trigger a repaint under
dirtyRendering: true.

Extends the pin-baseline fixture/test with re-pin-replace,
idempotent-re-pin, and anchor-removed auto-teardown coverage; the
idempotent case reproduces FAIL against the pre-fix pin() (2 tracked
records instead of 1) and passes after the fix.
…l size

pinToTop reparented the pinned obj directly into the shared pin
container and rewrote its transform. That broke Fill/Background
objects (e.g. QR codes), which size themselves via
parent.getBounds() -- the plain pin container has no bounds, so the
Fill fell back to a 100x100 default and rendered tiny in the
top-left of its placeholder.

Fix: reparent obj into a per-pin wrapper Container instead. The
wrapper carries the anchor's bounds and is transformed to the
anchor's global position; obj itself (and its own transform/regX/
regY) is left untouched. This reproduces the anchor as obj's parent
exactly (same bounds + same global transform), so Fills size
correctly and non-Fill objects render identically to before.

Record shape is now { obj, anchor, wrapper }. _restore detaches the
wrapper and reattaches obj to its original anchor (if still on
stage), which also makes explicit unpin() put the object back where
it came from instead of leaving it stranded with regX/regY reset.

Added a regression assertion in tools/pin-test.js (via a new
window.__wrapperBounds() fixture hook) proving the wrapper carries
the anchor's [50,50] bounds -- this fails against the pre-fix code
(no wrapper existed) and passes after.
pin() relied on obj's 'added' event when the object wasn't on a stage yet,
but the object is typically addChild'd to its (still-detached) placeholder
BEFORE pinToTop is called -- so 'added' already fired and never fires again
when the placeholder is later attached, silently dropping the pin. Retry on
the ticker until getStage() is truthy. Adds a regression test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Lets callers hide all pinned overlays (e.g. the portal QR) while a modal is
open over them, restoring on close. Adds a regression test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
DisplayObject.getStage() is deprecated and logs a warning on every call;
the per-tick sync/teardown checks spammed thousands of warnings. Use the
non-deprecated .stage getter.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds pinToTop (element-level pin-to-top overlays) + stage._pinsHidden.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

📦 PR Preview deployed!

Examples are available at:

@daedeloth
daedeloth merged commit 45bd30d into master Jul 14, 2026
2 checks passed
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.

1 participant