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