Move the app under src/, add ring-spanning patterns with shared fonts and beat-driven motion, and wire global audio settings into the web UI. Co-authored-by: Cursor <cursoragent@cursor.com>
252 lines
12 KiB
Python
252 lines
12 KiB
Python
"""Angular rune and hieroglyph bitmaps for 9-row portal panels."""
|
||
|
||
from __future__ import annotations
|
||
|
||
from typing import TYPE_CHECKING, Any, Sequence
|
||
|
||
if TYPE_CHECKING:
|
||
from leds.pattern import Param
|
||
|
||
Glyph = tuple[str, ...]
|
||
RGB = tuple[int, int, int]
|
||
LIT = frozenset("#Xx1")
|
||
|
||
# Elder Futhark, carved from straight strokes on 5×7.
|
||
FUTHARK: tuple[Glyph, ...] = (
|
||
("#...#", "#..#.", "###..", "#..#.", "#...#", "#....", "#...."), # fehu
|
||
("#...#", "#...#", "#...#", "#...#", "##..#", "#.#.#", "#..##"), # uruz
|
||
("#....", "#.##.", "##..#", "#.##.", "#....", "#....", "#...."), # thurisaz
|
||
("#...#", "#..#.", "#.#..", "##...", "#.#..", "#..#.", "#...#"), # ansuz
|
||
("####.", "#...#", "#...#", "####.", "#.#..", "#..#.", "#...#"), # raidho
|
||
("#...#", ".#.#.", "..#..", ".#.#.", "#...#", ".....", "....."), # kenaz
|
||
("#...#", ".#.#.", "..#..", ".#.#.", "#...#", ".#.#.", "#...#"), # gebo
|
||
("####.", "#...#", "#...#", "####.", "#....", "#....", "#...."), # wunjo
|
||
("#...#", "#.#.#", "#.#.#", "#####", "#.#.#", "#.#.#", "#...#"), # hagalaz
|
||
("#...#", ".#..#", "..#.#", "...##", "..#.#", ".#..#", "#...#"), # naudiz
|
||
("..#..", "..#..", "..#..", "..#..", "..#..", "..#..", "..#.."), # isa
|
||
("....#", "...#.", "#.#.#", ".#.#.", "#.#.#", ".#...", "#...."), # jera
|
||
("....#", "#..#.", "#.#..", "##...", "#.#..", "#..#.", "#...."), # eihwaz
|
||
(".####", "#....", "#....", "#....", "#....", "#....", ".####"), # pertho
|
||
("#...#", ".#.#.", "..#..", "..#..", "..#..", "..#..", "..#.."), # algiz
|
||
("...##", "..#..", ".#...", "#....", ".#...", "..#..", "...##"), # sowilo
|
||
("#####", "..#..", "..#..", "..#..", "..#..", "..#..", "..#.."), # tiwaz
|
||
("####.", "#...#", "#...#", "####.", "#...#", "#...#", "####."), # berkano
|
||
("#...#", "#...#", "#.#.#", "##.##", "#.#.#", "#...#", "#...#"), # ehwaz
|
||
("#...#", "##.##", "#.#.#", "#.#.#", "#.#.#", "##.##", "#...#"), # mannaz
|
||
("#....", "#....", "#....", "#....", "#..#.", "#...#", ".##.."), # laguz
|
||
("..#..", ".#.#.", "#...#", ".#.#.", "..#..", ".....", "....."), # ingwaz
|
||
("#...#", "##.##", "#.#.#", "#.#.#", "#.#.#", "##.##", "#...#"), # dagaz
|
||
("..#..", ".#.#.", "#...#", "#...#", ".#.#.", "#...#", "#...#"), # othala
|
||
)
|
||
|
||
# Egyptian-style pictograms, 7×7, with one pixel of vertical padding on 9 rows.
|
||
HIEROGLYPHS: tuple[Glyph, ...] = (
|
||
("..###..", "..#.#..", "..###..", "...#...", ".#####.", "...#...", "...#..."), # ankh
|
||
(".......", ".#####.", "#..#..#", "#.#####", "#..#..#", ".#####.", "......."), # eye
|
||
("#.....#", ".#...#.", ".#####.", "##.###.", ".#.#...", ".#..#..", "#......"), # falcon
|
||
("...#...", "..#.#..", "..#.#..", "...#...", "...#...", "...#...", "..###.."), # reed
|
||
(".#...#.", "##.#.##", ".#####.", "#.#.#.#", ".#####.", "..###..", ".#...#."), # owl
|
||
(".......", ".#..#..", "#.##.##", ".......", "..#..#.", ".##.##.", "......."), # water
|
||
("...#...", ".#.#.#.", "..###..", "#######", "..###..", ".#.#.#.", "...#..."), # sun
|
||
(".###...", "#...#..", "...#...", "..#....", ".#...#.", "#.....#", ".#####."), # snake
|
||
("...#...", "..#.#..", "..#.#..", ".#...#.", ".#...#.", "#######", "#.....#"), # pyramid
|
||
("#.....#", ".##.##.", "#.#####", "#######", "#.#####", ".##.##.", "#.....#"), # scarab
|
||
(".......", ".......", "#.....#", ".#...#.", "..###..", ".......", "......."), # basket
|
||
("...#...", "..###..", ".#.#.#.", "...#...", "...#...", "...#...", "..#.#.."), # staff
|
||
)
|
||
|
||
# Ogham-style stem with side notches (5×7).
|
||
OGHAM: tuple[Glyph, ...] = (
|
||
("..#..", "..#..", "..#..", "..#..", "..#..", "..#..", "..#.."), # ailm
|
||
("#.#..", "..#..", "..#..", "..#..", "..#..", "..#..", "..#.."),
|
||
("#.#.#", "..#..", "..#..", "..#..", "..#..", "..#..", "..#.."),
|
||
(".#.#.", "..#..", "..#..", "..#..", "..#..", "..#..", "..#.."),
|
||
("..#.#", "..#..", "..#..", "..#..", "..#..", "..#..", "..#.."),
|
||
("..#..", "#.#..", "..#..", "..#..", "..#..", "..#..", "..#.."),
|
||
("..#..", "#.#.#", "..#..", "..#..", "..#..", "..#..", "..#.."),
|
||
("..#..", ".#.#.", "..#..", "..#..", "..#..", "..#..", "..#.."),
|
||
("..#..", "..#.#", "..#..", "..#..", "..#..", "..#..", "..#.."),
|
||
("..#..", "..#..", "#.#..", "..#..", "..#..", "..#..", "..#.."),
|
||
("..#..", "..#..", "#.#.#", "..#..", "..#..", "..#..", "..#.."),
|
||
("..#..", "..#..", ".#.#.", "..#..", "..#..", "..#..", "..#.."),
|
||
("..#..", "..#..", "..#.#", "..#..", "..#..", "..#..", "..#.."),
|
||
("#####", "..#..", "..#..", "..#..", "..#..", "..#..", "..#.."),
|
||
("..#..", "#####", "..#..", "..#..", "..#..", "..#..", "..#.."),
|
||
("..#..", "..#..", "#####", "..#..", "..#..", "..#..", "..#.."),
|
||
("#...#", ".#.#.", "..#..", ".#.#.", "#...#", ".....", "....."),
|
||
(".....", "#...#", ".#.#.", "..#..", ".#.#.", "#...#", "....."),
|
||
(".###.", "#...#", "....#", "..##.", "....#", "#...#", ".###."),
|
||
("##.##", "#.#.#", "#####", "#.#.#", "##.##", ".....", "....."),
|
||
)
|
||
|
||
# Wedge / nailhead marks (6×7).
|
||
CUNEIFORM: tuple[Glyph, ...] = (
|
||
("#.....", "##....", "###...", "####..", "###...", "##....", "#....."),
|
||
(".....#", "....##", "...###", "..####", "...###", "....##", ".....#"),
|
||
("######", ".#....", "..#...", "...#..", "....#.", ".....#", "......"),
|
||
("######", "....#.", "...#..", "..#...", ".#....", "#.....", "......"),
|
||
("..##..", ".####.", "######", ".####.", "..##..", "...#..", "...#.."),
|
||
("#....#", ".#..#.", "..##..", "..##..", ".#..#.", "#....#", "......"),
|
||
("###...", "#.#...", "###...", "..#...", "..#...", "..###.", "......"),
|
||
("...###", "...#.#", "...###", "...#..", "...#..", ".###..", "......"),
|
||
(".####.", "#....#", "#.##.#", "#....#", "#....#", ".####.", "......"),
|
||
("#.#.#.", ".###..", "#.#.#.", ".###..", "#.#.#.", "......", "......"),
|
||
("##..##", "##..##", "..##..", "..##..", "##..##", "##..##", "......"),
|
||
(".#..#.", "#.##.#", ".#..#.", "#.##.#", ".#..#.", "......", "......"),
|
||
("#.....", "#.#...", "#.#.#.", "######", "#.#.#.", "#.#...", "#....."),
|
||
("#####.", "#...#.", "#...#.", "#####.", "#.#...", "#..#..", "#...#."),
|
||
("..#...", ".###..", "#.#.#.", "..#...", ".#.#..", "#...#.", "......"),
|
||
("#...#.", ".#.#..", "..#...", ".#.#..", "#...#.", ".#.#..", "..#..."),
|
||
)
|
||
|
||
# Alchemical / planetary marks (7×7).
|
||
ALCHEMY: tuple[Glyph, ...] = (
|
||
("...#...", "..###..", ".#...#.", "#.....#", ".#...#.", "..###..", "...#..."), # sun
|
||
(".......", ".#####.", "#.....#", "#.....#", "#.....#", ".#####.", "...#..."), # moon
|
||
("...#...", "..#.#..", ".#...#.", "#..#..#", ".#...#.", "..###..", "...#..."), # mercury
|
||
("#.....#", ".#...#.", "..#.#..", "...#...", "..#.#..", ".#...#.", "#.....#"), # antimony
|
||
("...#...", "..###..", ".#####.", "...#...", "...#...", "..###..", ".#####."), # sulfur
|
||
("#######", "#.....#", "#.###.#", "#.#.#.#", "#.###.#", "#.....#", "#######"), # salt
|
||
("..###..", ".#...#.", "#.....#", "#..#..#", ".#...#.", "..#.#..", "...#..."), # venus
|
||
("...#...", "..###..", ".#.#.#.", "#..#..#", "...#...", "...#...", "..###.."), # mars
|
||
(".......", ".#.#.#.", "#.#.#.#", ".#####.", "#.#.#.#", ".#.#.#.", "......."), # jupiter
|
||
("#.....#", ".#.#.#.", "..###..", ".#####.", "..###..", ".#.#.#.", "#.....#"), # saturn
|
||
("...#...", ".#####.", "#..#..#", "...#...", ".#####.", "...#...", "...#..."), # earth
|
||
("..###..", ".#...#.", "#.#.#.#", "#.....#", "#.#.#.#", ".#...#.", "..###.."), # spirit
|
||
(".#...#.", "##.#.##", ".#####.", "..###..", ".#####.", "##.#.##", ".#...#."), # fire
|
||
(".......", "#.....#", ".#...#.", "..###..", ".#...#.", "#.....#", "......."), # air
|
||
("#######", ".#...#.", "..###..", "...#...", "..###..", ".#...#.", "#######"), # water
|
||
("...#...", "..#.#..", ".#...#.", "#.....#", ".#####.", "...#...", "..###.."), # copper
|
||
)
|
||
|
||
# Geometric occult sigils (7×7).
|
||
SIGILS: tuple[Glyph, ...] = (
|
||
("...#...", "..#.#..", ".#...#.", "#.....#", ".#...#.", "..#.#..", "...#..."),
|
||
("#######", "#.....#", "#.#.#.#", "#.....#", "#.#.#.#", "#.....#", "#######"),
|
||
("#.....#", ".#.#.#.", "..#.#..", ".#####.", "..#.#..", ".#.#.#.", "#.....#"),
|
||
("..###..", ".#...#.", "#..#..#", "#.#.#.#", "#..#..#", ".#...#.", "..###.."),
|
||
("#.#.#.#", ".#.#.#.", "#.#.#.#", ".#.#.#.", "#.#.#.#", ".#.#.#.", "#.#.#.#"),
|
||
("...#...", ".#####.", "#.....#", ".#####.", "#.....#", ".#####.", "...#..."),
|
||
(".#####.", "#.....#", "#.#.#.#", "#.#.#.#", "#.#.#.#", "#.....#", ".#####."),
|
||
("#.....#", "##...##", "#.#.#.#", "#..#..#", "#.#.#.#", "##...##", "#.....#"),
|
||
("..#.#..", ".#.#.#.", "#.#.#.#", ".#####.", "#.#.#.#", ".#.#.#.", "..#.#.."),
|
||
("#####.#", "#...#.#", "#.###.#", "#.#...#", "#.#####", ".......", "......."),
|
||
(".#.###.", "#.#...#", ".#.##.#", "...#.#.", "###.#..", ".......", "......."),
|
||
("#..#..#", ".##.##.", "#..#..#", ".##.##.", "#..#..#", ".##.##.", "#..#..#"),
|
||
("...#...", "..###..", ".##.##.", "##...##", ".##.##.", "..###..", "...#..."),
|
||
("##...##", "#.#.#.#", ".##.##.", "..###..", ".##.##.", "#.#.#.#", "##...##"),
|
||
("#.#.#.#", ".......", "#.#.#.#", ".......", "#.#.#.#", ".......", "#.#.#.#"),
|
||
("#######", ".......", "#######", ".......", "#######", ".......", "#######"),
|
||
)
|
||
|
||
|
||
def _bitmap_rows_to_glyph(rows: tuple[int, ...], *, width: int = 5) -> Glyph:
|
||
"""Convert 5×7 bit rows (bit width-1 = left) into ``#``.`` strings."""
|
||
out: list[str] = []
|
||
for row in rows:
|
||
cells: list[str] = []
|
||
for col in range(width - 1, -1, -1):
|
||
cells.append("#" if (int(row) >> col) & 1 else ".")
|
||
out.append("".join(cells))
|
||
return tuple(out)
|
||
|
||
|
||
# Latin capitals from the shared 5×7 LED text font.
|
||
def _load_latin() -> tuple[Glyph, ...]:
|
||
from leds.text import _FONT
|
||
|
||
return tuple(_bitmap_rows_to_glyph(_FONT[ch]) for ch in "ABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
||
|
||
|
||
LATIN: tuple[Glyph, ...] = _load_latin()
|
||
|
||
FONT_NAMES: tuple[str, ...] = (
|
||
"hieroglyphs",
|
||
"runes",
|
||
"ogham",
|
||
"cuneiform",
|
||
"alchemy",
|
||
"sigils",
|
||
"latin",
|
||
"mixed",
|
||
"ancient",
|
||
)
|
||
|
||
FONT_SETS: dict[str, tuple[Glyph, ...]] = {
|
||
"hieroglyphs": HIEROGLYPHS,
|
||
"runes": FUTHARK,
|
||
"ogham": OGHAM,
|
||
"cuneiform": CUNEIFORM,
|
||
"alchemy": ALCHEMY,
|
||
"sigils": SIGILS,
|
||
"latin": LATIN,
|
||
"mixed": FUTHARK + HIEROGLYPHS,
|
||
"ancient": FUTHARK + HIEROGLYPHS + OGHAM + CUNEIFORM + ALCHEMY + SIGILS,
|
||
}
|
||
|
||
|
||
def font_param(default: str = "hieroglyphs") -> Param:
|
||
# Lazy import avoids circular import with leds.pattern.
|
||
from leds.pattern import Param
|
||
|
||
choice = default if default in FONT_NAMES else "hieroglyphs"
|
||
return Param(choice, kind="choice", choices=FONT_NAMES, label="Font")
|
||
|
||
|
||
def resolve_glyphs(params: dict[str, Any] | None, *, default: str = "hieroglyphs") -> tuple[Glyph, ...]:
|
||
"""Return the glyph set selected on the preset (shared Font control)."""
|
||
extras = (params or {}).get("extras")
|
||
extras = extras if isinstance(extras, dict) else {}
|
||
name = str(extras.get("font") or default).strip().lower()
|
||
if name not in FONT_SETS:
|
||
name = default if default in FONT_SETS else "hieroglyphs"
|
||
return FONT_SETS[name]
|
||
|
||
|
||
def glyph_width(glyph: Glyph) -> int:
|
||
return max((len(row) for row in glyph), default=0)
|
||
|
||
|
||
def sequence_width(glyphs: Sequence[Glyph], gap: int = 1) -> int:
|
||
if not glyphs:
|
||
return 0
|
||
return sum(glyph_width(g) for g in glyphs) + gap * (len(glyphs) - 1)
|
||
|
||
|
||
def vcenter(panel_h: int, glyph_h: int) -> int:
|
||
return max(0, (panel_h - glyph_h) // 2)
|
||
|
||
|
||
def blit_glyph(surface, origin_x: int, origin_y: int, glyph: Glyph, color: RGB) -> None:
|
||
from leds.pattern import set_pixel, size
|
||
|
||
w, h = size(surface)
|
||
for gy, row in enumerate(glyph):
|
||
y = origin_y + gy
|
||
if y < 0 or y >= h:
|
||
continue
|
||
for gx, ch in enumerate(row):
|
||
if ch not in LIT:
|
||
continue
|
||
x = origin_x + gx
|
||
if 0 <= x < w:
|
||
set_pixel(surface, x, y, color)
|
||
|
||
|
||
def blit_sequence(
|
||
surface,
|
||
origin_x: int,
|
||
origin_y: int,
|
||
glyphs: Sequence[Glyph],
|
||
color: RGB,
|
||
*,
|
||
gap: int = 1,
|
||
wrap: int | None = None,
|
||
) -> None:
|
||
cursor = origin_x
|
||
for glyph in glyphs:
|
||
blit_glyph(surface, cursor, origin_y, glyph, color)
|
||
if wrap is not None:
|
||
blit_glyph(surface, cursor + wrap, origin_y, glyph, color)
|
||
cursor += glyph_width(glyph) + gap
|