skip to content
==> src/block.c <==
1/* ratatui-widgets 0.3.2's Block: sides, then corners, then titles (left, centre, right per edge). */
2#include "tui/block.h"
3
4#include <string.h>
5
6const tui_border_set TUI_BORDER_PLAIN = {"┌", "┐", "└", "┘", "│", "│", "─", "─"};
7const tui_border_set TUI_BORDER_ROUNDED = {"╭", "╮", "╰", "╯", "│", "│", "─", "─"};
8const tui_border_set TUI_BORDER_DOUBLE = {"╔", "╗", "╚", "╝", "║", "║", "═", "═"};
9const tui_border_set TUI_BORDER_THICK = {"┏", "┓", "┗", "┛", "┃", "┃", "━", "━"};
10
11void tui_block_add_title(tui_arena *a, tui_block *b, tui_line line, int8_t position) {
12    if (b->ntitles == b->captitles) {
13        size_t cap = b->captitles ? b->captitles * 2 : 2;
14        b->titles = tui_arena_grow(a, b->titles, b->captitles * sizeof *b->titles, cap * sizeof *b->titles);
15        b->captitles = cap;
16    }
17    b->titles[b->ntitles++] = (tui_block_title){line, position};
18}
19
20static int8_t pos_of(const tui_block *b, const tui_block_title *t) {
21    return t->position == TUI_TITLE_DEFAULT ? b->titles_position : t->position;
22}
23
24static bool has_title_at(const tui_block *b, int8_t pos) {
25    for (size_t i = 0; i < b->ntitles; i++)
26        if (pos_of(b, &b->titles[i]) == pos) return true;
27    return false;
28}
29
30tui_rect tui_block_inner(const tui_block *b, tui_rect area) {
31    tui_rect r = area;
32    if (b->borders & TUI_BORDER_LEFT) {
33        r.x = tui_min16(tui_sat_add(r.x, 1), tui_rect_right(r));
34        r.width = tui_sat_sub(r.width, 1);
35    }
36    if ((b->borders & TUI_BORDER_TOP) || has_title_at(b, TUI_TITLE_TOP)) {
37        r.y = tui_min16(tui_sat_add(r.y, 1), tui_rect_bottom(r));
38        r.height = tui_sat_sub(r.height, 1);
39    }
40    if (b->borders & TUI_BORDER_RIGHT) r.width = tui_sat_sub(r.width, 1);
41    if ((b->borders & TUI_BORDER_BOTTOM) || has_title_at(b, TUI_TITLE_BOTTOM)) r.height = tui_sat_sub(r.height, 1);
42    r.x = tui_sat_add(r.x, b->pad_left);
43    r.y = tui_sat_add(r.y, b->pad_top);
44    r.width = tui_sat_sub(r.width, tui_sat_add(b->pad_left, b->pad_right));
45    r.height = tui_sat_sub(r.height, tui_sat_add(b->pad_top, b->pad_bottom));
46    return r;
47}
48
49static void put(tui_buffer *buf, uint16_t x, uint16_t y, const char *sym, tui_style st) {
50    tui_cell *c = tui_buffer_cell(buf, x, y);
51    if (!c) return;
52    tui_cell_set_str(c, sym);
53    tui_cell_set_style(c, st);
54}
55
56static void render_borders(const tui_block *b, tui_rect area, tui_buffer *buf) {
57    const tui_border_set *s = b->set ? b->set : &TUI_BORDER_PLAIN;
58    uint16_t left = area.x, top = area.y;
59    uint16_t right = tui_sat_sub(tui_rect_right(area), 1), bottom = tui_sat_sub(tui_rect_bottom(area), 1);
60    if (b->borders & TUI_BORDER_LEFT)
61        for (uint16_t y = top; y <= bottom; y++) put(buf, left, y, s->vertical_left, b->border_style);
62    if (b->borders & TUI_BORDER_TOP)
63        for (uint16_t x = left; x <= right; x++) put(buf, x, top, s->horizontal_top, b->border_style);
64    if (b->borders & TUI_BORDER_RIGHT)
65        for (uint16_t y = top; y <= bottom; y++) put(buf, right, y, s->vertical_right, b->border_style);
66    if (b->borders & TUI_BORDER_BOTTOM)
67        for (uint16_t x = left; x <= right; x++) put(buf, x, bottom, s->horizontal_bottom, b->border_style);
68    if ((b->borders & (TUI_BORDER_RIGHT | TUI_BORDER_BOTTOM)) == (TUI_BORDER_RIGHT | TUI_BORDER_BOTTOM))
69        put(buf, right, bottom, s->bottom_right, b->border_style);
70    if ((b->borders & (TUI_BORDER_RIGHT | TUI_BORDER_TOP)) == (TUI_BORDER_RIGHT | TUI_BORDER_TOP))
71        put(buf, right, top, s->top_right, b->border_style);
72    if ((b->borders & (TUI_BORDER_LEFT | TUI_BORDER_BOTTOM)) == (TUI_BORDER_LEFT | TUI_BORDER_BOTTOM))
73        put(buf, left, bottom, s->bottom_left, b->border_style);
74    if ((b->borders & (TUI_BORDER_LEFT | TUI_BORDER_TOP)) == (TUI_BORDER_LEFT | TUI_BORDER_TOP))
75        put(buf, left, top, s->top_left, b->border_style);
76}
77
78static tui_rect titles_area(const tui_block *b, tui_rect area, int8_t pos) {
79    uint16_t lb = (b->borders & TUI_BORDER_LEFT) ? 1 : 0, rb = (b->borders & TUI_BORDER_RIGHT) ? 1 : 0;
80    return (tui_rect){tui_sat_add(area.x, lb), pos == TUI_TITLE_TOP ? area.y : tui_sat_sub(tui_rect_bottom(area), 1),
81                      tui_sat_sub(tui_sat_sub(area.width, lb), rb), 1};
82}
83
84static uint16_t lw(const tui_line *l) {
85    size_t w = tui_line_width(l);
86    return (uint16_t)(w > 0xFFFF ? 0xFFFF : w);
87}
88
89static bool matches(const tui_block *b, const tui_block_title *t, int8_t pos, int8_t align) {
90    int8_t a = t->line.align != TUI_ALIGN_NONE ? t->line.align : b->titles_alignment;
91    return pos_of(b, t) == pos && a == align;
92}
93
94static void left_titles(tui_arena *a, const tui_block *b, int8_t pos, tui_rect area, tui_buffer *buf) {
95    tui_rect ta = titles_area(b, area, pos);
96    for (size_t i = 0; i < b->ntitles; i++) {
97        if (!matches(b, &b->titles[i], pos, TUI_ALIGN_LEFT)) continue;
98        if (tui_rect_empty(ta)) break;
99        uint16_t w = lw(&b->titles[i].line);
100        tui_rect r = ta;
101        r.width = tui_min16(w, ta.width);
102        tui_buffer_set_style(buf, r, b->titles_style);
103        tui_line_render(a, &b->titles[i].line, r, buf, TUI_ALIGN_NONE);
104        uint16_t adv = tui_sat_add(w, 1);
105        ta.x = tui_sat_add(ta.x, adv);
106        ta.width = tui_sat_sub(ta.width, adv);
107    }
108}
109
110static void right_titles(tui_arena *a, const tui_block *b, int8_t pos, tui_rect area, tui_buffer *buf) {
111    tui_rect ta = titles_area(b, area, pos);
112    for (size_t k = b->ntitles; k-- > 0;) {
113        if (!matches(b, &b->titles[k], pos, TUI_ALIGN_RIGHT)) continue;
114        if (tui_rect_empty(ta)) break;
115        uint16_t w = lw(&b->titles[k].line);
116        tui_rect r = ta;
117        r.x = tui_max16(tui_sat_sub(tui_rect_right(ta), w), ta.x);
118        r.width = tui_min16(w, ta.width);
119        tui_buffer_set_style(buf, r, b->titles_style);
120        tui_line_render(a, &b->titles[k].line, r, buf, TUI_ALIGN_NONE);
121        ta.width = tui_sat_sub(tui_sat_sub(ta.width, w), 1);
122    }
123}
124
125static void center_titles(tui_arena *a, const tui_block *b, int8_t pos, tui_rect area, tui_buffer *buf) {
126    tui_rect ta = titles_area(b, area, pos);
127    uint16_t total = 0;
128    size_t count = 0;
129    for (size_t i = 0; i < b->ntitles; i++)
130        if (matches(b, &b->titles[i], pos, TUI_ALIGN_CENTER)) {
131            total = tui_sat_add(total, tui_sat_add(lw(&b->titles[i].line), 1));
132            count++;
133        }
134    if (!count) return;
135    total = tui_sat_sub(total, 1);
136    if (total <= ta.width) {
137        uint16_t x = tui_sat_add(ta.x, (uint16_t)(tui_sat_sub(ta.width, total) / 2));
138        tui_rect r = ta;
139        r.x = x;
140        for (size_t i = 0; i < b->ntitles; i++) {
141            if (!matches(b, &b->titles[i], pos, TUI_ALIGN_CENTER)) continue;
142            uint16_t w = lw(&b->titles[i].line);
143            tui_rect t = r;
144            t.width = w;
145            tui_buffer_set_style(buf, t, b->titles_style);
146            tui_line_render(a, &b->titles[i].line, t, buf, TUI_ALIGN_NONE);
147            uint16_t adv = tui_sat_add(w, 1);
148            r.x = tui_sat_add(r.x, adv);
149            r.width = tui_sat_sub(r.width, adv);
150        }
151    } else {
152        uint16_t offset = (uint16_t)(tui_sat_sub(total, ta.width) / 2);
153        for (size_t i = 0; i < b->ntitles; i++) {
154            if (!matches(b, &b->titles[i], pos, TUI_ALIGN_CENTER)) continue;
155            if (tui_rect_empty(ta)) break;
156            uint16_t w = tui_sat_sub(tui_min16(ta.width, lw(&b->titles[i].line)), offset);
157            tui_rect t = ta;
158            t.width = w;
159            tui_buffer_set_style(buf, t, b->titles_style);
160            tui_line l = b->titles[i].line;
161            if (offset > 0) {
162                l.align = TUI_ALIGN_RIGHT;
163                tui_line_render(a, &l, t, buf, TUI_ALIGN_NONE);
164                offset = tui_sat_sub(tui_sat_sub(offset, w), 1);
165            } else {
166                l.align = TUI_ALIGN_LEFT;
167                tui_line_render(a, &l, t, buf, TUI_ALIGN_NONE);
168            }
169            uint16_t adv = tui_sat_add(w, 1);
170            ta.x = tui_sat_add(ta.x, adv);
171            ta.width = tui_sat_sub(ta.width, adv);
172        }
173    }
174}
175
176void tui_block_render(tui_arena *a, const tui_block *b, tui_rect area, tui_buffer *buf) {
177    area = tui_rect_intersection(area, buf->area);
178    if (tui_rect_empty(area)) return;
179    tui_buffer_set_style(buf, area, b->style);
180    render_borders(b, area, buf);
181    for (int8_t pos = TUI_TITLE_TOP; pos <= TUI_TITLE_BOTTOM; pos++) {
182        left_titles(a, b, pos, area, buf);
183        center_titles(a, b, pos, area, buf);
184        right_titles(a, b, pos, area, buf);
185    }
186}