You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
notcurses/src/demo/zoo.c

429 lines
14 KiB
C

#include "demo.h"
// open up changes.jpg, stretch it to fill, drop it to greyscale
static int
draw_background(struct notcurses* nc){
if(notcurses_canopen_images(nc)){
struct ncplane* n = notcurses_stdplane(nc);
char* path = find_data("changes.jpg");
struct ncvisual* ncv = ncvisual_from_file(path);
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;
}
// we list all distributions on which notcurses is known to exist
static struct ncselector_item select_items[] = {
#define SITEM(short, long) { short, long, 0, 0, }
SITEM("fbsd", "FreeBSD"),
SITEM("deb", "Debian Unstable Linux"),
SITEM("rpm", "Fedora Rawhide Linux"),
SITEM("pac", "Arch Linux"),
SITEM("apk", "Alpine Edge Linux"),
SITEM(NULL, NULL),
#undef SITEM
};
static struct ncmselector_item mselect_items[] = {
{ "Pa231", "Protactinium-231 (162kg)", .selected = false, },
{ "U233", "Uranium-233 (15kg)", .selected = false, },
{ "U235", "Uranium-235 (50kg)", .selected = false, },
{ "Np236", "Neptunium-236 (7kg)", .selected = false, },
{ "Np237", "Neptunium-237 (60kg)", .selected = false, },
{ "Pu238", "Plutonium-238 (10kg)", .selected = false, },
{ "Pu239", "Plutonium-239 (10kg)", .selected = false, },
{ "Pu240", "Plutonium-240 (40kg)", .selected = false, },
{ "Pu241", "Plutonium-241 (13kg)", .selected = false, },
{ "Am241", "Americium-241 (100kg)", .selected = false, },
{ "Pu242", "Plutonium-242 (100kg)", .selected = false, },
{ "Am242", "Americium-242 (18kg)", .selected = false, },
{ "Am243", "Americium-243 (155kg)", .selected = false, },
{ "Cm243", "Curium-243 (10kg)", .selected = false, },
{ "Cm244", "Curium-244 (30kg)", .selected = false, },
{ "Cm245", "Curium-245 (13kg)", .selected = false, },
{ "Cm246", "Curium-246 (84kg)", .selected = false, },
{ "Cm247", "Curium-247 (7kg)", .selected = false, },
{ "Bk247", "Berkelium-247 (10kg)", .selected = false, },
{ "Cf249", "Californium-249 (6kg)", .selected = false, },
{ "Cf251", "Californium-251 (9kg)", .selected = false, },
{ NULL, NULL, .selected = false, },
};
static struct ncmultiselector*
multiselector_demo(struct ncplane* n, struct ncplane* under, int y){
ncmultiselector_options mopts = {
.maxdisplay = 8,
.title = "multi-item selector",
.items = mselect_items,
.boxchannels = CHANNELS_RGB_INITIALIZER(0x20, 0xe0, 0xe0, 0x20, 0, 0),
.opchannels = CHANNELS_RGB_INITIALIZER(0xe0, 0x80, 0x40, 0, 0, 0),
.descchannels = CHANNELS_RGB_INITIALIZER(0x80, 0xe0, 0x40, 0, 0, 0),
.footchannels = CHANNELS_RGB_INITIALIZER(0xe0, 0, 0x40, 0x20, 0x20, 0),
.titlechannels = CHANNELS_RGB_INITIALIZER(0x80, 0x80, 0xff, 0, 0, 0x20),
};
uint64_t bgchannels = CHANNELS_RGB_INITIALIZER(0, 0x40, 0, 0, 0x40, 0);
channels_set_fg_alpha(&bgchannels, CELL_ALPHA_BLEND);
channels_set_bg_alpha(&bgchannels, CELL_ALPHA_BLEND);
struct ncplane* mseln = ncplane_new(n, 1, 1, y, 0, NULL, NULL);
if(mseln == NULL){
return NULL;
}
ncplane_set_base(mseln, "", 0, bgchannels);
struct ncmultiselector* mselect = ncmultiselector_create(mseln, &mopts);
if(mselect == NULL){
return NULL;
}
struct ncplane* mplane = ncmultiselector_plane(mselect);
ncplane_move_below(mplane, under);
return mselect;
}
static struct ncselector*
selector_demo(struct ncplane* n, struct ncplane* under, int dimx, int y){
ncselector_options sopts = {
.title = "single-item selector",
.items = select_items,
.defidx = 4,
.maxdisplay = 3,
.boxchannels = CHANNELS_RGB_INITIALIZER(0xe0, 0x20, 0x40, 0x20, 0x20, 0x20),
.opchannels = CHANNELS_RGB_INITIALIZER(0xe0, 0x80, 0x40, 0, 0, 0),
.descchannels = CHANNELS_RGB_INITIALIZER(0x80, 0xe0, 0x40, 0, 0, 0),
.footchannels = CHANNELS_RGB_INITIALIZER(0xe0, 0, 0x40, 0x20, 0, 0),
.titlechannels = CHANNELS_RGB_INITIALIZER(0xff, 0xff, 0x80, 0, 0, 0x20),
};
uint64_t bgchannels = CHANNELS_RGB_INITIALIZER(0, 0, 0x40, 0, 0, 0x40);
channels_set_fg_alpha(&bgchannels, CELL_ALPHA_BLEND);
channels_set_bg_alpha(&bgchannels, CELL_ALPHA_BLEND);
struct ncplane* seln = ncplane_new(n, 1, 1, y, dimx, NULL, NULL);
if(seln == NULL){
return NULL;
}
ncplane_set_base(seln, "", 0, bgchannels);
struct ncselector* selector = ncselector_create(seln, &sopts);
if(selector == NULL){
return NULL;
}
ncplane_move_below(seln, 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);
uint64_t targ = cur + timespec_to_ns(&demodelay);
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){
return -1;
}else if(wc){
ncmultiselector_offer_input(mselector, &ni);
}
clock_gettime(CLOCK_MONOTONIC, &ts);
cur = timespec_to_ns(&ts);
if( (ret = demo_render(nc)) ){
return ret;
}
}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;
}
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);
int y, x, posy, posx;
struct ncplane* ncp = ncreader_plane(reader);
ncplane_cursor_yx(ncp, &y, &x);
ncplane_yx(ncp, &posy, &posx);
int stdy = ncplane_dim_y(notcurses_stdplane(ncplane_notcurses(ncp)));
if(y + posy < stdy - 1){
// don't check for failure -- not supported under vt100
notcurses_cursor_enable(ncplane_notcurses(ncp), y + posy, x + posx);
}
*textpos += towrite;
}
return 0;
}
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;
}
static int
get_word_count(const char* text){
bool inspace = true;
int words = 0;
while(*text){
if(isspace(*text)){
if(!inspace){
++words;
inspace = true;
}
}else{
inspace = false;
}
++text;
}
return words;
}
// 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"
"This NCReader widget facilitates free-form text entry complete with readline-style bindings.\n"
"NCSelector allows a single option to be selected from a list.\n"
"NCFdplane streams a file descriptor, while NCSubproc spawns a subprocess and streams its output.\n";
int titers = get_word_count(text);
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;
int iters = yiters > xiters ? yiters : xiters;
if(titers > iters){
iters = titers;
}
const double eachy = (double)iters / yiters;
const double eachx = (double)iters / xiters;
const double eacht = (double)iters / titers;
int xi = 1;
int yi = 1;
int ti = 1;
struct timespec iterdelay, start;
timespec_div(&demodelay, iters / 4, &iterdelay);
size_t textpos = 0;
clock_gettime(CLOCK_MONOTONIC, &start);
for(int i = 0 ; i < iters ; ++i){
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;
}
if(i == (int)(ti * eacht)){
if( (ret = layout_next_text(reader, text, &textpos)) ){
return ret;
}
++ti;
}
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.\n"
"A variety of plots are supported. "
"Menus can be placed along the top and/or bottom of any plane. "
"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 int titers = get_word_count(text);
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;
int iters = yiters > xiters ? yiters : xiters;
if(titers > iters){
iters = titers;
}
const double eachy = (double)iters / yiters;
const double eachx = (double)iters / xiters;
const double eacht = (double)iters / titers;
int xi = 1;
int yi = 1;
int ti = 1;
struct timespec iterdelay, start;
timespec_div(&demodelay, iters / 4, &iterdelay);
clock_gettime(CLOCK_MONOTONIC, &start);
size_t textpos = 0;
for(int i = 0 ; i < iters ; ++i){
if(i == (int)(ti * eacht)){
if( (ret = layout_next_text(reader, text, &textpos)) ){
return ret;
}
++ti;
}
if(i == (int)(xi * eachx)){
if(ncplane_move_yx(ncmultiselector_plane(mselector), 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){
ncmultiselector_offer_input(mselector, &ni);
}
clock_gettime(CLOCK_MONOTONIC, &now);
}
}
return run_out_text(reader, text, &textpos, &iterdelay);
}
// 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 = {
.tchannels = CHANNELS_RGB_INITIALIZER(0xa0, 0xe0, 0xe0, 0, 0, 0),
};
uint64_t echannels = CHANNELS_RGB_INITIALIZER(0x20, 0xe0, 0xe0, 0, 0, 0);
channels_set_bg_alpha(&echannels, CELL_ALPHA_BLEND);
const int x = ncplane_align(std, NCALIGN_CENTER, READER_COLS);
struct ncselector* selector = NULL;
struct ncmultiselector* mselector = NULL;
struct ncplane* rp = ncplane_new(std, READER_ROWS, READER_COLS, dimy, x, NULL, "read");
ncplane_set_base(rp, " ", 0, echannels);
struct ncreader* reader = ncreader_create(rp, &nopts);
if(reader == NULL){
goto done;
}
ncplane_set_scrolling(ncreader_plane(reader), true);
// 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;
}
done:
ncselector_destroy(selector, NULL);
ncmultiselector_destroy(mselector);
ncreader_destroy(reader, NULL);
// don't check for failure -- not supported under vt100
notcurses_cursor_disable(nc);
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){
if(draw_background(nc)){
return -1;
}
return reader_demo(nc);
}