==> 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}