1 #include "../../arcan_shmif.h"
2 #include "../../arcan_tui.h"
3 #include "../../arcan_tui_listwnd.h"
4 #include <errno.h>
5 #include <ctype.h>
6 #include <inttypes.h>
7 #include <stdint.h>
8 #include <string.h>
9 #include <assert.h>
10
11 /*
12 * Useful enhancements missing:
13 *
14 * - generalize all the proxying to a default table and mask out / manually
15 * replace the ones that should be treated like that so we don't repeat
16 * the same code everywhere.
17 *
18 * - scroll the current selected line on tick if cropped
19 * - expose scrollbar metadata
20 * - indicate sublevel / subnodes (can turn into tree-view through mask/unmask)
21 * use an attribute for level so |-> can be added do sub-ones,
22 * more difficult is adding an expand-collapse so selection would expand
23 * - handle accessibility subwindow (provide only selected item for t2s)
24 * - allow multiple weighted column formats for wider windows
25 * - prefix typing for searching
26 */
27
28 #ifndef COUNT_OF
29 #define COUNT_OF(x) \
30 ((sizeof(x)/sizeof(0[x])) / ((size_t)(!(sizeof(x) % sizeof(0[x])))))
31 #endif
32
33 #define INACTIVE_ITEM (LIST_SEPARATOR | LIST_LABEL | LIST_PASSIVE | LIST_HIDE)
34 #define HIDDEN_ITEM (LIST_HIDE)
35
36 #define LISTWND_MAGIC 0xfadef00e
37 struct listwnd_meta {
38 /* debug-help, check against LISTWND_MAGIC */
39 uint32_t magic;
40
41 /* actual entries, flags can mutate, size cannot */
42 struct tui_list_entry* list;
43 size_t list_sz;
44
45 /* current logical cursor position and resolved screen position */
46 size_t list_pos;
47 size_t list_row;
48
49 /* first row start */
50 size_t list_ofs;
51
52 /* set when user has made a selection, and the selected item */
53 int entry_state;
54 size_t entry_pos;
55
56 /* drawing characters for the flags */
57 uint32_t line_ch;
58 uint32_t check_ch;
59 uint32_t sub_ch;
60
61 /* to restore the context */
62 struct tui_cbcfg old_handlers;
63 int old_flags;
64 size_t orig_w, orig_h;
65 };
66
67 /* context validation, perform on every exported symbol */
validate(struct tui_context * T,struct listwnd_meta ** M)68 static bool validate(struct tui_context* T, struct listwnd_meta** M)
69 {
70 if (!T)
71 return false;
72
73 struct tui_cbcfg handlers;
74 arcan_tui_update_handlers(T, NULL, &handlers, sizeof(struct tui_cbcfg));
75
76 struct listwnd_meta* ch = handlers.tag;
77 if (!ch || ch->magic != LISTWND_MAGIC)
78 return false;
79
80 if (M)
81 *M = ch;
82
83 return true;
84 }
85
get_visible_offset(struct listwnd_meta * M)86 static size_t get_visible_offset(struct listwnd_meta* M)
87 {
88 size_t ofs = 0;
89 for (size_t i = M->list_ofs; i < M->list_pos; i++){
90 if (M->list[i].attributes & HIDDEN_ITEM)
91 continue;
92 ofs++;
93 }
94 return ofs;
95 }
96
arcan_tui_listwnd_tell(struct tui_context * T)97 ssize_t arcan_tui_listwnd_tell(struct tui_context* T)
98 {
99 struct listwnd_meta* M;
100 if (!validate(T, &M))
101 return -1;
102
103 return M->list_pos;
104 }
105
redraw(struct tui_context * T,struct listwnd_meta * M)106 static void redraw(struct tui_context* T, struct listwnd_meta* M)
107 {
108 size_t c_row = 0;
109 size_t rows, cols;
110 arcan_tui_dimensions(T, &rows, &cols);
111
112 if (!rows)
113 return;
114
115 /* safeguard that we fit in the current screen, else we search */
116 for (size_t ofs = get_visible_offset(M); ofs > rows; M->list_ofs++){
117 ofs = get_visible_offset(M);
118 }
119
120 #define GET_COL_INDEX(X) {.aflags = TUI_ATTR_COLOR_INDEXED, .fc[0] = X, .bc[0] = X};
121
122 struct tui_screen_attr reset_def = GET_COL_INDEX(TUI_COL_TEXT);
123 struct tui_screen_attr def = GET_COL_INDEX(TUI_COL_LABEL);
124 struct tui_screen_attr sel = GET_COL_INDEX(TUI_COL_HIGHLIGHT);
125 sel.aflags |= TUI_ATTR_BOLD;
126 struct tui_screen_attr inact = GET_COL_INDEX(TUI_COL_INACTIVE);
127 struct tui_screen_attr label = GET_COL_INDEX(TUI_COL_LABEL);
128
129 /* erase the screen as well as the entries can be fewer than the number of rows */
130 arcan_tui_defattr(T, &reset_def);
131 arcan_tui_erase_screen(T, false);
132
133 /* now we can just clear / draw the items on the page */
134 c_row = 0;
135 for (size_t i = M->list_ofs; rows && i < M->list_sz; i++){
136 int lattr = M->list[i].attributes;
137 const char* label = M->list[i].label;
138
139 if (lattr & HIDDEN_ITEM)
140 continue;
141
142 rows--;
143 arcan_tui_move_to(T, 0, c_row);
144
145 struct tui_screen_attr* cattr = &def;
146 if (i == M->list_pos){
147 cattr = &sel;
148 M->list_row = c_row;
149 }
150
151 /* cursor state doesn't matter for passive */
152 if (lattr & LIST_PASSIVE){
153 cattr = &inact;
154 }
155
156 if (lattr & LIST_SEPARATOR){
157 for (size_t c = 0; c < cols; c++)
158 arcan_tui_write(T, M->line_ch, &inact);
159 c_row++;
160 continue;
161 }
162
163 /* clear the target line with the attribute (as it can contain bgc) */
164 size_t ofs = 0;
165 arcan_tui_defattr(T, cattr);
166 arcan_tui_erase_region(T, 0, c_row, cols, c_row, false);
167
168 /* tactic: draw as much as possible from starting label offset,
169 * recall (& 0xc0) != 0x80 for utf8- start */
170 arcan_tui_move_to(T, 1+M->list[i].indent, c_row);
171 for (size_t vofs = 0; vofs < cols - 2 && label[ofs]; vofs++){
172 size_t end = ofs + 1;
173 while (label[end] && (label[end] & 0xc0) == 0x80) end++;
174 arcan_tui_writeu8(T, (const uint8_t*)&label[ofs], end - ofs, cattr);
175 ofs = end;
176 }
177
178 /* anotate with symbols */
179 if (lattr & LIST_CHECKED){
180 arcan_tui_move_to(T, 0, c_row);
181 arcan_tui_write(T, M->check_ch, cattr);
182 }
183
184 if (lattr & LIST_HAS_SUB){
185 arcan_tui_move_to(T, cols-1, c_row);
186 arcan_tui_write(T, M->sub_ch, cattr);
187 }
188 c_row++;
189 }
190
191 arcan_tui_defattr(T, &reset_def);
192 }
193
arcan_tui_listwnd_setpos(struct tui_context * T,size_t n)194 void arcan_tui_listwnd_setpos(struct tui_context* T, size_t n)
195 {
196 struct listwnd_meta* M;
197 if (!validate(T, &M))
198 return;
199
200 if (n < M->list_sz)
201 M->list_pos = n;
202
203 redraw(T, M);
204 }
205
select_current(struct tui_context * T,struct listwnd_meta * M)206 static void select_current(struct tui_context* T, struct listwnd_meta* M)
207 {
208 int flags = M->list[M->list_pos].attributes;
209 if (flags & INACTIVE_ITEM)
210 return;
211 M->entry_state = 1;
212 M->entry_pos = M->list_pos;
213 }
214
cancel(struct tui_context * T,struct listwnd_meta * M)215 static void cancel(struct tui_context* T, struct listwnd_meta* M)
216 {
217 M->entry_state = -1;
218 }
219
step_page_s(struct tui_context * T,struct listwnd_meta * M)220 static void step_page_s(struct tui_context* T, struct listwnd_meta* M)
221 {
222 size_t rows, cols;
223 arcan_tui_dimensions(T, &rows, &cols);
224
225 /* increment offset half- a page */
226 rows = (rows >> 1) + 1;
227 size_t c_row;
228 for (c_row = M->list_ofs; c_row < M->list_sz && rows; c_row++){
229 if (M->list[c_row].attributes & INACTIVE_ITEM)
230 continue;
231
232 rows--;
233 }
234
235 /* couldn't be done */
236 if (c_row == M->list_sz || M->list_pos == M->list_sz - 1){
237 M->list_ofs = 0;
238 M->list_pos = 0;
239 }
240 else{
241 M->list_ofs = c_row;
242 M->list_pos++;
243 }
244
245 /* step cursor to next sane */
246 for (c_row = M->list_pos; c_row < M->list_sz; c_row++){
247 if (!(M->list[c_row].attributes & INACTIVE_ITEM)){
248 M->list_pos = c_row;
249 break;
250 }
251 }
252
253 redraw(T, M);
254 }
255
step_page_n(struct tui_context * T,struct listwnd_meta * M)256 static void step_page_n(struct tui_context* T, struct listwnd_meta* M)
257 {
258 }
259
step_cursor_n(struct tui_context * T,struct listwnd_meta * M)260 static void step_cursor_n(struct tui_context* T, struct listwnd_meta* M)
261 {
262 size_t current = M->list_pos;
263 size_t vis_step = 0;
264 do {
265 current = current > 0 ? current - 1 : M->list_sz - 1;
266 if (!(M->list[current].attributes & HIDDEN_ITEM))
267 vis_step++;
268
269 if (!(M->list[current].attributes & INACTIVE_ITEM))
270 break;
271
272 } while (current != M->list_pos);
273
274 if (M->list_row < vis_step){
275 step_page_n(T, M);
276 return;
277 }
278
279 M->list_pos = current;
280 redraw(T, M);
281 }
282
step_cursor_s(struct tui_context * T,struct listwnd_meta * M)283 static void step_cursor_s(struct tui_context* T, struct listwnd_meta* M)
284 {
285 size_t rows, cols;
286 arcan_tui_dimensions(T, &rows, &cols);
287
288 /* find the next selectable item, and detect if it is on this page or not */
289 size_t current = M->list_pos;
290 size_t vis_step = 0, prev_vis = current;
291 bool new_page = false;
292
293 do {
294 current = (current + 1) % M->list_sz;
295 if (!(M->list[current].attributes & HIDDEN_ITEM)){
296 vis_step++;
297
298 /* track the first visible on the next page */
299 if (vis_step + M->list_row >= rows && !new_page){
300 new_page = true;
301 prev_vis = current;
302 }
303 }
304
305 if (!(M->list[current].attributes & INACTIVE_ITEM))
306 break;
307
308 /* end condition is wrap */
309 } while (current != M->list_pos);
310
311 /* outside window, need to find the new list ofset as well, that is
312 * why we need to track the previous visible */
313 if (new_page){
314 M->list_ofs = prev_vis;
315 }
316 else if (current < M->list_pos){
317 if (prev_vis < rows)
318 prev_vis = 0;
319 M->list_ofs = prev_vis;
320 }
321
322 M->list_pos = current;
323
324 redraw(T, M);
325 }
326
arcan_tui_listwnd_dirty(struct tui_context * T)327 void arcan_tui_listwnd_dirty(struct tui_context* T)
328 {
329 struct listwnd_meta* M;
330 if (!validate(T, &M))
331 return;
332
333 redraw(T, M);
334 }
335
u8(struct tui_context * T,const char * u8,size_t len,void * tag)336 static bool u8(struct tui_context* T, const char* u8, size_t len, void* tag)
337 {
338 /* not necessarily terminated, so terminate */
339 char cp[len+1];
340 memcpy(cp, u8, len);
341 cp[len] = '\0';
342
343 struct listwnd_meta* M = tag;
344 for (size_t i = 0; i < M->list_sz; i++){
345 if (M->list[i].shortcut && strcmp(M->list[i].shortcut, cp) == 0){
346 if (M->list[i].attributes & ~(INACTIVE_ITEM)){
347 M->list_pos = i;
348 redraw(T, M);
349 }
350 return true;
351 }
352 }
353
354 return false;
355 }
356
arcan_tui_listwnd_status(struct tui_context * T,struct tui_list_entry ** out)357 bool arcan_tui_listwnd_status(struct tui_context* T, struct tui_list_entry** out)
358 {
359 struct listwnd_meta* M;
360 if (!validate(T, &M))
361 return false;
362
363 if (!M->entry_state)
364 return false;
365
366 /* user requested cancellation */
367 else if (M->entry_state == -1 && out)
368 *out = NULL;
369 /* or selected a real item */
370 else if (M->entry_state == 1 && out)
371 *out = &M->list[M->entry_pos];
372
373 M->entry_state = 0;
374 return true;
375 }
376
377 struct labelent {
378 void (* handler)(struct tui_context* T, struct listwnd_meta* M);
379 struct tui_labelent ent;
380 int alt;
381 };
382
383 static struct labelent labels[] = {
384 {
385 .handler = select_current,
386 .ent =
387 {
388 .label = "SELECT",
389 .descr = "Activate/Toggle the currently selected item",
390 .initial = TUIK_RETURN
391 },
392 .alt = TUIK_RIGHT
393 },
394 {
395 .handler = step_cursor_s,
396 .ent =
397 {
398 .label = "NEXT",
399 .descr = "Move the cursor to the next valid entry",
400 .initial = TUIK_DOWN
401 }
402 },
403 {
404 .handler = step_cursor_n,
405 .ent =
406 {
407 .label = "PREV",
408 .descr = "Move the cursor to the previous valid entry",
409 .initial = TUIK_UP
410 }
411 },
412 {
413 .handler = step_page_s,
414 .ent =
415 {
416 .label = "NEXT_PAGE",
417 .descr = "Step the list to the next page",
418 .initial = TUIK_PAGEDOWN
419 }
420 },
421 {
422 .handler = step_page_n,
423 .ent =
424 {
425 .label = "PREV_PAGE",
426 .descr = "Step the list to the previous page",
427 .initial = TUIK_PAGEUP
428 }
429 },
430 {
431 .handler = cancel,
432 .ent =
433 {
434 .label = "CANCEL",
435 .descr = "Exit the list view state",
436 .initial = TUIK_ESCAPE
437 },
438 .alt = TUIK_LEFT
439 }
440 };
441
on_label_input(struct tui_context * T,const char * label,bool active,void * tag)442 static bool on_label_input(
443 struct tui_context* T, const char* label, bool active, void* tag)
444 {
445 if (!active)
446 return true;
447
448 for (size_t i = 0; i < COUNT_OF(labels); i++){
449 if (strcmp(label, labels[i].ent.label) == 0)
450 return labels[i].handler(T, (struct listwnd_meta*) tag), true;
451 }
452
453 return false;
454 }
455
key_input(struct tui_context * T,uint32_t keysym,uint8_t scancode,uint8_t mods,uint16_t subid,void * tag)456 static void key_input(struct tui_context* T, uint32_t keysym,
457 uint8_t scancode, uint8_t mods, uint16_t subid, void* tag)
458 {
459 struct listwnd_meta* M = tag;
460 for (size_t i = 0; i < COUNT_OF(labels); i++){
461 if ((keysym && keysym == labels[i].alt) ||
462 keysym == labels[i].ent.initial)
463 return labels[i].handler(T, (struct listwnd_meta*) tag);
464 }
465 }
466
arcan_tui_listwnd_release(struct tui_context * T)467 void arcan_tui_listwnd_release(struct tui_context* T)
468 {
469 struct listwnd_meta* M;
470 if (!validate(T, &M))
471 return;
472
473 /* restore old flags */
474 arcan_tui_reset_flags(T, ~0);
475 arcan_tui_set_flags(T, M->old_flags);
476
477 /* requery label through original handles */
478 arcan_tui_update_handlers(T,
479 &M->old_handlers, NULL, sizeof(struct tui_cbcfg));
480
481 arcan_tui_reset_labels(T);
482
483 /* it would make sense to 'fake' a resize here as well, but from some design
484 * oversights with the event, that requires tracking or exposing shmif_ context
485 * contents, or breaking ABI - so assume the caller actually has the sense to
486 * refresh on release() */
487
488 /* LTO could possibly do something about this, but basically just safeguard
489 * on a safeguard (UAF detection) for the listwnd_meta after freeing it */
490 *M = (struct listwnd_meta){
491 .magic = 0xdeadbeef
492 };
493
494 arcan_tui_wndhint(T, NULL,
495 (struct tui_constraints){
496 .min_cols = -1, .min_rows = -1,
497 .max_cols = M->orig_w, .max_rows = M->orig_h,
498 .anch_row = -1, .anch_col = -1
499 }
500 );
501
502 free(M);
503 }
504
resized(struct tui_context * T,size_t neww,size_t newh,size_t col,size_t row,void * t)505 static void resized(struct tui_context* T,
506 size_t neww, size_t newh, size_t col, size_t row, void* t)
507 {
508 struct listwnd_meta* M = t;
509 redraw(T, M);
510 if (M->old_handlers.resized){
511 M->old_handlers.resized(T, neww, newh, col, row, M->old_handlers.tag);
512 }
513 }
514
subwindow(struct tui_context * T,arcan_tui_conn * conn,uint32_t id,uint8_t type,void * t)515 static bool subwindow(struct tui_context* T,
516 arcan_tui_conn* conn, uint32_t id, uint8_t type, void* t)
517 {
518 struct listwnd_meta* M = t;
519 if (M->old_handlers.subwindow){
520 return M->old_handlers.subwindow(T, conn, id, type, M->old_handlers.tag);
521 }
522 else
523 return false;
524 }
525
resize(struct tui_context * T,size_t neww,size_t newh,size_t col,size_t row,void * t)526 static void resize(struct tui_context* T,
527 size_t neww, size_t newh, size_t col, size_t row, void* t)
528 {
529 struct listwnd_meta* M = t;
530 if (M->old_handlers.resize){
531 M->old_handlers.resize(T, neww, newh, col, row, M->old_handlers.tag);
532 }
533 }
534
tick(struct tui_context * T,void * t)535 static void tick(struct tui_context* T, void* t)
536 {
537 /* if current item is cropped, scroll it */
538 }
539
geohint(struct tui_context * T,float lat,float longit,float elev,const char * cnt,const char * lang,void * t)540 static void geohint(struct tui_context* T,
541 float lat, float longit, float elev, const char* cnt, const char* lang, void* t)
542 {
543 struct listwnd_meta* M = t;
544 if (M->old_handlers.geohint){
545 M->old_handlers.geohint(T, lat, longit, elev, cnt, lang, M->old_handlers.tag);
546 }
547 }
548
bchunk(struct tui_context * T,bool input,uint64_t size,int fd,const char * msg,void * tag)549 static void bchunk(struct tui_context* T,
550 bool input, uint64_t size, int fd, const char* msg, void* tag)
551 {
552 struct listwnd_meta* M = tag;
553 if (M->old_handlers.bchunk){
554 M->old_handlers.bchunk(T, input, size, fd, msg, M->old_handlers.tag);
555 }
556 }
557
mouse_motion(struct tui_context * T,bool relative,int mouse_x,int mouse_y,int modifiers,void * tag)558 static void mouse_motion(struct tui_context* T,
559 bool relative, int mouse_x, int mouse_y, int modifiers, void* tag)
560 {
561 struct listwnd_meta* M = tag;
562
563 /* uncommon but not impossible */
564 if (relative){
565 if (!mouse_y)
566 return;
567 if (mouse_y < 0){
568 for (int i = mouse_y; i != 0; i++)
569 step_cursor_n(T, M);
570 }
571 else{
572 for (int i = mouse_y; i != 0; i--)
573 step_cursor_s(T, M);
574 }
575 return;
576 }
577
578 for (size_t i = M->list_ofs, yp = 0; i < M->list_sz; i++){
579 if (M->list[i].attributes & HIDDEN_ITEM)
580 continue;
581
582 /* find matching position */
583 if (yp == mouse_y){
584 /* and move selection if it has changed */
585 if (M->list_pos != i){
586 M->list_pos = i;
587 redraw(T, M);
588 }
589 break;
590 }
591 yp++;
592 }
593 }
594
mouse_button(struct tui_context * T,int last_x,int last_y,int button,bool active,int modifiers,void * tag)595 static void mouse_button(struct tui_context* T,
596 int last_x, int last_y, int button, bool active, int modifiers, void* tag)
597 {
598 struct listwnd_meta* M = tag;
599 if (!active)
600 return;
601
602 /* mouse motion preceeds the button, so we can just trigger */
603 if (button == TUIBTN_LEFT){
604 select_current(T, M);
605 }
606 else if (button == TUIBTN_RIGHT){
607 cancel(T, M);
608 }
609 else if (button == TUIBTN_MIDDLE){
610 step_page_s(T, M);
611 }
612 else if (button == TUIBTN_WHEEL_UP){
613 step_cursor_n(T, M);
614 }
615 else if (button == TUIBTN_WHEEL_DOWN){
616 step_cursor_s(T, M);
617 }
618 }
619
recolor(struct tui_context * T,void * t)620 static void recolor(struct tui_context* T, void* t)
621 {
622 struct listwnd_meta* M = t;
623 redraw(T, M);
624 }
625
on_label_query(struct tui_context * T,size_t index,const char * country,const char * lang,struct tui_labelent * dstlbl,void * t)626 static bool on_label_query(struct tui_context* T,
627 size_t index, const char* country, const char* lang,
628 struct tui_labelent* dstlbl, void* t)
629 {
630 struct listwnd_meta* M = t;
631
632 if (COUNT_OF(labels) < index + 1){
633 return false;
634 }
635
636 memcpy(dstlbl, &labels[index].ent, sizeof(struct tui_labelent));
637 return true;
638 }
639
arcan_tui_listwnd_setup(struct tui_context * T,struct tui_list_entry * L,size_t n_entries)640 bool arcan_tui_listwnd_setup(
641 struct tui_context* T, struct tui_list_entry* L, size_t n_entries)
642 {
643 if (!T || !L || n_entries == 0)
644 return false;
645
646 struct listwnd_meta* meta = malloc(sizeof(struct listwnd_meta));
647 if (!meta)
648 return false;
649
650 *meta = (struct listwnd_meta){
651 .magic = LISTWND_MAGIC,
652 .list_sz = n_entries,
653 .list = L,
654 };
655
656 /* save old flags and just set clean + ALTERNATE */
657 meta->old_flags = arcan_tui_set_flags(T, 0);
658 arcan_tui_reset_flags(T, ~0);
659 arcan_tui_set_flags(T, TUI_ALTERNATE | TUI_HIDE_CURSOR | TUI_MOUSE);
660
661 struct tui_cbcfg cbcfg = {
662 .tag = meta,
663 .resize = resize,
664 .resized = resized,
665 .recolor = recolor,
666 .tick = tick,
667 .geohint = geohint,
668 .query_label = on_label_query,
669 .input_label = on_label_input,
670 .input_key = key_input,
671 .input_mouse_motion = mouse_motion,
672 .input_mouse_button = mouse_button,
673 .input_utf8 = u8,
674 .subwindow = subwindow,
675 .bchunk = bchunk
676 };
677
678 /* BOX DRAWINGS LIGHT HORIZONTAL U+2500 */
679 meta->line_ch = arcan_tui_hasglyph(T, 0x2500) ? 0x2500 : '-';
680 /* MODIFIER LETTER RIGHT ARROWHEAD U+02c3 */
681 meta->sub_ch = arcan_tui_hasglyph(T, 0x02c3) ? 0x02c3 : '>';
682 /* CHECK MARK U+2713 */
683 meta->check_ch = arcan_tui_hasglyph(T, 0x2713) ? 0x2713 : '*';
684
685 /* save old handlers and set new ones */
686 arcan_tui_update_handlers(T,
687 &cbcfg, &meta->old_handlers, sizeof(struct tui_cbcfg));
688
689 /* and check for misuse */
690 assert(meta->old_handlers.resize != resize);
691
692 /* rough utf8-len based on labels alone */
693 size_t max_w = 0;
694 for (size_t i = 0; i < n_entries; i++){
695 size_t j = 0, w = 0;
696 while (L[i].label[j]){
697 w += (L[i].label[j++] & 0xc0) != 0x80;
698 }
699 if (w > max_w)
700 max_w = w;
701 }
702
703 arcan_tui_dimensions(T, &meta->orig_h, &meta->orig_w);
704 arcan_tui_wndhint(T, NULL,
705 (struct tui_constraints){
706 .min_cols = -1, .min_rows = -1,
707 .max_cols = max_w + 4, .max_rows = n_entries + 1,
708 .anch_row = -1, .anch_col = -1
709 }
710 );
711
712 /* requery label through new handles (removes existing ones) */
713 arcan_tui_reset_labels(T);
714 redraw(T, meta);
715
716 return true;
717 }
718
719 #ifdef EXAMPLE
720
721 static struct tui_list_entry test_easy[] = {
722 {
723 .label = "hi",
724 .attributes = LIST_CHECKED,
725 .tag = 0,
726 },
727 {
728 .label = "there",
729 .attributes = LIST_PASSIVE,
730 .shortcut = "t",
731 .tag = 1
732 },
733 {
734 .attributes = LIST_SEPARATOR,
735 },
736 {
737 .label = "lolita",
738 .attributes = LIST_HAS_SUB,
739 .shortcut = "l",
740 .tag = 2
741 }
742 };
743
main(int argc,char ** argv)744 int main(int argc, char** argv)
745 {
746 struct tui_cbcfg cbcfg = {0};
747 arcan_tui_conn* conn = arcan_tui_open_display("test", "");
748 struct tui_context* tui = arcan_tui_setup(conn, NULL, &cbcfg, sizeof(cbcfg));
749 size_t test_cases = 2;
750 size_t index = 0;
751
752 if (argc > 1){
753 index = strtoull(argv[1], NULL, 10);
754 }
755
756 if (index > test_cases - 1){
757 fprintf(stderr, "Index (%zu) out of bounds\n", index);
758 return EXIT_FAILURE;
759 }
760
761 switch(index){
762 case 0:
763 arcan_tui_listwnd_setup(tui, test_easy, COUNT_OF(test_easy));
764 break;
765 case 1:{
766 struct tui_list_entry* ent = malloc(256 * sizeof(struct tui_list_entry));
767 for (size_t i = 0; i < 256; i++){
768 char buf[8];
769 snprintf(buf, 8, "%zu %c", i, (char)('a' + i % 10));
770 ent[i] = (struct tui_list_entry){
771 .label = strdup((char*)buf),
772 .tag = i
773 };
774 if (i % 5 == 0)
775 ent[i].attributes = LIST_HIDE;
776 if (i % 3 == 0 || i % 4 == 0)
777 ent[i].attributes = LIST_PASSIVE;
778 }
779 arcan_tui_listwnd_setup(tui, ent, 256);
780 }
781 break;
782 }
783
784 while(1){
785 struct tui_process_res res = arcan_tui_process(&tui, 1, NULL, 0, -1);
786 if (res.errc == TUI_ERRC_OK){
787 if (-1 == arcan_tui_refresh(tui) && errno == EINVAL)
788 break;
789 }
790 else
791 break;
792
793 struct tui_list_entry* ent;
794 if (arcan_tui_listwnd_status(tui, &ent)){
795 if (ent)
796 printf("user picked: %s\n", ent->label);
797 else
798 printf("user cancelled\n");
799 break;
800 }
801 }
802 arcan_tui_destroy(tui, NULL);
803 return 0;
804 }
805 #endif
806