mirror of
https://github.com/dankamongmen/notcurses
synced 2025-03-09 17:19:03 -04:00
Merge branch 'master' of github.com:dankamongmen/notcurses
This commit is contained in:
commit
94722c0f28
@ -1,5 +1,5 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(notcurses VERSION 0.9.0
|
||||
project(notcurses VERSION 1.0.0
|
||||
DESCRIPTION "UI for modern terminal emulators"
|
||||
HOMEPAGE_URL "https://nick-black.com/dankwiki/index.php/notcurses"
|
||||
LANGUAGES C CXX)
|
||||
@ -79,7 +79,7 @@ target_compile_definitions(notcurses-demo
|
||||
)
|
||||
|
||||
# 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})
|
||||
get_filename_component(fe "${f}" NAME_WE)
|
||||
add_executable(${fe} ${f})
|
||||
@ -202,11 +202,11 @@ file(GLOB MANPAGES1 CONFIGURE_DEPENDS doc/man/man1/*)
|
||||
file(GLOB MANPAGES3 CONFIGURE_DEPENDS doc/man/man3/*)
|
||||
install(FILES
|
||||
${MANPAGES1}
|
||||
DESTINATION ${CMAKE_INSTALL_PREFIX}/man/man1
|
||||
DESTINATION ${CMAKE_INSTALL_PREFIX}/share/man/man1
|
||||
)
|
||||
install(FILES
|
||||
${MANPAGES3}
|
||||
DESTINATION ${CMAKE_INSTALL_PREFIX}/man/man3
|
||||
DESTINATION ${CMAKE_INSTALL_PREFIX}/share/man/man3
|
||||
)
|
||||
|
||||
install(TARGETS notcurses-demo DESTINATION bin)
|
||||
|
118
README.md
118
README.md
@ -14,8 +14,8 @@ by [nick black](https://nick-black.com/dankwiki/index.php/Hack_on) (<nickblack@l
|
||||
* [Requirements](#requirements)
|
||||
* [Use](#use)
|
||||
* [Input](#input)
|
||||
* [Planes](#planes)
|
||||
* [Cells](#cells)
|
||||
* [Planes](#planes) ([Plane Channels API](#plane-channels-api), [Wide chars](#wide-chars))
|
||||
* [Cells](#cells) ([Cell Channels API](#cell-channels-api))
|
||||
* [Multimedia](#multimedia)
|
||||
* [Panelreels](#panelreels)
|
||||
* [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)
|
||||
* [Adapting NCURSES programs](#adapting-ncurses-programs)
|
||||
* [Environment notes](#environment-notes)
|
||||
* [DirectColor detection](#DirectColor-detection)
|
||||
* [Fonts](#fonts)
|
||||
* [Supplemental material](#supplemental-material)
|
||||
* [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
|
||||
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
|
||||
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:
|
||||
|
||||
```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
|
||||
// if necessary. Without a call to this function following a terminal resize
|
||||
// (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
|
||||
// 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
|
||||
// attempt to resize the standard plane. If either of 'keepy' or 'keepx' is
|
||||
// non-zero, both must be non-zero.
|
||||
// attempt to resize the standard plane. If either of 'keepleny' or 'keeplenx'
|
||||
// is non-zero, both must be non-zero.
|
||||
//
|
||||
// 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:
|
||||
// it is possible to implement ncplane_move() in terms of ncplane_resize().
|
||||
// resized plane. If there is no kept material, the plane can move freely.
|
||||
int ncplane_resize(struct ncplane* n, int keepy, int keepx, int keepleny,
|
||||
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.
|
||||
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,
|
||||
@ -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.
|
||||
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.
|
||||
// ncplane_set_userptr() returns the previous userptr after replacing
|
||||
// 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){
|
||||
ret = ncplane_box(n, &ul, &ur, &ll, &lr, &hl, &vl, ystop, xstop, ctlword);
|
||||
}
|
||||
cell_release(n, &ul);
|
||||
cell_release(n, &ur);
|
||||
cell_release(n, &ll);
|
||||
cell_release(n, &lr);
|
||||
cell_release(n, &hl);
|
||||
cell_release(n, &vl);
|
||||
cell_release(n, &ul); cell_release(n, &ur);
|
||||
cell_release(n, &ll); cell_release(n, &lr);
|
||||
cell_release(n, &hl); cell_release(n, &vl);
|
||||
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){
|
||||
ret = ncplane_box(n, &ul, &ur, &ll, &lr, &hl, &vl, ystop, xstop, ctlword);
|
||||
}
|
||||
cell_release(n, &ul);
|
||||
cell_release(n, &ur);
|
||||
cell_release(n, &ll);
|
||||
cell_release(n, &lr);
|
||||
cell_release(n, &hl);
|
||||
cell_release(n, &vl);
|
||||
cell_release(n, &ul); cell_release(n, &ur);
|
||||
cell_release(n, &ll); cell_release(n, &lr);
|
||||
cell_release(n, &hl); cell_release(n, &vl);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -870,6 +873,32 @@ void ncplane_set_fg_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
|
||||
|
||||
Unlike the `notcurses` or `ncplane` objects, the definition of `cell` is
|
||||
@ -934,12 +963,14 @@ typedef struct cell {
|
||||
#define CELL_ALPHA_OPAQUE 0
|
||||
```
|
||||
|
||||
`cell`s must be initialized with `CELL_TRIVIAL_INITIALIZER` or `cell_init()`
|
||||
before any other use (both merely zero out the `cell`).
|
||||
`cell`s must be initialized with an initialization macro or `cell_init()`
|
||||
before any other use. `cell_init()` and `CELL_TRIVIAL_INITIALIZER` both
|
||||
simply zero out the `cell`.
|
||||
|
||||
```c
|
||||
#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
|
||||
cell_init(cell* c){
|
||||
@ -1038,6 +1069,17 @@ cell_simple_p(const cell* c){
|
||||
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
|
||||
// unsafe results if called on a simple cell.
|
||||
static inline uint32_t
|
||||
@ -1745,6 +1787,7 @@ acquired using the `notcurses_stats()` function. This function cannot fail.
|
||||
```c
|
||||
typedef struct ncstats {
|
||||
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_max_bytes; // max 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
|
||||
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
|
||||
applications, until I have that handled better. notcurses doesn't recognize
|
||||
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
|
||||
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 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
|
||||
|
||||
@ -1930,9 +1992,13 @@ Fonts end up being a whole thing.
|
||||
|
||||
* [tui-rs](https://github.com/fdehau/tui-rs) (Rust)
|
||||
* [blessed-contrib](https://github.com/yaronn/blessed-contrib) (Javascript)
|
||||
* [FINAL CUT](https://github.com/gansm/finalcut) (C++)
|
||||
|
||||
### 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),
|
||||
the first generally usable notcurses. I prepare a [demo](https://www.youtube.com/watch?v=eEv2YRyiEVM),
|
||||
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
|
||||
thankless work by Thomas E. Dickey on NCURSES. His FAQ is a model of
|
||||
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.
|
||||
* Justine Tunney, one of my first friends at Google NYC, was always present
|
||||
with support, and pointed out the useful memstream functionality of
|
||||
|
13
debian/changelog
vendored
13
debian/changelog
vendored
@ -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
|
||||
|
||||
|
1
debian/libnotcurses-dev.install
vendored
1
debian/libnotcurses-dev.install
vendored
@ -2,3 +2,4 @@ usr/include
|
||||
usr/lib/*/*.so
|
||||
usr/lib/*/pkgconfig/*.pc
|
||||
usr/lib/*/cmake/*
|
||||
usr/share/man/man3/*
|
||||
|
1
debian/notcurses-bin.install
vendored
1
debian/notcurses-bin.install
vendored
@ -1,2 +1,3 @@
|
||||
usr/bin/*
|
||||
usr/share/notcurses/*
|
||||
usr/man/man1/*
|
||||
|
@ -27,25 +27,25 @@ contains a set of text-based demonstrations of capabilities from the notcurses l
|
||||
.P
|
||||
(i)ntro—a setting of tone
|
||||
.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
|
||||
.P
|
||||
(l)uigi-a dashing plumber of Apennine persuasion
|
||||
.P
|
||||
(u)niblocks—a series of blocks detailing Unicode pages
|
||||
.P
|
||||
(b)oxes—pulsating boxes with a transparent center
|
||||
.P
|
||||
(g)rid—a gradient of color lain atop a great grid
|
||||
.P
|
||||
(w)idecolors—letters of many languages in many colors
|
||||
(s)liders—a missing-piece puzzle made up of colorful blocks
|
||||
.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)anelreels—demonstration of the panelreel high-level widget
|
||||
.P
|
||||
(o)utro—a message of hope from the library author
|
||||
(o)utro—a message of hope from the library's author
|
||||
.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.
|
||||
.SH SEE ALSO
|
||||
|
@ -133,6 +133,9 @@ API int notcurses_stop(struct notcurses* nc);
|
||||
// successful call to notcurses_render().
|
||||
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
|
||||
// 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
|
||||
@ -279,7 +282,8 @@ API unsigned notcurses_supported_styles(const struct notcurses* nc);
|
||||
API int notcurses_palette_size(const struct notcurses* nc);
|
||||
|
||||
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
|
||||
int64_t render_max_bytes; // max 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
|
||||
// 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
|
||||
// attempt to resize the standard plane. If either of 'keepy' or 'keepx' is
|
||||
// non-zero, both must be non-zero.
|
||||
// attempt to resize the standard plane. If either of 'keepleny' or 'keeplenx'
|
||||
// is non-zero, both must be non-zero.
|
||||
//
|
||||
// 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:
|
||||
// it is possible to implement ncplane_move() in terms of ncplane_resize().
|
||||
// resized plane. If there is no kept material, the plane can move freely.
|
||||
API int ncplane_resize(struct ncplane* n, int keepy, int keepx, int keepleny,
|
||||
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_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'.
|
||||
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
|
||||
// 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);
|
||||
|
||||
// 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.
|
||||
// ncplane_set_userptr() returns the previous userptr after replacing
|
||||
// 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
|
||||
|
||||
#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
|
||||
cell_init(cell* c){
|
||||
@ -1174,6 +1206,17 @@ cell_simple_p(const cell* c){
|
||||
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
|
||||
// unsafe results if called on a simple cell.
|
||||
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){
|
||||
ret = ncplane_box(n, &ul, &ur, &ll, &lr, &hl, &vl, ystop, xstop, ctlword);
|
||||
}
|
||||
cell_release(n, &ul);
|
||||
cell_release(n, &ur);
|
||||
cell_release(n, &ll);
|
||||
cell_release(n, &lr);
|
||||
cell_release(n, &hl);
|
||||
cell_release(n, &vl);
|
||||
cell_release(n, &ul); cell_release(n, &ur);
|
||||
cell_release(n, &ll); cell_release(n, &lr);
|
||||
cell_release(n, &hl); cell_release(n, &vl);
|
||||
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){
|
||||
ret = ncplane_box(n, &ul, &ur, &ll, &lr, &hl, &vl, ystop, xstop, ctlword);
|
||||
}
|
||||
cell_release(n, &ul);
|
||||
cell_release(n, &ur);
|
||||
cell_release(n, &ll);
|
||||
cell_release(n, &lr);
|
||||
cell_release(n, &hl);
|
||||
cell_release(n, &vl);
|
||||
cell_release(n, &ul); cell_release(n, &ur);
|
||||
cell_release(n, &ll); cell_release(n, &lr);
|
||||
cell_release(n, &hl); cell_release(n, &vl);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -49,28 +49,28 @@ int box_demo(struct notcurses* nc){
|
||||
if(ncplane_putstr_aligned(n, ytargbase++, "┗━━┻━━┛", NCALIGN_CENTER) < 0){
|
||||
return -1;
|
||||
}
|
||||
ncplane_set_fg_rgb(n, 255, 255, 255);
|
||||
ncplane_set_bg_rgb(n, 180, 40, 180);
|
||||
do{
|
||||
int y = 0, x = 0;
|
||||
ncplane_dim_yx(n, &ylen, &xlen);
|
||||
while(ylen - y >= targy && xlen - x >= targx){
|
||||
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_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_bg_rgb(&hl, 20, zbonus, 20);
|
||||
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_bg_rgb(&lr, zbonus, 20 + y, 20 + y);
|
||||
cell_set_fg_rgb(&vl, 20, zbonus, 20);
|
||||
cell_set_bg_rgb(&vl, 107 - (y * 2), zbonus, 107 + (y * 2));
|
||||
cell_set_bg_rgb(&lr, 20, zbonus, 20);
|
||||
cell_set_fg_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)){
|
||||
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;
|
||||
}
|
||||
ylen -= 2;
|
||||
|
@ -52,7 +52,7 @@ usage(const char* exe, int status){
|
||||
fprintf(out, " s: run shuffle\n");
|
||||
fprintf(out, " u: run uniblock\n");
|
||||
fprintf(out, " v: run view\n");
|
||||
fprintf(out, " w: run widecolors\n");
|
||||
fprintf(out, " w: run widechomper\n");
|
||||
exit(status);
|
||||
}
|
||||
|
||||
@ -63,8 +63,29 @@ intro(struct notcurses* nc){
|
||||
return -1;
|
||||
}
|
||||
ncplane_erase(ncp);
|
||||
if(ncplane_cursor_move_yx(ncp, 0, 0)){
|
||||
return -1;
|
||||
}
|
||||
int x, y, 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_init(&c);
|
||||
const char* cstr = "Δ";
|
||||
@ -135,7 +156,7 @@ ext_demos(struct notcurses* nc, const char* demos){
|
||||
case 'g': ret = grid_demo(nc); break;
|
||||
case 'l': ret = luigi_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;
|
||||
default:
|
||||
fprintf(stderr, "Unknown demo specification: %c\n", *demos);
|
||||
|
@ -12,7 +12,7 @@ extern "C" {
|
||||
extern struct timespec demodelay;
|
||||
|
||||
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 maxcolor_demo(struct notcurses* nc);
|
||||
int grid_demo(struct notcurses* nc);
|
||||
|
@ -102,6 +102,9 @@ gridswitch_demo(struct notcurses* nc, struct ncplane *n){
|
||||
// center
|
||||
for(y = 1 ; y < maxy - 1 ; ++y){
|
||||
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_bg_rgb(&cl, 255 - rs * x, 255 - gs * (x + y), 255 - bs * y);
|
||||
ncplane_putc(n, &cl);
|
||||
@ -117,6 +120,9 @@ gridswitch_demo(struct notcurses* nc, struct ncplane *n){
|
||||
|
||||
// bottom line
|
||||
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_bg_rgb(&ll, 255 - rs * x, 255 - gs * (x + y), 255 - bs * y);
|
||||
ncplane_putc(n, &ll);
|
||||
@ -167,6 +173,9 @@ gridinv_demo(struct notcurses* nc, struct ncplane *n){
|
||||
// center
|
||||
for(y = 1 ; y < maxy - 1 ; ++y){
|
||||
x = 0;
|
||||
if(ncplane_cursor_move_yx(n, y, x)){
|
||||
return -1;
|
||||
}
|
||||
cell_set_fg_rgb(&cl, 0, 0, 0);
|
||||
cell_set_bg_rgb(&cl, 255 - rs * x, 255 - gs * (x + y), 255 - bs * y);
|
||||
ncplane_putc(n, &cl);
|
||||
@ -182,6 +191,9 @@ gridinv_demo(struct notcurses* nc, struct ncplane *n){
|
||||
|
||||
// bottom line
|
||||
x = 0;
|
||||
if(ncplane_cursor_move_yx(n, y, x)){
|
||||
return -1;
|
||||
}
|
||||
cell_set_fg_rgb(&ll, 0, 0, 0);
|
||||
cell_set_bg_rgb(&ll, 255 - rs * x, 255 - gs * (x + y), 255 - bs * y);
|
||||
ncplane_putc(n, &ll);
|
||||
@ -233,6 +245,9 @@ int grid_demo(struct notcurses* nc){
|
||||
// center
|
||||
for(y = 1 ; y < maxy - 1 ; ++y){
|
||||
x = 0;
|
||||
if(ncplane_cursor_move_yx(n, y, x)){
|
||||
return -1;
|
||||
}
|
||||
cell_set_bg_rgb(&cl, y, y, y);
|
||||
cell_set_bg_rgb(&cc, y, y, y);
|
||||
cell_set_bg_rgb(&cr, y, y, y);
|
||||
@ -248,6 +263,9 @@ int grid_demo(struct notcurses* nc){
|
||||
|
||||
// bottom line
|
||||
x = 0;
|
||||
if(ncplane_cursor_move_yx(n, y, x)){
|
||||
return -1;
|
||||
}
|
||||
cell_set_bg_rgb(&ll, y, y, y);
|
||||
cell_set_bg_rgb(&lc, y, y, y);
|
||||
cell_set_bg_rgb(&lr, y, y, y);
|
||||
|
212
src/demo/luigi.c
212
src/demo/luigi.c
@ -5,104 +5,109 @@
|
||||
//2 = yellow
|
||||
//3 = green
|
||||
|
||||
static const char luigi1[] = "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";
|
||||
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",
|
||||
|
||||
static const char luigi2[] = "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";
|
||||
"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 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
|
||||
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
|
||||
struct ncplane* lns[3];
|
||||
int i;
|
||||
struct ncplane* lastseen = NULL;
|
||||
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){
|
||||
while(--i){
|
||||
ncplane_destroy(lns[i]);
|
||||
}
|
||||
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;
|
||||
ns_to_timespec(timespec_to_ns(&demodelay) / (cols - 16 - 1), &stepdelay);
|
||||
for(i = 0 ; i < cols - 16 - 1 ; ++i){
|
||||
if(lastseen){ // hide the previous sprite
|
||||
ncplane_move_yx(lastseen, yoff, -16);
|
||||
}
|
||||
ncplane_move_bottom(lastseen); // hide the previous sprite
|
||||
lastseen = lns[i % 3];
|
||||
ncplane_move_top(lastseen);
|
||||
ncplane_move_yx(lastseen, yoff, i);
|
||||
notcurses_render(nc);
|
||||
nanosleep(&stepdelay, NULL);
|
||||
|
@ -22,7 +22,7 @@ view_video_demo(struct notcurses* nc){
|
||||
ncplane_dim_yx(ncp, &dimy, &dimx);
|
||||
int averr;
|
||||
struct ncvisual* ncv;
|
||||
ncv = ncplane_visual_open(ncp, "../tests/bob.mkv", &averr);
|
||||
ncv = ncplane_visual_open(ncp, "../tests/fm6.mkv", &averr);
|
||||
if(!ncv){
|
||||
return -1;
|
||||
}
|
||||
|
@ -7,6 +7,9 @@
|
||||
#include <pthread.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
|
||||
// ∮ E⋅da = Q, n → ∞, ∑ f(i) = ∏ g(i), ⎧⎡⎛┌─────┐⎞⎤⎫
|
||||
// ⎪⎢⎜│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){
|
||||
cell c = CELL_TRIVIAL_INITIALIZER;
|
||||
cell_load(n, &c, " ");
|
||||
cell_set_fg_alpha(&c, CELL_ALPHA_TRANS);
|
||||
cell_set_bg_alpha(&c, CELL_ALPHA_TRANS);
|
||||
ncplane_set_default(n, &c);
|
||||
cell_release(n, &c);
|
||||
@ -297,7 +301,7 @@ message(struct ncplane* n, int maxy, int maxx, int num, int total,
|
||||
}
|
||||
// bottom handle
|
||||
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_putegc(n, "│", 0, channels, NULL);
|
||||
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
|
||||
int widecolor_demo(struct notcurses* nc){
|
||||
int widechomper_demo(struct notcurses* nc){
|
||||
static const char* strs[] = {
|
||||
"Война и мир",
|
||||
"Бра́тья Карама́зовы",
|
||||
@ -575,9 +579,8 @@ int widecolor_demo(struct notcurses* nc){
|
||||
NULL
|
||||
};
|
||||
const char** s;
|
||||
int count = notcurses_palette_size(nc);
|
||||
const int steps[] = { 1, 0x100, 0x40000, 0x10001, };
|
||||
const int starts[] = { 0x4000, 0x40, 0x10000, 0x400040, };
|
||||
const int steps[] = { 0x100, 0x100, 0x40000, 0x10001, };
|
||||
const int starts[] = { 0x004000, 0x000040, 0x010101, 0x400040, };
|
||||
|
||||
struct ncplane* n = notcurses_stdplane(nc);
|
||||
size_t i;
|
||||
@ -588,13 +591,10 @@ int widecolor_demo(struct notcurses* nc){
|
||||
cell c;
|
||||
struct timespec 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
|
||||
const int start = starts[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);
|
||||
int y, x, maxy, maxx;
|
||||
ncplane_dim_yx(n, &maxy, &maxx);
|
||||
@ -634,11 +634,18 @@ int widecolor_demo(struct notcurses* nc){
|
||||
}
|
||||
int ulen = 0;
|
||||
int r;
|
||||
if((r = ncplane_putegc(n, &(*s)[idx], 0, channels, &ulen)) < 0){
|
||||
if(ulen < 0){
|
||||
if(wcwidth(wcs) <= maxx - x){
|
||||
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;
|
||||
}
|
||||
break;
|
||||
cell_release(n, &octo);
|
||||
}
|
||||
ncplane_cursor_yx(n, &y, &x);
|
||||
idx += ulen;
|
||||
@ -646,19 +653,7 @@ int widecolor_demo(struct notcurses* nc){
|
||||
cols_out += r;
|
||||
++egcs_out;
|
||||
}
|
||||
if(++rollcount % rollover == 0){
|
||||
step *= 256;
|
||||
}
|
||||
if((unsigned)step >= 1ul << 24){
|
||||
step >>= 24u;
|
||||
}
|
||||
if(step == 0){
|
||||
step = 1;
|
||||
}
|
||||
if((rgb += step) >= count){
|
||||
rgb = 0;
|
||||
step *= 256;
|
||||
}
|
||||
rgb += step;
|
||||
}
|
||||
}while(y < maxy && x < maxx);
|
||||
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);
|
||||
ncplane_destroy(mess);
|
||||
if(key == NCKEY_RESIZE){
|
||||
notcurses_resize(nc, NULL, NULL);
|
||||
notcurses_resize(nc, &maxy, &maxx);
|
||||
}
|
||||
}while(key == NCKEY_RESIZE);
|
||||
}
|
@ -84,11 +84,15 @@ typedef struct notcurses {
|
||||
FILE* ttyfp; // FILE* for controlling tty, from opts->ttyfp
|
||||
FILE* ttyinfp; // FILE* for processing input
|
||||
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
|
||||
ncstats stats; // some statistics across the lifetime of the notcurses ctx
|
||||
// We verify that some terminfo capabilities exist. These needn't be checked
|
||||
// before further use; just use tiparm() directly.
|
||||
char* cup; // move cursor
|
||||
bool RGBflag; // terminfo-reported "RGB" flag for 24bpc directcolor
|
||||
char* civis; // hide cursor
|
||||
// These might be NULL, and we can more or less work without them. Check!
|
||||
char* clearscr; // erase screen and home cursor
|
||||
@ -112,9 +116,7 @@ typedef struct notcurses {
|
||||
char* italoff; // CELL_STYLE_ITALIC (disable)
|
||||
char* smkx; // enter keypad transmit mode (keypad_xmit)
|
||||
char* rmkx; // leave keypad transmit mode (keypad_local)
|
||||
|
||||
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
|
||||
ncplane* top; // the contents of our topmost plane (initially entire 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);
|
||||
|
||||
// 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
|
||||
// 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
|
||||
// nor white, though, lame...FIXME
|
||||
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;
|
||||
r &= GREYMASK;
|
||||
g &= GREYMASK;
|
||||
b &= GREYMASK;
|
||||
if(r == g && g == b){ // 5 MSBs match, return grey
|
||||
r >>= 3u;
|
||||
r += 232;
|
||||
return r > 255 ? 255: r;
|
||||
// if all 5 MSBs match, return grey from 24-member grey ramp or pure
|
||||
// black/white from original 16 (0 and 15, respectively)
|
||||
if((r & GREYMASK) == (g & GREYMASK) && (g & GREYMASK) == (b & GREYMASK)){
|
||||
// 256 / 26 == 9.846
|
||||
int gidx = r * 5 / 49 - 1;
|
||||
if(gidx < 0){
|
||||
return 0;
|
||||
}
|
||||
if(gidx >= 24){
|
||||
return 15;
|
||||
}
|
||||
return 232 + gidx;
|
||||
}
|
||||
r /= 43;
|
||||
g /= 43;
|
||||
@ -190,6 +197,31 @@ rgb_to_ansi256(unsigned r, unsigned g, unsigned b){
|
||||
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
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
File diff suppressed because it is too large
Load Diff
532
src/lib/render.c
Normal file
532
src/lib/render.c
Normal 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
135
src/poc/sprite-enhalfen.c
Normal 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
66
src/poc/unidamage.cpp
Normal 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;
|
||||
}
|
BIN
tests/bob.mkv
BIN
tests/bob.mkv
Binary file not shown.
BIN
tests/fm6.mkv
Normal file
BIN
tests/fm6.mkv
Normal file
Binary file not shown.
@ -8,17 +8,74 @@ class InternalsTest : public :: testing::Test {
|
||||
void SetUp() override {
|
||||
setlocale(LC_ALL, "");
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
TEST_F(InternalsTest, RGBtoANSIWhite) {
|
||||
unsigned r, g, b;
|
||||
r = g = b = 0xff;
|
||||
EXPECT_EQ(255, rgb_to_ansi256(r, g, b));
|
||||
for(r = 250 ; r < 256 ; ++r){
|
||||
g = b = r;
|
||||
EXPECT_EQ(15, rgb_quantize_256(r, g, b));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(InternalsTest, RGBtoANSIBlack) {
|
||||
unsigned r, g, b;
|
||||
r = g = b = 0x0;
|
||||
EXPECT_EQ(232, rgb_to_ansi256(r, g, b));
|
||||
for(r = 0 ; r < 10 ; ++r){
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ TEST_F(LibavTest, LoadVideo) {
|
||||
int averr;
|
||||
int 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);
|
||||
EXPECT_EQ(0, averr);
|
||||
auto frame = ncvisual_decode(ncv, &averr);
|
||||
@ -74,7 +74,7 @@ TEST_F(LibavTest, LoadVideoCreatePlane) {
|
||||
int averr;
|
||||
int 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);
|
||||
EXPECT_EQ(0, averr);
|
||||
auto frame = ncvisual_decode(ncv, &averr);
|
||||
|
@ -762,3 +762,116 @@ TEST_F(NcplaneTest, MoveToLowerRight) {
|
||||
EXPECT_EQ(0, ncplane_destroy(ncp));
|
||||
// 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
136
tests/zaxis.cpp
Normal 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
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user