Zoo 2, electric boogaloo (#939)

* Reimplement the widget zoo demo. The previous PoC
  was a multithreaded monster with behavior dependent
  on screen geometry. Replace it with a single thread state
  machine. Closes #936.
* Support titles for ncplot. Adds title to the ncplot_options
  struct, which may be NULL. Closes #941 .
* Properly color ncplot according to maxchannels and
  minchannels. Closes #940
* Add tools/function-table.sh script for generating public API list.
This commit is contained in:
Nick Black 2020-08-23 12:44:53 -04:00 committed by GitHub
parent 1e6558fed9
commit 73dc0a7d69
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 319 additions and 392 deletions

View File

@ -3,6 +3,11 @@ rearrangements of Notcurses.
* 1.6.17 (not yet released)
* `ncdirect_flush()` now takes a `const struct ncdirect*`.
* A `const char* title` field has been added to `ncplot_options`. If not
`NULL`, this title will be displayed to the right of any labels. Plot
data will cover the title, if present.
* `ncplot` no longer inverts `maxchannel` and `minchannel`. Speaking
of which, both of these fields are now plural, `maxchannels` etc.
* 1.6.16 (2020-08-22)
* `cell_simple_p()` has been removed. It is no longer a useful concept for

View File

@ -20,8 +20,8 @@ notcurses_plot - high level widget for plotting
typedef struct ncplot_options {
// channels for the maximum and minimum levels.
// lerp across the domain between these two.
uint64_t maxchannel;
uint64_t minchannel;
uint64_t maxchannels;
uint64_t minchannels;
// styling used for labels (NCPLOT_OPTION_LABELTICKSD)
uint16_t legendstyle;
// number of "pixels" per row x column
@ -31,6 +31,7 @@ typedef struct ncplot_options {
bool labelaxisd; // label dependent axis
bool exponentially; // is dependent exponential?
bool vertical_indep; // vertical independent variable
const char* title; // optional title
} ncplot_options;
```

View File

@ -2206,20 +2206,20 @@ API struct ncvisual* ncvisual_from_plane(const struct ncplane* n,
struct ncvisual_options {
// if no ncplane is provided, one will be created using the exact size
// necessary to render the source with perfect fidelity (this might be
// smaller or larger than the rendering area). if provided, style is
// taken into account, relative to the provided ncplane.
// smaller or larger than the rendering area).
struct ncplane* n;
// the style is ignored if no ncplane is provided (it ought be NCSCALE_NONE
// the scaling is ignored if no ncplane is provided (it ought be NCSCALE_NONE
// in this case). otherwise, the source is stretched/scaled relative to the
// provided ncplane.
ncscale_e scaling;
// if an ncplane is provided, y and x specify where the visual will be
// rendered on that plane. otherwise, they specify where the created ncplane
// will be placed.
// will be placed relative to the standard plane's origin.
int y, x;
// the section of the visual that ought be rendered. for the entire visual,
// pass an origin of 0, 0 and a size of 0, 0 (or the true height and width).
// these numbers are all in terms of ncvisual pixels.
// these numbers are all in terms of ncvisual pixels. negative values are
// prohibited.
int begy, begx; // origin of rendered section
int leny, lenx; // size of rendered section
// use NCBLIT_DEFAULT if you don't care, to use NCBLIT_2x2 (assuming
@ -2571,6 +2571,7 @@ bprefix(uintmax_t val, uintmax_t decimal, char* buf, int omitdec){
return ncmetric(val, decimal, buf, omitdec, 1024, 'i');
}
// Enable or disable the terminal's cursor, if supported. Immediate effect.
API void notcurses_cursor_enable(struct notcurses* nc);
API void notcurses_cursor_disable(struct notcurses* nc);
@ -2580,16 +2581,16 @@ API void notcurses_cursor_disable(struct notcurses* nc);
// terminal. If you can limit yourself to 256 colors, that's probably best.
typedef struct palette256 {
// We store the RGB values as a regular ol' channel
uint32_t chans[NCPALETTESIZE];
uint32_t chans[NCPALETTESIZE]; // RGB values as regular ol' channels
} palette256;
// Create a new palette store. It will be initialized with notcurses' best
// knowledge of the currently configured palette.
// knowledge of the currently configured palette. The palette upon startup
// cannot be reliably detected, sadly.
API palette256* palette256_new(struct notcurses* nc);
// Attempt to configure the terminal with the provided palette 'p'. Does not
// transfer ownership of 'p'; palette256_free() can still be called.
// transfer ownership of 'p'; palette256_free() can (ought) still be called.
API int palette256_use(struct notcurses* nc, const palette256* p);
// Manipulate entries in the palette store 'p'. These are *not* locked.
@ -2671,6 +2672,8 @@ typedef struct ncselector_options {
API struct ncselector* ncselector_create(struct ncplane* n, int y, int x,
const ncselector_options* opts);
// Dynamically add or delete items. It is usually sufficient to supply a static
// list of items via ncselector_options->items.
API int ncselector_additem(struct ncselector* n, const struct ncselector_item* item);
API int ncselector_delitem(struct ncselector* n, const char* item);
@ -2890,8 +2893,8 @@ API int ncmenu_destroy(struct ncmenu* n);
typedef struct ncplot_options {
// channels for the maximum and minimum levels. linear or exponential
// interpolation will be applied across the domain between these two.
uint64_t maxchannel;
uint64_t minchannel;
uint64_t maxchannels;
uint64_t minchannels;
// styling used for the legend, if NCPLOT_OPTION_LABELTICKSD is set
uint16_t legendstyle;
// if you don't care, pass NCBLIT_DEFAULT and get NCBLIT_8x1 (assuming
@ -2903,6 +2906,7 @@ typedef struct ncplot_options {
// if rangex is 0, it is dynamically set to the number of columns.
int rangex;
uint64_t flags; // bitfield over NCPLOT_OPTION_*
const char* title; // optional, printed by the labels
} ncplot_options;
// Use the provided plane 'n' for plotting according to the options 'opts'.

View File

@ -348,12 +348,13 @@ int ncplane_rotate_ccw(struct ncplane* n);
void ncplane_translate(const struct ncplane* src, const struct ncplane* dst, int* y, int* x);
bool ncplane_translate_abs(const struct ncplane* n, int* y, int* x);
typedef struct ncplot_options {
uint64_t maxchannel;
uint64_t minchannel;
uint64_t maxchannels;
uint64_t minchannels;
uint16_t legendstyle;
ncblitter_e gridtype;
uint64_t rangex;
unsigned flags;
const char* title;
} ncplot_options;
struct ncuplot* ncuplot_create(struct ncplane* n, const ncplot_options* opts, uint64_t miny, uint64_t maxy);
struct ncdplot* ncdplot_create(struct ncplane* n, const ncplot_options* opts, double miny, double maxy);

View File

@ -119,6 +119,13 @@ timespec_subtract(struct timespec *result, const struct timespec *time0,
return timespec_to_ns(time0) < timespec_to_ns(time1);
}
static inline uint64_t
timespec_add(struct timespec *result, const struct timespec *time0,
struct timespec *time1){
uint64_t ns = timespec_to_ns(time0) + timespec_to_ns(time1);
ns_to_timespec(ns, result);
return ns;
}
// divide the provided timespec 'ts' by 'divisor' into 'quots'
static inline void

View File

@ -543,12 +543,13 @@ int fpsgraph_init(struct notcurses* nc){
memset(&opts, 0, sizeof(opts));
opts.flags = NCPLOT_OPTION_LABELTICKSD | NCPLOT_OPTION_EXPONENTIALD;
opts.legendstyle = NCSTYLE_ITALIC;
channels_set_fg_rgb(&opts.minchannel, 0xff, 0x00, 0xff);
channels_set_bg(&opts.minchannel, 0x201020);
channels_set_bg_alpha(&opts.minchannel, CELL_ALPHA_BLEND);
channels_set_fg_rgb(&opts.maxchannel, 0x00, 0xff, 0x00);
channels_set_bg(&opts.maxchannel, 0x201020);
channels_set_bg_alpha(&opts.maxchannel, CELL_ALPHA_BLEND);
opts.title = "frames per second";
channels_set_fg_rgb(&opts.minchannels, 0x80, 0x80, 0xff);
channels_set_bg(&opts.minchannels, 0x201020);
channels_set_bg_alpha(&opts.minchannels, CELL_ALPHA_BLEND);
channels_set_fg_rgb(&opts.maxchannels, 0x80, 0xff, 0x80);
channels_set_bg(&opts.maxchannels, 0x201020);
channels_set_bg_alpha(&opts.maxchannels, CELL_ALPHA_BLEND);
struct ncuplot* fpsplot = ncuplot_create(newp, &opts, 0, 0);
if(!fpsplot){
ncplane_destroy(newp);

View File

@ -96,7 +96,7 @@ int intro(struct notcurses* nc){
return -1;
}
ncplane_styles_on(ncp, NCSTYLE_ITALIC | NCSTYLE_BOLD);
if(ncplane_putstr_aligned(ncp, rows / 2 - 2, NCALIGN_CENTER, str) != (int)strlen(str)){
if(ncplane_putstr_aligned(ncp, rows / 2 - 3, NCALIGN_CENTER, str) != (int)strlen(str)){
return -1;
}
ncplane_styles_off(ncp, NCSTYLE_ITALIC);
@ -104,12 +104,12 @@ int intro(struct notcurses* nc){
int major, minor, patch, tweak;
notcurses_version_components(&major, &minor, &patch, &tweak);
if(tweak){
if(ncplane_printf_aligned(ncp, rows / 2, NCALIGN_CENTER, "notcurses %d.%d.%d.%d. press 'q' to quit.",
if(ncplane_printf_aligned(ncp, rows / 2 - 1, NCALIGN_CENTER, "notcurses %d.%d.%d.%d. press 'q' to quit.",
major, minor, patch, tweak) < 0){
return -1;
}
}else{
if(ncplane_printf_aligned(ncp, rows / 2, NCALIGN_CENTER, "notcurses %d.%d.%d. press 'q' to quit.",
if(ncplane_printf_aligned(ncp, rows / 2 - 1, NCALIGN_CENTER, "notcurses %d.%d.%d. press 'q' to quit.",
major, minor, patch) < 0){
return -1;
}
@ -119,6 +119,10 @@ int intro(struct notcurses* nc){
if(ncplane_putwstr_aligned(ncp, rows / 2 - 6, NCALIGN_CENTER, wstr) < 0){
return -1;
}
const wchar_t iwstr[] = L"▏█ ▇ ▆ ▅ ▄ ▃ ▂ ▁ ▁ ▂ ▃ ▄ ▅ ▆ ▇ █▕";
if(ncplane_putwstr_aligned(ncp, rows / 2 + 1, NCALIGN_CENTER, iwstr) < 0){
return -1;
}
if(rows < 45){
ncplane_set_fg_rgb(ncp, 0xc0, 0x80, 0x80);
ncplane_set_bg_rgb(ncp, 0x20, 0x20, 0x20);

View File

@ -1,29 +1,29 @@
#include "demo.h"
#include <pthread.h>
#define THREAD_RETURN_NEGATIVE ((void*)-1)
#define THREAD_RETURN_POSITIVE ((void*)1)
// As the subwidgets (selector etc.) are taking the catwalk out, they process
// input to show off their functionality. Once they're done, the expositional
// plane might still be generating output; it should then start using
// demo_getc_blocking(), passing any input to the widgets itself (since they're
// inactive otherwise). This is set by the last widget (current multiselector).
static int sub_widgets_done = 0; // guarded by main lock
static struct ncmultiselector* mselector = NULL;
// open up changes.jpg, stretch it to fill, drop it to greyscale
static int
locked_demo_render(struct notcurses* nc, pthread_mutex_t* lock){
int ret;
if(pthread_mutex_lock(lock)){
return -1;
draw_background(struct notcurses* nc){
if(notcurses_canopen_images(nc)){
struct ncplane* n = notcurses_stdplane(nc);
nc_err_e err;
char* path = find_data("changes.jpg");
struct ncvisual* ncv = ncvisual_from_file(path, &err);
free(path);
if(!ncv){
return -1;
}
struct ncvisual_options vopts = {
.scaling = NCSCALE_STRETCH,
.n = n,
};
if(ncvisual_render(nc, ncv, &vopts) == NULL){
ncvisual_destroy(ncv);
return -1;
}
ncplane_greyscale(n);
ncvisual_destroy(ncv);
}
ret = demo_render(nc);
if(pthread_mutex_unlock(lock)){
return -1;
}
return ret;
return 0;
}
// we list all distributions on which notcurses is known to exist
@ -64,9 +64,7 @@ static struct ncmselector_item mselect_items[] = {
};
static struct ncmultiselector*
multiselector_demo(struct ncplane* n, struct ncplane* under, int dimx,
int y, pthread_mutex_t* lock, int* ret){
struct notcurses* nc = ncplane_notcurses(n);
multiselector_demo(struct ncplane* n, struct ncplane* under, int y){
ncmultiselector_options mopts = {
.maxdisplay = 8,
.title = "multi-item selector",
@ -80,110 +78,17 @@ multiselector_demo(struct ncplane* n, struct ncplane* under, int dimx,
};
channels_set_fg_alpha(&mopts.bgchannels, CELL_ALPHA_BLEND);
channels_set_bg_alpha(&mopts.bgchannels, CELL_ALPHA_BLEND);
pthread_mutex_lock(lock);
struct ncmultiselector* mselect = ncmultiselector_create(n, y, 0, &mopts);
if(mselect == NULL){
pthread_mutex_unlock(lock);
return NULL;
}
struct ncplane* mplane = ncmultiselector_plane(mselect);
ncplane_move_below(mplane, under);
pthread_mutex_unlock(lock);
struct timespec swoopdelay;
timespec_div(&demodelay, dimx / 3, &swoopdelay);
pthread_mutex_lock(lock);
int length = ncplane_dim_x(mplane);
ncplane_move_yx(mplane, y, -length);
pthread_mutex_unlock(lock);
ncinput ni;
for(int i = -length + 1 ; i < dimx - (length + 1) ; ++i){
struct timespec now, deadline;
clock_gettime(CLOCK_MONOTONIC, &now);
ns_to_timespec(timespec_to_ns(&swoopdelay) + timespec_to_ns(&now), &deadline);
do{
pthread_mutex_lock(lock);
*ret = demo_render(nc);
ncplane_move_yx(mplane, y, i);
pthread_mutex_unlock(lock);
if(*ret){
ncmultiselector_destroy(mselect);
return NULL;
}
struct timespec iterdelay;
ns_to_timespec(timespec_subtract_ns(&deadline, &now), &iterdelay);
char32_t wc = demo_getc(nc, &iterdelay, &ni);
if(wc == (char32_t)-1){
ncmultiselector_destroy(mselect);
return NULL;
}else if(wc){
pthread_mutex_lock(lock);
ncmultiselector_offer_input(mselect, &ni);
*ret = demo_render(nc);
pthread_mutex_unlock(lock);
if(*ret){
ncmultiselector_destroy(mselect);
return NULL;
}
}
clock_gettime(CLOCK_MONOTONIC, &now);
}while(timespec_to_ns(&now) < timespec_to_ns(&deadline));
}
if( (*ret = locked_demo_render(nc, lock)) ){
ncmultiselector_destroy(mselect);
return NULL;
}
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
uint64_t cur = timespec_to_ns(&ts);
uint64_t targ = cur + timespec_to_ns(&demodelay);
do{
struct timespec rel;
ns_to_timespec(targ - cur, &rel);
char32_t wc = demo_getc(nc, &rel, &ni);
if(wc == (char32_t)-1){
ncmultiselector_destroy(mselect);
return NULL;
}else if(wc){
ncmultiselector_offer_input(mselect, &ni);
}
clock_gettime(CLOCK_MONOTONIC, &ts);
cur = timespec_to_ns(&ts);
}while(cur < targ);
pthread_mutex_lock(lock);
sub_widgets_done = 1;
pthread_mutex_unlock(lock);
return mselect;
}
static int
draw_background(struct notcurses* nc){
if(notcurses_canopen_images(nc)){
struct ncplane* n = notcurses_stdplane(nc);
nc_err_e err;
char* path = find_data("changes.jpg");
struct ncvisual* ncv = ncvisual_from_file(path, &err);
free(path);
if(!ncv){
return -1;
}
struct ncvisual_options vopts = {
.scaling = NCSCALE_STRETCH,
.n = n,
};
if(ncvisual_render(nc, ncv, &vopts) == NULL){
ncvisual_destroy(ncv);
return -1;
}
ncplane_greyscale(n);
ncvisual_destroy(ncv);
}
return 0;
}
static struct ncselector*
selector_demo(struct ncplane* n, struct ncplane* under, int dimx,
int y, pthread_mutex_t* lock, int* ret){
struct notcurses* nc = ncplane_notcurses(n);
selector_demo(struct ncplane* n, struct ncplane* under, int dimx, int y){
ncselector_options sopts = {
.title = "single-item selector",
.items = select_items,
@ -198,54 +103,20 @@ selector_demo(struct ncplane* n, struct ncplane* under, int dimx,
};
channels_set_fg_alpha(&sopts.bgchannels, CELL_ALPHA_BLEND);
channels_set_bg_alpha(&sopts.bgchannels, CELL_ALPHA_BLEND);
pthread_mutex_lock(lock);
struct ncselector* selector = ncselector_create(n, y, dimx, &sopts);
if(selector == NULL){
pthread_mutex_unlock(lock);
return NULL;
}
struct ncplane* splane = ncselector_plane(selector);
ncplane_move_below(splane, under);
pthread_mutex_unlock(lock);
struct timespec swoopdelay;
timespec_div(&demodelay, dimx / 3, &swoopdelay);
ncinput ni;
for(int i = dimx - 1 ; i > 1 ; --i){
struct timespec now, deadline;
clock_gettime(CLOCK_MONOTONIC, &now);
ns_to_timespec(timespec_to_ns(&swoopdelay) + timespec_to_ns(&now), &deadline);
do{
pthread_mutex_lock(lock);
*ret = demo_render(nc);
ncplane_move_yx(splane, y, i);
pthread_mutex_unlock(lock);
if(*ret){
ncselector_destroy(selector, NULL);
return NULL;
}
struct timespec iterdelay;
ns_to_timespec(timespec_subtract_ns(&deadline, &now), &iterdelay);
char32_t wc = demo_getc(nc, &iterdelay, &ni);
if(wc == (char32_t)-1){
ncselector_destroy(selector, NULL);
return NULL;
}else if(wc){
pthread_mutex_lock(lock);
ncselector_offer_input(selector, &ni);
*ret = demo_render(nc);
pthread_mutex_unlock(lock);
if(*ret){
ncselector_destroy(selector, NULL);
return NULL;
}
}
clock_gettime(CLOCK_MONOTONIC, &now);
}while(timespec_to_ns(&now) < timespec_to_ns(&deadline));
}
if( (*ret = locked_demo_render(nc, lock)) ){
ncselector_destroy(selector, NULL);
return NULL;
}
struct ncplane* mplane = ncselector_plane(selector);
ncplane_move_below(mplane, under);
return selector;
}
// wait one demodelay period, offering input to the multiselector, then fade
// out both widgets (if supported).
static int
reader_post(struct notcurses* nc, struct ncselector* selector, struct ncmultiselector* mselector){
int ret;
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
uint64_t cur = timespec_to_ns(&ts);
@ -253,143 +124,194 @@ selector_demo(struct ncplane* n, struct ncplane* under, int dimx,
do{
struct timespec rel;
ns_to_timespec(targ - cur, &rel);
ncinput ni;
char32_t wc = demo_getc(nc, &rel, &ni);
if(wc == (char32_t)-1){
ncselector_destroy(selector, NULL);
return NULL;
return -1;
}else if(wc){
pthread_mutex_lock(lock);
ncselector_offer_input(selector, &ni);
pthread_mutex_unlock(lock);
ncmultiselector_offer_input(mselector, &ni);
}
clock_gettime(CLOCK_MONOTONIC, &ts);
cur = timespec_to_ns(&ts);
}while(cur < targ);
return selector;
}
static int
riser_collect_input(struct notcurses* nc, const struct timespec* ts){
struct timespec now, deadline;
clock_gettime(CLOCK_MONOTONIC, &now);
ns_to_timespec(timespec_to_ns(&now) + timespec_to_ns(ts), &deadline);
do{
ncinput ni;
char32_t key = demo_getc(nc, ts, &ni);
if(key == (char32_t)-1){
return -1;
}else if(key){
ncmultiselector_offer_input(mselector, &ni);
if( (ret = demo_render(nc)) ){
return ret;
}
clock_gettime(CLOCK_MONOTONIC, &now);
}while(timespec_to_ns(&deadline) > timespec_to_ns(&now));
}while(cur < targ);
if(notcurses_canfade(nc)){
if(ncplane_fadeout(ncselector_plane(selector), &demodelay, demo_fader, NULL)){
return -1;
}
if(ncplane_fadeout(ncmultiselector_plane(mselector), &demodelay, demo_fader, NULL)){
return -1;
}
}
return 0;
}
typedef struct read_marshal {
struct notcurses* nc;
struct ncreader* reader;
pthread_mutex_t* lock;
} read_marshal;
static int
layout_next_text(struct ncreader* reader, const char* text, size_t* textpos){
size_t towrite = strcspn(text + *textpos, " \t\n");
towrite += strspn(text + *textpos + towrite, " \t\n");
if(towrite){
char* duped = strndup(text + *textpos, towrite);
size_t bytes;
if(ncplane_puttext(ncreader_plane(reader), -1, NCALIGN_LEFT, duped, &bytes) < 0 || bytes != strlen(duped)){
free(duped);
return -1;
}
free(duped);
*textpos += towrite;
}
return 0;
}
static void*
reader_thread(void* vmarsh){
static int
run_out_text(struct ncreader* reader, const char* text, size_t* textpos,
const struct timespec* iterdelay){
while(text[*textpos]){
int ret = layout_next_text(reader, text, textpos);
if(ret){
return ret;
}
demo_nanosleep(ncplane_notcurses(ncreader_plane(reader)), iterdelay);
}
return 0;
}
// selector moves across to the left; reader moves up halfway to the center
static int
selector_run(struct notcurses* nc, struct ncreader* reader, struct ncselector* selector){
const char text[] =
"Notcurses provides several widgets to quickly build vivid TUIs.\n\n"
"This NCReader widget facilitates free-form text entry complete with readline-style bindings. "
"NCSelector allows a single option to be selected from a list. "
"NCSelector allows a single option to be selected from a list. ";
int ret = 0, dimy, dimx;
ncplane_dim_yx(notcurses_stdplane(nc), &dimy, &dimx);
const int centery = (dimy - ncplane_dim_y(ncreader_plane(reader))) / 2;
int ry, rx, sy, sx;
ncplane_yx(ncreader_plane(reader), &ry, &rx);
ncplane_yx(ncselector_plane(selector), &sy, &sx);
const int xiters = sx - 2;
const int yiters = (ry - centery) / 2;
const int iters = yiters > xiters ? yiters : xiters;
const double eachy = (double)iters / yiters;
const double eachx = (double)iters / xiters;
int xi = 1;
int yi = 1;
struct timespec iterdelay, start;
timespec_div(&demodelay, iters, &iterdelay);
size_t textpos = 0;
clock_gettime(CLOCK_MONOTONIC, &start);
for(int i = 0 ; i < iters ; ++i){
if( (ret = layout_next_text(reader, text, &textpos)) ){
return ret;
}
if(i == (int)(xi * eachx)){
if(ncplane_move_yx(ncselector_plane(selector), sy, --sx)){
return -1;
}
++xi;
}
if(i == (int)(yi * eachy)){
if(ncplane_move_yx(ncreader_plane(reader), --ry, rx)){
return -1;
}
++yi;
}
struct timespec targettime, now;
timespec_mul(&iterdelay, i + 1, &targettime);
const uint64_t deadline_ns = timespec_to_ns(&start) + timespec_to_ns(&targettime);
clock_gettime(CLOCK_MONOTONIC, &now);
while(timespec_to_ns(&now) < deadline_ns){
if( (ret = demo_render(nc)) ){
return ret;
}
struct ncinput ni;
struct timespec inputtime;
ns_to_timespec(deadline_ns - timespec_to_ns(&now), &inputtime);
char32_t wc = demo_getc(nc, &inputtime, &ni);
if(wc == (char32_t)-1){
return -1;
}else if(wc){
ncselector_offer_input(selector, &ni);
}
clock_gettime(CLOCK_MONOTONIC, &now);
}
}
return run_out_text(reader, text, &textpos, &iterdelay);
}
// selector moves across to the right; reader moves up halfway to the center
static int
mselector_run(struct notcurses* nc, struct ncreader* reader, struct ncmultiselector* mselector){
const char text[] =
"NCMultiselector allows 0..n options to be selected from a list of n items. "
"NCFdplane streams a file descriptor, while NCSubproc spawns a subprocess and streams its output. "
"A variety of plots are supported, and menus can be placed along the top and/or bottom of any plane.\n\n"
"Widgets can be controlled with the keyboard and/or mouse. They are implemented atop ncplanes, and these planes can be manipulated like all others.";
const size_t textlen = strlen(text);
read_marshal* marsh = vmarsh;
struct notcurses* nc = marsh->nc;
struct ncreader* reader = marsh->reader;
pthread_mutex_t* lock = marsh->lock;
free(marsh);
int x, y;
struct ncplane* rplane = ncreader_plane(reader);
struct timespec rowdelay;
ncplane_yx(rplane, &y, &x);
int targrow = y / 2;
// the other widgets divide the movement range by 3 (and thus take about 3
// demodelays to transit). take about 3 demodelays to rise to midscreen. this
// also affects the "typing" speed.
timespec_div(&demodelay, (y - targrow) / 2, &rowdelay);
// we usually won't be done rendering the text before reaching our target row
int ret = 0, dimy, dimx;
ncplane_dim_yx(notcurses_stdplane(nc), &dimy, &dimx);
const int centery = (dimy - ncplane_dim_y(ncreader_plane(reader))) / 2;
int ry, rx, sy, sx;
ncplane_yx(ncreader_plane(reader), &ry, &rx);
ncplane_yx(ncmultiselector_plane(mselector), &sy, &sx);
const int xiters = dimx - ncplane_dim_x(ncmultiselector_plane(mselector));
const int yiters = ry - centery;
const int iters = yiters > xiters ? yiters : xiters;
const double eachy = (double)iters / yiters;
const double eachx = (double)iters / xiters;
int xi = 1;
int yi = 1;
struct timespec iterdelay, start;
timespec_div(&demodelay, iters, &iterdelay);
clock_gettime(CLOCK_MONOTONIC, &start);
size_t textpos = 0;
int ret;
bool collect_input = false;
while(textpos < textlen || y > targrow){
pthread_mutex_lock(lock);
ncplane_move_yx(rplane, y, x);
size_t towrite = strcspn(text + textpos, " \t\n");
towrite += strspn(text + textpos + towrite, " \t\n");
if(towrite){
char* duped = strndup(text + textpos, towrite);
size_t bytes;
if(ncplane_puttext(rplane, -1, NCALIGN_LEFT, duped, &bytes) < 0 || bytes != strlen(duped)){
free(duped);
return THREAD_RETURN_NEGATIVE;
}
free(duped);
textpos += towrite;
for(int i = 0 ; i < iters ; ++i){
if( (ret = layout_next_text(reader, text, &textpos)) ){
return ret;
}
if(i == (int)(xi * eachx)){
if(ncplane_move_yx(ncmultiselector_plane(mselector), sy, ++sx)){
return -1;
}
if(sub_widgets_done){
collect_input = true;
++xi;
}
if(i == (int)(yi * eachy)){
if(ncplane_move_yx(ncreader_plane(reader), --ry, rx)){
return -1;
}
++yi;
}
struct timespec targettime, now;
timespec_mul(&iterdelay, i + 1, &targettime);
const uint64_t deadline_ns = timespec_to_ns(&start) + timespec_to_ns(&targettime);
clock_gettime(CLOCK_MONOTONIC, &now);
while(timespec_to_ns(&now) < deadline_ns){
if( (ret = demo_render(nc)) ){
pthread_mutex_unlock(lock);
if(ret < 0){
return THREAD_RETURN_NEGATIVE;
}else if(ret > 0){
return THREAD_RETURN_POSITIVE;
}
return ret;
}
if(y > targrow){
--y;
struct ncinput ni;
struct timespec inputtime;
ns_to_timespec(deadline_ns - timespec_to_ns(&now), &inputtime);
char32_t wc = demo_getc(nc, &inputtime, &ni);
if(wc == (char32_t)-1){
return -1;
}else if(wc){
ncmultiselector_offer_input(mselector, &ni);
}
pthread_mutex_unlock(lock);
if(collect_input){
ret = riser_collect_input(nc, &rowdelay);
}else{
ret = clock_nanosleep(CLOCK_MONOTONIC, 0, &rowdelay, NULL);
}
if(ret < 0){
return THREAD_RETURN_NEGATIVE;
}else if(ret > 0){
return THREAD_RETURN_POSITIVE;
clock_gettime(CLOCK_MONOTONIC, &now);
}
}
pthread_mutex_lock(lock);
if(sub_widgets_done){
collect_input = true;
}
pthread_mutex_unlock(lock);
if(collect_input){
ret = riser_collect_input(nc, &demodelay);
}
if(ret < 0){
return THREAD_RETURN_NEGATIVE;
}else if(ret > 0){
return THREAD_RETURN_POSITIVE;
}
return NULL;
return run_out_text(reader, text, &textpos, &iterdelay);
}
// creates an ncreader, and spawns a thread which will fill it with text
// describing the rest of the demo
static struct ncreader*
reader_demo(struct notcurses* nc, pthread_t* tid, pthread_mutex_t* lock){
read_marshal* marsh = malloc(sizeof(*marsh));
if(marsh == NULL){
return NULL;
}
marsh->nc = nc;
marsh->lock = lock;
int dimy;
struct ncplane* std = notcurses_stddim_yx(nc, &dimy, NULL);
// creates an ncreader, ncselector, and ncmultiselector, and moves them into
// place. the latter two are then faded out. all three are then destroyed.
static int
reader_demo(struct notcurses* nc){
int ret = -1;
int dimy, dimx;
struct ncplane* std = notcurses_stddim_yx(nc, &dimy, &dimx);
const int READER_COLS = 64;
const int READER_ROWS = 8;
ncreader_options nopts = {
@ -401,87 +323,50 @@ reader_demo(struct notcurses* nc, pthread_t* tid, pthread_mutex_t* lock){
};
channels_set_bg_alpha(&nopts.echannels, CELL_ALPHA_BLEND);
const int x = ncplane_align(std, NCALIGN_CENTER, nopts.physcols);
if((marsh->reader = ncreader_create(std, dimy, x, &nopts)) == NULL){
free(marsh);
return NULL;
struct ncselector* selector = NULL;
struct ncmultiselector* mselector = NULL;
struct ncreader* reader = ncreader_create(std, dimy, x, &nopts);
if(reader == NULL){
goto done;
}
struct ncreader* reader = marsh->reader;
ncplane_set_scrolling(ncreader_plane(reader), true);
if(pthread_create(tid, NULL, reader_thread, marsh)){
ncreader_destroy(marsh->reader, NULL);
free(marsh);
return NULL;
// Bring the selector left across the top, while raising the exposition
// halfway to its target height.
selector = selector_demo(std, ncreader_plane(reader), dimx, 2);
if(selector == NULL){
goto done;
}
if( (ret = selector_run(nc, reader, selector)) ){
goto done;
}
// Bring the multiselector right across the top, while raising the exposition
// the remainder of its path to the center of the screen.
mselector = multiselector_demo(std, ncreader_plane(reader), 8);
if(mselector == NULL){
goto done;
}
if( (ret = mselector_run(nc, reader, mselector)) ){
goto done;
}
// Delay and fade
if( (ret = reader_post(nc, selector, mselector)) ){
goto done;
}
return reader;
}
static int
zap_reader(pthread_t tid, unsigned cancel){
if(cancel){
pthread_cancel(tid);
}
void* res;
int ret = pthread_join(tid, &res);
if(res == THREAD_RETURN_NEGATIVE){
return -1;
}else if(res == THREAD_RETURN_POSITIVE){
return 1;
}
done:
ncselector_destroy(selector, NULL);
ncmultiselector_destroy(mselector);
ncreader_destroy(reader, NULL);
return ret;
}
// a plane with exposition text rises from the bottom to the center of the
// screen. as it does so, two widgets (selector and multiselector) come in
// from the left and right, respectively. they then fade out.
int zoo_demo(struct notcurses* nc){
int dimx;
if(draw_background(nc)){
return -1;
}
pthread_mutex_t lock;
if(pthread_mutex_init(&lock, NULL)){
return -1;
}
struct ncplane* n = notcurses_stddim_yx(nc, NULL, &dimx);
pthread_t readertid;
struct ncreader* reader = reader_demo(nc, &readertid, &lock);
// if we didn't get a reader, need to hand-roll the exit, since we have no
// thread at which we might go off blasting
if(reader == NULL){
pthread_mutex_destroy(&lock);
return -1;
}
int ret = 0;
struct ncselector* selector = NULL;
selector = selector_demo(n, ncreader_plane(reader), dimx, 2, &lock, &ret);
if(selector == NULL || ret){
goto err;
}
mselector = multiselector_demo(n, ncreader_plane(reader), dimx, 8, &lock, &ret); // FIXME calculate from splane
if(mselector == NULL || ret){
goto err;
}
ret |= zap_reader(readertid, false); // let the thread do its thang
ret |= pthread_mutex_destroy(&lock);
if(notcurses_canfade(nc)){
if(ncplane_fadeout(ncselector_plane(selector), &demodelay, demo_fader, NULL)){
goto err;
}
if(ncplane_fadeout(ncmultiselector_plane(mselector), &demodelay, demo_fader, NULL)){
goto err;
}
}else{
if( (ret = demo_nanosleep(nc, &demodelay)) ){
goto err;
}
}
ncreader_destroy(reader, NULL);
ncselector_destroy(selector, NULL);
ncmultiselector_destroy(mselector);
return ret;
err:
zap_reader(readertid, true);
pthread_mutex_destroy(&lock);
ncreader_destroy(reader, NULL);
ncselector_destroy(selector, NULL);
ncmultiselector_destroy(mselector);
return ret ? ret : -1;
return reader_demo(nc);
}

View File

@ -210,9 +210,9 @@ int input_demo(ncpp::NotCurses* nc) {
struct ncplot_options popts{};
// FIXME would be nice to switch over to exponential at some level
popts.flags = NCPLOT_OPTION_LABELTICKSD;
popts.minchannel = popts.maxchannel = 0;
channels_set_fg_rgb(&popts.minchannel, 0x40, 0x50, 0xb0);
channels_set_fg_rgb(&popts.maxchannel, 0x40, 0xff, 0xd0);
popts.minchannels = popts.maxchannels = 0;
channels_set_fg_rgb(&popts.minchannels, 0x40, 0x50, 0xb0);
channels_set_fg_rgb(&popts.maxchannels, 0x40, 0xff, 0xd0);
popts.gridtype = static_cast<ncblitter_e>(NCBLIT_2x2);
plot = ncuplot_create(pplane, &popts, 0, 0);
if(!plot){

View File

@ -4,6 +4,7 @@
#include <cmath>
#include <array>
#include <limits>
#include <string>
#include "internal.h"
#include "notcurses/notcurses.h"
@ -53,6 +54,9 @@ class ncppplot {
return false;
}
int dimx = sdimx;
if(opts->title){
ncpp->title = std::string(opts->title);
}
ncpp->rangex = opts->rangex;
// if we're sizing the plot based off the plane dimensions, scale it by the
// plot geometry's width for all calculations
@ -77,8 +81,8 @@ class ncppplot {
if(ncpp->slots){
memset(ncpp->slots, 0, slotsize);
ncpp->ncp = n;
ncpp->maxchannel = opts->maxchannel;
ncpp->minchannel = opts->minchannel;
ncpp->maxchannels = opts->maxchannels;
ncpp->minchannels = opts->minchannels;
ncpp->bset = bset;
ncpp->miny = miny;
ncpp->maxy = maxy;
@ -127,13 +131,13 @@ class ncppplot {
// if we want fewer slots than there are available columns, our final column
// will be other than the plane's final column. most recent x goes here.
const int finalx = (slotcount < scaleddim - 1 - (startx * scale) ? startx + (slotcount / scale) - 1 : dimx - 1);
ncplane_set_attr(ncp, legendstyle);
if(labelaxisd){
// show the *top* of each interval range
ncplane_set_attr(ncp, legendstyle);
for(int y = 0 ; y < dimy ; ++y){
uint64_t channels = 0;
calc_gradient_channels(&channels, maxchannel, maxchannel,
minchannel, minchannel, y, 0, dimy, dimx);
calc_gradient_channels(&channels, minchannels, minchannels,
maxchannels, maxchannels, y, 0, dimy, dimx);
ncplane_set_channels(ncp, channels);
char buf[PREFIXSTRLEN + 1];
if(exponentiali){
@ -145,10 +149,20 @@ class ncppplot {
}else{
ncmetric((maxy - interval * states * (dimy - y - 1)) * 100, 100, buf, 0, 1000, '\0');
}
ncplane_printf_yx(ncp, dimy - y - 1, PREFIXCOLUMNS - strlen(buf), "%s", buf);
if(y == dimy - 1 && !title.empty()){
ncplane_printf_yx(ncp, dimy - y - 1, PREFIXCOLUMNS - strlen(buf), "%s %s", buf, title.c_str());
}else{
ncplane_printf_yx(ncp, dimy - y - 1, PREFIXCOLUMNS - strlen(buf), "%s", buf);
}
}
ncplane_set_attr(ncp, NCSTYLE_NONE);
}else if(!title.empty()){
uint64_t channels = 0;
calc_gradient_channels(&channels, minchannels, minchannels,
maxchannels, maxchannels, dimy - 1, 0, dimy, dimx);
ncplane_set_channels(ncp, channels);
ncplane_printf_yx(ncp, 0, PREFIXCOLUMNS - title.length(), "%s", title.c_str());
}
ncplane_set_attr(ncp, NCSTYLE_NONE);
if(finalx < startx){ // exit on pathologically narrow planes
return 0;
}
@ -182,8 +196,8 @@ class ncppplot {
const wchar_t* egc = bset->egcs;
for(int y = 0 ; y < dimy ; ++y){
uint64_t channels = 0;
calc_gradient_channels(&channels, maxchannel, maxchannel,
minchannel, minchannel, y, x, dimy, dimx);
calc_gradient_channels(&channels, minchannels, minchannels,
maxchannels, maxchannels, y, x, dimy, dimx);
ncplane_set_channels(ncp, channels);
size_t egcidx = 0, sumidx = 0;
// if we've got at least one interval's worth on the number of positions
@ -363,11 +377,12 @@ class ncppplot {
private:
uint64_t maxchannel;
uint64_t minchannel;
uint64_t maxchannels;
uint64_t minchannels;
uint16_t legendstyle;
bool vertical_indep; // not yet implemented FIXME
const struct blitset* bset;
std::string title;
// requested number of slots. 0 for automatically setting the number of slots
// to span the horizontal area. if there are more slots than there are
// columns, we prefer showing more recent slots to less recent. if there are

View File

@ -4,21 +4,23 @@
using namespace ncpp;
ncplot_options PlotD::default_options = {
0, // maxchannel
0, // minchannel
0, // legendstyle
0, // maxchannels
0, // minchannels
0, // legendstyle
ncblitter_e::NCBLIT_1x1, // ncblitter_e
0, // rangex
0, // flags
0, // rangex
0, // flags
"", // title
};
ncplot_options PlotU::default_options = {
0, // maxchannel
0, // minchannel
0, // legendstyle
0, // maxchannels
0, // minchannels
0, // legendstyle
ncblitter_e::NCBLIT_1x1, // ncblitter_e
0, // rangex
0, // flags
0, // rangex
0, // flags
"", // title
};
Plane* PlotD::get_plane () const noexcept

View File

@ -1,5 +1,7 @@
#!/bin/sh
set -e
# Extract a list of the public API, both shared object functions and those
# static inline functions in the public headers.