feat: Implement an interactive font face picker at startup, allowing selection of specific font faces from a font file.

This commit is contained in:
2026-03-15 03:38:14 -07:00
parent 0740e34293
commit e6826c884c
3 changed files with 250 additions and 6 deletions

View File

@@ -3,11 +3,14 @@ Application orchestrator — boot sequence, signal handling, main loop wiring.
"""
import sys
import os
import time
import signal
import atexit
import termios
import tty
from engine import config
from engine import config, render
from engine.terminal import (
RST, G_HI, G_MID, G_DIM, W_DIM, W_GHOST, CLR, CURSOR_OFF, CURSOR_ON, tw,
slow_print, boot_ln,
@@ -26,6 +29,166 @@ TITLE = [
" ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝╚══════╝╚═╝╚═╝ ╚═══╝╚══════╝",
]
def _read_picker_key():
ch = sys.stdin.read(1)
if ch == "\x03":
return "interrupt"
if ch in ("\r", "\n"):
return "enter"
if ch == "\x1b":
c1 = sys.stdin.read(1)
if c1 != "[":
return None
c2 = sys.stdin.read(1)
if c2 == "A":
return "up"
if c2 == "B":
return "down"
return None
if ch in ("k", "K"):
return "up"
if ch in ("j", "J"):
return "down"
if ch in ("q", "Q"):
return "enter"
return None
def _normalize_preview_rows(rows):
"""Trim shared left padding and trailing spaces for stable on-screen previews."""
non_empty = [r for r in rows if r.strip()]
if not non_empty:
return [""]
left_pad = min(len(r) - len(r.lstrip(" ")) for r in non_empty)
out = []
for row in rows:
if left_pad < len(row):
out.append(row[left_pad:].rstrip())
else:
out.append(row.rstrip())
return out
def _draw_font_picker(faces, selected):
w = tw()
h = 24
try:
h = os.get_terminal_size().lines
except Exception:
pass
max_preview_w = max(24, w - 8)
header_h = 6
footer_h = 3
preview_h = max(4, min(config.RENDER_H + 2, max(4, h // 2)))
visible = max(1, h - header_h - preview_h - footer_h)
top = max(0, selected - (visible // 2))
bottom = min(len(faces), top + visible)
top = max(0, bottom - visible)
print(CLR, end="")
print(CURSOR_OFF, end="")
print()
print(f" {G_HI}FONT PICKER{RST}")
print(f" {W_GHOST}{'' * (w - 4)}{RST}")
print(f" {W_DIM}{config.FONT_PATH[:max_preview_w]}{RST}")
print(f" {W_GHOST}↑/↓ move · Enter select · q accept current{RST}")
print()
for pos in range(top, bottom):
face = faces[pos]
active = pos == selected
pointer = "" if active else " "
color = G_HI if active else W_DIM
print(f" {color}{pointer} [{face['index']}] {face['name']}{RST}")
if top > 0:
print(f" {W_GHOST}{top} above{RST}")
if bottom < len(faces):
print(f" {W_GHOST}{len(faces) - bottom} below{RST}")
print()
print(f" {W_GHOST}{'' * (w - 4)}{RST}")
print(f" {W_DIM}Preview: {faces[selected]['name']}{RST}")
preview_rows = faces[selected]["preview_rows"][:preview_h]
for row in preview_rows:
shown = row[:max_preview_w]
print(f" {shown}")
def pick_font_face():
"""Interactive startup picker for selecting a face from a font file."""
if not config.FONT_PICKER:
return
print(CLR, end="")
print(CURSOR_OFF, end="")
print()
print(f" {G_HI}FONT PICKER{RST}")
print(f" {W_GHOST}{'' * (tw() - 4)}{RST}")
print(f" {W_DIM}{config.FONT_PATH}{RST}")
print()
try:
faces = render.list_font_faces(config.FONT_PATH, max_faces=64)
except Exception as exc:
print(f" {G_DIM}> unable to load font file: {exc}{RST}")
print(f" {W_GHOST}> startup aborted (font selection is required){RST}")
time.sleep(1.4)
sys.exit(1)
face_ids = [face["index"] for face in faces]
default = config.FONT_INDEX if config.FONT_INDEX in face_ids else faces[0]["index"]
prepared = []
for face in faces:
idx = face["index"]
name = face["name"]
try:
fnt = render.load_font_face(config.FONT_PATH, idx)
rows = _normalize_preview_rows(render.render_line(name, fnt))
except Exception:
rows = ["(preview unavailable)"]
prepared.append({"index": idx, "name": name, "preview_rows": rows})
selected = next((i for i, f in enumerate(prepared) if f["index"] == default), 0)
if not sys.stdin.isatty():
selected_index = prepared[selected]["index"]
config.set_font_selection(font_index=selected_index)
render.clear_font_cache()
print(f" {G_DIM}> using face {selected_index}{RST}")
time.sleep(0.8)
print(CLR, end="")
print(CURSOR_OFF, end="")
print()
return
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setcbreak(fd)
while True:
_draw_font_picker(prepared, selected)
key = _read_picker_key()
if key == "up":
selected = max(0, selected - 1)
elif key == "down":
selected = min(len(prepared) - 1, selected + 1)
elif key == "enter":
break
elif key == "interrupt":
raise KeyboardInterrupt
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
selected = prepared[selected]["index"]
config.set_font_selection(font_index=selected)
render.clear_font_cache()
print(f" {G_DIM}> using face {selected}{RST}")
time.sleep(0.8)
print(CLR, end="")
print(CURSOR_OFF, end="")
print()
def main():
atexit.register(lambda: print(CURSOR_ON, end="", flush=True))
@@ -40,6 +203,8 @@ def main():
w = tw()
print(CLR, end="")
print(CURSOR_OFF, end="")
pick_font_face()
w = tw()
print()
time.sleep(0.4)