aboutsummaryrefslogtreecommitdiff
path: root/ui-terminal-termbox2.c
diff options
context:
space:
mode:
authorMitchell Riedstra <mitch@riedstra.dev>2026-01-13 17:49:53 -0500
committerMitchell Riedstra <mitch@riedstra.dev>2026-01-13 17:49:53 -0500
commit3aad7f9f9f2b72551d359033783cb2d95a691b82 (patch)
treef51796fb5fb3f6ad8345efb3a2aec387e8edc746 /ui-terminal-termbox2.c
parent43f321d18256842c9aae29faeafa6815c579a9be (diff)
downloadvis-3aad7f9f9f2b72551d359033783cb2d95a691b82.tar.gz
vis-3aad7f9f9f2b72551d359033783cb2d95a691b82.tar.xz
Experiment with termbox2 for renderingHEADmaster
Diffstat (limited to 'ui-terminal-termbox2.c')
-rw-r--r--ui-terminal-termbox2.c226
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;
+}