disable/enable cursor for rasterize

This commit is contained in:
nick black 2020-08-25 03:52:12 -04:00 committed by Nick Black
parent d900fdb7fa
commit a53d5a21a8
8 changed files with 197 additions and 64 deletions

View File

@ -13,6 +13,13 @@ rearrangements of Notcurses.
desired location of the cursor. Both `notcurses_cursor_enable()` and
`notcurses_cursor_disable()` now return `int` rather than `void`.
* `NCOPTION_RETAIN_CURSOR` has been removed.
* `ncreader` now implements `NCREADER_OPTION_HORSCROLL` for horizontal
scrolling. In addition, the following functions have been added:
* `int ncreader_move_left(struct ncreader* n);`
* `int ncreader_move_right(struct ncreader* n);`
* `int ncreader_move_up(struct ncreader* n);`
* `int ncreader_move_down(struct ncreader* n);`
* `int ncreader_write_egc(struct ncreader* n, const char* egc);`
* 1.6.17 (2020-08-22)
* `ncdirect_flush()` now takes a `const struct ncdirect*`.

View File

@ -37,6 +37,16 @@ typedef struct ncreader_options {
**struct ncplane* ncreader_plane(struct ncreader* n);**
**int ncreader_move_left(struct ncreader* n);**
**int ncreader_move_right(struct ncreader* n);**
**int ncreader_move_up(struct ncreader* n);**
**int ncreader_move_down(struct ncreader* n);**
**int ncreader_write_egc(struct ncreader* n, const char* egc);**
**bool ncreader_offer_input(struct ncreader* n, const struct ncinput* ni);**
**char* ncreader_contents(const struct ncreader* n);**

View File

@ -3022,6 +3022,17 @@ API struct ncplane* ncreader_plane(struct ncreader* n);
// are relevant to an ncreader, save synthesized ones.
API bool ncreader_offer_input(struct ncreader* n, const struct ncinput* ni);
// Atttempt to move in the specified direction. Returns 0 if a move was
// successfully executed, -1 otherwise. Scrolling is taken into account.
API int ncreader_move_left(struct ncreader* n);
API int ncreader_move_right(struct ncreader* n);
API int ncreader_move_up(struct ncreader* n);
API int ncreader_move_down(struct ncreader* n);
// Destructively write the provided EGC to the current cursor location. Move
// the cursor as necessary, scrolling if applicable.
API int ncreader_write_egc(struct ncreader* n, const char* egc);
// return a heap-allocated copy of the current (UTF-8) contents.
API char* ncreader_contents(const struct ncreader* n);

View File

@ -434,6 +434,11 @@ const struct ncplane* ncplane_parent_const(const struct ncplane* n);
int notcurses_cursor_enable(struct notcurses* nc, int y, int x);
int notcurses_cursor_move_yx(struct notcurses* nc, int y, int x);
int notcurses_cursor_disable(struct notcurses* nc);
int ncreader_move_left(struct ncreader* n);
int ncreader_move_right(struct ncreader* n);
int ncreader_move_up(struct ncreader* n);
int ncreader_move_down(struct ncreader* n);
int ncreader_write_egc(struct ncreader* n, const char* egc);
""")
if __name__ == "__main__":

View File

@ -191,7 +191,9 @@ typedef struct ncreader {
ncplane* ncp; // always owned by ncreader
uint64_t tchannels; // channels for input text
uint32_t tattrs; // attributes for input text
ncplane* textarea; // might be NULL; can grow if it exists
ncplane* textarea; // grows as needed iff scrolling is enabled
int xproject; // virtual x location of ncp origin on textarea
bool horscroll; // is there horizontal panning?
} ncreader;
typedef struct ncmenu {
@ -599,6 +601,8 @@ cell_duplicate_far(egcpool* tpool, cell* targ, const ncplane* splane, const cell
}
assert(splane);
const char* egc = cell_extended_gcluster(splane, c);
// FIXME we could eliminate this strlen() with a cell_extended_gcluster_len()
// that returned the length, combined with O(1) length for inlined EGCs...
size_t ulen = strlen(egc);
int eoffset = egcpool_stash(tpool, egc, ulen);
if(eoffset < 0){
@ -943,6 +947,25 @@ iswordbreak(wchar_t wchar){
return uc_is_general_category_withtable(wchar, mask);
}
// the heart of damage detection. compare two cells (from two different planes)
// for equality. if they are equal, return 0. otherwise, dup the second onto
// the first and return non-zero.
static inline int
cellcmp_and_dupfar(egcpool* dampool, cell* damcell,
const ncplane* srcplane, const cell* srccell){
if(damcell->stylemask == srccell->stylemask){
if(damcell->channels == srccell->channels){
const char* srcegc = cell_extended_gcluster(srcplane, srccell);
const char* damegc = pool_extended_gcluster(dampool, damcell);
if(strcmp(damegc, srcegc) == 0){
return 0; // EGC match
}
}
}
cell_duplicate_far(dampool, damcell, srcplane, srccell);
return 1;
}
#ifdef __cplusplus
}
#endif

View File

@ -16,22 +16,20 @@ ncreader* ncreader_create(ncplane* n, int y, int x, const ncreader_options* opts
free(nr);
return NULL;
}
if(opts->flags & NCREADER_OPTION_HORSCROLL){
// do *not* bind it to the visible plane; we always want it offscreen,
// to the upper left of the true origin
if((nr->textarea = ncplane_new(n->nc, opts->physrows, opts->physcols, -opts->physrows, -opts->physcols, NULL)) == NULL){
ncplane_destroy(nr->ncp);
free(nr);
return NULL;
}
}else{
nr->textarea = NULL;
// do *not* bind it to the visible plane; we always want it offscreen,
// to the upper left of the true origin
if((nr->textarea = ncplane_new(n->nc, opts->physrows, opts->physcols, -opts->physrows, -opts->physcols, NULL)) == NULL){
ncplane_destroy(nr->ncp);
free(nr);
return NULL;
}
const char* egc = opts->egc ? opts->egc : "_";
if(ncplane_set_base(nr->ncp, egc, opts->eattrword, opts->echannels) <= 0){
ncreader_destroy(nr, NULL);
return NULL;
}
nr->horscroll = opts->flags & NCREADER_OPTION_HORSCROLL;
nr->xproject = 0;
nr->tchannels = opts->tchannels;
nr->tattrs = opts->tattrword;
ncplane_set_channels(nr->ncp, opts->tchannels);
@ -40,9 +38,11 @@ ncreader* ncreader_create(ncplane* n, int y, int x, const ncreader_options* opts
return nr;
}
// empty the ncreader of any user input, and home the cursor.
// empty both planes of all input, and home the cursors.
int ncreader_clear(ncreader* n){
ncplane_erase(n->ncp);
ncplane_erase(n->textarea);
n->xproject = 0;
return 0;
}
@ -50,13 +50,95 @@ ncplane* ncreader_plane(ncreader* n){
return n->ncp;
}
// copy the viewed area down from the textarea
static int
ncreader_redraw(ncreader* n){
int ret = 0;
fprintf(stderr, "redraw: xproj %d\n", n->xproject);
notcurses_debug(n->ncp->nc, stderr);
assert(n->xproject >= 0);
assert(n->textarea->lenx >= n->ncp->lenx);
assert(n->textarea->leny >= n->ncp->leny);
for(int y = 0 ; y < n->ncp->leny ; ++y){
const int texty = y;
for(int x = 0 ; x < n->ncp->lenx ; ++x){
const int textx = x + n->xproject;
const cell* src = &n->textarea->fb[nfbcellidx(n->textarea, texty, textx)];
cell* dst = &n->ncp->fb[nfbcellidx(n->ncp, y, x)];
fprintf(stderr, "projecting %d/%d [%s] to %d/%d [%s]\n", texty, textx, cell_extended_gcluster(n->textarea, src), y, x, cell_extended_gcluster(n->ncp, dst));
if(cellcmp_and_dupfar(&n->ncp->pool, dst, n->textarea, src) < 0){
ret = -1;
}
}
}
return ret;
}
// try to move left. does not move past the start of the textarea, but will
// try to move up and to the end of the previous row if not on the top row.
// if on the left side of the viewarea, but not the left side of the textarea,
// scrolls left. returns true if a move was made.
int ncreader_move_left(ncreader* n){
int viewx = n->ncp->x;
int textx = n->textarea->x;
int y = n->ncp->y;
fprintf(stderr, "moving left: tcurs: %dx%d vcurs: %dx%d xproj: %d\n", y, textx, y, viewx, n->xproject);
if(textx == 0){
// are we on the first column of the textarea? if so, we must also be on
// the first column of the viewarea. try to move up.
if(y == 0){
return -1; // no move possible
}
viewx = n->textarea->lenx - 1; // FIXME find end of particular row
--y;
textx = viewx;
}else{
// if we're on the first column of the viewarea, but not the first column
// of the textarea, we must be able to scroll to the left. do so.
// if we're not on the last column anywhere, move cursor right everywhere.
if(viewx < n->ncp->leny - 1){
++viewx;
}else{
++n->xproject;
}
++textx;
}
ncplane_cursor_move_yx(n->textarea, y, textx);
ncplane_cursor_move_yx(n->ncp, y, viewx);
fprintf(stderr, "moved right: tcurs: %dx%d vcurs: %dx%d xproj: %d\n", y, textx, y, viewx, n->xproject);
return 0;
}
// only writing can enlarge the textarea. movement can pan, but not enlarge.
int ncreader_write_egc(ncreader* n, const char* egc){
const int cols = mbswidth(egc);
if(cols < 0){
logerror(n->ncp->nc, "Fed illegal UTF-8 [%s]\n", egc);
return -1;
}
if(n->textarea->x >= n->textarea->lenx - (cols + 1)){
if(n->horscroll){
// FIXME resize
}
}
// use ncplane_putegc on both planes because it'll get cursor movement right
if(ncplane_putegc(n->textarea, egc, NULL) < 0){
return -1;
}
if(ncplane_putegc(n->ncp, egc, NULL) < 0){
return -1;
}
// FIXME pan right if necessary
return 0;
}
// we pass along:
// * anything with Alt
// * anything with Ctrl, except 'U' (which clears all input)
// * anything synthesized, save arrow keys and backspace
bool ncreader_offer_input(ncreader* n, const ncinput* ni){
int x = n->ncp->x;
int y = n->ncp->y;
int x = n->textarea->x;
int y = n->textarea->y;
if(ni->alt){ // pass on all alts
return false;
}
@ -68,41 +150,36 @@ bool ncreader_offer_input(ncreader* n, const ncinput* ni){
return false; // pass on all other ctrls
}
if(ni->id == NCKEY_BACKSPACE){
if(n->ncp->x == 0){
if(n->ncp->y){
y = n->ncp->y - 1;
x = n->ncp->lenx - 1;
if(n->textarea->x == 0){
if(n->textarea->y){
y = n->textarea->y - 1;
x = n->textarea->lenx - 1;
}
}else{
--x;
}
ncplane_putegc_yx(n->ncp, y, x, "", NULL);
ncplane_cursor_move_yx(n->ncp, y, x);
ncplane_putegc_yx(n->textarea, y, x, "", NULL);
ncplane_cursor_move_yx(n->textarea, y, x);
ncreader_redraw(n);
return true;
}
// FIXME deal with multicolumn EGCs -- probably extract these and make them
// general ncplane_cursor_{left, right, up, down}()
if(ni->id == NCKEY_LEFT){
if(x == 0){
if(y){
x = n->ncp->lenx - 1;
--y;
}
}else{
--x;
}
ncplane_cursor_move_yx(n->ncp, y, x);
ncreader_move_left(n);
ncreader_redraw(n);
return true;
}else if(ni->id == NCKEY_RIGHT){
if(x == n->ncp->lenx - 1){
if(y < n->ncp->leny - 1){
if(x == n->textarea->lenx - 1){
if(y < n->textarea->leny - 1){
++y;
x = 0;
}
}else{
++x;
}
ncplane_cursor_move_yx(n->ncp, y, x);
ncplane_cursor_move_yx(n->textarea, y, x);
ncreader_redraw(n);
return true;
}else if(ni->id == NCKEY_UP){
if(y == 0){
@ -110,15 +187,17 @@ bool ncreader_offer_input(ncreader* n, const ncinput* ni){
}else{
--y;
}
ncplane_cursor_move_yx(n->ncp, y, x);
ncplane_cursor_move_yx(n->textarea, y, x);
ncreader_redraw(n);
return true;
}else if(ni->id == NCKEY_DOWN){
if(y >= n->ncp->leny){
x = n->ncp->lenx - 1;
if(y >= n->textarea->leny){
x = n->textarea->lenx - 1;
}else{
++y;
}
ncplane_cursor_move_yx(n->ncp, y, x);
ncplane_cursor_move_yx(n->textarea, y, x);
ncreader_redraw(n);
return true;
}else if(nckey_supppuab_p(ni->id)){
return false;
@ -127,11 +206,8 @@ bool ncreader_offer_input(ncreader* n, const ncinput* ni){
char wbuf[WCHAR_MAX_UTF8BYTES + 1];
// FIXME breaks for wint_t < 32bits
if(snprintf(wbuf, sizeof(wbuf), "%lc", (wint_t)ni->id) < (int)sizeof(wbuf)){
if(ncplane_putegc(n->ncp, wbuf, NULL) > 0){
if(n->ncp->x == n->ncp->lenx && n->ncp->y < n->ncp->leny - 1){
ncplane_cursor_move_yx(n->ncp, n->ncp->y + 1, 0);
}
}
ncreader_write_egc(n, wbuf);
ncreader_redraw(n);
}
return true;
}

View File

@ -131,25 +131,6 @@ int cell_duplicate(ncplane* n, cell* targ, const cell* c){
return 0;
}
// the heart of damage detection. compare two cells (from two different planes)
// for equality. if they are equal, return 0. otherwise, dup the second onto
// the first and return non-zero.
static int
cellcmp_and_dupfar(egcpool* dampool, cell* damcell,
const ncplane* srcplane, const cell* srccell){
if(damcell->stylemask == srccell->stylemask){
if(damcell->channels == srccell->channels){
const char* srcegc = cell_extended_gcluster(srcplane, srccell);
const char* damegc = pool_extended_gcluster(dampool, damcell);
if(strcmp(damegc, srcegc) == 0){
return 0; // EGC match
}
}
}
cell_duplicate_far(dampool, damcell, srcplane, srccell);
return 1;
}
// Extracellular state for a cell during the render process. This array is
// passed along to rasterization, which uses only the 'damaged' bools. There
// is one crender per rendered cell, and they are initialized to all zeroes.
@ -757,7 +738,7 @@ stage_cursor(notcurses* nc, FILE* out, int y, int x){
// lastframe has *not yet been written to the screen*, i.e. it's only about to
// *become* the last frame rasterized.
static int
notcurses_rasterize(notcurses* nc, const struct crender* rvec, FILE* out){
notcurses_rasterize_inner(notcurses* nc, const struct crender* rvec, FILE* out){
int ret = 0;
int y, x;
fseeko(out, 0, SEEK_SET);
@ -921,6 +902,23 @@ notcurses_rasterize(notcurses* nc, const struct crender* rvec, FILE* out){
return nc->rstate.mstrsize;
}
// if the cursor is enabled, store its location and disable it. then, once done
// rasterizing, enable it afresh, moving it to the stored location. if left on
// during rasterization, we'll get grotesque flicker.
static inline int
notcurses_rasterize(notcurses* nc, const struct crender* rvec, FILE* out){
const int cursory = nc->cursory;
const int cursorx = nc->cursorx;
if(cursory >= 0){ // either both are good, or neither is
notcurses_cursor_disable(nc);
}
int ret = notcurses_rasterize_inner(nc, rvec, out);
if(cursory >= 0){
notcurses_cursor_enable(nc, cursory, cursorx);
}
return ret;
}
// get the cursor to the upper-left corner by one means or another. will clear
// the screen if need be.
static int
@ -990,7 +988,7 @@ int notcurses_render_to_file(notcurses* nc, FILE* fp){
for(int i = 0 ; i < count ; ++i){
rvec[i].damaged = true;
}
int ret = notcurses_rasterize(nc, rvec, out);
int ret = notcurses_rasterize_inner(nc, rvec, out);
free(rvec);
if(ret > 0){
if(fprintf(fp, "%s", rastered) == ret){

View File

@ -28,7 +28,7 @@ auto main() -> int {
if(nr == nullptr){
return EXIT_FAILURE;
}
if(!nc.cursor_enable(2 + opts.physrows, 2 + opts.physcols)){
if(!nc.cursor_enable(2, 2)){
return EXIT_FAILURE;
}
ncinput ni;
@ -37,6 +37,9 @@ auto main() -> int {
if(!ncreader_offer_input(nr, &ni)){
break;
}
int y, x;
ncplane_cursor_yx(ncreader_plane(nr), &y, &x);
nc.cursor_move_yx(y + 2, x + 2);
nc.render();
}
nc.render();
@ -44,7 +47,7 @@ auto main() -> int {
ncreader_destroy(nr, &contents);
nc.stop();
if(contents){
printf("input: %s\n", contents);
printf("\n input: %s\n", contents);
}
return EXIT_SUCCESS;
}