You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
946 lines
33 KiB
C
946 lines
33 KiB
C
#include <errno.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include "internal.h"
|
|
|
|
typedef enum {
|
|
DIRECTION_UP,
|
|
DIRECTION_DOWN,
|
|
} direction_e; // current direction of travel
|
|
|
|
// 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
|
|
// all be at the bottom of the reel (connect and anchor the reel).
|
|
// Third rule: the focused tablet gets all the space it can use.
|
|
// Fourth rule: thou shalt never wrap a tablet [across a border]
|
|
// Fifth rule: the focused tablet should remain where it is across redraws,
|
|
// except as necessary to accommodate the prior rules.
|
|
//
|
|
// 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. 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 four 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...
|
|
//
|
|
// On tablet remove:
|
|
// * destroy plane, remove from list
|
|
// * 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
|
|
// On focus change:
|
|
// * change focus, update travel direction
|
|
// On redraw:
|
|
// * if no tablets, we're done (deleted planes are already gone)
|
|
// * resize focused tablet to maximum, preserving nothing
|
|
// * call back for focused tablet redraw
|
|
// * shrink focused tablet if applicable
|
|
// * place focused tablet according to:
|
|
// * the focused tablet should be wholly visible, or if not, use all space
|
|
// * the focused tablet should be as close to its old position as possible.
|
|
// * if focused tablet was not wholly on screen, it goes to the side
|
|
// corresponding to the direction of movement.
|
|
// * if out of space or tablets, we're done
|
|
// FIXME *maybe* just draw up followed by down, rather than direction of travel?
|
|
// * walk the list in the direction of travel, foc->focw
|
|
// * designate tablet against the walk as 'focagainst', might be NULL
|
|
// * if focagainst || focagainst, focw only through edge, otherwise
|
|
// * focw can be as large as all remaining space
|
|
// * if there is space, draw what we can of next tablet
|
|
// * move the focused tablet againt the direction of travel if necessary
|
|
// * prefer the space in the direction of walking
|
|
// * last tablet drawn is 'backstop'
|
|
// * if out of space or tablets, we're done
|
|
// * walk the list in the direction against travel, foc->focw
|
|
// * if focw == backstop, we're done
|
|
// * draw through edge
|
|
|
|
static int
|
|
draw_borders(ncplane* n, unsigned mask, uint64_t channel, direction_e direction){
|
|
unsigned lenx, leny;
|
|
ncplane_dim_yx(n, &leny, &lenx);
|
|
int maxx = lenx - 1;
|
|
int maxy = leny - 1;
|
|
nccell ul, ur, ll, lr, hl, vl;
|
|
nccell_init(&ul); nccell_init(&ur); nccell_init(&hl);
|
|
nccell_init(&ll); nccell_init(&lr); nccell_init(&vl);
|
|
if(nccells_rounded_box(n, 0, channel, &ul, &ur, &ll, &lr, &hl, &vl)){
|
|
return -1;
|
|
}
|
|
//fprintf(stderr, "drawing borders %p ->%d/%d, mask: %04x\n", w, maxx, maxy, mask);
|
|
// lenx is the number of columns we have, but drop 2 due to corners. we thus
|
|
// want lenx - 2 horizontal lines in a top or bottom border.
|
|
int y = 0;
|
|
if(y < maxy || direction == DIRECTION_DOWN || (mask & NCBOXMASK_BOTTOM)){
|
|
if(!(mask & NCBOXMASK_TOP)){
|
|
ncplane_home(n);
|
|
ncplane_putc(n, &ul);
|
|
ncplane_hline(n, &hl, lenx - 2);
|
|
ncplane_putc(n, &ur);
|
|
++y;
|
|
}
|
|
}
|
|
// draw the vertical sides, assuming they're not masked out. start wherever
|
|
// we're left following the previous stanza, end based on maxhorizy.
|
|
const bool candrawbottom = y <= maxy || direction == DIRECTION_UP || (mask & NCBOXMASK_TOP);
|
|
const int maxhorizy = maxy - (candrawbottom && !(mask & NCBOXMASK_BOTTOM));
|
|
int ret = 0;
|
|
while(y <= maxhorizy){
|
|
if(!(mask & NCBOXMASK_LEFT)){
|
|
ret |= ncplane_cursor_move_yx(n, y, 0);
|
|
ncplane_putc(n, &vl);
|
|
}
|
|
if(!(mask & NCBOXMASK_RIGHT)){
|
|
ret |= ncplane_cursor_move_yx(n, y, maxx);
|
|
ncplane_putc(n, &vl);
|
|
}
|
|
++y;
|
|
}
|
|
if(candrawbottom){
|
|
if(!(mask & NCBOXMASK_BOTTOM)){
|
|
ret |= ncplane_cursor_move_yx(n, maxy, 0);
|
|
ncplane_putc(n, &ll);
|
|
ncplane_hline(n, &hl, lenx - 2);
|
|
ncplane_putc(n, &lr);
|
|
}
|
|
}
|
|
nccell_release(n, &ul); nccell_release(n, &ur); nccell_release(n, &hl);
|
|
nccell_release(n, &ll); nccell_release(n, &lr); nccell_release(n, &vl);
|
|
return ret;
|
|
}
|
|
|
|
// Draws the border (if one should be drawn) around the ncreel, and enforces
|
|
// any provided restrictions on visible window size.
|
|
static int
|
|
draw_ncreel_borders(const ncreel* nr){
|
|
return draw_borders(nr->p, nr->ropts.bordermask, nr->ropts.borderchan,
|
|
DIRECTION_UP); // direction shouldn't matter for reel
|
|
}
|
|
|
|
// Calculate the starting and ending coordinates available for occupation by
|
|
// the tablet, relative to the ncreel's ncplane. Returns non-zero if the
|
|
// tablet cannot be made visible as specified. If this is the focused tablet
|
|
// (nr->tablets == t), it can take the entire reel -- frontiery is only a
|
|
// suggestion in this case -- so give it the full breadth.
|
|
static int
|
|
tablet_geom(const ncreel* nr, nctablet* t, int* begx, int* begy,
|
|
int* lenx, int* leny, int frontiertop, int frontierbottom,
|
|
direction_e direction){
|
|
//fprintf(stderr, "jigsawing %p with %d/%d dir %d\n", t, frontiertop, frontierbottom, direction);
|
|
*begy = 0;
|
|
*begx = 0;
|
|
unsigned uleny, ulenx;
|
|
ncplane_dim_yx(nr->p, &uleny, &ulenx);
|
|
*leny = uleny;
|
|
*lenx = ulenx;
|
|
if(frontiertop < 0){
|
|
if(direction == DIRECTION_UP){
|
|
return -1;
|
|
}
|
|
frontiertop = 0;
|
|
}
|
|
if(frontierbottom >= *leny){
|
|
if(direction == DIRECTION_DOWN){
|
|
return -1;
|
|
}
|
|
frontierbottom = *leny - 1;
|
|
}
|
|
// account for the ncreel borders on the sides
|
|
if(!(nr->ropts.bordermask & NCBOXMASK_LEFT)){
|
|
++*begx;
|
|
--*lenx;
|
|
}
|
|
if(!(nr->ropts.bordermask & NCBOXMASK_RIGHT)){
|
|
--*lenx;
|
|
}
|
|
if(!(nr->ropts.bordermask & NCBOXMASK_TOP)){
|
|
++*begy;
|
|
--*leny;
|
|
}
|
|
if(!(nr->ropts.bordermask & NCBOXMASK_BOTTOM)){
|
|
--*leny;
|
|
}
|
|
// at this point, our coordinates describe the largest possible tablet for
|
|
// this ncreel. this is the correct solution for the focused tablet. other
|
|
// tablets can only grow in one of two directions, so tighten them up.
|
|
if(nr->tablets != t){
|
|
*leny -= (frontierbottom - (frontiertop + 1));
|
|
if(direction == DIRECTION_DOWN){
|
|
*begy = frontierbottom;
|
|
}else{
|
|
*begy = frontiertop - *leny;
|
|
}
|
|
}
|
|
if(*leny <= 0 || *lenx <= 0){
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// kill the planes associated with |t|
|
|
static void
|
|
nctablet_wipeout(nctablet* t){
|
|
if(t){
|
|
if(ncplane_set_widget(t->p, NULL, NULL) == 0){
|
|
ncplane_destroy_family(t->p);
|
|
}
|
|
t->p = NULL;
|
|
t->cbp = NULL;
|
|
}
|
|
}
|
|
|
|
// destroy all existing tablet planes pursuant to redraw
|
|
static void
|
|
clean_reel(ncreel* r){
|
|
nctablet* vft = r->vft;
|
|
if(vft){
|
|
for(nctablet* n = vft->next ; n->p && n != vft ; n = n->next){
|
|
//fprintf(stderr, "CLEANING NEXT: %p (%p)\n", n, n->p);
|
|
nctablet_wipeout(n);
|
|
}
|
|
for(nctablet* n = vft->prev ; n->p && n != vft ; n = n->prev){
|
|
//fprintf(stderr, "CLEANING PREV: %p (%p)\n", n, n->p);
|
|
nctablet_wipeout(n);
|
|
}
|
|
//fprintf(stderr, "CLEANING VFT: %p (%p)\n", vft, vft->p);
|
|
nctablet_wipeout(vft);
|
|
r->vft = NULL;
|
|
}
|
|
}
|
|
|
|
// used as ncplane widget destructor callback, and likewise the heart of
|
|
// ncreel_del(). without this, at context collapse time we'd wipe out all the
|
|
// planes (without wiping out their subsidiaries), and ncreel_destroy() would
|
|
// walk freed memory. as we have no handle on the ncreel, we do not update its
|
|
// metadata (tabletcount), nor redraw, but who cares? we're shutting down.
|
|
static void
|
|
nctablet_delete_internal(struct nctablet* t){
|
|
t->prev->next = t->next;
|
|
t->next->prev = t->prev;
|
|
if(t->p){
|
|
if(ncplane_set_widget(t->p, NULL, NULL) == 0){
|
|
ncplane_destroy_family(t->p);
|
|
}
|
|
}
|
|
free(t);
|
|
}
|
|
|
|
int ncreel_del(ncreel* nr, struct nctablet* t){
|
|
if(t == NULL){
|
|
return -1;
|
|
}
|
|
if(nr->tablets == t){
|
|
if((nr->tablets = t->next) == t){
|
|
nr->tablets = NULL;
|
|
}
|
|
// FIXME ought set direction based on actual location of replacement tablet
|
|
nr->direction = LASTDIRECTION_DOWN;
|
|
}
|
|
if(nr->vft == t){
|
|
clean_reel(nr); // maybe just make nr->tablets the vft?
|
|
}
|
|
nctablet_delete_internal(t);
|
|
--nr->tabletcount;
|
|
ncreel_redraw(nr);
|
|
return 0;
|
|
}
|
|
|
|
// Draw the specified tablet, if possible. DIRECTION_UP means we're laying out
|
|
// bottom-to-top. DIRECTION_DOWN means top-to-bottom. 'frontier{top, bottom}'
|
|
// are the lines to which we'll be fitting the tablet ('frontiertop' to our
|
|
// last row for DIRECTION_UP, and 'frontierbottom' to our first row for
|
|
// DIRECTION_DOWN). Gives the tablet all possible space to work with (i.e.
|
|
// everything beyond the frontiers, or the entire reel for the focused tablet).
|
|
// If the callback uses less space, shrinks the plane to that size.
|
|
static int
|
|
ncreel_draw_tablet(const ncreel* nr, nctablet* t, int frontiertop,
|
|
int frontierbottom, direction_e direction){
|
|
if(t->p || t->cbp){
|
|
//fprintf(stderr, "already drew %p: %p %p\n", t, t->p, t->cbp);
|
|
return -1;
|
|
}
|
|
int lenx, leny, begy, begx;
|
|
if(tablet_geom(nr, t, &begx, &begy, &lenx, &leny, frontiertop, frontierbottom, direction)){
|
|
//fprintf(stderr, "no room: %p base %d/%d len %d/%d dir %d\n", t, begy, begx, leny, lenx, direction);
|
|
return -1;
|
|
}
|
|
//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);
|
|
struct ncplane_options nopts = {
|
|
.y = begy,
|
|
.x = begx,
|
|
.rows = leny,
|
|
.cols = lenx,
|
|
.name = "tab",
|
|
};
|
|
ncplane* fp = ncplane_create(nr->p, &nopts);
|
|
if((t->p = fp) == NULL){
|
|
//fprintf(stderr, "failure creating border plane %d %d %d %d\n", leny, lenx, begy, begx);
|
|
return -1;
|
|
}
|
|
ncplane_set_widget(t->p, t, (void(*)(void*))nctablet_delete_internal);
|
|
// we allow the callback to use a bound plane that lives above our border
|
|
// plane, thus preventing the callback from spilling over the tablet border.
|
|
int cby = 0, cbx = 0, cbleny = leny, cblenx = lenx;
|
|
//cbleny -= !(nr->ropts.tabletmask & NCBOXMASK_BOTTOM);
|
|
// FIXME shouldn't this be instead "drop 1 if either is unmasked"?
|
|
if(!(nr->ropts.tabletmask & NCBOXMASK_TOP)){
|
|
--cbleny;
|
|
++cby;
|
|
}
|
|
cblenx -= !(nr->ropts.tabletmask & NCBOXMASK_RIGHT);
|
|
if(!(nr->ropts.tabletmask & NCBOXMASK_LEFT)){
|
|
--cblenx;
|
|
++cbx;
|
|
}
|
|
if(cbleny - cby + 1 > 0){
|
|
//fprintf(stderr, "cbp placement %dx%d @ %dx%d\n", cbleny, cblenx, cby, cbx);
|
|
struct ncplane_options dnopts = {
|
|
.y = cby,
|
|
.x = cbx,
|
|
.rows = cbleny,
|
|
.cols = cblenx,
|
|
.name = "tdat",
|
|
};
|
|
t->cbp = ncplane_create(t->p, &dnopts);
|
|
if(t->cbp == NULL){
|
|
//fprintf(stderr, "failure creating data plane %d %d %d %d\n", cbleny, cblenx, cby, cbx);
|
|
ncplane_destroy(t->p);
|
|
t->p = NULL;
|
|
return -1;
|
|
}
|
|
ncplane_move_above(t->cbp, t->p);
|
|
//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);
|
|
int ll = t->cbfxn(t, direction == DIRECTION_DOWN);
|
|
//fprintf(stderr, "RETURNRETURNRETURN %p %d (%d, %d, %d) DIR %d\n", t, ll, cby, cbleny, leny, direction);
|
|
if(ll > cbleny){
|
|
logwarn("tablet callback returned %d lines, %d allowed", ll, cbleny);
|
|
ll = cbleny;
|
|
}
|
|
if(ll != cbleny){
|
|
int diff = cbleny - ll;
|
|
//fprintf(stderr, "resizing data plane %d->%d\n", cbleny, ll);
|
|
if(ll){ // must be smaller than the space we provided; add back bottom
|
|
ncplane_resize_simple(t->cbp, ll, cblenx);
|
|
}else{
|
|
ncplane_destroy_family(t->cbp);
|
|
t->cbp = NULL;
|
|
}
|
|
// resize the borderplane iff we got smaller
|
|
if(!(nr->ropts.tabletmask & NCBOXMASK_BOTTOM)){
|
|
ncplane_resize_simple(t->p, leny - diff + 1, lenx);
|
|
}else{
|
|
ncplane_resize_simple(t->p, leny - diff, lenx);
|
|
}
|
|
// We needn't move the resized plane if drawing down, or the focused plane.
|
|
// The focused tablet will have been resized properly above, but it might
|
|
// be out of position (the focused tablet ought move as little as possible).
|
|
// Move it back to the frontier, or the nearest line above if it has grown.
|
|
if(nr->tablets == t){
|
|
if(leny - frontiertop + 1 < ll){
|
|
ncplane_yx(fp, &frontiertop, NULL);
|
|
frontiertop += (leny - ll);
|
|
}
|
|
ncplane_move_yx(fp, frontiertop, begx);
|
|
//fprintf(stderr, "moved to frontiertop %d\n", frontiertop);
|
|
}else if(direction == DIRECTION_UP){
|
|
ncplane_move_yx(fp, begy + diff, begx);
|
|
//fprintf(stderr, "MOVEDOWN from %d to %d\n", begy, begy + diff);
|
|
}
|
|
cbleny = ll;
|
|
}
|
|
}
|
|
// we can't push the border plane beyond its true boundaries, or we'll mess
|
|
// up layout later. instead, add a bottommask iff leny <= cbleny + 1
|
|
unsigned mask = nr->ropts.tabletmask;
|
|
if(leny <= cbleny + !(mask & NCBOXMASK_TOP)){
|
|
mask |= NCBOXMASK_BOTTOM;
|
|
}
|
|
uint64_t channels = nr->tablets == t ? nr->ropts.focusedchan : nr->ropts.tabletchan;
|
|
draw_borders(fp, mask, channels, direction);
|
|
return 0;
|
|
}
|
|
|
|
// move down below the focused tablet, filling up the reel to the bottom.
|
|
// returns the last tablet drawn.
|
|
static nctablet*
|
|
draw_following_tablets(const ncreel* nr, nctablet* otherend,
|
|
int frontiertop, int* frontierbottom){
|
|
const bool botborder = !(nr->ropts.bordermask & NCBOXMASK_BOTTOM);
|
|
//fprintf(stderr, "following otherend: %p ->p: %p %d/%d\n", otherend, otherend->p, frontiertop, *frontierbottom);
|
|
nctablet* working = nr->tablets->next;
|
|
const int maxx = ncplane_dim_y(nr->p) - 1 - botborder;
|
|
// move down past the focused tablet, filling up the reel to the bottom
|
|
while(*frontierbottom <= maxx && (working != otherend || !otherend->p)){
|
|
if(working->p){
|
|
break;
|
|
}
|
|
//fprintf(stderr, "following otherend: %p ->p: %p\n", otherend, otherend->p);
|
|
// modify frontier based off the one we're at
|
|
//fprintf(stderr, "EASTBOUND AND DOWN: %p->%p %d %d\n", working, working->next, frontiertop, *frontierbottom);
|
|
if(ncreel_draw_tablet(nr, working, frontiertop, *frontierbottom, DIRECTION_DOWN)){
|
|
return NULL;
|
|
}
|
|
if(working == otherend){
|
|
otherend = otherend->next;
|
|
}
|
|
*frontierbottom += ncplane_dim_y(working->p) + 1;
|
|
working = working->next;
|
|
}
|
|
//fprintf(stderr, "done with prevs: %p->%p %d %d\n", working, working->prev, frontiertop, *frontierbottom);
|
|
return working;
|
|
}
|
|
|
|
// move up above the focused tablet, filling up the reel to the top.
|
|
// returns the last tablet drawn.
|
|
static nctablet*
|
|
draw_previous_tablets(const ncreel* nr, nctablet* otherend,
|
|
int* frontiertop, int frontierbottom){
|
|
const bool topborder = !(nr->ropts.bordermask & NCBOXMASK_TOP);
|
|
nctablet* upworking = nr->tablets->prev;
|
|
//fprintf(stderr, "preceding %p otherend: %p ->p: %p frontiers: %d %d\n", upworking, otherend, otherend->p, *frontiertop, frontierbottom);
|
|
// modify frontier based off the one we're at
|
|
while(*frontiertop >= topborder && (upworking != otherend || !otherend->p)){
|
|
if(upworking->p){
|
|
break;
|
|
}
|
|
//fprintf(stderr, "MOVIN' ON UP: %p->%p %d %d\n", upworking, upworking->prev, *frontiertop, frontierbottom);
|
|
if(ncreel_draw_tablet(nr, upworking, *frontiertop, frontierbottom, DIRECTION_UP)){
|
|
return NULL;
|
|
}
|
|
if(upworking == otherend){
|
|
otherend = otherend->prev;
|
|
}
|
|
*frontiertop -= ncplane_dim_y(upworking->p) + 1;
|
|
upworking = upworking->prev;
|
|
}
|
|
//fprintf(stderr, "done with prevs: %p->%p %d %d\n", upworking, upworking->prev, *frontiertop, frontierbottom);
|
|
return upworking;
|
|
}
|
|
|
|
// Tablets are initially drawn assuming more space to be available than may
|
|
// actually exist. We do a pass at the end trimming any overhang.
|
|
static int
|
|
trim_reel_overhang(ncreel* r, nctablet* top, nctablet* bottom){
|
|
int y;
|
|
if(!top || !top->p || !bottom || !bottom->p){
|
|
return -1;
|
|
}
|
|
//fprintf(stderr, "trimming: top %p bottom %p\n", top->p, bottom->p);
|
|
ncplane_yx(top->p, &y, NULL);
|
|
unsigned ylen, xlen;
|
|
ncplane_dim_yx(top->p, &ylen, &xlen);
|
|
const int miny = !(r->ropts.bordermask & NCBOXMASK_TOP);
|
|
int boty = y + ylen - 1;
|
|
//fprintf(stderr, "top: %dx%d @ %d, miny: %d\n", ylen, xlen, y, miny);
|
|
if(boty < miny){
|
|
//fprintf(stderr, "NUKING top!\n");
|
|
ncplane_destroy_family(top->p);
|
|
top->p = NULL;
|
|
top->cbp = NULL;
|
|
top = top->next;
|
|
return trim_reel_overhang(r, top, bottom);
|
|
}else if(y < miny){
|
|
int ynew = ylen - (miny - y);
|
|
if(ynew <= 0){
|
|
ncplane_destroy_family(top->p);
|
|
top->p = NULL;
|
|
top->cbp = NULL;
|
|
}else{
|
|
if(ncplane_resize(top->p, miny - y, 0, ynew, xlen, 0, 0, ynew, xlen)){
|
|
return -1;
|
|
}
|
|
if(top->cbp){
|
|
if(ynew == !(r->ropts.tabletmask & NCBOXMASK_TOP)){
|
|
ncplane_destroy_family(top->cbp);
|
|
top->cbp = NULL;
|
|
}else{
|
|
ncplane_dim_yx(top->cbp, &ylen, &xlen);
|
|
ynew -= !(r->ropts.tabletmask & NCBOXMASK_TOP);
|
|
if(ncplane_resize(top->cbp, miny - y, 0, ynew, xlen, 0, 0, ynew, xlen)){
|
|
return -1;
|
|
}
|
|
int x;
|
|
ncplane_yx(top->cbp, &y, &x);
|
|
ncplane_move_yx(top->cbp, y - 1, x);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if(bottom->p){
|
|
ncplane_dim_yx(bottom->p, &ylen, &xlen);
|
|
ncplane_yx(bottom->p, &y, NULL);
|
|
const int maxy = ncplane_dim_y(r->p) - (1 + !(r->ropts.bordermask & NCBOXMASK_BOTTOM));
|
|
boty = y + ylen - 1;
|
|
//fprintf(stderr, "bot: %dx%d @ %d, maxy: %d\n", ylen, xlen, y, maxy);
|
|
if(maxy < y){
|
|
//fprintf(stderr, "NUKING bottom!\n");
|
|
if(ncplane_set_widget(bottom->p, NULL, NULL) == 0){
|
|
ncplane_destroy_family(bottom->p);
|
|
}
|
|
bottom->p = NULL;
|
|
bottom->cbp = NULL;
|
|
bottom = bottom->prev;
|
|
return trim_reel_overhang(r, top, bottom);
|
|
}if(maxy < boty){
|
|
int ynew = ylen - (boty - maxy);
|
|
if(ynew <= 0){
|
|
ncplane_destroy_family(bottom->p);
|
|
bottom->p = NULL;
|
|
bottom->cbp = NULL;
|
|
}else{
|
|
if(ncplane_resize(bottom->p, 0, 0, ynew, xlen, 0, 0, ynew, xlen)){
|
|
return -1;
|
|
}
|
|
//fprintf(stderr, "TRIMMED bottom %p from %d to %d (%d)\n", bottom->p, ylen, ynew, maxy - boty);
|
|
if(bottom->cbp){
|
|
if(ynew == !(r->ropts.tabletmask & NCBOXMASK_BOTTOM)){
|
|
ncplane_destroy_family(bottom->cbp);
|
|
bottom->cbp = NULL;
|
|
}else{
|
|
ncplane_dim_yx(bottom->cbp, &ylen, &xlen);
|
|
ynew -= !(r->ropts.tabletmask & NCBOXMASK_BOTTOM);
|
|
if(ncplane_resize(bottom->cbp, 0, 0, ynew, xlen, 0, 0, ynew, xlen)){
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//fprintf(stderr, "finished trimming\n");
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
tighten_reel_down(ncreel* r, int ybot){
|
|
nctablet* cur = r->tablets;
|
|
while(cur){
|
|
if(cur->p == NULL){
|
|
break;
|
|
}
|
|
int cury, curx;
|
|
unsigned ylen;
|
|
ncplane_yx(cur->p, &cury, &curx);
|
|
ncplane_dim_yx(cur->p, &ylen, NULL);
|
|
if(cury <= ybot - (int)ylen - 1){
|
|
break;
|
|
}
|
|
//fprintf(stderr, "tightening %p down to %d from %d\n", cur, ybot - ylen, cury);
|
|
cury = ybot - (int)ylen;
|
|
ncplane_move_yx(cur->p, cury, curx);
|
|
ybot = cury - 1;
|
|
if((cur = cur->prev) == r->tablets){
|
|
break;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// run at the end of redraw, this aligns the top tablet with the top of the
|
|
// reel. we prefer empty space at the bottom (FIXME but not really -- we ought
|
|
// prefer space away from the last direction of movement. rather than this
|
|
// postprocessing, draw things to the right places!). we then trim any tablet
|
|
// overhang. FIXME could pass top/bottom in directly, available as otherend
|
|
static int
|
|
tighten_reel(ncreel* r){
|
|
//fprintf(stderr, "tightening it up\n");
|
|
nctablet* top = r->tablets;
|
|
nctablet* cur = top;
|
|
int ytop = INT_MAX;
|
|
// find the top tablet
|
|
while(cur){
|
|
if(cur->p == NULL){
|
|
break;
|
|
}
|
|
int cury;
|
|
ncplane_yx(cur->p, &cury, NULL);
|
|
if(cury >= ytop){
|
|
break;
|
|
}
|
|
ytop = cury;
|
|
top = cur;
|
|
cur = cur->prev;
|
|
}
|
|
int expected = !(r->ropts.bordermask & NCBOXMASK_TOP);
|
|
cur = top;
|
|
nctablet* bottom = r->tablets;
|
|
// find the bottom tablet, moving tablets up as we go along
|
|
while(cur){
|
|
if(cur->p == NULL){
|
|
break;
|
|
}
|
|
int cury, curx;
|
|
ncplane_yx(cur->p, &cury, &curx);
|
|
if(cury != expected){
|
|
if(ncplane_move_yx(cur->p, expected, curx)){
|
|
//fprintf(stderr, "tightened %p up to %d\n", cur, expected);
|
|
return -1;
|
|
}
|
|
}
|
|
unsigned ylen;
|
|
ncplane_dim_yx(cur->p, &ylen, NULL);
|
|
expected += ylen + 1;
|
|
//fprintf(stderr, "bottom (%p) gets %p\n", bottom, cur);
|
|
bottom = cur;
|
|
cur = cur->next;
|
|
if(cur == top){
|
|
break;
|
|
}
|
|
}
|
|
cur = r->tablets;
|
|
if(cur){
|
|
const ncplane* n = cur->p;
|
|
int yoff;
|
|
unsigned ylen, rylen;
|
|
ncplane_dim_yx(r->p, &rylen, NULL);
|
|
ncplane_yx(n, &yoff, NULL);
|
|
ncplane_dim_yx(n, &ylen, NULL);
|
|
const int ybot = rylen - 1 + !!(r->ropts.bordermask & NCBOXMASK_BOTTOM);
|
|
// FIXME want to tighten down whenever we're at the bottom, and the reel
|
|
// is full, not just in this case (this can leave a gap of more than 1 row)
|
|
if(yoff + (int)ylen + 1 >= ybot){
|
|
if(tighten_reel_down(r, ybot)){
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
if(!top || !bottom){
|
|
return 0;
|
|
}
|
|
return trim_reel_overhang(r, top, bottom);
|
|
}
|
|
|
|
// Arrange the panels, starting with the focused window, wherever it may be.
|
|
// If necessary, resize it to the full size of the reel--focus has its
|
|
// privileges. We then work in the opposite direction of travel, filling out
|
|
// the reel above and below. If we moved down to get here, do the tablets above
|
|
// first. If we moved up, do the tablets below. This ensures tablets stay in
|
|
// place relative to the new focus; they could otherwise pivot around the new
|
|
// focus, if we're not filling out the reel.
|
|
int ncreel_redraw(ncreel* nr){
|
|
//fprintf(stderr, "\n--------> BEGIN REDRAW <--------\n");
|
|
nctablet* focused = nr->tablets;
|
|
int fulcrum; // target line
|
|
if(nr->direction == LASTDIRECTION_UP){
|
|
// 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.
|
|
if(!focused || !focused->p || !nr->vft){
|
|
fulcrum = 0;
|
|
//fprintf(stderr, "case i fulcrum %d\n", fulcrum);
|
|
}else{
|
|
int focy, vfty;
|
|
ncplane_yx(focused->p, &focy, NULL);
|
|
ncplane_yx(nr->vft->p, &vfty, NULL);
|
|
if(focy > vfty){
|
|
fulcrum = 0;
|
|
//fprintf(stderr, "case i fulcrum %d (%d %d) %p %p\n", fulcrum, focy, vfty, focused, nr->vft);
|
|
}else{
|
|
ncplane_yx(focused->p, &fulcrum, NULL); // case iii
|
|
//fprintf(stderr, "case iii fulcrum %d (%d %d) %p %p lastdir: %d\n", fulcrum, focy, vfty, focused, nr->vft, nr->direction);
|
|
}
|
|
}
|
|
}else{
|
|
// 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.
|
|
if(!focused || !focused->p || !nr->vft){
|
|
fulcrum = ncplane_dim_y(nr->p) - 1;
|
|
//fprintf(stderr, "case ii fulcrum %d\n", fulcrum);
|
|
}else{
|
|
int focy, vfty;
|
|
//fprintf(stderr, "focused: %p\n", focused);
|
|
ncplane_yx(focused->p, &focy, NULL);
|
|
ncplane_yx(nr->vft->p, &vfty, NULL);
|
|
if(focy < vfty){
|
|
fulcrum = ncplane_dim_y(nr->p) - 1;
|
|
//fprintf(stderr, "case ii fulcrum %d (%d %d) %p %p\n", fulcrum, focy, vfty, focused, nr->vft);
|
|
}else{
|
|
ncplane_yx(focused->p, &fulcrum, NULL); // case iii
|
|
//fprintf(stderr, "case iiib fulcrum %d (%d %d) %p %p lastdir: %d\n", fulcrum, focy, vfty, focused, nr->vft, nr->direction);
|
|
}
|
|
}
|
|
}
|
|
clean_reel(nr);
|
|
if(focused){
|
|
//fprintf(stderr, "drawing focused tablet %p dir: %d fulcrum: %d!\n", focused, nr->direction, fulcrum);
|
|
if(ncreel_draw_tablet(nr, focused, fulcrum, fulcrum, DIRECTION_DOWN)){
|
|
logerror("error drawing tablet");
|
|
return -1;
|
|
}
|
|
//fprintf(stderr, "drew focused tablet %p -> %p lastdir: %d!\n", focused, focused->p, nr->direction);
|
|
nctablet* otherend = focused;
|
|
int frontiertop, frontierbottom;
|
|
ncplane_yx(nr->tablets->p, &frontiertop, NULL);
|
|
frontierbottom = frontiertop + ncplane_dim_y(nr->tablets->p) + 1;
|
|
frontiertop -= 2;
|
|
if(nr->direction == LASTDIRECTION_DOWN){
|
|
otherend = draw_previous_tablets(nr, otherend, &frontiertop, frontierbottom);
|
|
if(otherend == NULL){
|
|
logerror("error drawing higher tablets");
|
|
return -1;
|
|
}
|
|
otherend = draw_following_tablets(nr, otherend, frontiertop, &frontierbottom);
|
|
}else{ // DIRECTION_UP
|
|
otherend = draw_previous_tablets(nr, otherend, &frontiertop, frontierbottom);
|
|
if(otherend == NULL){
|
|
logerror("error drawing higher tablets");
|
|
return -1;
|
|
}
|
|
otherend = draw_following_tablets(nr, otherend, frontiertop, &frontierbottom);
|
|
}
|
|
if(otherend == NULL){
|
|
logerror("error drawing following tablets");
|
|
return -1;
|
|
}
|
|
//notcurses_debug(ncplane_notcurses(nr->p), stderr);
|
|
if(tighten_reel(nr)){
|
|
logerror("error tightening reel");
|
|
return -1;
|
|
}
|
|
//notcurses_debug(ncplane_notcurses(nr->p), stderr);
|
|
}
|
|
nr->vft = nr->tablets; // update the visually-focused tablet pointer
|
|
//fprintf(stderr, "DONE ARRANGING\n");
|
|
if(draw_ncreel_borders(nr)){
|
|
logerror("error drawing reel borders");
|
|
return -1; // enforces specified dimensional minima
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static bool
|
|
validate_ncreel_opts(ncplane* n, const ncreel_options* ropts){
|
|
if(n == NULL){
|
|
return false;
|
|
}
|
|
if(ropts->flags >= (NCREEL_OPTION_CIRCULAR << 1u)){
|
|
logwarn("provided unsupported flags 0x%016" PRIx64, ropts->flags);
|
|
}
|
|
if(ropts->flags & NCREEL_OPTION_CIRCULAR){
|
|
if(!(ropts->flags & NCREEL_OPTION_INFINITESCROLL)){
|
|
logerror("can't set circular without infinitescroll");
|
|
return false; // can't set circular without infinitescroll
|
|
}
|
|
}
|
|
// there exist higher NCBOX defines, but they're not valid in this context
|
|
const unsigned fullmask = NCBOXMASK_LEFT |
|
|
NCBOXMASK_RIGHT |
|
|
NCBOXMASK_TOP |
|
|
NCBOXMASK_BOTTOM;
|
|
if(ropts->bordermask > fullmask){
|
|
logerror("bad bordermask: 0x%016x", ropts->bordermask);
|
|
return false;
|
|
}
|
|
if(ropts->tabletmask > fullmask){
|
|
logerror("bad tabletmask: 0x%016x", ropts->bordermask);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
ncplane* nctablet_plane(nctablet* t){
|
|
return t->cbp;
|
|
}
|
|
|
|
ncplane* ncreel_plane(ncreel* nr){
|
|
return nr->p;
|
|
}
|
|
|
|
ncreel* ncreel_create(ncplane* n, const ncreel_options* ropts){
|
|
ncreel_options zeroed = {0};
|
|
ncreel* nr;
|
|
if(!ropts){
|
|
ropts = &zeroed;
|
|
}
|
|
if(!validate_ncreel_opts(n, ropts)){
|
|
return NULL;
|
|
}
|
|
if((nr = malloc(sizeof(*nr))) == NULL){
|
|
return NULL;
|
|
}
|
|
nr->tablets = NULL;
|
|
nr->tabletcount = 0;
|
|
nr->direction = LASTDIRECTION_DOWN; // draw down after the initial tablet
|
|
memcpy(&nr->ropts, ropts, sizeof(*ropts));
|
|
nr->p = n;
|
|
nr->vft = NULL;
|
|
if(ncplane_set_widget(nr->p, nr, (void(*)(void*))ncreel_destroy)){
|
|
ncplane_destroy(nr->p);
|
|
free(nr);
|
|
return NULL;
|
|
}
|
|
if(ncreel_redraw(nr)){
|
|
ncplane_destroy(nr->p);
|
|
free(nr);
|
|
return NULL;
|
|
}
|
|
return nr;
|
|
}
|
|
|
|
nctablet* ncreel_add(ncreel* nr, nctablet* after, nctablet *before,
|
|
tabletcb cbfxn, void* opaque){
|
|
nctablet* t;
|
|
if(after && before){
|
|
if(after->next != before || before->prev != after){
|
|
logerror("bad before (%p) / after (%p) spec", before, after);
|
|
return NULL;
|
|
}
|
|
}else if(!after && !before){
|
|
// This way, without user interaction or any specification, new tablets are
|
|
// inserted at the "end" relative to the focus. The first one to be added
|
|
// gets and keeps the focus. New ones will go on the bottom, until we run
|
|
// out of space. New tablets are then created off-screen.
|
|
before = nr->tablets;
|
|
}
|
|
if((t = malloc(sizeof(*t))) == NULL){
|
|
return NULL;
|
|
}
|
|
//fprintf(stderr, "--------->NEW TABLET %p\n", t);
|
|
if(after){
|
|
t->next = after->next;
|
|
after->next = t;
|
|
t->prev = after;
|
|
t->next->prev = t;
|
|
}else if(before){
|
|
t->prev = before->prev;
|
|
before->prev = t;
|
|
t->next = before;
|
|
t->prev->next = t;
|
|
}else{ // we're the first tablet
|
|
t->prev = t->next = t;
|
|
nr->tablets = t;
|
|
}
|
|
t->cbfxn = cbfxn;
|
|
t->curry = opaque;
|
|
++nr->tabletcount;
|
|
t->p = NULL;
|
|
t->cbp = NULL;
|
|
ncreel_redraw(nr);
|
|
return t;
|
|
}
|
|
|
|
void ncreel_destroy(ncreel* nreel){
|
|
if(nreel){
|
|
if(ncplane_set_widget(nreel->p, NULL, NULL) == 0){
|
|
nctablet* t;
|
|
while( (t = nreel->tablets) ){
|
|
ncreel_del(nreel, t);
|
|
}
|
|
ncplane_destroy(nreel->p);
|
|
}
|
|
free(nreel);
|
|
}
|
|
}
|
|
|
|
void* nctablet_userptr(nctablet* t){
|
|
return t->curry;
|
|
}
|
|
|
|
int ncreel_tabletcount(const ncreel* nreel){
|
|
return nreel->tabletcount;
|
|
}
|
|
|
|
nctablet* ncreel_focused(ncreel* nr){
|
|
return nr->tablets;
|
|
}
|
|
|
|
nctablet* ncreel_next(ncreel* nr){
|
|
if(nr->tablets){
|
|
nr->tablets = nr->tablets->next;
|
|
//fprintf(stderr, "---------------> moved to next, %p to %p <----------\n", nr->tablets->prev, nr->tablets);
|
|
nr->direction = LASTDIRECTION_DOWN;
|
|
ncreel_redraw(nr);
|
|
}
|
|
return nr->tablets;
|
|
}
|
|
|
|
nctablet* ncreel_prev(ncreel* nr){
|
|
if(nr->tablets){
|
|
nr->tablets = nr->tablets->prev;
|
|
//fprintf(stderr, "----------------> moved to prev, %p to %p <----------\n", nr->tablets->next, nr->tablets);
|
|
nr->direction = LASTDIRECTION_UP;
|
|
ncreel_redraw(nr);
|
|
}
|
|
return nr->tablets;
|
|
}
|
|
|
|
bool ncreel_offer_input(ncreel* n, const ncinput* nc){
|
|
if(nc->evtype == NCTYPE_RELEASE){
|
|
return false;
|
|
}
|
|
if(nc->id == NCKEY_UP){
|
|
ncreel_prev(n);
|
|
return true;
|
|
}else if(nc->id == NCKEY_DOWN){
|
|
ncreel_next(n);
|
|
return true;
|
|
}else if(nc->id == NCKEY_SCROLL_UP){
|
|
ncreel_prev(n);
|
|
return true;
|
|
}else if(nc->id == NCKEY_SCROLL_DOWN){
|
|
ncreel_next(n);
|
|
return true;
|
|
}
|
|
// FIXME page up/page down
|
|
// FIXME end/home (when not using infinite scrolling)
|
|
return false;
|
|
}
|