Files
MeshTerm/meshterm/ui/theme.py
T
Jean-Pierre MartineauandClaude Opus 4.8 6d51b4e2ab Add a TYPE column and name known devices on the picker
Reworks the startup device picker so it reads honestly at a glance:

- VENDOR is now just the hardware maker. A BLE advert rarely carries one
  (confirmed against real hardware: the companion advertises no
  manufacturer_data), so it stays blank instead of mislabelling the
  transport "Bluetooth" as a vendor.
- A new TYPE column marks the transport with an icon: a plug emoji for
  wired serial, and the Bjarkan rune the Bluetooth logo is drawn from,
  rendered white on Bluetooth blue and flanked by half-block slivers so
  it reads as a slim rounded badge.
- Every confirmed companion (not just the most-recent one) is shown by
  the mesh node name learned at connect time, so a known COM11 reads as
  its node name rather than the OS's generic "USB Serial Device".
- Confirmed companions sort to the top, most-recently-used first, and
  wear their name in white so they stand out from merely-detected ports.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 18:59:54 -04:00

85 lines
3.5 KiB
Python

"""Shared Rich theme and console factory for a consistent, modern look."""
from __future__ import annotations
from rich.console import Console
from rich.theme import Theme
MESH_THEME = Theme(
{
"brand": "bold #5eead4",
"accent": "bold #818cf8",
# The highlighted-button fill (cyan block, dark text) shared by every popup dialog.
# It must be a *single* theme name: Rich silently drops a style string that mixes a
# theme name with an attribute (e.g. "reverse brand" renders as plain text), so the
# reverse is baked into the definition here rather than tacked on at the call site.
"selected": "reverse bold #5eead4",
# Reversed error (the text-editor cursor sitting on an over-budget character). Baked
# in for the same reason as ``selected`` — "reverse err" would render as plain text.
"err.reverse": "reverse bold #f87171",
# Our own messages in a chat: pure white, deliberately outside the per-sender hue
# palette (see meshterm.ui.chat._SENDER_COLORS) so "you" is always easy to spot.
"you": "bold #ffffff",
# A confirmed companion's name on the startup device picker: pure white so the
# devices we've actually talked to before jump out above the merely-detected ports.
"device.known": "bold #ffffff",
# The picker's Bluetooth TYPE badge: a white rune on the official Bluetooth blue
# (Pantone 300, #0057b8), echoing the real logo so BLE reads at a glance.
"bluetooth": "bold #ffffff on #0057b8",
# The badge's tapered edges: half-block glyphs drawn in the same blue as the
# foreground (over the terminal's own background), so only their inner half fills and
# the badge reads a touch wider than the single rune cell without a hard rectangle.
"bluetooth.edge": "#0057b8",
"ok": "bold #4ade80",
"warn": "bold #fbbf24",
"err": "bold #f87171",
"muted": "#94a3b8",
# A step darker than ``muted`` for placeholder dashes (a node's missing packet count /
# age) that should recede below the real, muted values around them.
"faint": "#64748b",
"snr.good": "bold #4ade80",
"snr.ok": "bold #fbbf24",
"snr.bad": "bold #f87171",
}
)
def make_console() -> Console:
"""Create the application's themed Rich console.
On legacy Windows consoles the standard streams default to ``cp1252``, which cannot
encode the box-drawing and marker glyphs (``◆ ● ★``) the UI uses; this reconfigures
them to UTF-8 where the runtime supports it so output never raises ``UnicodeEncodeError``.
Returns:
A :class:`rich.console.Console` configured with the MeshTerm theme.
"""
import sys
for stream in (sys.stdout, sys.stderr):
reconfigure = getattr(stream, "reconfigure", None)
if reconfigure is not None:
try:
reconfigure(encoding="utf-8")
except (ValueError, OSError): # pragma: no cover - stream not reconfigurable
pass
return Console(theme=MESH_THEME)
def snr_style(snr: float | None) -> str:
"""Return a theme style name describing an SNR value's quality.
Args:
snr: An SNR reading in dB, or ``None``.
Returns:
``"snr.good"``, ``"snr.ok"``, ``"snr.bad"``, or ``"muted"`` for ``None``.
"""
if snr is None:
return "muted"
if snr >= 5:
return "snr.good"
if snr >= -5:
return "snr.ok"
return "snr.bad"