notcurses_at_yx(): value-result u32+u64, not cell

Resolves #410. notcurses_at_yx() accepted a cell*, but the
gcluster of this cell was always set to 0. The EGC is instead
a heap-allocated copy, returned as the primary return value.
This is due to the absence of an egcpool to bind against.
Existing callers can be converted thus:

* instead of passing cell 'c', pass &(c)->attrword, &(c)->channels
* either initialize 'c' with CELL_TRIVIAL_INITIALIZER, or set its
   gcluster field to 0 following the call

I've updated all calls from tests/demos, updated the docs, and
updated the C++ and Python wrappers.
pull/433/head
nick black 5 years ago
parent 73b61f6a69
commit a77774f4dc
No known key found for this signature in database
GPG Key ID: 5F43400C21CBFACC

@ -7,6 +7,12 @@ rearrangements of Notcurse.
** `ncvisual_render()` now interprets length parameters of -1 to mean "to the ** `ncvisual_render()` now interprets length parameters of -1 to mean "to the
end along this axis", and no longer interprets 0 to mean this. 0 now means end along this axis", and no longer interprets 0 to mean this. 0 now means
"a length of 0", resulting in a zero-area rendering. "a length of 0", resulting in a zero-area rendering.
** `notcurses_at_yx()` no longer accepts a `cell*` as its last parameter.
Instead, it accepts a `uint32_t*` and a `uint64_t*`, and writes the
attribute and channels to these parameters. This was done because the
`gcluster` field of the `cell*` was always set to 0, which was surprising
and a source of blunders. The EGC is returned via the `char*` return
value. https://github.com/dankamongmen/notcurses/issues/410
* 1.2.4 2020-03-24 * 1.2.4 2020-03-24
** Add ncmultiselector ** Add ncmultiselector

@ -257,10 +257,10 @@ updated to reflect the changes:
int notcurses_render(struct notcurses* nc); int notcurses_render(struct notcurses* nc);
// Retrieve the contents of the specified cell as last rendered. The EGC is // Retrieve the contents of the specified cell as last rendered. The EGC is
// returned, or NULL on error. This EGC must be free()d by the caller. The cell // returned, or NULL on error. This EGC must be free()d by the caller. The
// 'c' is not bound to a plane, and thus its gcluster value must not be used-- // attrword and channels are written to 'attrword' and 'channels', respectively.
// use the return value only. char* notcurses_at_yx(struct notcurses* nc, int yoff, int xoff,
char* notcurses_at_yx(struct notcurses* nc, int yoff, int xoff, cell* c); uint32_t* attrword, uint64_t* channels);
``` ```
One `ncplane` is guaranteed to exist: the "standard plane". The user cannot One `ncplane` is guaranteed to exist: the "standard plane". The user cannot

@ -12,7 +12,7 @@ notcurses_render - sync the physical display to the virtual ncplanes
**int notcurses_render(struct notcurses* nc);** **int notcurses_render(struct notcurses* nc);**
**char* notcurses_at_yx(struct notcurses* nc, int yoff, int xoff, cell* c);** **char* notcurses_at_yx(struct notcurses* nc, int yoff, int xoff, uint32_t* attrword, uint64_t* channels);**
# DESCRIPTION # DESCRIPTION
@ -63,12 +63,18 @@ If the algorithm concludes without an EGC, the cell is rendered with no glyph
and a default background. If the algorithm concludes without a color locked in, and a default background. If the algorithm concludes without a color locked in,
the color as computed thus far is used. the color as computed thus far is used.
**notcurses_at_yx** retrieves a call *as rendered*. The EGC in that cell is
copied and returned; it must be **free(3)**d by the caller.
# RETURN VALUES # RETURN VALUES
On success, 0 is returned. On failure, a non-zero value is returned. A success On success, 0 is returned. On failure, a non-zero value is returned. A success
will result in the **renders** stat being increased by 1. A failure will result will result in the **renders** stat being increased by 1. A failure will result
in the **failed_renders** stat being increased by 1. in the **failed_renders** stat being increased by 1.
**notcurses_at_yx** returns a heap-allocated copy of the cell's EGC on success,
and **NULL** on failure.
# BUGS # BUGS
In addition to the RGB colors, it is possible to use the "default foreground color" In addition to the RGB colors, it is possible to use the "default foreground color"

@ -203,9 +203,9 @@ namespace ncpp
return notcurses_getc_nblock (nc, ni); return notcurses_getc_nblock (nc, ni);
} }
char* get_at (int yoff, int xoff, Cell &c) const noexcept char* get_at (int yoff, int xoff, uint32_t* attr, uint64_t* channels) const noexcept
{ {
return notcurses_at_yx (nc, yoff, xoff, c); return notcurses_at_yx (nc, yoff, xoff, attr, channels);
} }
void drop_planes () const noexcept void drop_planes () const noexcept

@ -381,10 +381,10 @@ notcurses_term_dim_yx(struct notcurses* n, int* RESTRICT rows, int* RESTRICT col
} }
// Retrieve the contents of the specified cell as last rendered. The EGC is // Retrieve the contents of the specified cell as last rendered. The EGC is
// returned, or NULL on error. This EGC must be free()d by the caller. The cell // returned, or NULL on error. This EGC must be free()d by the caller. The
// 'c' is not bound to a plane, and thus its gcluster value must not be used-- // attrword and channels are written to 'attrword' and 'channels', respectively.
// use the return value only. API char* notcurses_at_yx(struct notcurses* nc, int yoff, int xoff,
API char* notcurses_at_yx(struct notcurses* nc, int yoff, int xoff, cell* c); uint32_t* attrword, uint64_t* channels);
// Alignment within the ncplane. Left/right-justified, or centered. // Alignment within the ncplane. Left/right-justified, or centered.
typedef enum { typedef enum {

@ -129,7 +129,7 @@ int ncplane_move_bottom(struct ncplane* n);
int ncplane_move_below(struct ncplane* n, struct ncplane* below); int ncplane_move_below(struct ncplane* n, struct ncplane* below);
int ncplane_move_above(struct ncplane* n, struct ncplane* above); int ncplane_move_above(struct ncplane* n, struct ncplane* above);
struct ncplane* ncplane_below(struct ncplane* n); struct ncplane* ncplane_below(struct ncplane* n);
char* notcurses_at_yx(struct notcurses* nc, int yoff, int xoff, cell* c); char* notcurses_at_yx(struct notcurses* nc, int yoff, int xoff, uint32_t* attrword, uint64_t* channels);
int ncplane_at_cursor(struct ncplane* n, cell* c); int ncplane_at_cursor(struct ncplane* n, cell* c);
int ncplane_at_yx(struct ncplane* n, int y, int x, cell* c); int ncplane_at_yx(struct ncplane* n, int y, int x, cell* c);
void* ncplane_set_userptr(struct ncplane* n, void* opaque); void* ncplane_set_userptr(struct ncplane* n, void* opaque);

@ -1040,16 +1040,16 @@ int notcurses_render(notcurses* nc){
return ret; return ret;
} }
char* notcurses_at_yx(notcurses* nc, int y, int x, cell* c){ char* notcurses_at_yx(notcurses* nc, int yoff, int xoff, uint32_t* attrword, uint64_t* channels){
char* egc = NULL; char* egc = NULL;
if(nc->lastframe){ if(nc->lastframe){
if(y >= 0 && y < nc->lfdimy){ if(yoff >= 0 && yoff < nc->lfdimy){
if(x >= 0 || x < nc->lfdimx){ if(xoff >= 0 || xoff < nc->lfdimx){
const cell* srccell = &nc->lastframe[y * nc->lfdimx + x]; const cell* srccell = &nc->lastframe[yoff * nc->lfdimx + xoff];
memcpy(c, srccell, sizeof(*c)); // unsafe copy of gcluster *attrword = srccell->attrword;
//fprintf(stderr, "COPYING: %d from %p\n", c->gcluster, &nc->pool); *channels = srccell->channels;
//fprintf(stderr, "COPYING: %d from %p\n", srccell->gcluster, &nc->pool);
egc = pool_egc_copy(&nc->pool, srccell); egc = pool_egc_copy(&nc->pool, srccell);
c->gcluster = 0; // otherwise cell_release() will blow up
} }
} }
} }

@ -922,14 +922,14 @@ TEST_CASE("NCPlane") {
REQUIRE(0 < ncplane_at_yx(ncp, 1, 0, &c)); REQUIRE(0 < ncplane_at_yx(ncp, 1, 0, &c));
CHECK(!strcmp(cell_extended_gcluster(ncp, &c), "")); CHECK(!strcmp(cell_extended_gcluster(ncp, &c), ""));
cell_release(ncp, &c); cell_release(ncp, &c);
char* egc = notcurses_at_yx(nc_, 1, 0, &c); char* egc = notcurses_at_yx(nc_, 1, 0, &c.attrword, &c.channels);
REQUIRE(egc); REQUIRE(egc);
CHECK(!strcmp(egc, "")); CHECK(!strcmp(egc, ""));
free(egc); free(egc);
REQUIRE(0 < ncplane_at_yx(ncp, 1, 3, &c)); REQUIRE(0 < ncplane_at_yx(ncp, 1, 3, &c));
CHECK(!strcmp(cell_extended_gcluster(ncp, &c), "")); CHECK(!strcmp(cell_extended_gcluster(ncp, &c), ""));
cell_release(ncp, &c); cell_release(ncp, &c);
egc = notcurses_at_yx(nc_, 1, 3, &c); egc = notcurses_at_yx(nc_, 1, 3, &c.attrword, &c.channels);
REQUIRE(egc); REQUIRE(egc);
CHECK(!strcmp(egc, "")); CHECK(!strcmp(egc, ""));
free(egc); free(egc);
@ -950,16 +950,16 @@ TEST_CASE("NCPlane") {
ncplane_at_yx(n_, 0, 4, &c); ncplane_at_yx(n_, 0, 4, &c);
CHECK(!cell_double_wide_p(&c)); CHECK(!cell_double_wide_p(&c));
REQUIRE(0 == notcurses_render(nc_)); REQUIRE(0 == notcurses_render(nc_));
notcurses_at_yx(nc_, 0, 0, &c); notcurses_at_yx(nc_, 0, 0, &c.attrword, &c.channels);
CHECK(cell_double_wide_p(&c)); CHECK(0 != (c.channels & 0x8000000080000000ull));
notcurses_at_yx(nc_, 0, 1, &c); notcurses_at_yx(nc_, 0, 1, &c.attrword, &c.channels);
CHECK(cell_double_wide_p(&c)); CHECK(0 != (c.channels & 0x8000000080000000ull));
notcurses_at_yx(nc_, 0, 2, &c); notcurses_at_yx(nc_, 0, 2, &c.attrword, &c.channels);
CHECK(cell_double_wide_p(&c)); CHECK(0 != (c.channels & 0x8000000080000000ull));
notcurses_at_yx(nc_, 0, 3, &c); notcurses_at_yx(nc_, 0, 3, &c.attrword, &c.channels);
CHECK(cell_double_wide_p(&c)); CHECK(0 != (c.channels & 0x8000000080000000ull));
notcurses_at_yx(nc_, 0, 4, &c); notcurses_at_yx(nc_, 0, 4, &c.attrword, &c.channels);
CHECK(!cell_double_wide_p(&c)); CHECK(!(c.channels & 0x8000000080000000ull));
} }
SUBCASE("Perimeter") { SUBCASE("Perimeter") {

@ -102,7 +102,7 @@ TEST_CASE("Palette256") {
cell_release(n_, &c); cell_release(n_, &c);
CHECK(0 == notcurses_render(nc_)); CHECK(0 == notcurses_render(nc_));
cell r = CELL_TRIVIAL_INITIALIZER; cell r = CELL_TRIVIAL_INITIALIZER;
CHECK(nullptr != notcurses_at_yx(nc_, 0, 0, &r)); CHECK(nullptr != notcurses_at_yx(nc_, 0, 0, &r.attrword, &r.channels));
CHECK(cell_fg_palindex_p(&r)); CHECK(cell_fg_palindex_p(&r));
CHECK(cell_bg_palindex_p(&r)); CHECK(cell_bg_palindex_p(&r));
CHECK(CELL_ALPHA_OPAQUE == cell_fg_alpha(&r)); CHECK(CELL_ALPHA_OPAQUE == cell_fg_alpha(&r));

@ -48,7 +48,7 @@ TEST_CASE("RenderTest") {
CHECK(!strcmp("\xe5\x85\xa8", egc)); CHECK(!strcmp("\xe5\x85\xa8", egc));
CHECK(cell_double_wide_p(&c)); CHECK(cell_double_wide_p(&c));
free(egc); free(egc);
egc = notcurses_at_yx(nc_, 0, 0, &c); egc = notcurses_at_yx(nc_, 0, 0, &c.attrword, &c.channels);
REQUIRE(egc); REQUIRE(egc);
CHECK(!strcmp("\xe5\x85\xa8", egc)); CHECK(!strcmp("\xe5\x85\xa8", egc));
CHECK(cell_double_wide_p(&c)); CHECK(cell_double_wide_p(&c));
@ -61,7 +61,7 @@ TEST_CASE("RenderTest") {
CHECK(!strcmp("", egc)); CHECK(!strcmp("", egc));
CHECK(cell_double_wide_p(&c)); CHECK(cell_double_wide_p(&c));
free(egc); free(egc);
egc = notcurses_at_yx(nc_, 0, 1, &c); egc = notcurses_at_yx(nc_, 0, 1, &c.attrword, &c.channels);
REQUIRE(egc); REQUIRE(egc);
CHECK(!strcmp("", egc)); CHECK(!strcmp("", egc));
CHECK(cell_double_wide_p(&c)); CHECK(cell_double_wide_p(&c));
@ -75,7 +75,7 @@ TEST_CASE("RenderTest") {
CHECK(!strcmp("\xe5\xbd\xa2", egc)); CHECK(!strcmp("\xe5\xbd\xa2", egc));
CHECK(cell_double_wide_p(&c)); CHECK(cell_double_wide_p(&c));
free(egc); free(egc);
egc = notcurses_at_yx(nc_, 0, 2, &c); egc = notcurses_at_yx(nc_, 0, 2, &c.attrword, &c.channels);
REQUIRE(egc); REQUIRE(egc);
CHECK(!strcmp("\xe5\xbd\xa2", egc)); CHECK(!strcmp("\xe5\xbd\xa2", egc));
CHECK(cell_double_wide_p(&c)); CHECK(cell_double_wide_p(&c));
@ -88,7 +88,7 @@ TEST_CASE("RenderTest") {
CHECK(!strcmp("", egc)); CHECK(!strcmp("", egc));
CHECK(cell_double_wide_p(&c)); CHECK(cell_double_wide_p(&c));
free(egc); free(egc);
egc = notcurses_at_yx(nc_, 0, 3, &c); egc = notcurses_at_yx(nc_, 0, 3, &c.attrword, &c.channels);
REQUIRE(egc); REQUIRE(egc);
CHECK(!strcmp("", egc)); CHECK(!strcmp("", egc));
CHECK(cell_double_wide_p(&c)); CHECK(cell_double_wide_p(&c));
@ -101,26 +101,26 @@ TEST_CASE("RenderTest") {
CHECK(!notcurses_render(nc_)); CHECK(!notcurses_render(nc_));
// should be nothing, having been stomped // should be nothing, having been stomped
egc = notcurses_at_yx(nc_, 0, 0, &c); egc = notcurses_at_yx(nc_, 0, 0, &c.attrword, &c.channels);
REQUIRE(egc); REQUIRE(egc);
CHECK(!strcmp(" ", egc)); CHECK(!strcmp(" ", egc));
free(egc); free(egc);
cell_init(&c); cell_init(&c);
// should be character from higher plane // should be character from higher plane
egc = notcurses_at_yx(nc_, 0, 1, &c); egc = notcurses_at_yx(nc_, 0, 1, &c.attrword, &c.channels);
REQUIRE(egc); REQUIRE(egc);
CHECK(!strcmp("A", egc)); CHECK(!strcmp("A", egc));
free(egc); free(egc);
cell_init(&c); cell_init(&c);
egc = notcurses_at_yx(nc_, 0, 2, &c); egc = notcurses_at_yx(nc_, 0, 2, &c.attrword, &c.channels);
REQUIRE(egc); REQUIRE(egc);
CHECK(!strcmp("B", egc)); CHECK(!strcmp("B", egc));
free(egc); free(egc);
cell_init(&c); cell_init(&c);
// should be nothing, having been stomped // should be nothing, having been stomped
egc = notcurses_at_yx(nc_, 0, 3, &c); egc = notcurses_at_yx(nc_, 0, 3, &c.attrword, &c.channels);
REQUIRE(egc); REQUIRE(egc);
CHECK(!strcmp(" ", egc)); CHECK(!strcmp(" ", egc));
free(egc); free(egc);

Loading…
Cancel
Save