/* This file provides a termbox2-based backend for the Vis editor's terminal UI. * It is designed to be included from ui-terminal.c in place of ui-terminal-vt100.c * or ui-terminal-curses.c. Compile with termbox2 linked and define necessary * options like TB_OPT_ATTR_W=32 for truecolor support if desired. * * Focus is on portability, simplicity, and performance. We leverage termbox2's * double-buffering to eliminate flicker while keeping overhead low. Color * handling falls back gracefully from truecolor to 256 or 8 colors based on * terminal capabilities. * * Cells are inspected properly for content; empty data uses space. Colors map * from Vis CellColor (index or RGB) to termbox attrs, with approximation for * reduced palettes. */ #define TB_OPT_V1_COMPAT #define TB_IMPL #include "termbox2.h" #define TB_OUTPUT_TRUECOLOR 1 #define CELL_COLOR_BLACK { .index = 0 } #define CELL_COLOR_RED { .index = 1 } #define CELL_COLOR_GREEN { .index = 2 } #define CELL_COLOR_YELLOW { .index = 3 } #define CELL_COLOR_BLUE { .index = 4 } #define CELL_COLOR_MAGENTA { .index = 5 } #define CELL_COLOR_CYAN { .index = 6 } #define CELL_COLOR_WHITE { .index = 7 } #define CELL_COLOR_DEFAULT { .index = 9 } #define CELL_ATTR_NORMAL 0 #define CELL_ATTR_UNDERLINE (1 << 0) #define CELL_ATTR_REVERSE (1 << 1) #define CELL_ATTR_BLINK (1 << 2) #define CELL_ATTR_BOLD (1 << 3) #define CELL_ATTR_ITALIC (1 << 4) #define CELL_ATTR_DIM (1 << 5) #define UI_TERMKEY_FLAGS TERMKEY_FLAG_UTF8|TERMKEY_FLAG_NOTERMIOS static int output_mode; static const unsigned char color_256_to_16[256] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 4, 4, 4, 12, 12, 2, 6, 4, 4, 12, 12, 2, 2, 6, 4, 12, 12, 2, 2, 2, 6, 12, 12, 10, 10, 10, 10, 14, 12, 10, 10, 10, 10, 10, 14, 1, 5, 4, 4, 12, 12, 3, 8, 4, 4, 12, 12, 2, 2, 6, 4, 12, 12, 2, 2, 2, 6, 12, 12, 10, 10, 10, 10, 14, 12, 10, 10, 10, 10, 10, 14, 1, 1, 5, 4, 12, 12, 1, 1, 5, 4, 12, 12, 3, 3, 8, 4, 12, 12, 2, 2, 2, 6, 12, 12, 10, 10, 10, 10, 14, 12, 10, 10, 10, 10, 10, 14, 1, 1, 1, 5, 12, 12, 1, 1, 1, 5, 12, 12, 1, 1, 1, 5, 12, 12, 3, 3, 3, 7, 12, 12, 10, 10, 10, 10, 14, 12, 10, 10, 10, 10, 10, 14, 9, 9, 9, 9, 13, 12, 9, 9, 9, 9, 13, 12, 9, 9, 9, 9, 13, 12, 9, 9, 9, 9, 13, 12, 11, 11, 11, 11, 7, 12, 10, 10, 10, 10, 10, 14, 9, 9, 9, 9, 9, 13, 9, 9, 9, 9, 9, 13, 9, 9, 9, 9, 9, 13, 9, 9, 9, 9, 9, 13, 9, 9, 9, 9, 9, 13, 11, 11, 11, 11, 11, 15, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8, 8, 8, 7, 7, 7, 7, 7, 7, 15, 15, 15, 15, 15, 15 }; static void get_6cube_rgb(unsigned int n, int *r, int *g, int *b) { if (n < 16) { return; } else if (n < 232) { n -= 16; *r = (n / 36) ? (n / 36) * 40 + 55 : 0; *g = ((n / 6) % 6) ? ((n / 6) % 6) * 40 + 55 : 0; *b = (n % 6) ? (n % 6) * 40 + 55 : 0; } else if (n < 256) { n -= 232; *r = n * 10 + 8; *g = n * 10 + 8; *b = n * 10 + 8; } } static inline bool cell_color_equal(CellColor c1, CellColor c2) { if (c1.index != (uint8_t)-1 || c2.index != (uint8_t)-1) return c1.index == c2.index; return c1.r == c2.r && c1.g == c2.g && c1.b == c2.b; } static CellColor color_rgb(Ui *ui, uint8_t r, uint8_t g, uint8_t b) { return (CellColor){ .r = r, .g = g, .b = b, .index = (uint8_t)-1 }; } static CellColor color_terminal(Ui *ui, uint8_t index) { return (CellColor){ .r = 0, .g = 0, .b = 0, .index = index }; } static uintattr_t get_tb_attr(CellAttr a) { uintattr_t ret = 0; if (a & CELL_ATTR_BOLD) ret |= TB_BOLD; if (a & CELL_ATTR_UNDERLINE) ret |= TB_UNDERLINE; if (a & CELL_ATTR_REVERSE) ret |= TB_REVERSE; if (a & CELL_ATTR_BLINK) ret |= TB_BLINK; if (a & CELL_ATTR_ITALIC) ret |= TB_ITALIC; if (a & CELL_ATTR_DIM) ret |= TB_DIM; return ret; } static uintattr_t get_tb_color(CellColor c) { int mode = output_mode; int r, g, b; if (c.index != (uint8_t)-1) { if (c.index == 9) return TB_DEFAULT; static const int ansi_r[8] = {0, 128, 0, 128, 0, 128, 0, 192}; static const int ansi_g[8] = {0, 0, 128, 128, 0, 0, 128, 192}; static const int ansi_b[8] = {0, 0, 0, 0, 128, 128, 128, 192}; r = ansi_r[c.index]; g = ansi_g[c.index]; b = ansi_b[c.index]; } else { r = c.r; g = c.g; b = c.b; } if (mode == TB_OUTPUT_TRUECOLOR) { return (r << 16) | (g << 8) | b; } // Approximate to 256-color index int i = 0; if ((!r || (r - 55) % 40 == 0) && (!g || (g - 55) % 40 == 0) && (!b || (b - 55) % 40 == 0)) { i = 16; i += r ? ((r - 55) / 40) * 36 : 0; i += g ? ((g - 55) / 40) * 6 : 0; i += b ? ((b - 55) / 40) : 0; } else if (r == g && g == b && (r - 8) % 10 == 0 && r < 239) { i = 232 + ((r - 8) / 10); } else { unsigned lowest = UINT_MAX; for (int j = 16; j < 256; ++j) { int jr, jg, jb; get_6cube_rgb(j, &jr, &jg, &jb); int dr = jr - r, dg = jg - g, db = jb - b; unsigned distance = dr * dr + dg * dg + db * db; if (distance < lowest) { lowest = distance; i = j; } } } if (mode == TB_OUTPUT_256) { return i; } else { // TB_OUTPUT_NORMAL int idx16 = color_256_to_16[i]; if (idx16 == 0) return TB_BLACK; // Treat as black for non-default if (idx16 < 8) return idx16 + 1; return (idx16 - 8 + 1) | TB_BRIGHT; } } static void ui_term_backend_blit(Ui *tui) { int w = tui->width, h = tui->height; Cell *cell = tui->cells; for (int y = 0; y < h; y++) { for (int x = 0; x < w; x++) { uint32_t ch = ' '; tb_utf8_char_to_unicode(&ch, cell->data); uintattr_t fg = get_tb_color(cell->style.fg) | get_tb_attr(cell->style.attr); uintattr_t bg = get_tb_color(cell->style.bg); tb_change_cell(x, y, ch, fg, bg); cell++; } } tb_present(); } static void ui_term_backend_clear(Ui *tui) { tb_clear(); } static bool ui_term_backend_resize(Ui *tui, int width, int height) { return true; // termbox2 handles resize internally } static void ui_term_backend_save(Ui *tui, bool fscr) { tb_shutdown(); } static void ui_term_backend_restore(Ui *tui) { tb_init(); tb_select_output_mode(output_mode); tb_hide_cursor(); tb_clear(); } int ui_terminal_colors(void) { switch (output_mode) { case TB_OUTPUT_TRUECOLOR: return 16777216; case TB_OUTPUT_256: return 256; default: return 16; } } static bool ui_term_backend_init(Ui *tui, char *term) { if (tb_init() < 0) return false; output_mode = tb_select_output_mode(TB_OUTPUT_TRUECOLOR); if (output_mode != TB_OUTPUT_TRUECOLOR) output_mode = tb_select_output_mode(TB_OUTPUT_256); if (output_mode != TB_OUTPUT_256) output_mode = TB_OUTPUT_NORMAL; tb_hide_cursor(); return true; } static bool ui_backend_init(Ui *ui) { return true; } void ui_terminal_resume(Ui *term) { } static void ui_term_backend_suspend(Ui *tui) { } static void ui_term_backend_free(Ui *tui) { tb_shutdown(); } static bool is_default_color(CellColor c) { return c.index == ((CellColor) CELL_COLOR_DEFAULT).index; }