skip to content
==> src/table.c <==
1/* ratatui-widgets 0.3.2's Table (StatefulWidget for &Table), ported. */
2#include "tui/table.h"
3
4#include <stdlib.h>
5#include <string.h>
6
7void tui_table_row_push(tui_arena *a, tui_table_row *r, tui_table_cell c) {
8    if (r->n == r->cap) {
9        size_t cap = r->cap ? r->cap * 2 : 8;
10        r->cells = tui_arena_grow(a, r->cells, r->cap * sizeof *r->cells, cap * sizeof *r->cells);
11        r->cap = cap;
12    }
13    r->cells[r->n++] = c;
14}
15
16static uint16_t height_with_margin(const tui_table_row *r) {
17    return tui_sat_add(tui_sat_add(r->height, r->top_margin), r->bottom_margin);
18}
19
20static void render_cell(tui_arena *a, const tui_table_cell *c, tui_rect area, tui_buffer *buf) {
21    tui_buffer_set_style(buf, area, c->style);
22    tui_text_render(a, &c->content, area, buf);
23}
24
25static void render_edge(tui_arena *a, const tui_table_row *r, tui_rect area, tui_buffer *buf, const tui_rect *cols, size_t ncols) {
26    if (!r) return;
27    tui_buffer_set_style(buf, area, r->style);
28    for (size_t i = 0; i < ncols && i < r->n; i++) {
29        tui_rect ca = tui_rect_new((uint16_t)(area.x + cols[i].x), area.y, cols[i].width, area.height);
30        render_cell(a, &r->cells[i], ca, buf);
31    }
32}
33
34static void visible_rows(const tui_table *t, const tui_table_state *st, tui_rect area, size_t *s, size_t *e) {
35    size_t last = t->nrows ? t->nrows - 1 : 0;
36    size_t start = st->offset < last ? st->offset : last;
37    if (st->has_selected && st->selected < start) start = st->selected;
38    size_t end = start;
39    uint32_t height = 0;
40    for (size_t i = start; i < t->nrows; i++) {
41        if (height + t->rows[i].height > area.height) break;
42        height += height_with_margin(&t->rows[i]);
43        end++;
44    }
45    if (st->has_selected) {
46        size_t sel = st->selected < last ? st->selected : last;
47        while (sel >= end) {
48            height += height_with_margin(&t->rows[end]);
49            end++;
50            while (height > area.height) {
51                height = height > height_with_margin(&t->rows[start]) ? height - height_with_margin(&t->rows[start]) : 0;
52                start++;
53            }
54        }
55    }
56    if (height < area.height && end < t->nrows) end++;
57    *s = start;
58    *e = end;
59}
60
61void tui_table_render(tui_arena *a, const tui_table *t, tui_rect area, tui_buffer *buf, tui_table_state *state) {
62    tui_table_state local = {0};
63    if (!state) state = &local;
64    tui_buffer_set_style(buf, area, t->style);
65    if (t->block) tui_block_render(a, t->block, area, buf);
66    tui_rect ta = tui_block_inner_opt(t->block, area);
67    if (tui_rect_empty(ta)) return;
68    if (state->has_selected && state->selected >= t->nrows) state->selected = t->nrows ? t->nrows - 1 : 0;
69    if (t->nrows == 0) state->has_selected = false;
70    size_t ncols = 0;
71    for (size_t i = 0; i < t->nrows; i++)
72        if (t->rows[i].n > ncols) ncols = t->rows[i].n;
73    if (t->footer && t->footer->n > ncols) ncols = t->footer->n;
74    if (t->header && t->header->n > ncols) ncols = t->header->n;
75    if (state->has_selected_column && state->selected_column >= ncols) state->selected_column = ncols ? ncols - 1 : 0;
76    if (ncols == 0) state->has_selected_column = false;
77    bool add_sel = t->highlight_spacing == TUI_HIGHLIGHT_ALWAYS ||
78                   (t->highlight_spacing == TUI_HIGHLIGHT_WHEN_SELECTED && state->has_selected);
79    uint16_t sel_w = add_sel ? (uint16_t)tui_text_width(&t->highlight_symbol) : 0;
80
81    /* Column widths: the selection column, then the widths' layout. */
82    size_t nw = t->nwidths;
83    tui_constraint *widths = NULL;
84    if (nw == 0) {
85        nw = ncols;
86        widths = tui_arena_alloc(a, (nw ? nw : 1) * sizeof *widths);
87        for (size_t i = 0; i < nw; i++) widths[i] = TUI_LENGTH(ta.width / (ncols ? ncols : 1));
88    } else {
89        widths = tui_arena_alloc(a, nw * sizeof *widths);
90        memcpy(widths, t->widths, nw * sizeof *widths);
91    }
92    tui_rect two[2];
93    tui_hsplit((tui_rect){0, 0, ta.width, 1}, 2, (tui_constraint[]){TUI_LENGTH(sel_w), TUI_FILL(0)}, two);
94    tui_rect *cols = tui_arena_alloc(a, (nw ? nw : 1) * sizeof *cols);
95    tui_layout cl = {.vertical = false, .constraints = widths, .n = nw, .flex = t->flex, .spacing = (int16_t)t->column_spacing};
96    tui_layout_split(&cl, two[1], cols, NULL);
97    for (size_t i = 0; i < nw; i++) cols[i] = tui_rect_new(cols[i].x, 0, cols[i].width, 1);
98
99    /* Header, rows, footer areas. */
100    tui_rect parts[7];
101    tui_constraint pc[7] = {
102        TUI_LENGTH(t->header ? t->header->top_margin : 0), TUI_LENGTH(t->header ? t->header->height : 0),
103        TUI_LENGTH(t->header ? t->header->bottom_margin : 0), TUI_MIN(0),
104        TUI_LENGTH(t->footer ? t->footer->top_margin : 0), TUI_LENGTH(t->footer ? t->footer->height : 0),
105        TUI_LENGTH(t->footer ? t->footer->bottom_margin : 0),
106    };
107    tui_vsplit(ta, 7, pc, parts);
108    render_edge(a, t->header, parts[1], buf, cols, nw);
109
110    tui_rect rows_area = parts[3];
111    if (t->nrows) {
112        size_t start, end;
113        visible_rows(t, state, rows_area, &start, &end);
114        state->offset = start;
115        uint16_t y_off = 0;
116        bool has_row_area = false;
117        tui_rect sel_row = {0, 0, 0, 0};
118        for (size_t i = start; i < end; i++) {
119            const tui_table_row *r = &t->rows[i];
120            uint16_t y = (uint16_t)(rows_area.y + y_off + r->top_margin);
121            uint16_t h = tui_sat_sub(tui_min16((uint16_t)(y + r->height), tui_rect_bottom(rows_area)), y);
122            tui_rect ra = {rows_area.x, y, rows_area.width, h};
123            tui_buffer_set_style(buf, ra, r->style);
124            bool is_sel = state->has_selected && state->selected == i;
125            if (sel_w > 0 && is_sel) {
126                tui_rect sa = ra;
127                sa.width = sel_w;
128                tui_buffer_set_style(buf, sa, r->style);
129                tui_text_render(a, &t->highlight_symbol, sa, buf);
130            }
131            /* Cells, spanning columns. */
132            size_t ci = 0;
133            for (size_t k = 0; k < r->n; k++) {
134                uint16_t span = r->cells[k].column_span;
135                if (span == 0) continue;
136                if (ci >= nw) break;
137                tui_rect first = cols[ci++];
138                uint16_t width = first.width;
139                uint16_t taken = 1;
140                for (uint16_t s = 1; s < span && ci < nw; s++, ci++) {
141                    width = (uint16_t)(width + cols[ci].width);
142                    taken++;
143                }
144                width = (uint16_t)(width + (taken - 1) * t->column_spacing);
145                tui_rect ca = tui_rect_new((uint16_t)(ra.x + first.x), ra.y, width, ra.height);
146                render_cell(a, &r->cells[k], ca, buf);
147            }
148            if (is_sel) {
149                sel_row = ra;
150                has_row_area = true;
151            }
152            y_off = (uint16_t)(y_off + height_with_margin(r));
153        }
154        bool has_col = state->has_selected_column && state->selected_column < nw;
155        tui_rect col_area = {0, 0, 0, 0};
156        if (has_col) col_area = (tui_rect){(uint16_t)(cols[state->selected_column].x + rows_area.x), rows_area.y,
157                                           cols[state->selected_column].width, rows_area.height};
158        if (has_row_area && has_col) {
159            tui_buffer_set_style(buf, sel_row, t->row_highlight_style);
160            tui_buffer_set_style(buf, col_area, t->column_highlight_style);
161            tui_buffer_set_style(buf, tui_rect_intersection(sel_row, col_area), t->cell_highlight_style);
162        } else if (has_row_area) {
163            tui_buffer_set_style(buf, sel_row, t->row_highlight_style);
164        } else if (has_col) {
165            tui_buffer_set_style(buf, col_area, t->column_highlight_style);
166        }
167    }
168    render_edge(a, t->footer, parts[5], buf, cols, nw);
169}