diff options
| author | blasty <peter@haxx.in> | 2026-08-21 12:08:42 +0200 |
|---|---|---|
| committer | blasty <peter@haxx.in> | 2026-08-21 12:08:42 +0200 |
| commit | 7a3b198a904da186cc2860df059b063db24282ea (patch) | |
| tree | fd5a754169522c0ccd8b7e0d70b2238395d81b09 /idatui/app.py | |
| parent | docs: be honest that the triskel fork is unpublished (diff) | |
| download | ida-tui-7a3b198a904da186cc2860df059b063db24282ea.tar.gz ida-tui-7a3b198a904da186cc2860df059b063db24282ea.tar.xz ida-tui-7a3b198a904da186cc2860df059b063db24282ea.zip | |
splash: give the logo a placement id so re-anchoring replaces, not stacks
A kitty placement is identified by (image id, placement id); an a=p with no
p= key is anonymous and every one stacks another copy. The splash re-anchors
on every progress note (~5/s), so a 60s load ended with ~300 placements of an
RGBA logo alpha-compositing over each other -- soft edges creeping to solid,
and the terminal re-rendering all of them per frame.
Same pair every time (LOGO_PLACEMENT) = the terminal REPLACES the placement,
so re-anchoring is free and atomic. That is also why on_resize no longer
clear()s first (that showed a hole for a frame), and why a shrunk-to-nothing
region now drops the placement instead of leaving a stale one anchored.
tests/test_kittygfx.py pins the escapes (pure, stdlib);
experiments/splash_place_count.py counts what a real splash sends.
Diffstat (limited to 'idatui/app.py')
| -rw-r--r-- | idatui/app.py | 16 |
1 files changed, 13 insertions, 3 deletions
diff --git a/idatui/app.py b/idatui/app.py index 3f67756..d0bf403 100644 --- a/idatui/app.py +++ b/idatui/app.py @@ -4425,8 +4425,11 @@ class LoadingScreen(ModalScreen): except Exception: # noqa: BLE001 -- not mounted yet / already gone pass # Textual doesn't know the image is there, so a repaint can drop it. - # Re-anchoring is one short escape with no image data; throttled so a - # chatty progress callback can't turn it into a flicker. + # Re-anchoring is one short escape with no image data, and it REPLACES + # the placement rather than adding one (kittygfx.LOGO_PLACEMENT), so a + # long load ends with one image on screen instead of a stack of them. + # Still throttled: a chatty progress callback shouldn't drive the + # terminal's image compositor at status-write rate. if self._image: now = time.monotonic() if now - self._last_place > 0.2: @@ -4439,6 +4442,8 @@ class LoadingScreen(ModalScreen): Deferred to after a refresh because a widget has no screen region until it has been laid out, and re-run on resize because the region moves. + Idempotent: the placement carries an id, so calling this a hundred times + during a slow load leaves exactly one image on the screen. """ if not self._image: return @@ -4446,9 +4451,13 @@ class LoadingScreen(ModalScreen): region = self.query_one("#loading-image", Static).region except Exception as e: # noqa: BLE001 -- gone already kittygfx.log(f"place_logo: no widget ({e})") + kittygfx.clear() return kittygfx.log(f"place_logo: region={region}") if not region.width or not region.height: + # No room left to draw into -- drop the placement rather than leave + # the old, bigger one anchored over whatever now occupies the cells. + kittygfx.clear() return # The reserved region is the truth about how much room there is; the # image is scaled into exactly it, so a resize needs no relayout. @@ -4470,8 +4479,9 @@ class LoadingScreen(ModalScreen): self.call_after_refresh(self._place_logo) def on_resize(self) -> None: + # No clear() first: re-placing with the same placement id replaces the + # old one atomically, where delete-then-draw shows a hole for a frame. if self._image: - kittygfx.clear() self.call_after_refresh(self._place_logo) def on_unmount(self) -> None: |
