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