1 #include "demo.h"
2 #include <pthread.h>
3 #include <inttypes.h>
4 #include "lib/fbuf.h"
5 #include "lib/internal.h"
6
7 // we provide a heads-up display throughout the demo, detailing the demos we're
8 // about to run, running, and just runned. the user can move this HUD with
9 // their mouse. it should always be on the top of the z-stack, unless hidden.
10 struct ncplane* hud = NULL;
11 static struct elem* elems; // tracks the last n demos
12
13 static bool hud_hidden;
14 static bool plot_hidden;
15 static struct ncuplot* plot;
16 static uint64_t plottimestart;
17
18 // while the HUD is grabbed by the mouse, these are set to the position where
19 // the grab started. they are reset once the HUD is released.
20 static int hud_grab_x = -1;
21 static int hud_grab_y = -1;
22 // position of the HUD *when grab started*
23 static int hud_pos_x;
24 static int hud_pos_y;
25
26 // while the plot is grabbed by the mouse, these are set to the position where
27 // the grab started. they are reset once the plot is released.
28 static int plot_grab_y = -1;
29 // position of the plot *when grab started*
30 static int plot_pos_y;
31
32 #define FPSGRAPH_MAX_COLS 72 // give it some room on each side of an 80-column term
33
34 // how many columns for runtime?
35 #define HUD_ROWS (3 + 2) // 2 for borders
36 static const int HUD_COLS = 23 + 2; // 2 for borders
37
38 typedef struct elem {
39 char* name;
40 uint64_t startns;
41 uint64_t totalns;
42 unsigned frames;
43 struct elem* next;
44 } elem;
45
46 static struct ncmenu* menu;
47 static struct ncplane* about; // "about" modal popup
48 static struct ncplane* debug; // "debug info" modal popup
49
50 #define MENUSTR_TOGGLE_HUD "Toggle HUD"
51 #define MENUSTR_TOGGLE_PLOT "Toggle FPS plot"
52 #define MENUSTR_REDRAW_SCREEN "Redraw the screen"
53 #define MENUSTR_RESTART "Restart"
54 #define MENUSTR_ABOUT "About"
55 #define MENUSTR_DEBUG "Debug info"
56 #define MENUSTR_QUIT "Quit"
57
58 static int
hud_standard_bg_rgb(struct ncplane * n)59 hud_standard_bg_rgb(struct ncplane* n){
60 uint64_t channels = 0;
61 ncchannels_set_fg_alpha(&channels, NCALPHA_BLEND);
62 ncchannels_set_fg_rgb8(&channels, 0x80, 0x80, 0x80);
63 ncchannels_set_bg_alpha(&channels, NCALPHA_BLEND);
64 ncchannels_set_bg_rgb8(&channels, 0x80, 0x80, 0x80);
65 if(ncplane_set_base(n, "", 0, channels) >= 0){
66 return -1;
67 }
68 return 0;
69 }
70
71 static int
count_debug_lines(const char * output,size_t outputlen)72 count_debug_lines(const char* output, size_t outputlen){
73 int ll = 0;
74 for(size_t i = 0 ; i < outputlen ; ++i){
75 if(output[i] == '\n'){
76 ++ll;
77 }
78 }
79 return ll;
80 }
81
82 static void
debug_toggle(struct notcurses * nc)83 debug_toggle(struct notcurses* nc){
84 ncmenu_rollup(menu);
85 if(debug){
86 ncplane_destroy(debug);
87 debug = NULL;
88 return;
89 }
90 unsigned dimy, dimx;
91 notcurses_term_dim_yx(nc, &dimy, &dimx);
92 fbuf f;
93 if(fbuf_init_small(&f)){
94 return;
95 }
96 notcurses_debug_fbuf(nc, &f);
97 ncplane_options nopts = {
98 .y = 3,
99 .x = NCALIGN_CENTER,
100 .rows = count_debug_lines(f.buf, f.used) + 1,
101 // make it one column longer than the maximum debug output, so that a full
102 // line of output doesn't cause trigger a newline. we'll make the last
103 // column transparent.
104 .cols = 81,
105 .flags = NCPLANE_OPTION_HORALIGNED | NCPLANE_OPTION_FIXED,
106 .name = "dbg",
107 };
108 struct ncplane* n = ncplane_create(notcurses_stdplane(nc), &nopts);
109 if(n == NULL){
110 fbuf_free(&f);
111 return;
112 }
113 uint64_t channels = 0;
114 ncchannels_set_fg_alpha(&channels, NCALPHA_TRANSPARENT);
115 ncchannels_set_bg_rgb(&channels, 0xffffe5);
116 ncplane_set_base(n, " ", 0, channels);
117 ncplane_set_scrolling(n, true);
118 ncplane_set_fg_rgb(n, 0x0a0a0a);
119 ncplane_set_bg_rgb(n, 0xffffe5);
120 size_t b = f.used;
121 if(ncplane_puttext(n, 0, NCALIGN_LEFT, f.buf, &b) < 0){
122 fbuf_free(&f);
123 ncplane_destroy(n);
124 return;
125 }
126 fbuf_free(&f);
127 for(unsigned y = 0 ; y < ncplane_dim_y(n) ; ++y){
128 nccell c = NCCELL_TRIVIAL_INITIALIZER;
129 nccell_set_fg_alpha(&c, NCALPHA_TRANSPARENT);
130 nccell_set_bg_alpha(&c, NCALPHA_TRANSPARENT);
131 ncplane_putc_yx(n, y, ncplane_dim_x(n) - 1, &c);
132 nccell_release(n, &c);
133 }
134 ncplane_putstr_aligned(n, ncplane_dim_y(n) - 1, NCALIGN_CENTER, "Press Alt+d to hide this window");
135 debug = n;
136 }
137
138 static void
about_toggle(struct notcurses * nc)139 about_toggle(struct notcurses* nc){
140 ncmenu_rollup(menu);
141 if(about){
142 ncplane_destroy(about);
143 about = NULL;
144 return;
145 }
146 const int ABOUT_ROWS = 9;
147 const int ABOUT_COLS = 40;
148 unsigned dimy;
149 notcurses_term_dim_yx(nc, &dimy, NULL);
150 ncplane_options nopts = {
151 .y = 3,
152 .x = NCALIGN_CENTER,
153 .rows = ABOUT_ROWS,
154 .cols = ABOUT_COLS,
155 .flags = NCPLANE_OPTION_HORALIGNED | NCPLANE_OPTION_FIXED,
156 };
157 struct ncplane* n = ncplane_create(notcurses_stdplane(nc), &nopts);
158 // let the glyphs below show through, but only dimly
159 uint64_t channels = 0;
160 ncchannels_set_fg_alpha(&channels, NCALPHA_BLEND);
161 ncchannels_set_fg_rgb8(&channels, 0x0, 0x0, 0x0);
162 ncchannels_set_bg_alpha(&channels, NCALPHA_BLEND);
163 ncchannels_set_bg_rgb8(&channels, 0x0, 0x0, 0x0);
164 if(ncplane_set_base(n, "", 0, channels) >= 0){
165 ncplane_set_fg_rgb(n, 0x11ffff);
166 ncplane_set_bg_rgb(n, 0);
167 ncplane_set_bg_alpha(n, NCALPHA_BLEND);
168 ncplane_printf_aligned(n, 1, NCALIGN_CENTER, "notcurses-demo %s", notcurses_version());
169 ncplane_printf_aligned(n, 3, NCALIGN_LEFT, " P toggle plot");
170 ncplane_printf_aligned(n, 3, NCALIGN_RIGHT, "toggle help Ctrl+U ");
171 ncplane_printf_aligned(n, 4, NCALIGN_LEFT, " H toggle HUD");
172 ncplane_printf_aligned(n, 4, NCALIGN_RIGHT, "restart Ctrl+R ");
173 ncplane_printf_aligned(n, 5, NCALIGN_CENTER, "q quit");
174 ncplane_putstr_aligned(n, 7, NCALIGN_CENTER, "\u00a9 nick black <nickblack@linux.com>");
175 nccell ul = NCCELL_TRIVIAL_INITIALIZER, ur = NCCELL_TRIVIAL_INITIALIZER;
176 nccell lr = NCCELL_TRIVIAL_INITIALIZER, ll = NCCELL_TRIVIAL_INITIALIZER;
177 nccell hl = NCCELL_TRIVIAL_INITIALIZER, vl = NCCELL_TRIVIAL_INITIALIZER;
178 channels = 0;
179 ncchannels_set_fg_rgb(&channels, 0xc020c0);
180 ncchannels_set_bg_rgb(&channels, 0);
181 if(nccells_rounded_box(n, NCSTYLE_NONE, channels, &ul, &ur, &ll, &lr, &hl, &vl) == 0){
182 if(ncplane_perimeter(n, &ul, &ur, &ll, &lr, &hl, &vl, 0) == 0){
183 nccell_release(n, &ul); nccell_release(n, &ur); nccell_release(n, &hl);
184 nccell_release(n, &ll); nccell_release(n, &lr); nccell_release(n, &vl);
185 about = n;
186 return;
187 }
188 nccell_release(n, &ul); nccell_release(n, &ur); nccell_release(n, &hl);
189 nccell_release(n, &ll); nccell_release(n, &lr); nccell_release(n, &vl);
190 }
191 }
192 ncplane_destroy(n);
193 }
194
195 // kills about window *and* debug window
about_destroy(struct notcurses * nc)196 void about_destroy(struct notcurses* nc){
197 if(about){
198 about_toggle(nc);
199 }
200 if(debug){
201 debug_toggle(nc);
202 }
203 }
204
205 static void
hud_toggle(struct notcurses * nc)206 hud_toggle(struct notcurses* nc){
207 ncmenu_rollup(menu);
208 if(!hud){
209 return;
210 }
211 hud_hidden = !hud_hidden;
212 if(hud_hidden){
213 ncplane_reparent(hud, hud);
214 }else{
215 ncplane_reparent(hud, notcurses_stdplane(nc));
216 ncplane_move_top(hud);
217 }
218 demo_render(nc);
219 }
220
221 static int
fpsplot_toggle(struct notcurses * nc)222 fpsplot_toggle(struct notcurses* nc){
223 ncmenu_rollup(menu);
224 if(!plot){
225 return 0;
226 }
227 plot_hidden = !plot_hidden;
228 if(plot_hidden){
229 ncplane_reparent_family(ncuplot_plane(plot), ncuplot_plane(plot));
230 }else{
231 ncplane_reparent_family(ncuplot_plane(plot), notcurses_stdplane(nc));
232 ncplane_move_family_top(ncuplot_plane(plot));
233 }
234 return demo_render(nc);
235 }
236
237 // returns true if the input was handled by the menu/HUD. 'q' is passed through
238 // (we return false) so that it can interrupt a demo blocking on input.
menu_or_hud_key(struct notcurses * nc,const struct ncinput * ni)239 bool menu_or_hud_key(struct notcurses *nc, const struct ncinput *ni){
240 struct ncinput tmpni;
241 if(menu && ni->id == NCKEY_ENTER){
242 const char* sel = ncmenu_selected(menu, &tmpni);
243 if(sel == NULL){
244 return false;
245 }
246 }else if(menu && ni->id == NCKEY_BUTTON1 && ni->evtype == NCTYPE_RELEASE){
247 const char* sel = ncmenu_mouse_selected(menu, ni, &tmpni);
248 if(sel == NULL){
249 memcpy(&tmpni, ni, sizeof(tmpni));
250 }
251 }else{
252 memcpy(&tmpni, ni, sizeof(tmpni));
253 }
254 if(tmpni.evtype == NCTYPE_RELEASE){
255 return false;
256 }
257 // toggle the HUD
258 if(tmpni.id == 'H' && !tmpni.alt && !tmpni.ctrl){
259 hud_toggle(nc);
260 return true;
261 }
262 if(tmpni.id == 'P' && !tmpni.alt && !tmpni.ctrl){
263 fpsplot_toggle(nc);
264 return true;
265 }
266 if(tmpni.id == 'U' && !tmpni.alt && tmpni.ctrl){
267 about_toggle(nc);
268 return true;
269 }
270 if(tmpni.id == 'd' && tmpni.alt && !tmpni.ctrl){
271 debug_toggle(nc);
272 return true;
273 }
274 if(tmpni.id == 'L' && !tmpni.alt && tmpni.ctrl){
275 if(menu){
276 ncmenu_rollup(menu);
277 }
278 notcurses_refresh(nc, NULL, NULL);
279 return true;
280 }
281 if(tmpni.id == 'R' && !tmpni.alt && tmpni.ctrl){
282 if(menu){
283 ncmenu_rollup(menu);
284 }
285 interrupt_and_restart_demos();
286 return true;
287 }
288 if(tmpni.id == 'q' && !tmpni.alt && !tmpni.ctrl){
289 if(menu){
290 ncmenu_rollup(menu);
291 }
292 interrupt_demo();
293 return false; // see comment above
294 }
295 if(!menu){
296 return false;
297 }
298 if(ncmenu_offer_input(menu, ni)){
299 return true;
300 }
301 return false;
302 }
303
menu_create(struct notcurses * nc)304 struct ncmenu* menu_create(struct notcurses* nc){
305 struct ncmenu_item demo_items[] = {
306 { .desc = MENUSTR_TOGGLE_HUD, .shortcut = { .id = 'H', }, },
307 { .desc = MENUSTR_TOGGLE_PLOT, .shortcut = { .id = 'P', }, },
308 { .desc = MENUSTR_REDRAW_SCREEN, .shortcut = { .id = 'L', .ctrl = true }, },
309 { .desc = NULL, },
310 { .desc = MENUSTR_RESTART, .shortcut = { .id = 'R', .ctrl = true, }, },
311 { .desc = MENUSTR_QUIT, .shortcut = { .id = 'q', }, },
312 };
313 struct ncmenu_item help_items[] = {
314 { .desc = MENUSTR_ABOUT, .shortcut = { .id = 'U', .ctrl = true, }, },
315 { .desc = MENUSTR_DEBUG, .shortcut = { .id = 'd', .alt = true, }, },
316 };
317 struct ncmenu_section sections[] = {
318 { .name = "notcurses-demo", .items = demo_items,
319 .itemcount = sizeof(demo_items) / sizeof(*demo_items),
320 .shortcut = { .id = 'o', .alt = true, }, },
321 { .name = NULL, .items = NULL, .itemcount = 0, },
322 { .name = "help", .items = help_items,
323 .itemcount = sizeof(help_items) / sizeof(*help_items),
324 .shortcut = { .id = 'h', .alt = true, }, },
325 };
326 uint64_t headerchannels = 0;
327 uint64_t sectionchannels = 0;
328 ncchannels_set_fg_rgb(§ionchannels, 0xffffff);
329 ncchannels_set_bg_rgb(§ionchannels, 0x000000);
330 ncchannels_set_fg_alpha(§ionchannels, NCALPHA_HIGHCONTRAST);
331 ncchannels_set_bg_alpha(§ionchannels, NCALPHA_BLEND);
332 ncchannels_set_fg_rgb(&headerchannels, 0xffffff);
333 ncchannels_set_bg_rgb(&headerchannels, 0x7f347f);
334 ncchannels_set_bg_alpha(&headerchannels, NCALPHA_BLEND);
335 const ncmenu_options mopts = {
336 .sections = sections,
337 .sectioncount = sizeof(sections) / sizeof(*sections),
338 .headerchannels = headerchannels,
339 .sectionchannels = sectionchannels,
340 .flags = 0,
341 };
342 menu = ncmenu_create(notcurses_stdplane(nc), &mopts);
343 return menu;
344 }
345
346 static int
hud_refresh(struct ncplane * n)347 hud_refresh(struct ncplane* n){
348 ncplane_erase(n);
349 nccell ul = NCCELL_TRIVIAL_INITIALIZER, ur = NCCELL_TRIVIAL_INITIALIZER;
350 nccell lr = NCCELL_TRIVIAL_INITIALIZER, ll = NCCELL_TRIVIAL_INITIALIZER;
351 nccell hl = NCCELL_TRIVIAL_INITIALIZER, vl = NCCELL_TRIVIAL_INITIALIZER;
352 if(nccells_rounded_box(n, NCSTYLE_NONE, 0, &ul, &ur, &ll, &lr, &hl, &vl)){
353 return -1;
354 }
355 ul.channels = NCCHANNELS_INITIALIZER(0xf0, 0xc0, 0xc0, 0, 0, 0);
356 ur.channels = NCCHANNELS_INITIALIZER(0xf0, 0xc0, 0xc0, 0, 0, 0);
357 ll.channels = NCCHANNELS_INITIALIZER(0xf0, 0xc0, 0xc0, 0, 0, 0);
358 lr.channels = NCCHANNELS_INITIALIZER(0xf0, 0xc0, 0xc0, 0, 0, 0);
359 hl.channels = NCCHANNELS_INITIALIZER(0xf0, 0xc0, 0xc0, 0, 0, 0);
360 vl.channels = NCCHANNELS_INITIALIZER(0xf0, 0xc0, 0xc0, 0, 0, 0);
361 nccell_set_bg_alpha(&ul, NCALPHA_BLEND);
362 nccell_set_bg_alpha(&ur, NCALPHA_BLEND);
363 nccell_set_bg_alpha(&ll, NCALPHA_BLEND);
364 nccell_set_bg_alpha(&lr, NCALPHA_BLEND);
365 nccell_set_bg_alpha(&hl, NCALPHA_BLEND);
366 nccell_set_bg_alpha(&vl, NCALPHA_BLEND);
367 if(ncplane_perimeter(n, &ul, &ur, &ll, &lr, &hl, &vl, 0)){
368 nccell_release(n, &ul); nccell_release(n, &ur); nccell_release(n, &hl);
369 nccell_release(n, &ll); nccell_release(n, &lr); nccell_release(n, &vl);
370 return -1;
371 }
372 nccell_release(n, &ul); nccell_release(n, &ur); nccell_release(n, &hl);
373 nccell_release(n, &ll); nccell_release(n, &lr); nccell_release(n, &vl);
374 return 0;
375 }
376
377 static int
hud_print_finished(elem * list)378 hud_print_finished(elem* list){
379 elem* e = list;
380 if(hud){
381 hud_refresh(hud);
382 }
383 int line = 0;
384 while(e){
385 if(++line == HUD_ROWS - 1){
386 if(e->next){
387 free(e->next->name);
388 free(e->next);
389 e->next = NULL;
390 }
391 break;
392 }
393 if(hud){
394 nccell c = NCCELL_TRIVIAL_INITIALIZER;
395 ncplane_base(hud, &c);
396 ncplane_set_bg_rgb(hud, nccell_bg_rgb(&c));
397 ncplane_set_bg_alpha(hud, NCALPHA_BLEND);
398 ncplane_set_fg_rgb(hud, 0xffffff);
399 ncplane_set_fg_alpha(hud, NCALPHA_OPAQUE);
400 nccell_release(hud, &c);
401 if(ncplane_printf_yx(hud, line, 1, "%d", e->frames) < 0){
402 return -1;
403 }
404 char buf[NCPREFIXCOLUMNS + 2];
405 ncnmetric(e->totalns, sizeof(buf), NANOSECS_IN_SEC, buf, 0, 1000, '\0');
406 for(int x = 6 ; x < 14 - ncstrwidth(buf, NULL, NULL) ; ++x){
407 nccell ci = NCCELL_TRIVIAL_INITIALIZER;
408 ncplane_putc_yx(hud, 1, x, &ci);
409 }
410 if(ncplane_printf_yx(hud, line, 14 - ncstrwidth(buf, NULL, NULL), "%ss", buf) < 0){
411 return -1;
412 }
413 if(ncplane_putstr_yx(hud, line, 16, e->name) < 0){
414 return -1;
415 }
416 }
417 e = e->next;
418 }
419 return 0;
420 }
421
hud_create(struct notcurses * nc)422 struct ncplane* hud_create(struct notcurses* nc){
423 if(hud){
424 return NULL;
425 }
426 unsigned dimx, dimy;
427 notcurses_term_dim_yx(nc, &dimy, &dimx);
428 struct ncplane_options nopts = {
429 // FPS graph is 6 rows tall; we want one row above it
430 .y = dimy - 6 - HUD_ROWS - 1,
431 .x = NCALIGN_CENTER,
432 .rows = HUD_ROWS,
433 .cols = HUD_COLS,
434 .userptr = NULL,
435 .name = "hud",
436 .resizecb = ncplane_resize_placewithin,
437 .flags = NCPLANE_OPTION_HORALIGNED |
438 NCPLANE_OPTION_FIXED,
439 };
440 struct ncplane* n = ncplane_create(notcurses_stdplane(nc), &nopts);
441 if(n == NULL){
442 return NULL;
443 }
444 hud_standard_bg_rgb(n);
445 hud_refresh(n);
446 ncplane_set_fg_rgb(n, 0xffffff);
447 ncplane_set_bg_rgb(n, 0);
448 ncplane_set_bg_alpha(n, NCALPHA_BLEND);
449 if(hud_hidden){
450 ncplane_reparent(n, n);
451 }
452 return (hud = n);
453 }
454
hud_destroy(void)455 int hud_destroy(void){
456 int ret = 0;
457 if(hud){
458 ret = ncplane_destroy(hud);
459 hud = NULL;
460 }
461 return ret;
462 }
463
464 // mouse has been pressed on the hud. the caller is responsible for rerendering.
hud_grab(int y,int x)465 int hud_grab(int y, int x){
466 int ret;
467 if(hud == NULL || hud_hidden){
468 return -1;
469 }
470 // are we in the middle of a grab?
471 if(hud_grab_x >= 0 && hud_grab_y >= 0){
472 int delty = y - hud_grab_y;
473 int deltx = x - hud_grab_x;
474 ret = ncplane_move_yx(hud, hud_pos_y + delty, hud_pos_x + deltx);
475 }else{
476 // new grab. stash point of original grab, and location of HUD at original
477 // grab. any delta while grabbed (relative to the original grab point)
478 // will see the HUD moved by delta (relative to the original HUD location).
479 int ty = y, tx = x;
480 // first, though, verify that we're clicking within the hud
481 if(!ncplane_translate_abs(hud, &ty, &tx)){
482 return -1;
483 }
484 hud_grab_x = x;
485 hud_grab_y = y;
486 ncplane_yx(hud, &hud_pos_y, &hud_pos_x);
487 ret = 0;
488 }
489 return ret;
490 }
491
hud_release(void)492 int hud_release(void){
493 if(hud == NULL){
494 return -1;
495 }
496 if(hud_grab_x < 0 && hud_grab_y < 0){
497 return -1;
498 }
499 hud_grab_x = -1;
500 hud_grab_y = -1;
501 return hud_standard_bg_rgb(hud);
502 }
503
fpsplot_release(void)504 int fpsplot_release(void){
505 if(plot == NULL){
506 return -1;
507 }
508 if(plot_grab_y < 0){
509 return -1;
510 }
511 plot_grab_y = -1;
512 return hud_standard_bg_rgb(hud);
513 }
514
515 // currently running demo is always at y = HUD_ROWS-2
hud_completion_notify(const demoresult * result)516 int hud_completion_notify(const demoresult* result){
517 if(elems){
518 elems->totalns = result->timens;
519 elems->frames = result->stats.renders;
520 }
521 return 0;
522 }
523
524 // inform the HUD of an upcoming demo, starting at startns.
hud_schedule(const char * demoname,uint64_t startns)525 int hud_schedule(const char* demoname, uint64_t startns){
526 elem* cure = malloc(sizeof(*cure));
527 if(!cure){
528 return -1;
529 }
530 cure->next = elems;
531 cure->name = strdup(demoname);
532 cure->totalns = 0;
533 cure->frames = 0;
534 cure->startns = startns;
535 elems = cure;
536 return hud_print_finished(elems);
537 }
538
539 // wake up every 10ms and render a frame so the HUD doesn't appear locked up.
540 // provide an absolute deadline calculated via CLOCK_MONOTONIC.
541 static int
demo_nanosleep_abstime_ns(struct notcurses * nc,uint64_t deadline)542 demo_nanosleep_abstime_ns(struct notcurses* nc, uint64_t deadline){
543 struct timespec fsleep;
544 struct timespec now;
545
546 clock_gettime(CLOCK_MONOTONIC, &now);
547 while(deadline > timespec_to_ns(&now)){
548 fsleep.tv_sec = 0;
549 fsleep.tv_nsec = MAXSLEEP;
550 if(deadline - timespec_to_ns(&now) < NANOSECS_IN_SEC / 100){
551 fsleep.tv_nsec = deadline - timespec_to_ns(&now);
552 }
553 ncinput ni;
554 // throw away any input we receive. if it was for the menu or HUD, it was
555 // already dispatched internally to demo_getc(). we need to ensure input
556 // is being procesed, however, to drive the demo elements.
557 demo_getc(nc, &fsleep, &ni);
558 if(hud){
559 int r = demo_render(nc);
560 if(r){
561 return r;
562 }
563 }
564 clock_gettime(CLOCK_MONOTONIC, &now);
565 }
566 return 0;
567 }
568
demo_nanosleep(struct notcurses * nc,const struct timespec * ts)569 int demo_nanosleep(struct notcurses* nc, const struct timespec *ts){
570 uint64_t deadline;
571 struct timespec now;
572 uint64_t nstotal = timespec_to_ns(ts);
573 clock_gettime(CLOCK_MONOTONIC, &now);
574 deadline = timespec_to_ns(&now) + nstotal;
575 return demo_nanosleep_abstime_ns(nc, deadline);
576 }
577
demo_nanosleep_abstime(struct notcurses * nc,const struct timespec * abstime)578 int demo_nanosleep_abstime(struct notcurses* nc, const struct timespec* abstime){
579 return demo_nanosleep_abstime_ns(nc, timespec_to_ns(abstime));
580 }
581
582 // FIXME needs to pass back any ncinput read, if requested...hrmmm
demo_render(struct notcurses * nc)583 int demo_render(struct notcurses* nc){
584 if(interrupted){
585 return 1;
586 }
587 if(about){
588 ncplane_move_top(about);
589 }
590 if(debug){
591 ncplane_move_top(debug);
592 }
593 struct timespec ts;
594 clock_gettime(CLOCK_MONOTONIC, &ts);
595 if(plot){
596 if(!plot_hidden){
597 ncplane_move_family_top(ncuplot_plane(plot));
598 }
599 uint64_t ns = (timespec_to_ns(&ts) - plottimestart) / NANOSECS_IN_SEC;
600 ncuplot_add_sample(plot, ns, 1);
601 }
602 if(menu){
603 ncplane_move_top(ncmenu_plane(menu));
604 }
605 if(hud){
606 if(!hud_hidden){
607 ncplane_move_top(hud);
608 }
609 uint64_t ns = timespec_to_ns(&ts) - elems->startns;
610 ++elems->frames;
611 nccell c = NCCELL_TRIVIAL_INITIALIZER;
612 ncplane_base(hud, &c);
613 ncplane_set_bg_rgb(hud, nccell_bg_rgb(&c));
614 ncplane_set_bg_alpha(hud, NCALPHA_BLEND);
615 ncplane_set_fg_rgb(hud, 0x80d0ff);
616 ncplane_set_fg_alpha(hud, NCALPHA_OPAQUE);
617 nccell_release(hud, &c);
618 ncplane_on_styles(hud, NCSTYLE_BOLD);
619 if(ncplane_printf_yx(hud, 1, 1, "%d", elems->frames) < 0){
620 return -1;
621 }
622 char buf[NCPREFIXCOLUMNS + 2];
623 ncnmetric(ns, sizeof(buf), NANOSECS_IN_SEC, buf, 0, 1000, '\0');
624 for(int x = 6 ; x < 14 - ncstrwidth(buf, NULL, NULL) ; ++x){
625 nccell ci = NCCELL_TRIVIAL_INITIALIZER;
626 ncplane_putc_yx(hud, 1, x, &ci);
627 }
628 //fprintf(stderr, "[%s] %zu %d\n", buf, strlen(buf), ncstrwidth(buf, NULL, NULL));
629 if(ncplane_printf_yx(hud, 1, 14 - ncstrwidth(buf, NULL, NULL), "%ss", buf) < 0){
630 return -1;
631 }
632 if(ncplane_putstr_yx(hud, 1, 16, elems->name) < 0){
633 return -1;
634 }
635 ncplane_off_styles(hud, NCSTYLE_BOLD);
636 }
637 ncinput ni;
638 uint32_t id;
639 id = demo_getc_nblock(nc, &ni);
640 int ret = notcurses_render(nc);
641 if(ret){
642 return ret;
643 }
644 if(id == 'q'){
645 return 1;
646 }
647 return 0;
648 }
649
fpsgraph_init(struct notcurses * nc)650 int fpsgraph_init(struct notcurses* nc){
651 const int PLOTHEIGHT = 6;
652 unsigned dimy, dimx;
653 struct ncplane* stdn = notcurses_stddim_yx(nc, &dimy, &dimx);
654 ncplane_options nopts = {
655 .y = NCALIGN_BOTTOM,
656 .x = NCALIGN_CENTER,
657 .rows = PLOTHEIGHT,
658 .cols = dimx > FPSGRAPH_MAX_COLS ? FPSGRAPH_MAX_COLS : dimx,
659 .userptr = NULL,
660 .name = "fps",
661 .resizecb = ncplane_resize_realign,
662 .flags = NCPLANE_OPTION_HORALIGNED | NCPLANE_OPTION_VERALIGNED
663 | NCPLANE_OPTION_FIXED,
664 };
665 struct ncplane* newp = ncplane_create(stdn, &nopts);
666 if(newp == NULL){
667 return -1;
668 }
669 uint32_t style = 0;
670 uint64_t channels = 0;
671 ncchannels_set_fg_alpha(&channels, NCALPHA_BLEND);
672 ncchannels_set_fg_rgb(&channels, 0x201040);
673 ncchannels_set_bg_alpha(&channels, NCALPHA_BLEND);
674 ncchannels_set_bg_rgb(&channels, 0x201040);
675 ncplane_set_base(newp, "", style, channels);
676 ncplot_options opts;
677 memset(&opts, 0, sizeof(opts));
678 opts.flags = NCPLOT_OPTION_LABELTICKSD |
679 NCPLOT_OPTION_EXPONENTIALD |
680 NCPLOT_OPTION_DETECTMAXONLY |
681 NCPLOT_OPTION_PRINTSAMPLE;
682 opts.gridtype = NCBLIT_BRAILLE;
683 opts.legendstyle = NCSTYLE_ITALIC | NCSTYLE_BOLD;
684 opts.title = "frames per second";
685 ncchannels_set_fg_rgb8(&opts.minchannels, 0x80, 0x80, 0xff);
686 ncchannels_set_bg_rgb(&opts.minchannels, 0x201040);
687 ncchannels_set_bg_alpha(&opts.minchannels, NCALPHA_BLEND);
688 ncchannels_set_fg_rgb8(&opts.maxchannels, 0x80, 0xff, 0x80);
689 ncchannels_set_bg_rgb(&opts.maxchannels, 0x201040);
690 ncchannels_set_bg_alpha(&opts.maxchannels, NCALPHA_BLEND);
691 // takes ownership of newp on all paths
692 struct ncuplot* fpsplot = ncuplot_create(newp, &opts, 0, 0);
693 if(!fpsplot){
694 return EXIT_FAILURE;
695 }
696 plot = fpsplot;
697 struct timespec ts;
698 clock_gettime(CLOCK_MONOTONIC, &ts);
699 plottimestart = timespec_to_ns(&ts);
700 if(plot_hidden){
701 ncplane_reparent_family(ncuplot_plane(plot), ncuplot_plane(plot));
702 }
703 return 0;
704 }
705
fpsgraph_stop(void)706 int fpsgraph_stop(void){
707 if(plot){
708 ncuplot_destroy(plot);
709 plot = NULL;
710 }
711 return 0;
712 }
713
714 // mouse has maybe pressed on the plot. the caller is responsible for rerendering.
fpsplot_grab(int y)715 int fpsplot_grab(int y){
716 int ret;
717 if(plot == NULL || plot_hidden){
718 return -1;
719 }
720 // are we in the middle of a grab?
721 if(plot_grab_y >= 0){
722 int delty = y - plot_grab_y;
723 ret = ncplane_move_yx(ncuplot_plane(plot), plot_pos_y + delty, ncplane_x(ncuplot_plane(plot)));
724 }else{
725 // new grab. stash point of original grab, and location of plot at original
726 // grab. any delta while grabbed (relative to the original grab point)
727 // will see the plot moved by delta (relative to the original plot location).
728 int ty = y;
729 // first, though, verify that we're clicking within the plot
730 if(!ncplane_translate_abs(ncuplot_plane(plot), &ty, NULL)){
731 return -1;
732 }
733 plot_grab_y = y;
734 ncplane_yx(ncuplot_plane(plot), &plot_pos_y, NULL);
735 ret = 0;
736 }
737 return ret;
738 }
739