skip to content
==> 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}