mirror of
https://github.com/dankamongmen/notcurses
synced 2025-03-09 17:19:03 -04:00
fade: finish out proposed API, basic unit tests #659
This commit is contained in:
parent
74e8e9c3d9
commit
3deeecdf2e
6
NEWS.md
6
NEWS.md
@ -18,9 +18,9 @@ rearrangements of Notcurses.
|
||||
any meaning, and has been removed.
|
||||
* The `fadecb` typedef now accepts as its third argument a `const struct
|
||||
timespec`. This is the absolute deadline through which the frame ought
|
||||
be displayed. Like the changes to `ncvisual_stream()`, this gives more
|
||||
flexibility to the callback, and allows more precise timing. There will
|
||||
be further changes to the Fade API before API freeze (see #659).
|
||||
be displayed. New functions have been added to the Fade API: like the
|
||||
changes to `ncvisual_stream()`, this gives more flexibility, and allows
|
||||
more precise timing. All old functions remain available.
|
||||
|
||||
* 1.4.3 (2020-05-22)
|
||||
* Plot: make 8x1 the default, instead of 1x1.
|
||||
|
50
USAGE.md
50
USAGE.md
@ -1189,34 +1189,64 @@ int ncplane_stain(struct ncplane* n, int ystop, int xstop,
|
||||
uint64_t ul, uint64_t ur, uint64_t ll, uint64_t lr);
|
||||
```
|
||||
|
||||
My 14 year-old self would never forgive me if we didn't have sweet palette fades.
|
||||
My 14 year-old self would never forgive me if we didn't have sweet palette
|
||||
fades. The simple fade API runs the operation over a time interval, adapting
|
||||
to the actual runtime, invoking a callback at each iteration.
|
||||
|
||||
```c
|
||||
// Called for each delta performed in a fade on ncp. If anything but 0 is returned,
|
||||
// the fading operation ceases immediately, and that value is propagated out. If provided
|
||||
// and not NULL, the faders will not themselves call notcurses_render().
|
||||
typedef int (*fadecb)(struct notcurses* nc, struct ncplane* ncp, void* curry);
|
||||
// Called for each fade iteration on 'ncp'. If anything but 0 is returned,
|
||||
// the fading operation ceases immediately, and that value is propagated out.
|
||||
// The recommended absolute display time target is passed in 'tspec'.
|
||||
typedef int (*fadecb)(struct notcurses* nc, struct ncplane* ncp,
|
||||
const struct timespec*, void* curry);
|
||||
|
||||
// Fade the ncplane out over the provided time, calling the specified function
|
||||
// when done. Requires a terminal which supports truecolor, or at least palette
|
||||
// Fade the ncplane out over the provided time, calling 'fader' at each
|
||||
// iteration. Requires a terminal which supports truecolor, or at least palette
|
||||
// modification (if the terminal uses a palette, our ability to fade planes is
|
||||
// limited, and affected by the complexity of the rest of the screen).
|
||||
int ncplane_fadeout(struct ncplane* n, const struct timespec* ts, fadecb fader, void* curry);
|
||||
int ncplane_fadeout(struct ncplane* n, const struct timespec* ts,
|
||||
fadecb fader, void* curry);
|
||||
|
||||
// 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.
|
||||
int ncplane_fadein(struct ncplane* n, const struct timespec* ts, fadecb fader, void* curry);
|
||||
int ncplane_fadein(struct ncplane* n, const struct timespec* ts,
|
||||
fadecb fader, void* curry);
|
||||
|
||||
// Rather than the simple ncplane_fade{in/out}(), ncfadectx_setup() can be
|
||||
// Pulse the plane in and out until the callback returns non-zero, relying on
|
||||
// the callback 'fader' to initiate rendering. 'ts' defines the half-period
|
||||
// (i.e. the transition from black to full brightness, or back again). Proper
|
||||
// use involves preparing (but not rendering) an ncplane, then calling
|
||||
// ncplane_pulse(), which will fade in from black to the specified colors.
|
||||
int ncplane_pulse(struct ncplane* n, const struct timespec* ts, fadecb fader, void* curry);
|
||||
|
||||
```
|
||||
|
||||
Finally, a raw stream of RGBA or BGRx data can be blitted directly to an ncplane:
|
||||
The more flexible fade API allows for fine control of the process.
|
||||
|
||||
```c
|
||||
// paired with a loop over ncplane_fade{in/out}_iteration() + ncfadectx_free().
|
||||
struct ncfadectx* ncfadectx_setup(struct ncplane* n, const struct timespec* ts);
|
||||
|
||||
// Return the number of iterations through which 'nctx' will fade.
|
||||
int ncfadectx_iterations(const struct ncfadectx* nctx);
|
||||
|
||||
// Fade out through 'iter' iterations, where
|
||||
// 'iter' < 'ncfadectx_iterations(nctx)'.
|
||||
int ncplane_fadeout_iteration(struct ncplane* n, struct ncfadectx* nctx,
|
||||
int iter, fadecb fader, void* curry);
|
||||
|
||||
// Fade in through 'iter' iterations, where
|
||||
// 'iter' < 'ncfadectx_iterations(nctx)'.
|
||||
int ncplane_fadein_iteration(struct ncplane* n, struct ncfadectx* nctx,
|
||||
int iter, fadecb fader, void* curry);
|
||||
|
||||
// Release the resources associated with 'nctx'.
|
||||
void ncfadectx_free(struct ncfadectx* nctx);
|
||||
```
|
||||
|
||||
Raw streams of RGBA or BGRx data can be blitted directly to an ncplane:
|
||||
|
||||
```c
|
||||
// Blit a flat array 'data' of BGRx 32-bit values to the ncplane 'nc', offset
|
||||
|
@ -39,6 +39,7 @@ struct ncsubproc; // ncfdplane wrapper with subprocess management
|
||||
struct ncselector;// widget supporting selecting 1 from a list of options
|
||||
struct ncmultiselector; // widget supporting selecting 0..n from n options
|
||||
struct ncreader; // widget supporting free string input ala readline
|
||||
struct ncfadectx; // context for a palette fade operation
|
||||
|
||||
// Initialize a direct-mode notcurses context on the connected terminal at 'fp'.
|
||||
// 'fp' must be a tty. You'll usually want stdout. Direct mode supportes a
|
||||
@ -1971,8 +1972,8 @@ API unsigned ncplane_styles(const struct ncplane* n);
|
||||
typedef int (*fadecb)(struct notcurses* nc, struct ncplane* ncp,
|
||||
const struct timespec*, void* curry);
|
||||
|
||||
// Fade the ncplane out over the provided time, calling the specified function
|
||||
// when done. Requires a terminal which supports truecolor, or at least palette
|
||||
// Fade the ncplane out over the provided time, calling 'fader' at each
|
||||
// iteration. Requires a terminal which supports truecolor, or at least palette
|
||||
// modification (if the terminal uses a palette, our ability to fade planes is
|
||||
// limited, and affected by the complexity of the rest of the screen).
|
||||
API int ncplane_fadeout(struct ncplane* n, const struct timespec* ts,
|
||||
@ -1984,6 +1985,23 @@ API int ncplane_fadeout(struct ncplane* n, const struct timespec* ts,
|
||||
API int ncplane_fadein(struct ncplane* n, const struct timespec* ts,
|
||||
fadecb fader, void* curry);
|
||||
|
||||
// Rather than the simple ncplane_fade{in/out}(), ncfadectx_setup() can be
|
||||
// paired with a loop over ncplane_fade{in/out}_iteration() + ncfadectx_free().
|
||||
API struct ncfadectx* ncfadectx_setup(struct ncplane* n, const struct timespec* ts);
|
||||
|
||||
// Return the number of iterations through which 'nctx' will fade.
|
||||
API int ncfadectx_iterations(const struct ncfadectx* nctx);
|
||||
|
||||
// Fade out through 'iter' iterations, where
|
||||
// 'iter' < 'ncfadectx_iterations(nctx)'.
|
||||
API int ncplane_fadeout_iteration(struct ncplane* n, struct ncfadectx* nctx,
|
||||
int iter, fadecb fader, void* curry);
|
||||
|
||||
// Fade in through 'iter' iterations, where
|
||||
// 'iter' < 'ncfadectx_iterations(nctx)'.
|
||||
API int ncplane_fadein_iteration(struct ncplane* n, struct ncfadectx* nctx,
|
||||
int iter, fadecb fader, void* curry);
|
||||
|
||||
// Pulse the plane in and out until the callback returns non-zero, relying on
|
||||
// the callback 'fader' to initiate rendering. 'ts' defines the half-period
|
||||
// (i.e. the transition from black to full brightness, or back again). Proper
|
||||
@ -1991,6 +2009,9 @@ API int ncplane_fadein(struct ncplane* n, const struct timespec* ts,
|
||||
// ncplane_pulse(), which will fade in from black to the specified colors.
|
||||
API int ncplane_pulse(struct ncplane* n, const struct timespec* ts, fadecb fader, void* curry);
|
||||
|
||||
// Release the resources associated with 'nctx'.
|
||||
API void ncfadectx_free(struct ncfadectx* nctx);
|
||||
|
||||
// load up six cells with the EGCs necessary to draw a box. returns 0 on
|
||||
// success, -1 on error. on error, any cells this function might
|
||||
// have loaded before the error are cell_release()d. There must be at least
|
||||
|
160
src/lib/fade.c
160
src/lib/fade.c
@ -11,6 +11,10 @@ typedef struct ncfadectx {
|
||||
uint64_t* channels; // all channels from the framebuffer
|
||||
} ncfadectx;
|
||||
|
||||
int ncfadectx_iterations(const ncfadectx* nctx){
|
||||
return nctx->maxsteps;
|
||||
}
|
||||
|
||||
// These arrays are too large to be safely placed on the stack. Get an atomic
|
||||
// snapshot of all channels on the plane. While copying the snapshot, determine
|
||||
// the maxima across each of the six components.
|
||||
@ -53,7 +57,6 @@ alloc_ncplane_palette(ncplane* n, ncfadectx* pp, const struct timespec* ts){
|
||||
}
|
||||
}
|
||||
}
|
||||
// FIXME factor this duplication out
|
||||
channels = n->basecell.channels;
|
||||
pp->channels[y * pp->cols] = channels;
|
||||
channels_fg_rgb(channels, &r, &g, &b);
|
||||
@ -84,9 +87,14 @@ alloc_ncplane_palette(ncplane* n, ncfadectx* pp, const struct timespec* ts){
|
||||
if(pp->maxsteps == 0){
|
||||
pp->maxsteps = 1;
|
||||
}
|
||||
uint64_t nanosecs_total = timespec_to_ns(ts);
|
||||
pp->nanosecs_step = nanosecs_total / pp->maxsteps;
|
||||
if(pp->nanosecs_step == 0){
|
||||
uint64_t nanosecs_total;
|
||||
if(ts){
|
||||
nanosecs_total = timespec_to_ns(ts);
|
||||
pp->nanosecs_step = nanosecs_total / pp->maxsteps;
|
||||
if(pp->nanosecs_step == 0){
|
||||
pp->nanosecs_step = 1;
|
||||
}
|
||||
}else{
|
||||
pp->nanosecs_step = 1;
|
||||
}
|
||||
struct timespec times;
|
||||
@ -96,11 +104,54 @@ alloc_ncplane_palette(ncplane* n, ncfadectx* pp, const struct timespec* ts){
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ncplane_fadein_iteration(ncplane* n, ncfadectx* nctx, int iter,
|
||||
fadecb fader, void* curry){
|
||||
int y, x;
|
||||
// each time through, we need look each cell back up, due to the
|
||||
// possibility of a resize event :/
|
||||
int dimy, dimx;
|
||||
ncplane_dim_yx(n, &dimy, &dimx);
|
||||
for(y = 0 ; y < nctx->rows && y < dimy ; ++y){
|
||||
for(x = 0 ; x < nctx->cols && x < dimx; ++x){
|
||||
unsigned r, g, b;
|
||||
channels_fg_rgb(nctx->channels[nctx->cols * y + x], &r, &g, &b);
|
||||
unsigned br, bg, bb;
|
||||
channels_bg_rgb(nctx->channels[nctx->cols * y + x], &br, &bg, &bb);
|
||||
cell* c = &n->fb[dimx * y + x];
|
||||
if(!cell_fg_default_p(c)){
|
||||
r = r * iter / nctx->maxsteps;
|
||||
g = g * iter / nctx->maxsteps;
|
||||
b = b * iter / nctx->maxsteps;
|
||||
cell_set_fg_rgb(c, r, g, b);
|
||||
}
|
||||
if(!cell_bg_default_p(c)){
|
||||
br = br * iter / nctx->maxsteps;
|
||||
bg = bg * iter / nctx->maxsteps;
|
||||
bb = bb * iter / nctx->maxsteps;
|
||||
cell_set_bg_rgb(c, br, bg, bb);
|
||||
}
|
||||
}
|
||||
}
|
||||
uint64_t nextwake = (iter + 1) * nctx->nanosecs_step + nctx->startns;
|
||||
struct timespec sleepspec;
|
||||
sleepspec.tv_sec = nextwake / NANOSECS_IN_SEC;
|
||||
sleepspec.tv_nsec = nextwake % NANOSECS_IN_SEC;
|
||||
int ret = 0;
|
||||
if(fader){
|
||||
ret |= fader(n->nc, n, &sleepspec, curry);
|
||||
}else{
|
||||
ret |= notcurses_render(n->nc);
|
||||
// clock_nanosleep() has no love for CLOCK_MONOTONIC_RAW, at least as
|
||||
// of Glibc 2.29 + Linux 5.3 (or FreeBSD 12) :/.
|
||||
clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &sleepspec, NULL);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int
|
||||
ncplane_fadein_internal(ncplane* n, fadecb fader, ncfadectx* pp, void* curry){
|
||||
// Current time, sampled each iteration
|
||||
uint64_t curns;
|
||||
int ret = 0;
|
||||
do{
|
||||
struct timespec times;
|
||||
clock_gettime(CLOCK_MONOTONIC, ×);
|
||||
@ -109,49 +160,13 @@ ncplane_fadein_internal(ncplane* n, fadecb fader, ncfadectx* pp, void* curry){
|
||||
if(iter > pp->maxsteps){
|
||||
break;
|
||||
}
|
||||
int y, x;
|
||||
// each time through, we need look each cell back up, due to the
|
||||
// possibility of a resize event :/
|
||||
int dimy, dimx;
|
||||
ncplane_dim_yx(n, &dimy, &dimx);
|
||||
for(y = 0 ; y < pp->rows && y < dimy ; ++y){
|
||||
for(x = 0 ; x < pp->cols && x < dimx; ++x){
|
||||
unsigned r, g, b;
|
||||
channels_fg_rgb(pp->channels[pp->cols * y + x], &r, &g, &b);
|
||||
unsigned br, bg, bb;
|
||||
channels_bg_rgb(pp->channels[pp->cols * y + x], &br, &bg, &bb);
|
||||
cell* c = &n->fb[dimx * y + x];
|
||||
if(!cell_fg_default_p(c)){
|
||||
r = r * iter / pp->maxsteps;
|
||||
g = g * iter / pp->maxsteps;
|
||||
b = b * iter / pp->maxsteps;
|
||||
cell_set_fg_rgb(c, r, g, b);
|
||||
}
|
||||
if(!cell_bg_default_p(c)){
|
||||
br = br * iter / pp->maxsteps;
|
||||
bg = bg * iter / pp->maxsteps;
|
||||
bb = bb * iter / pp->maxsteps;
|
||||
cell_set_bg_rgb(c, br, bg, bb);
|
||||
}
|
||||
}
|
||||
}
|
||||
uint64_t nextwake = (iter + 1) * pp->nanosecs_step + pp->startns;
|
||||
struct timespec sleepspec;
|
||||
sleepspec.tv_sec = nextwake / NANOSECS_IN_SEC;
|
||||
sleepspec.tv_nsec = nextwake % NANOSECS_IN_SEC;
|
||||
if(fader){
|
||||
ret |= fader(n->nc, n, &sleepspec, curry);
|
||||
}else{
|
||||
ret |= notcurses_render(n->nc);
|
||||
// clock_nanosleep() has no love for CLOCK_MONOTONIC_RAW, at least as
|
||||
// of Glibc 2.29 + Linux 5.3 (or FreeBSD 12) :/.
|
||||
ret |= clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &sleepspec, NULL);
|
||||
}
|
||||
if(ret){
|
||||
break;
|
||||
int r = ncplane_fadein_iteration(n, pp, iter, fader, curry);
|
||||
if(r){
|
||||
return r;
|
||||
}
|
||||
clock_gettime(CLOCK_MONOTONIC, ×);
|
||||
}while(true);
|
||||
return ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ncplane_fadeout_iteration(ncplane* n, ncfadectx* nctx, int iter,
|
||||
@ -213,36 +228,54 @@ int ncplane_fadeout_iteration(ncplane* n, ncfadectx* nctx, int iter,
|
||||
return ret;
|
||||
}
|
||||
|
||||
int ncplane_fadeout(ncplane* n, const struct timespec* ts, fadecb fader, void* curry){
|
||||
ncfadectx pp;
|
||||
ncfadectx* ncfadectx_setup(ncplane* n, const struct timespec* ts){
|
||||
if(!n->nc->tcache.RGBflag && !n->nc->tcache.CCCflag){ // terminal can't fade
|
||||
return NULL;
|
||||
}
|
||||
ncfadectx* nctx = malloc(sizeof(*nctx));
|
||||
if(nctx){
|
||||
if(alloc_ncplane_palette(n, nctx, ts) == 0){
|
||||
return nctx;
|
||||
}
|
||||
free(nctx);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void ncfadectx_free(ncfadectx* nctx){
|
||||
if(nctx){
|
||||
free(nctx->channels);
|
||||
free(nctx);
|
||||
}
|
||||
}
|
||||
|
||||
int ncplane_fadeout(ncplane* n, const struct timespec* ts, fadecb fader, void* curry){
|
||||
ncfadectx* pp = ncfadectx_setup(n, ts);
|
||||
if(!pp){
|
||||
return -1;
|
||||
}
|
||||
if(alloc_ncplane_palette(n, &pp, ts)){
|
||||
return -1;
|
||||
}
|
||||
int ret = 0;
|
||||
struct timespec times;
|
||||
ns_to_timespec(pp.startns, ×);
|
||||
ns_to_timespec(pp->startns, ×);
|
||||
do{
|
||||
uint64_t curns = times.tv_sec * NANOSECS_IN_SEC + times.tv_nsec;
|
||||
int iter = (curns - pp.startns) / pp.nanosecs_step + 1;
|
||||
if(iter > pp.maxsteps){
|
||||
int iter = (curns - pp->startns) / pp->nanosecs_step + 1;
|
||||
if(iter > pp->maxsteps){
|
||||
break;
|
||||
}
|
||||
int r = ncplane_fadeout_iteration(n, &pp, iter, fader, curry);
|
||||
int r = ncplane_fadeout_iteration(n, pp, iter, fader, curry);
|
||||
if(r){
|
||||
ncfadectx_free(pp);
|
||||
return r;
|
||||
}
|
||||
clock_gettime(CLOCK_MONOTONIC, ×);
|
||||
}while(true);
|
||||
free(pp.channels);
|
||||
return ret;
|
||||
ncfadectx_free(pp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ncplane_fadein(ncplane* n, const struct timespec* ts, fadecb fader, void* curry){
|
||||
ncfadectx pp;
|
||||
if(!n->nc->tcache.RGBflag && !n->nc->tcache.CCCflag){ // terminal can't fade
|
||||
ncfadectx* nctx = ncfadectx_setup(n, ts);
|
||||
if(nctx == NULL){
|
||||
struct timespec now;
|
||||
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||
if(fader){
|
||||
@ -252,11 +285,8 @@ int ncplane_fadein(ncplane* n, const struct timespec* ts, fadecb fader, void* cu
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
if(alloc_ncplane_palette(n, &pp, ts)){
|
||||
return -1;
|
||||
}
|
||||
int ret = ncplane_fadein_internal(n, fader, &pp, curry);
|
||||
free(pp.channels);
|
||||
int ret = ncplane_fadein_internal(n, fader, nctx, curry);
|
||||
ncfadectx_free(nctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -78,6 +78,28 @@ TEST_CASE("Fade") {
|
||||
CHECK(0 < ncplane_pulse(n_, &ts, pulser, &pulsestart));
|
||||
}
|
||||
|
||||
SUBCASE("FadeOutUntimed") {
|
||||
auto nctx = ncfadectx_setup(n_, nullptr);
|
||||
REQUIRE(nctx);
|
||||
auto maxiter = ncfadectx_iterations(nctx);
|
||||
CHECK(0 < maxiter);
|
||||
for(int i = 0 ; i < maxiter ; ++i){
|
||||
CHECK(0 == ncplane_fadeout_iteration(n_, nctx, i, nullptr, nullptr));
|
||||
}
|
||||
ncfadectx_free(nctx);
|
||||
}
|
||||
|
||||
SUBCASE("FadeInUntimed") {
|
||||
auto nctx = ncfadectx_setup(n_, nullptr);
|
||||
REQUIRE(nctx);
|
||||
auto maxiter = ncfadectx_iterations(nctx);
|
||||
CHECK(0 < maxiter);
|
||||
for(int i = 0 ; i < maxiter ; ++i){
|
||||
CHECK(0 == ncplane_fadein_iteration(n_, nctx, i, nullptr, nullptr));
|
||||
}
|
||||
ncfadectx_free(nctx);
|
||||
}
|
||||
|
||||
CHECK(0 == notcurses_stop(nc_));
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user