diff --git a/include/notcurses.h b/include/notcurses.h index 86812200c..93a885be6 100644 --- a/include/notcurses.h +++ b/include/notcurses.h @@ -353,6 +353,11 @@ API unsigned ncplane_styles(const struct ncplane* n); // It is not safe to resize or destroy the plane during the fadeout FIXME. API int ncplane_fadeout(struct ncplane* n, const struct timespec* ts); +// Fade the ncplane in over the specified time. Load the ncplane with the +// target cells without rendering, then call this function. When it's done, the +// ncplane will have reached the target levels, starting from zeroes. +API int ncplane_fadein(struct ncplane* n, const struct timespec* ts); + // Working with cells #define CELL_TRIVIAL_INITIALIZER { .gcluster = '\0', .attrword = 0, .channels = 0, } diff --git a/src/demo/widecolor.c b/src/demo/widecolor.c index 49fa463ea..0764be52c 100644 --- a/src/demo/widecolor.c +++ b/src/demo/widecolor.c @@ -312,10 +312,12 @@ int widecolor_demo(struct notcurses* nc){ if(notcurses_render(nc)){ return -1; } - /*if(i){ FIXME - fadein(w, count, palette, FADE_MILLISECONDS); + if(i){ + struct timespec tv = { + .tv_sec = 0, .tv_nsec = FADE_MILLISECONDS * 1000000, + }; + ncplane_fadein(n, &tv); } - */ int key; do{ key = notcurses_getc_blocking(nc, &c, &special); diff --git a/src/lib/notcurses.c b/src/lib/notcurses.c index 2e98cbbc2..93cb1d858 100644 --- a/src/lib/notcurses.c +++ b/src/lib/notcurses.c @@ -1648,6 +1648,68 @@ alloc_ncplane_palette(ncplane* n, planepalette* pp){ return 0; } +int ncplane_fadein(ncplane* n, const struct timespec* ts){ + planepalette pp; + if(!n->nc->RGBflag && !n->nc->CCCflag){ // terminal can't fade + notcurses_render(n->nc); // render at the target levels (ought we delay?) + return -1; + } + if(alloc_ncplane_palette(n, &pp)){ + return -1; + } + int maxfsteps = pp.maxg > pp.maxr ? (pp.maxb > pp.maxg ? pp.maxb : pp.maxg) : + (pp.maxb > pp.maxr ? pp.maxb : pp.maxr); + int maxbsteps = pp.maxbg > pp.maxbr ? (pp.maxbb > pp.maxbg ? pp.maxbb : pp.maxbg) : + (pp.maxbb > pp.maxbr ? pp.maxbb : pp.maxbr); + int maxsteps = maxfsteps > maxbsteps ? maxfsteps : maxbsteps; + uint64_t nanosecs_total = ts->tv_sec * NANOSECS_IN_SEC + ts->tv_nsec; + uint64_t nanosecs_step = nanosecs_total / maxsteps; + struct timespec times; + clock_gettime(CLOCK_MONOTONIC, ×); + // Start time in absolute nanoseconds + uint64_t startns = times.tv_sec * NANOSECS_IN_SEC + times.tv_nsec; + // Current time, sampled each iteration + uint64_t curns; + do{ + clock_gettime(CLOCK_MONOTONIC, ×); + curns = times.tv_sec * NANOSECS_IN_SEC + times.tv_nsec; + int iter = (curns - startns) / nanosecs_step + 1; + if(iter > maxsteps){ + break; + } + int p; + for(p = 0 ; p < pp.size ; ++p){ + cell* c = &n->fb[p]; + unsigned r, g, b; + cell_rgb_get_fg(pp.channels[p], &r, &g, &b); + unsigned br, bg, bb; + cell_rgb_get_bg(pp.channels[p], &br, &bg, &bb); + r = r * iter / maxsteps; + g = g * iter / maxsteps; + b = b * iter / maxsteps; + cell_set_fg(c, r, g, b); + br = br * iter / maxsteps; + bg = bg * iter / maxsteps; + bb = bb * iter / maxsteps; + cell_set_bg(c, br, bg, bb); + } + notcurses_render(n->nc); + uint64_t nextwake = (iter + 1) * nanosecs_step + startns; + struct timespec sleepspec; + sleepspec.tv_sec = nextwake / NANOSECS_IN_SEC; + sleepspec.tv_nsec = nextwake % NANOSECS_IN_SEC; + int r; + // clock_nanosleep() has no love for CLOCK_MONOTONIC_RAW, at least as + // of Glibc 2.29 + Linux 5.3 :/. + r = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &sleepspec, NULL); + if(r){ + break; + } + }while(true); + free(pp.channels); + return 0; +} + int ncplane_fadeout(ncplane* n, const struct timespec* ts){ planepalette pp; if(!n->nc->RGBflag && !n->nc->CCCflag){ // terminal can't fade