Merge branch 'master' of github.com:dankamongmen/notcurses

This commit is contained in:
nick black 2019-12-18 11:06:21 -05:00
commit 94722c0f28
No known key found for this signature in database
GPG Key ID: 5F43400C21CBFACC
25 changed files with 1614 additions and 832 deletions

View File

@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.13) cmake_minimum_required(VERSION 3.13)
project(notcurses VERSION 0.9.0 project(notcurses VERSION 1.0.0
DESCRIPTION "UI for modern terminal emulators" DESCRIPTION "UI for modern terminal emulators"
HOMEPAGE_URL "https://nick-black.com/dankwiki/index.php/notcurses" HOMEPAGE_URL "https://nick-black.com/dankwiki/index.php/notcurses"
LANGUAGES C CXX) LANGUAGES C CXX)
@ -79,7 +79,7 @@ target_compile_definitions(notcurses-demo
) )
# tiny proofs of concept, one binary per source file # tiny proofs of concept, one binary per source file
file(GLOB POCSRCS CONFIGURE_DEPENDS src/poc/*.c) file(GLOB POCSRCS CONFIGURE_DEPENDS src/poc/*.c src/poc/*.cpp)
foreach(f ${POCSRCS}) foreach(f ${POCSRCS})
get_filename_component(fe "${f}" NAME_WE) get_filename_component(fe "${f}" NAME_WE)
add_executable(${fe} ${f}) add_executable(${fe} ${f})
@ -202,11 +202,11 @@ file(GLOB MANPAGES1 CONFIGURE_DEPENDS doc/man/man1/*)
file(GLOB MANPAGES3 CONFIGURE_DEPENDS doc/man/man3/*) file(GLOB MANPAGES3 CONFIGURE_DEPENDS doc/man/man3/*)
install(FILES install(FILES
${MANPAGES1} ${MANPAGES1}
DESTINATION ${CMAKE_INSTALL_PREFIX}/man/man1 DESTINATION ${CMAKE_INSTALL_PREFIX}/share/man/man1
) )
install(FILES install(FILES
${MANPAGES3} ${MANPAGES3}
DESTINATION ${CMAKE_INSTALL_PREFIX}/man/man3 DESTINATION ${CMAKE_INSTALL_PREFIX}/share/man/man3
) )
install(TARGETS notcurses-demo DESTINATION bin) install(TARGETS notcurses-demo DESTINATION bin)

118
README.md
View File

@ -14,8 +14,8 @@ by [nick black](https://nick-black.com/dankwiki/index.php/Hack_on) (<nickblack@l
* [Requirements](#requirements) * [Requirements](#requirements)
* [Use](#use) * [Use](#use)
* [Input](#input) * [Input](#input)
* [Planes](#planes) * [Planes](#planes) ([Plane Channels API](#plane-channels-api), [Wide chars](#wide-chars))
* [Cells](#cells) * [Cells](#cells) ([Cell Channels API](#cell-channels-api))
* [Multimedia](#multimedia) * [Multimedia](#multimedia)
* [Panelreels](#panelreels) * [Panelreels](#panelreels)
* [Channels](#channels) * [Channels](#channels)
@ -25,6 +25,7 @@ by [nick black](https://nick-black.com/dankwiki/index.php/Hack_on) (<nickblack@l
* [Features missing relative to NCURSES](#features-missing-relative-to-ncurses) * [Features missing relative to NCURSES](#features-missing-relative-to-ncurses)
* [Adapting NCURSES programs](#adapting-ncurses-programs) * [Adapting NCURSES programs](#adapting-ncurses-programs)
* [Environment notes](#environment-notes) * [Environment notes](#environment-notes)
* [DirectColor detection](#DirectColor-detection)
* [Fonts](#fonts) * [Fonts](#fonts)
* [Supplemental material](#supplemental-material) * [Supplemental material](#supplemental-material)
* [Useful links](#useful-links) * [Useful links](#useful-links)
@ -41,7 +42,7 @@ by [nick black](https://nick-black.com/dankwiki/index.php/Hack_on) (<nickblack@l
* **What it is not**: a source-compatible X/Open Curses implementation, nor a * **What it is not**: a source-compatible X/Open Curses implementation, nor a
replacement for NCURSES on existing systems, nor a widely-ported and -tested replacement for NCURSES on existing systems, nor a widely-ported and -tested
bedrock of Open Source, nor a battle-proven, veteran library. bedrock of free software, nor a battle-proven, veteran library.
notcurses abandons the X/Open Curses API bundled as part of the Single UNIX notcurses abandons the X/Open Curses API bundled as part of the Single UNIX
Specification. The latter shows its age, and seems not capable of making use of Specification. The latter shows its age, and seems not capable of making use of
@ -202,6 +203,9 @@ you off guard.
Utility functions operating on the toplevel `notcurses` object include: Utility functions operating on the toplevel `notcurses` object include:
```c ```c
// Return the topmost ncplane, of which there is always at least one.
struct ncplane* notcurses_top(struct notcurses* n);
// Refresh our idea of the terminal's dimensions, reshaping the standard plane // Refresh our idea of the terminal's dimensions, reshaping the standard plane
// if necessary. Without a call to this function following a terminal resize // if necessary. Without a call to this function following a terminal resize
// (as signaled via SIGWINCH), notcurses_render() might not function properly. // (as signaled via SIGWINCH), notcurses_render() might not function properly.
@ -376,12 +380,11 @@ corresponding to a bare NCURSES `WINDOW`.
// of the resized ncplane. Finally, 'ylen' and 'xlen' are the dimensions of the // of the resized ncplane. Finally, 'ylen' and 'xlen' are the dimensions of the
// ncplane after resizing. 'ylen' must be greater than or equal to 'keepleny', // ncplane after resizing. 'ylen' must be greater than or equal to 'keepleny',
// and 'xlen' must be greater than or equal to 'keeplenx'. It is an error to // and 'xlen' must be greater than or equal to 'keeplenx'. It is an error to
// attempt to resize the standard plane. If either of 'keepy' or 'keepx' is // attempt to resize the standard plane. If either of 'keepleny' or 'keeplenx'
// non-zero, both must be non-zero. // is non-zero, both must be non-zero.
// //
// Essentially, the kept material does not move. It serves to anchor the // Essentially, the kept material does not move. It serves to anchor the
// resized plane. If there is no kept material, the plane can move freely: // resized plane. If there is no kept material, the plane can move freely.
// it is possible to implement ncplane_move() in terms of ncplane_resize().
int ncplane_resize(struct ncplane* n, int keepy, int keepx, int keepleny, int ncplane_resize(struct ncplane* n, int keepy, int keepx, int keepleny,
int keeplenx, int yoff, int xoff, int ylen, int xlen); int keeplenx, int yoff, int xoff, int ylen, int xlen);
@ -420,6 +423,8 @@ void ncplane_styles_off(struct ncplane* n, unsigned stylebits);
// Return the current styling for this ncplane. // Return the current styling for this ncplane.
unsigned ncplane_styles(const struct ncplane* n); unsigned ncplane_styles(const struct ncplane* n);
// Return the ncplane below this one, or NULL if this is at the stack's bottom.
struct ncplane* ncplane_below(struct ncplane* n);
``` ```
If a given cell's glyph is zero, or its foreground channel is fully transparent, If a given cell's glyph is zero, or its foreground channel is fully transparent,
@ -463,6 +468,10 @@ not necessarily reflect anything on the actual screen).
// it in 'c'. This copy is safe to use until the ncplane is destroyed/erased. // it in 'c'. This copy is safe to use until the ncplane is destroyed/erased.
int ncplane_at_cursor(struct ncplane* n, cell* c); int ncplane_at_cursor(struct ncplane* n, cell* c);
// Retrieve the cell at the specified location on the specified plane, returning
// it in 'c'. This copy is safe to use until the ncplane is destroyed/erased.
int ncplane_at_yx(struct ncplane* n, int y, int x, cell* c);
// Manipulate the opaque user pointer associated with this plane. // Manipulate the opaque user pointer associated with this plane.
// ncplane_set_userptr() returns the previous userptr after replacing // ncplane_set_userptr() returns the previous userptr after replacing
// it with 'opaque'. the others simply return the userptr. // it with 'opaque'. the others simply return the userptr.
@ -710,12 +719,9 @@ ncplane_rounded_box(struct ncplane* n, uint32_t attr, uint64_t channels,
if((ret = cells_rounded_box(n, attr, channels, &ul, &ur, &ll, &lr, &hl, &vl)) == 0){ if((ret = cells_rounded_box(n, attr, channels, &ul, &ur, &ll, &lr, &hl, &vl)) == 0){
ret = ncplane_box(n, &ul, &ur, &ll, &lr, &hl, &vl, ystop, xstop, ctlword); ret = ncplane_box(n, &ul, &ur, &ll, &lr, &hl, &vl, ystop, xstop, ctlword);
} }
cell_release(n, &ul); cell_release(n, &ul); cell_release(n, &ur);
cell_release(n, &ur); cell_release(n, &ll); cell_release(n, &lr);
cell_release(n, &ll); cell_release(n, &hl); cell_release(n, &vl);
cell_release(n, &lr);
cell_release(n, &hl);
cell_release(n, &vl);
return ret; return ret;
} }
@ -738,12 +744,9 @@ ncplane_double_box(struct ncplane* n, uint32_t attr, uint64_t channels,
if((ret = cells_double_box(n, attr, channels, &ul, &ur, &ll, &lr, &hl, &vl)) == 0){ if((ret = cells_double_box(n, attr, channels, &ul, &ur, &ll, &lr, &hl, &vl)) == 0){
ret = ncplane_box(n, &ul, &ur, &ll, &lr, &hl, &vl, ystop, xstop, ctlword); ret = ncplane_box(n, &ul, &ur, &ll, &lr, &hl, &vl, ystop, xstop, ctlword);
} }
cell_release(n, &ul); cell_release(n, &ul); cell_release(n, &ur);
cell_release(n, &ur); cell_release(n, &ll); cell_release(n, &lr);
cell_release(n, &ll); cell_release(n, &hl); cell_release(n, &vl);
cell_release(n, &lr);
cell_release(n, &hl);
cell_release(n, &vl);
return ret; return ret;
} }
@ -870,6 +873,32 @@ void ncplane_set_fg_default(struct ncplane* n);
void ncplane_set_bg_default(struct ncplane* n); void ncplane_set_bg_default(struct ncplane* n);
``` ```
#### Wide chars
Notcurses assumes that all glyphs occupy widths which are an integral multiple
of the smallest possible glyph's cell width (aka a "fixed-width font"). Unicode
introduces characters which generally occupy two such cells, known as wide
characters (though in the end, width of a glyph is a property of the font). It
is not possible to print half of such a glyph, nor is it generally possible to
print a wide glyph on the last column of a terminal.
Notcurses does not consider it an error to place a wide character on the last
column of a line. It will obliterate any content which was in that cell, but
will not itself be rendered. The default content will not be reproduced in such
a cell, either. When any character is placed atop a wide character's left or
right half, the wide character is obliterated in its entirety. When a wide
character is placed, any character under its left or right side is annihilated,
including wide characters. It is thus possible for two wide characters to sit
at columns 0 and 2, and for both to be obliterated by a single wide character
placed at column 1.
Likewise, when rendering, a plane which would partially obstruct a wide glyph
prevents it from being rendered entirely. A pathological case would be that of
a terminal _n_ columns in width, containing _n-1_ planes, each 2 columns wide.
The planes are placed at offsets [0..n - 2]. Each plane is above the plane to
its left, and each plane contains a single wide character. Were this to be
rendered, only the rightmost plane (and its single glyph) would be rendered!
### Cells ### Cells
Unlike the `notcurses` or `ncplane` objects, the definition of `cell` is Unlike the `notcurses` or `ncplane` objects, the definition of `cell` is
@ -934,12 +963,14 @@ typedef struct cell {
#define CELL_ALPHA_OPAQUE 0 #define CELL_ALPHA_OPAQUE 0
``` ```
`cell`s must be initialized with `CELL_TRIVIAL_INITIALIZER` or `cell_init()` `cell`s must be initialized with an initialization macro or `cell_init()`
before any other use (both merely zero out the `cell`). before any other use. `cell_init()` and `CELL_TRIVIAL_INITIALIZER` both
simply zero out the `cell`.
```c ```c
#define CELL_TRIVIAL_INITIALIZER { .gcluster = '\0', .attrword = 0, .channels = 0, } #define CELL_TRIVIAL_INITIALIZER { .gcluster = '\0', .attrword = 0, .channels = 0, }
#define CELL_SIMPLE_INITIALIZER(c) { .gcluster = c, .attrword = 0, .channels = 0, } #define CELL_SIMPLE_INITIALIZER(c) { .gcluster = (c), .attrword = 0, .channels = 0, }
#define CELL_INITIALIZER(c, a, chan) { .gcluster = (c), .attrword = (a), .channels = (chan), }
static inline void static inline void
cell_init(cell* c){ cell_init(cell* c){
@ -1038,6 +1069,17 @@ cell_simple_p(const cell* c){
return c->gcluster < 0x80; return c->gcluster < 0x80;
} }
static inline int
cell_load_simple(struct ncplane* n, cell* c, char ch){
cell_release(n, c);
c->channels &= ~CELL_WIDEASIAN_MASK;
c->gcluster = ch;
if(cell_simple_p(c)){
return 1;
}
return -1;
}
// get the offset into the egcpool for this cell's EGC. returns meaningless and // get the offset into the egcpool for this cell's EGC. returns meaningless and
// unsafe results if called on a simple cell. // unsafe results if called on a simple cell.
static inline uint32_t static inline uint32_t
@ -1745,6 +1787,7 @@ acquired using the `notcurses_stats()` function. This function cannot fail.
```c ```c
typedef struct ncstats { typedef struct ncstats {
uint64_t renders; // number of notcurses_render() runs uint64_t renders; // number of notcurses_render() runs
uint64_t failed_renders; // number of aborted renders, should be 0
uint64_t render_bytes; // bytes emitted to ttyfp uint64_t render_bytes; // bytes emitted to ttyfp
uint64_t render_max_bytes; // max bytes emitted for a frame uint64_t render_max_bytes; // max bytes emitted for a frame
uint64_t render_min_bytes; // min bytes emitted for a frame uint64_t render_min_bytes; // min bytes emitted for a frame
@ -1895,19 +1938,38 @@ These are pretty obvious, implementation-wise.
* If your terminal has an option about default interpretation of "ambiguous-width * If your terminal has an option about default interpretation of "ambiguous-width
characters" (this is actually a technical term from Unicode), ensure it is characters" (this is actually a technical term from Unicode), ensure it is
set to **Wide**, not narrow. set to **Wide**, not narrow. If that doesn't work, ensure it is set to
**Narrow**, heh.
* If you can disable BiDi in your terminal, do so while running notcurses * If you can disable BiDi in your terminal, do so while running notcurses
applications, until I have that handled better. notcurses doesn't recognize applications, until I have that handled better. notcurses doesn't recognize
the BiDi state machine transitions, and thus merrily continues writing the BiDi state machine transitions, and thus merrily continues writing
left-to-right. ﷽! left-to-right. Likewise, ultra-wide glyphs will have interesting effects.
﷽!
* The unit tests assume dimensions of at least 80x25. They might work in a * The unit tests assume dimensions of at least 80x25. They might work in a
smaller terminal. They might not. Don't file bugs on it. smaller terminal. They might not. Don't file bugs on it.
### DirectColor detection
notcurses aims to use only information found in the terminal's terminfo entry to detect capabilities, DirectColor
being one of them. Support for this is indicated by terminfo having a flag, added in NCURSES 6.1, named `RGB` set
to `true`. However, as of today there are few and far between terminfo entries which have the capability in their
database entry and so DirectColor won't be used in most cases. Terminal emulators have had for years a kludge to
work around this limitation of terminfo in the form of the `COLORTERM` environment variable which, if set to either
`truecolor` or `24bit` does the job of indicating the capability of sending the escapes 48 and 38 together with a
tripartite RGB (0 ≤ c ≤ 255 for all three components) to specify fore- and background colors.
Checking for `COLORTERM` admittedly goes against the goal stated at the top of this section but, for all practical
purposes, makes the detection work quite well **today**.
### Fonts ### Fonts
Fonts end up being a whole thing. Fonts end up being a whole thing, little of which is pleasant. I'll write this
up someday **FIXME**.
### When all else fails...
...fuck wit' it harder, hax0r.
## Supplemental material ## Supplemental material
@ -1930,9 +1992,13 @@ Fonts end up being a whole thing.
* [tui-rs](https://github.com/fdehau/tui-rs) (Rust) * [tui-rs](https://github.com/fdehau/tui-rs) (Rust)
* [blessed-contrib](https://github.com/yaronn/blessed-contrib) (Javascript) * [blessed-contrib](https://github.com/yaronn/blessed-contrib) (Javascript)
* [FINAL CUT](https://github.com/gansm/finalcut) (C++)
### History ### History
* 2019-12-18: notcurses [0.9.0 "You dig in! You dig out! You get out!"](https://github.com/dankamongmen/notcurses/releases/tag/v0.9.0),
and also the first contributor besides myself (@grendello). Last major
pre-GA release.
* 2019-12-05: notcurses [0.4.0 "TRAP MUSIC ALL NIGHT LONG"](https://github.com/dankamongmen/notcurses/releases/tag/v0.4.0), * 2019-12-05: notcurses [0.4.0 "TRAP MUSIC ALL NIGHT LONG"](https://github.com/dankamongmen/notcurses/releases/tag/v0.4.0),
the first generally usable notcurses. I prepare a [demo](https://www.youtube.com/watch?v=eEv2YRyiEVM), the first generally usable notcurses. I prepare a [demo](https://www.youtube.com/watch?v=eEv2YRyiEVM),
and release it on YouTube. and release it on YouTube.
@ -1961,7 +2027,7 @@ Fonts end up being a whole thing.
* Notcurses could never be what it is without decades of tireless, likely * Notcurses could never be what it is without decades of tireless, likely
thankless work by Thomas E. Dickey on NCURSES. His FAQ is a model of thankless work by Thomas E. Dickey on NCURSES. His FAQ is a model of
engineering history. He exemplifies documentation excellence and engineering history. He exemplifies documentation excellence and
conservative, thoughtful stewardship. The Open Source community owes conservative, thoughtful stewardship. The free software community owes
Mr. Dickey a great debt. Mr. Dickey a great debt.
* Justine Tunney, one of my first friends at Google NYC, was always present * Justine Tunney, one of my first friends at Google NYC, was always present
with support, and pointed out the useful memstream functionality of with support, and pointed out the useful memstream functionality of

13
debian/changelog vendored
View File

@ -1,8 +1,15 @@
notcurses (0.9.0-1) UNRELEASED; urgency=medium notcurses (1.0.0-1) UNRELEASED; urgency=medium
* * Install binaries' man pages in notcurses-bin
* Install library man pages in libnotcurses-dev
-- Nick Black <dankamongmen@gmail.com> Thu, 05 Dec 2019 04:37:32 -0500 -- Nick Black <dankamongmen@gmail.com> Wed, 18 Dec 2019 06:47:44 -0500
notcurses (0.9.0-1) unstable; urgency=medium
* New upstream version
-- Nick Black <dankamongmen@gmail.com> Wed, 18 Dec 2019 05:32:55 -0500
notcurses (0.4.0-1) unstable; urgency=medium notcurses (0.4.0-1) unstable; urgency=medium

View File

@ -2,3 +2,4 @@ usr/include
usr/lib/*/*.so usr/lib/*/*.so
usr/lib/*/pkgconfig/*.pc usr/lib/*/pkgconfig/*.pc
usr/lib/*/cmake/* usr/lib/*/cmake/*
usr/share/man/man3/*

View File

@ -1,2 +1,3 @@
usr/bin/* usr/bin/*
usr/share/notcurses/* usr/share/notcurses/*
usr/man/man1/*

View File

@ -27,25 +27,25 @@ contains a set of text-based demonstrations of capabilities from the notcurses l
.P .P
(i)ntro—a setting of tone (i)ntro—a setting of tone
.P .P
(s)liders—a missing-piece puzzle made up of colorful blocks
.P
(u)niblocks—a series of blocks detailing Unicode pages
.P
(m)axcolors—smoothly changing colors (m)axcolors—smoothly changing colors
.P .P
(l)uigi-a dashing plumber of Apennine persuasion (l)uigi-a dashing plumber of Apennine persuasion
.P .P
(u)niblocks—a series of blocks detailing Unicode pages
.P
(b)oxes—pulsating boxes with a transparent center (b)oxes—pulsating boxes with a transparent center
.P .P
(g)rid—a gradient of color lain atop a great grid (g)rid—a gradient of color lain atop a great grid
.P .P
(w)idecolors—letters of many languages in many colors (s)liders—a missing-piece puzzle made up of colorful blocks
.P .P
(v)iew—in which PNGs are rendered as text, and a video, too (w)idechomper—a gremlin feasts upon wide characters
.P
(v)iew—images and a video are rendered as text
.P .P
(p)anelreels—demonstration of the panelreel high-level widget (p)anelreels—demonstration of the panelreel high-level widget
.P .P
(o)utro—a message of hope from the library author (o)utro—a message of hope from the library's author
.SH NOTES .SH NOTES
Proper display requires a terminal advertising the RGB terminfo(5) capability (necessary for specification of arbitrary 24bpp colors), and a monospaced font with good Unicode support. Proper display requires a terminal advertising the RGB terminfo(5) capability (necessary for specification of arbitrary 24bpp colors), and a monospaced font with good Unicode support.
.SH SEE ALSO .SH SEE ALSO

View File

@ -133,6 +133,9 @@ API int notcurses_stop(struct notcurses* nc);
// successful call to notcurses_render(). // successful call to notcurses_render().
API int notcurses_render(struct notcurses* nc); API int notcurses_render(struct notcurses* nc);
// Return the topmost ncplane, of which there is always at least one.
API struct ncplane* notcurses_top(struct notcurses* n);
// All input is currently taken from stdin, though this will likely change. We // All input is currently taken from stdin, though this will likely change. We
// attempt to read a single UTF8-encoded Unicode codepoint, *not* an entire // attempt to read a single UTF8-encoded Unicode codepoint, *not* an entire
// Extended Grapheme Cluster. It is also possible that we will read a special // Extended Grapheme Cluster. It is also possible that we will read a special
@ -279,7 +282,8 @@ API unsigned notcurses_supported_styles(const struct notcurses* nc);
API int notcurses_palette_size(const struct notcurses* nc); API int notcurses_palette_size(const struct notcurses* nc);
typedef struct ncstats { typedef struct ncstats {
uint64_t renders; // number of notcurses_render() runs uint64_t renders; // number of successful notcurses_render() runs
uint64_t failed_renders; // number of aborted renders, should be 0
uint64_t render_bytes; // bytes emitted to ttyfp uint64_t render_bytes; // bytes emitted to ttyfp
int64_t render_max_bytes; // max bytes emitted for a frame int64_t render_max_bytes; // max bytes emitted for a frame
int64_t render_min_bytes; // min bytes emitted for a frame int64_t render_min_bytes; // min bytes emitted for a frame
@ -309,12 +313,11 @@ API void notcurses_stats(const struct notcurses* nc, ncstats* stats);
// of the resized ncplane. Finally, 'ylen' and 'xlen' are the dimensions of the // of the resized ncplane. Finally, 'ylen' and 'xlen' are the dimensions of the
// ncplane after resizing. 'ylen' must be greater than or equal to 'keepleny', // ncplane after resizing. 'ylen' must be greater than or equal to 'keepleny',
// and 'xlen' must be greater than or equal to 'keeplenx'. It is an error to // and 'xlen' must be greater than or equal to 'keeplenx'. It is an error to
// attempt to resize the standard plane. If either of 'keepy' or 'keepx' is // attempt to resize the standard plane. If either of 'keepleny' or 'keeplenx'
// non-zero, both must be non-zero. // is non-zero, both must be non-zero.
// //
// Essentially, the kept material does not move. It serves to anchor the // Essentially, the kept material does not move. It serves to anchor the
// resized plane. If there is no kept material, the plane can move freely: // resized plane. If there is no kept material, the plane can move freely.
// it is possible to implement ncplane_move() in terms of ncplane_resize().
API int ncplane_resize(struct ncplane* n, int keepy, int keepx, int keepleny, API int ncplane_resize(struct ncplane* n, int keepy, int keepx, int keepleny,
int keeplenx, int yoff, int xoff, int ylen, int xlen); int keeplenx, int yoff, int xoff, int ylen, int xlen);
@ -342,16 +345,44 @@ API void ncplane_yx(const struct ncplane* n, int* RESTRICT y, int* RESTRICT x);
API int ncplane_move_top(struct ncplane* n); API int ncplane_move_top(struct ncplane* n);
API int ncplane_move_bottom(struct ncplane* n); API int ncplane_move_bottom(struct ncplane* n);
// Splice ncplane 'n' out of the z-buffer, and reinsert it below 'below'.
API int ncplane_move_below(struct ncplane* RESTRICT n, struct ncplane* RESTRICT below);
// Splice ncplane 'n' out of the z-buffer, and reinsert it above 'above'. // Splice ncplane 'n' out of the z-buffer, and reinsert it above 'above'.
API int ncplane_move_above(struct ncplane* RESTRICT n, struct ncplane* RESTRICT above); API int ncplane_move_above_unsafe(struct ncplane* RESTRICT n,
struct ncplane* RESTRICT above);
static inline int
ncplane_move_above(struct ncplane* n, struct ncplane* above){
if(n == above){
return -1;
}
return ncplane_move_above_unsafe(n, above);
}
// Splice ncplane 'n' out of the z-buffer, and reinsert it below 'below'.
API int ncplane_move_below_unsafe(struct ncplane* RESTRICT n,
struct ncplane* RESTRICT below);
static inline int
ncplane_move_below(struct ncplane* n, struct ncplane* below){
if(n == below){
return -1;
}
return ncplane_move_below_unsafe(n, below);
}
// Return the plane above this one, or NULL if this is at the top.
API struct ncplane* ncplane_below(struct ncplane* n);
// Return the plane below this one, or NULL if this is at the bottom.
API struct ncplane* ncplane_below(struct ncplane* n);
// Retrieve the cell at the cursor location on the specified plane, returning // Retrieve the cell at the cursor location on the specified plane, returning
// it in 'c'. This copy is safe to use until the ncplane is destroyed/erased. // it in 'c'. This copy is safe to use until the ncplane is destroyed/erased.
API int ncplane_at_cursor(struct ncplane* n, cell* c); API int ncplane_at_cursor(struct ncplane* n, cell* c);
// Retrieve the cell at the specified location on the specified plane, returning
// it in 'c'. This copy is safe to use until the ncplane is destroyed/erased.
API int ncplane_at_yx(struct ncplane* n, int y, int x, cell* c);
// Manipulate the opaque user pointer associated with this plane. // Manipulate the opaque user pointer associated with this plane.
// ncplane_set_userptr() returns the previous userptr after replacing // ncplane_set_userptr() returns the previous userptr after replacing
// it with 'opaque'. the others simply return the userptr. // it with 'opaque'. the others simply return the userptr.
@ -1074,7 +1105,8 @@ API int ncplane_fadein(struct ncplane* n, const struct timespec* ts);
// Working with cells // Working with cells
#define CELL_TRIVIAL_INITIALIZER { .gcluster = '\0', .attrword = 0, .channels = 0, } #define CELL_TRIVIAL_INITIALIZER { .gcluster = '\0', .attrword = 0, .channels = 0, }
#define CELL_SIMPLE_INITIALIZER(c) { .gcluster = c, .attrword = 0, .channels = 0, } #define CELL_SIMPLE_INITIALIZER(c) { .gcluster = (c), .attrword = 0, .channels = 0, }
#define CELL_INITIALIZER(c, a, chan) { .gcluster = (c), .attrword = (a), .channels = (chan), }
static inline void static inline void
cell_init(cell* c){ cell_init(cell* c){
@ -1174,6 +1206,17 @@ cell_simple_p(const cell* c){
return c->gcluster < 0x80; return c->gcluster < 0x80;
} }
static inline int
cell_load_simple(struct ncplane* n, cell* c, char ch){
cell_release(n, c);
c->channels &= ~CELL_WIDEASIAN_MASK;
c->gcluster = ch;
if(cell_simple_p(c)){
return 1;
}
return -1;
}
// get the offset into the egcpool for this cell's EGC. returns meaningless and // get the offset into the egcpool for this cell's EGC. returns meaningless and
// unsafe results if called on a simple cell. // unsafe results if called on a simple cell.
static inline uint32_t static inline uint32_t
@ -1231,12 +1274,9 @@ ncplane_rounded_box(struct ncplane* n, uint32_t attr, uint64_t channels,
if((ret = cells_rounded_box(n, attr, channels, &ul, &ur, &ll, &lr, &hl, &vl)) == 0){ if((ret = cells_rounded_box(n, attr, channels, &ul, &ur, &ll, &lr, &hl, &vl)) == 0){
ret = ncplane_box(n, &ul, &ur, &ll, &lr, &hl, &vl, ystop, xstop, ctlword); ret = ncplane_box(n, &ul, &ur, &ll, &lr, &hl, &vl, ystop, xstop, ctlword);
} }
cell_release(n, &ul); cell_release(n, &ul); cell_release(n, &ur);
cell_release(n, &ur); cell_release(n, &ll); cell_release(n, &lr);
cell_release(n, &ll); cell_release(n, &hl); cell_release(n, &vl);
cell_release(n, &lr);
cell_release(n, &hl);
cell_release(n, &vl);
return ret; return ret;
} }
@ -1265,12 +1305,9 @@ ncplane_double_box(struct ncplane* n, uint32_t attr, uint64_t channels,
if((ret = cells_double_box(n, attr, channels, &ul, &ur, &ll, &lr, &hl, &vl)) == 0){ if((ret = cells_double_box(n, attr, channels, &ul, &ur, &ll, &lr, &hl, &vl)) == 0){
ret = ncplane_box(n, &ul, &ur, &ll, &lr, &hl, &vl, ystop, xstop, ctlword); ret = ncplane_box(n, &ul, &ur, &ll, &lr, &hl, &vl, ystop, xstop, ctlword);
} }
cell_release(n, &ul); cell_release(n, &ul); cell_release(n, &ur);
cell_release(n, &ur); cell_release(n, &ll); cell_release(n, &lr);
cell_release(n, &ll); cell_release(n, &hl); cell_release(n, &vl);
cell_release(n, &lr);
cell_release(n, &hl);
cell_release(n, &vl);
return ret; return ret;
} }

View File

@ -49,28 +49,28 @@ int box_demo(struct notcurses* nc){
if(ncplane_putstr_aligned(n, ytargbase++, "┗━━┻━━┛", NCALIGN_CENTER) < 0){ if(ncplane_putstr_aligned(n, ytargbase++, "┗━━┻━━┛", NCALIGN_CENTER) < 0){
return -1; return -1;
} }
ncplane_set_fg_rgb(n, 255, 255, 255);
ncplane_set_bg_rgb(n, 180, 40, 180);
do{ do{
int y = 0, x = 0; int y = 0, x = 0;
ncplane_dim_yx(n, &ylen, &xlen); ncplane_dim_yx(n, &ylen, &xlen);
while(ylen - y >= targy && xlen - x >= targx){ while(ylen - y >= targy && xlen - x >= targx){
cell_set_fg_rgb(&ul, 107 - (y * 2), zbonus, 107 + (y * 2)); cell_set_fg_rgb(&ul, 107 - (y * 2), zbonus, 107 + (y * 2));
cell_set_bg_rgb(&ul, zbonus, 20 + y, 20 + y); cell_set_bg_rgb(&ul, 20, zbonus, 20);
cell_set_fg_rgb(&ur, 107 - (y * 2), zbonus, 107 + (y * 2)); cell_set_fg_rgb(&ur, 107 - (y * 2), zbonus, 107 + (y * 2));
cell_set_bg_rgb(&ur, zbonus, 20 + y, 20 + y); cell_set_bg_rgb(&ur, 20, zbonus, 20);
cell_set_fg_rgb(&hl, 107 - (y * 2), zbonus, 107 + (y * 2)); cell_set_fg_rgb(&hl, 107 - (y * 2), zbonus, 107 + (y * 2));
cell_set_bg_rgb(&hl, 20, zbonus, 20); cell_set_bg_rgb(&hl, 20, zbonus, 20);
cell_set_fg_rgb(&ll, 107 - (y * 2), zbonus, 107 + (y * 2)); cell_set_fg_rgb(&ll, 107 - (y * 2), zbonus, 107 + (y * 2));
cell_set_bg_rgb(&ll, zbonus, 20 + y, 20 + y); cell_set_bg_rgb(&ll, 20, zbonus, 20);
cell_set_fg_rgb(&lr, 107 - (y * 2), zbonus, 107 + (y * 2)); cell_set_fg_rgb(&lr, 107 - (y * 2), zbonus, 107 + (y * 2));
cell_set_bg_rgb(&lr, zbonus, 20 + y, 20 + y); cell_set_bg_rgb(&lr, 20, zbonus, 20);
cell_set_fg_rgb(&vl, 20, zbonus, 20); cell_set_fg_rgb(&vl, 107 - (y * 2), zbonus, 107 + (y * 2));
cell_set_bg_rgb(&vl, 107 - (y * 2), zbonus, 107 + (y * 2)); cell_set_bg_rgb(&vl, 20, zbonus, 20);
if(ncplane_cursor_move_yx(n, y, x)){ if(ncplane_cursor_move_yx(n, y, x)){
return -1; return -1;
} }
if(ncplane_box_sized(n, &ul, &ur, &ll, &lr, &hl, &vl, ylen, xlen, 0)){ if(ncplane_box_sized(n, &ul, &ur, &ll, &lr, &hl, &vl, ylen, xlen,
NCBOXGRAD_LEFT | NCBOXGRAD_BOTTOM |
NCBOXGRAD_RIGHT | NCBOXGRAD_TOP)){
return -1; return -1;
} }
ylen -= 2; ylen -= 2;

View File

@ -52,7 +52,7 @@ usage(const char* exe, int status){
fprintf(out, " s: run shuffle\n"); fprintf(out, " s: run shuffle\n");
fprintf(out, " u: run uniblock\n"); fprintf(out, " u: run uniblock\n");
fprintf(out, " v: run view\n"); fprintf(out, " v: run view\n");
fprintf(out, " w: run widecolors\n"); fprintf(out, " w: run widechomper\n");
exit(status); exit(status);
} }
@ -63,8 +63,29 @@ intro(struct notcurses* nc){
return -1; return -1;
} }
ncplane_erase(ncp); ncplane_erase(ncp);
if(ncplane_cursor_move_yx(ncp, 0, 0)){
return -1;
}
int x, y, rows, cols; int x, y, rows, cols;
ncplane_dim_yx(ncp, &rows, &cols); ncplane_dim_yx(ncp, &rows, &cols);
cell ul = CELL_TRIVIAL_INITIALIZER, ur = CELL_TRIVIAL_INITIALIZER;
cell ll = CELL_TRIVIAL_INITIALIZER, lr = CELL_TRIVIAL_INITIALIZER;
cell hl = CELL_TRIVIAL_INITIALIZER, vl = CELL_TRIVIAL_INITIALIZER;
if(cells_rounded_box(ncp, CELL_STYLE_BOLD, 0, &ul, &ur, &ll, &lr, &hl, &vl)){
return -1;
}
channels_set_fg_rgb(&ul.channels, 0xff, 0, 0);
channels_set_fg_rgb(&ur.channels, 0, 0xff, 0);
channels_set_fg_rgb(&ll.channels, 0, 0, 0xff);
channels_set_fg_rgb(&lr.channels, 0xff, 0xff, 0xff);
if(ncplane_box_sized(ncp, &ul, &ur, &ll, &lr, &hl, &vl, rows, cols,
NCBOXGRAD_TOP | NCBOXGRAD_BOTTOM |
NCBOXGRAD_RIGHT | NCBOXGRAD_LEFT)){
return -1;
}
cell_release(ncp, &ul); cell_release(ncp, &ur);
cell_release(ncp, &ll); cell_release(ncp, &lr);
cell_release(ncp, &hl); cell_release(ncp, &vl);
cell c; cell c;
cell_init(&c); cell_init(&c);
const char* cstr = "Δ"; const char* cstr = "Δ";
@ -135,7 +156,7 @@ ext_demos(struct notcurses* nc, const char* demos){
case 'g': ret = grid_demo(nc); break; case 'g': ret = grid_demo(nc); break;
case 'l': ret = luigi_demo(nc); break; case 'l': ret = luigi_demo(nc); break;
case 'v': ret = view_demo(nc); break; case 'v': ret = view_demo(nc); break;
case 'w': ret = widecolor_demo(nc); break; case 'w': ret = widechomper_demo(nc); break;
case 'p': ret = panelreel_demo(nc); break; case 'p': ret = panelreel_demo(nc); break;
default: default:
fprintf(stderr, "Unknown demo specification: %c\n", *demos); fprintf(stderr, "Unknown demo specification: %c\n", *demos);

View File

@ -12,7 +12,7 @@ extern "C" {
extern struct timespec demodelay; extern struct timespec demodelay;
int unicodeblocks_demo(struct notcurses* nc); int unicodeblocks_demo(struct notcurses* nc);
int widecolor_demo(struct notcurses* nc); int widechomper_demo(struct notcurses* nc);
int box_demo(struct notcurses* nc); int box_demo(struct notcurses* nc);
int maxcolor_demo(struct notcurses* nc); int maxcolor_demo(struct notcurses* nc);
int grid_demo(struct notcurses* nc); int grid_demo(struct notcurses* nc);

View File

@ -102,6 +102,9 @@ gridswitch_demo(struct notcurses* nc, struct ncplane *n){
// center // center
for(y = 1 ; y < maxy - 1 ; ++y){ for(y = 1 ; y < maxy - 1 ; ++y){
x = 0; x = 0;
if(ncplane_cursor_move_yx(n, y, x)){
return -1;
}
cell_set_fg_rgb(&cl, 255 - rs * y, 255 - gs * (x + y), 255 - bs * x); cell_set_fg_rgb(&cl, 255 - rs * y, 255 - gs * (x + y), 255 - bs * x);
cell_set_bg_rgb(&cl, 255 - rs * x, 255 - gs * (x + y), 255 - bs * y); cell_set_bg_rgb(&cl, 255 - rs * x, 255 - gs * (x + y), 255 - bs * y);
ncplane_putc(n, &cl); ncplane_putc(n, &cl);
@ -117,6 +120,9 @@ gridswitch_demo(struct notcurses* nc, struct ncplane *n){
// bottom line // bottom line
x = 0; x = 0;
if(ncplane_cursor_move_yx(n, y, x)){
return -1;
}
cell_set_fg_rgb(&ll, 255 - rs * y, 255 - gs * (x + y), 255 - bs * x); cell_set_fg_rgb(&ll, 255 - rs * y, 255 - gs * (x + y), 255 - bs * x);
cell_set_bg_rgb(&ll, 255 - rs * x, 255 - gs * (x + y), 255 - bs * y); cell_set_bg_rgb(&ll, 255 - rs * x, 255 - gs * (x + y), 255 - bs * y);
ncplane_putc(n, &ll); ncplane_putc(n, &ll);
@ -167,6 +173,9 @@ gridinv_demo(struct notcurses* nc, struct ncplane *n){
// center // center
for(y = 1 ; y < maxy - 1 ; ++y){ for(y = 1 ; y < maxy - 1 ; ++y){
x = 0; x = 0;
if(ncplane_cursor_move_yx(n, y, x)){
return -1;
}
cell_set_fg_rgb(&cl, 0, 0, 0); cell_set_fg_rgb(&cl, 0, 0, 0);
cell_set_bg_rgb(&cl, 255 - rs * x, 255 - gs * (x + y), 255 - bs * y); cell_set_bg_rgb(&cl, 255 - rs * x, 255 - gs * (x + y), 255 - bs * y);
ncplane_putc(n, &cl); ncplane_putc(n, &cl);
@ -182,6 +191,9 @@ gridinv_demo(struct notcurses* nc, struct ncplane *n){
// bottom line // bottom line
x = 0; x = 0;
if(ncplane_cursor_move_yx(n, y, x)){
return -1;
}
cell_set_fg_rgb(&ll, 0, 0, 0); cell_set_fg_rgb(&ll, 0, 0, 0);
cell_set_bg_rgb(&ll, 255 - rs * x, 255 - gs * (x + y), 255 - bs * y); cell_set_bg_rgb(&ll, 255 - rs * x, 255 - gs * (x + y), 255 - bs * y);
ncplane_putc(n, &ll); ncplane_putc(n, &ll);
@ -233,6 +245,9 @@ int grid_demo(struct notcurses* nc){
// center // center
for(y = 1 ; y < maxy - 1 ; ++y){ for(y = 1 ; y < maxy - 1 ; ++y){
x = 0; x = 0;
if(ncplane_cursor_move_yx(n, y, x)){
return -1;
}
cell_set_bg_rgb(&cl, y, y, y); cell_set_bg_rgb(&cl, y, y, y);
cell_set_bg_rgb(&cc, y, y, y); cell_set_bg_rgb(&cc, y, y, y);
cell_set_bg_rgb(&cr, y, y, y); cell_set_bg_rgb(&cr, y, y, y);
@ -248,6 +263,9 @@ int grid_demo(struct notcurses* nc){
// bottom line // bottom line
x = 0; x = 0;
if(ncplane_cursor_move_yx(n, y, x)){
return -1;
}
cell_set_bg_rgb(&ll, y, y, y); cell_set_bg_rgb(&ll, y, y, y);
cell_set_bg_rgb(&lc, y, y, y); cell_set_bg_rgb(&lc, y, y, y);
cell_set_bg_rgb(&lr, y, y, y); cell_set_bg_rgb(&lr, y, y, y);

View File

@ -5,104 +5,109 @@
//2 = yellow //2 = yellow
//3 = green //3 = green
static const char luigi1[] = "0000000000000000" static const char* luigis[] = {
"0000000000000000" "0000000000000000"
"0000000111110000" "0000000000000000"
"0000011111120000" "0000000111110000"
"0000111111220000" "0000011111120000"
"0000111111111110" "0000111111220000"
"0000333223222000" "0000111111111110"
"0003223223322220" "0000333223222000"
"0003223322222222" "0003223223322220"
"0033223322232222" "0003223322222222"
"0033222223333330" "0033223322232222"
"0003322222333330" "0033222223333330"
"0000032222222200" "0003322222333330"
"0000311122200000" "0000032222222200"
"0003133313000000" "0000311122200000"
"0003133331300000" "0003133313000000"
"0033133333112200" "0003133331300000"
"0031133333332222" "0033133333112200"
"0031113333332222" "0031133333332222"
"0001113333333222" "0031113333332222"
"0001111333333222" "0001113333333222"
"0001111113331000" "0001111333333222"
"0001111111111000" "0001111113331000"
"0001111111113000" "0001111111111000"
"3333111111131100" "0001111111113000"
"3333111113311100" "3333111111131100"
"3333111131111000" "3333111113311100"
"3333111001111000" "3333111131111000"
"3333000003333000" "3333111001111000"
"3300000003333000" "3333000003333000"
"3000000003333330" "3300000003333000"
"0000000003333330"; "3000000003333330"
"0000000003333330",
static const char luigi2[] = "0000000000000000" "0000000000000000"
"0000001111100000" "0000001111100000"
"0000111111200000" "0000111111200000"
"0001111112200000" "0001111112200000"
"0001111111111100" "0001111111111100"
"0003332232220000" "0003332232220000"
"0032232233222200" "0032232233222200"
"0032233222222220" "0032233222222220"
"0332233222322220" "0332233222322220"
"0332222233333300" "0332222233333300"
"0033222223333300" "0033222223333300"
"0003322222222000" "0003322222222000"
"0000111122000000" "0000111122000000"
"0003133113300000" "0003133113300000"
"0031333311300000" "0031333311300000"
"0031333311330000" "0031333311330000"
"0031333311130000" "0031333311130000"
"0031333332230000" "0031333332230000"
"0031333322220000" "0031333322220000"
"0011133322221000" "0011133322221000"
"0011133322221100" "0011133322221100"
"0011113322211100" "0011113322211100"
"0011111133111100" "0011111133111100"
"0001111133311000" "0001111133311000"
"0000111333333000" "0000111333333000"
"0000113333330000" "0000113333330000"
"0000011333300000" "0000011333300000"
"0000031113330000" "0000031113330000"
"0000033330330000" "0000033330330000"
"0000333330000000" "0000333330000000"
"0000333333300000" "0000333333300000"
"0000003333300000"; "0000003333300000",
"0000001111100000"
"0000111111200000"
"0001111112200000"
"0001111111111100"
"0003332232220000"
"0032232233222200"
"0032233222222220"
"0332233222322220"
"0332222233333300"
"0333222223333300"
"0003322222222000"
"0000033322000000"
"0000111133100020"
"0003333113310222"
"0033333311313222"
"0333333311331222"
"0333333311331323"
"0333333111331330"
"3333331112132300"
"3333111111111000"
"2222211111111000"
"2222211111111003"
"2222111111111033"
"0222111111133333"
"0001311111133333"
"0031131111133333"
"3331113311133333"
"3333111100033333"
"3333310000000000"
"0333000000000000"
"0333000000000000"
"0033300000000000",
NULL
};
static const char luigi3[] = "0000001111100000"
"0000111111200000"
"0001111112200000"
"0001111111111100"
"0003332232220000"
"0032232233222200"
"0032233222222220"
"0332233222322220"
"0332222233333300"
"0333222223333300"
"0003322222222000"
"0000033322000000"
"0000111133100020"
"0003333113310222"
"0033333311313222"
"0333333311331222"
"0333333311331323"
"0333333111331330"
"3333331112132300"
"3333111111111000"
"2222211111111000"
"2222211111111003"
"2222111111111033"
"0222111111133333"
"0001311111133333"
"0031131111133333"
"3331113311133333"
"3333111100033333"
"3333310000000000"
"0333000000000000"
"0333000000000000"
"0033300000000000";
static int static int
draw_luigi(struct ncplane* n, const char* sprite){ draw_luigi(struct ncplane* n, const char* sprite){
@ -159,26 +164,25 @@ int luigi_demo(struct notcurses* nc){
int yoff = rows * 4 / 5 - height + 1; // tuned int yoff = rows * 4 / 5 - height + 1; // tuned
struct ncplane* lns[3]; struct ncplane* lns[3];
int i; int i;
struct ncplane* lastseen = NULL;
for(i = 0 ; i < 3 ; ++i){ for(i = 0 ; i < 3 ; ++i){
lns[i] = notcurses_newplane(nc, height, 16, yoff, -16, NULL); lns[i] = notcurses_newplane(nc, height, 16, yoff, 0, NULL);
if(lns[i] == NULL){ if(lns[i] == NULL){
while(--i){ while(--i){
ncplane_destroy(lns[i]); ncplane_destroy(lns[i]);
} }
return -1; return -1;
} }
lastseen = lns[i];
draw_luigi(lns[i], luigis[i]);
ncplane_move_bottom(lastseen); // all start hidden underneath stdplane
} }
draw_luigi(lns[0], luigi1);
draw_luigi(lns[1], luigi2);
draw_luigi(lns[2], luigi3);
struct ncplane* lastseen = NULL;
struct timespec stepdelay; struct timespec stepdelay;
ns_to_timespec(timespec_to_ns(&demodelay) / (cols - 16 - 1), &stepdelay); ns_to_timespec(timespec_to_ns(&demodelay) / (cols - 16 - 1), &stepdelay);
for(i = 0 ; i < cols - 16 - 1 ; ++i){ for(i = 0 ; i < cols - 16 - 1 ; ++i){
if(lastseen){ // hide the previous sprite ncplane_move_bottom(lastseen); // hide the previous sprite
ncplane_move_yx(lastseen, yoff, -16);
}
lastseen = lns[i % 3]; lastseen = lns[i % 3];
ncplane_move_top(lastseen);
ncplane_move_yx(lastseen, yoff, i); ncplane_move_yx(lastseen, yoff, i);
notcurses_render(nc); notcurses_render(nc);
nanosleep(&stepdelay, NULL); nanosleep(&stepdelay, NULL);

View File

@ -22,7 +22,7 @@ view_video_demo(struct notcurses* nc){
ncplane_dim_yx(ncp, &dimy, &dimx); ncplane_dim_yx(ncp, &dimy, &dimx);
int averr; int averr;
struct ncvisual* ncv; struct ncvisual* ncv;
ncv = ncplane_visual_open(ncp, "../tests/bob.mkv", &averr); ncv = ncplane_visual_open(ncp, "../tests/fm6.mkv", &averr);
if(!ncv){ if(!ncv){
return -1; return -1;
} }

View File

@ -7,6 +7,9 @@
#include <pthread.h> #include <pthread.h>
#include "demo.h" #include "demo.h"
// Fill up the screen with as much crazy Unicode as we can, and then set a
// gremlin loose, looking to eat up all the wide characters.
// FIXME throw this in there somehow // FIXME throw this in there somehow
// ∮ E⋅da = Q, n → ∞, ∑ f(i) = ∏ g(i), ⎧⎡⎛┌─────┐⎞⎤⎫ // ∮ E⋅da = Q, n → ∞, ∑ f(i) = ∏ g(i), ⎧⎡⎛┌─────┐⎞⎤⎫
// ⎪⎢⎜│a²+b³ ⎟⎥⎪ // ⎪⎢⎜│a²+b³ ⎟⎥⎪
@ -283,6 +286,7 @@ message(struct ncplane* n, int maxy, int maxx, int num, int total,
int bytes_out, int egs_out, int cols_out){ int bytes_out, int egs_out, int cols_out){
cell c = CELL_TRIVIAL_INITIALIZER; cell c = CELL_TRIVIAL_INITIALIZER;
cell_load(n, &c, " "); cell_load(n, &c, " ");
cell_set_fg_alpha(&c, CELL_ALPHA_TRANS);
cell_set_bg_alpha(&c, CELL_ALPHA_TRANS); cell_set_bg_alpha(&c, CELL_ALPHA_TRANS);
ncplane_set_default(n, &c); ncplane_set_default(n, &c);
cell_release(n, &c); cell_release(n, &c);
@ -297,7 +301,7 @@ message(struct ncplane* n, int maxy, int maxx, int num, int total,
} }
// bottom handle // bottom handle
ncplane_cursor_move_yx(n, 4, 17); ncplane_cursor_move_yx(n, 4, 17);
ncplane_putegc(n, "", 0, 0, NULL); ncplane_putegc(n, "", 0, channels, NULL);
ncplane_cursor_move_yx(n, 5, 17); ncplane_cursor_move_yx(n, 5, 17);
ncplane_putegc(n, "", 0, channels, NULL); ncplane_putegc(n, "", 0, channels, NULL);
ncplane_cursor_move_yx(n, 6, 17); ncplane_cursor_move_yx(n, 6, 17);
@ -344,7 +348,7 @@ message(struct ncplane* n, int maxy, int maxx, int num, int total,
} }
// Much of this text comes from http://kermitproject.org/utf8.html // Much of this text comes from http://kermitproject.org/utf8.html
int widecolor_demo(struct notcurses* nc){ int widechomper_demo(struct notcurses* nc){
static const char* strs[] = { static const char* strs[] = {
"Война и мир", "Война и мир",
"Бра́тья Карама́зовы", "Бра́тья Карама́зовы",
@ -575,9 +579,8 @@ int widecolor_demo(struct notcurses* nc){
NULL NULL
}; };
const char** s; const char** s;
int count = notcurses_palette_size(nc); const int steps[] = { 0x100, 0x100, 0x40000, 0x10001, };
const int steps[] = { 1, 0x100, 0x40000, 0x10001, }; const int starts[] = { 0x004000, 0x000040, 0x010101, 0x400040, };
const int starts[] = { 0x4000, 0x40, 0x10000, 0x400040, };
struct ncplane* n = notcurses_stdplane(nc); struct ncplane* n = notcurses_stdplane(nc);
size_t i; size_t i;
@ -588,13 +591,10 @@ int widecolor_demo(struct notcurses* nc){
cell c; cell c;
struct timespec screenend; struct timespec screenend;
clock_gettime(CLOCK_MONOTONIC, &screenend); clock_gettime(CLOCK_MONOTONIC, &screenend);
ns_to_timespec(timespec_to_ns(&screenend) + timespec_to_ns(&demodelay), &screenend); ns_to_timespec(timespec_to_ns(&screenend) + 2 * timespec_to_ns(&demodelay), &screenend);
do{ // (re)draw a screen do{ // (re)draw a screen
const int start = starts[i]; const int start = starts[i];
int step = steps[i]; int step = steps[i];
const int rollover = 256 / ((step & 0xff) | ((step & 0xff00) >> 8u)
| ((step & 0xff0000) >> 16u));
int rollcount = 0; // number of times we've added this step
cell_init(&c); cell_init(&c);
int y, x, maxy, maxx; int y, x, maxy, maxx;
ncplane_dim_yx(n, &maxy, &maxx); ncplane_dim_yx(n, &maxy, &maxx);
@ -634,11 +634,18 @@ int widecolor_demo(struct notcurses* nc){
} }
int ulen = 0; int ulen = 0;
int r; int r;
if((r = ncplane_putegc(n, &(*s)[idx], 0, channels, &ulen)) < 0){ if(wcwidth(wcs) <= maxx - x){
if(ulen < 0){ if((r = ncplane_putegc(n, &(*s)[idx], 0, channels, &ulen)) < 0){
if(ulen < 0){
return -1;
}
}
}else{
cell octo = CELL_INITIALIZER('#', 0, channels);
if((r = ncplane_putc(n, &octo)) < 1){
return -1; return -1;
} }
break; cell_release(n, &octo);
} }
ncplane_cursor_yx(n, &y, &x); ncplane_cursor_yx(n, &y, &x);
idx += ulen; idx += ulen;
@ -646,19 +653,7 @@ int widecolor_demo(struct notcurses* nc){
cols_out += r; cols_out += r;
++egcs_out; ++egcs_out;
} }
if(++rollcount % rollover == 0){ rgb += step;
step *= 256;
}
if((unsigned)step >= 1ul << 24){
step >>= 24u;
}
if(step == 0){
step = 1;
}
if((rgb += step) >= count){
rgb = 0;
step *= 256;
}
} }
}while(y < maxy && x < maxx); }while(y < maxy && x < maxx);
struct ncplane* mess = notcurses_newplane(nc, 7, 57, 1, 4, NULL); struct ncplane* mess = notcurses_newplane(nc, 7, 57, 1, 4, NULL);
@ -697,7 +692,7 @@ int widecolor_demo(struct notcurses* nc){
pthread_join(tid, NULL); pthread_join(tid, NULL);
ncplane_destroy(mess); ncplane_destroy(mess);
if(key == NCKEY_RESIZE){ if(key == NCKEY_RESIZE){
notcurses_resize(nc, NULL, NULL); notcurses_resize(nc, &maxy, &maxx);
} }
}while(key == NCKEY_RESIZE); }while(key == NCKEY_RESIZE);
} }

View File

@ -84,11 +84,15 @@ typedef struct notcurses {
FILE* ttyfp; // FILE* for controlling tty, from opts->ttyfp FILE* ttyfp; // FILE* for controlling tty, from opts->ttyfp
FILE* ttyinfp; // FILE* for processing input FILE* ttyinfp; // FILE* for processing input
unsigned char* damage; // damage map (row granularity) unsigned char* damage; // damage map (row granularity)
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 int colors; // number of colors usable for this screen
ncstats stats; // some statistics across the lifetime of the notcurses ctx ncstats stats; // some statistics across the lifetime of the notcurses ctx
// We verify that some terminfo capabilities exist. These needn't be checked // We verify that some terminfo capabilities exist. These needn't be checked
// before further use; just use tiparm() directly. // before further use; just use tiparm() directly.
char* cup; // move cursor char* cup; // move cursor
bool RGBflag; // terminfo-reported "RGB" flag for 24bpc directcolor
char* civis; // hide cursor char* civis; // hide cursor
// These might be NULL, and we can more or less work without them. Check! // These might be NULL, and we can more or less work without them. Check!
char* clearscr; // erase screen and home cursor char* clearscr; // erase screen and home cursor
@ -112,9 +116,7 @@ typedef struct notcurses {
char* italoff; // CELL_STYLE_ITALIC (disable) char* italoff; // CELL_STYLE_ITALIC (disable)
char* smkx; // enter keypad transmit mode (keypad_xmit) char* smkx; // enter keypad transmit mode (keypad_xmit)
char* rmkx; // leave keypad transmit mode (keypad_local) char* rmkx; // leave keypad transmit mode (keypad_local)
struct termios tpreserved; // terminal state upon entry struct termios tpreserved; // terminal state upon entry
bool RGBflag; // terminfo-reported "RGB" flag for 24bpc directcolor
bool CCCflag; // terminfo-reported "CCC" flag for palette set capability bool CCCflag; // terminfo-reported "CCC" flag for palette set capability
ncplane* top; // the contents of our topmost plane (initially entire screen) ncplane* top; // the contents of our topmost plane (initially entire screen)
ncplane* stdscr;// aliases some plane from the z-buffer, covers screen ncplane* stdscr;// aliases some plane from the z-buffer, covers screen
@ -168,21 +170,26 @@ flash_damage_map(unsigned char* damage, int count, bool val){
void ncplane_updamage(ncplane* n); void ncplane_updamage(ncplane* n);
// For our first attempt, O(1) uniform conversion from 8-bit r/g/b down to // For our first attempt, O(1) uniform conversion from 8-bit r/g/b down to
// ~2.4-bit 6x6x6 ANSI cube + greyscale (assumed on entry; I know no way 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 // even semi-portably recover the palette) proceeds via: map each 8-bit to
// a 5-bit target grey. if all 3 components match, select that grey. // a 5-bit target grey. if all 3 components match, select that grey.
// otherwise, c / 42.7 to map to 6 values. this never generates pure black // otherwise, c / 42.7 to map to 6 values. this never generates pure black
// nor white, though, lame...FIXME // nor white, though, lame...FIXME
static inline int static inline int
rgb_to_ansi256(unsigned r, unsigned g, unsigned b){ rgb_quantize_256(unsigned r, unsigned g, unsigned b){
const unsigned GREYMASK = 0xf8; const unsigned GREYMASK = 0xf8;
r &= GREYMASK; // if all 5 MSBs match, return grey from 24-member grey ramp or pure
g &= GREYMASK; // black/white from original 16 (0 and 15, respectively)
b &= GREYMASK; if((r & GREYMASK) == (g & GREYMASK) && (g & GREYMASK) == (b & GREYMASK)){
if(r == g && g == b){ // 5 MSBs match, return grey // 256 / 26 == 9.846
r >>= 3u; int gidx = r * 5 / 49 - 1;
r += 232; if(gidx < 0){
return r > 255 ? 255: r; return 0;
}
if(gidx >= 24){
return 15;
}
return 232 + gidx;
} }
r /= 43; r /= 43;
g /= 43; g /= 43;
@ -190,6 +197,31 @@ rgb_to_ansi256(unsigned r, unsigned g, unsigned b){
return r * 36 + g * 6 + b + 16; return r * 36 + g * 6 + b + 16;
} }
static inline int
term_emit(const char* name __attribute__ ((unused)), const char* seq,
FILE* out, bool flush){
int ret = fprintf(out, "%s", seq);
if(ret < 0){
// fprintf(stderr, "Error emitting %zub %s escape (%s)\n", strlen(seq), name, strerror(errno));
return -1;
}
if((size_t)ret != strlen(seq)){
// fprintf(stderr, "Short write (%db) for %zub %s sequence\n", ret, strlen(seq), name);
return -1;
}
if(flush && fflush(out)){
// fprintf(stderr, "Error flushing after %db %s sequence (%s)\n", ret, name, strerror(errno));
return -1;
}
return 0;
}
static inline const char*
extended_gcluster(const ncplane* n, const cell* c){
uint32_t idx = cell_egc_idx(c);
return n->pool.pool + idx;
}
#define NANOSECS_IN_SEC 1000000000 #define NANOSECS_IN_SEC 1000000000
#ifdef __cplusplus #ifdef __cplusplus

File diff suppressed because it is too large Load Diff

532
src/lib/render.c Normal file
View File

@ -0,0 +1,532 @@
#include <ctype.h>
#include <unistd.h>
#include <sys/poll.h>
#include "internal.h"
static inline uint64_t
timespec_to_ns(const struct timespec* t){
return t->tv_sec * NANOSECS_IN_SEC + t->tv_nsec;
}
static void
mutex_unlock(void* vlock){
pthread_mutex_unlock(vlock);
}
static int
blocking_write(int fd, const char* buf, size_t buflen){
size_t written = 0;
do{
ssize_t w = write(fd, buf + written, buflen - written);
if(w < 0){
if(errno != EAGAIN && errno != EWOULDBLOCK){
return -1;
}
}else{
written += w;
}
if(written < buflen){
struct pollfd pfd = {
.fd = fd,
.events = POLLOUT,
.revents = 0,
};
poll(&pfd, 1, -1);
}
}while(written < buflen);
return 0;
}
int notcurses_refresh(notcurses* nc){
int ret = 0;
pthread_mutex_lock(&nc->lock);
pthread_cleanup_push(mutex_unlock, &nc->lock);
if(nc->mstream == NULL){
ret = -1; // haven't rendered yet, and thus don't know what should be there
}else if(blocking_write(nc->ttyfd, nc->mstream, nc->mstrsize)){
ret = -1;
}
pthread_cleanup_pop(1);
return ret;
}
static void
update_render_stats(const struct timespec* time1, const struct timespec* time0,
ncstats* stats, int bytes){
int64_t elapsed = timespec_to_ns(time1) - timespec_to_ns(time0);
//fprintf(stderr, "Rendering took %ld.%03lds\n", elapsed / NANOSECS_IN_SEC,
// (elapsed % NANOSECS_IN_SEC) / 1000000);
if(bytes >= 0){
stats->render_bytes += bytes;
if(bytes > stats->render_max_bytes){
stats->render_max_bytes = bytes;
}
if(bytes < stats->render_min_bytes){
stats->render_min_bytes = bytes;
}
}else{
++stats->failed_renders;
}
if(elapsed > 0){ // don't count clearly incorrect information, egads
++stats->renders;
stats->render_ns += elapsed;
if(elapsed > stats->render_max_ns){
stats->render_max_ns = elapsed;
}
if(elapsed < stats->render_min_ns){
stats->render_min_ns = elapsed;
}
}
}
// determine the best palette for the current frame, and write the necessary
// escape sequences to 'out'. for now, we just assume the ANSI palettes. at
// 256 colors, this is the 16 normal ones, 6x6x6 color cubes, and 32 greys.
// it's probably better to sample the darker regions rather than cover so much
// chroma, but whatever....FIXME
static int
prep_optimized_palette(notcurses* nc, FILE* out __attribute__ ((unused))){
if(nc->RGBflag){
return 0; // DirectColor, no need to write palette
}
if(!nc->CCCflag){
return 0; // can't change palette
}
// FIXME
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(fb == NULL){
return -1;
}
free(nc->shadowbuf);
nc->shadowbuf = fb;
nc->shadowy = nc->stdscr->leny;
nc->shadowx = nc->stdscr->lenx;
memset(fb, 0, size);
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
// transparency in either the back- or foreground. If the alpha channel is
// active, keep descending and blending until we hit opacity, or bedrock. We
// recurse to find opacity, and blend the result into what we have. The
// 'findfore' and 'findback' bools control our recursion--there's no point in
// going further down when a color is locked in, so don't (for instance) recurse
// further when we have a transparent foreground and opaque background atop an
// opaque foreground and transparent background. The cell we ultimately return
// (a const ref to 'c') is backed by '*retp' via rawdog copy; the caller must
// not call cell_release() upon it, nor use it beyond the scope of the render.
//
// So, as we go down, we find planes which can have impact on the result. Once
// we've locked the result in (base case), write the deep values we have to 'c'.
// Then, as we come back up, blend them as appropriate. The actual glyph is
// 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){
while(p){
// where in the plane this coordinate would be, based off absy/absx. the
// true origin is 0,0, so abs=2,2 means coordinate 3,3 would be 1,1, while
// abs=-2,-2 would make coordinate 3,3 relative 5, 5.
int poffx, poffy;
poffy = y - p->absy;
poffx = x - p->absx;
if(poffy < p->leny && poffy >= 0){
if(poffx < p->lenx && poffx >= 0){ // p is valid for this y, x
const cell* vis = &p->fb[fbcellidx(p, poffy, poffx)];
// if we never loaded any content into the cell (or obliterated it by
// writing in a zero), use the plane's background cell.
if(vis->gcluster == 0){
vis = &p->defcell;
}
bool lockedglyph = false;
int nalpha;
if(falpha > 0 && (nalpha = cell_get_fg_alpha(vis)) < CELL_ALPHA_TRANS){
if(c->gcluster == 0){ // never write fully trans glyphs, never replace
if( (c->gcluster = vis->gcluster) ){ // index copy only
lockedglyph = true; // must return this ncplane for this glyph
c->attrword = vis->attrword;
cell_set_fchannel(c, cell_get_fchannel(vis)); // FIXME blend it in
falpha -= (CELL_ALPHA_TRANS - 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_TRANS){
cell_set_bchannel(c, cell_get_bchannel(vis)); // FIXME blend it in
balpha -= (CELL_ALPHA_TRANS - nalpha);
if(p->damage[poffy]){
*damage = true;
p->damage[poffy] = false;
}
}
if((falpha > 0 || balpha > 0) && p->z){ // we must go further!
ncplane* cand = dig_visible_cell(c, y, x, p->z, falpha, balpha, damage);
if(!lockedglyph && cand){
p = cand;
}
}
return p;
}
}
p = p->z;
}
// should never happen for valid y, x thanks to the stdplane. you fucked up!
return NULL;
}
static inline ncplane*
visible_cell(cell* c, int y, int x, ncplane* n, bool* damage){
cell_init(c);
return dig_visible_cell(c, y, x, n, CELL_ALPHA_TRANS, CELL_ALPHA_TRANS, damage);
}
// write the cell's UTF-8 grapheme cluster to the provided FILE*. returns the
// number of columns occupied by this EGC (only an approximation; it's actually
// a property of the font being used).
static int
term_putc(FILE* out, const ncplane* n, const cell* c){
if(cell_simple_p(c)){
if(c->gcluster == 0 || iscntrl(c->gcluster)){
// fprintf(stderr, "[ ]\n");
if(fputc_unlocked(' ', out) == EOF){
return -1;
}
}else{
// fprintf(stderr, "[%c]\n", c->gcluster);
if(fputc_unlocked(c->gcluster, out) == EOF){
return -1;
}
}
}else{
const char* ext = extended_gcluster(n, c);
// fprintf(stderr, "[%s]\n", ext);
if(fputs_unlocked(ext, out) < 0){ // FIXME check for short write?
return -1;
}
}
return 0;
}
// check the current and target style bitmasks against the specified 'stylebit'.
// if they are different, and we have the necessary capability, write the
// applicable terminfo entry to 'out'. returns -1 only on a true error.
static int
term_setstyle(FILE* out, unsigned cur, unsigned targ, unsigned stylebit,
const char* ton, const char* toff){
int ret = 0;
unsigned curon = cur & stylebit;
unsigned targon = targ & stylebit;
if(curon != targon){
if(targon){
if(ton){
ret = term_emit("ton", ton, out, false);
}
}else{
if(toff){ // how did this happen? we can turn it on, but not off?
ret = term_emit("toff", toff, out, false);
}
}
}
if(ret < 0){
return -1;
}
return 0;
}
// write any escape sequences necessary to set the desired style
static int
term_setstyles(const notcurses* nc, FILE* out, uint32_t* curattr, const cell* c,
bool* normalized){
*normalized = false;
uint32_t cellattr = cell_styles(c);
if(cellattr == *curattr){
return 0; // happy agreement, change nothing
}
int ret = 0;
// if only italics changed, don't emit any sgr escapes. xor of current and
// target ought have all 0s in the lower 8 bits if only italics changed.
if((cellattr ^ *curattr) & 0x00ff0000ul){
*normalized = true; // FIXME this is pretty conservative
// if everything's 0, emit the shorter sgr0
if(nc->sgr0 && ((cellattr & CELL_STYLE_MASK) == 0)){
if(term_emit("sgr0", nc->sgr0, out, false) < 0){
ret = -1;
}
}else if(term_emit("sgr", tiparm(nc->sgr, cellattr & CELL_STYLE_STANDOUT,
cellattr & CELL_STYLE_UNDERLINE,
cellattr & CELL_STYLE_REVERSE,
cellattr & CELL_STYLE_BLINK,
cellattr & CELL_STYLE_DIM,
cellattr & CELL_STYLE_BOLD,
cellattr & CELL_STYLE_INVIS,
cellattr & CELL_STYLE_PROTECT, 0),
out, false) < 0){
ret = -1;
}
}
// sgr will blow away italics if they were set beforehand
ret |= term_setstyle(out, *curattr, cellattr, CELL_STYLE_ITALIC, nc->italics, nc->italoff);
*curattr = cellattr;
return ret;
}
// 3 for foreground, 4 for background, ugh FIXME
static int
term_esc_rgb(notcurses* nc __attribute__ ((unused)), FILE* out, int esc,
unsigned r, unsigned g, unsigned b){
// The correct way to do this is using tiparm+tputs, but doing so (at least
// as of terminfo 6.1.20191019) both emits ~3% more bytes for a run of 'rgb'
// and gives rise to some corrupted cells (possibly due to special handling of
// values < 256; I'm not at this time sure). So we just cons up our own.
/*if(esc == 4){
return term_emit("setab", tiparm(nc->setab, (int)((r << 16u) | (g << 8u) | b)), out, false);
}else if(esc == 3){
return term_emit("setaf", tiparm(nc->setaf, (int)((r << 16u) | (g << 8u) | b)), out, false);
}else{
return -1;
}*/
#define RGBESC1 "\x1b" "["
#define RGBESC2 "8;2;"
// rrr;ggg;bbbm
char rgbesc[] = RGBESC1 " " RGBESC2 " ";
int len = strlen(RGBESC1);
rgbesc[len++] = esc;
len += strlen(RGBESC2);
if(r > 99){
rgbesc[len++] = r / 100 + '0';
}
if(r > 9){
rgbesc[len++] = (r % 100) / 10 + '0';
}
rgbesc[len++] = (r % 10) + '0';
rgbesc[len++] = ';';
if(g > 99){ rgbesc[len++] = g / 100 + '0'; }
if(g > 9){ rgbesc[len++] = (g % 100) / 10 + '0'; }
rgbesc[len++] = g % 10 + '0';
rgbesc[len++] = ';';
if(b > 99){ rgbesc[len++] = b / 100 + '0'; }
if(b > 9){ rgbesc[len++] = (b % 100) / 10 + '0'; }
rgbesc[len++] = b % 10 + '0';
rgbesc[len++] = 'm';
rgbesc[len] = '\0';
int w;
if((w = fputs_unlocked(rgbesc, out)) < len){
return -1;
}
return 0;
}
static int
term_bg_rgb8(notcurses* nc, FILE* out, unsigned r, unsigned g, unsigned b){
// We typically want to use tputs() and tiperm() to acquire and write the
// escapes, as these take into account terminal-specific delays, padding,
// etc. For the case of DirectColor, there is no suitable terminfo entry, but
// we're also in that case working with hopefully more robust terminals.
// If it doesn't work, eh, it doesn't work. Fuck the world; save yourself.
if(nc->RGBflag){
return term_esc_rgb(nc, out, '4', r, g, b);
}else{
if(nc->setab == NULL){
return -1;
}
// For 256-color indexed mode, start constructing a palette based off
// the inputs *if we can change the palette*. If more than 256 are used on
// a single screen, start... combining close ones? For 8-color mode, simple
// interpolation. I have no idea what to do for 88 colors. FIXME
if(nc->colors >= 256){
term_emit("setab", tiparm(nc->setab, rgb_quantize_256(r, g, b)), out, false);
}
return -1;
}
return 0;
}
static int
term_fg_rgb8(notcurses* nc, FILE* out, unsigned r, unsigned g, unsigned b){
// We typically want to use tputs() and tiperm() to acquire and write the
// escapes, as these take into account terminal-specific delays, padding,
// etc. For the case of DirectColor, there is no suitable terminfo entry, but
// we're also in that case working with hopefully more robust terminals.
// If it doesn't work, eh, it doesn't work. Fuck the world; save yourself.
if(nc->RGBflag){
return term_esc_rgb(nc, out, '3', r, g, b);
}else{
if(nc->setaf == NULL){
return -1;
}
if(nc->colors >= 256){
term_emit("setaf", tiparm(nc->setaf, rgb_quantize_256(r, g, b)), out, false);
}
// For 256-color indexed mode, start constructing a palette based off
// the inputs *if we can change the palette*. If more than 256 are used on
// a single screen, start... combining close ones? For 8-color mode, simple
// interpolation. I have no idea what to do for 88 colors. FIXME
return -1;
}
return 0;
}
static inline int
notcurses_render_internal(notcurses* nc){
int ret = 0;
int y, x;
FILE* out = nc->mstreamfp;
fseeko(out, 0, SEEK_SET);
// don't write a clearscreen. we only update things that have been changed.
// we explicitly move the cursor at the beginning of each output line, so no
// need to home it expliticly.
prep_optimized_palette(nc, out); // FIXME do what on failure?
uint32_t curattr = 0; // current attributes set (does not include colors)
// FIXME as of at least gcc 9.2.1, we get a false -Wmaybe-uninitialized below
// when using these without explicit initializations. for the life of me, i
// can't see any such path, and valgrind is cool with it, so what ya gonna do?
unsigned lastr = 0, lastg = 0, lastb = 0;
unsigned lastbr = 0, lastbg = 0, lastbb = 0;
// we can elide a color escape iff the color has not changed between the two
// 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);
}*/
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
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);
// 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.
if((x + 1 >= nc->stdscr->lenx && cell_double_wide_p(&c))){
continue;
}
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{
continue;
}
}
// set the style. this can change the color back to the default; if it
// does, we need update our elision possibilities.
bool normalized;
term_setstyles(nc, out, &curattr, &c, &normalized);
if(normalized){
defaultelidable = true;
bgelidable = false;
fgelidable = false;
}
// we allow these to be set distinctly, but terminfo only supports using
// them both via the 'op' capability. unless we want to generate the 'op'
// escapes ourselves, if either is set to default, we first send op, and
// then a turnon for whichever aren't default.
// we can elide the default set iff the previous used both defaults
if(cell_fg_default_p(&c) || cell_bg_default_p(&c)){
if(!defaultelidable){
++nc->stats.defaultemissions;
term_emit("op", nc->op, out, false);
}else{
++nc->stats.defaultelisions;
}
// if either is not default, this will get turned off
defaultelidable = true;
fgelidable = false;
bgelidable = false;
}
// we can elide the foreground set iff the previous used fg and matched
if(!cell_fg_default_p(&c)){
cell_get_fg_rgb(&c, &r, &g, &b);
if(fgelidable && lastr == r && lastg == g && lastb == b){
++nc->stats.fgelisions;
}else{
term_fg_rgb8(nc, out, r, g, b);
++nc->stats.fgemissions;
fgelidable = true;
}
lastr = r; lastg = g; lastb = b;
defaultelidable = false;
}
if(!cell_bg_default_p(&c)){
cell_get_bg_rgb(&c, &br, &bg, &bb);
if(bgelidable && lastbr == br && lastbg == bg && lastbb == bb){
++nc->stats.bgelisions;
}else{
term_bg_rgb8(nc, out, br, bg, bb);
++nc->stats.bgemissions;
bgelidable = true;
}
lastbr = br; lastbg = bg; lastbb = bb;
defaultelidable = false;
}
// fprintf(stderr, "[%02d/%02d] 0x%02x 0x%02x 0x%02x %p\n", y, x, r, g, b, p);
term_putc(out, p, &c);
if(cell_double_wide_p(&c)){
++x;
}
}
if(linedamaged == false){
nc->stats.cellelisions += x;
}
}
ret |= fflush(out);
fflush(nc->ttyfp);
if(blocking_write(nc->ttyfd, nc->mstream, nc->mstrsize)){
ret = -1;
}
/*fprintf(stderr, "%lu/%lu %lu/%lu %lu/%lu\n", defaultelisions, defaultemissions,
fgelisions, fgemissions, bgelisions, bgemissions);*/
if(nc->renderfp){
fprintf(nc->renderfp, "%s\n", nc->mstream);
}
return nc->mstrsize;
}
int notcurses_render(notcurses* nc){
int ret = 0;
struct timespec start, done;
clock_gettime(CLOCK_MONOTONIC_RAW, &start);
pthread_mutex_lock(&nc->lock);
pthread_cleanup_push(mutex_unlock, &nc->lock);
int bytes = notcurses_render_internal(nc);
int dimy, dimx;
notcurses_resize(nc, &dimy, &dimx);
clock_gettime(CLOCK_MONOTONIC_RAW, &done);
update_render_stats(&done, &start, &nc->stats, bytes);
if(bytes < 0){
ret = -1;
}
pthread_cleanup_pop(1);
return ret;
}

135
src/poc/sprite-enhalfen.c Normal file
View File

@ -0,0 +1,135 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static const char* luigis[] = {
"0000000000000000"
"0000000000000000"
"0000000111110000"
"0000011111120000"
"0000111111220000"
"0000111111111110"
"0000333223222000"
"0003223223322220"
"0003223322222222"
"0033223322232222"
"0033222223333330"
"0003322222333330"
"0000032222222200"
"0000311122200000"
"0003133313000000"
"0003133331300000"
"0033133333112200"
"0031133333332222"
"0031113333332222"
"0001113333333222"
"0001111333333222"
"0001111113331000"
"0001111111111000"
"0001111111113000"
"3333111111131100"
"3333111113311100"
"3333111131111000"
"3333111001111000"
"3333000003333000"
"3300000003333000"
"3000000003333330"
"0000000003333330",
"0000000000000000"
"0000001111100000"
"0000111111200000"
"0001111112200000"
"0001111111111100"
"0003332232220000"
"0032232233222200"
"0032233222222220"
"0332233222322220"
"0332222233333300"
"0033222223333300"
"0003322222222000"
"0000111122000000"
"0003133113300000"
"0031333311300000"
"0031333311330000"
"0031333311130000"
"0031333332230000"
"0031333322220000"
"0011133322221000"
"0011133322221100"
"0011113322211100"
"0011111133111100"
"0001111133311000"
"0000111333333000"
"0000113333330000"
"0000011333300000"
"0000031113330000"
"0000033330330000"
"0000333330000000"
"0000333333300000"
"0000003333300000",
"0000001111100000"
"0000111111200000"
"0001111112200000"
"0001111111111100"
"0003332232220000"
"0032232233222200"
"0032233222222220"
"0332233222322220"
"0332222233333300"
"0333222223333300"
"0003322222222000"
"0000033322000000"
"0000111133100020"
"0003333113310222"
"0033333311313222"
"0333333311331222"
"0333333311331323"
"0333333111331330"
"3333331112132300"
"3333111111111000"
"2222211111111000"
"2222211111111003"
"2222111111111033"
"0222111111133333"
"0001311111133333"
"0031131111133333"
"3331113311133333"
"3333111100033333"
"3333310000000000"
"0333000000000000"
"0333000000000000"
"0033300000000000",
NULL
};
static int
enhalfen(const char* s, int width){
if(strlen(s) % (width * 2)){
fprintf(stderr, "Bad length: %zu\n", strlen(s));
return -1;
}
size_t rowoff = 0;
size_t idx;
for(rowoff = 0 ; s[rowoff] ; rowoff += 2 * width){
for(idx = 0 ; idx < width ; ++idx){
int c = (s[rowoff + idx] - '0') * 4 + (s[rowoff + width + idx] - '0');
printf("%x", c);
}
printf("\n");
}
return 0;
}
int main(void){
const char** sprite;
for(sprite = luigis ; *sprite ; ++sprite){
if(enhalfen(*sprite, 16)){
return EXIT_FAILURE;
}
printf("\n");
}
return EXIT_SUCCESS;
}

66
src/poc/unidamage.cpp Normal file
View File

@ -0,0 +1,66 @@
#include <cstdio>
#include <cstdlib>
#include <clocale>
#include <cassert>
#include <notcurses.h>
// What happens when we print over half of a wide glyph?
int main(int argc, char** argv){
setlocale(LC_ALL, "");
notcurses_options opts{};
opts.inhibit_alternate_screen = true;
struct notcurses* nc;
if((nc = notcurses_init(&opts, stdout)) == nullptr){
return EXIT_FAILURE;
}
struct ncplane* n = notcurses_stdplane(nc);
int dimx, dimy;
ncplane_dim_yx(n, &dimy, &dimx);
cell c = CELL_TRIVIAL_INITIALIZER;
cell_set_bg_rgb(&c, 0, 0x80, 0);
//ncplane_set_default(n, &c);
if(cell_load(n, &c, "🐳") < 0){
goto err;
}
if(dimy > 5){
dimy = 5;
}
for(int i = 0 ; i < dimy ; ++i){
for(int j = 8 ; j < dimx / 2 ; ++j){ // leave some empty spaces
if(ncplane_putc_yx(n, i, j * 2, &c) < 0){
goto err;
}
}
}
ncplane_putc_yx(n, dimy, dimx - 3, &c);
ncplane_putc_yx(n, dimy, dimx - 1, &c);
ncplane_putc_yx(n, dimy + 1, dimx - 2, &c);
ncplane_putc_yx(n, dimy + 1, dimx - 4, &c);
cell_release(n, &c);
// put these on the right side of the wide glyphs
for(int i = 0 ; i < dimy / 2 ; ++i){
for(int j = 5 ; j < dimx / 2 ; j += 2){
if(ncplane_putsimple_yx(n, i, j, (j % 10) + '0') < 0){
goto err;
}
}
}
// put these on the left side of the wide glyphs
for(int i = dimy / 2 ; i < dimy ; ++i){
for(int j = 4 ; j < dimx / 2 ; j += 2){
if(ncplane_putsimple_yx(n, i, j, (j % 10) + '0') < 0){
goto err;
}
}
}
if(notcurses_render(nc)){
goto err;
}
printf("\n");
return notcurses_stop(nc) ? EXIT_FAILURE : EXIT_SUCCESS;
err:
notcurses_stop(nc);
return EXIT_FAILURE;
}

Binary file not shown.

BIN
tests/fm6.mkv Normal file

Binary file not shown.

View File

@ -8,17 +8,74 @@ class InternalsTest : public :: testing::Test {
void SetUp() override { void SetUp() override {
setlocale(LC_ALL, ""); setlocale(LC_ALL, "");
} }
}; };
TEST_F(InternalsTest, RGBtoANSIWhite) { TEST_F(InternalsTest, RGBtoANSIWhite) {
unsigned r, g, b; unsigned r, g, b;
r = g = b = 0xff; for(r = 250 ; r < 256 ; ++r){
EXPECT_EQ(255, rgb_to_ansi256(r, g, b)); g = b = r;
EXPECT_EQ(15, rgb_quantize_256(r, g, b));
}
} }
TEST_F(InternalsTest, RGBtoANSIBlack) { TEST_F(InternalsTest, RGBtoANSIBlack) {
unsigned r, g, b; unsigned r, g, b;
r = g = b = 0x0; for(r = 0 ; r < 10 ; ++r){
EXPECT_EQ(232, rgb_to_ansi256(r, g, b)); g = b = r;
EXPECT_EQ(0, rgb_quantize_256(r, g, b));
}
}
TEST_F(InternalsTest, RGBtoANSIGrey) {
unsigned r, g, b;
for(r = 10 ; r < 244 ; ++r){
g = b = r;
EXPECT_EQ(231 + (r * 5) / 49, rgb_quantize_256(r, g, b));
}
}
// Pure reds are either 0 (black), or 16 plus 36 * [0..5].
TEST_F(InternalsTest, RGBtoANSIRed) {
unsigned r, g, b;
g = b = 0x0;
for(r = 0 ; r < 256 ; ++r){
int c256 = rgb_quantize_256(r, g, b);
if(r < 8){
EXPECT_EQ(0, c256);
}else{
EXPECT_LT(15, c256);
EXPECT_EQ(16, c256 % 36);
}
}
}
// Pure greens are either 0 (black), or 16 plus 6 * [0..5].
TEST_F(InternalsTest, RGBtoANSIGreen) {
unsigned r, g, b;
r = b = 0x0;
for(g = 0 ; g < 256 ; ++g){
int c256 = rgb_quantize_256(r, g, b);
EXPECT_GT(48, c256);
if(g < 8){
EXPECT_EQ(0, c256);
}else{
EXPECT_LT(15, c256);
EXPECT_EQ(4, c256 % 6);
}
}
}
// Pure blues are either 0 (black), or one of the first 6 colors [16..22].
TEST_F(InternalsTest, RGBtoANSIBlue) {
unsigned r, g, b;
r = g = 0x0;
for(b = 0 ; b < 256 ; ++b){
int c256 = rgb_quantize_256(r, g, b);
EXPECT_GT(22, c256);
if(b < 8){
EXPECT_EQ(0, c256);
}else{
EXPECT_LT(15, c256);
}
}
} }

View File

@ -57,7 +57,7 @@ TEST_F(LibavTest, LoadVideo) {
int averr; int averr;
int dimy, dimx; int dimy, dimx;
ncplane_dim_yx(ncp_, &dimy, &dimx); ncplane_dim_yx(ncp_, &dimy, &dimx);
auto ncv = ncplane_visual_open(ncp_, "../tests/bob.mkv", &averr); auto ncv = ncplane_visual_open(ncp_, "../tests/fm6.mkv", &averr);
ASSERT_NE(nullptr, ncv); ASSERT_NE(nullptr, ncv);
EXPECT_EQ(0, averr); EXPECT_EQ(0, averr);
auto frame = ncvisual_decode(ncv, &averr); auto frame = ncvisual_decode(ncv, &averr);
@ -74,7 +74,7 @@ TEST_F(LibavTest, LoadVideoCreatePlane) {
int averr; int averr;
int dimy, dimx; int dimy, dimx;
ncplane_dim_yx(ncp_, &dimy, &dimx); ncplane_dim_yx(ncp_, &dimy, &dimx);
auto ncv = ncvisual_open_plane(nc_, "../tests/bob.mkv", &averr, 0, 0, false); auto ncv = ncvisual_open_plane(nc_, "../tests/fm6.mkv", &averr, 0, 0, false);
ASSERT_NE(nullptr, ncv); ASSERT_NE(nullptr, ncv);
EXPECT_EQ(0, averr); EXPECT_EQ(0, averr);
auto frame = ncvisual_decode(ncv, &averr); auto frame = ncvisual_decode(ncv, &averr);

View File

@ -762,3 +762,116 @@ TEST_F(NcplaneTest, MoveToLowerRight) {
EXPECT_EQ(0, ncplane_destroy(ncp)); EXPECT_EQ(0, ncplane_destroy(ncp));
// FIXME verify with ncplane_at_cursor() // FIXME verify with ncplane_at_cursor()
} }
// Placing a wide char to the immediate left of any other char ought obliterate
// that cell.
TEST_F(NcplaneTest, WideCharAnnihilatesRight) {
const wchar_t* w = L"🐸";
const wchar_t* wbashed = L"🦂";
const char bashed = 'X';
int sbytes = 0;
EXPECT_LT(0, ncplane_putwegc_yx(n_, 0, 1, wbashed, 0, 0, &sbytes));
EXPECT_LT(0, ncplane_putsimple_yx(n_, 1, 1, bashed));
int x, y;
ncplane_cursor_yx(n_, &y, &x);
EXPECT_EQ(1, y);
EXPECT_EQ(2, x);
EXPECT_LT(0, ncplane_putwegc_yx(n_, 0, 0, w, 0, 0, &sbytes));
EXPECT_LT(0, ncplane_putwegc_yx(n_, 1, 0, w, 0, 0, &sbytes));
cell c = CELL_TRIVIAL_INITIALIZER;
ncplane_at_yx(n_, 0, 0, &c);
const char* wres = extended_gcluster(n_, &c);
EXPECT_EQ(0, strcmp(wres, "🐸")); // should be frog
ncplane_at_yx(n_, 0, 1, &c);
EXPECT_TRUE(cell_double_wide_p(&c)); // should be wide
ncplane_at_yx(n_, 0, 2, &c);
EXPECT_EQ(0, c.gcluster); // should be nothing
ncplane_at_yx(n_, 1, 0, &c);
wres = extended_gcluster(n_, &c);
EXPECT_EQ(0, strcmp(wres, "🐸")); // should be frog
ncplane_at_yx(n_, 1, 1, &c);
EXPECT_TRUE(cell_double_wide_p(&c)); //should be wide
ncplane_at_yx(n_, 0, 2, &c);
EXPECT_EQ(0, c.gcluster);
EXPECT_EQ(0, notcurses_render(nc_)); // should be nothing
}
// Placing a wide char on the right half of a wide char ought obliterate the
// original wide char.
TEST_F(NcplaneTest, WideCharAnnihilatesWideLeft) {
const wchar_t* w = L"🐍";
const wchar_t* wbashed = L"🦂";
int sbytes = 0;
EXPECT_LT(0, ncplane_putwegc_yx(n_, 0, 0, wbashed, 0, 0, &sbytes));
EXPECT_LT(0, ncplane_putwegc_yx(n_, 0, 1, w, 0, 0, &sbytes));
int x, y;
ncplane_cursor_yx(n_, &y, &x);
EXPECT_EQ(0, y);
EXPECT_EQ(3, x);
cell c = CELL_TRIVIAL_INITIALIZER;
ncplane_at_yx(n_, 0, 0, &c);
EXPECT_EQ(0, c.gcluster); // should be nothing
ncplane_at_yx(n_, 0, 1, &c);
const char* wres = extended_gcluster(n_, &c);
EXPECT_EQ(0, strcmp(wres, "🐍")); // should be snake
ncplane_at_yx(n_, 0, 2, &c);
EXPECT_TRUE(cell_double_wide_p(&c)); // should be wide
EXPECT_EQ(0, notcurses_render(nc_));
}
// Placing a normal char on either half of a wide char ought obliterate
// the original wide char.
TEST_F(NcplaneTest, WideCharsAnnihilated) {
const char cc = 'X';
const wchar_t* wbashedl = L"🐍";
const wchar_t* wbashedr = L"🦂";
int sbytes = 0;
EXPECT_LT(0, ncplane_putwegc_yx(n_, 0, 0, wbashedl, 0, 0, &sbytes));
EXPECT_LT(0, ncplane_putwegc_yx(n_, 0, 2, wbashedr, 0, 0, &sbytes));
EXPECT_EQ(1, ncplane_putsimple_yx(n_, 0, 1, cc));
EXPECT_EQ(1, ncplane_putsimple_yx(n_, 0, 2, cc));
int x, y;
ncplane_cursor_yx(n_, &y, &x);
EXPECT_EQ(0, y);
EXPECT_EQ(3, x);
cell c = CELL_TRIVIAL_INITIALIZER;
ncplane_at_yx(n_, 0, 0, &c);
EXPECT_EQ(0, c.gcluster); // should be nothing
ncplane_at_yx(n_, 0, 1, &c);
EXPECT_EQ(cc, c.gcluster); // should be 'X'
ncplane_at_yx(n_, 0, 2, &c);
EXPECT_EQ(cc, c.gcluster); // should be 'X"
ncplane_at_yx(n_, 0, 3, &c);
EXPECT_EQ(0, c.gcluster); // should be nothing
EXPECT_EQ(0, notcurses_render(nc_));
}
// But placing something to the immediate right of any glyph, that is not a
// problem. Ensure it is so.
TEST_F(NcplaneTest, AdjacentCharsSafe) {
const char cc = 'X';
const wchar_t* wsafel = L"🐍";
const wchar_t* wsafer = L"🦂";
int sbytes = 0;
EXPECT_LT(0, ncplane_putwegc_yx(n_, 0, 0, wsafel, 0, 0, &sbytes));
EXPECT_LT(0, ncplane_putwegc_yx(n_, 0, 3, wsafer, 0, 0, &sbytes));
EXPECT_EQ(1, ncplane_putsimple_yx(n_, 0, 2, cc));
int x, y;
ncplane_cursor_yx(n_, &y, &x);
EXPECT_EQ(0, y);
EXPECT_EQ(3, x);
cell c = CELL_TRIVIAL_INITIALIZER;
ncplane_at_yx(n_, 0, 0, &c);
const char* wres = extended_gcluster(n_, &c);
EXPECT_EQ(0, strcmp(wres, "🐍")); // should be snake
ncplane_at_yx(n_, 0, 1, &c);
EXPECT_TRUE(cell_double_wide_p(&c)); // should be snake
ncplane_at_yx(n_, 0, 2, &c);
EXPECT_EQ(cc, c.gcluster); // should be 'X'
ncplane_at_yx(n_, 0, 3, &c);
wres = extended_gcluster(n_, &c);
EXPECT_EQ(0, strcmp(wres, "🦂")); // should be scorpion
ncplane_at_yx(n_, 0, 4, &c);
EXPECT_TRUE(cell_double_wide_p(&c)); // should be scorpion
EXPECT_EQ(0, notcurses_render(nc_));
}

136
tests/zaxis.cpp Normal file
View File

@ -0,0 +1,136 @@
#include "main.h"
#include <cstdlib>
#include <iostream>
class ZAxisTest : public :: testing::Test {
protected:
void SetUp() override {
setlocale(LC_ALL, "");
if(getenv("TERM") == nullptr){
GTEST_SKIP();
}
notcurses_options nopts{};
nopts.inhibit_alternate_screen = true;
outfp_ = fopen("/dev/tty", "wb");
ASSERT_NE(nullptr, outfp_);
nc_ = notcurses_init(&nopts, outfp_);
ASSERT_NE(nullptr, nc_);
n_ = notcurses_stdplane(nc_);
ASSERT_NE(nullptr, n_);
}
void TearDown() override {
if(nc_){
EXPECT_EQ(0, notcurses_stop(nc_));
}
if(outfp_){
fclose(outfp_);
}
}
struct notcurses* nc_{};
struct ncplane* n_{};
FILE* outfp_{};
};
TEST_F(ZAxisTest, StdPlaneOnly) {
struct ncplane* top = notcurses_top(nc_);
EXPECT_EQ(n_, top);
EXPECT_EQ(nullptr, ncplane_below(top));
}
// if you want to move the plane which is already top+bottom to either, go ahead
TEST_F(ZAxisTest, StdPlaneOnanism) {
EXPECT_EQ(0, ncplane_move_top(n_));
struct ncplane* top = notcurses_top(nc_);
EXPECT_EQ(n_, top);
EXPECT_EQ(nullptr, ncplane_below(top));
EXPECT_EQ(0, ncplane_move_bottom(n_));
EXPECT_EQ(nullptr, ncplane_below(n_));
}
// you can't place a plane above or below itself, stdplane or otherwise
TEST_F(ZAxisTest, NoMoveSelf) {
struct ncplane* np = notcurses_newplane(nc_, 2, 2, 0, 0, nullptr);
ASSERT_NE(nullptr, np);
EXPECT_NE(0, ncplane_move_below(n_, n_));
EXPECT_NE(0, ncplane_move_above(n_, n_));
EXPECT_NE(0, ncplane_move_below(np, np));
EXPECT_NE(0, ncplane_move_above(np, np));
}
// new planes ought be on the top
TEST_F(ZAxisTest, NewPlaneOnTop) {
struct ncplane* np = notcurses_newplane(nc_, 2, 2, 0, 0, nullptr);
ASSERT_NE(nullptr, np);
struct ncplane* top = notcurses_top(nc_);
EXPECT_EQ(np, top);
EXPECT_EQ(n_, ncplane_below(top));
EXPECT_EQ(nullptr, ncplane_below(n_));
}
// "move" top plane to top. everything ought remain the same.
TEST_F(ZAxisTest, TopToTop) {
struct ncplane* np = notcurses_newplane(nc_, 2, 2, 0, 0, nullptr);
ASSERT_NE(nullptr, np);
struct ncplane* top = notcurses_top(nc_);
EXPECT_EQ(np, top);
EXPECT_EQ(n_, ncplane_below(top));
EXPECT_EQ(nullptr, ncplane_below(n_));
EXPECT_EQ(0, ncplane_move_top(np));
// verify it
top = notcurses_top(nc_);
EXPECT_EQ(np, top);
EXPECT_EQ(n_, ncplane_below(top));
EXPECT_EQ(nullptr, ncplane_below(n_));
}
// move top plane to bottom, and verify enumeration
TEST_F(ZAxisTest, TopToBottom) {
struct ncplane* np = notcurses_newplane(nc_, 2, 2, 0, 0, nullptr);
ASSERT_NE(nullptr, np);
struct ncplane* top = notcurses_top(nc_);
EXPECT_EQ(np, top);
EXPECT_EQ(n_, ncplane_below(top));
EXPECT_EQ(nullptr, ncplane_below(n_));
EXPECT_EQ(0, ncplane_move_bottom(np));
top = notcurses_top(nc_);
EXPECT_EQ(n_, top);
EXPECT_EQ(np, ncplane_below(top));
EXPECT_EQ(nullptr, ncplane_below(np));
}
// verify that moving one above another, with no other changes, is reflected at
// render time (requires explicit damage maintenance from move functionality).
TEST_F(ZAxisTest, ZAxisDamage) {
cell cat = CELL_TRIVIAL_INITIALIZER;
cell c = CELL_SIMPLE_INITIALIZER('x');
ASSERT_EQ(0, cell_set_fg_rgb(&c, 0xff, 0, 0));
ASSERT_EQ(1, ncplane_putc(n_, &c));
EXPECT_EQ(0, notcurses_render(nc_));
ASSERT_EQ(0, ncplane_cursor_move_yx(n_, 0, 0));
ASSERT_EQ(1, ncplane_at_cursor(n_, &cat));
ASSERT_TRUE(cell_simple_p(&cat));
ASSERT_EQ('x', cat.gcluster);
struct ncplane* n2 = notcurses_newplane(nc_, 2, 2, 0, 0, nullptr);
ASSERT_EQ(1, cell_load(n2, &c, "y"));
ASSERT_EQ(0, cell_set_fg_rgb(&c, 0, 0xff, 0));
ASSERT_EQ(1, ncplane_putc(n2, &c));
EXPECT_EQ(0, notcurses_render(nc_));
ASSERT_EQ(0, ncplane_cursor_move_yx(n2, 0, 0));
ASSERT_EQ(1, ncplane_at_cursor(n2, &cat));
ASSERT_EQ('y', cat.gcluster);
struct ncplane* n3 = notcurses_newplane(nc_, 2, 2, 0, 0, nullptr);
ASSERT_EQ(1, cell_load(n3, &c, "z"));
ASSERT_EQ(0, cell_set_fg_rgb(&c, 0, 0, 0xff));
ASSERT_EQ(1, ncplane_putc(n3, &c));
EXPECT_EQ(0, notcurses_render(nc_));
ASSERT_EQ(0, ncplane_cursor_move_yx(n3, 0, 0));
ASSERT_EQ(1, ncplane_at_cursor(n3, &cat));
ASSERT_EQ('z', cat.gcluster);
// FIXME testing damage requires notcurses keeping a copy of the screen....
// FIXME move y atop z
// FIXME inspect
// FIXME move z atop y
// FIXME inspect
}