textgrid-rust/demo_features.py
2026-07-20 12:53:51 +10:00

350 lines
11 KiB
Python

"""Feature demo: colors, bold/italic, CJK, emoji, background colors — cycles through 5 panels."""
import math
import time
import termstr
import textgrid
from cwcwidth import wcswidth
ROWS = 24
COLS = 80
BLOCKS = " ░▒▓█"
def center(text):
"""Center text in COLS columns."""
w = wcswidth(text)
if w >= COLS:
return text
left = (COLS - w) // 2
return " " * left + text + " " * (COLS - w - left)
def pad(text):
"""Left-align and pad text to display width COLS."""
w = wcswidth(text)
if w >= COLS:
return text
return text + " " * (COLS - w)
def rainbow(hue):
r = int(255 * (0.5 + 0.5 * math.cos(hue * math.tau)))
g = int(255 * (0.5 + 0.5 * math.cos(hue * math.tau - 2.094)))
b = int(255 * (0.5 + 0.5 * math.cos(hue * math.tau - 4.188)))
return r, g, b
# ---------------------------------------------------------------------------
# Panel 1 — Colors
# ---------------------------------------------------------------------------
def panel_colors(phase):
rows = []
# header
rows.append(termstr.TermStr(
center("Colors: RGB and xterm 256"),
termstr.CharStyle(fg_color=(120, 120, 120)),
))
rows.append(" " * COLS)
# 2 rows of RGB rainbow
for _ in range(2):
s = termstr.CharStyle(fg_color=(0, 0, 0))
ts = termstr.TermStr(
"".join(BLOCKS[int((math.sin(x / COLS * math.pi) + 1) * 2)] for x in range(COLS)),
s,
)
rows.append(ts)
# 2 rows of xterm 256 gradient
for offset in (16, 80):
text = "".join(BLOCKS[(offset + x) % 5] for x in range(COLS))
styles = tuple(
termstr.CharStyle(fg_color=(offset + x) % 256) for x in range(COLS)
)
rows.append(termstr.TermStr(text, styles))
rows.append(" " * COLS)
# ripple (plain str to show default rendering still works)
for y in range(ROWS - len(rows)):
line = []
for x in range(COLS):
dx = x - COLS / 2
dy = (y - 2) - ROWS / 2 + 6
v = math.sin(math.sqrt(dx * dx + dy * dy) * 0.3 - phase)
v = max(-1, min(1, v))
idx = int((v + 1) * 0.5 * (len(BLOCKS) - 1))
line.append(BLOCKS[idx])
rows.append("".join(line))
return rows
# ---------------------------------------------------------------------------
# Panel 2 — Font Styles
# ---------------------------------------------------------------------------
def panel_font_styles():
rows = []
rows.append(termstr.TermStr(
center("Font Styles: Bold · Italic · Bold+Italic"),
termstr.CharStyle(fg_color=(120, 120, 120)),
))
rows.append(" " * COLS)
demos = [
("Regular", lambda s: s),
("Bold", lambda s: s.bold()),
("Italic", lambda s: s.italic()),
("Underline", lambda s: s.underline()),
("Bold+Italic", lambda s: s.bold().italic()),
("Bold+Underline", lambda s: s.bold().underline()),
("Italic+Underline", lambda s: s.italic().underline()),
("Bold+Ita+Under", lambda s: s.bold().italic().underline()),
]
for label, mod in demos:
text = f" {label:14s} The quick brown fox jumps over the lazy dog"
text = pad(text)
styles = tuple(
mod(termstr.CharStyle(fg_color=(200, 200, 200)))
for _ in range(COLS)
)
rows.append(termstr.TermStr(text, styles))
rows.append(" " * COLS)
# per-character alternation (no wide chars)
text = " MIXED: Bold | Italic | Regular | Bold+Italic | Repeat..."
text = pad(text)
styles = []
for i in range(COLS):
s = termstr.CharStyle(fg_color=rainbow(i / COLS))
if i % 4 == 0:
s = s.bold()
elif i % 4 == 1:
s = s.italic()
elif i % 4 == 3:
s = s.bold().italic()
styles.append(s)
rows.append(termstr.TermStr(text, tuple(styles)))
while len(rows) < ROWS:
rows.append(" " * COLS)
return rows
# ---------------------------------------------------------------------------
# Panel 3 — Wide Characters
# ---------------------------------------------------------------------------
def panel_wide_chars():
rows = []
rows.append(termstr.TermStr(
center("Wide Characters: CJK & Emoji (2 columns each)"),
termstr.CharStyle(fg_color=(120, 120, 120)),
))
rows.append(" " * COLS)
# CJK rows — uniform colors, single CharStyle (broadcast)
for text, fg in [
(" CJK: 你好世界 你好世界 你好世界 你好世界", (0, 200, 255)),
(" 你好世界 你好世界 你好世界 你好世界", (100, 220, 255)),
(" 你好世界 你好世界 你好世界 你好世界", (180, 240, 255)),
]:
text = pad(text)
rows.append(termstr.TermStr(text, termstr.CharStyle(fg_color=fg)))
rows.append(" " * COLS)
# Emoji rows
for text, fg in [
(" Emoji: 🍕🍔🌭🎉🔥⭐💎🚀❤️🌟🎵💡⚡❤️💎", (255, 200, 0)),
(" 🍕🍔🌭🎉🔥⭐💎🚀❤️🌟🎵💡⚡❤️💎", (255, 220, 80)),
(" 🍕🍔🌭🎉🔥⭐💎🚀❤️🌟🎵💡⚡❤️💎", (255, 240, 120)),
]:
text = pad(text)
rows.append(termstr.TermStr(text, termstr.CharStyle(fg_color=fg)))
rows.append(" " * COLS)
# Mixed row — use plain str (default colors)
rows.append(pad("hello你好🍕world你好🍔!!!"))
rows.append(" " * COLS)
rows.append(termstr.TermStr(
center("(Zero-width padding chars are invisible; wide chars occupy 2 cells)"),
termstr.CharStyle(fg_color=(100, 100, 100)),
))
while len(rows) < ROWS:
rows.append(" " * COLS)
return rows
# ---------------------------------------------------------------------------
# Panel 4 — Background Colors
# ---------------------------------------------------------------------------
def panel_bg_colors():
rows = []
rows.append(termstr.TermStr(
center("Background Colors: text & emoji on colored backgrounds"),
termstr.CharStyle(fg_color=(120, 120, 120)),
))
rows.append(" " * COLS)
# Highlighted text rows
labels = [" Highlight", " Selection", " Warning ", " Success "]
bgs = [(60, 60, 100), (50, 50, 150), (100, 30, 30), (30, 80, 30)]
for label, bg in zip(labels, bgs):
text = f"{label}: Lorem ipsum dolor sit amet, consectetur adipiscing elit "
text = pad(text)
rows.append(termstr.TermStr(text, termstr.CharStyle(bg_color=bg)))
rows.append(" " * COLS)
# Underline with background (full row)
rows.append(termstr.TermStr(
pad(" Underlined text on colored background — highlight + underline "),
termstr.CharStyle(bg_color=(50, 30, 60)).underline(),
))
rows.append(" " * COLS)
# Emoji with background — tests bg + color emoji blending
rows.append(termstr.TermStr(
pad(" Emoji on colored backgrounds — 🍕🍔🌭🎉🔥⭐💎🚀❤️🌟🎵💡⚡ "),
termstr.CharStyle(bg_color=(40, 40, 80)),
))
rows.append(termstr.TermStr(
pad(" Brighter bg — 🍕🍔🌭🎉🔥⭐💎🚀❤️🌟🎵💡⚡ "),
termstr.CharStyle(bg_color=(80, 60, 40)),
))
rows.append(termstr.TermStr(
pad(" Green bg — 🍕🍔🌭🎉🔥⭐💎🚀❤️🌟🎵💡⚡ "),
termstr.CharStyle(bg_color=(20, 80, 40)),
))
rows.append(" " * COLS)
# Alternating row highlights (like terminal selection)
rows.append(termstr.TermStr(
center("Alternating row backgrounds"),
termstr.CharStyle(fg_color=(100, 100, 100)),
))
for i in range(6):
bg = (30, 35 + i * 8, 50 + i * 12) if i % 2 == 0 else (0, 0, 0)
icon = "🍕" if i % 2 == 0 else " "
text = f" Row {i+1}: {icon} Alternating background colors in a list view "
text = pad(text)
rows.append(termstr.TermStr(text, termstr.CharStyle(bg_color=bg)))
while len(rows) < ROWS:
rows.append(" " * COLS)
return rows
# ---------------------------------------------------------------------------
# Panel 5 — Everything Together
# ---------------------------------------------------------------------------
def panel_mixed(phase):
rows = []
rows.append(termstr.TermStr(
center("All Features Combined"),
termstr.CharStyle(fg_color=(120, 120, 120)),
))
for y in range(ROWS - 1):
# Top half: per-char styles with ASCII chars only
if y < (ROWS - 1) // 2:
row_chars = []
row_styles = []
for x in range(COLS):
p = math.sin(x * 0.2 + y * 0.3 - phase)
ch = BLOCKS[int((p + 1) * 2)]
s = termstr.CharStyle(fg_color=rainbow(x / COLS + y / ROWS))
if y % 2 == 0:
s = s.bold()
elif y % 3 == 0:
s = s.italic()
row_chars.append(ch)
row_styles.append(s)
rows.append(termstr.TermStr("".join(row_chars), tuple(row_styles)))
# Bottom half: three bands with mixed features
else:
band = y - (ROWS - 1) // 2
if band % 3 == 0:
# xterm gradient
styles = tuple(
termstr.CharStyle(fg_color=(x + band * 10) % 256)
for x in range(COLS)
)
text = "".join(BLOCKS[(x + band) % 5] for x in range(COLS))
rows.append(termstr.TermStr(text, styles))
elif band % 3 == 1:
text = "CJK: 你好世界你好世界你好世界你好世界"
text = pad(text)
rows.append(termstr.TermStr(text, termstr.CharStyle(fg_color=(0, 200, 255))))
else:
text = "Emoji 🍕🍔🌭🎉🔥⭐💎🚀❤️🌟🎵💡⚡"
text = pad(text)
rows.append(termstr.TermStr(text, termstr.CharStyle(fg_color=(255, 200, 0))))
return rows
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
_closed = False
def _on_close():
global _closed
_closed = True
def main():
tg = textgrid.TextGrid(
ROWS, COLS,
"TextGrid Feature Demo",
font_path="/usr/local/share/fonts/truetype/sarasa/SarasaTermSC-Regular.ttf",
color_font_path="/usr/share/fonts/truetype/noto/NotoColorEmoji.ttf",
on_close=_on_close,
)
phase = 0.0
try:
while not _closed:
tg.update(panel_colors(phase))
if _closed: break
time.sleep(4)
if _closed: break
tg.update(panel_font_styles())
if _closed: break
time.sleep(4)
if _closed: break
tg.update(panel_wide_chars())
if _closed: break
time.sleep(4)
if _closed: break
tg.update(panel_bg_colors())
if _closed: break
time.sleep(4)
if _closed: break
tg.update(panel_mixed(phase))
if _closed: break
time.sleep(4)
phase += 0.25
except KeyboardInterrupt:
pass
finally:
tg.close()
if __name__ == "__main__":
main()