1 #include "demo.h"
2 
3 // we have a set of cyclic glyphs, with each cycle composed of some number N_c
4 // of glyphs. our string is made up of each cycle, with each occupying N_c
5 // cells, each iterating through the N_c states in a round. the string emerges
6 // from the center of the screen, moving in a spiral.
7 //
8 // so, each iteration, start at the head of the chain, and move one forward.
9 // work back along the path, moving back through the string. if we reach the
10 // end of the string, clear the cell behind it. eventually, we'll clear the
11 // entirety of the string, and we're done.
12 //
13 // path: the geometry of the spiral is dictated by distance from the center.
14 //
15 static const char* cycles[] = {
16   "������������",   // 6 five-point asterisks
17   "������������",   // 6 six-point asterisks
18   "����������",    // 5 eight-point asterisks
19 #ifndef __APPLE__ // FIXME
20   "◧◩⬒⬔◨◪⬓⬕", // 8 half-black squares
21   "◐◓◑◒",     // 4 half-black circles
22   "◢◣◤◥",     // 4 black triangles
23   "◰◳◲◱",     // 4 white squares with quadrants
24   "◴◷◶◵",     // 4 white circles with quadrants
25   "������������",   // 6 white circles
26   "������������",   // 6 white squares
27   "▤▥▦▧▨▩",   // 6 squares with fill
28   "⯁⯂⯃⯄",     // 4 regular black polyhedra
29   "⌌⌍⌎⌏",     // 4 crops
30 #endif
31   NULL,
32 };
33 
34 typedef enum {
35   PHASE_SPIRAL,
36   PHASE_DONE,
37 } phase_e;
38 
39 // get the new head position, given the old head position
40 static void
get_next_head(struct ncplane * std,struct ncplane * left,struct ncplane * right,int * heady,int * headx,phase_e * phase)41 get_next_head(struct ncplane* std, struct ncplane* left, struct ncplane* right,
42               int* heady, int* headx, phase_e* phase){
43   if(*heady == -1 && *headx == -1){
44     *headx = ncplane_dim_x(std) / 2;
45     *heady = ncplane_dim_y(std) / 2;
46     *phase = PHASE_SPIRAL;
47     return;
48   }
49   if(*phase == PHASE_DONE){
50     return;
51   }
52   int lrcol; // left column of right progbar
53   int rlcol; // right column of left progbar
54   int trow, brow; // top and bottom
55   ncplane_abs_yx(right, &trow, &lrcol);
56   ncplane_abs_yx(left, &brow, &rlcol);
57   rlcol += ncplane_dim_x(left) - 1;
58   brow += ncplane_dim_y(left) - 1;
59   // in the spiral cycle. it's a counterclockwise spiral, come out the bottom,
60   // calculate distances from the center in both directions. if the absolute
61   // values are equal, turn counterclockwise, *unless* xdist is positive and
62   // ydist is negative. in that case, we're coming down the left side, and
63   // need go down one further, only then turning right. that case is xdist is
64   // positive, ydist is negative, and xdist + ydist == -1. otherwise, continue
65   // moving counterclockwise (right if |ydist|>|xdist| and negative y, left
66   // if |ydist|>|xdist| and positive y, etc.)
67   int ydist = ncplane_dim_y(std) / 2 - *heady;
68   int xdist = ncplane_dim_x(std) / 2 - *headx;
69   if(*heady < trow && xdist < 0){
70     *phase = PHASE_DONE;
71   }
72   if(ydist == 0 && xdist == 0){
73     ++*heady; // move down
74   }else if(abs(ydist) == abs(xdist)){ // corner
75     if(ydist < 0 && xdist > 0){ // lower-left, move down
76       ++*heady; // move down
77     }else if(ydist < 0 && xdist < 0){ // lower-right, move up
78       --*heady;
79     }else if(xdist > 0){ // upper-left, move down
80       ++*heady;
81     }else{ // upper-right, love left
82       --*headx;
83     }
84   }else if(ydist < 0 && xdist > 0 && ydist + xdist == -1){ // new iteration
85     ++*headx;
86   }else{
87     if(abs(ydist) > abs(xdist)){
88       if(ydist < 0){
89         ++*headx;
90       }else{
91         --*headx;
92       }
93     }else{
94       if(xdist < 0){
95         if(--*heady < trow){
96           *phase = PHASE_DONE;
97         }
98       }else{
99         ++*heady;
100       }
101     }
102   }
103 }
104 
105 static void
get_next_end(struct ncplane * std,struct ncplane * left,struct ncplane * right,int * endy,int * endx,phase_e * endphase)106 get_next_end(struct ncplane* std, struct ncplane* left, struct ncplane* right,
107              int* endy, int* endx, phase_e* endphase){
108   get_next_head(std, left, right, endy, endx, endphase);
109 }
110 
111 // determine the total number of moves we will make. this is most easily and
112 // accurately done by running through a loop.
113 static int
determine_totalmoves(struct ncplane * std,struct ncplane * left,struct ncplane * right,int heady,int headx,int endy,int endx,int totallength)114 determine_totalmoves(struct ncplane* std, struct ncplane* left, struct ncplane* right,
115                      int heady, int headx, int endy, int endx, int totallength){
116   int moves = 0, length = 0;
117   phase_e headphase, endphase;
118   do{
119     get_next_head(std, left, right, &heady, &headx, &headphase);
120     if(length < totallength){
121       ++length;
122     }else{
123       get_next_end(std, left, right, &endy, &endx, &endphase);
124     }
125     ++moves;
126   }while(endy != heady || endx != headx);
127   return moves;
128 }
129 
130 // find the 'iters'th EGC in 'utf8', modulo the number of EGCs in 'utf8'
131 static int
spin_cycle(const char * utf8,int iters)132 spin_cycle(const char* utf8, int iters){
133   int offsets[10]; // no cycles longer than this
134   mbstate_t mbs = { };
135   int offset = 0;
136   size_t s;
137   int o = 0;
138   while((s = mbrtowc(NULL, utf8 + offset, strlen(utf8 + offset) + 1, &mbs)) != (size_t)-1){
139     if(s == 0){ // ended with o EGCs
140       if(o == 0){
141         return -1;
142       }
143       return offsets[iters % o];
144     }
145     if(o == sizeof(offsets) / sizeof(*offsets)){
146       return offsets[iters % o]; // FIXME?
147     }
148     offsets[o] = offset;
149     offset += s;
150     if(o++ == iters){
151       return offsets[iters % o];
152     }
153   }
154   return -1;
155 }
156 
157 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)158 drawcycles(struct ncplane* std, struct ncprogbar* left, struct ncprogbar* right,
159            int length, int endy, int endx, phase_e endphase, uint64_t* channels,
160            int iters){
161   const char** c = cycles;
162   const char* cstr = *c;
163   int offset = spin_cycle(cstr, iters);
164   if(offset < 0){
165     return -1;
166   }
167   while(length--){
168     get_next_head(std, ncprogbar_plane(left), ncprogbar_plane(right),
169                   &endy, &endx, &endphase);
170     free(ncplane_at_yx(std, endy, endx, NULL, channels));
171     ncplane_set_bg_rgb(std, ncchannels_bg_rgb(*channels));
172     ncplane_set_fg_rgb(std, 0xffffff);
173     size_t sbytes;
174     if(ncplane_putegc_yx(std, endy, endx, cstr + offset, &sbytes) < 0){
175       return -1;
176     }
177     offset += sbytes;
178     if(cstr[offset] == '\0'){
179       cstr = *++c;
180       if(cstr == NULL){
181         c = cycles;
182         cstr = *c;
183       }
184       if((offset = spin_cycle(cstr, iters)) < 0){
185         return -1;
186       }
187     }
188   }
189   return 0;
190 }
191 
192 static int
animate(struct notcurses * nc,struct ncprogbar * left,struct ncprogbar * right,uint64_t expect_ns)193 animate(struct notcurses* nc, struct ncprogbar* left, struct ncprogbar* right,
194         uint64_t expect_ns){
195   unsigned dimy, dimx;
196   struct ncplane* std = notcurses_stddim_yx(nc, &dimy, &dimx);
197   int headx = -1;
198   int heady = -1;
199   int endy = -1;
200   int endx = -1;
201   int totallength = 0;
202   for(const char** c = cycles ; *c ; ++c){
203     totallength += ncstrwidth(*c, NULL, NULL);
204   }
205   int totalmoves = determine_totalmoves(std, ncprogbar_plane(left), ncprogbar_plane(right),
206                                         heady, headx, endy, endx, totallength);
207   ++totalmoves; // for final render
208   // headx and heady will not return to their starting location until the
209   // string begins to disappear. endx and endy won't equal heady/headx until
210   // the entire string has been consumed.
211   struct timespec delay;
212   uint64_t iterns = (timespec_to_ns(&demodelay) * 5) / totalmoves;
213   phase_e headphase = PHASE_SPIRAL;
214   phase_e endphase = PHASE_SPIRAL;
215   int moves = 0;
216   uint64_t channels = 0;
217   int length = 1;
218   do{
219     get_next_head(std, ncprogbar_plane(left), ncprogbar_plane(right),
220                   &heady, &headx, &headphase);
221     if(headphase != PHASE_DONE){
222       if(drawcycles(std, left, right, length, endy, endx, endphase, &channels, moves) < 0){
223         return -1;
224       }
225     }
226     if(length < totallength){
227       ++length;
228     }else{
229       get_next_end(std, ncprogbar_plane(left), ncprogbar_plane(right),
230                   &endy, &endx, &endphase);
231       ncplane_set_fg_rgb(std, ncchannels_fg_rgb(channels));
232       ncplane_putwc_yx(std, endy, endx, L'▄');
233     }
234     ++moves;
235     ncprogbar_set_progress(left, ((float)moves) / totalmoves);
236     ncprogbar_set_progress(right, ((float)moves) / totalmoves);
237     DEMO_RENDER(nc);
238     struct timespec now;
239     expect_ns += iterns;
240     clock_gettime(CLOCK_MONOTONIC, &now);
241     uint64_t nowns = timespec_to_ns(&now);
242     if(nowns < expect_ns){
243       ns_to_timespec(expect_ns - nowns, &delay);
244       demo_nanosleep(nc, &delay);
245     }
246   }while(endy != heady || endx != headx);
247   ncprogbar_set_progress(left, 1);
248   ncprogbar_set_progress(right, 1);
249   DEMO_RENDER(nc);
250   return 0;
251 }
252 
253 static int
make_pbars(struct ncplane * column,struct ncprogbar ** left,struct ncprogbar ** right)254 make_pbars(struct ncplane* column, struct ncprogbar** left, struct ncprogbar** right){
255   unsigned dimy, dimx, coly, colx;
256   struct notcurses* nc = ncplane_notcurses(column);
257   notcurses_stddim_yx(nc, &dimy, &dimx);
258   ncplane_dim_yx(column, &coly, &colx);
259   int colposy, colposx;
260   ncplane_yx(column, &colposy, &colposx);
261   ncplane_options opts = {
262     .x = colposx / 4 * -3,
263     .rows = coly,
264     .cols = (dimx - colx) / 4,
265   };
266   struct ncplane* leftp = ncplane_create(column, &opts);
267   if(leftp == NULL){
268     return -1;
269   }
270   ncplane_set_base(leftp, " ", 0, NCCHANNELS_INITIALIZER(0xdd, 0xdd, 0xdd, 0x1b, 0x1b, 0x1b));
271   ncprogbar_options popts = { };
272   ncchannel_set_rgb8(&popts.brchannel, 0, 0, 0);
273   ncchannel_set_rgb8(&popts.blchannel, 0, 0xff, 0);
274   ncchannel_set_rgb8(&popts.urchannel, 0, 0, 0xff);
275   ncchannel_set_rgb8(&popts.ulchannel, 0, 0xff, 0xff);
276   *left = ncprogbar_create(leftp, &popts);
277   if(*left == NULL){
278     return -1;
279   }
280   opts.x = colx + colposx / 4;
281   struct ncplane* rightp = ncplane_create(column, &opts);
282   if(rightp == NULL){
283     return -1;
284   }
285   ncplane_set_base(rightp, " ", 0, NCCHANNELS_INITIALIZER(0xdd, 0xdd, 0xdd, 0x1b, 0x1b, 0x1b));
286   popts.flags = NCPROGBAR_OPTION_RETROGRADE;
287   *right = ncprogbar_create(rightp, &popts);
288   if(*right == NULL){
289     ncprogbar_destroy(*left);
290     return -1;
291   }
292   return 0;
293 }
294 
animate_demo(struct notcurses * nc,uint64_t startns)295 int animate_demo(struct notcurses* nc, uint64_t startns){
296   if(!notcurses_canutf8(nc)){
297     return 0;
298   }
299   unsigned dimy, dimx;
300   struct ncplane* n = notcurses_stddim_yx(nc, &dimy, &dimx);
301   ncplane_erase(n);
302   ncplane_home(n);
303   uint32_t tl = 0, tr = 0, bl = 0, br = 0;
304   ncchannel_set_rgb8(&tl, 0, 0, 0);
305   ncchannel_set_rgb8(&tr, 0, 0xff, 0);
306   ncchannel_set_rgb8(&bl, 0, 0, 0xff);
307   ncchannel_set_rgb8(&br, 0, 0xff, 0xff);
308   if(ncplane_gradient2x1(n, -1, -1, 0, 0, tl, tr, bl, br) < 0){
309     return -1;
310   }
311   ncplane_set_fg_rgb(n, 0xf0f0a0);
312   ncplane_set_bg_rgb(n, 0);
313   unsigned width = 40;
314   if(width > dimx - 8){
315     if((width = dimx - 8) <= 0){
316       return -1;
317     }
318   }
319   unsigned height = 40;
320   if(height >= dimy - 4){
321     if((height = dimy - 5) <= 0){
322       return -1;
323     }
324   }
325   const int planey = (dimy - height) / 2 + 1;
326   ncplane_options nopts = {
327     .y = planey,
328     .x = NCALIGN_CENTER,
329     .rows = height,
330     .cols = width,
331     .flags = NCPLANE_OPTION_HORALIGNED,
332   };
333   struct ncplane* column = ncplane_create(n, &nopts);
334   if(column == NULL){
335     return -1;
336   }
337   struct ncprogbar *pbarleft, *pbarright;
338   if(make_pbars(column, &pbarleft, &pbarright)){
339     ncplane_destroy(column);
340     return -1;
341   }
342   ncplane_destroy(column);
343   int r = animate(nc, pbarleft, pbarright, startns);
344   ncprogbar_destroy(pbarleft);
345   ncprogbar_destroy(pbarright);
346   return r;
347 }
348