mirror of
https://github.com/dankamongmen/notcurses
synced 2025-03-09 09:09:03 -04:00
disable/enable cursor for rasterize
This commit is contained in:
parent
d900fdb7fa
commit
a53d5a21a8
7
NEWS.md
7
NEWS.md
@ -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*`.
|
||||
|
@ -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);**
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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__":
|
||||
|
@ -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
|
||||
|
156
src/lib/reader.c
156
src/lib/reader.c
@ -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;
|
||||
}
|
||||
|
@ -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){
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user