Files
MeshTerm/meshterm/ui/logo.py
T
Jean-Pierre MartineauandClaude Opus 5 eaafc87403 Narrow wordmark for narrow screens; move the art out of the code
JP drew a 53-cell MeshTerm at the original's height. It gets the same
colouring: the vertical ramp — white, yellow, light red, red, light
black — with every box-drawing character, shadow row and letter bevel
alike, in the light black.

The art moves to meshterm/assets/. It was sitting in meshterm/ui/ among
the modules, which is the wrong place for a file nothing imports; it
still ships inside the package, since the splash reads it at runtime and
an installed venv has no repo to read from. scripts/colour-logo.py paints
a .txt into its .ansi, so the palette is stated once in code instead of
retyped into escape sequences by hand, and re-styling stays a text edit.

Which mark is drawn is a question about the *screen*, not the platform: a
53-column console and a desktop terminal dragged narrow have the same
problem. load_logo(cols) answers it — widest that fits — and
compose_startup asks, since it is the only place that knows the width.
Below the smallest mark it returns nothing at all; no banner beats a torn
one. A test now draws the real wordmark at each platform's width, which
is the gate the splash never had (it is the one screen the gallery
doesn't cover, which is how a 71-cell mark shipped to a 53-cell console).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 21:53:28 -04:00

67 lines
2.6 KiB
Python

"""The MeshTerm wordmark for the startup splash, loaded from the assets folder.
The art itself lives in ``meshterm/assets`` — hand-drawn ``.txt`` sources beside the
pre-coloured ``.ansi`` the splash actually draws (``scripts/colour-logo.py`` paints one
into the other). Files are read fresh on every call, so the wordmark can be re-styled by
editing the art alone: no code change and no restart of the design loop.
There is more than one mark, at different widths, and the *screen* picks — not the
platform. A 53-column PicoCalc console and a desktop terminal dragged narrow have the same
problem, so :func:`load_logo` answers the only question that matters: which is the widest
mark that fits the columns I have?
"""
from __future__ import annotations
from pathlib import Path
from typing import Optional
from rich.cells import cell_len
from rich.text import Text
#: Where the art lives — a sibling of the code, not mixed into it, and inside the package
#: so it ships with an installed wheel.
_ASSETS = Path(__file__).resolve().parent.parent / "assets"
#: Every wordmark, widest first. :func:`load_logo` walks this and takes the first that
#: fits, so adding a size is dropping a file in and naming it here.
_VARIANTS: tuple[str, ...] = ("logo.ansi", "logo_53.ansi")
def _rows(name: str) -> list[str]:
"""The named mark's rows, or ``[]`` when it can't be read."""
try:
text = (_ASSETS / name).read_text(encoding="utf-8")
except OSError:
return []
lines = text.split("\n")
if lines and lines[-1] == "": # drop the trailing newline's empty row
lines.pop()
return lines
def logo_width(rows: list[str]) -> int:
"""The display width of a mark's widest row, escape sequences discounted."""
return max((cell_len(Text.from_ansi(row).plain) for row in rows), default=0)
def load_logo(max_cols: Optional[int] = None) -> list[str]:
"""Return the widest wordmark that fits ``max_cols``, as pre-coloured ANSI rows.
Args:
max_cols: The columns available to draw in. ``None`` asks for the full-size mark
without fitting — for a caller that will fit it later (see
:func:`~meshterm.ui.tui.frame.compose_startup`, which knows the real width
only at compose time).
Returns:
The mark's rows, or ``[]`` when none fits (or none could be read) — the splash
draws no banner at all rather than a torn one, and a missing file degrades the
same way rather than raising.
"""
for name in _VARIANTS:
rows = _rows(name)
if rows and (max_cols is None or logo_width(rows) <= max_cols):
return rows
return []