From 58f7dab1c0874115a929029811030c01ec7409bf Mon Sep 17 00:00:00 2001 From: nick black Date: Wed, 5 Aug 2020 22:09:11 -0400 Subject: [PATCH] doc it up (a plan for ncreels, that is) --- src/lib/reel.c | 114 +++++++++++++++++++++++++++++++------------------ 1 file changed, 72 insertions(+), 42 deletions(-) diff --git a/src/lib/reel.c b/src/lib/reel.c index 0fd972ba9..f6fd60302 100644 --- a/src/lib/reel.c +++ b/src/lib/reel.c @@ -9,7 +9,6 @@ // a single, distinct ncplane. typedef struct nctablet { ncplane* p; // visible panel, NULL when offscreen - ncplane* border; struct nctablet* next; struct nctablet* prev; tabletcb cbfxn; // application callback to draw tablet @@ -21,6 +20,25 @@ typedef enum { DIRECTION_DOWN, } direction_e; +// A UNIFIED THEORY OF NCREELS +// (which are more complex than they may seem) +// +// We only redraw when ncreel_redraw() is explicitly called (and at creation). +// Redrawing consists of arranging the tablets, and calling the user's +// callbacks to fill them in. In general, drawing a tablet involves creating a +// plane having all available size, calling the callback, and then trimming the +// plane if it was not filled. +// +// Things which can happen between ncreel_redraw() calls: +// +// * new focused tablet added (only when no tablets exist) +// * new unfocused tablet added +// * tablet removed (may be the focused tablet) +// * tablets may grow or shrink +// * focus can change due to new selection +// +// Any number and mix of these events can occur between draws. +// // First rule: there must not be 2+ consecutive lines of blank space if there is // data which could be presented there (always fill the reel). // Second rule: if there must be 2+ consecutive lines of blank space, they must @@ -29,34 +47,51 @@ typedef enum { // Fourth rule: the focused tablet should remain where it is across redraws, // except as necessary to accommodate the prior rules. // -// At any time, you can make three types of moves: -// - move up from the topmost tablet -// - move down from the bottommost tablet -// - move otherwise +// At any ncreel_redraw(), you can make three types of moves: +// +// - i. moving up and replacing the topmost tablet (spinning operation) +// - ii. moving down and replacing the bottommost tablet (spinning operation) +// - iii. don't move / move otherwise (no necessary spin) +// // The first two are simple -- draw the focused tablet next to the appropriate // border of the reel, and then draw what we can in the other direction until // running out of space (and then shift up if there is more than one line of // gap at the top, or if we were moving up from the topmost tablet). This can // be done independently of all other tablets; it is immaterial if some were -// removed, added, etc. +// removed, added, etc. We can detect these two cases thus: +// +// - store a direction_e, written to by ncreel_next(), ncreel_prev(), and +// ncreel_del() (when the focused tablet is deleted), defaulting to DOWN. +// - store a pointer to the "visibly focused" tablet, written and read only by +// ncreel_redraw(). this identifies the focused tablet upon last redraw. if +// the visibly-focused tablet is removed, it instead takes the next tablet, +// iff that tablet is visible. it otherwise takes the prev tablet, iff that +// tablet is visible. it otherwise takes NULL. +// - with these, in ncreel_redraw(), we have: +// - case i iff the last direction was UP, and either the focused tablet is +// not visible, or below the visibly-focused tablet, or there is no +// visibly-focused tablet. +// - case ii iff the last direction was DOWN, and either the focused tablet +// is not visible, or above the visibly-focused tablet, or there is no +// visibly-focused tablet. +// +// We otherwise have case iii. The focused tablet must be on-screen (if it was +// off-screen, we matched one of case i or case ii). We want to draw it as near +// to its current position as possible, subject to the first three Rules. +// +// ncreel_redraw() thus starts by determining the case. This must be done +// before any changes are made to the arrangement. It then clears the reel. +// The focused tablet is drawn as close to its desired line as possible. For +// case i, then draw the tablets below the focused tablet. For case ii, then +// draw the tablets above the focused tablet (and move them up, if necessary). +// Both of these cases are then handled. +// +// For case iii, see below... // -// The visible screen can be reconstructed from four things: -// * which tablet is focused (pointed at by ncreel->tablets) -// * which row the focused tablet starts at (derived from focused tablet) -// * *except* when we reached it via border wrap, in which case it is -// defined by the border -// * the list of tablets (available from the focused tablet) -// * from which direction we arrived at the focused tablet -// Things which can happen between ncreel_redraw() calls: -// * new focused tablet added (only when no tablets exist) -// * new unfocused tablet added -// * tablet removed (may be focused) -// * tablets may grow or shrink -// * focus can change due to move -// * we *only* redraw when ncreel_redraw() is explicitly called (and creation) // On tablet remove: // * destroy plane, remove from list -// * if tablet was focused, change focus +// * if tablet was focused, change focus, update travel direction +// * if tablet was visibly focused, change visible focus // On tablet add: // * add to list (do not create plane) // * if no tablet existed, change focus to new tablet @@ -88,19 +123,14 @@ typedef enum { // * draw through edge typedef struct ncreel { ncplane* p; // ncplane this ncreel occupies, under tablets - ncreel_options ropts; // copied in ncreel_create() // doubly-linked list, a circular one when infinity scrolling is in effect. // points at the focused tablet (when at least one tablet exists, one must be - // focused), which might be anywhere on the screen (but is always visible). + // focused). it will be visibly focused following the next redraw. nctablet* tablets; - // these values could all be derived at any time, but keeping them computed - // makes other things easier, or saves us time (at the cost of complexity). - int tabletcount; // could be derived, but we keep it o(1) - // last direction in which we moved. positive if we moved down ("next"), - // negative if we moved up ("prev"), 0 for non-linear operation. we start - // drawing unfocused tablets opposite the direction of our last movement, so - // that movement in an unfilled reel doesn't reorient our tablets. - int last_traveled_direction; + nctablet* vft; // the visibly-focused tablet + direction_e direction;// last direction of travel + int tabletcount; // could be derived, but we keep it o(1) + ncreel_options ropts; // copied in ncreel_create() } ncreel; // Returns the starting coordinates (relative to the screen) of the specified @@ -382,16 +412,16 @@ draw_focused_tablet(const ncreel* nr){ ncplane_dim_yx(nr->p, &pleny, &plenx); int fulcrum; if(nr->tablets->p == NULL){ - if(nr->last_traveled_direction >= 0){ + if(nr->direction == DIRECTION_DOWN){ fulcrum = pleny + !(nr->ropts.bordermask & NCBOXMASK_BOTTOM); }else{ fulcrum = !(nr->ropts.bordermask & NCBOXMASK_TOP); } -//fprintf(stderr, "LTD: %d placing new at %d\n", nr->last_traveled_direction, fulcrum); +//fprintf(stderr, "LTD: %d placing new at %d\n", nr->direction, fulcrum); }else{ // focused was already present. want to stay where we are, if possible ncplane_yx(nr->tablets->p, &fulcrum, NULL); // FIXME ugh can't we just remember the previous fulcrum? - if(nr->last_traveled_direction > 0){ + if(nr->direction == DIRECTION_DOWN){ if(nr->tablets->prev->p){ int prevfulcrum; ncplane_yx(nr->tablets->prev->p, &prevfulcrum, NULL); @@ -399,7 +429,7 @@ draw_focused_tablet(const ncreel* nr){ fulcrum = pleny + !(nr->ropts.bordermask & NCBOXMASK_BOTTOM); } } - }else if(nr->last_traveled_direction < 0){ + }else if(nr->direction == DIRECTION_UP){ if(nr->tablets->next->p){ int nextfulcrum; ncplane_yx(nr->tablets->next->p, &nextfulcrum, NULL); @@ -408,7 +438,7 @@ draw_focused_tablet(const ncreel* nr){ } } } -//fprintf(stderr, "existing: %p %d placing at %d\n", nr->tablets, nr->last_traveled_direction, fulcrum); +//fprintf(stderr, "existing: %p %d placing at %d\n", nr->tablets, nr->direction, fulcrum); } //fprintf(stderr, "PR dims: %d/%d fulcrum: %d\n", pleny, plenx, fulcrum); return ncreel_draw_tablet(nr, nr->tablets, fulcrum, 0); @@ -650,12 +680,12 @@ int ncreel_redraw(ncreel* nr){ //fprintf(stderr, "no focus!\n"); return 0; // if none are focused, none exist } -//fprintf(stderr, "drawing focused tablet %p dir: %d!\n", focused, nr->last_traveled_direction); +//fprintf(stderr, "drawing focused tablet %p dir: %d!\n", focused, nr->direction); draw_focused_tablet(nr); -//fprintf(stderr, "drew focused tablet %p dir: %d!\n", focused, nr->last_traveled_direction); +//fprintf(stderr, "drew focused tablet %p dir: %d!\n", focused, nr->direction); clean_reel(nr); nctablet* otherend = focused; - if(nr->last_traveled_direction >= 0){ + if(nr->direction >= DIRECTION_DOWN){ otherend = draw_previous_tablets(nr, otherend); otherend = draw_following_tablets(nr, otherend); draw_previous_tablets(nr, otherend); @@ -715,7 +745,7 @@ ncreel* ncreel_create(ncplane* w, const ncreel_options* ropts){ } nr->tablets = NULL; nr->tabletcount = 0; - nr->last_traveled_direction = -1; // draw down after the initial tablet + nr->direction = DIRECTION_DOWN; // draw down after the initial tablet memcpy(&nr->ropts, ropts, sizeof(*ropts)); nr->p = w; ncplane_set_base(nr->p, "", 0, ropts->bgchannel); @@ -815,7 +845,7 @@ nctablet* ncreel_next(ncreel* nr){ nr->tablets = nr->tablets->next; //fprintf(stderr, "---------------> moved to next, %p to %p <----------\n", // nr->tablets->prev, nr->tablets); - nr->last_traveled_direction = 1; + nr->direction = DIRECTION_DOWN; } return nr->tablets; } @@ -825,7 +855,7 @@ nctablet* ncreel_prev(ncreel* nr){ nr->tablets = nr->tablets->prev; //fprintf(stderr, "----------------> moved to prev, %p to %p <----------\n", // nr->tablets->next, nr->tablets); - nr->last_traveled_direction = -1; + nr->direction = DIRECTION_UP; } return nr->tablets; }