diff --git a/CMakeLists.txt b/CMakeLists.txt index dc38c5c55..9804c03a5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.14) +cmake_minimum_required(VERSION 3.11.4) project(notcurses VERSION 1.6.11 DESCRIPTION "UI for modern terminal emulators" HOMEPAGE_URL "https://nick-black.com/dankwiki/index.php/notcurses" diff --git a/NEWS.md b/NEWS.md index 32c06f3cd..168d9fe55 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,11 +2,23 @@ This document attempts to list user-visible changes and any major internal rearrangements of Notcurses. * 1.6.12 (not yet released) + * `ncreel`s `tabletcb` callback function semantics are radically simplified. + No more worrying about borders that might or might not have been drawn; + simply fill up the plane that you're handed. This eliminates four of the + seven arguments to these callbacks. I hope the inconvenience of adapting + them is worth the elimination of complexity therein; I obviously think + it is =]. * `ncselector_redraw()` and `ncmultiselector_redraw()` no longer call `notcurses_render()`. You will need to call `notcurses_render()` for the display to reflect any changes. `ncselector_create` now binds the plane it creates to the plane it was provided, and no longer checks to ensure the widget can be fit within the borders of this binding plane. + * Added `ncplane_new_named()`, `ncplane_bound_named()`, and + `ncplane_aligned_named()`. These would be the defaults, but I didn't want + to break existing code. They might become the defaults by 2.0. Names are + used only for debugging (`notcurses_debug()`) at this time. + * Added `ncplane_parent()` and `ncplane_parent_const()` for accessing the + plane to which a plane is bound. * 1.6.11 (2020-08-03) * `cell_egc_idx()` is no longer exported; it was never intended to be. diff --git a/README.md b/README.md index 07262fe2e..ca16336a1 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,7 @@ that fine library. ## Requirements * (build) A C11 and a C++17 compiler -* (build) CMake 3.14.0+ +* (build) CMake 3.11.4+ * (build+runtime) From NCURSES: terminfo 6.1+ * (build+runtime) GNU libunistring 0.9.10+ * (OPTIONAL) (build+runtime) From QR-Code-generator: [libqrcodegen](https://github.com/nayuki/QR-Code-generator) 1.5.0+ diff --git a/USAGE.md b/USAGE.md index ac0645545..c3478bd82 100644 --- a/USAGE.md +++ b/USAGE.md @@ -536,7 +536,7 @@ typedef struct ncinput { // be NULL. Returns 0 on a timeout. If an event is processed, the return value // is the 'id' field from that event. 'ni' may be NULL. char32_t notcurses_getc(struct notcurses* n, const struct timespec* ts, - sigset_t* sigmask, ncinput* ni); + sigset_t* sigmask, ncinput* ni); // 'ni' may be NULL if the caller is uninterested in event details. If no event // is ready, returns 0. @@ -617,9 +617,10 @@ quickly reset the `ncplane`, use `ncplane_erase()`. struct ncplane* ncplane_new(struct notcurses* nc, int rows, int cols, int yoff, int xoff, void* opaque); -// Create a new ncplane aligned relative to 'n'. -struct ncplane* ncplane_aligned(struct ncplane* n, int rows, int cols, - int yoff, ncalign_e align, void* opaque); +// Create a named plane ala ncplane_new(). Names are only used for debugging. +struct ncplane* ncplane_new_named(struct notcurses* nc, int rows, int cols, + int yoff, int xoff, void* opaque, + const char* name); // Create a plane bound to plane 'n'. Being bound to 'n' means that 'yoff' and // 'xoff' are interpreted relative to that plane's origin, and that if that @@ -627,10 +628,28 @@ struct ncplane* ncplane_aligned(struct ncplane* n, int rows, int cols, struct ncplane* ncplane_bound(struct ncplane* n, int rows, int cols, int yoff, int xoff, void* opaque); +// Create a named plane ala ncplane_bound(). Names are used only for debugging. +struct ncplane* ncplane_bound_named(struct ncplane* n, int rows, int cols, + int yoff, int xoff, void* opaque, + const char* name); + +// Create a plane bound to 'n', and aligned relative to it using 'align'. +struct ncplane* ncplane_aligned(struct ncplane* n, int rows, int cols, + int yoff, ncalign_e align, void* opaque); + +// Create a named plane ala ncplane_aligned(). Names are used only for debugging. +struct ncplane* ncplane_aligned_named(struct ncplane* n, int rows, int cols, + int yoff, ncalign_e align, + void* opaque, const char* name); + // Plane 'n' will be unbound from its parent plane, if it is currently bound, // and will be made a bound child of 'newparent', if 'newparent' is not NULL. struct ncplane* ncplane_reparent(struct ncplane* n, struct ncplane* newparent); +// Get the plane to which the plane 'n' is bound, if any. +struct ncplane* ncplane_parent(struct ncplane* n); +const struct ncplane* ncplane_parent_const(const struct ncplane* n); + // Duplicate an existing ncplane. The new plane will have the same geometry, // will duplicate all content, and will start with the same rendering state. struct ncplane* ncplane_dup(struct ncplane* n, void* opaque); @@ -699,7 +718,7 @@ It is an error to invoke these functions on the standard plane. // Essentially, the kept material does not move. It serves to anchor the // resized plane. If there is no kept material, the plane can move freely. int ncplane_resize(struct ncplane* n, int keepy, int keepx, int keepleny, - int keeplenx, int yoff, int xoff, int ylen, int xlen); + int keeplenx, int yoff, int xoff, int ylen, int xlen); // Resize the plane, retaining what data we can (everything, unless we're // shrinking in some dimension). Keep the origin where it is. @@ -1038,10 +1057,10 @@ ncplane_putwstr(struct ncplane* n, const wchar_t* gclustarr){ // The ncplane equivalents of printf(3) and vprintf(3). int ncplane_vprintf_aligned(struct ncplane* n, int y, ncalign_e align, - const char* format, va_list ap); + const char* format, va_list ap); int ncplane_vprintf_yx(struct ncplane* n, int y, int x, - const char* format, va_list ap); + const char* format, va_list ap); static inline int ncplane_vprintf(struct ncplane* n, const char* format, va_list ap){ @@ -1119,7 +1138,7 @@ on both sides. Boxes allow fairly detailed specification of how they're drawn. // number of cells drawn on success. On error, return the negative number of // cells drawn. int ncplane_hline_interp(struct ncplane* n, const cell* c, int len, - uint64_t c1, uint64_t c2); + uint64_t c1, uint64_t c2); static inline int ncplane_hline(struct ncplane* n, const cell* c, int len){ @@ -1127,7 +1146,7 @@ ncplane_hline(struct ncplane* n, const cell* c, int len){ } int ncplane_vline_interp(struct ncplane* n, const cell* c, int len, - uint64_t c1, uint64_t c2); + uint64_t c1, uint64_t c2); static inline int ncplane_vline(struct ncplane* n, const cell* c, int len){ @@ -1165,9 +1184,8 @@ ncplane_vline(struct ncplane* n, const cell* c, int len){ #define NCBOXCORNER_SHIFT 8u int ncplane_box(struct ncplane* n, const cell* ul, const cell* ur, - const cell* ll, const cell* lr, const cell* hline, - const cell* vline, int ystop, int xstop, - unsigned ctlword); + const cell* ll, const cell* lr, const cell* hline, + const cell* vline, int ystop, int xstop, unsigned ctlword); // Draw a box with its upper-left corner at the current cursor position, having // dimensions 'ylen'x'xlen'. See ncplane_box() for more information. The @@ -1323,13 +1341,13 @@ typedef int (*fadecb)(struct notcurses* nc, struct ncplane* ncp, // modification (if the terminal uses a palette, our ability to fade planes is // limited, and affected by the complexity of the rest of the screen). int ncplane_fadeout(struct ncplane* n, const struct timespec* ts, - fadecb fader, void* curry); + fadecb fader, void* curry); // Fade the ncplane in over the specified time. Load the ncplane with the // target cells without rendering, then call this function. When it's done, the // ncplane will have reached the target levels, starting from zeroes. int ncplane_fadein(struct ncplane* n, const struct timespec* ts, - fadecb fader, void* curry); + fadecb fader, void* curry); // Rather than the simple ncplane_fade{in/out}(), ncfadectx_setup() can be // Pulse the plane in and out until the callback returns non-zero, relying on @@ -1382,8 +1400,6 @@ int ncblit_bgrx(const void* data, int linesize, const struct ncvisual_options* vopts); ``` - - ### Plane channels API Helpers are provided to manipulate an `ncplane`'s `channels` member. They are diff --git a/doc/man/man1/notcurses-demo.1.md b/doc/man/man1/notcurses-demo.1.md index 272ce094e..85c186115 100644 --- a/doc/man/man1/notcurses-demo.1.md +++ b/doc/man/man1/notcurses-demo.1.md @@ -55,7 +55,7 @@ At any time, press 'q' to quit. The demo is best run in at least an 80x45 termin **-d delaymult**: Apply a non-negative rational multiplier to the standard delay of 1s. -**-l loglevel**: Log everything (high log level) or nothing (log level 0) to stderr. +**-l loglevel**: Log everything (log level 8) or nothing (log level 0) to stderr. **-f renderfile**: Render each frame to **renderfile** in addition to the screen. diff --git a/doc/man/man1/notcurses-ncreel.1.md b/doc/man/man1/notcurses-ncreel.1.md index 4497c111a..d3e3a33f8 100644 --- a/doc/man/man1/notcurses-ncreel.1.md +++ b/doc/man/man1/notcurses-ncreel.1.md @@ -8,7 +8,7 @@ notcurses-ncreel - Experiment with ncreels # SYNOPSIS -**notcurses-ncreel** [**-t tabletbordermask**] [**-b bordermask**] [**-ob bottomoffset**] [**-ot topoffset**] [**-ol leftoffset**] [**-or rightoffset**] +**notcurses-ncreel** [**-t tabletbordermask**] [**-b bordermask**] [**-ob bottomoffset**] [**-ot topoffset**] [**-ol leftoffset**] [**-or rightoffset**] [**-ln**] # DESCRIPTION @@ -18,6 +18,8 @@ tablet (if one exists). 'q' quits at any time. # OPTIONS +**-l loglevel**: Log everything (log level 8) or nothing (log level 0) to stderr. + # NOTES Optimal display requires a terminal advertising the **rgb** terminfo(5) diff --git a/doc/man/man3/notcurses_plane.3.md b/doc/man/man3/notcurses_plane.3.md index 4ecc4ded3..ee8888596 100644 --- a/doc/man/man3/notcurses_plane.3.md +++ b/doc/man/man3/notcurses_plane.3.md @@ -12,12 +12,18 @@ notcurses_plane - operations on ncplanes **struct ncplane* ncplane_new(struct notcurses* nc, int rows, int cols, int yoff, int xoff, void* opaque);** +**struct ncplane* ncplane_new_named(struct notcurses* nc, int rows, int cols, int yoff, int xoff, void* opaque, const char* name);** + **struct ncplane* ncplane_bound(struct ncplane* n, int rows, int cols, int yoff, int xoff, void* opaque);** -**struct ncplane* ncplane_reparent(struct ncplane* n, struct ncplane* newparent);** +**struct ncplane* ncplane_bound_named(struct ncplane* n, int rows, int cols, int yoff, int xoff, void* opaque, const char* name);** **struct ncplane* ncplane_aligned(struct ncplane* n, int rows, int cols, int yoff, ncalign_e align, void* opaque);** +**struct ncplane* ncplane_aligned_named(struct ncplane* n, int rows, int cols, int yoff, ncalign_e align, void* opaque, const char* name);** + +**struct ncplane* ncplane_reparent(struct ncplane* n, struct ncplane* newparent);** + **struct ncplane* ncplane_dup(struct ncplane* n, void* opaque);** **int ncplane_resize(struct ncplane* n, int keepy, int keepx, int keepleny, int keeplenx, int yoff, int xoff, int ylen, int xlen);** @@ -26,6 +32,10 @@ notcurses_plane - operations on ncplanes **void ncplane_yx(const struct ncplane* n, int* restrict y, int* restrict x);** +**struct ncplane* ncplane_parent(struct ncplane* n);** + +**const struct ncplane* ncplane_parent_const(const struct ncplane* n);** + **int ncplane_set_base_cell(struct ncplane* ncp, const cell* c);** **int ncplane_set_base(struct ncplane* ncp, const char* egc, uint32_t attrword, uint64_t channels);** diff --git a/include/notcurses/notcurses.h b/include/notcurses/notcurses.h index 8bbf322de..e706aee3f 100644 --- a/include/notcurses/notcurses.h +++ b/include/notcurses/notcurses.h @@ -984,8 +984,10 @@ API char* notcurses_at_yx(struct notcurses* nc, int yoff, int xoff, API struct ncplane* ncplane_new(struct notcurses* nc, int rows, int cols, int yoff, int xoff, void* opaque); -API struct ncplane* ncplane_aligned(struct ncplane* n, int rows, int cols, - int yoff, ncalign_e align, void* opaque); +// Create a named plane ala ncplane_new(). Names are only used for debugging. +API struct ncplane* ncplane_new_named(struct notcurses* nc, int rows, int cols, + int yoff, int xoff, void* opaque, + const char* name); // Create a plane bound to plane 'n'. Being bound to 'n' means that 'yoff' and // 'xoff' are interpreted relative to that plane's origin, and that if that @@ -993,6 +995,20 @@ API struct ncplane* ncplane_aligned(struct ncplane* n, int rows, int cols, API struct ncplane* ncplane_bound(struct ncplane* n, int rows, int cols, int yoff, int xoff, void* opaque); +// Create a named plane ala ncplane_bound(). Names are used only for debugging. +API struct ncplane* ncplane_bound_named(struct ncplane* n, int rows, int cols, + int yoff, int xoff, void* opaque, + const char* name); + +// Create a plane bound to 'n', and aligned relative to it using 'align'. +API struct ncplane* ncplane_aligned(struct ncplane* n, int rows, int cols, + int yoff, ncalign_e align, void* opaque); + +// Create a named plane ala ncplane_aligned(). Names are used only for debugging. +API struct ncplane* ncplane_aligned_named(struct ncplane* n, int rows, int cols, + int yoff, ncalign_e align, + void* opaque, const char* name); + // Plane 'n' will be unbound from its parent plane, if it is currently bound, // and will be made a bound child of 'newparent', if 'newparent' is not NULL. API struct ncplane* ncplane_reparent(struct ncplane* n, struct ncplane* newparent); @@ -1141,6 +1157,10 @@ API int ncplane_move_yx(struct ncplane* n, int y, int x); // which it is bound (if it is bound to a plane). API void ncplane_yx(const struct ncplane* n, int* RESTRICT y, int* RESTRICT x); +// Get the plane to which the plane 'n' is bound, if any. +API struct ncplane* ncplane_parent(struct ncplane* n); +API const struct ncplane* ncplane_parent_const(const struct ncplane* n); + // Splice ncplane 'n' out of the z-buffer, and reinsert it at the top or bottom. API void ncplane_move_top(struct ncplane* n); API void ncplane_move_bottom(struct ncplane* n); @@ -2463,22 +2483,10 @@ API struct ncreel* ncreel_create(struct ncplane* nc, const ncreel_options* popts API struct ncplane* ncreel_plane(struct ncreel* pr); // Tablet draw callback, provided a tablet (from which the ncplane and userptr -// may be extracted), the first column that may be used, the first row that may -// be used, the first column that may not be used, the first row that may not -// be used, and a bool indicating whether output ought be clipped at the top -// (true) or bottom (false). Rows and columns are zero-indexed, and both are -// relative to the tablet's plane. -// -// Regarding clipping: it is possible that the tablet is only partially -// displayed on the screen. If so, it is either partially present on the top of -// the screen, or partially present at the bottom. In the former case, the top -// is clipped (cliptop will be true), and output ought start from the end. In -// the latter case, cliptop is false, and output ought start from the beginning. -// -// Returns the number of lines of output, which ought be less than or equal to -// maxy - begy, and non-negative (negative values might be used in the future). -typedef int (*tabletcb)(struct nctablet* t, int begx, int begy, int maxx, - int maxy, bool cliptop); +// may be extracted), and a bool indicating whether output ought be drawn from +// the top (true) or bottom (false). Returns non-negative count of output lines, +// which must be less than or equal to ncplane_dim_y(nctablet_plane(t)). +typedef int (*tabletcb)(struct nctablet* t, bool drawfromtop); // Add a new nctablet to the provided ncreel, having the callback object // opaque. Neither, either, or both of after and before may be specified. If diff --git a/python/src/notcurses/build_notcurses.py b/python/src/notcurses/build_notcurses.py index e763d7f4e..f6a61d2aa 100644 --- a/python/src/notcurses/build_notcurses.py +++ b/python/src/notcurses/build_notcurses.py @@ -96,15 +96,18 @@ int ncplane_base(struct ncplane* ncp, cell* c); struct ncplane* notcurses_top(struct notcurses* n); void notcurses_drop_planes(struct notcurses* nc); int notcurses_refresh(struct notcurses* n, int* restrict y, int* restrict x); -struct ncplane* ncplane_new(struct notcurses* nc, int rows, int cols, int yoff, int xoff, void* opaque); -struct ncplane* ncplane_bound(struct ncplane* n, int rows, int cols, int yoff, int xoff, void* opaque); struct ncplane* ncplane_reparent(struct ncplane* n, struct ncplane* newparent); typedef enum { NCALIGN_LEFT, NCALIGN_CENTER, NCALIGN_RIGHT, } ncalign_e; +struct ncplane* ncplane_new(struct notcurses* nc, int rows, int cols, int yoff, int xoff, void* opaque); +struct ncplane* ncplane_new_named(struct notcurses* nc, int rows, int cols, int yoff, int xoff, void* opaque, const char* name); +struct ncplane* ncplane_bound(struct ncplane* n, int rows, int cols, int yoff, int xoff, void* opaque); +struct ncplane* ncplane_bound_named(struct ncplane* n, int rows, int cols, int yoff, int xoff, void* opaque, const char* name); struct ncplane* ncplane_aligned(struct ncplane* n, int rows, int cols, int yoff, ncalign_e align, void* opaque); +struct ncplane* ncplane_aligned_named(struct ncplane* n, int rows, int cols, int yoff, ncalign_e align, void* opaque, const char* name); unsigned notcurses_supported_styles(const struct notcurses* nc); int notcurses_palette_size(const struct notcurses* nc); bool notcurses_cantruecolor(const struct notcurses* nc); @@ -510,6 +513,8 @@ int ncdirect_double_box(struct ncdirect* n, uint64_t ul, uint64_t ur, uint64_t l bool ncdirect_canopen_images(const struct ncdirect* n); bool ncdirect_canutf8(const struct ncdirect* n); nc_err_e ncdirect_render_image(struct ncdirect* n, const char* filename, ncalign_e align, ncblitter_e blitter, ncscale_e scale); +struct ncplane* ncplane_parent(struct ncplane* n); +const struct ncplane* ncplane_parent_const(const struct ncplane* n); """) if __name__ == "__main__": diff --git a/src/demo/reel.c b/src/demo/reel.c index b62f71c0f..d130cfd70 100644 --- a/src/demo/reel.c +++ b/src/demo/reel.c @@ -64,25 +64,25 @@ kill_active_tablet(struct ncreel* pr, tabletctx** tctx){ // partially off-screen), but also leave unused space at the end (since // wresize() only keeps the top and left on a shrink). static int -tabletup(struct ncplane* w, int begx, int begy, int maxx, int maxy, - tabletctx* tctx, int rgb){ +tabletup(struct ncplane* w, int maxy, tabletctx* tctx, int rgb){ char cchbuf[2]; cell c = CELL_TRIVIAL_INITIALIZER; int y, idx; idx = tctx->lines; - if(maxy - begy > tctx->lines){ - maxy -= (maxy - begy - tctx->lines); + int maxx = ncplane_dim_x(w) - 1; + if(maxy > tctx->lines){ + maxy = tctx->lines; } /*fprintf(stderr, "-OFFSET BY %d (%d->%d)\n", maxy - begy - tctx->lines, maxy, maxy - (maxy - begy - tctx->lines));*/ - for(y = maxy ; y >= begy ; --y, rgb += 16){ + for(y = maxy ; y >= 0 ; --y, rgb += 16){ snprintf(cchbuf, sizeof(cchbuf) / sizeof(*cchbuf), "%x", idx % 16); cell_load(w, &c, cchbuf); if(cell_set_fg_rgb(&c, (rgb >> 16u) % 0xffu, (rgb >> 8u) % 0xffu, rgb % 0xffu)){ return -1; } int x; - for(x = begx ; x <= maxx ; ++x){ + for(x = 0 ; x <= maxx ; ++x){ if(ncplane_putc_yx(w, y, x, &c) <= 0){ return -1; } @@ -97,57 +97,58 @@ tabletup(struct ncplane* w, int begx, int begy, int maxx, int maxy, } static int -tabletdown(struct ncplane* w, int begx, int begy, int maxx, int maxy, - tabletctx* tctx, unsigned rgb){ +tabletdown(struct ncplane* w, int maxy, tabletctx* tctx, unsigned rgb){ char cchbuf[2]; cell c = CELL_TRIVIAL_INITIALIZER; int y; - for(y = begy ; y <= maxy ; ++y, rgb += 16){ - if(y - begy >= tctx->lines){ - break; - } + int maxx = ncplane_dim_x(w) - 1; + if(maxy > tctx->lines){ + maxy = tctx->lines; + } + for(y = 0 ; y <= maxy ; ++y, rgb += 16){ snprintf(cchbuf, sizeof(cchbuf) / sizeof(*cchbuf), "%x", y % 16); cell_load(w, &c, cchbuf); if(cell_set_fg_rgb(&c, (rgb >> 16u) % 0xffu, (rgb >> 8u) % 0xffu, rgb % 0xffu)){ return -1; } int x; - for(x = begx ; x <= maxx ; ++x){ + for(x = 0 ; x <= maxx ; ++x){ if(ncplane_putc_yx(w, y, x, &c) <= 0){ return -1; } } cell_release(w, &c); } - return y - begy; + return y; } static int -tabletdraw(struct nctablet* t, int begx, int begy, int maxx, int maxy, bool cliptop){ +tabletdraw(struct nctablet* t, bool cliptop){ struct ncplane* p = nctablet_ncplane(t); tabletctx* tctx = nctablet_userptr(t); pthread_mutex_lock(&tctx->lock); unsigned rgb = tctx->rgb; int ll; + int maxy = ncplane_dim_y(p); if(cliptop){ - ll = tabletup(p, begx, begy, maxx, maxy, tctx, rgb); + ll = tabletup(p, maxy, tctx, rgb); }else{ - ll = tabletdown(p, begx, begy, maxx, maxy, tctx, rgb); + ll = tabletdown(p, maxy, tctx, rgb); } ncplane_set_fg_rgb(p, 242, 242, 242); if(ll){ - int summaryy = begy; + int summaryy = 0; if(cliptop){ - if(ll == maxy - begy + 1){ + if(ll == maxy + 1){ summaryy = ll - 1; }else{ summaryy = ll; } } ncplane_styles_on(p, NCSTYLE_BOLD); - if(ncplane_printf_yx(p, summaryy, begx, "[#%u %d line%s %u/%u] ", + if(ncplane_printf_yx(p, summaryy, 0, "[#%u %d line%s %u available] ", tctx->id, tctx->lines, tctx->lines == 1 ? "" : "s", - begy, maxy) < 0){ + maxy) < 0){ pthread_mutex_unlock(&tctx->lock); return -1; } @@ -184,7 +185,10 @@ tablet_thread(void* vtabletctx){ pthread_mutex_lock(&renderlock); if(nctablet_ncplane(tctx->t)){ ncreel_redraw(tctx->pr); - demo_render(ncplane_notcurses(nctablet_ncplane(tctx->t))); + struct ncplane* tplane = nctablet_ncplane(tctx->t); + if(tplane){ + demo_render(ncplane_notcurses(tplane)); + } } pthread_mutex_unlock(&renderlock); } diff --git a/src/lib/debug.c b/src/lib/debug.c index 83b4d60f5..e5e379ea3 100644 --- a/src/lib/debug.c +++ b/src/lib/debug.c @@ -6,11 +6,11 @@ void notcurses_debug(notcurses* nc, FILE* debugfp){ int planeidx = 0; fprintf(debugfp, "*************************** notcurses debug state *****************************\n"); while(n){ - fprintf(debugfp, "%04d off y: %3d x: %3d geom y: %3d x: %3d curs y: %3d x: %3d %06llx %.8s\n", + fprintf(debugfp, "%04d off y: %3d x: %3d geom y: %3d x: %3d curs y: %3d x: %3d %p %.8s\n", planeidx, n->absy, n->absx, n->leny, n->lenx, n->y, n->x, - (uintptr_t)n % 0x100000000ull, n->name ? n->name : ""); + n, n->name ? n->name : ""); if(n->boundto || n->bnext || n->bprev || n->blist){ - fprintf(debugfp, " bound %p -> %p <- %p binds %p\n", + fprintf(debugfp, " bound %p → %p ← %p binds %p\n", n->boundto, n->bnext, n->bprev, n->blist); } if(n->bnext == n || n->boundto == n || n->blist == n){ diff --git a/src/lib/internal.h b/src/lib/internal.h index 2942bae17..009e10b0f 100644 --- a/src/lib/internal.h +++ b/src/lib/internal.h @@ -117,6 +117,32 @@ typedef struct renderstate { bool defaultelidable; } renderstate; +// Tablets are the toplevel entitites within an ncreel. Each corresponds to +// a single, distinct ncplane. +typedef struct nctablet { + ncplane* p; // border plane, NULL when offscreen + ncplane* cbp; // data plane, NULL when offscreen + struct nctablet* next; + struct nctablet* prev; + tabletcb cbfxn; // application callback to draw cbp + void* curry; // application data provided to cbfxn +} nctablet; + +typedef struct ncreel { + ncplane* p; // ncplane this ncreel occupies, under tablets + // 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). it will be visibly focused following the next redraw. + nctablet* tablets; + nctablet* vft; // the visibly-focused tablet + enum { + LASTDIRECTION_UP, + LASTDIRECTION_DOWN, + } direction; // last direction of travel + int tabletcount; // could be derived, but we keep it o(1) + ncreel_options ropts; // copied in ncreel_create() +} ncreel; + // ncmenu_item and ncmenu_section have internal and (minimal) external forms typedef struct ncmenu_int_item { char* desc; // utf-8 menu item, NULL for horizontal separator @@ -795,6 +821,9 @@ cell_nobackground_p(const cell* c){ return c->channels & CELL_NOBACKGROUND_MASK; } +// Destroy a plane and all its bound descendants. +int ncplane_genocide(ncplane *ncp); + #ifdef __cplusplus } #endif diff --git a/src/lib/notcurses.c b/src/lib/notcurses.c index 2001d1657..34ca2581a 100644 --- a/src/lib/notcurses.c +++ b/src/lib/notcurses.c @@ -297,11 +297,13 @@ void free_plane(ncplane* p){ ncplane* ncplane_create(notcurses* nc, ncplane* n, int rows, int cols, int yoff, int xoff, void* opaque, const char* name){ if(rows <= 0 || cols <= 0){ + logerror(nc, "Won't create denormalized plane (r=%d, c=%d)\n", rows, cols); return NULL; } ncplane* p = malloc(sizeof(*p)); size_t fbsize = sizeof(*p->fb) * (rows * cols); if((p->fb = malloc(fbsize)) == NULL){ + logerror(nc, "Error allocating cellmatrix (r=%d, c=%d)\n", rows, cols); free(p); return NULL; } @@ -346,6 +348,7 @@ ncplane* ncplane_create(notcurses* nc, ncplane* n, int rows, int cols, }else{ p->below = NULL; } + loginfo(nc, "Created new %dx%d plane @ %dx%d\n", rows, cols, yoff, xoff); return p; } @@ -371,16 +374,32 @@ ncplane* ncplane_new(notcurses* nc, int rows, int cols, int yoff, int xoff, void return ncplane_create(nc, NULL, rows, cols, yoff, xoff, opaque, NULL); } +ncplane* ncplane_new_named(notcurses* nc, int rows, int cols, int yoff, + int xoff, void* opaque, const char* name){ + return ncplane_create(nc, NULL, rows, cols, yoff, xoff, opaque, name); +} + ncplane* ncplane_bound(ncplane* n, int rows, int cols, int yoff, int xoff, void* opaque){ return ncplane_create(n->nc, n, rows, cols, yoff, xoff, opaque, NULL); } +ncplane* ncplane_bound_named(ncplane* n, int rows, int cols, int yoff, int xoff, + void* opaque, const char* name){ + return ncplane_create(n->nc, n, rows, cols, yoff, xoff, opaque, name); +} + ncplane* ncplane_aligned(ncplane* n, int rows, int cols, int yoff, ncalign_e align, void* opaque){ return ncplane_create(n->nc, n, rows, cols, yoff, ncplane_align(n, align, cols), opaque, NULL); } +ncplane* ncplane_aligned_named(ncplane* n, int rows, int cols, int yoff, + ncalign_e align, void* opaque, const char* name){ + return ncplane_create(n->nc, n, rows, cols, yoff, + ncplane_align(n, align, cols), opaque, name); +} + void ncplane_home(ncplane* n){ n->x = 0; n->y = 0; @@ -583,13 +602,33 @@ int ncplane_destroy(ncplane* ncp){ ncp->bnext->bprev = ncp->bprev; } } - if(ncp->blist){ - // FIXME need unlink all on list - ncp->blist->bprev = NULL; - ncp->blist->bnext = NULL; + int ret = 0; + struct ncplane* bound = ncp->blist; + while(bound){ + struct ncplane* tmp = bound->bnext; + if(ncplane_reparent(bound, ncp->boundto) == NULL){ + ret = -1; + } + bound = tmp; } free_plane(ncp); - return 0; + return ret; +} + +int ncplane_genocide(ncplane *ncp){ + if(ncp == NULL){ + return 0; + } + if(ncp->nc->stdplane == ncp){ + logerror(ncp->nc, "Won't destroy standard plane\n"); + return -1; + } + int ret = 0; + while(ncp->blist){ + ret |= ncplane_genocide(ncp->blist); + } + ret |= ncplane_destroy(ncp); + return ret; } static int @@ -2178,6 +2217,14 @@ const notcurses* ncplane_notcurses_const(const ncplane* n){ return n->nc; } +ncplane* ncplane_parent(ncplane* n){ + return n->boundto; +} + +const ncplane* ncplane_parent_const(const ncplane* n){ + return n->boundto; +} + ncplane* ncplane_reparent(ncplane* n, ncplane* newparent){ if(n == n->nc->stdplane){ return NULL; // can't reparent standard plane diff --git a/src/lib/reel.c b/src/lib/reel.c index f6fd60302..d44c2e7dc 100644 --- a/src/lib/reel.c +++ b/src/lib/reel.c @@ -5,20 +5,10 @@ #include #include "internal.h" -// Tablets are the toplevel entitites within an ncreel. Each corresponds to -// a single, distinct ncplane. -typedef struct nctablet { - ncplane* p; // visible panel, NULL when offscreen - struct nctablet* next; - struct nctablet* prev; - tabletcb cbfxn; // application callback to draw tablet - void* curry; // application data provided to cbfxn -} nctablet; - typedef enum { DIRECTION_UP, DIRECTION_DOWN, -} direction_e; +} direction_e; // current direction of travel // A UNIFIED THEORY OF NCREELS // (which are more complex than they may seem) @@ -44,8 +34,9 @@ typedef enum { // 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: the focused tablet should remain where it is across redraws, -// except as necessary to accommodate the prior rules. +// 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: // @@ -77,7 +68,7 @@ typedef enum { // // 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. +// 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. @@ -121,17 +112,6 @@ typedef enum { // * walk the list in the direction against travel, foc->focw // * if focw == backstop, we're done // * draw through edge -typedef struct ncreel { - ncplane* p; // ncplane this ncreel occupies, under tablets - // 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). it will be visibly focused following the next redraw. - nctablet* tablets; - 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 // tablet, and its length. End is (begx + lenx - 1, begy + leny - 1). @@ -142,8 +122,7 @@ tablet_coordinates(ncplane* w, int* begy, int* begx, int* leny, int* lenx){ } static int -draw_borders(ncplane* w, unsigned mask, uint64_t channel, - bool cliphead, bool clipfoot){ +draw_borders(ncplane* w, unsigned mask, uint64_t channel){ int lenx, leny; int ret = 0; ncplane_dim_yx(w, &leny, &lenx); @@ -155,30 +134,26 @@ draw_borders(ncplane* w, unsigned mask, uint64_t channel, if(cells_rounded_box(w, 0, channel, &ul, &ur, &ll, &lr, &hl, &vl)){ return -1; } -/*fprintf(stderr, "drawing borders %p ->%d/%d, mask: %04x, clipping: %c%c\n", - w, maxx, maxy, mask, - cliphead ? 'T' : 't', clipfoot ? 'F' : 'f');*/ - if(!cliphead){ - // lenx is the number of columns we have, but drop 2 due to - // corners. we thus want lenx horizontal lines. - if(!(mask & NCBOXMASK_TOP)){ +//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 horizontal lines. + if(!(mask & NCBOXMASK_TOP)){ + ncplane_home(w); + ncplane_putc(w, &ul); + ncplane_hline(w, &hl, lenx - 2); + ncplane_putc(w, &ur); + }else{ + if(!(mask & NCBOXMASK_LEFT)){ ncplane_home(w); ncplane_putc(w, &ul); - ncplane_hline(w, &hl, lenx - 2); + } + if(!(mask & NCBOXMASK_RIGHT)){ + ncplane_cursor_move_yx(w, 0, lenx - 1); ncplane_putc(w, &ur); - }else{ - if(!(mask & NCBOXMASK_LEFT)){ - ncplane_home(w); - ncplane_putc(w, &ul); - } - if(!(mask & NCBOXMASK_RIGHT)){ - ncplane_cursor_move_yx(w, 0, lenx - 1); - ncplane_putc(w, &ur); - } } } int y; - for(y = !cliphead ; y < maxy + !!clipfoot ; ++y){ + for(y = 1 ; y < maxy ; ++y){ if(!(mask & NCBOXMASK_LEFT)){ ret |= ncplane_cursor_move_yx(w, y, 0); ncplane_putc(w, &vl); @@ -188,32 +163,28 @@ draw_borders(ncplane* w, unsigned mask, uint64_t channel, ncplane_putc(w, &vl); } } - if(!clipfoot){ - if(!(mask & NCBOXMASK_BOTTOM)){ - ret |= ncplane_cursor_move_yx(w, maxy, 0); - ncplane_putc(w, &ll); - ncplane_hline(w, &hl, lenx - 2); - ncplane_putc(w, &lr); - }else{ - if(!(mask & NCBOXMASK_LEFT)){ - if(ncplane_cursor_move_yx(w, maxy, 0) || ncplane_putc(w, &ll) < 0){ - ret = -1; - } + if(!(mask & NCBOXMASK_BOTTOM)){ + ret |= ncplane_cursor_move_yx(w, maxy, 0); + ncplane_putc(w, &ll); + ncplane_hline(w, &hl, lenx - 2); + ncplane_putc(w, &lr); + }else{ + if(!(mask & NCBOXMASK_LEFT)){ + if(ncplane_cursor_move_yx(w, maxy, 0) || ncplane_putc(w, &ll) < 0){ + ret = -1; } - if(!(mask & NCBOXMASK_RIGHT)){ - // mvwadd_wch returns error if we print to the lowermost+rightmost - // character cell. maybe we can make this go away with scrolling controls - // at setup? until then, don't check for error here FIXME. - if(ncplane_cursor_move_yx(w, maxy, maxx) || ncplane_putc(w, &lr) < 0){ - ret = -1; - } + } + if(!(mask & NCBOXMASK_RIGHT)){ + // mvwadd_wch returns error if we print to the lowermost+rightmost + // character cell. maybe we can make this go away with scrolling controls + // at setup? until then, don't check for error here FIXME. + if(ncplane_cursor_move_yx(w, maxy, maxx) || ncplane_putc(w, &lr) < 0){ + ret = -1; } } } cell_release(w, &ul); cell_release(w, &ur); cell_release(w, &hl); cell_release(w, &ll); cell_release(w, &lr); cell_release(w, &vl); -// fprintf(stderr, "||--borders %d %d clip: %c%c ret: %d\n", -// maxx, maxy, cliphead ? 'y' : 'n', clipfoot ? 'y' : 'n', ret); return ret; } @@ -226,7 +197,7 @@ draw_ncreel_borders(const ncreel* nr){ assert(maxy >= 0 && maxx >= 0); --maxx; // last column we can safely write to --maxy; // last line we can safely write to - return draw_borders(nr->p, nr->ropts.bordermask, nr->ropts.borderchan, false, false); + return draw_borders(nr->p, nr->ropts.bordermask, nr->ropts.borderchan); } // Calculate the starting and ending coordinates available for occupation by @@ -235,32 +206,26 @@ draw_ncreel_borders(const ncreel* nr){ // (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_columns(const ncreel* nr, nctablet* t, int* begx, int* begy, - int* lenx, int* leny, int frontiery, direction_e direction){ +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; ncplane_dim_yx(nr->p, leny, lenx); - int maxy = *leny + *begy - 1; - int begindraw = *begy + !(nr->ropts.bordermask & NCBOXMASK_TOP); - int enddraw = maxy - !(nr->ropts.bordermask & NCBOXMASK_TOP); - if(direction == DIRECTION_UP || nr->tablets == t){ - if(frontiery < begindraw){ + if(frontiertop < 0){ + if(direction == DIRECTION_UP){ return -1; } - }else{ - if(frontiery > enddraw){ - // fprintf(stderr, "FRONTIER: %d ENDDRAW: %d\n", frontiery, enddraw); + frontiertop = 0; + } + if(frontierbottom >= *leny){ + if(direction == DIRECTION_DOWN){ return -1; } + frontierbottom = *leny - 1; } - // account for the ncreel borders - if((direction == DIRECTION_UP || nr->tablets == t) && !(nr->ropts.bordermask & NCBOXMASK_TOP)){ - ++*begy; - --*leny; - } - if((direction == DIRECTION_DOWN || nr->tablets == t) && !(nr->ropts.bordermask & NCBOXMASK_BOTTOM)){ - --*leny; - } + // account for the ncreel borders on the sides if(!(nr->ropts.bordermask & NCBOXMASK_LEFT)){ ++*begx; --*lenx; @@ -271,236 +236,235 @@ tablet_columns(const ncreel* nr, nctablet* t, int* begx, int* begy, // 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(direction == DIRECTION_DOWN && nr->tablets != t){ - *leny -= (frontiery - *begy); - *begy = frontiery; - }else if(direction == DIRECTION_UP && nr->tablets != t){ - *leny = frontiery - *begy + 1; + if(nr->tablets != t){ + *leny -= (frontierbottom - (frontiertop + 1)); + if(direction == DIRECTION_DOWN){ + *begy = frontierbottom; + }else{ + *begy = frontiertop - *leny; + } } return 0; } -// Draw the specified tablet, if possible. DIRECTION_UP means we're -// laying out towards the top. DIRECTION_DOWN means towards the bottom. 0 -// means this is the focused tablet, always the first one to be drawn. -// frontiery is the line on which we're placing the tablet (in the case of the -// focused window, this is only an ideal, subject to change). For direction -// greater than or equal to 0, it's the top line of the tablet. For direction -// less than 0, it's the bottom line. Gives the tablet all possible space to -// work with (i.e. up to the edge we're approaching, or the entire panel for -// the focused tablet). If the callback uses less space, shrinks the panel back -// down before displaying it. Destroys any panel if it ought be hidden. -// Returns 0 if the tablet was able to be wholly rendered, non-zero otherwise. +// 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 frontiery, int direction){ +ncreel_draw_tablet(const ncreel* nr, nctablet* t, int frontiertop, + int frontierbottom, direction_e direction){ + assert(!t->p); + 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; - ncplane* fp = t->p; - if(tablet_columns(nr, t, &begx, &begy, &lenx, &leny, frontiery, direction)){ -//fprintf(stderr, "no room: %p:%p base %d/%d len %d/%d dir %d\n", t, fp, begy, begx, leny, lenx, direction); -//fprintf(stderr, "FRONTIER DONE!!!!!!\n"); - if(fp){ -//fprintf(stderr, "HIDING %p at frontier %d (dir %d) with %d\n", t, frontiery, direction, leny); - ncplane_destroy(fp); - t->p = NULL; - } + 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, "tplacement: %p:%p base %d/%d len %d/%d\n", t, fp, begx, begy, lenx, leny); -//fprintf(stderr, "DRAWING %p at frontier %d (dir %d) with %d\n", t, frontiery, direction, leny); - if(fp == NULL){ // create a panel for the tablet - t->p = ncplane_bound(nr->p, leny + 1, lenx, begy, begx, NULL); - if((fp = t->p) == NULL){ - return -1; - } - }else{ - int trueby, truebx; - ncplane_yx(fp, &trueby, &truebx); - int truey, truex; - ncplane_dim_yx(fp, &truey, &truex); - if(truey != leny){ -//fprintf(stderr, "RESIZE TRUEY: %d BEGY: %d LENY: %d\n", truey, begy, leny); - if(ncplane_resize_simple(fp, leny, truex)){ - return -1; - } - truey = leny; - } -//fprintf(stderr, "begy: %d trueby: %d\n", begy, trueby); - if(begy != trueby){ - ncplane_move_yx(fp, begy, begx); - } - } - if(ncplane_resize_simple(fp, leny, lenx)){ +//fprintf(stderr, "tplacement: %p base %d/%d len %d/%d frontiery %d %d dir %d\n", t, begy, begx, leny, lenx, frontiertop, frontierbottom, direction); + ncplane* fp = ncplane_bound_named(nr->p, leny, lenx, begy, begx, NULL, "tab"); + if((t->p = fp) == NULL){ +//fprintf(stderr, "failure creating border plane %d %d %d %d\n", leny, lenx, begy, begx); return -1; } - bool cliphead = false; - bool clipfoot = false; - // We pass the coordinates in which the callback may freely write. That's - // the full width (minus tablet borders), and the full range of open space - // in the direction we're moving. We're not passing *lenghts* to the callback, - // but *coordinates* within the window--everywhere save tabletborders. - int cby = 0, cbx = 0, cbmaxy = leny, cbmaxx = lenx; - --cbmaxy; - --cbmaxx; - // If we're drawing up, we'll always have a bottom border unless it's masked - if((nr->tablets != t && direction == DIRECTION_UP) && !(nr->ropts.tabletmask & NCBOXMASK_BOTTOM)){ - --cbmaxy; + // 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; + if(!(nr->ropts.tabletmask & NCBOXMASK_BOTTOM)){ + --cbleny; } - // If we're drawing down, we'll always have a top border unless it's masked - if(direction == DIRECTION_DOWN && !(nr->ropts.tabletmask & NCBOXMASK_TOP)){ + if(!(nr->ropts.tabletmask & NCBOXMASK_TOP)){ + --cbleny; ++cby; } - // Adjust the x-bounds for side borders, which we always have if unmasked - cbmaxx -= !(nr->ropts.tabletmask & NCBOXMASK_RIGHT); - cbx += !(nr->ropts.tabletmask & NCBOXMASK_LEFT); - bool cbdir = (nr->tablets != t && direction == DIRECTION_UP) ? true : false; -// fprintf(stderr, "calling! lenx/leny: %d/%d cbx/cby: %d/%d cbmaxx/cbmaxy: %d/%d dir: %d\n", -// lenx, leny, cbx, cby, cbmaxx, cbmaxy, direction); - int ll = t->cbfxn(t, cbx, cby, cbmaxx, cbmaxy, cbdir); -//fprintf(stderr, "RETURNRETURNRETURN %p %d (%d, %d, %d) DIR %d\n", t, ll, cby, cbmaxy, leny, direction); - if(ll != leny){ - if(ll == leny - 1){ // only has one border visible (partially off-screen) - if(cbdir){ - ll += !(nr->ropts.tabletmask & NCBOXMASK_BOTTOM); - }else{ - ll += !(nr->ropts.tabletmask & NCBOXMASK_TOP); - } - ncplane_resize_simple(fp, ll, lenx); - if(direction == DIRECTION_UP && nr->tablets != t){ - cliphead = true; - ncplane_move_yx(fp, begy + leny - ll, begx); -//fprintf(stderr, "MOVEDOWN CLIPPED RESIZED (-1) from %d to %d\n", leny, ll); - }else{ - clipfoot = true; -//fprintf(stderr, "RESIZED (-1) from %d to %d\n", leny, ll); - } - }else if(ll < leny - 1){ // both borders are visible - ll += !(nr->ropts.tabletmask & NCBOXMASK_BOTTOM) + - !(nr->ropts.tabletmask & NCBOXMASK_TOP); -//fprintf(stderr, "RESIZING (-2) from %d to %d\n", leny, ll); - ncplane_resize_simple(fp, ll, lenx); - if(direction == DIRECTION_UP && nr->tablets != t){ -//fprintf(stderr, "MOVEDOWN UNCLIPPED (skip %d)\n", leny - ll); - ncplane_move_yx(fp, begy + leny - ll, begx); - } - }else{ - assert(ll == leny); // you fucked up! FIXME + cblenx -= !(nr->ropts.tabletmask & NCBOXMASK_RIGHT); + if(!(nr->ropts.tabletmask & NCBOXMASK_LEFT)){ + --cblenx; + ++cbx; + } + if(cbleny - cby + 1 > 0){ + t->cbp = ncplane_bound_named(t->p, cbleny - cby + 1, cblenx - cbx + 1, cby, cbx, NULL, "tdat"); + if(t->cbp == NULL){ +//fprintf(stderr, "failure creating data plane %d %d %d %d\n", cbleny - cby + 1, cblenx - cbx + 1, cby, cbx); + ncplane_destroy(t->p); + t->p = NULL; + return -1; } - // 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 - frontiery + 1 < ll){ -//fprintf(stderr, "frontieryIZING ADJ %d %d %d %d NEW %d\n", cbmaxy, leny, frontiery, ll, frontiery - ll + 1); - ncplane_yx(fp, &frontiery, NULL); - frontiery += (leny - ll); + 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 - cby + 1){ + int diff = (cbleny - cby + 1) - ll; +//fprintf(stderr, "resizing data plane %d->%d\n", cbleny - cby + 1, ll); + ncplane_resize_simple(t->cbp, ll, cblenx - cbx + 1); + 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); } -//fprintf(stderr, "moving to frontiery %d\n", frontiery); - ncplane_move_yx(fp, frontiery, begx); } } draw_borders(fp, nr->ropts.tabletmask, - nr->tablets == t ? nr->ropts.focusedchan : nr->ropts.tabletchan, - cliphead, clipfoot); - return cliphead || clipfoot; -} - -// draw and size the focused tablet, which must exist (nr->tablets may not be -// NULL). it can occupy the entire ncreel. -static int -draw_focused_tablet(const ncreel* nr){ - int plenx, pleny; // ncreel window coordinates - ncplane_dim_yx(nr->p, &pleny, &plenx); - int fulcrum; - if(nr->tablets->p == NULL){ - 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->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->direction == DIRECTION_DOWN){ - if(nr->tablets->prev->p){ - int prevfulcrum; - ncplane_yx(nr->tablets->prev->p, &prevfulcrum, NULL); - if(fulcrum < prevfulcrum){ - fulcrum = pleny + !(nr->ropts.bordermask & NCBOXMASK_BOTTOM); - } - } - }else if(nr->direction == DIRECTION_UP){ - if(nr->tablets->next->p){ - int nextfulcrum; - ncplane_yx(nr->tablets->next->p, &nextfulcrum, NULL); - if(fulcrum > nextfulcrum){ - fulcrum = !(nr->ropts.bordermask & NCBOXMASK_TOP); - } - } - } -//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); + nr->tablets == t ? nr->ropts.focusedchan : nr->ropts.tabletchan); + 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, const nctablet* otherend){ - int wmaxy, wbegy, wbegx, wlenx, wleny; // working tablet window coordinates - nctablet* working = nr->tablets; - int frontiery; +draw_following_tablets(const ncreel* nr, nctablet* otherend, + int frontiertop, int* frontierbottom){ +//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; // move down past the focused tablet, filling up the reel to the bottom - do{ -//fprintf(stderr, "following otherend: %p ->p: %p\n", otherend, otherend->p); - // modify frontier based off the one we're at - tablet_coordinates(working->p, &wbegy, &wbegx, &wleny, &wlenx); - wmaxy = wbegy + wleny - 1; - frontiery = wmaxy + 2; -//fprintf(stderr, "EASTBOUND AND DOWN: %p->%p %d %d\n", working, working->next, frontiery, wmaxy + 2); - working = working->next; - if(working == otherend && otherend->p){ -//fprintf(stderr, "BREAKOUT ON OTHEREND %p:%p\n", working, working->p); + while(*frontierbottom <= maxx && (working != otherend || !otherend->p)){ + if(working->p){ break; } - ncreel_draw_tablet(nr, working, frontiery, DIRECTION_DOWN); +//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; } - }while(working->p); + *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, const nctablet* otherend){ -//fprintf(stderr, "preceding otherend: %p ->p: %p\n", otherend, otherend->p); - int wbegy, wbegx, wlenx, wleny; // working tablet window coordinates - nctablet* upworking = nr->tablets; - int frontiery; +draw_previous_tablets(const ncreel* nr, nctablet* otherend, + int* frontiertop, int frontierbottom){ + 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 - tablet_coordinates(upworking->p, &wbegy, &wbegx, &wleny, &wlenx); - frontiery = wbegy - 2; - while(upworking->prev != otherend || otherend->p == NULL){ -//fprintf(stderr, "MOVIN' ON UP: %p->%p %d %d\n", upworking, upworking->prev, frontiery, wbegy - 2); - upworking = upworking->prev; - ncreel_draw_tablet(nr, upworking, frontiery, DIRECTION_UP); + while(*frontiertop >= 0 && (upworking != otherend || !otherend->p)){ if(upworking->p){ - tablet_coordinates(upworking->p, &wbegy, &wbegx, &wleny, &wlenx); -//fprintf(stderr, "new up coords: %d/%d + %d/%d, %d\n", wbegy, wbegx, wleny, wlenx, frontiery); - frontiery = wbegy - 2; - }else{ 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){ + assert(top); + assert(top->p); + assert(bottom); + assert(bottom->p); + int y; +//fprintf(stderr, "trimming: top %p bottom %p\n", top->p, bottom->p); + ncplane_yx(top->p, &y, NULL); + int 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_genocide(top->p); + top->p = NULL; + top->cbp = NULL; + top = top->next; + return trim_reel_overhang(r, top, bottom); // FIXME need this at bottom? + }else if(y < miny){ + const int ynew = ylen - (miny - y); + if(ynew <= 0){ + ncplane_genocide(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 == 1){ + ncplane_genocide(top->cbp); + top->cbp = NULL; + }else{ + ncplane_dim_yx(top->cbp, &ylen, &xlen); + if(ncplane_resize(top->cbp, miny - y, 0, ynew - 1, xlen, 0, 0, ynew - 1, xlen)){ + return -1; + } + int x; + ncplane_yx(top->cbp, &y, &x); + ncplane_move_yx(top->cbp, y - 1, x); + } + } +//fprintf(stderr, "TRIMMED top %p from %d to %d (%d)\n", top->p, ylen, ynew, y - miny); + } + } + 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 < boty){ + const int ynew = ylen - (boty - maxy); + if(ynew <= 0){ + ncplane_genocide(bottom->p); + bottom->p = NULL; + bottom->cbp = NULL; + }else{ + if(ncplane_resize(bottom->p, 0, 0, ynew, xlen, 0, 0, ynew, xlen)){ + return -1; + } + if(bottom->cbp){ + if(ynew == 1){ + ncplane_genocide(bottom->cbp); + bottom->cbp = NULL; + }else{ + ncplane_dim_yx(bottom->cbp, &ylen, &xlen); + if(ncplane_resize(bottom->cbp, 0, 0, ynew - 1, xlen, 0, 0, ynew - 1, xlen)){ + return -1; + } + } + } +//fprintf(stderr, "TRIMMED bottom %p from %d to %d (%d)\n", bottom->p, ylen, ynew, maxy - boty); + } + } +//fprintf(stderr, "finished trimming\n"); + return 0; +} + static int tighten_reel_down(ncreel* r, int ybot){ nctablet* cur = r->tablets; @@ -516,6 +480,7 @@ tighten_reel_down(ncreel* r, int ybot){ } cury = ybot - ylen; ncplane_move_yx(cur->p, cury, curx); +//fprintf(stderr, "tightened %p down to %d\n", cur, cury); ybot = cury - 1; if((cur = cur->prev) == r->tablets){ break; @@ -528,9 +493,11 @@ tighten_reel_down(ncreel* r, int ybot){ // 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!). +// 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; @@ -549,6 +516,7 @@ tighten_reel(ncreel* r){ } int expected = !(r->ropts.bordermask & NCBOXMASK_TOP); cur = top; + nctablet* bottom = r->tablets; while(cur){ if(cur->p == NULL){ break; @@ -557,6 +525,7 @@ tighten_reel(ncreel* r){ 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; } }else{ @@ -565,6 +534,7 @@ tighten_reel(ncreel* r){ int ylen; ncplane_dim_yx(cur->p, &ylen, NULL); expected += ylen + 1; + bottom = cur; cur = cur->next; if(cur == top){ break; @@ -581,84 +551,37 @@ tighten_reel(ncreel* r){ // 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 + ylen + 1 >= ybot){ - return tighten_reel_down(r, ybot); + if(tighten_reel_down(r, ybot)){ + return -1; + } } } - return 0; -} - -// debugging -bool ncreel_validate(const ncreel* n){ - if(n->tablets == NULL){ - return true; - } - const nctablet* t = n->tablets; - int cury = -1; - bool wentaround = false; - do{ - const ncplane* np = t->p; - if(np){ - int y, x; - ncplane_yx(np, &y, &x); -//fprintf(stderr, "forvart: %p (%p) @ %d\n", t, np, y); - if(y < cury){ - if(wentaround){ - return false; - } - wentaround = true; - }else if(y == cury){ - return false; - } - cury = y; - } - }while((t = t->next) != n->tablets); - cury = INT_MAX; - wentaround = false; - do{ - const ncplane* np = t->p; - if(np){ - int y, x; - ncplane_yx(np, &y, &x); -//fprintf(stderr, "backwards: %p (%p) @ %d\n", t, np, y); - if(y > cury){ - if(wentaround){ - return false; - } - wentaround = true; - }else if(y == cury){ - return false; - } - cury = y; - } - }while((t = t->prev) != n->tablets); - return true; + return trim_reel_overhang(r, top, bottom); } // destroy all existing tablet planes pursuant to redraw static void clean_reel(ncreel* r){ - if(r->tablets){ - nctablet* cur = r->tablets->next; - while(cur && cur != r->tablets){// && cur->p){ -//fprintf(stderr, "CLEANING: %p (%p)\n", cur, cur->p); - if(cur->p){ - ncplane_destroy(cur->p); - cur->p = NULL; - } - cur = cur->next; + 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); + ncplane_genocide(n->p); + n->p = NULL; + n->cbp = NULL; } -//fprintf(stderr, "done clean next, %p %p %p\n", cur, r->tablets, cur ? cur->p : NULL); - cur = r->tablets->prev; - while(cur && cur != r->tablets){// && cur->p){ -//fprintf(stderr, "CLEANING: %p (%p)\n", cur, cur->p); - if(cur->p){ - ncplane_destroy(cur->p); - cur->p = NULL; - } - cur = cur->prev; + for(nctablet* n = vft->prev ; n->p && n != vft ; n = n->prev){ +//fprintf(stderr, "CLEANING PREV: %p (%p)\n", n, n->p); + ncplane_genocide(n->p); + n->p = NULL; + n->cbp = NULL; } +//fprintf(stderr, "CLEANING VFT: %p (%p)\n", vft, vft->p); + ncplane_genocide(vft->p); + vft->p = NULL; + vft->cbp = NULL; + r->vft = NULL; } -//fprintf(stderr, "done clean prev, %p %p %p\n", cur, r->tablets, cur ? cur->p : NULL); } // Arrange the panels, starting with the focused window, wherever it may be. @@ -668,37 +591,89 @@ clean_reel(ncreel* r){ // 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. -// -// This can still leave a gap plus a partially-onscreen tablet FIXME int ncreel_redraw(ncreel* nr){ -//fprintf(stderr, "--------> BEGIN REDRAW <--------\n"); +//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 iii 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)){ + 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){ + return -1; + } + otherend = draw_following_tablets(nr, otherend, frontiertop, &frontierbottom); + }else{ // DIRECTION_UP + otherend = draw_previous_tablets(nr, otherend, &frontiertop, frontierbottom); + if(otherend == NULL){ + return -1; + } + otherend = draw_following_tablets(nr, otherend, frontiertop, &frontierbottom); + } + if(otherend == NULL){ + return -1; + } +//notcurses_debug(nr->p->nc, stderr); + if(tighten_reel(nr)){ + return -1; + } +//notcurses_debug(nr->p->nc, stderr); + } + nr->vft = nr->tablets; // update the visually-focused tablet pointer +//fprintf(stderr, "DONE ARRANGING\n"); if(draw_ncreel_borders(nr)){ return -1; // enforces specified dimensional minima } - nctablet* focused = nr->tablets; - if(focused == NULL){ -//fprintf(stderr, "no focus!\n"); - return 0; // if none are focused, none exist - } -//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->direction); - clean_reel(nr); - nctablet* otherend = focused; - if(nr->direction >= DIRECTION_DOWN){ - otherend = draw_previous_tablets(nr, otherend); - otherend = draw_following_tablets(nr, otherend); - draw_previous_tablets(nr, otherend); - }else{ - otherend = draw_following_tablets(nr, otherend); - otherend = draw_previous_tablets(nr, otherend); - draw_following_tablets(nr, otherend); - } - tighten_reel(nr); -//fprintf(stderr, "DONE ARRANGING\n"); - if(!ncreel_validate(nr)){ - return -1; - } return 0; } @@ -727,7 +702,7 @@ validate_ncreel_opts(ncplane* w, const ncreel_options* ropts){ } ncplane* nctablet_ncplane(nctablet* t){ - return t->p; + return t->cbp; } ncplane* ncreel_plane(ncreel* nr){ @@ -745,9 +720,10 @@ ncreel* ncreel_create(ncplane* w, const ncreel_options* ropts){ } nr->tablets = NULL; nr->tabletcount = 0; - nr->direction = DIRECTION_DOWN; // draw down after the initial tablet + nr->direction = LASTDIRECTION_DOWN; // draw down after the initial tablet memcpy(&nr->ropts, ropts, sizeof(*ropts)); nr->p = w; + nr->vft = NULL; ncplane_set_base(nr->p, "", 0, ropts->bgchannel); if(ncreel_redraw(nr)){ ncplane_destroy(nr->p); @@ -762,6 +738,7 @@ nctablet* ncreel_add(ncreel* nr, nctablet* after, nctablet *before, nctablet* t; if(after && before){ if(after->prev != before || before->next != after){ + logerror(nr->p->nc, "bad before (%p) / after (%p) spec\n", before, after); return NULL; } }else if(!after && !before){ @@ -793,6 +770,7 @@ nctablet* ncreel_add(ncreel* nr, nctablet* after, nctablet *before, t->curry = opaque; ++nr->tabletcount; t->p = NULL; + t->cbp = NULL; return t; } @@ -805,10 +783,15 @@ int ncreel_del(ncreel* nr, struct nctablet* t){ if((nr->tablets = t->next) == t){ nr->tablets = NULL; } + // FIXME ought set direction based on actual location of replacement t + nr->direction = LASTDIRECTION_DOWN; + } + if(nr->vft == t){ + clean_reel(nr); // maybe just make nr->tablets the vft? } t->next->prev = t->prev; if(t->p){ - ncplane_destroy(t->p); + ncplane_genocide(t->p); } free(t); --nr->tabletcount; @@ -843,9 +826,8 @@ nctablet* ncreel_focused(ncreel* nr){ 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 = DIRECTION_DOWN; +//fprintf(stderr, "---------------> moved to next, %p to %p <----------\n", nr->tablets->prev, nr->tablets); + nr->direction = LASTDIRECTION_DOWN; } return nr->tablets; } @@ -853,9 +835,8 @@ nctablet* ncreel_next(ncreel* nr){ 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 = DIRECTION_UP; +//fprintf(stderr, "----------------> moved to prev, %p to %p <----------\n", nr->tablets->next, nr->tablets); + nr->direction = LASTDIRECTION_UP; } return nr->tablets; } diff --git a/src/ncreel/main.cpp b/src/ncreel/main.cpp index 842c260e9..ab88efd2e 100644 --- a/src/ncreel/main.cpp +++ b/src/ncreel/main.cpp @@ -40,12 +40,7 @@ class TabletCtx { inline static int class_idx = 0; }; -int tabletfxn(struct nctablet* _t, int begx, int begy, int maxx, int maxy, - bool cliptop){ - (void)begx; - (void)begy; - (void)maxx; - (void)maxy; +int tabletfxn(struct nctablet* _t, bool cliptop){ (void)cliptop; NcTablet *t = NcTablet::map_tablet (_t); Plane* p = t->get_plane(); @@ -65,6 +60,7 @@ void usage(const char* argv0, std::ostream& c, int status){ c << "usage: " << argv0 << " [ -h ] | options\n" << " -b bordermask: hex ncreel border mask (0x0..0xf)\n" << " -m margin(s): margin(s) around the reel\n" + << " -ln: logging level, higher n for more output (0 ≤ n ≤ 8)\n" << " -t tabletmask: hex tablet border mask (0x0..0xf)" << std::endl; exit(status); } @@ -75,9 +71,19 @@ void parse_args(int argc, char** argv, struct notcurses_options* opts, { .name = nullptr, .has_arg = 0, .flag = nullptr, .val = 0, }, }; int c; - while((c = getopt_long(argc, argv, "b:t:m:h", longopts, nullptr)) != -1){ + while((c = getopt_long(argc, argv, "l:b:t:m:h", longopts, nullptr)) != -1){ switch(c){ - case 'b':{ + case 'l':{ + int loglevel; + std::stringstream s(optarg); + s >> loglevel; + opts->loglevel = static_cast(loglevel); + if(opts->loglevel < NCLOGLEVEL_SILENT || opts->loglevel > NCLOGLEVEL_TRACE){ + fprintf(stderr, "Invalid log level: %d\n", opts->loglevel); + usage(argv[0], std::cerr, EXIT_FAILURE); + } + break; + }case 'b':{ std::stringstream ss; ss << std::hex << optarg; ss >> ropts->bordermask; diff --git a/tests/reel.cpp b/tests/reel.cpp index 665a50d4b..057dab4c9 100644 --- a/tests/reel.cpp +++ b/tests/reel.cpp @@ -1,16 +1,70 @@ #include "main.h" #include -auto panelcb(struct nctablet* t, int begx, int begy, int maxx, int maxy, bool cliptop) -> int { +auto panelcb(struct nctablet* t, bool toptobottom) -> int { CHECK(nctablet_ncplane(t)); - CHECK(begx < maxx); - CHECK(begy < maxy); CHECK(!nctablet_userptr(t)); - CHECK(!cliptop); + CHECK(toptobottom); // FIXME verify geometry is as expected return 0; } +auto cbfxn(struct nctablet* t, bool toptobottom) -> int { + (void)toptobottom; + int* userptr = static_cast(nctablet_userptr(t)); + int y; + ncplane_yx(nctablet_ncplane(t), &y, NULL); + *userptr += y; + return 4; +} + +// debugging +bool ncreel_validate(const ncreel* n){ + if(n->tablets == NULL){ + return true; + } + const nctablet* t = n->tablets; + int cury = -1; + bool wentaround = false; + do{ + const ncplane* np = t->p; + if(np){ + int y, x; + ncplane_yx(np, &y, &x); +//fprintf(stderr, "forvart: %p (%p) @ %d\n", t, np, y); + if(y < cury){ + if(wentaround){ + return false; + } + wentaround = true; + }else if(y == cury){ + return false; + } + cury = y; + } + }while((t = t->next) != n->tablets); + cury = INT_MAX; + wentaround = false; + do{ + const ncplane* np = t->p; + if(np){ + int y, x; + ncplane_yx(np, &y, &x); +//fprintf(stderr, "backwards: %p (%p) @ %d\n", t, np, y); + if(y > cury){ + if(wentaround){ + return false; + } + wentaround = true; + }else if(y == cury){ + return false; + } + cury = y; + } + }while((t = t->prev) != n->tablets); + return true; +} + TEST_CASE("Reels") { auto nc_ = testing_notcurses(); if(!nc_){ @@ -38,6 +92,7 @@ TEST_CASE("Reels") { r.flags = NCREEL_OPTION_INFINITESCROLL | NCREEL_OPTION_CIRCULAR; struct ncreel* nr = ncreel_create(n_, &r); REQUIRE(nr); + CHECK(ncreel_validate(nr)); REQUIRE(0 == ncreel_destroy(nr)); } @@ -56,9 +111,13 @@ TEST_CASE("Reels") { struct ncreel* nr = ncreel_create(n_, &r); REQUIRE(nr); CHECK(!ncreel_next(nr)); - // CHECK_EQ(0, ncreel_validate(n_, pr)); + CHECK_EQ(0, ncreel_redraw(nr)); + CHECK_EQ(0, notcurses_render(nc_)); + CHECK(ncreel_validate(nr)); CHECK(!ncreel_prev(nr)); - // CHECK_EQ(0, ncreel_validate(n_, pr)); + CHECK_EQ(0, ncreel_redraw(nr)); + CHECK_EQ(0, notcurses_render(nc_)); + CHECK(ncreel_validate(nr)); } SUBCASE("OneTablet") { @@ -67,9 +126,13 @@ TEST_CASE("Reels") { REQUIRE(nr); struct nctablet* t = ncreel_add(nr, nullptr, nullptr, panelcb, nullptr); REQUIRE(t); - // CHECK_EQ(0, ncreel_validate(n_, pr)); + CHECK_EQ(0, ncreel_redraw(nr)); + CHECK_EQ(0, notcurses_render(nc_)); + CHECK(ncreel_validate(nr)); CHECK(0 == ncreel_del(nr, t)); - // CHECK_EQ(0, ncreel_validate(n_, pr)); + CHECK_EQ(0, ncreel_redraw(nr)); + CHECK_EQ(0, notcurses_render(nc_)); + CHECK(ncreel_validate(nr)); } SUBCASE("MovementWithOneTablet") { @@ -78,13 +141,21 @@ TEST_CASE("Reels") { REQUIRE(nr); struct nctablet* t = ncreel_add(nr, nullptr, nullptr, panelcb, nullptr); REQUIRE(t); - // CHECK_EQ(0, ncreel_validate(n_, pr)); + CHECK_EQ(0, ncreel_redraw(nr)); + CHECK_EQ(0, notcurses_render(nc_)); + CHECK(ncreel_validate(nr)); CHECK(ncreel_next(nr)); - // CHECK_EQ(0, ncreel_validate(n_, pr)); + CHECK_EQ(0, ncreel_redraw(nr)); + CHECK_EQ(0, notcurses_render(nc_)); + CHECK(ncreel_validate(nr)); CHECK(ncreel_prev(nr)); - // CHECK_EQ(0, ncreel_validate(n_, pr)); + CHECK_EQ(0, ncreel_redraw(nr)); + CHECK_EQ(0, notcurses_render(nc_)); + CHECK(ncreel_validate(nr)); CHECK(0 == ncreel_del(nr, t)); - // CHECK_EQ(0, ncreel_validate(n_, pr)); + CHECK_EQ(0, ncreel_redraw(nr)); + CHECK_EQ(0, notcurses_render(nc_)); + CHECK(ncreel_validate(nr)); } SUBCASE("DeleteActiveTablet") { @@ -94,6 +165,9 @@ TEST_CASE("Reels") { struct nctablet* t = ncreel_add(nr, nullptr, nullptr, panelcb, nullptr); REQUIRE(t); CHECK(0 == ncreel_del(nr, ncreel_focused(nr))); + CHECK_EQ(0, ncreel_redraw(nr)); + CHECK_EQ(0, notcurses_render(nc_)); + CHECK(ncreel_validate(nr)); } SUBCASE("NoBorder") { @@ -102,6 +176,9 @@ TEST_CASE("Reels") { NCBOXMASK_TOP | NCBOXMASK_BOTTOM; struct ncreel* nr = ncreel_create(n_, &r); REQUIRE(nr); + CHECK_EQ(0, ncreel_redraw(nr)); + CHECK_EQ(0, notcurses_render(nc_)); + CHECK(ncreel_validate(nr)); } SUBCASE("BadBorderBitsRejected") { @@ -117,6 +194,9 @@ TEST_CASE("Reels") { NCBOXMASK_TOP | NCBOXMASK_BOTTOM; struct ncreel* nr = ncreel_create(n_, &r); REQUIRE(nr); + CHECK_EQ(0, ncreel_redraw(nr)); + CHECK_EQ(0, notcurses_render(nc_)); + CHECK(ncreel_validate(nr)); } SUBCASE("NoTopBottomBorder") { @@ -124,6 +204,9 @@ TEST_CASE("Reels") { r.bordermask = NCBOXMASK_TOP | NCBOXMASK_BOTTOM; struct ncreel* nr = ncreel_create(n_, &r); REQUIRE(nr); + CHECK_EQ(0, ncreel_redraw(nr)); + CHECK_EQ(0, notcurses_render(nc_)); + CHECK(ncreel_validate(nr)); } SUBCASE("NoSideBorders") { @@ -131,6 +214,9 @@ TEST_CASE("Reels") { r.bordermask = NCBOXMASK_LEFT | NCBOXMASK_RIGHT; struct ncreel* nr = ncreel_create(n_, &r); REQUIRE(nr); + CHECK_EQ(0, ncreel_redraw(nr)); + CHECK_EQ(0, notcurses_render(nc_)); + CHECK(ncreel_validate(nr)); } SUBCASE("BadTabletBorderBitsRejected") { @@ -140,90 +226,97 @@ TEST_CASE("Reels") { REQUIRE(!nr); } - /* - // Make a target window occupying all but a containing perimeter of the - // specified WINDOW (which will usually be n_). - struct ncpanel* make_targwin(struct ncpanel* w) { - cchar_t cc; - int cpair = COLOR_GREEN; - CHECK_EQ(OK, setcchar(&cc, L"W", 0, 0, &cpair)); - int x, y, xx, yy; - getbegyx(w, y, x); - getmaxyx(w, yy, xx); - yy -= 2; - xx -= 2; - ++x; - ++y; - WINDOW* ww = subwin(w, yy, xx, y, x); - CHECK_NE(nullptr, ww); - PANEL* p = new_panel(ww); - CHECK_NE(nullptr, p); - CHECK_EQ(OK, wbkgrnd(ww, &cc)); - return p; - } - - SUBCASE("InitWithinSubwin") { - ncreel_options r{}; - r.loff = 1; - r.roff = 1; - r.toff = 1; - r.boff = 1; - CHECK_EQ(0, clear()); - PANEL* base = make_targwin(n_); - REQUIRE_NE(nullptr, base); - WINDOW* basew = panel_window(base); - REQUIRE_NE(nullptr, basew); - struct ncreel* nr = ncreel_create(basew, &r); - REQUIRE_NE(nullptr, pr); - CHECK_EQ(0, ncreel_validate(basew, pr)); - REQUIRE_EQ(0, ncreel_destroy(nr)); - CHECK_EQ(OK, del_panel(base)); - CHECK_EQ(OK, delwin(basew)); - } - - SUBCASE("SubwinNoncreelBorders") { - ncreel_options r{}; - r.loff = 1; - r.roff = 1; - r.toff = 1; - r.boff = 1; - r.bordermask = NCBOXMASK_LEFT | NCBOXMASK_RIGHT | - NCBOXMASK_TOP | NCBOXMASK_BOTTOM; - CHECK_EQ(0, clear()); - PANEL* base = make_targwin(n_); - REQUIRE_NE(nullptr, base); - WINDOW* basew = panel_window(base); - REQUIRE_NE(nullptr, basew); - struct ncreel* nr = ncreel_create(basew, &r); - REQUIRE_NE(nullptr, pr); - CHECK_EQ(0, ncreel_validate(basew, pr)); - REQUIRE_EQ(0, ncreel_destroy(nr)); - CHECK_EQ(OK, del_panel(base)); - CHECK_EQ(OK, delwin(basew)); - } - - SUBCASE("SubwinNoOffsetGeom") { - ncreel_options r{}; - CHECK_EQ(0, clear()); - PANEL* base = make_targwin(n_); - REQUIRE_NE(nullptr, base); - WINDOW* basew = panel_window(base); - REQUIRE_NE(nullptr, basew); - struct ncreel* nr = ncreel_create(basew, &r); - REQUIRE_NE(nullptr, pr); - CHECK_EQ(0, ncreel_validate(basew, pr)); - REQUIRE_EQ(0, ncreel_destroy(nr)); - CHECK_EQ(OK, del_panel(base)); - CHECK_EQ(OK, delwin(basew)); - } - */ - SUBCASE("TransparentBackground") { ncreel_options r{}; channels_set_bg_alpha(&r.bgchannel, 3); struct ncreel* nr = ncreel_create(n_, &r); REQUIRE(nr); - // FIXME + CHECK_EQ(0, ncreel_redraw(nr)); + CHECK_EQ(0, notcurses_render(nc_)); + CHECK(ncreel_validate(nr)); + } + + // Layout tests. Add some tablets, move around, and verify that they all + // have the expected locations/contents/geometries. + SUBCASE("ThreeCycleDown") { + ncreel_options r{}; + channels_set_bg_alpha(&r.bgchannel, 3); + struct ncreel* nr = ncreel_create(n_, &r); + REQUIRE(nr); + CHECK_EQ(0, ncreel_redraw(nr)); + CHECK_EQ(0, notcurses_render(nc_)); + CHECK(ncreel_validate(nr)); + int order[3]; + nctablet* tabs[3]; + for(size_t n = 0 ; n < sizeof(order) / sizeof(*order) ; ++n){ + order[n] = -1; + tabs[n] = ncreel_add(nr, nullptr, nullptr, cbfxn, &order[n]); + REQUIRE(tabs[n]); + CHECK(tabs[0] == nr->tablets); + CHECK_EQ(0, ncreel_redraw(nr)); + CHECK_EQ(0, notcurses_render(nc_)); + CHECK(ncreel_validate(nr)); + } + int expectedy = 1; + for(size_t n = 0 ; n < sizeof(order) / sizeof(*order) ; ++n){ + CHECK_LE(0, order[n]); + int y; + ncplane_yx(ncplane_parent(nctablet_ncplane(tabs[n])), &y, nullptr); + CHECK(y == expectedy); + expectedy += 7; + } + ncreel_next(nr); + CHECK(tabs[1] == nr->tablets); + CHECK_EQ(0, ncreel_redraw(nr)); + CHECK_EQ(0, notcurses_render(nc_)); + CHECK(ncreel_validate(nr)); + expectedy = 1; + for(size_t n = 0 ; n < sizeof(order) / sizeof(*order) ; ++n){ + CHECK_LE(1, order[n]); + int y; + ncplane_yx(ncplane_parent(nctablet_ncplane(tabs[n])), &y, nullptr); + CHECK(y == expectedy); + expectedy += 7; + } + ncreel_next(nr); + CHECK(tabs[2] == nr->tablets); + CHECK_EQ(0, ncreel_redraw(nr)); + CHECK_EQ(0, notcurses_render(nc_)); + CHECK(ncreel_validate(nr)); + expectedy = 1; + for(size_t n = 0 ; n < sizeof(order) / sizeof(*order) ; ++n){ + //CHECK_EQ(2 - n + 2, order[n]); + int y; + ncplane_yx(ncplane_parent(nctablet_ncplane(tabs[n])), &y, nullptr); + CHECK(y == expectedy); + expectedy += 7; + } + ncreel_prev(nr); + CHECK(tabs[1] == nr->tablets); + CHECK_EQ(0, ncreel_redraw(nr)); + CHECK_EQ(0, notcurses_render(nc_)); + CHECK(ncreel_validate(nr)); + expectedy = 1; + for(size_t n = 0 ; n < sizeof(order) / sizeof(*order) ; ++n){ + //CHECK_EQ(2 - n + 3, order[n]); + int y; + ncplane_yx(ncplane_parent(nctablet_ncplane(tabs[n])), &y, nullptr); + CHECK(y == expectedy); + expectedy += 7; + } + ncreel_prev(nr); + CHECK(tabs[0] == nr->tablets); + CHECK_EQ(0, ncreel_redraw(nr)); + CHECK_EQ(0, notcurses_render(nc_)); + CHECK(ncreel_validate(nr)); + expectedy = 1; + for(size_t n = 0 ; n < sizeof(order) / sizeof(*order) ; ++n){ + //CHECK_EQ(2 - n + 4, order[n]); + int y; + ncplane_yx(ncplane_parent(nctablet_ncplane(tabs[n])), &y, nullptr); + CHECK(y == expectedy); + expectedy += 7; + } } CHECK(0 == notcurses_stop(nc_));