diff options
Diffstat (limited to 'ui-terminal-termbox2.c')
| -rw-r--r-- | ui-terminal-termbox2.c | 226 |
1 files changed, 226 insertions, 0 deletions
diff --git a/ui-terminal-termbox2.c b/ui-terminal-termbox2.c new file mode 100644 index 0000000..2df68a1 --- /dev/null +++ b/ui-terminal-termbox2.c @@ -0,0 +1,226 @@ +/* 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; +} |
