New reel layout algorithm #818 (#870)

New reel layout algorithm based on trimming and sifting. Fixes the original issue of #818, though I'm not marking that bug fixed until I've resolved the little issues remaining with this one.
Back off CMake version dependency, see if we can get by with 3.11.4 for EPEL8 #851
Simplify tablet drawing tremendously by separating tablet border and data planes. Callbacks no longer need worry about the borders; they can simply fill the plane they're handed. #833
Improve notcurses_debug() a bit
Add ncplane_new_named() and friends to expose plane naming to the user.
Add internal ncplane_genocide() to kill a plane and all its bound descendents
New industrial-strength ncreel unit testing
notcurses-ncreel now accepts -ln for log level n
Add ncplane_parent() and ncplane_parent_const()
This commit is contained in:
Nick Black 2020-08-09 17:40:59 -04:00 committed by GitHub
parent dbdbf32b5e
commit 11d6a4eb89
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 756 additions and 543 deletions

View File

@ -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"

12
NEWS.md
View File

@ -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.

View File

@ -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+

View File

@ -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

View File

@ -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.

View File

@ -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)

View File

@ -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);**

View File

@ -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

View File

@ -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__":

View File

@ -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);
}

View File

@ -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){

View File

@ -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

View File

@ -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

View File

@ -5,20 +5,10 @@
#include <string.h>
#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;
}

View File

@ -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<ncloglevel_e>(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;

View File

@ -1,16 +1,70 @@
#include "main.h"
#include <iostream>
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<int*>(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_));