mirror of
https://github.com/dankamongmen/notcurses
synced 2025-03-09 17:19:03 -04:00
Port of panelreels, unit tests, and panelreel-demo from outcurses #52. Doesn't completely work yet, but is surprisingly close!
This commit is contained in:
parent
326e93fee1
commit
426f632300
@ -13,6 +13,7 @@ configure_file(tools/version.h.in include/version.h)
|
||||
include(GNUInstallDirs)
|
||||
|
||||
find_package(PkgConfig REQUIRED)
|
||||
find_package(Threads REQUIRED)
|
||||
pkg_check_modules(TERMINFO REQUIRED tinfo>=6.1)
|
||||
pkg_check_modules(AVUTIL REQUIRED libavutil>=56.0)
|
||||
pkg_check_modules(AVFORMAT REQUIRED libavformat>=57.0)
|
||||
@ -62,6 +63,7 @@ target_include_directories(notcurses-demo PRIVATE include)
|
||||
target_link_libraries(notcurses-demo
|
||||
PRIVATE
|
||||
notcurses
|
||||
Threads::Threads
|
||||
)
|
||||
target_compile_options(notcurses-demo
|
||||
PRIVATE
|
||||
|
@ -143,15 +143,16 @@ API int notcurses_render(struct notcurses* nc);
|
||||
//
|
||||
// In the case of a valid read, a positive value is returned corresponding to
|
||||
// the number of bytes in the UTF-8 character, or '1' for all specials keys.
|
||||
// 0 is returned only by notcurses_getc(), to indicate that no input was
|
||||
// available. Otherwise (including on EOF) -1 is returned.
|
||||
// 0 is returned to indicate that no input was available, but only by
|
||||
// notcurses_getc(). Otherwise (including on EOF) -1 is returned.
|
||||
typedef enum {
|
||||
NCKEY_INVALID,
|
||||
NCKEY_RESIZE,
|
||||
NCKEY_RESIZE, // generated interally in response to SIGWINCH
|
||||
NCKEY_UP,
|
||||
NCKEY_RIGHT,
|
||||
NCKEY_DOWN,
|
||||
NCKEY_LEFT,
|
||||
NCKEY_DC, // delete
|
||||
// FIXME...
|
||||
} ncspecial_key;
|
||||
|
||||
@ -660,13 +661,10 @@ typedef struct panelreel_options {
|
||||
// focused and non-focused tablets can have different styles. you can instead
|
||||
// draw your own borders, or forgo borders entirely.
|
||||
unsigned bordermask; // bitfield; 1s will not be drawn (see bordermaskbits)
|
||||
uint32_t borderattr; // attributes used for panelreel border, no color!
|
||||
int borderpair; // extended color pair for panelreel border
|
||||
cell borderattr; // attributes used for panelreel border
|
||||
unsigned tabletmask; // bitfield; same as bordermask but for tablet borders
|
||||
uint32_t tabletattr; // attributes used for tablet borders, no color!
|
||||
int tabletpair; // extended color pair for tablet borders
|
||||
uint32_t focusedattr;// attributes used for focused tablet borders, no color!
|
||||
int focusedpair; // extended color pair for focused tablet borders
|
||||
cell tabletattr; // attributes used for tablet borders
|
||||
cell focusedattr; // attributes used for focused tablet borders, no color!
|
||||
} panelreel_options;
|
||||
|
||||
struct tablet;
|
||||
@ -679,9 +677,9 @@ struct panelreel;
|
||||
// and columns can be enforced via popts. efd, if non-negative, is an eventfd
|
||||
// that ought be written to whenever panelreel_touch() updates a tablet (this
|
||||
// is useful in the case of nonblocking input).
|
||||
struct panelreel* panelreel_create(struct ncplane* nc,
|
||||
const panelreel_options* popts,
|
||||
int efd);
|
||||
API struct panelreel* panelreel_create(struct ncplane* nc,
|
||||
const panelreel_options* popts,
|
||||
int efd);
|
||||
|
||||
// Tablet draw callback, provided a ncplane the first column that may be used,
|
||||
// the first row that may be used, the first column that may not be used, the
|
||||
@ -698,7 +696,7 @@ struct panelreel* panelreel_create(struct ncplane* nc,
|
||||
// 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 ncplane* p, int begx, int begy, int maxx,
|
||||
int maxy, bool cliptop);
|
||||
int maxy, bool cliptop, void* curry);
|
||||
|
||||
// Add a new tablet to the provided panelreel, having the callback object
|
||||
// opaque. Neither, either, or both of after and before may be specified. If
|
||||
@ -707,44 +705,45 @@ typedef int (*tabletcb)(struct ncplane* p, int begx, int begy, int maxx,
|
||||
// specified tablet. If both are specifid, the tablet will be added to the
|
||||
// resulting location, assuming it is valid (after->next == before->prev); if
|
||||
// it is not valid, or there is any other error, NULL will be returned.
|
||||
struct tablet* panelreel_add(struct panelreel* pr, struct tablet* after,
|
||||
struct tablet *before, tabletcb cb, void* opaque);
|
||||
API struct tablet* panelreel_add(struct panelreel* pr, struct tablet* after,
|
||||
struct tablet *before, tabletcb cb,
|
||||
void* opaque);
|
||||
|
||||
// Return the number of tablets.
|
||||
int panelreel_tabletcount(const struct panelreel* pr);
|
||||
API int panelreel_tabletcount(const struct panelreel* pr);
|
||||
|
||||
// Indicate that the specified tablet has been updated in a way that would
|
||||
// change its display. This will trigger some non-negative number of callbacks
|
||||
// (though not in the caller's context).
|
||||
int panelreel_touch(struct panelreel* pr, struct tablet* t);
|
||||
API int panelreel_touch(struct panelreel* pr, struct tablet* t);
|
||||
|
||||
// Delete the tablet specified by t from the panelreel specified by pr. Returns
|
||||
// -1 if the tablet cannot be found.
|
||||
int panelreel_del(struct panelreel* pr, struct tablet* t);
|
||||
API int panelreel_del(struct panelreel* pr, struct tablet* t);
|
||||
|
||||
// Delete the active tablet. Returns -1 if there are no tablets.
|
||||
int panelreel_del_focused(struct panelreel* pr);
|
||||
API int panelreel_del_focused(struct panelreel* pr);
|
||||
|
||||
// Move to the specified location within the containing WINDOW.
|
||||
int panelreel_move(struct panelreel* pr, int x, int y);
|
||||
API int panelreel_move(struct panelreel* pr, int x, int y);
|
||||
|
||||
// Redraw the panelreel in its entirety, for instance after
|
||||
// clearing the screen due to external corruption, or a SIGWINCH.
|
||||
int panelreel_redraw(struct panelreel* pr);
|
||||
API int panelreel_redraw(struct panelreel* pr);
|
||||
|
||||
// Return the focused tablet, if any tablets are present. This is not a copy;
|
||||
// be careful to use it only for the duration of a critical section.
|
||||
struct tablet* panelreel_focused(struct panelreel* pr);
|
||||
API struct tablet* panelreel_focused(struct panelreel* pr);
|
||||
|
||||
// Change focus to the next tablet, if one exists
|
||||
struct tablet* panelreel_next(struct panelreel* pr);
|
||||
API struct tablet* panelreel_next(struct panelreel* pr);
|
||||
|
||||
// Change focus to the previous tablet, if one exists
|
||||
struct tablet* panelreel_prev(struct panelreel* pr);
|
||||
API struct tablet* panelreel_prev(struct panelreel* pr);
|
||||
|
||||
// Destroy a panelreel allocated with panelreel_create(). Does not destroy the
|
||||
// underlying WINDOW. Returns non-zero on failure.
|
||||
int panelreel_destroy(struct panelreel* pr);
|
||||
API int panelreel_destroy(struct panelreel* pr);
|
||||
|
||||
#undef API
|
||||
|
||||
|
@ -24,12 +24,13 @@ usage(const char* exe, int status){
|
||||
fprintf(out, " -d: delay multiplier (float)\n");
|
||||
fprintf(out, " -f: render to file in addition to stdout\n");
|
||||
fprintf(out, "all demos are run if no specification is provided\n");
|
||||
fprintf(out, " i: run intro\n");
|
||||
fprintf(out, " s: run shuffle\n");
|
||||
fprintf(out, " u: run uniblock\n");
|
||||
fprintf(out, " m: run maxcolor\n");
|
||||
fprintf(out, " b: run box\n");
|
||||
fprintf(out, " g: run grid\n");
|
||||
fprintf(out, " i: run intro\n");
|
||||
fprintf(out, " m: run maxcolor\n");
|
||||
fprintf(out, " p: run panelreels\n");
|
||||
fprintf(out, " s: run shuffle\n");
|
||||
fprintf(out, " u: run uniblock\n");
|
||||
fprintf(out, " v: run view\n");
|
||||
fprintf(out, " w: run widecolors\n");
|
||||
exit(status);
|
||||
@ -142,6 +143,7 @@ ext_demos(struct notcurses* nc, const char* demos){
|
||||
case 'g': ret = grid_demo(nc); break;
|
||||
case 'v': ret = view_demo(nc); break;
|
||||
case 'w': ret = widecolor_demo(nc); break;
|
||||
case 'p': ret = panelreel_demo(nc); break;
|
||||
}
|
||||
if(ret){
|
||||
return ret;
|
||||
@ -209,7 +211,7 @@ int main(int argc, char** argv){
|
||||
if(argv[optind] != NULL){
|
||||
usage(*argv, EXIT_FAILURE);
|
||||
}
|
||||
demos = "isumbgwv";
|
||||
demos = "isumbgwvp";
|
||||
}
|
||||
if((nc = notcurses_init(&nopts)) == NULL){
|
||||
return EXIT_FAILURE;
|
||||
|
@ -20,6 +20,7 @@ int maxcolor_demo(struct notcurses* nc);
|
||||
int grid_demo(struct notcurses* nc);
|
||||
int sliding_puzzle_demo(struct notcurses* nc);
|
||||
int view_demo(struct notcurses* nc);
|
||||
int panelreel_demo(struct notcurses* nc);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
349
src/demo/panelreel.c
Normal file
349
src/demo/panelreel.c
Normal file
@ -0,0 +1,349 @@
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <pthread.h>
|
||||
#include <sys/poll.h>
|
||||
#include <notcurses.h>
|
||||
#include <sys/eventfd.h>
|
||||
#include "demo.h"
|
||||
|
||||
// FIXME ought just be an unordered_map
|
||||
typedef struct tabletctx {
|
||||
pthread_t tid;
|
||||
struct panelreel* pr;
|
||||
struct tablet* t;
|
||||
int lines;
|
||||
unsigned rgb;
|
||||
unsigned id;
|
||||
struct tabletctx* next;
|
||||
pthread_mutex_t lock;
|
||||
} tabletctx;
|
||||
|
||||
static void
|
||||
kill_tablet(tabletctx** tctx){
|
||||
tabletctx* t = *tctx;
|
||||
if(t){
|
||||
if(pthread_cancel(t->tid)){
|
||||
fprintf(stderr, "Warning: error sending pthread_cancel (%s)\n", strerror(errno));
|
||||
}
|
||||
if(pthread_join(t->tid, NULL)){
|
||||
fprintf(stderr, "Warning: error joining pthread (%s)\n", strerror(errno));
|
||||
}
|
||||
panelreel_del(t->pr, t->t);
|
||||
*tctx = t->next;
|
||||
pthread_mutex_destroy(&t->lock);
|
||||
free(t);
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
kill_active_tablet(struct panelreel* pr, tabletctx** tctx){
|
||||
struct tablet* focused = panelreel_focused(pr);
|
||||
tabletctx* t;
|
||||
while( (t = *tctx) ){
|
||||
if(t->t == focused){
|
||||
*tctx = t->next; // pull it out of the list
|
||||
t->next = NULL; // finish splicing it out
|
||||
break;
|
||||
}
|
||||
tctx = &t->next;
|
||||
}
|
||||
if(t == NULL){
|
||||
return -1; // wasn't present in our list, wacky
|
||||
}
|
||||
kill_tablet(&t);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// We need write in reverse order (since only the bottom will be seen, if we're
|
||||
// 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){
|
||||
char cchbuf[2];
|
||||
cell c = CELL_TRIVIAL_INITIALIZER;
|
||||
int y, idx;
|
||||
idx = tctx->lines;
|
||||
if(maxy - begy > tctx->lines){
|
||||
maxy -= (maxy - begy - 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){
|
||||
ncplane_cursor_move_yx(w, y, begx);
|
||||
snprintf(cchbuf, sizeof(cchbuf) / sizeof(*cchbuf), "%x", idx % 16);
|
||||
cell_load(w, &c, cchbuf);
|
||||
cell_set_fg(&c, (rgb >> 16u) % 0xffu, (rgb >> 8u) % 0xffu, rgb % 0xffu);
|
||||
int x;
|
||||
for(x = begx ; x <= maxx ; ++x){
|
||||
// lower-right corner always returns an error unless scrollok() is used
|
||||
ncplane_putc(w, &c);
|
||||
}
|
||||
if(--idx == 0){
|
||||
break;
|
||||
}
|
||||
}
|
||||
// fprintf(stderr, "tabletup done%s at %d (%d->%d)\n", idx == 0 ? " early" : "", y, begy, maxy);
|
||||
return tctx->lines - idx;
|
||||
}
|
||||
|
||||
static int
|
||||
tabletdown(struct ncplane* w, int begx, int begy, int maxx, 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;
|
||||
}
|
||||
ncplane_cursor_move_yx(w, y, begx);
|
||||
snprintf(cchbuf, sizeof(cchbuf) / sizeof(*cchbuf), "%x", y % 16);
|
||||
cell_load(w, &c, cchbuf);
|
||||
cell_set_fg(&c, (rgb >> 16u) % 0xffu, (rgb >> 8u) % 0xffu, rgb % 0xffu);
|
||||
int x;
|
||||
for(x = begx ; x <= maxx ; ++x){
|
||||
// lower-right corner always returns an error unless scrollok() is used
|
||||
ncplane_putc(w, &c);
|
||||
}
|
||||
}
|
||||
return y - begy;
|
||||
}
|
||||
|
||||
static int
|
||||
tabletdraw(struct ncplane* p, int begx, int begy, int maxx, int maxy,
|
||||
bool cliptop, void* vtabletctx){
|
||||
int err = 0;
|
||||
tabletctx* tctx = vtabletctx;
|
||||
pthread_mutex_lock(&tctx->lock);
|
||||
unsigned rgb = tctx->rgb;
|
||||
int ll;
|
||||
if(cliptop){
|
||||
ll = tabletup(p, begx, begy, maxx, maxy, tctx, rgb);
|
||||
}else{
|
||||
ll = tabletdown(p, begx, begy, maxx, maxy, tctx, rgb);
|
||||
}
|
||||
ncplane_fg_rgb8(p, 242, 242, 242);
|
||||
if(ll){
|
||||
int summaryy = begy;
|
||||
if(cliptop){
|
||||
if(ll == maxy - begy + 1){
|
||||
summaryy = ll - 1;
|
||||
}else{
|
||||
summaryy = ll;
|
||||
}
|
||||
}
|
||||
err |= ncplane_cursor_move_yx(p, summaryy, begx);
|
||||
ncplane_printf(p, "[#%u %d line%s %u/%u] ", tctx->id, tctx->lines,
|
||||
tctx->lines == 1 ? "" : "s", begy, maxy);
|
||||
}
|
||||
/*fprintf(stderr, " \\--> callback for %d, %d lines (%d/%d -> %d/%d) dir: %s wrote: %d ret: %d\n", tctx->id,
|
||||
tctx->lines, begy, begx, maxy, maxx,
|
||||
cliptop ? "up" : "down", ll, err);*/
|
||||
pthread_mutex_unlock(&tctx->lock);
|
||||
assert(0 == err);
|
||||
return ll;
|
||||
}
|
||||
|
||||
// Each tablet has an associated thread which will periodically send update
|
||||
// events for its tablet.
|
||||
static void*
|
||||
tablet_thread(void* vtabletctx){
|
||||
static int MINSECONDS = 0;
|
||||
tabletctx* tctx = vtabletctx;
|
||||
while(true){
|
||||
struct timespec ts;
|
||||
ts.tv_sec = random() % 3 + MINSECONDS;
|
||||
ts.tv_nsec = random() % 1000000000;
|
||||
nanosleep(&ts, NULL);
|
||||
int action = random() % 5;
|
||||
pthread_mutex_lock(&tctx->lock);
|
||||
if(action < 2){
|
||||
if((tctx->lines -= (action + 1)) < 1){
|
||||
tctx->lines = 1;
|
||||
}
|
||||
panelreel_touch(tctx->pr, tctx->t);
|
||||
}else if(action > 2){
|
||||
if((tctx->lines += (action - 2)) < 1){
|
||||
tctx->lines = 1;
|
||||
}
|
||||
panelreel_touch(tctx->pr, tctx->t);
|
||||
}
|
||||
pthread_mutex_unlock(&tctx->lock);
|
||||
}
|
||||
return tctx;
|
||||
}
|
||||
|
||||
static tabletctx*
|
||||
new_tabletctx(struct panelreel* pr, unsigned *id){
|
||||
tabletctx* tctx = malloc(sizeof(*tctx));
|
||||
if(tctx == NULL){
|
||||
return NULL;
|
||||
}
|
||||
pthread_mutex_init(&tctx->lock, NULL);
|
||||
tctx->pr = pr;
|
||||
tctx->lines = random() % 10 + 1; // FIXME a nice gaussian would be swell
|
||||
tctx->rgb = random() % (1u << 24u);
|
||||
tctx->id = ++*id;
|
||||
if((tctx->t = panelreel_add(pr, NULL, NULL, tabletdraw, tctx)) == NULL){
|
||||
pthread_mutex_destroy(&tctx->lock);
|
||||
free(tctx);
|
||||
return NULL;
|
||||
}
|
||||
if(pthread_create(&tctx->tid, NULL, tablet_thread, tctx)){
|
||||
pthread_mutex_destroy(&tctx->lock);
|
||||
free(tctx);
|
||||
return NULL;
|
||||
}
|
||||
return tctx;
|
||||
}
|
||||
|
||||
static int
|
||||
handle_input(struct notcurses* nc, struct panelreel* pr, int efd,
|
||||
cell* c, ncspecial_key* special){
|
||||
struct pollfd fds[2] = {
|
||||
{ .fd = STDIN_FILENO, .events = POLLIN, .revents = 0, },
|
||||
{ .fd = efd, .events = POLLIN, .revents = 0, },
|
||||
};
|
||||
int key = -1;
|
||||
int pret;
|
||||
notcurses_render(nc);
|
||||
do{
|
||||
pret = poll(fds, sizeof(fds) / sizeof(*fds), -1);
|
||||
if(pret < 0){
|
||||
fprintf(stderr, "Error polling on stdin/eventfd (%s)\n", strerror(errno));
|
||||
}else{
|
||||
if(fds[0].revents & POLLIN){
|
||||
key = notcurses_getc(nc, c, special);
|
||||
if(key < 0){
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
if(fds[1].revents & POLLIN){
|
||||
uint64_t val;
|
||||
if(read(efd, &val, sizeof(val)) != sizeof(val)){
|
||||
fprintf(stderr, "Error reading from eventfd %d (%s)\n", efd, strerror(errno)); }else if(key < 0){
|
||||
panelreel_redraw(pr);
|
||||
notcurses_render(nc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}while(key < 0);
|
||||
return key;
|
||||
}
|
||||
|
||||
static struct panelreel*
|
||||
panelreel_demo_core(struct notcurses* nc, int efd, tabletctx** tctxs){
|
||||
bool done = false;
|
||||
int x = 4, y = 4;
|
||||
panelreel_options popts = {
|
||||
.infinitescroll = true,
|
||||
.circular = true,
|
||||
.min_supported_cols = 8,
|
||||
.min_supported_rows = 5,
|
||||
.borderattr = CELL_TRIVIAL_INITIALIZER,
|
||||
.tabletattr = CELL_TRIVIAL_INITIALIZER,
|
||||
.focusedattr = CELL_TRIVIAL_INITIALIZER,
|
||||
.toff = y,
|
||||
.loff = x,
|
||||
.roff = 0,
|
||||
.boff = 0,
|
||||
};
|
||||
cell_set_fg(&popts.focusedattr, 58, 150, 221);
|
||||
cell_set_fg(&popts.tabletattr, 19, 161, 14);
|
||||
cell_set_fg(&popts.borderattr, 136, 23, 152);
|
||||
struct ncplane* w = notcurses_stdplane(nc);
|
||||
struct panelreel* pr = panelreel_create(w, &popts, efd);
|
||||
if(pr == NULL){
|
||||
fprintf(stderr, "Error creating panelreel\n");
|
||||
return NULL;
|
||||
}
|
||||
// Press a for a new panel above the current, c for a new one below the
|
||||
// current, and b for a new block at arbitrary placement. q quits.
|
||||
ncplane_fg_rgb8(w, 58, 150, 221);
|
||||
ncplane_cursor_move_yx(w, 1, 1);
|
||||
ncplane_printf(w, "a, b, c create tablets, DEL deletes, q quits.");
|
||||
// FIXME clrtoeol();
|
||||
unsigned id = 0;
|
||||
do{
|
||||
ncplane_styles_set(w, 0);
|
||||
ncplane_fg_rgb8(w, 197, 15, 31);
|
||||
int count = panelreel_tabletcount(pr);
|
||||
ncplane_cursor_move_yx(w, 2, 2);
|
||||
ncplane_printf(w, "%d tablet%s", count, count == 1 ? "" : "s");
|
||||
// FIXME wclrtoeol(w);
|
||||
ncplane_fg_rgb8(w, 0, 55, 218);
|
||||
ncspecial_key special = NCKEY_INVALID;
|
||||
cell c = CELL_TRIVIAL_INITIALIZER;
|
||||
if(handle_input(nc, pr, efd, &c, &special) < 0){
|
||||
done = true;
|
||||
break;
|
||||
}
|
||||
// FIXME clrtoeol();
|
||||
struct tabletctx* newtablet = NULL;
|
||||
if(cell_simple_p(&c)){
|
||||
if(c.gcluster){
|
||||
switch(c.gcluster){
|
||||
case 'p': sleep(60); exit(EXIT_FAILURE); break;
|
||||
case 'a': newtablet = new_tabletctx(pr, &id); break;
|
||||
case 'b': newtablet = new_tabletctx(pr, &id); break;
|
||||
case 'c': newtablet = new_tabletctx(pr, &id); break;
|
||||
case 'h': --x; if(panelreel_move(pr, x, y)){ ++x; } break;
|
||||
case 'l': ++x; if(panelreel_move(pr, x, y)){ --x; } break;
|
||||
case 'k': panelreel_prev(pr); break;
|
||||
case 'j': panelreel_next(pr); break;
|
||||
case 'q': done = true; break;
|
||||
default:
|
||||
ncplane_cursor_move_yx(w, 3, 2);
|
||||
ncplane_printf(w, "Unknown keycode (%d)\n", c.gcluster);
|
||||
}
|
||||
}else{
|
||||
switch(special){
|
||||
case NCKEY_LEFT: --x; if(panelreel_move(pr, x, y)){ ++x; } break;
|
||||
case NCKEY_RIGHT: ++x; if(panelreel_move(pr, x, y)){ --x; } break;
|
||||
case NCKEY_UP: panelreel_prev(pr); break;
|
||||
case NCKEY_DOWN: panelreel_next(pr); break;
|
||||
case NCKEY_DC: kill_active_tablet(pr, tctxs); break;
|
||||
default:
|
||||
ncplane_cursor_move_yx(w, 3, 2);
|
||||
ncplane_printf(w, "Unknown special (%d)\n", special);
|
||||
}
|
||||
}
|
||||
}
|
||||
cell_release(w, &c);
|
||||
if(newtablet){
|
||||
newtablet->next = *tctxs;
|
||||
*tctxs = newtablet;
|
||||
}
|
||||
//panelreel_validate(w, pr); // do what, if not assert()ing? FIXME
|
||||
}while(!done);
|
||||
return pr;
|
||||
}
|
||||
|
||||
int panelreel_demo(struct notcurses* nc){
|
||||
tabletctx* tctxs = NULL;
|
||||
int efd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
|
||||
if(efd < 0){
|
||||
fprintf(stderr, "Error creating eventfd (%s)\n", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
struct panelreel* pr;
|
||||
if((pr = panelreel_demo_core(nc, efd, &tctxs)) == NULL){
|
||||
close(efd);
|
||||
return -1;
|
||||
}
|
||||
/*fadeout(w, FADE_MILLISECONDS);*/
|
||||
while(tctxs){
|
||||
kill_tablet(&tctxs);
|
||||
}
|
||||
close(efd);
|
||||
if(panelreel_destroy(pr)){
|
||||
fprintf(stderr, "Error destroying panelreel\n");
|
||||
return -1;
|
||||
}
|
||||
close(efd);
|
||||
return 0;
|
||||
}
|
@ -85,12 +85,12 @@ int widecolor_demo(struct notcurses* nc){
|
||||
"⑥ И҆ речѐ гдⷭ҇ь: сѐ, ро́дъ є҆ди́нъ, и҆ ѹ҆стнѣ̀ є҆ди҄нѣ всѣ́хъ, и҆ сїѐ нача́ша твори́ти: и҆ нн҃ѣ не ѡ҆скꙋдѣ́ютъ ѿ ни́хъ всѧ҄, є҆ли҄ка а́҆ще восхотѧ́тъ твори́ти.",
|
||||
"⑦ Ⱂⱃⰻⰻⰴⱑⱅⰵ ⰺ ⰺⰸⱎⰵⰴⱎⰵ ⱄⰿⱑⱄⰻⰿⱏ ⰺⰿⱏ ⱅⱆ ⱔⰸⱏⰹⰽⰻ ⰺⱈⱏ · ⰴⰰ ⱀⰵ ⱆⱄⰾⱏⰹⱎⰰⱅⱏ ⰽⱁⰶⰴⱁ ⰴⱃⱆⰳⰰ ⱄⰲⱁⰵⰳⱁ ⁖⸏",
|
||||
"काचं शक्नोम्यत्तुम् । नोपहिनस्ति माम्",
|
||||
*/
|
||||
"kācaṃ śaknomyattum; nopahinasti mām",
|
||||
"ὕαλον ϕαγεῖν δύναμαι· τοῦτο οὔ με βλάπτει",
|
||||
"Μπορῶ νὰ φάω σπασμένα γυαλιὰ χωρὶς νὰ πάθω τίποτα",
|
||||
"Vitrum edere possum; mihi non nocet",
|
||||
"🚬🌿💉💊☢☣🔫💣⚔🤜🤛🧠🦹🤺🏋️,🦔🐧🐣🦆🦢🦜🦉🐊🐸🦕 🦖🐬🐙🦂🦠🦀",
|
||||
*/
|
||||
// "🚬🌿💉💊☢☣🔫💣⚔🤜🤛🧠🦹🤺🏋️,🦔🐧🐣🦆🦢🦜🦉🐊🐸🦕 🦖🐬🐙🦂🦠🦀",
|
||||
"Je puis mangier del voirre. Ne me nuit",
|
||||
"Je peux manger du verre, ça ne me fait pas mal",
|
||||
"Pòdi manjar de veire, me nafrariá pas",
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include "egcpool.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@ -18,7 +19,35 @@ struct AVCodec;
|
||||
struct AVCodecParameters;
|
||||
struct AVPacket;
|
||||
struct SwsContext;
|
||||
struct ncplane;
|
||||
|
||||
// A plane is memory for some rectilinear virtual window, plus current cursor
|
||||
// state for that window. A notcurses context describes a single terminal, and
|
||||
// has a z-order of planes (I see no advantage to maintaining a poset, and we
|
||||
// instead just use a list, top-to-bottom). Every cell on the terminal is part
|
||||
// of at least one plane, and at least one plane covers the entirety of the
|
||||
// terminal (this plane is created during initialization).
|
||||
//
|
||||
// Functions update these virtual planes over a series of API calls. Eventually,
|
||||
// notcurses_render() is called. We then do a depth buffer blit of updated
|
||||
// cells. A cell is updated if the topmost plane including that cell updates it,
|
||||
// not simply if any plane updates it.
|
||||
//
|
||||
// A plane may be partially or wholly offscreen--this might occur if the
|
||||
// screen is resized, for example. Offscreen portions will not be rendered.
|
||||
// Accesses beyond the borders of a panel, however, are errors.
|
||||
typedef struct ncplane {
|
||||
cell* fb; // "framebuffer" of character cells
|
||||
int x, y; // current location within this plane
|
||||
int absx, absy; // origin of the plane relative to the screen
|
||||
int lenx, leny; // size of the plane, [0..len{x,y}) is addressable
|
||||
struct ncplane* z; // plane below us
|
||||
egcpool pool; // attached storage pool for UTF-8 EGCs
|
||||
uint64_t channels; // works the same way as cells
|
||||
uint32_t attrword; // same deal as in a cell
|
||||
void* userptr; // slot for the user to stick some opaque pointer
|
||||
cell background; // cell written anywhere that fb[i].gcluster == 0
|
||||
struct notcurses* nc; // notcurses object of which we are a part
|
||||
} ncplane;
|
||||
|
||||
typedef struct ncvisual {
|
||||
struct AVFormatContext* fmtctx;
|
||||
@ -31,7 +60,7 @@ typedef struct ncvisual {
|
||||
struct SwsContext* swsctx;
|
||||
int packet_outstanding;
|
||||
int dstwidth, dstheight;
|
||||
struct ncplane* ncp;
|
||||
ncplane* ncp;
|
||||
} ncvisual;
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
@ -26,35 +26,6 @@
|
||||
|
||||
#define ESC "\x1b"
|
||||
|
||||
// A plane is memory for some rectilinear virtual window, plus current cursor
|
||||
// state for that window. A notcurses context describes a single terminal, and
|
||||
// has a z-order of planes (I see no advantage to maintaining a poset, and we
|
||||
// instead just use a list, top-to-bottom). Every cell on the terminal is part
|
||||
// of at least one plane, and at least one plane covers the entirety of the
|
||||
// terminal (this plane is created during initialization).
|
||||
//
|
||||
// Functions update these virtual planes over a series of API calls. Eventually,
|
||||
// notcurses_render() is called. We then do a depth buffer blit of updated
|
||||
// cells. A cell is updated if the topmost plane including that cell updates it,
|
||||
// not simply if any plane updates it.
|
||||
//
|
||||
// A plane may be partially or wholly offscreen--this might occur if the
|
||||
// screen is resized, for example. Offscreen portions will not be rendered.
|
||||
// Accesses beyond the borders of a panel, however, are errors.
|
||||
typedef struct ncplane {
|
||||
cell* fb; // "framebuffer" of character cells
|
||||
int x, y; // current location within this plane
|
||||
int absx, absy; // origin of the plane relative to the screen
|
||||
int lenx, leny; // size of the plane, [0..len{x,y}) is addressable
|
||||
struct ncplane* z; // plane below us
|
||||
egcpool pool; // attached storage pool for UTF-8 EGCs
|
||||
uint64_t channels; // works the same way as cells
|
||||
uint32_t attrword; // same deal as in a cell
|
||||
void* userptr; // slot for the user to stick some opaque pointer
|
||||
cell background; // cell written anywhere that fb[i].gcluster == 0
|
||||
struct notcurses* nc; // notcurses object of which we are a part
|
||||
} ncplane;
|
||||
|
||||
typedef struct ncstats {
|
||||
uint64_t renders; // number of notcurses_render() runs
|
||||
uint64_t renders_ns; // number of nanoseconds spent in notcurses_render()
|
||||
@ -1445,8 +1416,12 @@ void ncplane_move_yx(ncplane* n, int y, int x){
|
||||
}
|
||||
|
||||
void ncplane_yx(const ncplane* n, int* y, int* x){
|
||||
*y = n->absy;
|
||||
*x = n->absx;
|
||||
if(y){
|
||||
*y = n->absy;
|
||||
}
|
||||
if(x){
|
||||
*x = n->absx;
|
||||
}
|
||||
}
|
||||
|
||||
// copy the UTF8-encoded EGC out of the cell, whether simple or complex. the
|
||||
|
816
src/lib/panelreel.c
Normal file
816
src/lib/panelreel.c
Normal file
@ -0,0 +1,816 @@
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "notcurses.h"
|
||||
#include "internal.h"
|
||||
|
||||
// Tablets are the toplevel entitites within a panelreel. Each corresponds to
|
||||
// a single, distinct ncplane.
|
||||
typedef struct tablet {
|
||||
ncplane* p; // visible panel, NULL when offscreen
|
||||
struct tablet* next;
|
||||
struct tablet* prev;
|
||||
tabletcb cbfxn; // application callback to draw tablet
|
||||
void* curry; // application data provided to cbfxn
|
||||
} tablet;
|
||||
|
||||
// The visible screen can be reconstructed from three things:
|
||||
// * which tablet is focused (pointed at by tablets)
|
||||
// * which row the focused tablet starts at (derived from focused window)
|
||||
// * the list of tablets (available from the focused tablet)
|
||||
typedef struct panelreel {
|
||||
ncplane* p; // ncplane this panelreel occupies, under tablets
|
||||
panelreel_options popts; // copied in panelreel_create()
|
||||
// doubly-linked list, a circular one when infinity scrolling is in effect.
|
||||
// points at the focused tablet (when at least one tablet exists, one must be
|
||||
// focused), which might be anywhere on the screen (but is always visible).
|
||||
int efd; // eventfd, signaled in panelreel_touch() if >= 0
|
||||
tablet* tablets;
|
||||
// these values could all be derived at any time, but keeping them computed
|
||||
// makes other things easier, or saves us time (at the cost of complexity).
|
||||
int tabletcount; // could be derived, but we keep it o(1)
|
||||
// last direction in which we moved. positive if we moved down ("next"),
|
||||
// negative if we moved up ("prev"), 0 for non-linear operation. we start
|
||||
// drawing unfocused tablets opposite the direction of our last movement, so
|
||||
// that movement in an unfilled reel doesn't reorient our tablets.
|
||||
int last_traveled_direction;
|
||||
// are all of our tablets currently visible? our arrangement algorithm works
|
||||
// differently when the reel is not completely filled. ideally we'd unite the
|
||||
// two modes, but for now, check this bool and take one of two paths.
|
||||
bool all_visible;
|
||||
} panelreel;
|
||||
|
||||
// Returns the starting coordinates (relative to the screen) of the specified
|
||||
// window, and its length. End is (begx + lenx - 1, begy + leny - 1).
|
||||
static inline void
|
||||
window_coordinates(const ncplane* w, int* begy, int* begx, int* leny, int* lenx){
|
||||
ncplane_yx(w, begy, begx);
|
||||
ncplane_dim_yx(w, leny, lenx);
|
||||
}
|
||||
|
||||
// FIXME compatability wrapper for libpanel
|
||||
int wresize(ncplane* n, int leny, int lenx){
|
||||
int y, x;
|
||||
ncplane_yx(n, &y, &x);
|
||||
int dimy, dimx;
|
||||
ncplane_dim_yx(n, &dimy, &dimx);
|
||||
return ncplane_resize(n, 0, 0, dimy, dimx, y, x, leny, lenx);
|
||||
}
|
||||
|
||||
// bchrs: 6-element array of wide border characters + attributes FIXME
|
||||
static int
|
||||
draw_borders(ncplane* w, unsigned nobordermask, const cell* attr,
|
||||
bool cliphead, bool clipfoot){
|
||||
int begx, begy, lenx, leny;
|
||||
int ret = 0;
|
||||
window_coordinates(w, &begy, &begx, &leny, &lenx);
|
||||
begx = 0;
|
||||
begy = 0;
|
||||
int maxx = begx + lenx - 1;
|
||||
int maxy = begy + leny - 1;
|
||||
cell ul, ur, ll, lr, hl, vl;
|
||||
cell_init(&ul); cell_init(&ur); cell_init(&hl);
|
||||
cell_init(&ll); cell_init(&lr); cell_init(&vl);
|
||||
if(ncplane_rounded_box_cells(w, &ul, &ur, &ll, &lr, &hl, &vl)){
|
||||
return -1;
|
||||
}
|
||||
fprintf(stderr, "drawing borders %d/%d->%d/%d, mask: %04x, clipping: %c%c\n",
|
||||
begx, begy, maxx, maxy, nobordermask,
|
||||
cliphead ? 'T' : 't', clipfoot ? 'F' : 'f');
|
||||
ul.attrword = attr->attrword; ul.channels = attr->channels;
|
||||
ur.attrword = attr->attrword; ur.channels = attr->channels;
|
||||
ll.attrword = attr->attrword; ll.channels = attr->channels;
|
||||
lr.attrword = attr->attrword; lr.channels = attr->channels;
|
||||
hl.attrword = attr->attrword; hl.channels = attr->channels;
|
||||
vl.attrword = attr->attrword; vl.channels = attr->channels;
|
||||
if(!cliphead){
|
||||
// lenx - begx + 1 is the number of columns we have, but drop 2 due to
|
||||
// corners. we thus want lenx - begx - 1 horizontal lines.
|
||||
if(!(nobordermask & BORDERMASK_TOP)){
|
||||
ret |= ncplane_cursor_move_yx(w, begy, begx);
|
||||
ncplane_putc(w, &ul);
|
||||
ncplane_hline(w, &hl, lenx - 2);
|
||||
ncplane_putc(w, &ur);
|
||||
}else{
|
||||
if(!(nobordermask & BORDERMASK_LEFT)){
|
||||
ret |= ncplane_cursor_move_yx(w, begy, begx);
|
||||
ncplane_putc(w, &ul);
|
||||
}
|
||||
if(!(nobordermask & BORDERMASK_RIGHT)){
|
||||
ret |= ncplane_cursor_move_yx(w, begy, maxx);
|
||||
ncplane_putc(w, &ur);
|
||||
}
|
||||
}
|
||||
}
|
||||
int y;
|
||||
for(y = begy + !cliphead ; y < maxy + !!clipfoot ; ++y){
|
||||
if(!(nobordermask & BORDERMASK_LEFT)){
|
||||
ret |= ncplane_cursor_move_yx(w, y, begx);
|
||||
ncplane_putc(w, &vl);
|
||||
}
|
||||
if(!(nobordermask & BORDERMASK_RIGHT)){
|
||||
ret |= ncplane_cursor_move_yx(w, y, maxx);
|
||||
ncplane_putc(w, &vl);
|
||||
}
|
||||
}
|
||||
if(!clipfoot){
|
||||
if(!(nobordermask & BORDERMASK_BOTTOM)){
|
||||
ret |= ncplane_cursor_move_yx(w, maxy, begx);
|
||||
ncplane_putc(w, &ll);
|
||||
ncplane_hline(w, &hl, lenx - 2);
|
||||
ncplane_putc(w, &lr);
|
||||
}else{
|
||||
if(!(nobordermask & BORDERMASK_LEFT)){
|
||||
ret |= ncplane_cursor_move_yx(w, maxy, begx);
|
||||
ret |= ncplane_putc(w, &ll);
|
||||
}
|
||||
if(!(nobordermask & BORDERMASK_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.
|
||||
ret |= ncplane_cursor_move_yx(w, maxy, maxx);
|
||||
ret |= ncplane_putc(w, &lr);
|
||||
}
|
||||
}
|
||||
}
|
||||
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 %d %d clip: %c%c ret: %d\n",
|
||||
// begx, begy, maxx, maxy, cliphead ? 'y' : 'n', clipfoot ? 'y' : 'n', ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Draws the border (if one should be drawn) around the panelreel, and enforces
|
||||
// any provided restrictions on visible window size.
|
||||
static int
|
||||
draw_panelreel_borders(const panelreel* pr){
|
||||
int begx, begy;
|
||||
int maxx, maxy;
|
||||
window_coordinates(pr->p, &begy, &begx, &maxy, &maxx);
|
||||
assert(begy >= 0 && begx >= 0);
|
||||
assert(maxy >= 0 && maxx >= 0);
|
||||
--maxx; // last column we can safely write to
|
||||
--maxy; // last line we can safely write to
|
||||
if(begx >= maxx || maxx - begx + 1 < pr->popts.min_supported_rows){
|
||||
return 0; // no room
|
||||
}
|
||||
if(begy >= maxy || maxy - begy + 1 < pr->popts.min_supported_cols){
|
||||
return 0; // no room
|
||||
}
|
||||
return draw_borders(pr->p, pr->popts.bordermask, &pr->popts.borderattr, false, false);
|
||||
}
|
||||
|
||||
// Calculate the starting and ending coordinates available for occupation by
|
||||
// the tablet, relative to the panelreel's ncplane. Returns non-zero if the
|
||||
// tablet cannot be made visible as specified. If this is the focused tablet
|
||||
// (direction == 0), 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 panelreel* pr, int* begx, int* begy, int* lenx, int* leny,
|
||||
int frontiery, int direction){
|
||||
window_coordinates(pr->p, begy, begx, leny, lenx);
|
||||
int maxy = *leny + *begy - 1;
|
||||
int begindraw = *begy + !(pr->popts.bordermask & BORDERMASK_TOP);
|
||||
// FIXME i think this fails to account for an absent panelreel bottom?
|
||||
int enddraw = maxy - !(pr->popts.bordermask & BORDERMASK_TOP);
|
||||
if(direction){
|
||||
if(frontiery < begindraw){
|
||||
return -1;
|
||||
}
|
||||
if(frontiery > enddraw){
|
||||
// fprintf(stderr, "FRONTIER: %d ENDDRAW: %d\n", frontiery, enddraw);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
// account for the panelreel borders
|
||||
if(direction <= 0 && !(pr->popts.bordermask & BORDERMASK_TOP)){
|
||||
++*begy;
|
||||
--*leny;
|
||||
}
|
||||
if(direction >= 0 && !(pr->popts.bordermask & BORDERMASK_BOTTOM)){
|
||||
--*leny;
|
||||
}
|
||||
if(!(pr->popts.bordermask & BORDERMASK_LEFT)){
|
||||
++*begx;
|
||||
--*lenx;
|
||||
}
|
||||
if(!(pr->popts.bordermask & BORDERMASK_RIGHT)){
|
||||
--*lenx;
|
||||
}
|
||||
// at this point, our coordinates describe the largest possible tablet for
|
||||
// this panelreel. 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 > 0){
|
||||
*leny -= (frontiery - *begy);
|
||||
*begy = frontiery;
|
||||
}else if(direction < 0){
|
||||
*leny = frontiery - *begy + 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Draw the specified tablet, if possible. A direction less than 0 means we're
|
||||
// laying out towards the top. Greater than zero 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.
|
||||
static int
|
||||
panelreel_draw_tablet(const panelreel* pr, tablet* t, int frontiery,
|
||||
int direction){
|
||||
int lenx, leny, begy, begx;
|
||||
ncplane* fp = t->p;
|
||||
if(tablet_columns(pr, &begx, &begy, &lenx, &leny, frontiery, direction)){
|
||||
//fprintf(stderr, "no room: %p:%p base %d/%d len %d/%d\n", t, fp, begx, begy, lenx, leny);
|
||||
// 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;
|
||||
}
|
||||
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 = notcurses_newplane(pr->p->nc, 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(wresize(fp, leny, truex)){
|
||||
return -1;
|
||||
}
|
||||
truey = leny;
|
||||
}
|
||||
if(begy != trueby){
|
||||
ncplane_move_yx(fp, begy, begx);
|
||||
}
|
||||
}
|
||||
if(wresize(fp, leny, lenx)){
|
||||
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(direction < 0 && !(pr->popts.tabletmask & BORDERMASK_BOTTOM)){
|
||||
--cbmaxy;
|
||||
}
|
||||
// If we're drawing down, we'll always have a top border unless it's masked
|
||||
if(direction >= 0 && !(pr->popts.tabletmask & BORDERMASK_TOP)){
|
||||
++cby;
|
||||
}
|
||||
// Adjust the x-bounds for side borders, which we always have if unmasked
|
||||
cbmaxx -= !(pr->popts.tabletmask & BORDERMASK_RIGHT);
|
||||
cbx += !(pr->popts.tabletmask & BORDERMASK_LEFT);
|
||||
bool cbdir = direction < 0 ? 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(fp, cbx, cby, cbmaxx, cbmaxy, cbdir, t->curry);
|
||||
//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)
|
||||
++ll; // account for that border
|
||||
wresize(fp, ll, lenx);
|
||||
if(direction < 0){
|
||||
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{ // both borders are visible
|
||||
ll += 2; // account for both borders
|
||||
// fprintf(stderr, "RESIZING (-2) from %d to %d\n", leny, ll);
|
||||
wresize(fp, ll, lenx);
|
||||
if(direction < 0){
|
||||
// fprintf(stderr, "MOVEDOWN UNCLIPPED (skip %d)\n", leny - ll);
|
||||
ncplane_move_yx(fp, begy + leny - ll, begx);
|
||||
}
|
||||
}
|
||||
// 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(direction == 0){
|
||||
if(leny - frontiery + 1 < ll){
|
||||
//fprintf(stderr, "frontieryIZING ADJ %d %d %d %d NEW %d\n", cbmaxy, leny,
|
||||
// frontiery, ll, frontiery - ll + 1);
|
||||
int dontcarex;
|
||||
ncplane_yx(pr->p, &frontiery, &dontcarex);
|
||||
frontiery += (leny - ll + 1);
|
||||
}
|
||||
ncplane_move_yx(fp, frontiery, begx);
|
||||
}
|
||||
}
|
||||
draw_borders(fp, pr->popts.tabletmask,
|
||||
direction == 0 ? &pr->popts.focusedattr : &pr->popts.tabletattr,
|
||||
cliphead, clipfoot);
|
||||
return cliphead || clipfoot;
|
||||
}
|
||||
|
||||
// draw and size the focused tablet, which must exist (pr->tablets may not be
|
||||
// NULL). it can occupy the entire panelreel.
|
||||
static int
|
||||
draw_focused_tablet(const panelreel* pr){
|
||||
int pbegy, pbegx, plenx, pleny; // panelreel window coordinates
|
||||
window_coordinates(pr->p, &pbegy, &pbegx, &pleny, &plenx);
|
||||
int fulcrum;
|
||||
if(pr->tablets->p == NULL){
|
||||
if(pr->last_traveled_direction >= 0){
|
||||
fulcrum = pleny + pbegy - !(pr->popts.bordermask & BORDERMASK_BOTTOM);
|
||||
}else{
|
||||
fulcrum = pbegy + !(pr->popts.bordermask & BORDERMASK_TOP);
|
||||
}
|
||||
}else{ // focused was already present. want to stay where we are, if possible
|
||||
int dontcarex;
|
||||
ncplane_yx(pr->tablets->p, &fulcrum, &dontcarex);
|
||||
// FIXME ugh can't we just remember the previous fulcrum?
|
||||
int prevfulcrum;
|
||||
ncplane_yx(pr->tablets->prev->p, &prevfulcrum, &dontcarex);
|
||||
int nextfulcrum;
|
||||
ncplane_yx(pr->tablets->next->p, &nextfulcrum, &dontcarex);
|
||||
if(pr->last_traveled_direction > 0 && fulcrum < prevfulcrum){
|
||||
fulcrum = pleny + pbegy - !(pr->popts.bordermask & BORDERMASK_BOTTOM);
|
||||
}else if(pr->last_traveled_direction < 0 && fulcrum > nextfulcrum){
|
||||
fulcrum = pbegy + !(pr->popts.bordermask & BORDERMASK_TOP);
|
||||
}
|
||||
}
|
||||
//fprintf(stderr, "PR dims: %d/%d + %d/%d fulcrum: %d\n", pbegy, pbegx, pleny, plenx, fulcrum);
|
||||
panelreel_draw_tablet(pr, pr->tablets, fulcrum, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// move down below the focused tablet, filling up the reel to the bottom.
|
||||
// returns the last tablet drawn.
|
||||
static tablet*
|
||||
draw_following_tablets(const panelreel* pr, const tablet* otherend){
|
||||
int wmaxy, wbegy, wbegx, wlenx, wleny; // working tablet window coordinates
|
||||
tablet* working = pr->tablets;
|
||||
int frontiery;
|
||||
// move down past the focused tablet, filling up the reel to the bottom
|
||||
while(working->next != otherend || otherend->p == NULL){
|
||||
window_coordinates(working->p, &wbegy, &wbegx, &wleny, &wlenx);
|
||||
wmaxy = wbegy + wleny - 1;
|
||||
frontiery = wmaxy + 2;
|
||||
//fprintf(stderr, "EASTBOUND AND DOWN: %d %d\n", frontiery, wmaxy + 2);
|
||||
working = working->next;
|
||||
panelreel_draw_tablet(pr, working, frontiery, 1);
|
||||
if(working->p == NULL){ // FIXME might be more to hide
|
||||
break;
|
||||
}
|
||||
}
|
||||
// FIXME keep going forward, hiding those no longer visible
|
||||
return working;
|
||||
}
|
||||
|
||||
// move up above the focused tablet, filling up the reel to the top.
|
||||
// returns the last tablet drawn.
|
||||
static tablet*
|
||||
draw_previous_tablets(const panelreel* pr, const tablet* otherend){
|
||||
int wbegy, wbegx, wlenx, wleny; // working tablet window coordinates
|
||||
tablet* upworking = pr->tablets;
|
||||
int frontiery;
|
||||
while(upworking->prev != otherend || otherend->p == NULL){
|
||||
window_coordinates(upworking->p, &wbegy, &wbegx, &wleny, &wlenx);
|
||||
//fprintf(stderr, "MOVIN' ON UP: %d %d\n", frontiery, wbegy - 2);
|
||||
frontiery = wbegy - 2;
|
||||
upworking = upworking->prev;
|
||||
panelreel_draw_tablet(pr, upworking, frontiery, -1);
|
||||
if(upworking->p){
|
||||
window_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;
|
||||
}
|
||||
if(upworking == otherend){
|
||||
otherend = otherend->prev;
|
||||
}
|
||||
}
|
||||
// FIXME keep going backwards, hiding those no longer visible
|
||||
return upworking;
|
||||
}
|
||||
|
||||
// all tablets must be visible (valid ->p), and at least one tablet must exist
|
||||
static tablet*
|
||||
find_topmost(panelreel* pr){
|
||||
tablet* t = pr->tablets;
|
||||
int curline;
|
||||
ncplane_yx(t->p, &curline, NULL);
|
||||
int trialline;
|
||||
ncplane_yx(t->prev->p, &trialline, NULL);
|
||||
while(trialline < curline){
|
||||
t = t->prev;
|
||||
curline = trialline;
|
||||
ncplane_yx(t->prev->p, &trialline, NULL);
|
||||
}
|
||||
// fprintf(stderr, "topmost: %p @ %d\n", t, curline);
|
||||
return t;
|
||||
}
|
||||
|
||||
// all the tablets are believed to be wholly visible. in this case, we only want
|
||||
// to fill up the necessary top rows, even if it means moving everything up at
|
||||
// the end. large gaps should always be at the bottom to avoid ui discontinuity.
|
||||
// this must only be called if we actually have at least one tablet. note that
|
||||
// as a result of this function, we might not longer all be wholly visible.
|
||||
// good god almighty, this is some fucking garbage.
|
||||
static int
|
||||
panelreel_arrange_denormalized(panelreel* pr){
|
||||
// we'll need the starting line of the tablet which just lost focus, and the
|
||||
// starting line of the tablet which just gained focus.
|
||||
int fromline, nowline;
|
||||
ncplane_yx(pr->tablets->p, &nowline, NULL);
|
||||
// we've moved to the next or previous tablet. either we were not at the end,
|
||||
// in which case we can just move the focus, or we were at the end, in which
|
||||
// case we need bring the target tablet to our end, and draw in the direction
|
||||
// opposite travel (a single tablet is a trivial case of the latter case).
|
||||
// how do we know whether we were at the end? if the new line is not in the
|
||||
// direction of movement relative to the old one, of course!
|
||||
tablet* topmost = find_topmost(pr);
|
||||
int wbegy, wbegx, wleny, wlenx;
|
||||
window_coordinates(pr->p, &wbegy, &wbegx, &wleny, &wlenx);
|
||||
int frontiery = wbegy + !(pr->popts.bordermask & BORDERMASK_TOP);
|
||||
if(pr->last_traveled_direction >= 0){
|
||||
ncplane_yx(pr->tablets->prev->p, &fromline, NULL);
|
||||
if(fromline > nowline){ // keep the order we had
|
||||
topmost = topmost->next;
|
||||
}
|
||||
}else{
|
||||
ncplane_yx(pr->tablets->next->p, &fromline, NULL);
|
||||
if(fromline < nowline){ // keep the order we had
|
||||
topmost = topmost->prev;
|
||||
}
|
||||
}
|
||||
// fprintf(stderr, "gotta draw 'em all FROM: %d NOW: %d!\n", fromline, nowline);
|
||||
tablet* t = topmost;
|
||||
do{
|
||||
int broken;
|
||||
if(t == pr->tablets){
|
||||
broken = panelreel_draw_tablet(pr, t, frontiery, 0);
|
||||
}else{
|
||||
broken = panelreel_draw_tablet(pr, t, frontiery, 1);
|
||||
}
|
||||
if(t->p == NULL || broken){
|
||||
pr->all_visible = false;
|
||||
break;
|
||||
}
|
||||
int dontcarex, basey;
|
||||
ncplane_dim_yx(t->p, &frontiery, &dontcarex);
|
||||
ncplane_yx(t->p, &basey, &dontcarex);
|
||||
frontiery += basey + 1;
|
||||
}while((t = t->next) != topmost);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Arrange the panels, starting with the focused window, wherever it may be.
|
||||
// If necessary, resize it to the full size of the reel--focus has its
|
||||
// privileges. We then work in the opposite direction of travel, filling out
|
||||
// the reel above and below. If we moved down to get here, do the tablets above
|
||||
// first. If we moved up, do the tablets below. This ensures tablets stay in
|
||||
// place relative to the new focus; they could otherwise pivot around the new
|
||||
// focus, if we're not filling out the reel.
|
||||
//
|
||||
// This can still leave a gap plus a partially-onscreen tablet FIXME
|
||||
static int
|
||||
panelreel_arrange(panelreel* pr){
|
||||
tablet* focused = pr->tablets;
|
||||
if(focused == NULL){
|
||||
return 0; // if none are focused, none exist
|
||||
}
|
||||
// FIXME we special-cased this because i'm dumb and couldn't think of a more
|
||||
// elegant way to do this. we keep 'all_visible' as boolean state to avoid
|
||||
// having to do an o(n) iteration each round, but this is still grotesque, and
|
||||
// feels fragile...
|
||||
if(pr->all_visible){
|
||||
return panelreel_arrange_denormalized(pr);
|
||||
}
|
||||
draw_focused_tablet(pr);
|
||||
tablet* otherend = focused;
|
||||
if(pr->last_traveled_direction >= 0){
|
||||
otherend = draw_previous_tablets(pr, otherend);
|
||||
otherend = draw_following_tablets(pr, otherend);
|
||||
}else{
|
||||
otherend = draw_following_tablets(pr, otherend);
|
||||
otherend = draw_previous_tablets(pr, otherend);
|
||||
}
|
||||
// FIXME move them up to plug any holes in original direction?
|
||||
fprintf(stderr, "DONE ARRANGING\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int panelreel_redraw(panelreel* pr){
|
||||
fprintf(stderr, "--------> BEGIN REDRAW <--------\n");
|
||||
int ret = 0;
|
||||
if(draw_panelreel_borders(pr)){
|
||||
return -1; // enforces specified dimensional minima
|
||||
}
|
||||
ret |= panelreel_arrange(pr);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool
|
||||
validate_panelreel_opts(ncplane* w, const panelreel_options* popts){
|
||||
if(w == NULL){
|
||||
return false;
|
||||
}
|
||||
if(!popts->infinitescroll){
|
||||
if(popts->circular){
|
||||
return false; // can't set circular without infinitescroll
|
||||
}
|
||||
}
|
||||
const unsigned fullmask = BORDERMASK_LEFT |
|
||||
BORDERMASK_RIGHT |
|
||||
BORDERMASK_TOP |
|
||||
BORDERMASK_BOTTOM;
|
||||
if(popts->bordermask > fullmask){
|
||||
return false;
|
||||
}
|
||||
if(popts->tabletmask > fullmask){
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
panelreel* panelreel_create(ncplane* w, const panelreel_options* popts, int efd){
|
||||
panelreel* pr;
|
||||
|
||||
if(!validate_panelreel_opts(w, popts)){
|
||||
return NULL;
|
||||
}
|
||||
if((pr = malloc(sizeof(*pr))) == NULL){
|
||||
return NULL;
|
||||
}
|
||||
pr->efd = efd;
|
||||
pr->tablets = NULL;
|
||||
pr->tabletcount = 0;
|
||||
pr->all_visible = true;
|
||||
pr->last_traveled_direction = -1; // draw down after the initial tablet
|
||||
memcpy(&pr->popts, popts, sizeof(*popts));
|
||||
int maxx, maxy, wx, wy;
|
||||
window_coordinates(w, &wy, &wx, &maxy, &maxx);
|
||||
--maxy;
|
||||
--maxx;
|
||||
int ylen, xlen;
|
||||
ylen = maxy - popts->boff - popts->toff + 1;
|
||||
if(ylen < 0){
|
||||
ylen = maxy - popts->toff;
|
||||
if(ylen < 0){
|
||||
ylen = 0; // but this translates to a full-screen window...FIXME
|
||||
}
|
||||
}
|
||||
xlen = maxx - popts->roff - popts->loff + 1;
|
||||
if(xlen < 0){
|
||||
xlen = maxx - popts->loff;
|
||||
if(xlen < 0){
|
||||
xlen = 0; // FIXME see above...
|
||||
}
|
||||
}
|
||||
if((pr->p = notcurses_newplane(w->nc, ylen, xlen, popts->toff + wy, popts->loff + wx, NULL)) == NULL){
|
||||
free(pr);
|
||||
return NULL;
|
||||
}
|
||||
if(panelreel_redraw(pr)){
|
||||
ncplane_destroy(pr->p);
|
||||
free(pr);
|
||||
return NULL;
|
||||
}
|
||||
return pr;
|
||||
}
|
||||
|
||||
// we've just added a new tablet. it need be inserted at the correct place in
|
||||
// the reel. this will naturally fall out of things if the panelreel is full; we
|
||||
// can just call panelreel_redraw(). otherwise, we need make ourselves at least
|
||||
// minimally visible, to satisfy the preconditions of
|
||||
// panelreel_arrange_denormalized(). this function, and approach, is shit.
|
||||
// FIXME get rid of nc param here
|
||||
static tablet*
|
||||
insert_new_panel(struct notcurses* nc, panelreel* pr, tablet* t){
|
||||
if(!pr->all_visible){
|
||||
return t;
|
||||
}
|
||||
int wbegy, wbegx, wleny, wlenx; // params of PR
|
||||
window_coordinates(pr->p, &wbegy, &wbegx, &wleny, &wlenx);
|
||||
// are we the only tablet?
|
||||
int begx, begy, lenx, leny, frontiery;
|
||||
if(t->prev == t){
|
||||
frontiery = wbegy + !(pr->popts.bordermask & BORDERMASK_TOP);
|
||||
if(tablet_columns(pr, &begx, &begy, &lenx, &leny, frontiery, 1)){
|
||||
pr->all_visible = false;
|
||||
return t;
|
||||
}
|
||||
fprintf(stderr, "newwin: %d/%d + %d/%d\n", begy, begx, leny, lenx);
|
||||
if((t->p = notcurses_newplane(nc, leny, lenx, begy, begx, NULL)) == NULL){
|
||||
pr->all_visible = false;
|
||||
return t;
|
||||
}
|
||||
fprintf(stderr, "created first tablet!\n");
|
||||
return t;
|
||||
}
|
||||
// we're not the only tablet, alas.
|
||||
// our new window needs to be after our prev
|
||||
int dontcarex;
|
||||
ncplane_yx(t->prev->p, &frontiery, &dontcarex);
|
||||
int dimprevy, dimprevx;
|
||||
ncplane_dim_yx(t->prev->p, &dimprevy, &dimprevx);
|
||||
frontiery += dimprevy + 2;
|
||||
frontiery += 2;
|
||||
if(tablet_columns(pr, &begx, &begy, &lenx, &leny, frontiery, 1)){
|
||||
pr->all_visible = false;
|
||||
return t;
|
||||
}
|
||||
if((t->p = notcurses_newplane(nc, 2, lenx, begy, begx, NULL)) == NULL){
|
||||
pr->all_visible = false;
|
||||
return t;
|
||||
}
|
||||
// FIXME push the other ones down by 4
|
||||
return t;
|
||||
}
|
||||
|
||||
tablet* panelreel_add(panelreel* pr, tablet* after, tablet *before,
|
||||
tabletcb cbfxn, void* opaque){
|
||||
tablet* t;
|
||||
if(after && before){
|
||||
if(after->prev != before || before->next != after){
|
||||
return NULL;
|
||||
}
|
||||
}else if(!after && !before){
|
||||
// This way, without user interaction or any specification, new tablets are
|
||||
// inserted at the "end" relative to the focus. The first one to be added
|
||||
// gets and keeps the focus. New ones will go on the bottom, until we run
|
||||
// out of space. New tablets are then created off-screen.
|
||||
before = pr->tablets;
|
||||
}
|
||||
if((t = malloc(sizeof(*t))) == NULL){
|
||||
return NULL;
|
||||
}
|
||||
fprintf(stderr, "--------->NEW TABLET %p\n", t);
|
||||
if(after){
|
||||
t->next = after->next;
|
||||
after->next = t;
|
||||
t->prev = after;
|
||||
t->next->prev = t;
|
||||
}else if(before){
|
||||
t->prev = before->prev;
|
||||
before->prev = t;
|
||||
t->next = before;
|
||||
t->prev->next = t;
|
||||
}else{ // we're the first tablet
|
||||
t->prev = t->next = t;
|
||||
pr->tablets = t;
|
||||
}
|
||||
t->cbfxn = cbfxn;
|
||||
t->curry = opaque;
|
||||
++pr->tabletcount;
|
||||
t->p = NULL;
|
||||
// if we have room, it needs become visible immediately, in the proper place,
|
||||
// lest we invalidate the preconditions of panelreel_arrange_denormalized().
|
||||
insert_new_panel(pr->p->nc, pr, t);
|
||||
panelreel_redraw(pr); // don't return failure; tablet was still created...
|
||||
return t;
|
||||
}
|
||||
|
||||
int panelreel_del_focused(panelreel* pr){
|
||||
return panelreel_del(pr, pr->tablets);
|
||||
}
|
||||
|
||||
int panelreel_del(panelreel* pr, struct tablet* t){
|
||||
if(pr == NULL || t == NULL){
|
||||
return -1;
|
||||
}
|
||||
t->prev->next = t->next;
|
||||
if(pr->tablets == t){
|
||||
if((pr->tablets = t->next) == t){
|
||||
pr->tablets = NULL;
|
||||
}
|
||||
}
|
||||
t->next->prev = t->prev;
|
||||
if(t->p){
|
||||
ncplane_destroy(t->p);
|
||||
}
|
||||
free(t);
|
||||
--pr->tabletcount;
|
||||
panelreel_redraw(pr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int panelreel_destroy(panelreel* preel){
|
||||
int ret = 0;
|
||||
if(preel){
|
||||
tablet* t = preel->tablets;
|
||||
while(t){
|
||||
t->prev->next = NULL;
|
||||
tablet* tmp = t->next;
|
||||
panelreel_del(preel, t);
|
||||
t = tmp;
|
||||
}
|
||||
free(preel);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int panelreel_tabletcount(const panelreel* preel){
|
||||
return preel->tabletcount;
|
||||
}
|
||||
|
||||
int panelreel_touch(panelreel* pr, tablet* t){
|
||||
(void)t; // FIXME make these more granular eventually
|
||||
int ret = 0;
|
||||
if(pr->efd >= 0){
|
||||
uint64_t val = 1;
|
||||
if(write(pr->efd, &val, sizeof(val)) != sizeof(val)){
|
||||
fprintf(stderr, "Error writing to eventfd %d (%s)\n",
|
||||
pr->efd, strerror(errno));
|
||||
ret = -1;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Move to some position relative to the current position
|
||||
static int
|
||||
move_tablet(ncplane* p, int deltax, int deltay){
|
||||
int oldx, oldy;
|
||||
ncplane_yx(p, &oldy, &oldx);
|
||||
int x = oldx + deltax;
|
||||
int y = oldy + deltay;
|
||||
ncplane_move_yx(p, y, x);
|
||||
return 0;
|
||||
}
|
||||
|
||||
tablet* panelreel_focused(panelreel* pr){
|
||||
return pr->tablets;
|
||||
}
|
||||
|
||||
int panelreel_move(panelreel* preel, int x, int y){
|
||||
ncplane* w = preel->p;
|
||||
int oldx, oldy;
|
||||
ncplane_yx(w, &oldy, &oldx);
|
||||
const int deltax = x - oldx;
|
||||
const int deltay = y - oldy;
|
||||
if(move_tablet(preel->p, deltax, deltay)){
|
||||
ncplane_move_yx(preel->p, oldy, oldx);
|
||||
panelreel_redraw(preel);
|
||||
return -1;
|
||||
}
|
||||
if(preel->tablets){
|
||||
tablet* t = preel->tablets;
|
||||
do{
|
||||
if(t->p == NULL){
|
||||
break;
|
||||
}
|
||||
move_tablet(t->p, deltax, deltay);
|
||||
}while((t = t->prev) != preel->tablets);
|
||||
if(t != preel->tablets){ // don't repeat if we covered all tablets
|
||||
for(t = preel->tablets->next ; t != preel->tablets ; t = t->next){
|
||||
if(t->p == NULL){
|
||||
break;
|
||||
}
|
||||
move_tablet(t->p, deltax, deltay);
|
||||
}
|
||||
}
|
||||
}
|
||||
panelreel_redraw(preel);
|
||||
return 0;
|
||||
}
|
||||
|
||||
tablet* panelreel_next(panelreel* pr){
|
||||
if(pr->tablets){
|
||||
pr->tablets = pr->tablets->next;
|
||||
fprintf(stderr, "---------------> moved to next, %p to %p <----------\n",
|
||||
pr->tablets->prev, pr->tablets);
|
||||
pr->last_traveled_direction = 1;
|
||||
}
|
||||
panelreel_redraw(pr);
|
||||
return pr->tablets;
|
||||
}
|
||||
|
||||
tablet* panelreel_prev(panelreel* pr){
|
||||
if(pr->tablets){
|
||||
pr->tablets = pr->tablets->prev;
|
||||
fprintf(stderr, "----------------> moved to prev, %p to %p <----------\n",
|
||||
pr->tablets->next, pr->tablets);
|
||||
pr->last_traveled_direction = -1;
|
||||
}
|
||||
panelreel_redraw(pr);
|
||||
return pr->tablets;
|
||||
}
|
233
tests/panelreel.cpp
Normal file
233
tests/panelreel.cpp
Normal file
@ -0,0 +1,233 @@
|
||||
#include "main.h"
|
||||
#include <iostream>
|
||||
|
||||
class PanelReelTest : public :: testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
setlocale(LC_ALL, nullptr);
|
||||
if(getenv("TERM") == nullptr){
|
||||
GTEST_SKIP();
|
||||
}
|
||||
notcurses_options nopts{};
|
||||
nopts.inhibit_alternate_screen = true;
|
||||
nopts.retain_cursor = true;
|
||||
nopts.pass_through_esc = true;
|
||||
nopts.outfp = stdin;
|
||||
nc_ = notcurses_init(&nopts);
|
||||
ASSERT_NE(nullptr, nc_);
|
||||
n_ = notcurses_stdplane(nc_);
|
||||
ASSERT_NE(nullptr, n_);
|
||||
ASSERT_EQ(0, ncplane_cursor_move_yx(n_, 0, 0));
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
if(nc_){
|
||||
EXPECT_EQ(0, notcurses_stop(nc_));
|
||||
}
|
||||
}
|
||||
|
||||
struct notcurses* nc_{};
|
||||
struct ncplane* n_{};
|
||||
};
|
||||
|
||||
|
||||
TEST_F(PanelReelTest, InitLinear) {
|
||||
panelreel_options p = { };
|
||||
struct panelreel* pr = panelreel_create(n_, &p, -1);
|
||||
ASSERT_NE(nullptr, pr);
|
||||
}
|
||||
|
||||
TEST_F(PanelReelTest, InitLinearInfinite) {
|
||||
panelreel_options p{};
|
||||
p.infinitescroll = true;
|
||||
struct panelreel* pr = panelreel_create(n_, &p, -1);
|
||||
ASSERT_NE(nullptr, pr);
|
||||
}
|
||||
|
||||
TEST_F(PanelReelTest, InitCircular) {
|
||||
panelreel_options p{};
|
||||
p.infinitescroll = true;
|
||||
p.circular = true;
|
||||
struct panelreel* pr = panelreel_create(n_, &p, -1);
|
||||
ASSERT_NE(nullptr, pr);
|
||||
ASSERT_EQ(0, panelreel_destroy(pr));
|
||||
}
|
||||
|
||||
// circular is not allowed to be true when infinitescroll is false
|
||||
TEST_F(PanelReelTest, FiniteCircleRejected) {
|
||||
panelreel_options p{};
|
||||
p.infinitescroll = false;
|
||||
p.circular = true;
|
||||
struct panelreel* pr = panelreel_create(n_, &p, -1);
|
||||
ASSERT_EQ(nullptr, pr);
|
||||
}
|
||||
|
||||
// We ought be able to invoke panelreel_next() and panelreel_prev() safely,
|
||||
// even if there are no tablets. They both ought return nullptr.
|
||||
TEST_F(PanelReelTest, MovementWithoutTablets) {
|
||||
panelreel_options p{};
|
||||
p.infinitescroll = false;
|
||||
struct panelreel* pr = panelreel_create(n_, &p, -1);
|
||||
ASSERT_NE(nullptr, pr);
|
||||
EXPECT_EQ(nullptr, panelreel_next(pr));
|
||||
// EXPECT_EQ(0, panelreel_validate(n_, pr));
|
||||
EXPECT_EQ(nullptr, panelreel_prev(pr));
|
||||
// EXPECT_EQ(0, panelreel_validate(n_, pr));
|
||||
}
|
||||
|
||||
int panelcb(struct ncplane* p, int begx, int begy, int maxx, int maxy,
|
||||
bool cliptop, void* curry){
|
||||
EXPECT_NE(nullptr, p);
|
||||
EXPECT_LT(begx, maxx);
|
||||
EXPECT_LT(begy, maxy);
|
||||
EXPECT_EQ(nullptr, curry);
|
||||
EXPECT_FALSE(cliptop);
|
||||
// FIXME verify geometry is as expected
|
||||
return 0;
|
||||
}
|
||||
|
||||
TEST_F(PanelReelTest, OneTablet) {
|
||||
panelreel_options p{};
|
||||
p.infinitescroll = false;
|
||||
struct panelreel* pr = panelreel_create(n_, &p, -1);
|
||||
ASSERT_NE(nullptr, pr);
|
||||
struct tablet* t = panelreel_add(pr, nullptr, nullptr, panelcb, nullptr);
|
||||
ASSERT_NE(nullptr, t);
|
||||
// EXPECT_EQ(0, panelreel_validate(n_, pr));
|
||||
EXPECT_EQ(0, panelreel_del(pr, t));
|
||||
// EXPECT_EQ(0, panelreel_validate(n_, pr));
|
||||
}
|
||||
|
||||
TEST_F(PanelReelTest, MovementWithOneTablet) {
|
||||
panelreel_options p{};
|
||||
p.infinitescroll = false;
|
||||
struct panelreel* pr = panelreel_create(n_, &p, -1);
|
||||
ASSERT_NE(nullptr, pr);
|
||||
struct tablet* t = panelreel_add(pr, nullptr, nullptr, panelcb, nullptr);
|
||||
ASSERT_NE(nullptr, t);
|
||||
// EXPECT_EQ(0, panelreel_validate(n_, pr));
|
||||
EXPECT_NE(nullptr, panelreel_next(pr));
|
||||
// EXPECT_EQ(0, panelreel_validate(n_, pr));
|
||||
EXPECT_NE(nullptr, panelreel_prev(pr));
|
||||
// EXPECT_EQ(0, panelreel_validate(n_, pr));
|
||||
EXPECT_EQ(0, panelreel_del(pr, t));
|
||||
// EXPECT_EQ(0, panelreel_validate(n_, pr));
|
||||
}
|
||||
|
||||
TEST_F(PanelReelTest, DeleteActiveTablet) {
|
||||
panelreel_options p{};
|
||||
p.infinitescroll = false;
|
||||
struct panelreel* pr = panelreel_create(n_, &p, -1);
|
||||
ASSERT_NE(nullptr, pr);
|
||||
struct tablet* t = panelreel_add(pr, nullptr, nullptr, panelcb, nullptr);
|
||||
ASSERT_NE(nullptr, t);
|
||||
EXPECT_EQ(0, panelreel_del_focused(pr));
|
||||
}
|
||||
|
||||
TEST_F(PanelReelTest, NoBorder) {
|
||||
panelreel_options p{};
|
||||
p.bordermask = BORDERMASK_LEFT | BORDERMASK_RIGHT |
|
||||
BORDERMASK_TOP | BORDERMASK_BOTTOM;
|
||||
struct panelreel* pr = panelreel_create(n_, &p, -1);
|
||||
ASSERT_NE(nullptr, pr);
|
||||
}
|
||||
|
||||
TEST_F(PanelReelTest, BadBorderBitsRejected) {
|
||||
panelreel_options p{};
|
||||
p.bordermask = BORDERMASK_LEFT * 2;
|
||||
struct panelreel* pr = panelreel_create(n_, &p, -1);
|
||||
ASSERT_EQ(nullptr, pr);
|
||||
}
|
||||
|
||||
TEST_F(PanelReelTest, NoTabletBorder) {
|
||||
panelreel_options p{};
|
||||
p.tabletmask = BORDERMASK_LEFT | BORDERMASK_RIGHT |
|
||||
BORDERMASK_TOP | BORDERMASK_BOTTOM;
|
||||
struct panelreel* pr = panelreel_create(n_, &p, -1);
|
||||
ASSERT_NE(nullptr, pr);
|
||||
}
|
||||
|
||||
TEST_F(PanelReelTest, BadTabletBorderBitsRejected) {
|
||||
panelreel_options p{};
|
||||
p.tabletmask = BORDERMASK_LEFT * 2;
|
||||
struct panelreel* pr = panelreel_create(n_, &p, -1);
|
||||
ASSERT_EQ(nullptr, pr);
|
||||
}
|
||||
|
||||
/*
|
||||
// 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;
|
||||
EXPECT_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);
|
||||
EXPECT_NE(nullptr, ww);
|
||||
PANEL* p = new_panel(ww);
|
||||
EXPECT_NE(nullptr, p);
|
||||
EXPECT_EQ(OK, wbkgrnd(ww, &cc));
|
||||
return p;
|
||||
}
|
||||
|
||||
TEST_F(PanelReelTest, InitWithinSubwin) {
|
||||
panelreel_options p{};
|
||||
p.loff = 1;
|
||||
p.roff = 1;
|
||||
p.toff = 1;
|
||||
p.boff = 1;
|
||||
EXPECT_EQ(0, clear());
|
||||
PANEL* base = make_targwin(n_);
|
||||
ASSERT_NE(nullptr, base);
|
||||
WINDOW* basew = panel_window(base);
|
||||
ASSERT_NE(nullptr, basew);
|
||||
struct panelreel* pr = panelreel_create(basew, &p, -1);
|
||||
ASSERT_NE(nullptr, pr);
|
||||
EXPECT_EQ(0, panelreel_validate(basew, pr));
|
||||
ASSERT_EQ(0, panelreel_destroy(pr));
|
||||
EXPECT_EQ(OK, del_panel(base));
|
||||
EXPECT_EQ(OK, delwin(basew));
|
||||
}
|
||||
|
||||
TEST_F(PanelReelTest, SubwinNoPanelreelBorders) {
|
||||
panelreel_options p{};
|
||||
p.loff = 1;
|
||||
p.roff = 1;
|
||||
p.toff = 1;
|
||||
p.boff = 1;
|
||||
p.bordermask = BORDERMASK_LEFT | BORDERMASK_RIGHT |
|
||||
BORDERMASK_TOP | BORDERMASK_BOTTOM;
|
||||
EXPECT_EQ(0, clear());
|
||||
PANEL* base = make_targwin(n_);
|
||||
ASSERT_NE(nullptr, base);
|
||||
WINDOW* basew = panel_window(base);
|
||||
ASSERT_NE(nullptr, basew);
|
||||
struct panelreel* pr = panelreel_create(basew, &p, -1);
|
||||
ASSERT_NE(nullptr, pr);
|
||||
EXPECT_EQ(0, panelreel_validate(basew, pr));
|
||||
ASSERT_EQ(0, panelreel_destroy(pr));
|
||||
EXPECT_EQ(OK, del_panel(base));
|
||||
EXPECT_EQ(OK, delwin(basew));
|
||||
}
|
||||
|
||||
TEST_F(PanelReelTest, SubwinNoOffsetGeom) {
|
||||
panelreel_options p{};
|
||||
EXPECT_EQ(0, clear());
|
||||
PANEL* base = make_targwin(n_);
|
||||
ASSERT_NE(nullptr, base);
|
||||
WINDOW* basew = panel_window(base);
|
||||
ASSERT_NE(nullptr, basew);
|
||||
struct panelreel* pr = panelreel_create(basew, &p, -1);
|
||||
ASSERT_NE(nullptr, pr);
|
||||
EXPECT_EQ(0, panelreel_validate(basew, pr));
|
||||
ASSERT_EQ(0, panelreel_destroy(pr));
|
||||
EXPECT_EQ(OK, del_panel(base));
|
||||
EXPECT_EQ(OK, delwin(basew));
|
||||
}
|
||||
*/
|
Loading…
x
Reference in New Issue
Block a user