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