mirror of
https://github.com/dankamongmen/notcurses
synced 2025-03-09 17:19:03 -04:00
* notcurses: set up lastframe #189 * render: o(1) take no prisoners damage detection #189
This commit is contained in:
parent
8b1b2ebdf0
commit
9b81de3789
@ -6,6 +6,7 @@ notcurses-demo \- Show off some notcurses features
|
||||
[ \fB\-p \fIpath \fR]
|
||||
[ \fB\-d \fIdelaymult \fR]
|
||||
[ \fB\-k \fR]
|
||||
[ \fB\-c \fR]
|
||||
[ \fB\-h / \fB\-\-help \fR]
|
||||
.IR demospec
|
||||
.SH OPTIONS
|
||||
@ -20,6 +21,9 @@ Apply a (floating-point) multiplier to the standard delay of 1s.
|
||||
Inhibit use of the alternate screen. Necessary if you want the output left
|
||||
on your terminal after the program exits.
|
||||
.TP
|
||||
.BR \-c
|
||||
Do not attempt to seed the PRNG. This is useful when benchmarking.
|
||||
.TP
|
||||
.BR \-h ", " \-\-help
|
||||
Print a usage message, and exit with success.
|
||||
.TP
|
||||
@ -45,7 +49,7 @@ contains a set of text-based demonstrations of capabilities from the notcurses l
|
||||
.P
|
||||
(s)liders—a missing-piece puzzle made up of colorful blocks
|
||||
.P
|
||||
(b)leachworm—a great Nothing slowly robs the world of color
|
||||
(w)hiteworm—a great Nothing slowly robs the world of color
|
||||
.P
|
||||
(v)iew—images and a video are rendered as text
|
||||
.P
|
||||
|
@ -10,6 +10,10 @@
|
||||
#include <notcurses.h>
|
||||
#include "demo.h"
|
||||
|
||||
// ansi terminal definition-4-life
|
||||
static const int MIN_SUPPORTED_ROWS = 25;
|
||||
static const int MIN_SUPPORTED_COLS = 80;
|
||||
|
||||
static const char DEFAULT_DEMO[] = "iemlubgswvpo";
|
||||
static char datadir[PATH_MAX] = "/usr/share/notcurses"; // FIXME
|
||||
|
||||
@ -46,11 +50,12 @@ struct timespec demodelay = {
|
||||
static void
|
||||
usage(const char* exe, int status){
|
||||
FILE* out = status == EXIT_SUCCESS ? stdout : stderr;
|
||||
fprintf(out, "usage: %s [ -h ] [ -k ] [ -d mult ] [ -f renderfile ] demospec\n", exe);
|
||||
fprintf(out, "usage: %s [ -h ] [ -k ] [ -d mult ] [ -c ] [ -f renderfile ] demospec\n", exe);
|
||||
fprintf(out, " -h: this message\n");
|
||||
fprintf(out, " -k: keep screen; do not switch to alternate\n");
|
||||
fprintf(out, " -d: delay multiplier (float)\n");
|
||||
fprintf(out, " -f: render to file in addition to stdout\n");
|
||||
fprintf(out, " -c: constant PRNG seed, useful for benchmarking\n");
|
||||
fprintf(out, "all demos are run if no specification is provided\n");
|
||||
fprintf(out, " b: run box\n");
|
||||
fprintf(out, " e: run eagles\n");
|
||||
@ -63,7 +68,7 @@ usage(const char* exe, int status){
|
||||
fprintf(out, " s: run shuffle\n");
|
||||
fprintf(out, " u: run uniblock\n");
|
||||
fprintf(out, " v: run view\n");
|
||||
fprintf(out, " w: run bleachworm\n");
|
||||
fprintf(out, " w: run witherworm\n");
|
||||
exit(status);
|
||||
}
|
||||
|
||||
@ -183,7 +188,7 @@ ext_demos(struct notcurses* nc, const char* demos){
|
||||
case 'l': ret = luigi_demo(nc); break;
|
||||
case 'v': ret = view_demo(nc); break;
|
||||
case 'e': ret = eagle_demo(nc); break;
|
||||
case 'w': ret = bleachworm_demo(nc); break;
|
||||
case 'w': ret = witherworm_demo(nc); break;
|
||||
case 'p': ret = panelreel_demo(nc); break;
|
||||
default:
|
||||
fprintf(stderr, "Unknown demo specification: %c\n", *demos);
|
||||
@ -208,13 +213,17 @@ ext_demos(struct notcurses* nc, const char* demos){
|
||||
// if it's NULL, there were valid options, but no spec.
|
||||
static const char*
|
||||
handle_opts(int argc, char** argv, notcurses_options* opts){
|
||||
bool constant_seed = false;
|
||||
int c;
|
||||
memset(opts, 0, sizeof(*opts));
|
||||
while((c = getopt(argc, argv, "hkd:f:p:")) != EOF){
|
||||
while((c = getopt(argc, argv, "hckd:f:p:")) != EOF){
|
||||
switch(c){
|
||||
case 'h':
|
||||
usage(*argv, EXIT_SUCCESS);
|
||||
break;
|
||||
case 'c':
|
||||
constant_seed = true;
|
||||
break;
|
||||
case 'k':
|
||||
opts->inhibit_alternate_screen = true;
|
||||
break;
|
||||
@ -244,6 +253,9 @@ handle_opts(int argc, char** argv, notcurses_options* opts){
|
||||
usage(*argv, EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
if(!constant_seed){
|
||||
srand(time(NULL)); // a classic blunder lol
|
||||
}
|
||||
const char* demos = argv[optind];
|
||||
return demos;
|
||||
}
|
||||
@ -252,7 +264,6 @@ handle_opts(int argc, char** argv, notcurses_options* opts){
|
||||
int main(int argc, char** argv){
|
||||
struct notcurses* nc;
|
||||
notcurses_options nopts;
|
||||
struct ncplane* ncp;
|
||||
if(!setlocale(LC_ALL, "")){
|
||||
fprintf(stderr, "Couldn't set locale based on user preferences\n");
|
||||
return EXIT_FAILURE;
|
||||
@ -267,8 +278,9 @@ int main(int argc, char** argv){
|
||||
if((nc = notcurses_init(&nopts, stdout)) == NULL){
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if((ncp = notcurses_stdplane(nc)) == NULL){
|
||||
fprintf(stderr, "Couldn't get standard plane\n");
|
||||
int dimx, dimy;
|
||||
notcurses_term_dim_yx(nc, &dimy, &dimx);
|
||||
if(dimy < MIN_SUPPORTED_ROWS || dimx < MIN_SUPPORTED_COLS){
|
||||
goto err;
|
||||
}
|
||||
// no one cares about the leaderscreen. 1s max.
|
||||
@ -306,6 +318,10 @@ int main(int argc, char** argv){
|
||||
return EXIT_SUCCESS;
|
||||
|
||||
err:
|
||||
notcurses_term_dim_yx(nc, &dimy, &dimx);
|
||||
notcurses_stop(nc);
|
||||
if(dimy < MIN_SUPPORTED_ROWS || dimx < MIN_SUPPORTED_COLS){
|
||||
fprintf(stderr, "At least an 80x25 terminal is required (current: %dx%d)\n", dimx, dimy);
|
||||
}
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ extern struct timespec demodelay;
|
||||
char* find_data(const char* datum);
|
||||
|
||||
int unicodeblocks_demo(struct notcurses* nc);
|
||||
int bleachworm_demo(struct notcurses* nc);
|
||||
int witherworm_demo(struct notcurses* nc);
|
||||
int box_demo(struct notcurses* nc);
|
||||
int maxcolor_demo(struct notcurses* nc);
|
||||
int grid_demo(struct notcurses* nc);
|
||||
|
@ -160,81 +160,112 @@ lightup_surrounding_cells(struct ncplane* n, const cell* cells, int y, int x){
|
||||
return 0;
|
||||
}
|
||||
|
||||
typedef struct snake {
|
||||
cell lightup[13];
|
||||
int x, y;
|
||||
uint64_t channels;
|
||||
int prevx, prevy;
|
||||
} snake;
|
||||
|
||||
static void
|
||||
init_snake(snake* s, int dimy, int dimx){
|
||||
for(size_t i = 0 ; i < sizeof(s->lightup) / sizeof(*s->lightup) ; ++i){
|
||||
cell_init(&s->lightup[i]);
|
||||
}
|
||||
// start it in the lower center of the screen
|
||||
s->x = (random() % (dimx / 2)) + (dimx / 4);
|
||||
s->y = (random() % (dimy * 2 / 3)) + (dimy / 3);
|
||||
s->channels = 0;
|
||||
channels_set_fg_rgb(&s->channels, 255, 255, 255);
|
||||
channels_set_bg_rgb(&s->channels, 20, 20, 20);
|
||||
s->prevx = 0;
|
||||
s->prevy = 0;
|
||||
}
|
||||
|
||||
static int
|
||||
snakey_top(struct notcurses* nc, snake* s){
|
||||
struct ncplane* n = notcurses_stdplane(nc);
|
||||
get_surrounding_cells(n, s->lightup, s->y, s->x);
|
||||
ncplane_cursor_move_yx(n, s->y, s->x);
|
||||
if(lightup_surrounding_cells(n, s->lightup, s->y, s->x)){
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
snakey(struct notcurses* nc, snake* s, int dimy, int dimx, const struct timespec* iterdelay){
|
||||
struct ncplane* n = notcurses_stdplane(nc);
|
||||
int oldy, oldx;
|
||||
clock_nanosleep(CLOCK_MONOTONIC, 0, iterdelay, NULL);
|
||||
cell c = CELL_TRIVIAL_INITIALIZER;
|
||||
do{ // force a move
|
||||
oldy = s->y;
|
||||
oldx = s->x;
|
||||
// FIXME he ought be weighted to avoid light; he's a snake after all
|
||||
int direction = random() % 4;
|
||||
switch(direction){
|
||||
case 0: --s->y; break;
|
||||
case 1: ++s->x; break;
|
||||
case 2: ++s->y; break;
|
||||
case 3: --s->x; break;
|
||||
}
|
||||
// keep him away from the sides due to width irregularities
|
||||
if(s->x < (dimx / 4)){
|
||||
s->x = dimx / 4;
|
||||
}else if(s->x >= dimx * 3 / 4){
|
||||
s->x = dimx * 3 / 4;
|
||||
}
|
||||
if(s->y < 0){
|
||||
s->y = 0;
|
||||
}else if(s->y >= dimy){
|
||||
s->y = dimy - 1;
|
||||
}
|
||||
ncplane_cursor_move_yx(n, s->y, s->x);
|
||||
ncplane_at_cursor(n, &c);
|
||||
// don't allow the snake into the summary zone (test for walls)
|
||||
if(wall_p(n, &c)){
|
||||
s->x = oldx;
|
||||
s->y = oldy;
|
||||
}
|
||||
}while((oldx == s->x && oldy == s->y) || (s->x == s->prevx && s->y == s->prevy));
|
||||
s->prevy = oldy;
|
||||
s->prevx = oldx;
|
||||
cell_release(n, &c);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// each snake wanders around aimlessly, prohibited from entering the summary
|
||||
// section. it ought light up the cells around it; to do this, we keep an array
|
||||
// of 13 cells with the original colors, which we tune up for the duration of
|
||||
// our colocality (unless they're summary area walls).
|
||||
static void *
|
||||
snake_thread(void* vnc){
|
||||
const int snakecount = 3; // FIXME base count off area
|
||||
struct notcurses* nc = vnc;
|
||||
struct ncplane* n = notcurses_stdplane(nc);
|
||||
cell lightup[13];
|
||||
size_t i;
|
||||
for(i = 0 ; i < sizeof(lightup) / sizeof(*lightup) ; ++i){
|
||||
cell_init(&lightup[i]);
|
||||
}
|
||||
int dimy, dimx;
|
||||
ncplane_dim_yx(n, &dimy, &dimx);
|
||||
int x, y;
|
||||
// start it in the lower center of the screen
|
||||
x = (random() % (dimx / 2)) + (dimx / 4);
|
||||
y = (random() % (dimy / 2)) + (dimy / 2);
|
||||
cell head = CELL_TRIVIAL_INITIALIZER;
|
||||
uint64_t channels = 0;
|
||||
channels_set_fg_rgb(&channels, 255, 255, 255);
|
||||
channels_set_bg_rgb(&channels, 20, 20, 20);
|
||||
cell_prime(n, &head, "א", 0, channels);
|
||||
cell c = CELL_TRIVIAL_INITIALIZER;
|
||||
snake snakes[snakecount];
|
||||
for(int s = 0 ; s < snakecount ; ++s){
|
||||
init_snake(&snakes[s], dimy, dimx);
|
||||
}
|
||||
struct timespec iterdelay = { .tv_sec = 0, .tv_nsec = 1000000000ul / 20, };
|
||||
int prevx = 0, prevy = 0;
|
||||
while(true){
|
||||
pthread_testcancel();
|
||||
get_surrounding_cells(n, lightup, y, x);
|
||||
ncplane_cursor_move_yx(n, y, x);
|
||||
ncplane_at_cursor(n, &c);
|
||||
if(lightup_surrounding_cells(n, lightup, y, x)){
|
||||
for(int s = 0 ; s < snakecount ; ++s){
|
||||
if(snakey_top(nc, &snakes[s])){
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
if(notcurses_render(nc)){
|
||||
return NULL;
|
||||
}
|
||||
notcurses_render(nc);
|
||||
int oldy, oldx;
|
||||
clock_nanosleep(CLOCK_MONOTONIC, 0, &iterdelay, NULL);
|
||||
do{ // force a move
|
||||
oldy = y;
|
||||
oldx = x;
|
||||
// FIXME he ought be weighted to avoid light; he's a snake after all
|
||||
int direction = random() % 4;
|
||||
switch(direction){
|
||||
case 0: --y; break;
|
||||
case 1: ++x; break;
|
||||
case 2: ++y; break;
|
||||
case 3: --x; break;
|
||||
for(int s = 0 ; s < snakecount ; ++s){
|
||||
if(snakey(nc, &snakes[s], dimy, dimx, &iterdelay)){
|
||||
return NULL;
|
||||
}
|
||||
// keep him away from the sides due to width irregularities
|
||||
if(x < (dimx / 4)){
|
||||
x = dimx / 4;
|
||||
}else if(x >= dimx * 3 / 4){
|
||||
x = dimx * 3 / 4;
|
||||
}
|
||||
if(y < 0){
|
||||
y = 0;
|
||||
}else if(y >= dimy){
|
||||
y = dimy - 1;
|
||||
}
|
||||
ncplane_cursor_move_yx(n, y, x);
|
||||
ncplane_at_cursor(n, &c);
|
||||
// don't allow the snake into the summary zone (test for walls)
|
||||
if(wall_p(n, &c)){
|
||||
x = oldx;
|
||||
y = oldy;
|
||||
}
|
||||
}while((oldx == x && oldy == y) || (x == prevx && y == prevy));
|
||||
prevy = oldy;
|
||||
prevx = oldx;
|
||||
}
|
||||
cell_release(n, &head); // FIXME won't be released when cancelled
|
||||
cell_release(n, &c); // FIXME won't be released when cancelled
|
||||
for(i = 0 ; i < sizeof(lightup) / sizeof(*lightup) ; ++i){
|
||||
cell_release(n, &lightup[i]);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
@ -306,7 +337,7 @@ message(struct ncplane* n, int maxy, int maxx, int num, int total,
|
||||
}
|
||||
|
||||
// Much of this text comes from http://kermitproject.org/utf8.html
|
||||
int bleachworm_demo(struct notcurses* nc){
|
||||
int witherworm_demo(struct notcurses* nc){
|
||||
static const char* strs[] = {
|
||||
"Война и мир",
|
||||
"Бра́тья Карама́зовы",
|
||||
@ -537,8 +568,8 @@ int bleachworm_demo(struct notcurses* nc){
|
||||
NULL
|
||||
};
|
||||
const char** s;
|
||||
const int steps[] = { 0x100, 0x100, 0x40000, 0x10001, };
|
||||
const int starts[] = { 0x004000, 0x000040, 0x010101, 0x400040, };
|
||||
const int steps[] = { 0x10040, 0x100, 0x100, 0x10001, };
|
||||
const int starts[] = { 0x10101, 0x004000, 0x000040, 0x400040, };
|
||||
|
||||
struct ncplane* n = notcurses_stdplane(nc);
|
||||
size_t i;
|
@ -230,6 +230,12 @@ egcpool_dump(egcpool* pool){
|
||||
pool->poolused = 0;
|
||||
}
|
||||
|
||||
static inline const char*
|
||||
egcpool_extended_gcluster(const egcpool* pool, const cell* c){
|
||||
uint32_t idx = cell_egc_idx(c);
|
||||
return pool->pool + idx;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
@ -134,7 +134,6 @@ int ncplane_fadein(ncplane* n, const struct timespec* ts){
|
||||
}
|
||||
}
|
||||
}
|
||||
ncplane_updamage(n);
|
||||
notcurses_render(n->nc);
|
||||
uint64_t nextwake = (iter + 1) * nanosecs_step + startns;
|
||||
struct timespec sleepspec;
|
||||
@ -204,7 +203,6 @@ int ncplane_fadeout(ncplane* n, const struct timespec* ts){
|
||||
}
|
||||
}
|
||||
}
|
||||
ncplane_updamage(n);
|
||||
cell* c = &n->defcell;
|
||||
if(!cell_fg_default_p(c)){
|
||||
channels_get_fg_rgb(pp.channels[pp.cols * y], &r, &g, &b);
|
||||
|
@ -51,7 +51,6 @@ typedef struct ncplane {
|
||||
uint32_t attrword; // same deal as in a cell
|
||||
void* userptr; // slot for the user to stick some opaque pointer
|
||||
cell defcell; // cell written anywhere that fb[i].gcluster == 0
|
||||
unsigned char* damage;// damage map, one per row
|
||||
struct notcurses* nc; // notcurses object of which we are a part
|
||||
} ncplane;
|
||||
|
||||
@ -80,20 +79,32 @@ typedef struct ncvisual {
|
||||
|
||||
typedef struct notcurses {
|
||||
pthread_mutex_t lock;
|
||||
int ttyfd; // file descriptor for controlling tty, from opts->ttyfp
|
||||
FILE* ttyfp; // FILE* for controlling tty, from opts->ttyfp
|
||||
FILE* ttyinfp; // FILE* for processing input
|
||||
unsigned char* damage; // damage map (row granularity)
|
||||
ncplane* top; // the contents of our topmost plane (initially entire screen)
|
||||
ncplane* stdscr;// aliases some plane from the z-buffer, covers screen
|
||||
|
||||
// we keep a copy of the last rendered frame. this facilitates O(1)
|
||||
// notcurses_at_yx() and O(1) damage detection (at the cost of some memory).
|
||||
cell* lastframe;// last rendered framebuffer, NULL until first render
|
||||
int lfdimx; // dimensions of lastframe, unchanged by screen resize
|
||||
int lfdimy; // lfdimx/lfdimy are 0 until first render
|
||||
egcpool pool; // duplicate EGCs into this pool
|
||||
|
||||
// we assemble the encoded output in a POSIX memstream, and keep it around
|
||||
// between uses. this could be a problem if it ever tremendously spiked, but
|
||||
// that's a highly unlikely situation.
|
||||
char* mstream; // buffer for rendering memstream, see open_memstream(3)
|
||||
FILE* mstreamfp;// FILE* for rendering memstream
|
||||
size_t mstrsize;// size of rendering memstream
|
||||
int colors; // number of colors usable for this screen
|
||||
|
||||
ncstats stats; // some statistics across the lifetime of the notcurses ctx
|
||||
ncstats stashstats; // cumulative stats, unaffected by notcurses_reset_stats()
|
||||
|
||||
// We verify that some terminfo capabilities exist. These needn't be checked
|
||||
// before further use; just use tiparm() directly.
|
||||
int colors; // number of colors terminfo reported usable for this screen
|
||||
char* cup; // move cursor
|
||||
bool RGBflag; // terminfo-reported "RGB" flag for 24bpc directcolor
|
||||
char* cuf; // move n cells right
|
||||
char* cuf1; // move 1 cell right
|
||||
char* civis; // hide cursor
|
||||
// These might be NULL, and we can more or less work without them. Check!
|
||||
char* clearscr; // erase screen and home cursor
|
||||
@ -117,12 +128,15 @@ typedef struct notcurses {
|
||||
char* italoff; // CELL_STYLE_ITALIC (disable)
|
||||
char* smkx; // enter keypad transmit mode (keypad_xmit)
|
||||
char* rmkx; // leave keypad transmit mode (keypad_local)
|
||||
bool RGBflag; // terminfo-reported "RGB" flag for 24bpc directcolor
|
||||
bool CCCflag; // terminfo-reported "CCC" flag for palette set capability
|
||||
|
||||
int ttyfd; // file descriptor for controlling tty, from opts->ttyfp
|
||||
FILE* ttyfp; // FILE* for controlling tty, from opts->ttyfp
|
||||
FILE* ttyinfp; // FILE* for processing input
|
||||
FILE* renderfp; // debugging FILE* to which renderings are written
|
||||
struct termios tpreserved; // terminal state upon entry
|
||||
bool suppress_banner; // from notcurses_options
|
||||
bool CCCflag; // terminfo-reported "CCC" flag for palette set capability
|
||||
ncplane* top; // the contents of our topmost plane (initially entire screen)
|
||||
ncplane* stdscr;// aliases some plane from the z-buffer, covers screen
|
||||
FILE* renderfp; // debugging FILE* to which renderings are written
|
||||
unsigned char inputbuf[BUFSIZ];
|
||||
// we keep a wee ringbuffer of input queued up for delivery. if
|
||||
// inputbuf_occupied == sizeof(inputbuf), there is no room. otherwise, data
|
||||
@ -161,19 +175,6 @@ fbcellidx(const ncplane* n, int row, int col){
|
||||
return row * n->lenx + col;
|
||||
}
|
||||
|
||||
// set all elements of a damage map true or false
|
||||
static inline void
|
||||
flash_damage_map(unsigned char* damage, int count, bool val){
|
||||
if(val){
|
||||
memset(damage, 0xff, sizeof(*damage) * count);
|
||||
}else{
|
||||
memset(damage, 0, sizeof(*damage) * count);
|
||||
}
|
||||
}
|
||||
|
||||
// mark all lines of the notcurses object touched by this plane as damaged
|
||||
void ncplane_updamage(ncplane* n);
|
||||
|
||||
// For our first attempt, O(1) uniform conversion from 8-bit r/g/b down to
|
||||
// ~2.4-bit 6x6x6 cube + greyscale (assumed on entry; I know no way to
|
||||
// even semi-portably recover the palette) proceeds via: map each 8-bit to
|
||||
@ -223,8 +224,7 @@ term_emit(const char* name __attribute__ ((unused)), const char* seq,
|
||||
|
||||
static inline const char*
|
||||
extended_gcluster(const ncplane* n, const cell* c){
|
||||
uint32_t idx = cell_egc_idx(c);
|
||||
return n->pool.pool + idx;
|
||||
return egcpool_extended_gcluster(&n->pool, c);
|
||||
}
|
||||
|
||||
#define NANOSECS_IN_SEC 1000000000
|
||||
|
@ -354,7 +354,6 @@ int ncvisual_render(const ncvisual* ncv, int begy, int begx, int leny, int lenx)
|
||||
cell_release(ncv->ncp, &c);
|
||||
}
|
||||
}
|
||||
flash_damage_map(ncv->ncp->damage + ncv->placey, y - ncv->placey, true);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -262,9 +262,7 @@ term_verify_seq(char** gseq, const char* name){
|
||||
static void
|
||||
free_plane(ncplane* p){
|
||||
if(p){
|
||||
ncplane_updamage(p);
|
||||
egcpool_dump(&p->pool);
|
||||
free(p->damage);
|
||||
free(p->fb);
|
||||
free(p);
|
||||
}
|
||||
@ -287,13 +285,6 @@ ncplane_create(notcurses* nc, int rows, int cols, int yoff, int xoff){
|
||||
return NULL;
|
||||
}
|
||||
memset(p->fb, 0, fbsize);
|
||||
p->damage = malloc(sizeof(*p->damage) * rows);
|
||||
if(p->damage == NULL){
|
||||
free(p->fb);
|
||||
free(p);
|
||||
return NULL;
|
||||
}
|
||||
flash_damage_map(p->damage, rows, false);
|
||||
p->userptr = NULL;
|
||||
p->leny = rows;
|
||||
p->lenx = cols;
|
||||
@ -341,8 +332,7 @@ ncplane* notcurses_newplane(notcurses* nc, int rows, int cols,
|
||||
return n;
|
||||
}
|
||||
|
||||
// can be used on stdscr, unlike ncplane_resize() which prohibits it. sets all
|
||||
// members of the plane's damage map to damaged.
|
||||
// can be used on stdscr, unlike ncplane_resize() which prohibits it.
|
||||
static int
|
||||
ncplane_resize_internal(ncplane* n, int keepy, int keepx, int keepleny,
|
||||
int keeplenx, int yoff, int xoff, int ylen, int xlen){
|
||||
@ -377,19 +367,6 @@ ncplane_resize_internal(ncplane* n, int keepy, int keepx, int keepleny,
|
||||
if(fb == NULL){
|
||||
return -1;
|
||||
}
|
||||
// if we're the standard plane, the updamage can be charged at the end. it's
|
||||
// unsafe to call now anyway, because if we shrank, the notcurses damage map
|
||||
// has already been shrunk down
|
||||
if(n != n->nc->stdscr){
|
||||
ncplane_updamage(n); // damage any lines we were on
|
||||
}
|
||||
unsigned char* tmpdamage;
|
||||
if((tmpdamage = realloc(n->damage, sizeof(*n->damage) * ylen)) == NULL){
|
||||
free(fb);
|
||||
return -1;
|
||||
}
|
||||
n->damage = tmpdamage;
|
||||
flash_damage_map(n->damage, ylen, true);
|
||||
// update the cursor, if it would otherwise be off-plane
|
||||
if(n->y >= ylen){
|
||||
n->y = ylen - 1;
|
||||
@ -411,7 +388,6 @@ ncplane_resize_internal(ncplane* n, int keepy, int keepx, int keepleny,
|
||||
n->lenx = xlen;
|
||||
n->leny = ylen;
|
||||
free(preserved);
|
||||
ncplane_updamage(n); // damage any lines we're now on
|
||||
return 0;
|
||||
}
|
||||
// we currently have maxy rows of maxx cells each. we will be keeping rows
|
||||
@ -448,7 +424,6 @@ ncplane_resize_internal(ncplane* n, int keepy, int keepx, int keepleny,
|
||||
n->lenx = xlen;
|
||||
n->leny = ylen;
|
||||
free(preserved);
|
||||
ncplane_updamage(n); // damage any lines we're now on
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -489,19 +464,6 @@ int notcurses_resize(notcurses* n, int* rows, int* cols){
|
||||
if(keepx > oldcols){
|
||||
keepx = oldcols;
|
||||
}
|
||||
unsigned char* tmpdamage;
|
||||
if((tmpdamage = malloc(sizeof(*n->damage) * *rows)) == NULL){
|
||||
return -1;
|
||||
}
|
||||
if(oldcols < *cols){ // all are busted if rows got bigger
|
||||
free(n->damage);
|
||||
flash_damage_map(tmpdamage, *rows, true);
|
||||
}else if(oldrows <= *rows){ // new rows are pre-busted, old are straight
|
||||
memcpy(tmpdamage, n->damage, oldrows * sizeof(*tmpdamage));
|
||||
flash_damage_map(tmpdamage + oldrows, *rows - oldrows, true);
|
||||
free(n->damage);
|
||||
}
|
||||
n->damage = tmpdamage;
|
||||
if(ncplane_resize_internal(n->stdscr, 0, 0, keepy, keepx, 0, 0, *rows, *cols)){
|
||||
return -1;
|
||||
}
|
||||
@ -593,6 +555,8 @@ interrogate_terminfo(notcurses* nc, const notcurses_options* opts){
|
||||
term_verify_seq(&nc->clearscr, "clear");
|
||||
term_verify_seq(&nc->cleareol, "el");
|
||||
term_verify_seq(&nc->clearbol, "el1");
|
||||
term_verify_seq(&nc->cuf, "cuf"); // n non-destructive spaces
|
||||
term_verify_seq(&nc->cuf1, "cuf1"); // non-destructive space
|
||||
if(prep_special_keys(nc)){
|
||||
return -1;
|
||||
}
|
||||
@ -725,6 +689,10 @@ notcurses* notcurses_init(const notcurses_options* opts, FILE* outfp){
|
||||
ret->ttyinfp = stdin; // FIXME
|
||||
ret->mstream = NULL;
|
||||
ret->mstrsize = 0;
|
||||
ret->lastframe = NULL;
|
||||
ret->lfdimy = 0;
|
||||
ret->lfdimx = 0;
|
||||
egcpool_init(&ret->pool);
|
||||
if(make_nonblocking(ret->ttyinfp)){
|
||||
free(ret);
|
||||
return NULL;
|
||||
@ -783,16 +751,10 @@ notcurses* notcurses_init(const notcurses_options* opts, FILE* outfp){
|
||||
free_plane(ret->top);
|
||||
goto err;
|
||||
}
|
||||
if((ret->damage = malloc(sizeof(*ret->damage) * ret->stdscr->leny)) == NULL){
|
||||
free_plane(ret->top);
|
||||
goto err;
|
||||
}
|
||||
if((ret->mstreamfp = open_memstream(&ret->mstream, &ret->mstrsize)) == NULL){
|
||||
free(ret->damage);
|
||||
free_plane(ret->top);
|
||||
goto err;
|
||||
}
|
||||
flash_damage_map(ret->damage, ret->stdscr->leny, false);
|
||||
// term_emit("clear", ret->clear, ret->ttyfp, false);
|
||||
ret->suppress_banner = opts->suppress_bannner;
|
||||
if(!opts->suppress_bannner){
|
||||
@ -856,10 +818,11 @@ int notcurses_stop(notcurses* nc){
|
||||
nc->top = p->z;
|
||||
free_plane(p);
|
||||
}
|
||||
free(nc->damage);
|
||||
if(nc->mstreamfp){
|
||||
fclose(nc->mstreamfp);
|
||||
}
|
||||
egcpool_dump(&nc->pool);
|
||||
free(nc->lastframe);
|
||||
free(nc->mstream);
|
||||
input_free_esctrie(&nc->inputescapes);
|
||||
ret |= pthread_mutex_destroy(&nc->lock);
|
||||
@ -955,7 +918,6 @@ int ncplane_set_default(ncplane* ncp, const cell* c){
|
||||
if(ret < 0){
|
||||
return -1;
|
||||
}
|
||||
ncplane_updamage(ncp);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -991,7 +953,6 @@ int ncplane_move_above_unsafe(ncplane* restrict n, ncplane* restrict above){
|
||||
*an = n->z; // splice n out
|
||||
n->z = above; // attach above below n
|
||||
*aa = n; // spline n in above
|
||||
ncplane_updamage(n); // conservative (we might not actually be visible)
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -1004,7 +965,6 @@ int ncplane_move_below_unsafe(ncplane* restrict n, ncplane* restrict below){
|
||||
*an = n->z; // splice n out
|
||||
n->z = below->z; // reattach subbelow list to n
|
||||
below->z = n; // splice n in below
|
||||
ncplane_updamage(n); // conservative (we might not actually be visible)
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -1016,7 +976,6 @@ int ncplane_move_top(ncplane* n){
|
||||
*an = n->z; // splice n out
|
||||
n->z = n->nc->top;
|
||||
n->nc->top = n;
|
||||
ncplane_updamage(n);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -1032,7 +991,6 @@ int ncplane_move_bottom(ncplane* n){
|
||||
}
|
||||
*an = n;
|
||||
n->z = NULL;
|
||||
ncplane_updamage(n);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -1062,24 +1020,6 @@ ncplane_cursor_stuck(const ncplane* n){
|
||||
return (n->x == n->lenx && n->y == n->leny);
|
||||
}
|
||||
|
||||
int cell_duplicate(ncplane* n, cell* targ, const cell* c){
|
||||
cell_release(n, targ);
|
||||
targ->attrword = c->attrword;
|
||||
targ->channels = c->channels;
|
||||
if(cell_simple_p(c)){
|
||||
targ->gcluster = c->gcluster;
|
||||
return !!c->gcluster;
|
||||
}
|
||||
size_t ulen = strlen(extended_gcluster(n, c));
|
||||
// fprintf(stderr, "[%s] (%zu)\n", extended_gcluster(n, c), strlen(extended_gcluster(n, c)));
|
||||
int eoffset = egcpool_stash(&n->pool, extended_gcluster(n, c), ulen);
|
||||
if(eoffset < 0){
|
||||
return -1;
|
||||
}
|
||||
targ->gcluster = eoffset + 0x80;
|
||||
return ulen;
|
||||
}
|
||||
|
||||
static inline void
|
||||
cell_set_wide(cell* c){
|
||||
c->channels |= CELL_WIDEASIAN_MASK;
|
||||
@ -1129,7 +1069,6 @@ int ncplane_putc(ncplane* n, const cell* c){
|
||||
cell_release(n, candidate);
|
||||
}
|
||||
}
|
||||
n->damage[n->y] = true;
|
||||
advance_cursor(n, cols);
|
||||
ncplane_unlock(n);
|
||||
return cols;
|
||||
@ -1174,13 +1113,6 @@ int ncplane_cursor_at(const ncplane* n, cell* c, char** gclust){
|
||||
return 0;
|
||||
}
|
||||
|
||||
void cell_release(ncplane* n, cell* c){
|
||||
if(!cell_simple_p(c)){
|
||||
egcpool_release(&n->pool, cell_egc_idx(c));
|
||||
c->gcluster = 0; // don't subject ourselves to double-release problems
|
||||
}
|
||||
}
|
||||
|
||||
int cell_load(ncplane* n, cell* c, const char* gcluster){
|
||||
cell_release(n, c);
|
||||
int bytes;
|
||||
@ -1536,33 +1468,12 @@ int ncplane_box(ncplane* n, const cell* ul, const cell* ur,
|
||||
return 0;
|
||||
}
|
||||
|
||||
// mark all lines of the notcurses object touched by this plane as damaged
|
||||
void ncplane_updamage(ncplane* n){
|
||||
int drangelow = n->absy;
|
||||
int drangehigh = n->absy + n->leny;
|
||||
if(drangehigh > n->nc->stdscr->leny){
|
||||
drangehigh = n->nc->stdscr->leny;
|
||||
}
|
||||
if(drangelow < n->nc->stdscr->absy){
|
||||
drangelow = n->nc->stdscr->absy;
|
||||
}
|
||||
if(drangelow > n->nc->stdscr->absy + n->nc->stdscr->leny - 1){
|
||||
drangelow = n->nc->stdscr->absy + n->nc->stdscr->leny - 1;
|
||||
}
|
||||
flash_damage_map(n->nc->damage + drangelow, drangehigh - drangelow, true);
|
||||
}
|
||||
|
||||
int ncplane_move_yx(ncplane* n, int y, int x){
|
||||
if(n == n->nc->stdscr){
|
||||
return -1;
|
||||
}
|
||||
ncplane_updamage(n); // damage any lines we are currently on
|
||||
bool movedy = n->absy != y;
|
||||
n->absy = y;
|
||||
n->absx = x;
|
||||
if(movedy){
|
||||
ncplane_updamage(n);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -1602,7 +1513,6 @@ void ncplane_erase(ncplane* n){
|
||||
egcpool_init(&n->pool);
|
||||
cell_load(n, &n->defcell, egc);
|
||||
free(egc);
|
||||
ncplane_updamage(n);
|
||||
ncplane_unlock(n);
|
||||
}
|
||||
|
||||
|
172
src/lib/render.c
172
src/lib/render.c
@ -1,4 +1,5 @@
|
||||
#include <ctype.h>
|
||||
#include <limits.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/poll.h>
|
||||
#include "internal.h"
|
||||
@ -98,24 +99,33 @@ prep_optimized_palette(notcurses* nc, FILE* out __attribute__ ((unused))){
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
// reshape the shadow framebuffer to match the stdplane's dimensions, throwing
|
||||
// away the old one.
|
||||
static int
|
||||
reshape_shadow_fb(notcurses* nc){
|
||||
const size_t size = sizeof(nc->shadowbuf) * nc->stdscr->leny * nc->stdscr->lenx;
|
||||
cell* fb = malloc(size);
|
||||
if(nc->lfdimx == nc->stdscr->lenx && nc->lfdimy == nc->stdscr->leny){
|
||||
return 0; // no change
|
||||
}
|
||||
const size_t size = sizeof(*nc->lastframe) * nc->stdscr->leny * nc->stdscr->lenx;
|
||||
cell* fb = realloc(nc->lastframe, size);
|
||||
if(fb == NULL){
|
||||
free(nc->lastframe);
|
||||
nc->lastframe = NULL;
|
||||
nc->lfdimx = 0;
|
||||
nc->lfdimy = 0;
|
||||
return -1;
|
||||
}
|
||||
free(nc->shadowbuf);
|
||||
nc->shadowbuf = fb;
|
||||
nc->shadowy = nc->stdscr->leny;
|
||||
nc->shadowx = nc->stdscr->lenx;
|
||||
nc->lastframe = fb;
|
||||
// FIXME more memset()tery than we need, both wasting work and wrecking
|
||||
// damage detection for the upcoming render
|
||||
memset(nc->lastframe, 0, size);
|
||||
nc->lastframe = fb;
|
||||
nc->lfdimy = nc->stdscr->leny;
|
||||
nc->lfdimx = nc->stdscr->lenx;
|
||||
memset(fb, 0, size);
|
||||
egcpool_dump(&nc->pool);
|
||||
return 0;
|
||||
}
|
||||
*/
|
||||
|
||||
// Find the topmost cell for this coordinate by walking down the z-buffer,
|
||||
// looking for an intersecting ncplane. Once we've found one, check it for
|
||||
@ -135,9 +145,8 @@ reshape_shadow_fb(notcurses* nc){
|
||||
// whichever one occurs at the top with a non-transparent α (α < 3). To effect
|
||||
// tail recursion, though, we instead write first, and then recurse, blending
|
||||
// as we descend. α <= 0 is opaque. α >= 3 is fully transparent.
|
||||
static ncplane*
|
||||
dig_visible_cell(cell* c, int y, int x, ncplane* p, int falpha, int balpha,
|
||||
bool* damage){
|
||||
static inline ncplane*
|
||||
dig_visible_cell(cell* c, int y, int x, ncplane* p, int falpha, int balpha){
|
||||
// once we decide on our glyph, it cannot be changed by anything below, so
|
||||
// lock in this plane for the actual cell return.
|
||||
ncplane* glyphplane = NULL;
|
||||
@ -164,20 +173,12 @@ dig_visible_cell(cell* c, int y, int x, ncplane* p, int falpha, int balpha,
|
||||
c->attrword = vis->attrword;
|
||||
cell_set_fchannel(c, cell_get_fchannel(vis)); // FIXME blend it in
|
||||
falpha -= (CELL_ALPHA_TRANSPARENT - nalpha); // FIXME blend it in
|
||||
if(p->damage[poffy]){
|
||||
*damage = true;
|
||||
p->damage[poffy] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(balpha > 0 && (nalpha = cell_get_bg_alpha(vis)) < CELL_ALPHA_TRANSPARENT){
|
||||
cell_set_bchannel(c, cell_get_bchannel(vis)); // FIXME blend it in
|
||||
balpha -= (CELL_ALPHA_TRANSPARENT - nalpha);
|
||||
if(p->damage[poffy]){
|
||||
*damage = true;
|
||||
p->damage[poffy] = false;
|
||||
}
|
||||
}
|
||||
if((falpha <= 0 && balpha <= 0) || !p->z){ // done!
|
||||
return glyphplane ? glyphplane : p;
|
||||
@ -191,10 +192,9 @@ dig_visible_cell(cell* c, int y, int x, ncplane* p, int falpha, int balpha,
|
||||
}
|
||||
|
||||
static inline ncplane*
|
||||
visible_cell(cell* c, int y, int x, ncplane* n, bool* damage){
|
||||
visible_cell(cell* c, int y, int x, ncplane* n){
|
||||
cell_init(c);
|
||||
return dig_visible_cell(c, y, x, n, CELL_ALPHA_TRANSPARENT,
|
||||
CELL_ALPHA_TRANSPARENT, damage);
|
||||
return dig_visible_cell(c, y, x, n, CELL_ALPHA_TRANSPARENT, CELL_ALPHA_TRANSPARENT);
|
||||
}
|
||||
|
||||
// write the cell's UTF-8 grapheme cluster to the provided FILE*. returns the
|
||||
@ -385,6 +385,74 @@ term_fg_rgb8(notcurses* nc, FILE* out, unsigned r, unsigned g, unsigned b){
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void
|
||||
pool_release(egcpool* pool, cell* c){
|
||||
if(!cell_simple_p(c)){
|
||||
egcpool_release(pool, cell_egc_idx(c));
|
||||
c->gcluster = 0; // don't subject ourselves to double-release problems
|
||||
}
|
||||
}
|
||||
|
||||
void cell_release(ncplane* n, cell* c){
|
||||
pool_release(&n->pool, c);
|
||||
}
|
||||
|
||||
// Duplicate one cell onto another, possibly crossing ncplanes.
|
||||
static inline int
|
||||
cell_duplicate_far(egcpool* tpool, cell* targ, const ncplane* splane, const cell* c){
|
||||
pool_release(tpool, targ);
|
||||
targ->attrword = c->attrword;
|
||||
targ->channels = c->channels;
|
||||
if(cell_simple_p(c)){
|
||||
targ->gcluster = c->gcluster;
|
||||
return !!c->gcluster;
|
||||
}
|
||||
size_t ulen = strlen(extended_gcluster(splane, c));
|
||||
// fprintf(stderr, "[%s] (%zu)\n", extended_gcluster(n, c), strlen(extended_gcluster(n, c)));
|
||||
int eoffset = egcpool_stash(tpool, extended_gcluster(splane, c), ulen);
|
||||
if(eoffset < 0){
|
||||
return -1;
|
||||
}
|
||||
targ->gcluster = eoffset + 0x80;
|
||||
return ulen;
|
||||
}
|
||||
|
||||
// Duplicate one cell onto another when they share a plane. Convenience wrapper.
|
||||
int cell_duplicate(ncplane* n, cell* targ, const cell* c){
|
||||
return cell_duplicate_far(&n->pool, targ, n, c);
|
||||
}
|
||||
|
||||
// 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->attrword == srccell->attrword){
|
||||
if(damcell->channels == srccell->channels){
|
||||
bool damsimple = cell_simple_p(damcell);
|
||||
bool srcsimple = cell_simple_p(srccell);
|
||||
if(damsimple == srcsimple){
|
||||
if(damsimple){
|
||||
if(damcell->gcluster == srccell->gcluster){
|
||||
return 0; // simple match
|
||||
}
|
||||
}else{
|
||||
const char* damegc = egcpool_extended_gcluster(dampool, damcell);
|
||||
const char* srcegc = extended_gcluster(srcplane, srccell);
|
||||
assert(strcmp(damegc, "三体"));
|
||||
assert(strcmp(srcegc, "三体"));
|
||||
if(strcmp(damegc, srcegc) == 0){
|
||||
return 0; // EGC match
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
cell_duplicate_far(dampool, damcell, srcplane, srccell);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static inline int
|
||||
notcurses_render_internal(notcurses* nc){
|
||||
int ret = 0;
|
||||
@ -405,40 +473,55 @@ notcurses_render_internal(notcurses* nc){
|
||||
// cells and the current cell uses no defaults, or if both the current and
|
||||
// the last used both defaults.
|
||||
bool fgelidable = false, bgelidable = false, defaultelidable = false;
|
||||
/*if(nc->stdscr->leny != nc->shadowy || nc->stdscr->lenx != nc->shadowx){
|
||||
reshape_shadow_fb(nc);
|
||||
}*/
|
||||
// if this fails, struggle bravely on. we can live without a lastframe.
|
||||
reshape_shadow_fb(nc);
|
||||
for(y = 0 ; y < nc->stdscr->leny ; ++y){
|
||||
bool linedamaged = false; // have we repositioned the cursor to start line?
|
||||
bool newdamage = nc->damage[y];
|
||||
// fprintf(stderr, "nc->damage[%d] (%p) = %u\n", y, nc->damage + y, nc->damage[y]);
|
||||
if(newdamage){
|
||||
nc->damage[y] = 0;
|
||||
}
|
||||
// move to the beginning of the line, in case our accounting was befouled
|
||||
// by wider- (or narrower-) than-reported characters
|
||||
// how many characters have we elided? it's not worthwhile to invoke a
|
||||
// cursor movement with cup if we only elided one or two. set to INT_MAX
|
||||
// whenever we're on a new line.
|
||||
int needmove = INT_MAX;
|
||||
for(x = 0 ; x < nc->stdscr->lenx ; ++x){
|
||||
unsigned r, g, b, br, bg, bb;
|
||||
ncplane* p;
|
||||
cell c; // no need to initialize
|
||||
p = visible_cell(&c, y, x, nc->top, &newdamage);
|
||||
assert(p);
|
||||
p = visible_cell(&c, y, x, nc->top);
|
||||
// don't try to print a wide character on the last column; it'll instead
|
||||
// be printed on the next line. they probably shouldn't be admitted, but
|
||||
// we can end up with one due to a resize.
|
||||
// FIXME but...print what, exactly, instead?
|
||||
if((x + 1 >= nc->stdscr->lenx && cell_double_wide_p(&c))){
|
||||
continue;
|
||||
continue; // needmove will be reset as we restart the line
|
||||
}
|
||||
if(!linedamaged){
|
||||
if(newdamage){
|
||||
term_emit("cup", tiparm(nc->cup, y, x), out, false);
|
||||
nc->stats.cellelisions += x;
|
||||
nc->stats.cellemissions += (nc->stdscr->lenx - x);
|
||||
linedamaged = true;
|
||||
}else{
|
||||
// lastframe has already been sized to match the current size, so no need
|
||||
// to check whether we're within its bounds. just check the cell.
|
||||
if(nc->lastframe){
|
||||
cell* oldcell = &nc->lastframe[fbcellidx(nc->stdscr, y, x)];
|
||||
if(cellcmp_and_dupfar(&nc->pool, oldcell, p, &c) == 0){
|
||||
// no need to emit a cell; what we rendered appears to already be
|
||||
// here. no updates are performed to elision state nor lastframe.
|
||||
++nc->stats.cellelisions;
|
||||
if(needmove < INT_MAX){
|
||||
++needmove;
|
||||
}
|
||||
if(cell_double_wide_p(&c)){
|
||||
if(needmove < INT_MAX){
|
||||
++needmove;
|
||||
}
|
||||
++nc->stats.cellelisions;
|
||||
++x;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
++nc->stats.cellemissions;
|
||||
if(needmove > 8){ // FIXME cuf and cuf1 aren't guaranteed!
|
||||
term_emit("cup", tiparm(nc->cup, y, x), out, false);
|
||||
}else if(needmove > 1){
|
||||
term_emit("cuf", tiparm(nc->cuf, needmove), out, false);
|
||||
}else if(needmove){
|
||||
term_emit("cuf1", tiparm(nc->cuf1), out, false);
|
||||
}
|
||||
needmove = 0;
|
||||
// set the style. this can change the color back to the default; if it
|
||||
// does, we need update our elision possibilities.
|
||||
bool normalized;
|
||||
@ -498,9 +581,6 @@ notcurses_render_internal(notcurses* nc){
|
||||
++x;
|
||||
}
|
||||
}
|
||||
if(linedamaged == false){
|
||||
nc->stats.cellelisions += x;
|
||||
}
|
||||
}
|
||||
ret |= fflush(out);
|
||||
fflush(nc->ttyfp);
|
||||
|
Loading…
x
Reference in New Issue
Block a user