159 lines
4.6 KiB
Python
159 lines
4.6 KiB
Python
"""Unicode character lookup tables for box-drawing, block elements, braille, and indicators."""
|
||
|
||
from __future__ import annotations
|
||
|
||
from uframe.ir import BorderWeight
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Box-drawing characters by weight
|
||
# ---------------------------------------------------------------------------
|
||
|
||
# Keys: (weight) → dict of part names → char
|
||
BOX_CHARS: dict[BorderWeight, dict[str, str]] = {
|
||
BorderWeight.LIGHT: {
|
||
"tl": "┌", "tr": "┐", "bl": "└", "br": "┘",
|
||
"h": "─", "v": "│",
|
||
"t_down": "┬", "t_up": "┴", "t_right": "├", "t_left": "┤",
|
||
"cross": "┼",
|
||
},
|
||
BorderWeight.HEAVY: {
|
||
"tl": "┏", "tr": "┓", "bl": "┗", "br": "┛",
|
||
"h": "━", "v": "┃",
|
||
"t_down": "┳", "t_up": "┻", "t_right": "┣", "t_left": "┫",
|
||
"cross": "╋",
|
||
},
|
||
BorderWeight.DOUBLE: {
|
||
"tl": "╔", "tr": "╗", "bl": "╚", "br": "╝",
|
||
"h": "═", "v": "║",
|
||
"t_down": "╦", "t_up": "╩", "t_right": "╠", "t_left": "╣",
|
||
"cross": "╬",
|
||
},
|
||
BorderWeight.ROUNDED: {
|
||
"tl": "╭", "tr": "╮", "bl": "╰", "br": "╯",
|
||
"h": "─", "v": "│",
|
||
"t_down": "┬", "t_up": "┴", "t_right": "├", "t_left": "┤",
|
||
"cross": "┼",
|
||
},
|
||
}
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Block elements for gauges and bars
|
||
# ---------------------------------------------------------------------------
|
||
|
||
# Horizontal fill blocks: full → 1/8
|
||
HFILL = "█▉▊▋▌▍▎▏"
|
||
|
||
# Vertical fill blocks: 1/8 → full (bottom-up)
|
||
VFILL = "▁▂▃▄▅▆▇█"
|
||
|
||
# Shade blocks: 25% → 100%
|
||
SHADE = "░▒▓█"
|
||
|
||
# Gauge characters
|
||
GAUGE_FILLED = "█"
|
||
GAUGE_EMPTY = "░"
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Braille patterns for sparklines
|
||
# ---------------------------------------------------------------------------
|
||
|
||
# Braille base: U+2800. Each character is a 2×4 dot matrix.
|
||
# Dot positions (bit index):
|
||
# 0 3
|
||
# 1 4
|
||
# 2 5
|
||
# 6 7
|
||
BRAILLE_BASE = 0x2800
|
||
|
||
# Row dot bits for left column (bits 0,1,2,6) and right column (bits 3,4,5,7)
|
||
BRAILLE_LEFT = [0x01, 0x02, 0x04, 0x40] # rows 0-3
|
||
BRAILLE_RIGHT = [0x08, 0x10, 0x20, 0x80] # rows 0-3
|
||
|
||
|
||
def braille_char(dots: list[tuple[int, int]]) -> str:
|
||
"""Build a braille character from a list of (col, row) positions.
|
||
|
||
col: 0 (left) or 1 (right)
|
||
row: 0 (top) to 3 (bottom)
|
||
"""
|
||
code = BRAILLE_BASE
|
||
for col, row in dots:
|
||
if 0 <= row <= 3:
|
||
if col == 0:
|
||
code |= BRAILLE_LEFT[row]
|
||
else:
|
||
code |= BRAILLE_RIGHT[row]
|
||
return chr(code)
|
||
|
||
|
||
def sparkline_chars(values: list[float], width: int) -> list[str]:
|
||
"""Convert a list of values into braille sparkline characters.
|
||
|
||
Each output character represents two consecutive values (left + right columns).
|
||
Values are normalized to 0–7 (mapping to 4 braille rows × 2 resolution).
|
||
"""
|
||
if not values:
|
||
return []
|
||
|
||
lo = min(values)
|
||
hi = max(values)
|
||
span = hi - lo if hi != lo else 1.0
|
||
|
||
# Normalize to 0–7 range (8 vertical positions: 4 rows × 2 resolution)
|
||
norm = [int((v - lo) / span * 7) for v in values]
|
||
|
||
# Pad to even length
|
||
if len(norm) % 2:
|
||
norm.append(norm[-1])
|
||
|
||
chars = []
|
||
for i in range(0, min(len(norm), width * 2), 2):
|
||
left_val = norm[i]
|
||
right_val = norm[i + 1] if i + 1 < len(norm) else norm[i]
|
||
|
||
dots = []
|
||
# Fill dots from bottom up for each column
|
||
for row in range(3, -1, -1):
|
||
threshold = (3 - row) * 2 # row 3=0, row 2=2, row 1=4, row 0=6
|
||
if left_val >= threshold:
|
||
dots.append((0, row))
|
||
if right_val >= threshold:
|
||
dots.append((1, row))
|
||
|
||
chars.append(braille_char(dots))
|
||
|
||
return chars[:width]
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Status indicators
|
||
# ---------------------------------------------------------------------------
|
||
|
||
STATUS_CHARS: dict[str, str] = {
|
||
"online": "●",
|
||
"offline": "○",
|
||
"degraded": "◐",
|
||
"unknown": "◌",
|
||
"alert": "⚠",
|
||
}
|
||
|
||
STATUS_COLORS: dict[str, str] = {
|
||
"online": "0f0",
|
||
"offline": "f00",
|
||
"degraded": "ff0",
|
||
"unknown": "888",
|
||
"alert": "f00",
|
||
}
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Divider characters
|
||
# ---------------------------------------------------------------------------
|
||
|
||
DIVIDER_CHARS: dict[str, str] = {
|
||
"light": "─",
|
||
"heavy": "━",
|
||
"double": "═",
|
||
"dash": "╌",
|
||
"dot": "┄",
|
||
}
|