From b73fcb254e9a056a3260ebd84d76097de5976092 Mon Sep 17 00:00:00 2001 From: Jean-Pierre Martineau Date: Fri, 2 Oct 2026 02:01:30 -0400 Subject: [PATCH] The splash fits its wordmark once, not on every paint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Arrowing down the device list on the PicoCalc stalled on every keystroke. The time was not in the list: at 53 columns the full-size mark does not fit, so each paint sent load_logo back to the disk for every size until one did, parsed and measured each, and decoded every row again to centre it. None of that changes between paints, and the splash paints on every keystroke and every idle tick. compose_startup now asks _fitted_banner, a one-slot memo keyed by the banner and the width, dropped on a platform switch with the other composition memos since the fold bakes the theme in. Measured on the desktop under the picocalc platform, splash at 53x26, ↓/↑ alternating: 31.1 ms a paint before, 2.3 ms after. The logo was nine-tenths of the frame; the list itself is under 0.1 ms. 108 frames across 53x26, 53x40, 72x24, 100x30, 40x20 and 53x12, with and without a footnote, picocalc then regular then picocalc again, come out byte-identical before and after. Not yet timed on the device, which was unreachable; A/B it with profile-picocalc. The splash's live rescan (f9e9271) was the first suspect and is not the cost: a serial poll's device ids are stable, so it repaints only when a device comes or goes, and the enumeration itself runs in a thread. Co-Authored-By: Claude Opus 5.5 (1M context) --- meshterm/ui/tui/frame.py | 49 ++++++++++++++++++++++++++++++++-------- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/meshterm/ui/tui/frame.py b/meshterm/ui/tui/frame.py index 0a54197..9f2c5f5 100644 --- a/meshterm/ui/tui/frame.py +++ b/meshterm/ui/tui/frame.py @@ -449,6 +449,41 @@ def _banner_lines(banner: Sequence[str], cols: int) -> list[str]: return _center(padded, cols) +#: The wordmark as the splash last drew it: ``(banner, cols)`` and what that came to. One +#: slot, because there is one splash and one terminal size at a time. +_BANNER_CACHE: tuple[tuple, tuple[list[str], int]] | None = None + + +def _fitted_banner(banner: Sequence[str], cols: int) -> tuple[list[str], int]: + """The wordmark drawn at ``cols`` — fitted, folded and centred — and the art's width. + + Fitting the mark is the dearest thing the splash draws: a mark wider than the terminal + sends :func:`~meshterm.ui.logo.load_logo` back to the disk for every size until one + fits, each one parsed and measured, and then every row is decoded again to centre it. + None of that moves from one paint to the next, and the splash paints on every keystroke + and every idle tick. Done fresh each time it was nine-tenths of a splash frame at + 53×26, which on the PicoCalc put a visible wait behind every ↑↓ on the device list. + + Args: + banner: The wordmark rows the screen names (usually the full-size mark). + cols: Terminal width. + + Returns: + The centred rows to draw — shared with the memo, so never mutate them — and the + display width of the mark actually chosen, for hanging the footnote off its edge. + """ + global _BANNER_CACHE + key = (tuple(banner), cols) + if _BANNER_CACHE is not None and _BANNER_CACHE[0] == key: + return _BANNER_CACHE[1] + raw = list(banner) + if raw and logo_width(raw) > cols: + raw = load_logo(cols) + fitted = (_banner_lines(raw, cols), logo_width(raw)) + _BANNER_CACHE = (key, fitted) + return fitted + + def fit_hint(hint: str, width: int, *, shed_first: Sequence[str] = ()) -> str: """Drop ``·`` atoms from a footer hint, right to left, until it fits ``width``. @@ -508,16 +543,11 @@ def compose_startup(screen: Screen, cols: int, rows: int) -> str: """ # A screen names its wordmark; the width to draw it at is only known here, so this is # where the size is chosen — a narrow terminal gets a narrow mark rather than a torn one. - raw_banner = screen.banner or [] - if raw_banner and logo_width(raw_banner) > cols: - raw_banner = load_logo(cols) - banner = _banner_lines(raw_banner, cols) + # ``logo_w`` is the chosen mark's own width, so the footnote below can hang off its right + # edge (the wordmark is centered as one block, so every row shares one left margin). + banner, logo_w = _fitted_banner(screen.banner or (), cols) banner_h = len(banner) gap = 1 if banner else 0 # the blank line under the banner - - # The logo's own left margin and width, so the footnote below can hang off its right edge - # (the wordmark is centered as one block, so every row shares this margin). - logo_w = logo_width(raw_banner) logo_right = max(0, (cols - logo_w) // 2) + logo_w # Size the box to its widest real row (probe at a generous width, then measure), never @@ -848,7 +878,8 @@ def _bind(platform: Platform) -> None: Registered at module bottom so the immediate first run (see :func:`~meshterm.platforms.on_platform`) finds every cache already defined. """ - global _BASE_BOX_CACHE + global _BASE_BOX_CACHE, _BANNER_CACHE _BASE_BOX_CACHE = None + _BANNER_CACHE = None _DIALOG_CACHE.clear() _OVERLAY_CACHE.clear()