diff options
Diffstat (limited to 'idatui')
| -rw-r--r-- | idatui/app.py | 16 | ||||
| -rw-r--r-- | idatui/kittygfx.py | 19 |
2 files changed, 29 insertions, 6 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: diff --git a/idatui/kittygfx.py b/idatui/kittygfx.py index e53fba3..2f6766d 100644 --- a/idatui/kittygfx.py +++ b/idatui/kittygfx.py @@ -45,6 +45,14 @@ import time #: One id for the splash. Ids are a terminal-wide namespace shared with whatever #: else the user is running, so this is deliberately not 1. LOGO_ID = 0x1DA7 +#: Placement id for the splash. A placement is identified by the PAIR (image id, +#: placement id): re-placing with the same pair REPLACES the placement, while a +#: placement with no ``p`` key is anonymous and every one of those stacks a new +#: copy on the screen. The splash re-anchors itself on every progress note, so +#: without this the terminal ends a long load holding hundreds of placements of +#: the same image at the same cell -- alpha-compositing the (RGBA) logo over +#: itself until its soft edges go solid, and re-rendering all of them per frame. +LOGO_PLACEMENT = 1 _supported: bool | None = None _uploaded: dict[int, tuple[int, int]] = {} # image id -> (pixel w, pixel h) @@ -210,21 +218,26 @@ def is_uploaded(image_id: int = LOGO_ID) -> bool: def place(row: int, col: int, cols: int, rows: int, - image_id: int = LOGO_ID) -> bool: + image_id: int = LOGO_ID, placement_id: int = LOGO_PLACEMENT) -> bool: """Draw the uploaded image at (``row``, ``col``), 0-based, sized in cells. Saves and restores the cursor, and asks the terminal not to move it (``C=1``), so Textual's idea of where the cursor is stays true. + + Always carries a placement id (``p``), so calling this again REPLACES the + previous placement instead of adding another one underneath it -- see + ``LOGO_PLACEMENT``. Callers re-anchor freely; the screen holds exactly one. """ size = _uploaded.get(image_id) if size is None or cols <= 0 or rows <= 0: log(f"place: refused size={size} cols={cols} rows={rows}") return False w, h = size - log(f"place row={row} col={col} c={cols} r={rows}") + log(f"place row={row} col={col} c={cols} r={rows} p={placement_id}") return _write( f"\033[s\033[{row + 1};{col + 1}H" - f"\033_Ga=p,i={image_id},s={w},v={h},c={cols},r={rows},C=1,q=2\033\\" + f"\033_Ga=p,i={image_id},p={placement_id}," + f"s={w},v={h},c={cols},r={rows},C=1,q=2\033\\" f"\033[u") |
