"""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()