mirror of
https://github.com/dankamongmen/notcurses.git
synced 2024-11-20 03:25:47 +00:00
347 lines
11 KiB
C
347 lines
11 KiB
C
#include "demo.h"
|
|
|
|
// we have a set of cyclic glyphs, with each cycle composed of some number N_c
|
|
// of glyphs. our string is made up of each cycle, with each occupying N_c
|
|
// cells, each iterating through the N_c states in a round. the string emerges
|
|
// from the center of the screen, moving in a spiral.
|
|
//
|
|
// so, each iteration, start at the head of the chain, and move one forward.
|
|
// work back along the path, moving back through the string. if we reach the
|
|
// end of the string, clear the cell behind it. eventually, we'll clear the
|
|
// entirety of the string, and we're done.
|
|
//
|
|
// path: the geometry of the spiral is dictated by distance from the center.
|
|
//
|
|
static const char* cycles[] = {
|
|
"🞯🞰🞱🞲🞳🞴", // 6 five-point asterisks
|
|
"🞵🞶🞷🞸🞹🞺", // 6 six-point asterisks
|
|
"🞻🞼🞽🞾🞿", // 5 eight-point asterisks
|
|
"◧◩⬒⬔◨◪⬓⬕", // 8 half-black squares
|
|
"◐◓◑◒", // 4 half-black circles
|
|
"◢◣◤◥", // 4 black triangles
|
|
"◰◳◲◱", // 4 white squares with quadrants
|
|
"◴◷◶◵", // 4 white circles with quadrants
|
|
"🞅🞆🞇🞈🞉🞊", // 6 white circles
|
|
"🞎🞏🞐🞑🞒🞓", // 6 white squares
|
|
"▤▥▦▧▨▩", // 6 squares with fill
|
|
"⯁⯂⯃⯄", // 4 regular black polyhedra
|
|
"⌌⌍⌎⌏", // 4 crops
|
|
NULL,
|
|
};
|
|
|
|
typedef enum {
|
|
PHASE_SPIRAL,
|
|
PHASE_DONE,
|
|
} phase_e;
|
|
|
|
// get the new head position, given the old head position
|
|
static void
|
|
get_next_head(struct ncplane* std, struct ncplane* left, struct ncplane* right,
|
|
int* heady, int* headx, phase_e* phase){
|
|
if(*heady == -1 && *headx == -1){
|
|
*headx = ncplane_dim_x(std) / 2;
|
|
*heady = ncplane_dim_y(std) / 2;
|
|
*phase = PHASE_SPIRAL;
|
|
return;
|
|
}
|
|
if(*phase == PHASE_DONE){
|
|
return;
|
|
}
|
|
int lrcol; // left column of right progbar
|
|
int rlcol; // right column of left progbar
|
|
int trow, brow; // top and bottom
|
|
ncplane_abs_yx(right, &trow, &lrcol);
|
|
ncplane_abs_yx(left, &brow, &rlcol);
|
|
rlcol += ncplane_dim_x(left) - 1;
|
|
brow += ncplane_dim_y(left) - 1;
|
|
// in the spiral cycle. it's a counterclockwise spiral, come out the bottom,
|
|
// calculate distances from the center in both directions. if the absolute
|
|
// values are equal, turn counterclockwise, *unless* xdist is positive and
|
|
// ydist is negative. in that case, we're coming down the left side, and
|
|
// need go down one further, only then turning right. that case is xdist is
|
|
// positive, ydist is negative, and xdist + ydist == -1. otherwise, continue
|
|
// moving counterclockwise (right if |ydist|>|xdist| and negative y, left
|
|
// if |ydist|>|xdist| and positive y, etc.)
|
|
int ydist = ncplane_dim_y(std) / 2 - *heady;
|
|
int xdist = ncplane_dim_x(std) / 2 - *headx;
|
|
if(*heady < trow && xdist < 0){
|
|
*phase = PHASE_DONE;
|
|
}
|
|
if(ydist == 0 && xdist == 0){
|
|
++*heady; // move down
|
|
}else if(abs(ydist) == abs(xdist)){ // corner
|
|
if(ydist < 0 && xdist > 0){ // lower-left, move down
|
|
++*heady; // move down
|
|
}else if(ydist < 0 && xdist < 0){ // lower-right, move up
|
|
--*heady;
|
|
}else if(xdist > 0){ // upper-left, move down
|
|
++*heady;
|
|
}else{ // upper-right, love left
|
|
--*headx;
|
|
}
|
|
}else if(ydist < 0 && xdist > 0 && ydist + xdist == -1){ // new iteration
|
|
++*headx;
|
|
}else{
|
|
if(abs(ydist) > abs(xdist)){
|
|
if(ydist < 0){
|
|
++*headx;
|
|
}else{
|
|
--*headx;
|
|
}
|
|
}else{
|
|
if(xdist < 0){
|
|
if(--*heady < trow){
|
|
*phase = PHASE_DONE;
|
|
}
|
|
}else{
|
|
++*heady;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
get_next_end(struct ncplane* std, struct ncplane* left, struct ncplane* right,
|
|
int* endy, int* endx, phase_e* endphase){
|
|
get_next_head(std, left, right, endy, endx, endphase);
|
|
}
|
|
|
|
// determine the total number of moves we will make. this is most easily and
|
|
// accurately done by running through a loop.
|
|
static int
|
|
determine_totalmoves(struct ncplane* std, struct ncplane* left, struct ncplane* right,
|
|
int heady, int headx, int endy, int endx, int totallength){
|
|
int moves = 0, length = 0;
|
|
phase_e headphase, endphase;
|
|
do{
|
|
get_next_head(std, left, right, &heady, &headx, &headphase);
|
|
if(length < totallength){
|
|
++length;
|
|
}else{
|
|
get_next_end(std, left, right, &endy, &endx, &endphase);
|
|
}
|
|
++moves;
|
|
}while(endy != heady || endx != headx);
|
|
return moves;
|
|
}
|
|
|
|
// find the 'iters'th EGC in 'utf8', modulo the number of EGCs in 'utf8'
|
|
static int
|
|
spin_cycle(const char* utf8, int iters){
|
|
int offsets[10]; // no cycles longer than this
|
|
mbstate_t mbs = { };
|
|
int offset = 0;
|
|
size_t s;
|
|
int o = 0;
|
|
while((s = mbrtowc(NULL, utf8 + offset, strlen(utf8 + offset) + 1, &mbs)) != (size_t)-1){
|
|
if(s == 0){ // ended with o EGCs
|
|
if(o == 0){
|
|
return -1;
|
|
}
|
|
return offsets[iters % o];
|
|
}
|
|
if(o == sizeof(offsets) / sizeof(*offsets)){
|
|
break;
|
|
}
|
|
offsets[o] = offset;
|
|
offset += s;
|
|
if(++o == iters){
|
|
return offsets[iters % o];
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static int
|
|
drawcycles(struct ncplane* std, struct ncprogbar* left, struct ncprogbar* right,
|
|
int length, int endy, int endx, phase_e endphase, uint64_t* channels,
|
|
int iters){
|
|
const char** c = cycles;
|
|
const char* cstr = *c;
|
|
int offset = spin_cycle(cstr, iters);
|
|
if(offset < 0){
|
|
return -1;
|
|
}
|
|
while(length--){
|
|
get_next_head(std, ncprogbar_plane(left), ncprogbar_plane(right),
|
|
&endy, &endx, &endphase);
|
|
free(ncplane_at_yx(std, endy, endx, NULL, channels));
|
|
ncplane_set_bg_rgb(std, ncchannels_bg_rgb(*channels));
|
|
ncplane_set_fg_rgb(std, 0xffffff);
|
|
int sbytes;
|
|
if(ncplane_putegc_yx(std, endy, endx, cstr + offset, &sbytes) < 0){
|
|
return -1;
|
|
}
|
|
offset += sbytes;
|
|
if(cstr[offset] == '\0'){
|
|
cstr = *++c;
|
|
if(cstr == NULL){
|
|
c = cycles;
|
|
cstr = *c;
|
|
}
|
|
if((offset = spin_cycle(cstr, iters)) < 0){
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
animate(struct notcurses* nc, struct ncprogbar* left, struct ncprogbar* right){
|
|
int dimy, dimx;
|
|
struct ncplane* std = notcurses_stddim_yx(nc, &dimy, &dimx);
|
|
int headx = -1;
|
|
int heady = -1;
|
|
int endy = -1;
|
|
int endx = -1;
|
|
int totallength = 0;
|
|
for(const char** c = cycles ; *c ; ++c){
|
|
totallength += ncstrwidth(*c);
|
|
}
|
|
int totalmoves = determine_totalmoves(std, ncprogbar_plane(left), ncprogbar_plane(right),
|
|
heady, headx, endy, endx, totallength);
|
|
++totalmoves; // for final render
|
|
// headx and heady will not return to their starting location until the
|
|
// string begins to disappear. endx and endy won't equal heady/headx until
|
|
// the entire string has been consumed.
|
|
struct timespec delay;
|
|
uint64_t iterns = (timespec_to_ns(&demodelay) * 5) / totalmoves;
|
|
phase_e headphase = PHASE_SPIRAL;
|
|
phase_e endphase = PHASE_SPIRAL;
|
|
int moves = 0;
|
|
struct timespec expected;
|
|
clock_gettime(CLOCK_MONOTONIC, &expected);
|
|
uint64_t expect_ns = timespec_to_ns(&expected);
|
|
uint64_t channels = 0;
|
|
int length = 1;
|
|
do{
|
|
get_next_head(std, ncprogbar_plane(left), ncprogbar_plane(right),
|
|
&heady, &headx, &headphase);
|
|
if(headphase != PHASE_DONE){
|
|
if(drawcycles(std, left, right, length, endy, endx, endphase, &channels, moves) < 0){
|
|
return -1;
|
|
}
|
|
}
|
|
if(length < totallength){
|
|
++length;
|
|
}else{
|
|
get_next_end(std, ncprogbar_plane(left), ncprogbar_plane(right),
|
|
&endy, &endx, &endphase);
|
|
ncplane_set_fg_rgb(std, ncchannels_fg_rgb(channels));
|
|
ncplane_putwc_yx(std, endy, endx, L'▄');
|
|
}
|
|
++moves;
|
|
ncprogbar_set_progress(left, ((float)moves) / totalmoves);
|
|
ncprogbar_set_progress(right, ((float)moves) / totalmoves);
|
|
DEMO_RENDER(nc);
|
|
struct timespec now;
|
|
expect_ns += iterns;
|
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
uint64_t nowns = timespec_to_ns(&now);
|
|
if(nowns < expect_ns){
|
|
ns_to_timespec(expect_ns - nowns, &delay);
|
|
demo_nanosleep(nc, &delay);
|
|
}
|
|
}while(endy != heady || endx != headx);
|
|
ncprogbar_set_progress(left, 1);
|
|
ncprogbar_set_progress(right, 1);
|
|
DEMO_RENDER(nc);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
make_pbars(struct ncplane* column, struct ncprogbar** left, struct ncprogbar** right){
|
|
int dimy, dimx, coly, colx, colposy, colposx;
|
|
struct notcurses* nc = ncplane_notcurses(column);
|
|
notcurses_stddim_yx(nc, &dimy, &dimx);
|
|
ncplane_dim_yx(column, &coly, &colx);
|
|
ncplane_yx(column, &colposy, &colposx);
|
|
ncplane_options opts = {
|
|
.x = colposx / 4 * -3,
|
|
.rows = coly,
|
|
.cols = (dimx - colx) / 4,
|
|
};
|
|
struct ncplane* leftp = ncplane_create(column, &opts);
|
|
if(leftp == NULL){
|
|
return -1;
|
|
}
|
|
ncplane_set_base(leftp, " ", 0, NCCHANNELS_INITIALIZER(0xdd, 0xdd, 0xdd, 0x1b, 0x1b, 0x1b));
|
|
ncprogbar_options popts = { };
|
|
ncchannel_set_rgb8(&popts.brchannel, 0, 0, 0);
|
|
ncchannel_set_rgb8(&popts.blchannel, 0, 0xff, 0);
|
|
ncchannel_set_rgb8(&popts.urchannel, 0, 0, 0xff);
|
|
ncchannel_set_rgb8(&popts.ulchannel, 0, 0xff, 0xff);
|
|
*left = ncprogbar_create(leftp, &popts);
|
|
if(*left == NULL){
|
|
return -1;
|
|
}
|
|
opts.x = colx + colposx / 4;
|
|
struct ncplane* rightp = ncplane_create(column, &opts);
|
|
if(rightp == NULL){
|
|
return -1;
|
|
}
|
|
ncplane_set_base(rightp, " ", 0, NCCHANNELS_INITIALIZER(0xdd, 0xdd, 0xdd, 0x1b, 0x1b, 0x1b));
|
|
popts.flags = NCPROGBAR_OPTION_RETROGRADE;
|
|
*right = ncprogbar_create(rightp, &popts);
|
|
if(*right == NULL){
|
|
ncprogbar_destroy(*left);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int animate_demo(struct notcurses* nc){
|
|
if(!notcurses_canutf8(nc)){
|
|
return 0;
|
|
}
|
|
int dimy, dimx;
|
|
struct ncplane* n = notcurses_stddim_yx(nc, &dimy, &dimx);
|
|
ncplane_erase(n);
|
|
ncplane_home(n);
|
|
uint32_t tl = 0, tr = 0, bl = 0, br = 0;
|
|
ncchannel_set_rgb8(&tl, 0, 0, 0);
|
|
ncchannel_set_rgb8(&tr, 0, 0xff, 0);
|
|
ncchannel_set_rgb8(&bl, 0, 0, 0xff);
|
|
ncchannel_set_rgb8(&br, 0, 0xff, 0xff);
|
|
if(ncplane_highgradient(n, tl, tr, bl, br, dimy - 1, dimx - 1) < 0){
|
|
return -1;
|
|
}
|
|
ncplane_set_fg_rgb(n, 0xf0f0a0);
|
|
ncplane_set_bg_rgb(n, 0);
|
|
int width = 40;
|
|
if(width > dimx - 8){
|
|
if((width = dimx - 8) <= 0){
|
|
return -1;
|
|
}
|
|
}
|
|
int height = 40;
|
|
if(height >= dimy - 4){
|
|
if((height = dimy - 5) <= 0){
|
|
return -1;
|
|
}
|
|
}
|
|
const int planey = (dimy - height) / 2 + 1;
|
|
ncplane_options nopts = {
|
|
.y = planey,
|
|
.x = NCALIGN_CENTER,
|
|
.rows = height,
|
|
.cols = width,
|
|
.flags = NCPLANE_OPTION_HORALIGNED,
|
|
};
|
|
struct ncplane* column = ncplane_create(n, &nopts);
|
|
if(column == NULL){
|
|
return -1;
|
|
}
|
|
struct ncprogbar *pbarleft, *pbarright;
|
|
if(make_pbars(column, &pbarleft, &pbarright)){
|
|
ncplane_destroy(column);
|
|
return -1;
|
|
}
|
|
ncplane_destroy(column);
|
|
int r = animate(nc, pbarleft, pbarright);
|
|
ncprogbar_destroy(pbarleft);
|
|
ncprogbar_destroy(pbarright);
|
|
return r;
|
|
}
|