Notcurses 3.0.16
a blingful library for TUIs and character graphics
Loading...
Searching...
No Matches
reel.c
Go to the documentation of this file.
1#include <errno.h>
2#include <unistd.h>
3#include <stdlib.h>
4#include <string.h>
5#include "internal.h"
6
7typedef enum {
10} direction_e; // current direction of travel
11
12// A UNIFIED THEORY OF NCREELS
13// (which are more complex than they may seem)
14//
15// We only redraw when ncreel_redraw() is explicitly called (and at creation).
16// Redrawing consists of arranging the tablets, and calling the user's
17// callbacks to fill them in. In general, drawing a tablet involves creating a
18// plane having all available size, calling the callback, and then trimming the
19// plane if it was not filled.
20//
21// Things which can happen between ncreel_redraw() calls:
22//
23// * new focused tablet added (only when no tablets exist)
24// * new unfocused tablet added
25// * tablet removed (may be the focused tablet)
26// * tablets may grow or shrink
27// * focus can change due to new selection
28//
29// Any number and mix of these events can occur between draws.
30//
31// First rule: there must not be 2+ consecutive lines of blank space if there is
32// data which could be presented there (always fill the reel).
33// Second rule: if there must be 2+ consecutive lines of blank space, they must
34// all be at the bottom of the reel (connect and anchor the reel).
35// Third rule: the focused tablet gets all the space it can use.
36// Fourth rule: thou shalt never wrap a tablet [across a border]
37// Fifth rule: the focused tablet should remain where it is across redraws,
38// except as necessary to accommodate the prior rules.
39//
40// At any ncreel_redraw(), you can make three types of moves:
41//
42// - i. moving up and replacing the topmost tablet (spinning operation)
43// - ii. moving down and replacing the bottommost tablet (spinning operation)
44// - iii. don't move / move otherwise (no necessary spin)
45//
46// The first two are simple -- draw the focused tablet next to the appropriate
47// border of the reel, and then draw what we can in the other direction until
48// running out of space (and then shift up if there is more than one line of
49// gap at the top, or if we were moving up from the topmost tablet). This can
50// be done independently of all other tablets; it is immaterial if some were
51// removed, added, etc. We can detect these two cases thus:
52//
53// - store a direction_e, written to by ncreel_next(), ncreel_prev(), and
54// ncreel_del() (when the focused tablet is deleted), defaulting to DOWN.
55// - store a pointer to the "visibly focused" tablet, written and read only by
56// ncreel_redraw(). this identifies the focused tablet upon last redraw. if
57// the visibly-focused tablet is removed, it instead takes the next tablet,
58// iff that tablet is visible. it otherwise takes the prev tablet, iff that
59// tablet is visible. it otherwise takes NULL.
60// - with these, in ncreel_redraw(), we have:
61// - case i iff the last direction was UP, and either the focused tablet is
62// not visible, or below the visibly-focused tablet, or there is no
63// visibly-focused tablet.
64// - case ii iff the last direction was DOWN, and either the focused tablet
65// is not visible, or above the visibly-focused tablet, or there is no
66// visibly-focused tablet.
67//
68// We otherwise have case iii. The focused tablet must be on-screen (if it was
69// off-screen, we matched one of case i or case ii). We want to draw it as near
70// to its current position as possible, subject to the first four Rules.
71//
72// ncreel_redraw() thus starts by determining the case. This must be done
73// before any changes are made to the arrangement. It then clears the reel.
74// The focused tablet is drawn as close to its desired line as possible. For
75// case i, then draw the tablets below the focused tablet. For case ii, then
76// draw the tablets above the focused tablet (and move them up, if necessary).
77// Both of these cases are then handled.
78//
79// For case iii, see below...
80//
81// On tablet remove:
82// * destroy plane, remove from list
83// * if tablet was focused, change focus, update travel direction
84// * if tablet was visibly focused, change visible focus
85// On tablet add:
86// * add to list (do not create plane)
87// * if no tablet existed, change focus to new tablet
88// On focus change:
89// * change focus, update travel direction
90// On redraw:
91// * if no tablets, we're done (deleted planes are already gone)
92// * resize focused tablet to maximum, preserving nothing
93// * call back for focused tablet redraw
94// * shrink focused tablet if applicable
95// * place focused tablet according to:
96// * the focused tablet should be wholly visible, or if not, use all space
97// * the focused tablet should be as close to its old position as possible.
98// * if focused tablet was not wholly on screen, it goes to the side
99// corresponding to the direction of movement.
100// * if out of space or tablets, we're done
101// FIXME *maybe* just draw up followed by down, rather than direction of travel?
102// * walk the list in the direction of travel, foc->focw
103// * designate tablet against the walk as 'focagainst', might be NULL
104// * if focagainst || focagainst, focw only through edge, otherwise
105// * focw can be as large as all remaining space
106// * if there is space, draw what we can of next tablet
107// * move the focused tablet againt the direction of travel if necessary
108// * prefer the space in the direction of walking
109// * last tablet drawn is 'backstop'
110// * if out of space or tablets, we're done
111// * walk the list in the direction against travel, foc->focw
112// * if focw == backstop, we're done
113// * draw through edge
114
115static int
116draw_borders(ncplane* n, unsigned mask, uint64_t channel, direction_e direction){
117 unsigned lenx, leny;
118 ncplane_dim_yx(n, &leny, &lenx);
119 int maxx = lenx - 1;
120 int maxy = leny - 1;
121 nccell ul, ur, ll, lr, hl, vl;
122 nccell_init(&ul); nccell_init(&ur); nccell_init(&hl);
123 nccell_init(&ll); nccell_init(&lr); nccell_init(&vl);
124 if(nccells_rounded_box(n, 0, channel, &ul, &ur, &ll, &lr, &hl, &vl)){
125 return -1;
126 }
127//fprintf(stderr, "drawing borders %p ->%d/%d, mask: %04x\n", w, maxx, maxy, mask);
128 // lenx is the number of columns we have, but drop 2 due to corners. we thus
129 // want lenx - 2 horizontal lines in a top or bottom border.
130 int y = 0;
131 if(y < maxy || direction == DIRECTION_DOWN || (mask & NCBOXMASK_BOTTOM)){
132 if(!(mask & NCBOXMASK_TOP)){
134 ncplane_putc(n, &ul);
135 ncplane_hline(n, &hl, lenx - 2);
136 ncplane_putc(n, &ur);
137 ++y;
138 }
139 }
140 // draw the vertical sides, assuming they're not masked out. start wherever
141 // we're left following the previous stanza, end based on maxhorizy.
142 const bool candrawbottom = y <= maxy || direction == DIRECTION_UP || (mask & NCBOXMASK_TOP);
143 const int maxhorizy = maxy - (candrawbottom && !(mask & NCBOXMASK_BOTTOM));
144 int ret = 0;
145 while(y <= maxhorizy){
146 if(!(mask & NCBOXMASK_LEFT)){
147 ret |= ncplane_cursor_move_yx(n, y, 0);
148 ncplane_putc(n, &vl);
149 }
150 if(!(mask & NCBOXMASK_RIGHT)){
151 ret |= ncplane_cursor_move_yx(n, y, maxx);
152 ncplane_putc(n, &vl);
153 }
154 ++y;
155 }
156 if(candrawbottom){
157 if(!(mask & NCBOXMASK_BOTTOM)){
158 ret |= ncplane_cursor_move_yx(n, maxy, 0);
159 ncplane_putc(n, &ll);
160 ncplane_hline(n, &hl, lenx - 2);
161 ncplane_putc(n, &lr);
162 }
163 }
166 return ret;
167}
168
169// Draws the border (if one should be drawn) around the ncreel, and enforces
170// any provided restrictions on visible window size.
171static int
172draw_ncreel_borders(const ncreel* nr){
173 return draw_borders(nr->p, nr->ropts.bordermask, nr->ropts.borderchan,
174 DIRECTION_UP); // direction shouldn't matter for reel
175}
176
177// Calculate the starting and ending coordinates available for occupation by
178// the tablet, relative to the ncreel's ncplane. Returns non-zero if the
179// tablet cannot be made visible as specified. If this is the focused tablet
180// (nr->tablets == t), it can take the entire reel -- frontiery is only a
181// suggestion in this case -- so give it the full breadth.
182static int
183tablet_geom(const ncreel* nr, nctablet* t, int* begx, int* begy,
184 int* lenx, int* leny, int frontiertop, int frontierbottom,
185 direction_e direction){
186//fprintf(stderr, "jigsawing %p with %d/%d dir %d\n", t, frontiertop, frontierbottom, direction);
187 *begy = 0;
188 *begx = 0;
189 unsigned uleny, ulenx;
190 ncplane_dim_yx(nr->p, &uleny, &ulenx);
191 *leny = uleny;
192 *lenx = ulenx;
193 if(frontiertop < 0){
194 if(direction == DIRECTION_UP){
195 return -1;
196 }
197 frontiertop = 0;
198 }
199 if(frontierbottom >= *leny){
200 if(direction == DIRECTION_DOWN){
201 return -1;
202 }
203 frontierbottom = *leny - 1;
204 }
205 // account for the ncreel borders on the sides
206 if(!(nr->ropts.bordermask & NCBOXMASK_LEFT)){
207 ++*begx;
208 --*lenx;
209 }
210 if(!(nr->ropts.bordermask & NCBOXMASK_RIGHT)){
211 --*lenx;
212 }
213 if(!(nr->ropts.bordermask & NCBOXMASK_TOP)){
214 ++*begy;
215 --*leny;
216 }
217 if(!(nr->ropts.bordermask & NCBOXMASK_BOTTOM)){
218 --*leny;
219 }
220 // at this point, our coordinates describe the largest possible tablet for
221 // this ncreel. this is the correct solution for the focused tablet. other
222 // tablets can only grow in one of two directions, so tighten them up.
223 if(nr->tablets != t){
224 *leny -= (frontierbottom - (frontiertop + 1));
225 if(direction == DIRECTION_DOWN){
226 *begy = frontierbottom;
227 }else{
228 *begy = frontiertop - *leny;
229 }
230 }
231 if(*leny <= 0 || *lenx <= 0){
232 return -1;
233 }
234 return 0;
235}
236
237// kill the planes associated with |t|
238static void
239nctablet_wipeout(nctablet* t){
240 if(t){
241 if(ncplane_set_widget(t->p, NULL, NULL) == 0){
243 }
244 t->p = NULL;
245 t->cbp = NULL;
246 }
247}
248
249// destroy all existing tablet planes pursuant to redraw
250static void
251clean_reel(ncreel* r){
252 nctablet* vft = r->vft;
253 if(vft){
254 for(nctablet* n = vft->next ; n->p && n != vft ; n = n->next){
255//fprintf(stderr, "CLEANING NEXT: %p (%p)\n", n, n->p);
256 nctablet_wipeout(n);
257 }
258 for(nctablet* n = vft->prev ; n->p && n != vft ; n = n->prev){
259//fprintf(stderr, "CLEANING PREV: %p (%p)\n", n, n->p);
260 nctablet_wipeout(n);
261 }
262//fprintf(stderr, "CLEANING VFT: %p (%p)\n", vft, vft->p);
263 nctablet_wipeout(vft);
264 r->vft = NULL;
265 }
266}
267
268// used as ncplane widget destructor callback, and likewise the heart of
269// ncreel_del(). without this, at context collapse time we'd wipe out all the
270// planes (without wiping out their subsidiaries), and ncreel_destroy() would
271// walk freed memory. as we have no handle on the ncreel, we do not update its
272// metadata (tabletcount), nor redraw, but who cares? we're shutting down.
273static void
274nctablet_delete_internal(struct nctablet* t){
275 t->prev->next = t->next;
276 t->next->prev = t->prev;
277 if(t->p){
278 if(ncplane_set_widget(t->p, NULL, NULL) == 0){
280 }
281 }
282 free(t);
283}
284
285int ncreel_del(ncreel* nr, struct nctablet* t){
286 if(t == NULL){
287 return -1;
288 }
289 if(nr->tablets == t){
290 if((nr->tablets = t->next) == t){
291 nr->tablets = NULL;
292 }
293 // FIXME ought set direction based on actual location of replacement tablet
294 nr->direction = LASTDIRECTION_DOWN;
295 }
296 if(nr->vft == t){
297 clean_reel(nr); // maybe just make nr->tablets the vft?
298 }
299 nctablet_delete_internal(t);
300 --nr->tabletcount;
301 ncreel_redraw(nr);
302 return 0;
303}
304
305// Draw the specified tablet, if possible. DIRECTION_UP means we're laying out
306// bottom-to-top. DIRECTION_DOWN means top-to-bottom. 'frontier{top, bottom}'
307// are the lines to which we'll be fitting the tablet ('frontiertop' to our
308// last row for DIRECTION_UP, and 'frontierbottom' to our first row for
309// DIRECTION_DOWN). Gives the tablet all possible space to work with (i.e.
310// everything beyond the frontiers, or the entire reel for the focused tablet).
311// If the callback uses less space, shrinks the plane to that size.
312static int
313ncreel_draw_tablet(const ncreel* nr, nctablet* t, int frontiertop,
314 int frontierbottom, direction_e direction){
315 if(t->p || t->cbp){
316//fprintf(stderr, "already drew %p: %p %p\n", t, t->p, t->cbp);
317 return -1;
318 }
319 int lenx, leny, begy, begx;
320 if(tablet_geom(nr, t, &begx, &begy, &lenx, &leny, frontiertop, frontierbottom, direction)){
321//fprintf(stderr, "no room: %p base %d/%d len %d/%d dir %d\n", t, begy, begx, leny, lenx, direction);
322 return -1;
323 }
324//fprintf(stderr, "p tplacement: %p base %d/%d len %d/%d frontiery %d %d dir %d\n", t, begy, begx, leny, lenx, frontiertop, frontierbottom, direction);
325 struct ncplane_options nopts = {
326 .y = begy,
327 .x = begx,
328 .rows = leny,
329 .cols = lenx,
330 .name = "tab",
331 };
332 ncplane* fp = ncplane_create(nr->p, &nopts);
333 if((t->p = fp) == NULL){
334//fprintf(stderr, "failure creating border plane %d %d %d %d\n", leny, lenx, begy, begx);
335 return -1;
336 }
337 ncplane_set_widget(t->p, t, (void(*)(void*))nctablet_delete_internal);
338 // we allow the callback to use a bound plane that lives above our border
339 // plane, thus preventing the callback from spilling over the tablet border.
340 int cby = 0, cbx = 0, cbleny = leny, cblenx = lenx;
341 //cbleny -= !(nr->ropts.tabletmask & NCBOXMASK_BOTTOM);
342 // FIXME shouldn't this be instead "drop 1 if either is unmasked"?
343 if(!(nr->ropts.tabletmask & NCBOXMASK_TOP)){
344 --cbleny;
345 ++cby;
346 }
347 cblenx -= !(nr->ropts.tabletmask & NCBOXMASK_RIGHT);
348 if(!(nr->ropts.tabletmask & NCBOXMASK_LEFT)){
349 --cblenx;
350 ++cbx;
351 }
352 if(cbleny - cby + 1 > 0){
353//fprintf(stderr, "cbp placement %dx%d @ %dx%d\n", cbleny, cblenx, cby, cbx);
354 struct ncplane_options dnopts = {
355 .y = cby,
356 .x = cbx,
357 .rows = cbleny,
358 .cols = cblenx,
359 .name = "tdat",
360 };
361 t->cbp = ncplane_create(t->p, &dnopts);
362 if(t->cbp == NULL){
363//fprintf(stderr, "failure creating data plane %d %d %d %d\n", cbleny, cblenx, cby, cbx);
364 ncplane_destroy(t->p);
365 t->p = NULL;
366 return -1;
367 }
368 ncplane_move_above(t->cbp, t->p);
369//fprintf(stderr, "calling! lenx/leny: %d/%d cbx/cby: %d/%d cblenx/cbleny: %d/%d dir: %d\n", lenx, leny, cbx, cby, cblenx, cbleny, direction);
370 int ll = t->cbfxn(t, direction == DIRECTION_DOWN);
371//fprintf(stderr, "RETURNRETURNRETURN %p %d (%d, %d, %d) DIR %d\n", t, ll, cby, cbleny, leny, direction);
372 if(ll > cbleny){
373 logwarn("tablet callback returned %d lines, %d allowed", ll, cbleny);
374 ll = cbleny;
375 }
376 if(ll != cbleny){
377 int diff = cbleny - ll;
378//fprintf(stderr, "resizing data plane %d->%d\n", cbleny, ll);
379 if(ll){ // must be smaller than the space we provided; add back bottom
380 ncplane_resize_simple(t->cbp, ll, cblenx);
381 }else{
383 t->cbp = NULL;
384 }
385 // resize the borderplane iff we got smaller
386 if(!(nr->ropts.tabletmask & NCBOXMASK_BOTTOM)){
387 ncplane_resize_simple(t->p, leny - diff + 1, lenx);
388 }else{
389 ncplane_resize_simple(t->p, leny - diff, lenx);
390 }
391 // We needn't move the resized plane if drawing down, or the focused plane.
392 // The focused tablet will have been resized properly above, but it might
393 // be out of position (the focused tablet ought move as little as possible).
394 // Move it back to the frontier, or the nearest line above if it has grown.
395 if(nr->tablets == t){
396 if(leny - frontiertop + 1 < ll){
397 ncplane_yx(fp, &frontiertop, NULL);
398 frontiertop += (leny - ll);
399 }
400 ncplane_move_yx(fp, frontiertop, begx);
401//fprintf(stderr, "moved to frontiertop %d\n", frontiertop);
402 }else if(direction == DIRECTION_UP){
403 ncplane_move_yx(fp, begy + diff, begx);
404//fprintf(stderr, "MOVEDOWN from %d to %d\n", begy, begy + diff);
405 }
406 cbleny = ll;
407 }
408 }
409 // we can't push the border plane beyond its true boundaries, or we'll mess
410 // up layout later. instead, add a bottommask iff leny <= cbleny + 1
411 unsigned mask = nr->ropts.tabletmask;
412 if(leny <= cbleny + !(mask & NCBOXMASK_TOP)){
413 mask |= NCBOXMASK_BOTTOM;
414 }
415 uint64_t channels = nr->tablets == t ? nr->ropts.focusedchan : nr->ropts.tabletchan;
416 draw_borders(fp, mask, channels, direction);
417 return 0;
418}
419
420// move down below the focused tablet, filling up the reel to the bottom.
421// returns the last tablet drawn.
422static nctablet*
423draw_following_tablets(const ncreel* nr, nctablet* otherend,
424 int frontiertop, int* frontierbottom){
425 const bool botborder = !(nr->ropts.bordermask & NCBOXMASK_BOTTOM);
426//fprintf(stderr, "following otherend: %p ->p: %p %d/%d\n", otherend, otherend->p, frontiertop, *frontierbottom);
427 nctablet* working = nr->tablets->next;
428 const int maxx = ncplane_dim_y(nr->p) - 1 - botborder;
429 // move down past the focused tablet, filling up the reel to the bottom
430 while(*frontierbottom <= maxx && (working != otherend || !otherend->p)){
431 if(working->p){
432 break;
433 }
434//fprintf(stderr, "following otherend: %p ->p: %p\n", otherend, otherend->p);
435 // modify frontier based off the one we're at
436//fprintf(stderr, "EASTBOUND AND DOWN: %p->%p %d %d\n", working, working->next, frontiertop, *frontierbottom);
437 if(ncreel_draw_tablet(nr, working, frontiertop, *frontierbottom, DIRECTION_DOWN)){
438 return NULL;
439 }
440 if(working == otherend){
441 otherend = otherend->next;
442 }
443 *frontierbottom += ncplane_dim_y(working->p) + 1;
444 working = working->next;
445 }
446//fprintf(stderr, "done with prevs: %p->%p %d %d\n", working, working->prev, frontiertop, *frontierbottom);
447 return working;
448}
449
450// move up above the focused tablet, filling up the reel to the top.
451// returns the last tablet drawn.
452static nctablet*
453draw_previous_tablets(const ncreel* nr, nctablet* otherend,
454 int* frontiertop, int frontierbottom){
455 const bool topborder = !(nr->ropts.bordermask & NCBOXMASK_TOP);
456 nctablet* upworking = nr->tablets->prev;
457//fprintf(stderr, "preceding %p otherend: %p ->p: %p frontiers: %d %d\n", upworking, otherend, otherend->p, *frontiertop, frontierbottom);
458 // modify frontier based off the one we're at
459 while(*frontiertop >= topborder && (upworking != otherend || !otherend->p)){
460 if(upworking->p){
461 break;
462 }
463//fprintf(stderr, "MOVIN' ON UP: %p->%p %d %d\n", upworking, upworking->prev, *frontiertop, frontierbottom);
464 if(ncreel_draw_tablet(nr, upworking, *frontiertop, frontierbottom, DIRECTION_UP)){
465 return NULL;
466 }
467 if(upworking == otherend){
468 otherend = otherend->prev;
469 }
470 *frontiertop -= ncplane_dim_y(upworking->p) + 1;
471 upworking = upworking->prev;
472 }
473//fprintf(stderr, "done with prevs: %p->%p %d %d\n", upworking, upworking->prev, *frontiertop, frontierbottom);
474 return upworking;
475}
476
477// Tablets are initially drawn assuming more space to be available than may
478// actually exist. We do a pass at the end trimming any overhang.
479static int
480trim_reel_overhang(ncreel* r, nctablet* top, nctablet* bottom){
481 int y;
482 if(!top || !top->p || !bottom || !bottom->p){
483 return -1;
484 }
485//fprintf(stderr, "trimming: top %p bottom %p\n", top->p, bottom->p);
486 ncplane_yx(top->p, &y, NULL);
487 unsigned ylen, xlen;
488 ncplane_dim_yx(top->p, &ylen, &xlen);
489 const int miny = !(r->ropts.bordermask & NCBOXMASK_TOP);
490 int boty = y + ylen - 1;
491//fprintf(stderr, "top: %dx%d @ %d, miny: %d\n", ylen, xlen, y, miny);
492 if(boty < miny){
493//fprintf(stderr, "NUKING top!\n");
495 top->p = NULL;
496 top->cbp = NULL;
497 top = top->next;
498 return trim_reel_overhang(r, top, bottom);
499 }else if(y < miny){
500 int ynew = ylen - (miny - y);
501 if(ynew <= 0){
503 top->p = NULL;
504 top->cbp = NULL;
505 }else{
506 if(ncplane_resize(top->p, miny - y, 0, ynew, xlen, 0, 0, ynew, xlen)){
507 return -1;
508 }
509 if(top->cbp){
510 if(ynew == !(r->ropts.tabletmask & NCBOXMASK_TOP)){
512 top->cbp = NULL;
513 }else{
514 ncplane_dim_yx(top->cbp, &ylen, &xlen);
515 ynew -= !(r->ropts.tabletmask & NCBOXMASK_TOP);
516 if(ncplane_resize(top->cbp, miny - y, 0, ynew, xlen, 0, 0, ynew, xlen)){
517 return -1;
518 }
519 int x;
520 ncplane_yx(top->cbp, &y, &x);
521 ncplane_move_yx(top->cbp, y - 1, x);
522 }
523 }
524 }
525 }
526 if(bottom->p){
527 ncplane_dim_yx(bottom->p, &ylen, &xlen);
528 ncplane_yx(bottom->p, &y, NULL);
529 const int maxy = ncplane_dim_y(r->p) - (1 + !(r->ropts.bordermask & NCBOXMASK_BOTTOM));
530 boty = y + ylen - 1;
531 //fprintf(stderr, "bot: %dx%d @ %d, maxy: %d\n", ylen, xlen, y, maxy);
532 if(maxy < y){
533 //fprintf(stderr, "NUKING bottom!\n");
534 if(ncplane_set_widget(bottom->p, NULL, NULL) == 0){
535 ncplane_family_destroy(bottom->p);
536 }
537 bottom->p = NULL;
538 bottom->cbp = NULL;
539 bottom = bottom->prev;
540 return trim_reel_overhang(r, top, bottom);
541 }if(maxy < boty){
542 int ynew = ylen - (boty - maxy);
543 if(ynew <= 0){
544 ncplane_family_destroy(bottom->p);
545 bottom->p = NULL;
546 bottom->cbp = NULL;
547 }else{
548 if(ncplane_resize(bottom->p, 0, 0, ynew, xlen, 0, 0, ynew, xlen)){
549 return -1;
550 }
551 //fprintf(stderr, "TRIMMED bottom %p from %d to %d (%d)\n", bottom->p, ylen, ynew, maxy - boty);
552 if(bottom->cbp){
553 if(ynew == !(r->ropts.tabletmask & NCBOXMASK_BOTTOM)){
555 bottom->cbp = NULL;
556 }else{
557 ncplane_dim_yx(bottom->cbp, &ylen, &xlen);
558 ynew -= !(r->ropts.tabletmask & NCBOXMASK_BOTTOM);
559 if(ncplane_resize(bottom->cbp, 0, 0, ynew, xlen, 0, 0, ynew, xlen)){
560 return -1;
561 }
562 }
563 }
564 }
565 }
566 }
567//fprintf(stderr, "finished trimming\n");
568 return 0;
569}
570
571static int
572tighten_reel_down(ncreel* r, int ybot){
573 nctablet* cur = r->tablets;
574 while(cur){
575 if(cur->p == NULL){
576 break;
577 }
578 int cury, curx;
579 unsigned ylen;
580 ncplane_yx(cur->p, &cury, &curx);
581 ncplane_dim_yx(cur->p, &ylen, NULL);
582 if(cury <= ybot - (int)ylen - 1){
583 break;
584 }
585//fprintf(stderr, "tightening %p down to %d from %d\n", cur, ybot - ylen, cury);
586 cury = ybot - (int)ylen;
587 ncplane_move_yx(cur->p, cury, curx);
588 ybot = cury - 1;
589 if((cur = cur->prev) == r->tablets){
590 break;
591 }
592 }
593 return 0;
594}
595
596// run at the end of redraw, this aligns the top tablet with the top of the
597// reel. we prefer empty space at the bottom (FIXME but not really -- we ought
598// prefer space away from the last direction of movement. rather than this
599// postprocessing, draw things to the right places!). we then trim any tablet
600// overhang. FIXME could pass top/bottom in directly, available as otherend
601static int
602tighten_reel(ncreel* r){
603//fprintf(stderr, "tightening it up\n");
604 nctablet* top = r->tablets;
605 nctablet* cur = top;
606 int ytop = INT_MAX;
607 // find the top tablet
608 while(cur){
609 if(cur->p == NULL){
610 break;
611 }
612 int cury;
613 ncplane_yx(cur->p, &cury, NULL);
614 if(cury >= ytop){
615 break;
616 }
617 ytop = cury;
618 top = cur;
619 cur = cur->prev;
620 }
621 int expected = !(r->ropts.bordermask & NCBOXMASK_TOP);
622 cur = top;
623 nctablet* bottom = r->tablets;
624 // find the bottom tablet, moving tablets up as we go along
625 while(cur){
626 if(cur->p == NULL){
627 break;
628 }
629 int cury, curx;
630 ncplane_yx(cur->p, &cury, &curx);
631 if(cury != expected){
632 if(ncplane_move_yx(cur->p, expected, curx)){
633//fprintf(stderr, "tightened %p up to %d\n", cur, expected);
634 return -1;
635 }
636 }
637 unsigned ylen;
638 ncplane_dim_yx(cur->p, &ylen, NULL);
639 expected += ylen + 1;
640//fprintf(stderr, "bottom (%p) gets %p\n", bottom, cur);
641 bottom = cur;
642 cur = cur->next;
643 if(cur == top){
644 break;
645 }
646 }
647 cur = r->tablets;
648 if(cur){
649 const ncplane* n = cur->p;
650 int yoff;
651 unsigned ylen, rylen;
652 ncplane_dim_yx(r->p, &rylen, NULL);
653 ncplane_yx(n, &yoff, NULL);
655 const int ybot = rylen - 1 + !!(r->ropts.bordermask & NCBOXMASK_BOTTOM);
656 // FIXME want to tighten down whenever we're at the bottom, and the reel
657 // is full, not just in this case (this can leave a gap of more than 1 row)
658 if(yoff + (int)ylen + 1 >= ybot){
659 if(tighten_reel_down(r, ybot)){
660 return -1;
661 }
662 }
663 }
664 if(!top || !bottom){
665 return 0;
666 }
667 return trim_reel_overhang(r, top, bottom);
668}
669
670// Arrange the panels, starting with the focused window, wherever it may be.
671// If necessary, resize it to the full size of the reel--focus has its
672// privileges. We then work in the opposite direction of travel, filling out
673// the reel above and below. If we moved down to get here, do the tablets above
674// first. If we moved up, do the tablets below. This ensures tablets stay in
675// place relative to the new focus; they could otherwise pivot around the new
676// focus, if we're not filling out the reel.
678//fprintf(stderr, "\n--------> BEGIN REDRAW <--------\n");
679 nctablet* focused = nr->tablets;
680 int fulcrum; // target line
681 if(nr->direction == LASTDIRECTION_UP){
682 // case i iff the last direction was UP, and either the focused tablet is
683 // not visible, or below the visibly-focused tablet, or there is no
684 // visibly-focused tablet.
685 if(!focused || !focused->p || !nr->vft){
686 fulcrum = 0;
687//fprintf(stderr, "case i fulcrum %d\n", fulcrum);
688 }else{
689 int focy, vfty;
690 ncplane_yx(focused->p, &focy, NULL);
691 ncplane_yx(nr->vft->p, &vfty, NULL);
692 if(focy > vfty){
693 fulcrum = 0;
694//fprintf(stderr, "case i fulcrum %d (%d %d) %p %p\n", fulcrum, focy, vfty, focused, nr->vft);
695 }else{
696 ncplane_yx(focused->p, &fulcrum, NULL); // case iii
697//fprintf(stderr, "case iii fulcrum %d (%d %d) %p %p lastdir: %d\n", fulcrum, focy, vfty, focused, nr->vft, nr->direction);
698 }
699 }
700 }else{
701 // case ii iff the last direction was DOWN, and either the focused tablet
702 // is not visible, or above the visibly-focused tablet, or there is no
703 // visibly-focused tablet.
704 if(!focused || !focused->p || !nr->vft){
705 fulcrum = ncplane_dim_y(nr->p) - 1;
706//fprintf(stderr, "case ii fulcrum %d\n", fulcrum);
707 }else{
708 int focy, vfty;
709//fprintf(stderr, "focused: %p\n", focused);
710 ncplane_yx(focused->p, &focy, NULL);
711 ncplane_yx(nr->vft->p, &vfty, NULL);
712 if(focy < vfty){
713 fulcrum = ncplane_dim_y(nr->p) - 1;
714//fprintf(stderr, "case ii fulcrum %d (%d %d) %p %p\n", fulcrum, focy, vfty, focused, nr->vft);
715 }else{
716 ncplane_yx(focused->p, &fulcrum, NULL); // case iii
717//fprintf(stderr, "case iiib fulcrum %d (%d %d) %p %p lastdir: %d\n", fulcrum, focy, vfty, focused, nr->vft, nr->direction);
718 }
719 }
720 }
721 clean_reel(nr);
722 if(focused){
723//fprintf(stderr, "drawing focused tablet %p dir: %d fulcrum: %d!\n", focused, nr->direction, fulcrum);
724 if(ncreel_draw_tablet(nr, focused, fulcrum, fulcrum, DIRECTION_DOWN)){
725 logerror("error drawing tablet");
726 return -1;
727 }
728//fprintf(stderr, "drew focused tablet %p -> %p lastdir: %d!\n", focused, focused->p, nr->direction);
729 nctablet* otherend = focused;
730 int frontiertop, frontierbottom;
731 ncplane_yx(nr->tablets->p, &frontiertop, NULL);
732 frontierbottom = frontiertop + ncplane_dim_y(nr->tablets->p) + 1;
733 frontiertop -= 2;
734 if(nr->direction == LASTDIRECTION_DOWN){
735 otherend = draw_previous_tablets(nr, otherend, &frontiertop, frontierbottom);
736 if(otherend == NULL){
737 logerror("error drawing higher tablets");
738 return -1;
739 }
740 otherend = draw_following_tablets(nr, otherend, frontiertop, &frontierbottom);
741 }else{ // DIRECTION_UP
742 otherend = draw_previous_tablets(nr, otherend, &frontiertop, frontierbottom);
743 if(otherend == NULL){
744 logerror("error drawing higher tablets");
745 return -1;
746 }
747 otherend = draw_following_tablets(nr, otherend, frontiertop, &frontierbottom);
748 }
749 if(otherend == NULL){
750 logerror("error drawing following tablets");
751 return -1;
752 }
753//notcurses_debug(ncplane_notcurses(nr->p), stderr);
754 if(tighten_reel(nr)){
755 logerror("error tightening reel");
756 return -1;
757 }
758//notcurses_debug(ncplane_notcurses(nr->p), stderr);
759 }
760 nr->vft = nr->tablets; // update the visually-focused tablet pointer
761//fprintf(stderr, "DONE ARRANGING\n");
762 if(draw_ncreel_borders(nr)){
763 logerror("error drawing reel borders");
764 return -1; // enforces specified dimensional minima
765 }
766 return 0;
767}
768
769static bool
770validate_ncreel_opts(ncplane* n, const ncreel_options* ropts){
771 if(n == NULL){
772 return false;
773 }
774 if(ropts->flags >= (NCREEL_OPTION_CIRCULAR << 1u)){
775 logwarn("provided unsupported flags 0x%016" PRIx64, ropts->flags);
776 }
777 if(ropts->flags & NCREEL_OPTION_CIRCULAR){
778 if(!(ropts->flags & NCREEL_OPTION_INFINITESCROLL)){
779 logerror("can't set circular without infinitescroll");
780 return false; // can't set circular without infinitescroll
781 }
782 }
783 // there exist higher NCBOX defines, but they're not valid in this context
784 const unsigned fullmask = NCBOXMASK_LEFT |
788 if(ropts->bordermask > fullmask){
789 logerror("bad bordermask: 0x%016x", ropts->bordermask);
790 return false;
791 }
792 if(ropts->tabletmask > fullmask){
793 logerror("bad tabletmask: 0x%016x", ropts->bordermask);
794 return false;
795 }
796 return true;
797}
798
800 return t->cbp;
801}
802
804 return nr->p;
805}
806
808 ncreel_options zeroed = {0};
809 ncreel* nr;
810 if(!ropts){
811 ropts = &zeroed;
812 }
813 if(!validate_ncreel_opts(n, ropts)){
814 return NULL;
815 }
816 if((nr = malloc(sizeof(*nr))) == NULL){
817 return NULL;
818 }
819 nr->tablets = NULL;
820 nr->tabletcount = 0;
821 nr->direction = LASTDIRECTION_DOWN; // draw down after the initial tablet
822 memcpy(&nr->ropts, ropts, sizeof(*ropts));
823 nr->p = n;
824 nr->vft = NULL;
825 if(ncplane_set_widget(nr->p, nr, (void(*)(void*))ncreel_destroy)){
826 ncplane_destroy(nr->p);
827 free(nr);
828 return NULL;
829 }
830 if(ncreel_redraw(nr)){
831 ncplane_destroy(nr->p);
832 free(nr);
833 return NULL;
834 }
835 return nr;
836}
837
839 tabletcb cbfxn, void* opaque){
840 nctablet* t;
841 if(after && before){
842 if(after->next != before || before->prev != after){
843 logerror("bad before (%p) / after (%p) spec", before, after);
844 return NULL;
845 }
846 }else if(!after && !before){
847 // This way, without user interaction or any specification, new tablets are
848 // inserted at the "end" relative to the focus. The first one to be added
849 // gets and keeps the focus. New ones will go on the bottom, until we run
850 // out of space. New tablets are then created off-screen.
851 before = nr->tablets;
852 }
853 if((t = malloc(sizeof(*t))) == NULL){
854 return NULL;
855 }
856//fprintf(stderr, "--------->NEW TABLET %p\n", t);
857 if(after){
858 t->next = after->next;
859 after->next = t;
860 t->prev = after;
861 t->next->prev = t;
862 }else if(before){
863 t->prev = before->prev;
864 before->prev = t;
865 t->next = before;
866 t->prev->next = t;
867 }else{ // we're the first tablet
868 t->prev = t->next = t;
869 nr->tablets = t;
870 }
871 t->cbfxn = cbfxn;
872 t->curry = opaque;
873 ++nr->tabletcount;
874 t->p = NULL;
875 t->cbp = NULL;
876 ncreel_redraw(nr);
877 return t;
878}
879
881 if(nreel){
882 if(ncplane_set_widget(nreel->p, NULL, NULL) == 0){
883 nctablet* t;
884 while( (t = nreel->tablets) ){
885 ncreel_del(nreel, t);
886 }
887 ncplane_destroy(nreel->p);
888 }
889 free(nreel);
890 }
891}
892
894 return t->curry;
895}
896
897int ncreel_tabletcount(const ncreel* nreel){
898 return nreel->tabletcount;
899}
900
902 return nr->tablets;
903}
904
906 if(nr->tablets){
907 nr->tablets = nr->tablets->next;
908//fprintf(stderr, "---------------> moved to next, %p to %p <----------\n", nr->tablets->prev, nr->tablets);
909 nr->direction = LASTDIRECTION_DOWN;
910 ncreel_redraw(nr);
911 }
912 return nr->tablets;
913}
914
916 if(nr->tablets){
917 nr->tablets = nr->tablets->prev;
918//fprintf(stderr, "----------------> moved to prev, %p to %p <----------\n", nr->tablets->next, nr->tablets);
919 nr->direction = LASTDIRECTION_UP;
920 ncreel_redraw(nr);
921 }
922 return nr->tablets;
923}
924
926 if(nc->evtype == NCTYPE_RELEASE){
927 return false;
928 }
929 if(nc->id == NCKEY_UP){
930 ncreel_prev(n);
931 return true;
932 }else if(nc->id == NCKEY_DOWN){
933 ncreel_next(n);
934 return true;
935 }else if(nc->id == NCKEY_SCROLL_UP){
936 ncreel_prev(n);
937 return true;
938 }else if(nc->id == NCKEY_SCROLL_DOWN){
939 ncreel_next(n);
940 return true;
941 }
942 // FIXME page up/page down
943 // FIXME end/home (when not using infinite scrolling)
944 return false;
945}
API int API int API int uint64_t uint64_t uint64_t uint64_t lr
Definition direct.h:216
API int API int API int uint64_t uint64_t uint64_t ll
Definition direct.h:216
API int API int API int uint64_t uint64_t uint64_t uint64_t unsigned unsigned xlen
Definition direct.h:217
API int API int API int uint64_t uint64_t uint64_t uint64_t unsigned ylen
Definition direct.h:217
API int API int API int uint64_t ul
Definition direct.h:215
API int API int API int uint64_t uint64_t ur
Definition direct.h:215
int r
Definition fbuf.h:226
#define logerror(fmt,...)
Definition logging.h:32
#define logwarn(fmt,...)
Definition logging.h:37
#define NCKEY_SCROLL_UP
Definition nckeys.h:192
#define NCKEY_SCROLL_DOWN
Definition nckeys.h:193
#define NCKEY_UP
Definition nckeys.h:37
#define NCKEY_DOWN
Definition nckeys.h:39
int ncplane_cursor_move_yx(ncplane *n, int y, int x)
Definition notcurses.c:723
void ncplane_home(ncplane *n)
Definition notcurses.c:718
int ncplane_destroy(ncplane *ncp)
Definition notcurses.c:1021
int ncplane_move_yx(ncplane *n, int y, int x)
Definition notcurses.c:2417
void ncplane_yx(const ncplane *n, int *y, int *x)
Definition notcurses.c:2454
int ncplane_resize(ncplane *n, int keepy, int keepx, unsigned keepleny, unsigned keeplenx, int yoff, int xoff, unsigned ylen, unsigned xlen)
Definition notcurses.c:1009
ncplane * ncplane_create(ncplane *n, const ncplane_options *nopts)
Definition notcurses.c:710
int ncplane_move_above(ncplane *restrict n, ncplane *restrict above)
Definition notcurses.c:1584
int ncplane_family_destroy(ncplane *ncp)
Definition notcurses.c:1071
void ncplane_dim_yx(const ncplane *n, unsigned *rows, unsigned *cols)
Definition notcurses.c:304
int y
Definition notcurses.h:1905
#define NCBOXMASK_TOP
Definition notcurses.h:2605
int(* tabletcb)(struct nctablet *t, bool drawfromtop)
Definition notcurses.h:3732
#define NCREEL_OPTION_CIRCULAR
Definition notcurses.h:3702
#define NCBOXMASK_LEFT
Definition notcurses.h:2608
@ NCTYPE_RELEASE
Definition notcurses.h:1198
vopts n
Definition notcurses.h:3506
#define NCBOXMASK_BOTTOM
Definition notcurses.h:2607
#define NCREEL_OPTION_INFINITESCROLL
Definition notcurses.h:3698
int int x
Definition notcurses.h:1905
#define NCBOXMASK_RIGHT
Definition notcurses.h:2606
nctablet * ncreel_focused(ncreel *nr)
Definition reel.c:901
int ncreel_del(ncreel *nr, struct nctablet *t)
Definition reel.c:285
bool ncreel_offer_input(ncreel *n, const ncinput *nc)
Definition reel.c:925
ncplane * ncreel_plane(ncreel *nr)
Definition reel.c:803
void ncreel_destroy(ncreel *nreel)
Definition reel.c:880
int ncreel_redraw(ncreel *nr)
Definition reel.c:677
nctablet * ncreel_prev(ncreel *nr)
Definition reel.c:915
ncreel * ncreel_create(ncplane *n, const ncreel_options *ropts)
Definition reel.c:807
direction_e
Definition reel.c:7
@ DIRECTION_DOWN
Definition reel.c:9
@ DIRECTION_UP
Definition reel.c:8
nctablet * ncreel_next(ncreel *nr)
Definition reel.c:905
void * nctablet_userptr(nctablet *t)
Definition reel.c:893
int ncreel_tabletcount(const ncreel *nreel)
Definition reel.c:897
nctablet * ncreel_add(ncreel *nr, nctablet *after, nctablet *before, tabletcb cbfxn, void *opaque)
Definition reel.c:838
ncplane * nctablet_plane(nctablet *t)
Definition reel.c:799
void nccell_release(ncplane *n, nccell *c)
Definition render.c:128
ncintype_e evtype
Definition notcurses.h:1218
uint32_t id
Definition notcurses.h:1210
unsigned bordermask
Definition notcurses.h:3711
uint64_t borderchan
Definition notcurses.h:3712
uint64_t tabletchan
Definition notcurses.h:3714
uint64_t flags
Definition notcurses.h:3716
unsigned tabletmask
Definition notcurses.h:3713
uint64_t focusedchan
Definition notcurses.h:3715
ncreel_options ropts
Definition internal.h:190
int tabletcount
Definition internal.h:189
nctablet * tablets
Definition internal.h:183
ncplane * p
Definition internal.h:179
nctablet * vft
Definition internal.h:184
enum ncreel::@1 direction
ncplane * cbp
Definition internal.h:171
struct nctablet * prev
Definition internal.h:173
void * curry
Definition internal.h:175
ncplane * p
Definition internal.h:170
tabletcb cbfxn
Definition internal.h:174
struct nctablet * next
Definition internal.h:172
return NULL
Definition termdesc.h:229