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(&sectionchannels, 0xffffff);
329   ncchannels_set_bg_rgb(&sectionchannels, 0x000000);
330   ncchannels_set_fg_alpha(&sectionchannels, NCALPHA_HIGHCONTRAST);
331   ncchannels_set_bg_alpha(&sectionchannels, 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