skip to content
==> src/kit.c <==
1/* ui.rs's palette and helpers, ported. Float math stays in f32 where the Rust's does. */
2#include "mesh/top_ui_kit.h"
3
4#include <math.h>
5#include <stdio.h>
6#include <string.h>
7
8const tui_color MT_MACHINE[8] = {
9    {TUI_COLOR_RGB, 90, 210, 255}, {TUI_COLOR_RGB, 255, 150, 80}, {TUI_COLOR_RGB, 150, 240, 120}, {TUI_COLOR_RGB, 240, 120, 200},
10    {TUI_COLOR_RGB, 250, 230, 90}, {TUI_COLOR_RGB, 170, 140, 255}, {TUI_COLOR_RGB, 80, 230, 200}, {TUI_COLOR_RGB, 230, 230, 230},
11};
12
13const uint8_t MT_WALL_RGB[7][3] = {
14    {210, 60, 50}, {70, 185, 85}, {70, 115, 225}, {225, 190, 60}, {175, 85, 210}, {60, 195, 205}, {205, 205, 210},
15};
16
17static const struct {
18    const char *name;
19    uint8_t rgb[3];
20} NAMED_RGB[4] = {{"doom", {255, 110, 60}}, {"train", {80, 220, 110}}, {"infer", {80, 160, 255}}, {"http", {200, 110, 255}}};
21static const uint8_t SLOT_RGB[6][3] = {{240, 200, 70}, {60, 200, 200}, {240, 120, 180}, {170, 200, 90}, {150, 140, 255}, {230, 150, 90}};
22
23tui_color mt_sys_color(uint32_t k) { return MT_MACHINE[k % 8]; }
24
25void mt_prog_rgb(const char *name, uint32_t slot, uint8_t out[3]) {
26    for (size_t i = 0; i < 4; i++)
27        if (strcmp(NAMED_RGB[i].name, name) == 0) {
28            memcpy(out, NAMED_RGB[i].rgb, 3);
29            return;
30        }
31    memcpy(out, SLOT_RGB[slot % 6], 3);
32}
33
34uint8_t mt_sat_u8(float v) {
35    if (!(v > 0.0f)) return 0;
36    if (v >= 255.0f) return 255;
37    return (uint8_t)v;
38}
39
40uint16_t mt_sat_u16(double v) {
41    if (!(v > 0.0)) return 0;
42    if (v >= 65535.0) return 65535;
43    return (uint16_t)v;
44}
45
46uint32_t mt_sat_u32(double v) {
47    if (!(v > 0.0)) return 0;
48    if (v >= 4294967295.0) return UINT32_MAX;
49    return (uint32_t)v;
50}
51
52uint64_t mt_sat_u64(double v) {
53    if (!(v > 0.0)) return 0;
54    if (v >= 18446744073709551615.0) return UINT64_MAX;
55    return (uint64_t)v;
56}
57
58int32_t mt_sat_i32(double v) {
59    if (v != v) return 0;
60    if (v <= -2147483648.0) return INT32_MIN;
61    if (v >= 2147483647.0) return INT32_MAX;
62    return (int32_t)v;
63}
64
65static float clampf(float v, float lo, float hi) { return v < lo ? lo : v > hi ? hi : v; }
66
67tui_color mt_rgb(const uint8_t c[3], float k) {
68    uint8_t o[3];
69    for (int i = 0; i < 3; i++) o[i] = mt_sat_u8(clampf((float)c[i] * k, 0.0f, 255.0f));
70    return TUI_RGB(o[0], o[1], o[2]);
71}
72
73tui_color mt_lerp(const uint8_t a[3], const uint8_t b[3], float t) {
74    t = clampf(t, 0.0f, 1.0f);
75    uint8_t o[3];
76    for (int i = 0; i < 3; i++) o[i] = mt_sat_u8((float)a[i] + ((float)b[i] - (float)a[i]) * t);
77    return TUI_RGB(o[0], o[1], o[2]);
78}
79
80tui_color mt_prog_color(const char *name, uint32_t slot, float k) {
81    uint8_t c[3];
82    mt_prog_rgb(name, slot, c);
83    return mt_rgb(c, k);
84}
85
86/* ---- text */
87
88const char *mt_ff(mt_frame *f, double v, int prec) {
89    char b[400];
90    rsfmt_fixed(b, sizeof b, v, prec, 0);
91    return tui_arena_strdup(f->a, b);
92}
93
94const char *mt_ffp(mt_frame *f, double v, int prec) {
95    char b[400];
96    rsfmt_fixed(b, sizeof b, v, prec, RSFMT_PLUS);
97    return tui_arena_strdup(f->a, b);
98}
99
100const char *mt_fe(mt_frame *f, double v, int prec) {
101    char b[64];
102    rsfmt_exp(b, sizeof b, v, prec, 0);
103    return tui_arena_strdup(f->a, b);
104}
105
106const char *mt_pad(mt_frame *f, const char *s, int w, char align) {
107    size_t n = strlen(s) + (w > 0 ? (size_t)w : 0) + 1;
108    char *o = tui_arena_alloc(f->a, n);
109    rsfmt_pad(o, n, s, w, align, ' ');
110    return o;
111}
112
113const char *mt_take(mt_frame *f, const char *s, size_t n) { return tui_arena_strndup(f->a, s, rsfmt_prefix(s, n)); }
114
115const char *mt_name(mt_frame *f, const mt_machine *m) {
116    size_t cap = 98 + strlen(m->endpoint);
117    char *o = tui_arena_alloc(f->a, cap);
118    mt_machine_name(m, o, cap);
119    return o;
120}
121
122tui_table_cell mt_cell_str(mt_frame *f, const char *s) { return tui_table_cell_text(tui_text_from_str(f->a, s)); }
123
124tui_table_cell mt_cell_span(mt_frame *f, tui_span s) { return tui_table_cell_text(tui_text_line(f->a, tui_line_span(f->a, s))); }
125
126tui_table_cell mt_cell_line(mt_frame *f, tui_line l) { return tui_table_cell_text(tui_text_line(f->a, l)); }
127
128tui_table_cell mt_cell_styled(mt_frame *f, const char *s, tui_style st) {
129    tui_table_cell c = mt_cell_str(f, s);
130    c.style = st;
131    return c;
132}
133
134tui_table_row mt_row(mt_frame *f, const tui_table_cell *cells, size_t n) {
135    tui_table_row r = tui_table_row_new();
136    for (size_t i = 0; i < n; i++) tui_table_row_push(f->a, &r, cells[i]);
137    return r;
138}
139
140tui_table_row mt_header(mt_frame *f, const char *const *names, size_t n) {
141    tui_table_row r = tui_table_row_new();
142    for (size_t i = 0; i < n; i++) tui_table_row_push(f->a, &r, mt_cell_span(f, mt_label(names[i])));
143    return r;
144}
145
146void mt_rows_push(mt_frame *f, mt_rows *r, tui_table_row row) {
147    if (r->n == r->cap) {
148        size_t cap = r->cap ? r->cap * 2 : 8;
149        r->rows = tui_arena_grow(f->a, r->rows, r->cap * sizeof *r->rows, cap * sizeof *r->rows);
150        r->cap = cap;
151    }
152    r->rows[r->n++] = row;
153}
154
155void mt_cells_push(mt_frame *f, mt_cells *c, tui_table_cell cell) {
156    if (c->n == c->cap) {
157        size_t cap = c->cap ? c->cap * 2 : 16;
158        c->c = tui_arena_grow(f->a, c->c, c->cap * sizeof *c->c, cap * sizeof *c->c);
159        c->cap = cap;
160    }
161    c->c[c->n++] = cell;
162}
163
164void mt_lines_push(mt_frame *f, mt_lines *l, tui_line line) {
165    if (l->n == l->cap) {
166        size_t cap = l->cap ? l->cap * 2 : 8;
167        l->l = tui_arena_grow(f->a, l->l, l->cap * sizeof *l->l, cap * sizeof *l->l);
168        l->cap = cap;
169    }
170    l->l[l->n++] = line;
171}
172
173tui_text mt_text(mt_frame *f, const mt_lines *l) { return tui_text_of(f->a, l->n, l->l); }
174
175tui_block mt_panel(mt_frame *f, tui_line title, tui_color color) {
176    tui_block b = tui_block_bordered();
177    b.set = &TUI_BORDER_ROUNDED;
178    b.border_style = tui_fg(color);
179    tui_block_add_title(f->a, &b, title, TUI_TITLE_DEFAULT);
180    b.style = tui_fg_bg(MT_FG, MT_BG);
181    return b;
182}
183
184void mt_par(mt_frame *f, tui_text t, tui_rect area) {
185    tui_paragraph p = tui_paragraph_new(t);
186    tui_paragraph_render(f->a, &p, area, f->buf);
187}
188
189void mt_par_line(mt_frame *f, tui_line l, tui_rect area) { mt_par(f, tui_text_line(f->a, l), area); }
190
191void mt_block(mt_frame *f, const tui_block *b, tui_rect area) { tui_block_render(f->a, b, area, f->buf); }
192
193void mt_table(mt_frame *f, const tui_table *t, tui_rect area) {
194    tui_table_state st = {0};
195    tui_table_render(f->a, t, area, f->buf, &st);
196}
197
198/* ---- formats */
199
200const char *mt_fmt_uptime(mt_frame *f, uint64_t ms) {
201    uint64_t s = ms / 1000;
202    if (s >= 3600)
203        return MT_FMT(f, "%lluh%02llum%02llus", (unsigned long long)(s / 3600), (unsigned long long)(s / 60 % 60),
204                      (unsigned long long)(s % 60));
205    return MT_FMT(f, "%llum%02llus", (unsigned long long)(s / 60), (unsigned long long)(s % 60));
206}
207
208const char *mt_fmt_rate(mt_frame *f, float r) {
209    if (r >= 1e6f) return MT_FMT(f, "%sM", mt_ff(f, r / 1e6f, 2));
210    if (r >= 1e4f) return MT_FMT(f, "%sk", mt_ff(f, r / 1e3f, 1));
211    return mt_ff(f, r, 1);
212}
213
214const char *mt_fmt_us(mt_frame *f, float us) {
215    if (!isfinite(us) || us <= 0.0f) return "0";
216    if (us >= 1000.0f) return MT_FMT(f, "%sms", mt_ff(f, us / 1000.0f, 1));
217    return MT_FMT(f, "%sus", mt_ff(f, us, 0));
218}
219
220const char *mt_fmt_bytes(mt_frame *f, uint64_t b) {
221    if (b == 0) return "-";
222    if (b >= (uint64_t)1 << 30) return MT_FMT(f, "%sG", mt_ff(f, (double)b / (double)((uint64_t)1 << 30), 1));
223    return MT_FMT(f, "%sM", mt_ff(f, (double)b / (double)((uint64_t)1 << 20), 1));
224}
225
226const char *mt_pct(mt_frame *f, float x) { return MT_FMT(f, "%s%%", mt_ff(f, clampf(x * 100.0f, 0.0f, 999.0f), 0)); }
227
228const char *mt_mask_list(mt_frame *f, uint32_t mask) {
229    char b[128];
230    size_t n = 0;
231    b[0] = 0;
232    for (uint32_t k = 0; k < 32; k++)
233        if (mask & (1u << k)) n += (size_t)snprintf(b + n, sizeof b - n, "%s%u", n ? "," : "", k);
234    return tui_arena_strdup(f->a, b);
235}
236
237const char *mt_short_gpu(mt_frame *f, const char *g) {
238    while (strncmp(g, "NVIDIA ", 7) == 0) g += 7;
239    while (strncmp(g, "GeForce ", 8) == 0) g += 8;
240    return tui_arena_strdup(f->a, g);
241}
242
243/* ---- styles */
244
245tui_style mt_state_style(const mt_section *s) {
246    switch (mt_section_state(s)) {
247    case MESH_ST_RUNNING: return tui_fg_bold(MT_GOOD);
248    case MESH_ST_FAILED: return tui_fg_bold(MT_BAD);
249    case MESH_ST_STOPPING: return tui_fg(MT_WARN);
250    default: return tui_fg(MT_DIM);
251    }
252}
253
254tui_style mt_share_style(float x) { return tui_fg_bold(x >= 0.8f ? MT_GOOD : x >= 0.35f ? MT_WARN : MT_BAD); }
255
256tui_style mt_busy_style(float x) {
257    return tui_fg(x >= 0.9f ? MT_BAD : x >= 0.6f ? MT_WARN : x < 0.05f ? MT_DIM : MT_FG);
258}
259
260tui_span mt_share_span(mt_frame *f, mt_opt_f32 s) {
261    if (s.some) return mt_styled(mt_pct(f, s.v), mt_share_style(s.v));
262    return mt_fgs("-", MT_FAINT);
263}
264
265tui_span mt_peer_span(mt_frame *f, const mt_snapshot *s) {
266    uint32_t x = mt_snapshot_host_u32(s, MESH_S_PEER_STATE);
267    switch (x) {
268    case 0: return mt_fgs("single", MT_DIM);
269    case 1: return mt_fgb("waiting for members", MT_WARN);
270    case 2: return mt_fgb("peered", MT_GOOD);
271    default: return mt_fgs(MT_FMT(f, "state %u", x), MT_BAD);
272    }
273}
274
275tui_line mt_machine_title(mt_frame *f, const mt_machine *m, bool focused, const char *extra, tui_color *color) {
276    tui_line l = tui_line_empty();
277    if (focused) tui_line_push(f->a, &l, mt_fgs(" ◆", MT_ACCENT));
278    tui_line_push(f->a, &l, mt_fgb(MT_FMT(f, " %s ", mt_name(f, m)), MT_FG));
279    if (!m->online) {
280        tui_line_push(f->a, &l, mt_styled(" OFFLINE ", mt_fg_bg_bold(TUI_WHITE, MT_BAD)));
281        *color = MT_BAD;
282    } else {
283        *color = focused ? MT_ACCENT : MT_FAINT;
284    }
285    if (*extra) tui_line_push(f->a, &l, mt_fgs(MT_FMT(f, "%s ", extra), MT_DIM));
286    return l;
287}
288
289void mt_no_data(mt_frame *f, tui_rect area, const mt_machine *m) {
290    tui_line ls[3] = {
291        tui_line_span(f->a, mt_fgs("no snapshot yet", MT_DIM)),
292        MT_LINE(f, mt_label("endpoint "), mt_val(m->endpoint)),
293        MT_LINE(f, mt_label("status   "), mt_fgs(m->status, MT_WARN)),
294    };
295    mt_par(f, tui_text_of(f->a, 3, ls), area);
296}
297
298void mt_not_loaded(mt_frame *f, tui_rect area, const mt_machine *m, const char *name) {
299    tui_line ls[2] = {
300        tui_line_span(f->a, mt_styled(MT_FMT(f, " %s is not loaded on %s ", name, mt_name(f, m)), mt_fg_bg_bold(MT_BG, MT_WARN))),
301        tui_line_span(f->a, mt_label(MT_FMT(f, "Programs tab (2), then L to load %s from the catalog", name))),
302    };
303    tui_paragraph p = tui_paragraph_new(tui_text_of(f->a, 2, ls));
304    p.alignment = TUI_ALIGN_CENTER;
305    uint16_t y = (uint16_t)(area.y + area.height / 2);
306    uint16_t top = tui_max16(tui_sat_sub(y, 1), area.y);
307    tui_paragraph_render(f->a, &p, mt_rect(area.x, top, area.width, tui_min16(area.height, 2)), f->buf);
308}
309
310const mt_section *mt_bound(mt_frame *f, tui_rect inner, const mt_machine *m, const char *name) {
311    if (!m->has_snap) {
312        mt_no_data(f, inner, m);
313        return NULL;
314    }
315    const mt_section *p = mt_snapshot_by_name(&m->snap, name);
316    if (!p) mt_not_loaded(f, inner, m, name);
317    return p;
318}
319
320/* f32::total_cmp on finite values: -0 sorts before +0. */
321static int total_cmp(float a, float b) {
322    if (a < b) return -1;
323    if (a > b) return 1;
324    return (int)(signbit(b) != 0) - (int)(signbit(a) != 0);
325}
326
327bool mt_slowest_wait(const mt_snapshot *s, uint32_t *k, float *us) {
328    uint32_t me;
329    if (!mt_snapshot_system(s, &me)) return false;
330    mt_member ms[MESH_MAX_SYSTEMS];
331    size_t n = mt_snapshot_members(s, ms);
332    bool any = false;
333    for (size_t i = 0; i < n; i++) {
334        if (ms[i].system == me || !isfinite(ms[i].wait_us)) continue;
335        /* max_by: the last of equal maxima wins */
336        if (!any || total_cmp(ms[i].wait_us, *us) >= 0) {
337            *k = ms[i].system;
338            *us = ms[i].wait_us;
339            any = true;
340        }
341    }
342    return any;
343}
344
345const char *mt_sec_name(mt_frame *f, const mt_section *p) {
346    char b[3 * MESH_NAME_BYTES + 1];
347    mt_section_name(p, b);
348    return tui_arena_strdup(f->a, b);
349}