==> src/layout.c <==1/* ratatui-core 0.1.2's Layout::try_split, ported constraint for constraint. The order constraints are
2 * added in is ratatui's, so the solver pivots the same way. */
3#include "tui/layout.h"
4
5#include "tui/solver.h"
6
7#include <math.h>
8#include <stdlib.h>
9#include <string.h>
10
11#define PRECISION 100.0 /* FLOAT_PRECISION_MULTIPLIER */
12
13/* ratatui's strengths (layout.rs `mod strengths`). */
14#define S_REQUIRED TUI_STRENGTH_REQUIRED
15#define SPACER_SIZE_EQ (TUI_STRENGTH_REQUIRED / 10.0)
16#define MIN_SIZE_GE (TUI_STRENGTH_STRONG * 100.0)
17#define MAX_SIZE_LE (TUI_STRENGTH_STRONG * 100.0)
18#define LENGTH_SIZE_EQ (TUI_STRENGTH_STRONG * 10.0)
19#define PERCENTAGE_SIZE_EQ TUI_STRENGTH_STRONG
20#define RATIO_SIZE_EQ (TUI_STRENGTH_STRONG / 10.0)
21#define MIN_SIZE_EQ (TUI_STRENGTH_MEDIUM * 10.0)
22#define MAX_SIZE_EQ (TUI_STRENGTH_MEDIUM * 10.0)
23#define FILL_GROW TUI_STRENGTH_MEDIUM
24#define GROW (TUI_STRENGTH_MEDIUM / 10.0)
25#define SPACE_GROW (TUI_STRENGTH_WEAK * 10.0)
26#define ALL_SEGMENT_GROW TUI_STRENGTH_WEAK
27#define EMPTY_STRENGTH (TUI_STRENGTH_REQUIRED - TUI_STRENGTH_WEAK) /* Element::is_empty */
28
29typedef struct {
30 uint32_t start, end;
31} element;
32
33static tui_expr size(element e) { return tui_expr_sub(tui_expr_var(e.end), tui_expr_var(e.start)); }
34
35static void add(tui_solver *s, tui_expr lhs, tui_relation op, double strength, tui_expr rhs) {
36 (void)tui_solver_add(s, lhs, op, strength, rhs); /* ratatui's constraints are always satisfiable */
37}
38
39static void has_size(tui_solver *s, element e, tui_expr sz, double st) { add(s, size(e), TUI_EQ, st, sz); }
40static void has_int_size(tui_solver *s, element e, uint32_t n, double st) {
41 add(s, size(e), TUI_EQ, st, tui_expr_const((double)(uint16_t)n * PRECISION));
42}
43static void has_min_size(tui_solver *s, element e, int16_t n, double st) {
44 add(s, size(e), TUI_GE, st, tui_expr_const((double)n * PRECISION));
45}
46static void has_max_size(tui_solver *s, element e, uint32_t n, double st) {
47 add(s, size(e), TUI_LE, st, tui_expr_const((double)(uint16_t)n * PRECISION));
48}
49static void has_double_size(tui_solver *s, element e, tui_expr sz, double st) {
50 add(s, size(e), TUI_EQ, st, tui_expr_scale(sz, 2.0));
51}
52static void is_empty(tui_solver *s, element e) { add(s, size(e), TUI_EQ, EMPTY_STRENGTH, tui_expr_const(0.0)); }
53
54static void flex_constraints(tui_solver *s, element area, const element *sp, size_t nsp, tui_flex flex, int16_t spacing) {
55 double spacing_f = (double)spacing * PRECISION;
56 const element *mid = nsp > 2 ? sp + 1 : NULL;
57 size_t nmid = nsp > 2 ? nsp - 2 : 0;
58 switch (flex) {
59 case TUI_FLEX_LEGACY:
60 for (size_t i = 0; i < nmid; i++) has_size(s, mid[i], tui_expr_const(spacing_f), SPACER_SIZE_EQ);
61 if (nsp) {
62 is_empty(s, sp[0]);
63 is_empty(s, sp[nsp - 1]);
64 }
65 break;
66 case TUI_FLEX_SPACE_AROUND:
67 if (nsp <= 2) {
68 for (size_t i = 0; i < nsp; i++)
69 for (size_t j = i + 1; j < nsp; j++) has_size(s, sp[i], size(sp[j]), SPACER_SIZE_EQ);
70 for (size_t i = 0; i < nsp; i++) {
71 has_min_size(s, sp[i], spacing, SPACER_SIZE_EQ);
72 has_size(s, sp[i], size(area), SPACE_GROW);
73 }
74 } else {
75 for (size_t i = 0; i < nmid; i++)
76 for (size_t j = i + 1; j < nmid; j++) has_size(s, mid[i], size(mid[j]), SPACER_SIZE_EQ);
77 has_double_size(s, mid[0], size(sp[0]), SPACER_SIZE_EQ);
78 has_double_size(s, mid[0], size(sp[nsp - 1]), SPACER_SIZE_EQ);
79 for (size_t i = 0; i < nsp; i++) {
80 has_min_size(s, sp[i], spacing, SPACER_SIZE_EQ);
81 has_size(s, sp[i], size(area), SPACE_GROW);
82 }
83 }
84 break;
85 case TUI_FLEX_SPACE_EVENLY:
86 for (size_t i = 0; i < nsp; i++)
87 for (size_t j = i + 1; j < nsp; j++) has_size(s, sp[i], size(sp[j]), SPACER_SIZE_EQ);
88 for (size_t i = 0; i < nsp; i++) {
89 has_min_size(s, sp[i], spacing, SPACER_SIZE_EQ);
90 has_size(s, sp[i], size(area), SPACE_GROW);
91 }
92 break;
93 case TUI_FLEX_SPACE_BETWEEN:
94 for (size_t i = 0; i < nmid; i++)
95 for (size_t j = i + 1; j < nmid; j++) has_size(s, mid[i], size(mid[j]), SPACER_SIZE_EQ);
96 for (size_t i = 0; i < nmid; i++) {
97 has_min_size(s, mid[i], spacing, SPACER_SIZE_EQ);
98 has_size(s, mid[i], size(area), SPACE_GROW);
99 }
100 if (nsp) {
101 is_empty(s, sp[0]);
102 is_empty(s, sp[nsp - 1]);
103 }
104 break;
105 case TUI_FLEX_START:
106 for (size_t i = 0; i < nmid; i++) has_size(s, mid[i], tui_expr_const(spacing_f), SPACER_SIZE_EQ);
107 if (nsp) {
108 is_empty(s, sp[0]);
109 has_size(s, sp[nsp - 1], size(area), GROW);
110 }
111 break;
112 case TUI_FLEX_CENTER:
113 for (size_t i = 0; i < nmid; i++) has_size(s, mid[i], tui_expr_const(spacing_f), SPACER_SIZE_EQ);
114 if (nsp) {
115 has_size(s, sp[0], size(area), GROW);
116 has_size(s, sp[nsp - 1], size(area), GROW);
117 has_size(s, sp[0], size(sp[nsp - 1]), SPACER_SIZE_EQ);
118 }
119 break;
120 case TUI_FLEX_END:
121 for (size_t i = 0; i < nmid; i++) has_size(s, mid[i], tui_expr_const(spacing_f), SPACER_SIZE_EQ);
122 if (nsp) {
123 is_empty(s, sp[nsp - 1]);
124 has_size(s, sp[0], size(area), GROW);
125 }
126 break;
127 }
128}
129
130static void segment_constraints(tui_solver *s, element area, const element *seg, const tui_constraint *c, size_t n,
131 tui_flex flex) {
132 bool legacy = flex == TUI_FLEX_LEGACY;
133 for (size_t i = 0; i < n; i++) {
134 switch (c[i].kind) {
135 case TUI_C_MAX:
136 has_max_size(s, seg[i], c[i].a, MAX_SIZE_LE);
137 has_int_size(s, seg[i], c[i].a, MAX_SIZE_EQ);
138 break;
139 case TUI_C_MIN:
140 has_min_size(s, seg[i], (int16_t)(uint16_t)c[i].a, MIN_SIZE_GE);
141 if (legacy) has_int_size(s, seg[i], c[i].a, MIN_SIZE_EQ);
142 else has_size(s, seg[i], size(area), FILL_GROW);
143 break;
144 case TUI_C_LENGTH:
145 has_int_size(s, seg[i], c[i].a, LENGTH_SIZE_EQ);
146 break;
147 case TUI_C_PERCENTAGE:
148 has_size(s, seg[i], tui_expr_div(tui_expr_scale(size(area), (double)(uint16_t)c[i].a), 100.0),
149 PERCENTAGE_SIZE_EQ);
150 break;
151 case TUI_C_RATIO:
152 has_size(s, seg[i], tui_expr_div(tui_expr_scale(size(area), (double)c[i].a), (double)(c[i].b ? c[i].b : 1)),
153 RATIO_SIZE_EQ);
154 break;
155 case TUI_C_FILL:
156 has_size(s, seg[i], size(area), FILL_GROW);
157 break;
158 }
159 }
160}
161
162static void fill_constraints(tui_solver *s, const element *seg, const tui_constraint *c, size_t n, tui_flex flex) {
163 bool legacy = flex == TUI_FLEX_LEGACY;
164 for (size_t i = 0; i < n; i++) {
165 if (!(c[i].kind == TUI_C_FILL || (!legacy && c[i].kind == TUI_C_MIN))) continue;
166 for (size_t j = i + 1; j < n; j++) {
167 if (!(c[j].kind == TUI_C_FILL || (!legacy && c[j].kind == TUI_C_MIN))) continue;
168 double lf = c[i].kind == TUI_C_FILL ? fmax((double)(uint16_t)c[i].a, 1e-6) : 1.0;
169 double rf = c[j].kind == TUI_C_FILL ? fmax((double)(uint16_t)c[j].a, 1e-6) : 1.0;
170 add(s, tui_expr_scale(size(seg[i]), rf), TUI_EQ, GROW, tui_expr_scale(size(seg[j]), lf));
171 }
172 }
173}
174
175static uint16_t to_u16(double v) {
176 if (v != v || v <= 0) return 0;
177 if (v >= 65535) return 65535;
178 return (uint16_t)v;
179}
180
181static void solve(const tui_layout *l, tui_rect area, tui_rect *out, tui_rect *spacers_out) {
182 size_t n = l->n, nvars = n * 2 + 2;
183 tui_rect inner = tui_rect_inner(area, l->margin_h, l->margin_v);
184 double start = (double)(l->vertical ? inner.y : inner.x) * PRECISION;
185 double end = (double)(l->vertical ? tui_rect_bottom(inner) : tui_rect_right(inner)) * PRECISION;
186 element *sp = malloc((n + 1) * sizeof *sp);
187 element *seg = malloc((n ? n : 1) * sizeof *seg);
188 if (!sp || !seg) abort();
189 for (size_t i = 0; i < n + 1; i++) sp[i] = (element){(uint32_t)(2 * i), (uint32_t)(2 * i + 1)};
190 for (size_t i = 0; i < n; i++) seg[i] = (element){(uint32_t)(2 * i + 1), (uint32_t)(2 * i + 2)};
191 element areael = {0, (uint32_t)(nvars - 1)};
192 tui_solver *s = tui_solver_new();
193 add(s, tui_expr_var(areael.start), TUI_EQ, S_REQUIRED, tui_expr_const(start));
194 add(s, tui_expr_var(areael.end), TUI_EQ, S_REQUIRED, tui_expr_const(end));
195 for (uint32_t v = 0; v < nvars; v++) {
196 add(s, tui_expr_var(v), TUI_GE, S_REQUIRED, tui_expr_var(areael.start));
197 add(s, tui_expr_var(v), TUI_LE, S_REQUIRED, tui_expr_var(areael.end));
198 }
199 for (uint32_t v = 1; v + 1 < nvars; v += 2) add(s, tui_expr_var(v), TUI_LE, S_REQUIRED, tui_expr_var(v + 1));
200 flex_constraints(s, areael, sp, n + 1, l->flex, l->spacing);
201 segment_constraints(s, areael, seg, l->constraints, n, l->flex);
202 fill_constraints(s, seg, l->constraints, n, l->flex);
203 if (l->flex != TUI_FLEX_LEGACY)
204 for (size_t i = 0; i + 1 < n; i++) has_size(s, seg[i], size(seg[i + 1]), ALL_SEGMENT_GROW);
205 for (size_t i = 0; i < n + (spacers_out ? n + 1 : 0); i++) {
206 element e = i < n ? seg[i] : sp[i - n];
207 uint16_t a = to_u16(round(round(tui_solver_value(s, e.start)) / PRECISION));
208 uint16_t b = to_u16(round(round(tui_solver_value(s, e.end)) / PRECISION));
209 uint16_t sz = tui_sat_sub(b, a);
210 tui_rect r = l->vertical ? (tui_rect){inner.x, a, inner.width, sz} : (tui_rect){a, inner.y, sz, inner.height};
211 if (i < n) out[i] = r;
212 else spacers_out[i - n] = r;
213 }
214 tui_solver_free(s);
215 free(sp);
216 free(seg);
217}
218
219/* ---- the cache: (area, layout) -> segments and spacers ---- */
220
221enum { CACHE = 512 };
222
223typedef struct {
224 tui_rect area;
225 tui_layout l;
226 tui_constraint *c;
227 tui_rect *rects; /* n segments then n + 1 spacers */
228} entry;
229
230static entry cache[CACHE];
231static size_t cache_next;
232
233static bool same(const entry *e, const tui_layout *l, tui_rect area) {
234 return tui_rect_eq(e->area, area) && e->l.vertical == l->vertical && e->l.n == l->n && e->l.margin_h == l->margin_h &&
235 e->l.margin_v == l->margin_v && e->l.flex == l->flex && e->l.spacing == l->spacing &&
236 (l->n == 0 || memcmp(e->c, l->constraints, l->n * sizeof *l->constraints) == 0);
237}
238
239void tui_layout_cache_clear(void) {
240 for (size_t i = 0; i < CACHE; i++) {
241 free(cache[i].c);
242 free(cache[i].rects);
243 cache[i] = (entry){{0, 0, 0, 0}, {0}, NULL, NULL};
244 }
245 cache_next = 0;
246}
247
248void tui_layout_split(const tui_layout *l, tui_rect area, tui_rect *out, tui_rect *spacers) {
249 for (size_t i = 0; i < CACHE; i++) {
250 if (cache[i].rects && same(&cache[i], l, area)) {
251 memcpy(out, cache[i].rects, l->n * sizeof *out);
252 if (spacers) memcpy(spacers, cache[i].rects + l->n, (l->n + 1) * sizeof *spacers);
253 return;
254 }
255 }
256 tui_rect *all = malloc((2 * l->n + 1) * sizeof *all);
257 if (!all) abort();
258 solve(l, area, all, all + l->n);
259 memcpy(out, all, l->n * sizeof *out);
260 if (spacers) memcpy(spacers, all + l->n, (l->n + 1) * sizeof *spacers);
261 entry *e = &cache[cache_next];
262 cache_next = (cache_next + 1) % CACHE;
263 free(e->c);
264 free(e->rects);
265 e->area = area;
266 e->l = *l;
267 e->c = malloc((l->n ? l->n : 1) * sizeof *e->c);
268 if (!e->c) abort();
269 if (l->n) memcpy(e->c, l->constraints, l->n * sizeof *e->c);
270 e->l.constraints = e->c;
271 e->rects = all;
272}
273
274void tui_vsplit(tui_rect area, size_t n, const tui_constraint *c, tui_rect *out) {
275 tui_layout l = {.vertical = true, .constraints = c, .n = n, .flex = TUI_FLEX_START};
276 tui_layout_split(&l, area, out, NULL);
277}
278
279void tui_hsplit(tui_rect area, size_t n, const tui_constraint *c, tui_rect *out) {
280 tui_layout l = {.vertical = false, .constraints = c, .n = n, .flex = TUI_FLEX_START};
281 tui_layout_split(&l, area, out, NULL);
282}