From 6045c54189d89779ab1892f76c77a8bc374b8df0 Mon Sep 17 00:00:00 2001 From: nick black Date: Fri, 24 Apr 2020 01:18:48 -0400 Subject: [PATCH] move usage stuff into USAGE.md --- README.md | 2325 +---------------------------------------------------- USAGE.md | 2325 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 2327 insertions(+), 2323 deletions(-) create mode 100644 USAGE.md diff --git a/README.md b/README.md index 49bf6506c..b25520093 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,8 @@ blingful TUI library for modern terminal emulators. definitely not curses. * C++ wrappers by [marek habersack](http://twistedcode.net/blog/) () for more information, see [dankwiki](https://nick-black.com/dankwiki/index.php/Notcurses) -and the [man pages](https://nick-black.com/notcurses). +and the [man pages](https://nick-black.com/notcurses). There's also a reference +[in this repo](USAGE.md). In addition, there is [Doxygen](https://nick-black.com/notcurses/html/) output. There is a [mailing @@ -135,2328 +136,6 @@ hides important output). To watch the bitchin' demo, run `./notcurses-demo -p ../data`. More details can be found on the `notcurses-demo(1)` man page. -## Use - -A full API reference [is available](https://nick-black.com/notcurses/). Manual -pages ought have been installed along with notcurses. - -A program wishing to use Notcurses will need to link it, ideally using the -output of `pkg-config --libs notcurses`. It is advised to compile with the -output of `pkg-config --cflags notcurses`. If using CMake, a support file is -provided, and can be accessed as `notcurses`. - -Before calling into Notcurses—and usually as one of the first calls of the -program—be sure to call `setlocale(3)` with an appropriate UTF-8 locale. It is -usually appropriate to use `setlocale(LC_ALL, "")`, relying on the user to -properly set the `LANG` environment variable. Notcurses will refuse to start if -`nl_langinfo(3)` doesn't indicate `ANSI_X3.4-1968` or `UTF-8`. In addition, it -is wise to mask most signals early in the program, before any threads are -spawned (this is particularly critical for `SIGWINCH`). - -Notcurses requires an available `terminfo(5)` definition appropriate for the -terminal. It is usually appropriate to pass `NULL` in the `termtype` field of a -`notcurses_options` struct, relying on the user to properly set the `TERM` -environment variable. This variable is usually set by the terminal itself. It -might be necessary to manually select a higher-quality definition for your -terminal, i.e. `xterm-direct` as opposed to `xterm` or `xterm-256color`. - -Each terminal can be prepared via a call to `notcurses_init()`, which is -supplied a struct of type `notcurses_options`: - -```c -// Get a human-readable string describing the running Notcurses version. -const char* notcurses_version(void); - -struct cell; // a coordinate on an ncplane: an EGC plus styling -struct ncplane; // a drawable Notcurses surface, composed of cells -struct notcurses; // Notcurses state for a given terminal, composed of ncplanes - -// These log levels consciously map cleanly to those of libav; Notcurses itself -// does not use this full granularity. The log level does not affect the opening -// and closing banners, which can be disabled via the notcurses_option struct's -// 'suppress_banner'. Note that if stderr is connected to the same terminal on -// which we're rendering, any kind of logging will disrupt the output. -typedef enum { - NCLOGLEVEL_SILENT, // default. print nothing once fullscreen service begins - NCLOGLEVEL_PANIC, // print diagnostics immediately related to crashing - NCLOGLEVEL_FATAL, // we're hanging around, but we've had a horrible fault - NCLOGLEVEL_ERROR, // we can't keep doin' this, but we can do other things - NCLOGLEVEL_WARNING, // you probably don't want what's happening to happen - NCLOGLEVEL_INFO, // "standard information" - NCLOGLEVEL_VERBOSE, // "detailed information" - NCLOGLEVEL_DEBUG, // this is honestly a bit much - NCLOGLEVEL_TRACE, // there's probably a better way to do what you want -} ncloglevel_e; - -// Configuration for notcurses_init(). -typedef struct notcurses_options { - // The name of the terminfo database entry describing this terminal. If NULL, - // the environment variable TERM is used. Failure to open the terminal - // definition will result in failure to initialize notcurses. - const char* termtype; - // If smcup/rmcup capabilities are indicated, notcurses defaults to making - // use of the "alternate screen". This flag inhibits use of smcup/rmcup. - bool inhibit_alternate_screen; - // By default, we hide the cursor if possible. This flag inhibits use of - // the civis capability, retaining the cursor. - bool retain_cursor; - // We typically install a signal handler for SIGINT and SIGQUIT that restores - // the screen, and then calls the old signal handler. Set this to inhibit - // registration of any signal handlers. - bool no_quit_sighandlers; - // We typically install a signal handler for SIGWINCH that generates a resize - // event in the notcurses_getc() queue. Set this to inhibit the handler. - bool no_winch_sighandler; - // Notcurses typically prints version info in notcurses_init() and - // performance info in notcurses_stop(). This inhibits that output. - bool suppress_banner; - // If non-NULL, notcurses_render() will write each rendered frame to this - // FILE* in addition to outfp. This is used primarily for debugging. - FILE* renderfp; - // Progressively higher log levels result in more logging to stderr. By - // default, nothing is printed to stderr once fullscreen service begins. - ncloglevel_e loglevel; - // Desirable margins. If all are 0 (default), we will render to the entirety - // of the screen. If the screen is too small, we do what we can--this is - // strictly best-effort. Absolute coordinates are relative to the rendering - // area ((0, 0) is always the origin of the rendering area). - int margin_t, margin_r, margin_b, margin_l; -} notcurses_options; - -// Initialize a notcurses context on the connected terminal at 'fp'. 'fp' must -// be a tty. You'll usually want stdout. Returns NULL on error, including any -// failure initializing terminfo. -struct notcurses* notcurses_init(const notcurses_options* opts, FILE* fp); - -// Destroy a notcurses context. -int notcurses_stop(struct notcurses* nc); -``` - -`notcurses_stop` should be called before exiting your program to restore the -terminal settings and free resources. - -notcurses does not typically generate diagnostics (aside from the intro banner -and outro performance summary). When `stderr` is connected to the same terminal -to which graphics are being printed, printing to stderr will corrupt the output. -Setting `loglevel` to a value higher than `NCLOGLEVEL_SILENT` will cause -diagnostics to be printed to `stderr`: you could ensure `stderr` is redirected -if you make use of this functionality. - -It's probably wise to export `inhibit_alternate_screen` to the user (e.g. via -command line option or environment variable). Developers and motivated users -might appreciate the ability to manipulate `loglevel` and `renderfp`. The -remaining options are typically of use only to application authors. - -The notcurses API draws almost entirely into the virtual buffers of `ncplane`s. -Only upon a call to `notcurses_render` will the visible terminal display be -updated to reflect the changes: - -```c -// Make the physical screen match the virtual screen. Changes made to the -// virtual screen (i.e. most other calls) will not be visible until after a -// successful call to notcurses_render(). -int notcurses_render(struct notcurses* nc); - -// Retrieve the contents of the specified cell as last rendered. The EGC is -// returned, or NULL on error. This EGC must be free()d by the caller. The -// attrword and channels are written to 'attrword' and 'channels', respectively. -char* notcurses_at_yx(struct notcurses* nc, int yoff, int xoff, - uint32_t* attrword, uint64_t* channels); -``` - -One `ncplane` is guaranteed to exist: the "standard plane". The user cannot -move, resize, reparent, or destroy the standard plane (it *can* be erased). -Its dimensions always match notcurses's conception of the visible terminal. A -handle on the standard plane can be acquired with two top-level functions: - -```c -// Get a reference to the standard plane (one matching our current idea of the -// terminal size) for this terminal. The standard plane always exists, and its -// origin is always at the uppermost, leftmost cell of the screen. -struct ncplane* notcurses_stdplane(struct notcurses* nc); -const struct ncplane* notcurses_stdplane_const(const struct notcurses* nc); - -// notcurses_stdplane(), plus free bonus dimensions written to non-NULL y/x! -static inline struct ncplane* -notcurses_stddim_yx(struct notcurses* nc, int* restrict y, int* restrict x){ - struct ncplane* s = notcurses_stdplane(nc); // can't fail - ncplane_dim_yx(s, y, x); // accepts NULL - return s; -} -``` - -A reference to the standard plane *is* persistent across a screen resize, as are -any indexes into its egcpool, but its framebuffer *is not* necessarily -persistent across a screen resize. Thankfully, you shouldn't have a reference -to its framebuffer, and thus only the change to its dimensions can really catch -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); - -// Return our current idea of the terminal dimensions in rows and cols. -static inline void -notcurses_term_dim_yx(const struct notcurses* n, int* restrict rows, - int* restrict cols){ - ncplane_dim_yx(notcurses_stdplane_const(n), rows, cols); -} - -// Refresh the physical screen to match what was last rendered (i.e., without -// reflecting any changes since the last call to notcurses_render()). This is -// primarily useful if the screen is externally corrupted, or if an -// NCKEY_RESIZE event has been read and you're not ready to render. -int notcurses_refresh(struct notcurses* n, int* restrict y, int* restrict x); - -// Returns a 16-bit bitmask in the LSBs of supported curses-style attributes -// (NCSTYLE_UNDERLINE, NCSTYLE_BOLD, etc.) The attribute is only -// indicated as supported if the terminal can support it together with color. -// For more information, see the "ncv" capability in terminfo(5). -unsigned notcurses_supported_styles(const struct notcurses* nc); - -// Returns the number of simultaneous colors claimed to be supported, or 1 if -// there is no color support. Note that several terminal emulators advertise -// more colors than they actually support, downsampling internally. -int notcurses_palette_size(const struct notcurses* nc); - -// Can we fade? Fading requires either the "rgb" or "ccc" terminfo capability. -bool notcurses_canfade(const struct notcurses* nc); - -// Can we load images/videos? This requires being built against FFmpeg. -bool notcurses_canopen(const struct notcurses* nc); - -// Can we change colors in the hardware palette? Requires "ccc" and "initc". -bool notcurses_canchangecolors(const struct notcurses* nc); -``` - -### Direct mode - -"Direct mode" makes a limited subset of notcurses is available for manipulating -typical scrolling or file-backed output. These functions output directly and -immediately to the provided `FILE*`, and `notcurses_render()` is neither -supported nor necessary for such an instance. Use `ncdirect_init()` to create a -direct mode context: - -```c -struct ncdirect; // minimal state for a terminal - -// Initialize a direct-mode notcurses context on the connected terminal at 'fp'. -// 'fp' must be a tty. You'll usually want stdout. Direct mode supportes a -// limited subset of notcurses routines which directly affect 'fp', and neither -// supports nor requires notcurses_render(). This can be used to add color and -// styling to text in the standard output paradigm. Returns NULL on error, -// including any failure initializing terminfo. -struct ncdirect* ncdirect_init(const char* termtype, FILE* fp); - -// Release 'nc' and any associated resources. 0 on success, non-0 on failure. -int ncdirect_stop(struct ncdirect* nc); -``` - -This context must be destroyed using `ncdirect_stop()`. The following functions -are available for direct mode: - -```c -int ncdirect_fg(struct ncdirect* nc, unsigned rgb); -int ncdirect_bg(struct ncdirect* nc, unsigned rgb); - -static inline int -ncdirect_bg_rgb8(struct ncdirect* nc, unsigned r, unsigned g, unsigned b){ - if(r > 255 || g > 255 || b > 255){ - return -1; - } - return ncdirect_bg(nc, (r << 16u) + (g << 8u) + b); -} - -static inline int -ncdirect_fg_rgb8(struct ncdirect* nc, unsigned r, unsigned g, unsigned b){ - if(r > 255 || g > 255 || b > 255){ - return -1; - } - return ncdirect_fg(nc, (r << 16u) + (g << 8u) + b); -} - -// Get the current number of columns/rows. -int ncdirect_dim_x(const struct ncdirect* nc); -int ncdirect_dim_y(const struct ncdirect* nc); - -int ncdirect_fg_default(struct ncdirect* nc); -int ncdirect_bg_default(struct ncdirect* nc); -int ncdirect_styles_set(struct ncdirect* n, unsigned stylebits); -int ncdirect_styles_on(struct ncdirect* n, unsigned stylebits); -int ncdirect_styles_off(struct ncdirect* n, unsigned stylebits); -int ncdirect_clear(struct ncdirect* nc); // clear the screen - -// Move the cursor in direct mode. -1 to retain current location on that axis. -int ncdirect_cursor_move_yx(struct ncdirect* n, int y, int x); -int ncdirect_cursor_enable(struct ncdirect* nc); -int ncdirect_cursor_disable(struct ncdirect* nc); - -// Relative moves. num < 0 is an error. -int ncdirect_cursor_up(struct ncdirect* nc, int num); -int ncdirect_cursor_left(struct ncdirect* nc, int num); -int ncdirect_cursor_right(struct ncdirect* nc, int num); -int ncdirect_cursor_down(struct ncdirect* nc, int num); -``` - -### Alignment - -Most functions that generate output can be aligned relative to an ncplane. -Alignment currently comes in three forms: `NCALIGN_LEFT`, `NCALIGN_CENTER`, and -`NCALIGN_RIGHT`. - -```c -// Alignment within the ncplane. Left/right-justified, or centered. -typedef enum { - NCALIGN_LEFT, - NCALIGN_CENTER, - NCALIGN_RIGHT, -} ncalign_e; - -// Return the column at which 'c' cols ought start in order to be aligned -// according to 'align' within ncplane 'n'. Returns INT_MAX on invalid 'align'. -// Undefined behavior on negative 'c'. -static inline int -ncplane_align(const struct ncplane* n, ncalign_e align, int c){ - if(align == NCALIGN_LEFT){ - return 0; - } - int cols; - ncplane_dim_yx(n, NULL, &cols); - if(align == NCALIGN_CENTER){ - return (cols - c) / 2; - }else if(align == NCALIGN_RIGHT){ - return cols - c; - } - return INT_MAX; -} -``` - -### Input - -Input can currently be taken only from `stdin`, but on the plus side, stdin -needn't be a terminal device (unlike the ttyfp `FILE*` passed to `notcurses_init()`). -Generalized input ought happen soon. There is only one input queue per `struct -notcurses`. - -Like NCURSES, notcurses will watch for escape sequences, check them against the -terminfo database, and return them as special keys (we hijack the Private Use -Area for special keys, specifically Supplementary Private Use Area B (u100000 -through u10ffffd). Unlike NCURSES, the fundamental unit of input is the -UTF8-encoded Unicode codepoint. Note, however, that only one codepoint is -returned at a time (as opposed to an entire EGC). - -It is generally possible for a false positive to occur, wherein keypresses -intended to be distinct are combined into an escape sequence. False negatives -where an intended escape sequence are read as an ESC key followed by distinct -keystrokes are also possible. NCURSES provides the `ESCDELAY` variable to -control timing. notcurses brooks no delay; all characters of an escape sequence -must be readable without delay for it to be interpreted as such. - -```c -// 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 -// keypress, i.e. anything that doesn't correspond to a Unicode codepoint (e.g. -// arrow keys, function keys, screen resize events, etc.). These are mapped -// into Unicode's Supplementary Private Use Area-B, starting at U+100000. -// -// notcurses_getc() and notcurses_getc_nblock() are both nonblocking. -// notcurses_getc_blocking() blocks until a codepoint or special key is read, -// or until interrupted by a signal. -// -// In the case of a valid read, a 32-bit Unicode codepoint is returned. 0 is -// returned to indicate that no input was available, but only by -// notcurses_getc(). Otherwise (including on EOF) (char32_t)-1 is returned. - -#define suppuabize(w) ((w) + 0x100000) - -// Special composed key definitions. These values are added to 0x100000. -#define NCKEY_INVALID suppuabize(0) -#define NCKEY_RESIZE suppuabize(1) // generated internally in response to SIGWINCH -#define NCKEY_UP suppuabize(2) -#define NCKEY_RIGHT suppuabize(3) -#define NCKEY_DOWN suppuabize(4) -#define NCKEY_LEFT suppuabize(5) -#define NCKEY_INS suppuabize(6) -#define NCKEY_DEL suppuabize(7) -#define NCKEY_BACKSPACE suppuabize(8) // backspace (sometimes) -#define NCKEY_PGDOWN suppuabize(9) -#define NCKEY_PGUP suppuabize(10) -#define NCKEY_HOME suppuabize(11) -#define NCKEY_END suppuabize(12) -#define NCKEY_F00 suppuabize(20) -#define NCKEY_F01 suppuabize(21) -#define NCKEY_F02 suppuabize(22) -#define NCKEY_F03 suppuabize(23) -#define NCKEY_F04 suppuabize(24) -// ... up to 100 function keys, egads -#define NCKEY_ENTER suppuabize(121) -#define NCKEY_CLS suppuabize(122) // "clear-screen or erase" -#define NCKEY_DLEFT suppuabize(123) // down + left on keypad -#define NCKEY_DRIGHT suppuabize(124) -#define NCKEY_ULEFT suppuabize(125) // up + left on keypad -#define NCKEY_URIGHT suppuabize(126) -#define NCKEY_CENTER suppuabize(127) // the most truly neutral of keypresses -#define NCKEY_BEGIN suppuabize(128) -#define NCKEY_CANCEL suppuabize(129) -#define NCKEY_CLOSE suppuabize(130) -#define NCKEY_COMMAND suppuabize(131) -#define NCKEY_COPY suppuabize(132) -#define NCKEY_EXIT suppuabize(133) -#define NCKEY_PRINT suppuabize(134) -#define NCKEY_REFRESH suppuabize(135) -// Mouse events. We try to encode some details into the char32_t (i.e. which -// button was pressed), but some is embedded in the ncinput event. The release -// event is generic across buttons; callers must maintain state, if they care. -#define NCKEY_BUTTON1 suppuabize(201) -#define NCKEY_BUTTON2 suppuabize(202) -#define NCKEY_BUTTON3 suppuabize(203) -// ... up to 11 mouse buttons -#define NCKEY_RELEASE suppuabize(212) - -// Is this char32_t a Supplementary Private Use Area-B codepoint? -static inline bool -nckey_supppuab_p(char32_t w){ - return w >= 0x100000 && w <= 0x10fffd; -} - -// Is the event a synthesized mouse event? -static inline bool -nckey_mouse_p(char32_t r){ - return r >= NCKEY_BUTTON1 && r <= NCKEY_RELEASE; -} - -// An input event. Cell coordinates are currently defined only for mouse events. -typedef struct ncinput { - char32_t id; // identifier. Unicode codepoint or synthesized NCKEY event - int y; // y cell coordinate of event, -1 for undefined - int x; // x cell coordinate of event, -1 for undefined - bool alt; // was alt held? - bool shift; // was shift held? - bool ctrl; // was ctrl held? - uint64_t seqnum; // input event number -} ncinput; - -// See ppoll(2) for more detail. Provide a NULL 'ts' to block at length, a 'ts' -// of 0 for non-blocking operation, and otherwise a timespec to bound blocking. -// Signals in sigmask (less several we handle internally) will be atomically -// masked and unmasked per ppoll(2). It should generally contain all signals. -// Returns a single Unicode code point, or (char32_t)-1 on error. 'sigmask' may -// be NULL. Returns 0 on a timeout. If an event is processed, the return value -// is the 'id' field from that event. 'ni' may be NULL. -char32_t notcurses_getc(struct notcurses* n, const struct timespec* ts, - sigset_t* sigmask, ncinput* ni); - -// 'ni' may be NULL if the caller is uninterested in event details. If no event -// is ready, returns 0. -static inline char32_t -notcurses_getc_nblock(struct notcurses* n, ncinput* ni){ - sigset_t sigmask; - sigfillset(&sigmask); - struct timespec ts = { .tv_sec = 0, .tv_nsec = 0 }; - return notcurses_getc(n, &ts, &sigmask, ni); -} - -// 'ni' may be NULL if the caller is uninterested in event details. Blocks -// until an event is processed or a signal is received. -static inline char32_t -notcurses_getc_blocking(struct notcurses* n, ncinput* ni){ - sigset_t sigmask; - sigemptyset(&sigmask); - return notcurses_getc(n, NULL, &sigmask, ni); -} -``` - -### Mice - -notcurses supports mice, though only through brokers such as X or -[GPM](https://www.nico.schottelius.org/software/gpm/). It does not speak -directly to hardware. Mouse events must be explicitly enabled with a -successful call to `notcurses_mouse_enable()`, and can later be disabled. - -```c -// Enable the mouse in "button-event tracking" mode with focus detection and -// UTF8-style extended coordinates. On failure, -1 is returned. On success, 0 -// is returned, and mouse events will be published to notcurses_getc(). -int notcurses_mouse_enable(struct notcurses* n); - -// Disable mouse events. Any events in the input queue can still be delivered. -int notcurses_mouse_disable(struct notcurses* n); -``` - -"Button-event tracking mode" implies the ability to detect mouse button -presses, and also mouse movement while holding down a mouse button (i.e. to -effect drag-and-drop). Mouse events are returned via the `NCKEY_MOUSE*` values, -with coordinate information in the `ncinput` struct. - -### Planes - -Fundamental to notcurses is a z-buffer of rectilinear virtual screens, known -as `ncplane`s. An `ncplane` can be larger than the physical screen, or smaller, -or the same size; it can be entirely contained within the physical screen, or -overlap in part, or lie wholly beyond the boundaries, never to be rendered. -In addition to its framebuffer--a rectilinear matrix of cells -(see [Cells](#cells))--an `ncplane` is defined by: - -* a base cell, used for any cell on the plane without a glyph, -* the egcpool backing its cells, -* a current cursor location, -* a current style, foreground channel, and background channel, -* its geometry, -* a configured user curry (a `void*`), -* its position relative to the visible plane, and -* its z-index. - -If opaque, a `cell` on a higher `ncplane` completely obstructs a corresponding -`cell` from a lower `ncplane` from being seen. An `ncplane` corresponds loosely -to an [NCURSES Panel](https://invisible-island.net/ncurses/ncurses-intro.html#panels), -but is the primary drawing surface of notcurses—there is no object -corresponding to a bare NCURSES `WINDOW`. - -In addition to `ncplane_new()`, an `ncplane` can be created aligned relative -to an existing `ncplane` (including the standard plane) using `ncplane_aligned()`. -When an `ncplane` is no longer needed, free it with `ncplane_destroy()`. To -quickly reset the `ncplane`, use `ncplane_erase()`. - -```c -// Create a new ncplane at the specified offset (relative to the standard plane) -// and the specified size. The number of rows and columns must both be positive. -// This plane is initially at the top of the z-buffer, as if ncplane_move_top() -// had been called on it. The void* 'opaque' can be retrieved (and reset) later. -struct ncplane* ncplane_new(struct notcurses* nc, int rows, int cols, - int yoff, int xoff, void* opaque); - -// Create a new ncplane aligned relative to 'n'. -struct ncplane* ncplane_aligned(struct ncplane* n, int rows, int cols, - int yoff, ncalign_e align, void* opaque); - -// Create a plane bound to plane 'n'. Being bound to 'n' means that 'yoff' and -// 'xoff' are interpreted relative to that plane's origin, and that if that -// plane is moved later, this new plane is moved by the same amount. -struct ncplane* ncplane_bound(struct ncplane* n, int rows, int cols, - int yoff, int xoff, void* opaque); - -// Plane 'n' will be unbound from its parent plane, if it is currently bound, -// and will be made a bound child of 'newparent', if 'newparent' is not NULL. -struct ncplane* ncplane_reparent(struct ncplane* n, struct ncplane* newparent); - -// Duplicate an existing ncplane. The new plane will have the same geometry, -// will duplicate all content, and will start with the same rendering state. -// The new plane will be immediately above the old one on the z axis. -struct ncplane* ncplane_dup(struct ncplane* n, void* opaque); - -// Merge the ncplane 'src' down onto the ncplane 'dst'. This is most rigorously -// defined as "write to 'dst' the frame that would be rendered were the entire -// stack made up only of 'src' and, below it, 'dst', and 'dst' was the entire -// rendering region." Merging is independent of the position of 'src' viz 'dst' -// on the z-axis. If 'src' does not intersect with 'dst', 'dst' will not be -// changed, but it is not an error. The source plane still exists following -// this operation. Do not supply the same plane for both 'src' and 'dst'. -int ncplane_mergedown(struct ncplane* restrict src, struct ncplane* restrict dst); - -// Erase every cell in the ncplane, resetting all attributes to normal, all -// colors to the default color, and all cells to undrawn. All cells associated -// with this ncplane are invalidated, and must not be used after the call, -// excluding the base cell. -void ncplane_erase(struct ncplane* n); -``` - -All planes, including the standard plane, are created with scrolling disabled. -Attempting to print past the end of a line will stop at the plane boundary, -and indicate an error. On a plane 10 columns wide and two rows high, printing -"0123456789" at the origin should succeed, but printing "01234567890" will by -default fail at the eleventh character. In either case, the cursor will be left -at location 0x10; it must be moved before further printing can take place. If -scrolling is enabled, the first row will be filled with 01234546789, the second -row will have 0 written to its first column, and the cursor will end up at 1x1. -Note that it is still an error to manually attempt to move the cursor off-plane, -or to specify off-plane output. Boxes do not scroll; attempting to draw a 2x11 -box on our 2x10 plane will result in an error and no output. When scrolling is -enabled, and output takes place while the cursor is past the end of the last -row, the first row is discarded, all other rows are moved up, the last row is -cleared, and output begins at the beginning of the last row. This does not take -place until output is generated (i.e. it is possible to fill a plane when -scrolling is enabled). - -```c -// All planes are created with scrolling disabled. Scrolling can be dynamically -// controlled with ncplane_set_scrolling(). Returns true if scrolling was -// previously enabled, or false if it was disabled. -bool ncplane_set_scrolling(struct ncplane* n, bool scrollp); -``` - -Planes can be freely resized, though they must retain a positive size in -both dimensions. The powerful `ncplane_resize()` allows resizing an `ncplane`, -retaining all or a portion of the plane's existing content, and translating -the plane in one step. The helper function `ncplane_resize_simple()` allows -resizing an `ncplane` without movement, retaining all possible data. To move -the plane without resizing it or changing its content, use `ncplane_move_yx()`. -It is an error to invoke these functions on the standard plane. - -```c -// Resize the specified ncplane. The four parameters 'keepy', 'keepx', -// 'keepleny', and 'keeplenx' define a subset of the ncplane to keep, -// unchanged. This may be a section of size 0, though none of these four -// parameters may be negative. 'keepx' and 'keepy' are relative to the ncplane. -// They must specify a coordinate within the ncplane's totality. 'yoff' and -// 'xoff' are relative to 'keepy' and 'keepx', and place the upper-left corner -// 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 '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. -int ncplane_resize(struct ncplane* n, int keepy, int keepx, int keepleny, - int keeplenx, int yoff, int xoff, int ylen, int xlen); - -// Resize the plane, retaining what data we can (everything, unless we're -// shrinking in some dimension). Keep the origin where it is. -static inline int -ncplane_resize_simple(struct ncplane* n, int ylen, int xlen){ - int oldy, oldx; - ncplane_dim_yx(n, &oldy, &oldx); // current dimensions of 'n' - int keepleny = oldy > ylen ? ylen : oldy; - int keeplenx = oldx > xlen ? xlen : oldx; - return ncplane_resize(n, 0, 0, keepleny, keeplenx, 0, 0, ylen, xlen); -} - -// Move this plane relative to the standard plane. It is an error to attempt to -// move the standard plane. -int ncplane_move_yx(struct ncplane* n, int y, int x); - -// Get the origin of this ncplane relative to the standard plane. -void ncplane_yx(struct ncplane* n, int* restrict y, int* restrict x); - -// Return the dimensions of this ncplane. -void ncplane_dim_yx(struct ncplane* n, int* restrict rows, int* restrict cols); - -static inline int -ncplane_dim_y(const struct ncplane* n){ - int dimy; - ncplane_dim_yx(n, &dimy, NULL); - return dimy; -} - -static inline int -ncplane_dim_x(const struct ncplane* n){ - int dimx; - ncplane_dim_yx(n, NULL, &dimx); - return dimx; -} - -// provided a coordinate relative to the origin of 'src', map it to the same -// absolute coordinate relative to thte origin of 'dst'. either or both of 'y' -// and 'x' may be NULL. if 'dst' is NULL, it is taken to be the standard plane. -void ncplane_translate(const struct ncplane* src, const struct ncplane* dst, - int* restrict y, int* restrict x); - -// Fed absolute 'y'/'x' coordinates, determine whether that coordinate is -// within the ncplane 'n'. If not, return false. If so, return true. Either -// way, translate the absolute coordinates relative to 'n'. If the point is not -// within 'n', these coordinates will not be within the dimensions of the plane. -bool ncplane_translate_abs(const struct ncplane* n, int* restrict y, int* restrict x); -``` - -If a given cell's glyph is zero, or its foreground channel is fully transparent, -it is considered to have no foreground. A _default_ cell can be chosen for the -`ncplane`, to be consulted in this case. If the base cell's glyph is likewise -zero (or its foreground channel fully transparent), the plane's foreground is -not rendered. Note that the base cell, like every other cell, has its own -foreground and background channels. - -```c -// Set the specified style bits for the ncplane 'n', whether they're actively -// supported or not. -void ncplane_styles_set(struct ncplane* n, unsigned stylebits); - -// Add the specified styles to the ncplane's existing spec. -void ncplane_styles_on(struct ncplane* n, unsigned stylebits); - -// Remove the specified styles from the ncplane's existing spec. -void ncplane_styles_off(struct ncplane* n, unsigned stylebits); - -// Return the current styling for this ncplane. -unsigned ncplane_styles(const struct ncplane* n); - -// Set the ncplane's base cell to this cell. It will be used for purposes of -// rendering anywhere that the ncplane's gcluster is 0. Erasing the ncplane -// does not reset the base cell; this function must be called with a zero 'c'. -int ncplane_set_base_cell(struct ncplane* ncp, const cell* c); - -// Set the ncplane's base cell to this cell. It will be used for purposes of -// rendering anywhere that the ncplane's gcluster is 0. Erasing the ncplane -// does not reset the base cell; this function must be called with an empty -// 'egc'. 'egc' must be a single extended grapheme cluster. -int ncplane_set_base(struct ncplane* ncp, const char* egc, - uint32_t attrword, uint64_t channels); - -// Extract the ncplane's base cell into 'c'. The reference is invalidated if -// 'ncp' is destroyed. -int ncplane_base(struct ncplane* ncp, cell* c); -``` - -`ncplane`s are completely ordered along an imaginary z-axis. Newly-created -`ncplane`s are on the top of the stack. They can be freely reordered. - -```c -// Splice ncplane 'n' out of the z-buffer, and reinsert it at the top or bottom. -int ncplane_move_top(struct ncplane* n); -int ncplane_move_bottom(struct ncplane* n); - -// Splice ncplane 'n' out of the z-buffer, and reinsert it below 'below'. -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'. -int ncplane_move_above(struct ncplane* restrict n, struct ncplane* restrict above); - -// Return the ncplane below this one, or NULL if this is at the stack's bottom. -struct ncplane* ncplane_below(struct ncplane* n); -``` - -Each plane holds a user pointer which can be retrieved and set (or ignored). In -addition, the plane's virtual framebuffer can be accessed (note that this does -not necessarily reflect anything on the actual screen). - -```c -// Retrieve the current contents of the cell under the cursor. The EGC is -// returned, or NULL on error. This EGC must be free()d by the caller. The -// attrword and channels are written to 'attrword' and 'channels', respectively. -char* ncplane_at_cursor(struct ncplane* n, uint32_t* attrword, uint64_t* channels); - -// Retrieve the current contents of the cell under the cursor into 'c'. This -// cell is invalidated if the associated plane is destroyed. -int ncplane_at_cursor_cell(struct ncplane* n, cell* c); - -// Retrieve the current contents of the specified cell. The EGC is returned, or -// NULL on error. This EGC must be free()d by the caller. The attrword and -// channels are written to 'attrword' and 'channels', respectively. -char* ncplane_at_yx(struct ncplane* n, int y, int x, - uint32_t* attrword, uint64_t* channels); - -// Retrieve the current contents of the specified cell into 'c'. This cell is -// invalidated if the associated plane is destroyed. -int ncplane_at_yx_cell(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. -void* ncplane_set_userptr(struct ncplane* n, void* opaque); -void* ncplane_userptr(struct ncplane* n); -``` - -All output is to `ncplane`s. There is no cost in moving the cursor around the -virtual framebuffer. Output that's never rendered still has some memory transfer -cost as the virtual framebuffer is prepared, but new data overwrites it in -memory. - -```c -// Move the cursor to the specified position (the cursor needn't be visible). -// Returns -1 on error, including negative parameters, or ones exceeding the -// plane's dimensions. -int ncplane_cursor_move_yx(struct ncplane* n, int y, int x); - -// Get the current position of the cursor within n. y and/or x may be NULL. -void ncplane_cursor_yx(const struct ncplane* n, int* restrict y, int* restrict x); - -// Replace the cell at the specified coordinates with the provided cell 'c', -// and advance the cursor by the width of the cell (but not past the end of the -// plane). On success, returns the number of columns the cursor was advanced. -// On failure, -1 is returned. -int ncplane_putc_yx(struct ncplane* n, int y, int x, const cell* c); - -// Call ncplane_putc_yx() for the current cursor location. -static inline int -ncplane_putc(struct ncplane* n, const cell* c){ - return ncplane_putc_yx(n, -1, -1, c); -} - -// Replace the cell at the specified coordinates with the provided 7-bit char -// 'c'. Advance the cursor by 1. On success, returns 1. On failure, returns -1. -// This works whether the underlying char is signed or unsigned. -static inline int -ncplane_putsimple_yx(struct ncplane* n, int y, int x, char c){ - cell ce = CELL_INITIALIZER(c, ncplane_attr(n), ncplane_channels(n)); - if(!cell_simple_p(&ce)){ - return -1; - } - return ncplane_putc_yx(n, y, x, &ce); -} - -// Call ncplane_putsimple_yx() at the current cursor location. -static inline int -ncplane_putsimple(struct ncplane* n, char c){ - return ncplane_putsimple_yx(n, -1, -1, c); -} - -// Replace the EGC underneath us, but retain the styling. The current styling -// of the plane will not be changed. -int ncplane_putsimple_stainable(struct ncplane* n, char c); - -// Replace the cell at the specified coordinates with the provided wide char -// 'w'. Advance the cursor by the character's width as reported by wcwidth(). -// On success, returns 1. On failure, returns -1. -static inline int -ncplane_putwc_yx(struct ncplane* n, int y, int x, wchar_t w){ - wchar_t warr[2] = { w, L'\0' }; - return ncplane_putwstr_yx(n, y, x, warr); -} - -// Call ncplane_putwc() at the current cursor position. -static inline int -ncplane_putwc(struct ncplane* n, wchar_t w){ - return ncplane_putwc_yx(n, -1, -1, w); -} - -// Replace the cell at the specified coordinates with the provided EGC, and -// advance the cursor by the width of the cluster (but not past the end of the -// plane). On success, returns the number of columns the cursor was advanced. -// On failure, -1 is returned. The number of bytes converted from gclust is -// written to 'sbytes' if non-NULL. -int ncplane_putegc_yx(struct ncplane* n, int y, int x, const char* gclust, int* sbytes); - -// Call ncplane_putegc() at the current cursor location. -static inline int -ncplane_putegc(struct ncplane* n, const char* gclust, int* sbytes){ - return ncplane_putegc_yx(n, -1, -1, gclust, sbytes); -} - -// Replace the EGC underneath us, but retain the styling. The current styling -// of the plane will not be changed. -int ncplane_putegc_stainable(struct ncplane* n, const char* gclust, int* sbytes); - -#define WCHAR_MAX_UTF8BYTES 6 - -// ncplane_putegc(), but following a conversion from wchar_t to UTF-8 multibyte. -static inline int -ncplane_putwegc(struct ncplane* n, const wchar_t* gclust, int* sbytes){ - // maximum of six UTF8-encoded bytes per wchar_t - const size_t mbytes = (wcslen(gclust) * WCHAR_MAX_UTF8BYTES) + 1; - char* mbstr = (char*)malloc(mbytes); // need cast for c++ callers - if(mbstr == NULL){ - return -1; - } - size_t s = wcstombs(mbstr, gclust, mbytes); - if(s == (size_t)-1){ - free(mbstr); - return -1; - } - int ret = ncplane_putegc(n, mbstr, sbytes); - free(mbstr); - return ret; -} - -// Call ncplane_putwegc() after successfully moving to y, x. -static inline int -ncplane_putwegc_yx(struct ncplane* n, int y, int x, const wchar_t* gclust, - int* sbytes){ - if(ncplane_cursor_move_yx(n, y, x)){ - return -1; - } - return ncplane_putwegc(n, gclust, sbytes); -} - -// Replace the EGC underneath us, but retain the styling. The current styling -// of the plane will not be changed. -int ncplane_putwegc_stainable(struct ncplane* n, const wchar_t* gclust, int* sbytes); - -// Write a series of EGCs to the current location, using the current style. -// They will be interpreted as a series of columns (according to the definition -// of ncplane_putc()). Advances the cursor by some positive number of cells -// (though not beyond the end of the plane); this number is returned on success. -// On error, a non-positive number is returned, indicating the number of cells -// which were written before the error. -static inline int -ncplane_putstr_yx(struct ncplane* n, int y, int x, const char* gclusters); - -static inline int -ncplane_putstr(struct ncplane* n, const char* gclustarr){ - return ncplane_putstr_yx(n, -1, -1, gclustarr); -} - -int ncplane_putstr_aligned(struct ncplane* n, int y, ncalign_e align, const char* s); - -// ncplane_putstr(), but following a conversion from wchar_t to UTF-8 multibyte. -static inline int -ncplane_putwstr_yx(struct ncplane* n, int y, int x, const wchar_t* gclustarr){ - // maximum of six UTF8-encoded bytes per wchar_t - const size_t mbytes = (wcslen(gclustarr) * WCHAR_MAX_UTF8BYTES) + 1; - char* mbstr = (char*)malloc(mbytes); // need cast for c++ callers - if(mbstr == NULL){ - return -1; - } - size_t s = wcstombs(mbstr, gclustarr, mbytes); - if(s == (size_t)-1){ - free(mbstr); - return -1; - } - int ret = ncplane_putstr_yx(n, y, x, mbstr); - free(mbstr); - return ret; -} - -static inline int -ncplane_putwstr_aligned(struct ncplane* n, int y, ncalign_e align, - const wchar_t* gclustarr){ - int width = wcswidth(gclustarr, INT_MAX); - int xpos = ncplane_align(n, align, width); - return ncplane_putwstr_yx(n, y, xpos, gclustarr); -} - -static inline int -ncplane_putwstr(struct ncplane* n, const wchar_t* gclustarr){ - return ncplane_putwstr_yx(n, -1, -1, gclustarr); -} - -// The ncplane equivalents of printf(3) and vprintf(3). -int ncplane_vprintf_aligned(struct ncplane* n, int y, ncalign_e align, - const char* format, va_list ap); - -int ncplane_vprintf_yx(struct ncplane* n, int y, int x, - const char* format, va_list ap); - -static inline int -ncplane_vprintf(struct ncplane* n, const char* format, va_list ap){ - return ncplane_vprintf_yx(n, -1, -1, format, ap); -} - -static inline int -ncplane_printf(struct ncplane* n, const char* format, ...) - __attribute__ ((format (printf, 2, 3))); - -static inline int -ncplane_printf(struct ncplane* n, const char* format, ...){ - va_list va; - va_start(va, format); - int ret = ncplane_vprintf(n, format, va); - va_end(va); - return ret; -} - -static inline int -ncplane_printf_aligned(struct ncplane* n, int y, ncalign_e align, - const char* format, ...) - __attribute__ ((format (printf, 4, 5))); - -static inline int -ncplane_printf_yx(struct ncplane* n, int y, int x, const char* format, ...){ - va_list va; - va_start(va, format); - int ret = ncplane_vprintf_yx(n, y, x, format, va); - va_end(va); - return ret; -} - -static inline int -ncplane_printf_yx(struct ncplane* n, int y, int x, const char* format, ...) - __attribute__ ((format (printf, 4, 5))); - -static inline int -ncplane_printf_aligned(struct ncplane* n, int y, ncalign_e align, const char* format, ...){ - va_list va; - va_start(va, format); - int ret = ncplane_vprintf_aligned(n, y, align, format, va); - va_end(va); - return ret; -} -``` - -Lines and boxes can be drawn, interpolating their colors between their two -endpoints. For a line of a single color, be sure to specify the same channels -on both sides. Boxes allow fairly detailed specification of how they're drawn. - -```c -// Draw horizontal or vertical lines using the specified cell, starting at the -// current cursor position. The cursor will end at the cell following the last -// cell output (even, perhaps counter-intuitively, when drawing vertical -// lines), just as if ncplane_putc() was called at that spot. Return the -// number of cells drawn on success. On error, return the negative number of -// cells drawn. -int ncplane_hline_interp(struct ncplane* n, const cell* c, int len, - uint64_t c1, uint64_t c2); - -static inline int -ncplane_hline(struct ncplane* n, const cell* c, int len){ - return ncplane_hline_interp(n, c, len, c->channels, c->channels); -} - -int ncplane_vline_interp(struct ncplane* n, const cell* c, int len, - uint64_t c1, uint64_t c2); - -static inline int -ncplane_vline(struct ncplane* n, const cell* c, int len){ - return ncplane_vline_interp(n, c, len, c->channels, c->channels); -} - -// Draw a box with its upper-left corner at the current cursor position, and its -// lower-right corner at 'ystop'x'xstop'. The 6 cells provided are used to draw the -// upper-left, ur, ll, and lr corners, then the horizontal and vertical lines. -// 'ctlword' is defined in the least significant byte, where bits [7, 4] are a -// gradient mask, and [3, 0] are a border mask: -// * 7, 3: top -// * 6, 2: right -// * 5, 1: bottom -// * 4, 0: left -// If the gradient bit is not set, the styling from the hl/vl cells is used for -// the horizontal and vertical lines, respectively. If the gradient bit is set, -// the color is linearly interpolated between the two relevant corner cells. -// -// By default, vertexes are drawn whether their connecting edges are drawn or -// not. The value of the bits corresponding to NCBOXCORNER_MASK control this, -// and are interpreted as the number of connecting edges necessary to draw a -// given corner. At 0 (the default), corners are always drawn. At 3, corners -// are never drawn (as at most 2 edges can touch a box's corner). - -#define NCBOXMASK_TOP 0x0001 -#define NCBOXMASK_RIGHT 0x0002 -#define NCBOXMASK_BOTTOM 0x0004 -#define NCBOXMASK_LEFT 0x0008 -#define NCBOXGRAD_TOP 0x0010 -#define NCBOXGRAD_RIGHT 0x0020 -#define NCBOXGRAD_BOTTOM 0x0040 -#define NCBOXGRAD_LEFT 0x0080 -#define NCBOXCORNER_MASK 0x0300 -#define NCBOXCORNER_SHIFT 8u - -int ncplane_box(struct ncplane* n, const cell* ul, const cell* ur, - const cell* ll, const cell* lr, const cell* hline, - const cell* vline, int ystop, int xstop, - unsigned ctlword); - -// Draw a box with its upper-left corner at the current cursor position, having -// dimensions 'ylen'x'xlen'. See ncplane_box() for more information. The -// minimum box size is 2x2, and it cannot be drawn off-screen. -static inline int -ncplane_box_sized(struct ncplane* n, const cell* ul, const cell* ur, - const cell* ll, const cell* lr, const cell* hline, - const cell* vline, int ylen, int xlen, unsigned ctlword){ - int y, x; - ncplane_cursor_yx(n, &y, &x); - return ncplane_box(n, ul, ur, ll, lr, hline, vline, y + ylen - 1, - x + xlen - 1, ctlword); -} - -static inline int -ncplane_perimeter(struct ncplane* n, const cell* ul, const cell* ur, - const cell* ll, const cell* lr, const cell* hline, - const cell* vline, unsigned ctlword){ - if(ncplane_cursor_move_yx(n, 0, 0)){ - return -1; - } - int dimy, dimx; - ncplane_dim_yx(n, &dimy, &dimx); - return ncplane_box_sized(n, ul, ur, ll, lr, hline, vline, dimy, dimx, ctlword); -} - -static inline int -ncplane_rounded_box(struct ncplane* n, uint32_t attr, uint64_t channels, - int ystop, int xstop, unsigned ctlword){ - int ret = 0; - 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((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); - return ret; -} - -static inline int -ncplane_rounded_box_sized(struct ncplane* n, uint32_t attr, uint64_t channels, - int ylen, int xlen, unsigned ctlword){ - int y, x; - ncplane_cursor_yx(n, &y, &x); - return ncplane_rounded_box(n, attr, channels, y + ylen - 1, - x + xlen - 1, ctlword); -} - -static inline int -ncplane_double_box(struct ncplane* n, uint32_t attr, uint64_t channels, - int ystop, int xstop, unsigned ctlword){ - int ret = 0; - 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((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); - return ret; -} - -static inline int -ncplane_double_box_sized(struct ncplane* n, uint32_t attr, uint64_t channels, - int ylen, int xlen, unsigned ctlword){ - int y, x; - ncplane_cursor_yx(n, &y, &x); - return ncplane_double_box(n, attr, channels, y + ylen - 1, - x + xlen - 1, ctlword); -} -``` - -Similarly, areas can be filled with a cell. - -```c -// Starting at the specified coordinate, if it has no glyph, 'c' is copied into -// it. We do the same to all cardinally-connected glyphless cells, filling in -// everything behind a boundary. Returns the number of cells polyfilled. An -// invalid initial y, x is an error. Returns the number of cells filled, or -// -1 on error. -int ncplane_polyfill_yx(struct ncplane* n, int y, int x, const cell* c); - -// Draw a gradient with its upper-left corner at the current cursor position, -// stopping at 'ystop'x'xstop'. The glyph composed of 'egc' and 'attrword' is -// used for all cells. The channels specified by 'ul', 'ur', 'll', and 'lr' -// are composed into foreground and background gradients. To do a vertical -// gradient, 'ul' ought equal 'ur' and 'll' ought equal 'lr'. To do a -// horizontal gradient, 'ul' ought equal 'll' and 'ur' ought equal 'ul'. To -// color everything the same, all four channels should be equivalent. The -// resulting alpha values are equal to incoming alpha values. Returns the -// number of cells filled on success, or -1 on failure. -int ncplane_gradient(struct ncplane* n, const char* egc, uint32_t attrword, - uint64_t ul, uint64_t ur, uint64_t ll, uint64_t lr, - int ystop, int xstop); - -// Draw a gradient with its upper-left corner at the current cursor position, -// having dimensions 'ylen'x'xlen'. See ncplane_gradient for more information. -static inline int -ncplane_gradient_sized(struct ncplane* n, const char* egc, uint32_t attrword, - uint64_t ul, uint64_t ur, uint64_t ll, uint64_t lr, - int ylen, int xlen){ - int y, x; - ncplane_cursor_yx(n, &y, &x); - return ncplane_gradient(n, egc, attrword, ul, ur, ll, lr, y + ylen - 1, x + xlen - 1); -} - -// Do a high-resolution gradient using upper blocks and synced backgrounds. -// This doubles the number of vertical gradations, but restricts you to -// half blocks (appearing to be full blocks). Returns the number of cells -// filled on success, or -1 on error. -int ncplane_highgradient(struct ncplane* n, uint32_t ul, uint32_t ur, - uint32_t ll, uint32_t lr, int ystop, int xstop); - -static inline int -ncplane_highgradient_sized(struct ncplane* n, uint64_t ul, uint64_t ur, - uint64_t ll, uint64_t lr, int ylen, int xlen){ - if(ylen < 1 || xlen < 1){ - return -1; - } - int y, x; - ncplane_cursor_yx(n, &y, &x); - return ncplane_highgradient(n, ul, ur, ll, lr, y + ylen - 1, x + xlen - 1); -} - -// Set the given style throughout the specified region, keeping content and -// channels unchanged. Returns the number of cells set, or -1 on failure. -int ncplane_format(struct ncplane* n, int ystop, int xstop, uint32_t attrword); - -// Set the given channels throughout the specified region, keeping content and -// attributes unchanged. Returns the number of cells set, or -1 on failure. -int ncplane_stain(struct ncplane* n, int ystop, int xstop, - uint64_t ul, uint64_t ur, uint64_t ll, uint64_t lr); -``` - -My 14 year-old self would never forgive me if we didn't have sweet palette fades. - -```c -// Called for each delta performed in a fade on ncp. If anything but 0 is returned, -// the fading operation ceases immediately, and that value is propagated out. If provided -// and not NULL, the faders will not themselves call notcurses_render(). -typedef int (*fadecb)(struct notcurses* nc, struct ncplane* ncp, void* curry); - -// Fade the ncplane out over the provided time, calling the specified function -// when done. Requires a terminal which supports truecolor, or at least palette -// modification (if the terminal uses a palette, our ability to fade planes is -// limited, and affected by the complexity of the rest of the screen). It is -// not safe to resize or destroy the plane during the fadeout FIXME. -int ncplane_fadeout(struct ncplane* n, const struct timespec* ts, fadecb fader, void* curry); - -// Fade the ncplane in over the specified time. Load the ncplane with the -// target cells without rendering, then call this function. When it's done, the -// ncplane will have reached the target levels, starting from zeroes. -int ncplane_fadein(struct ncplane* n, const struct timespec* ts, fadecb fader, void* curry); - -// Pulse the plane in and out until the callback returns non-zero, relying on -// the callback 'fader' to initiate rendering. 'ts' defines the half-period -// (i.e. the transition from black to full brightness, or back again). Proper -// use involves preparing (but not rendering) an ncplane, then calling -// ncplane_pulse(), which will fade in from black to the specified colors. -int ncplane_pulse(struct ncplane* n, const struct timespec* ts, fadecb fader, void* curry); -``` - -Finally, a raw stream of RGBA or BGRx data can be blitted directly to an ncplane: - -```c -// Blit a flat array 'data' of BGRx 32-bit values to the ncplane 'nc', offset -// from the upper left by 'placey' and 'placex'. Each row ought occupy -// 'linesize' bytes (this might be greater than lenx * 4 due to padding). A -// subregion of the input can be specified with 'begy'x'begx' and 'leny'x'lenx'. -int ncblit_bgrx(struct ncplane* nc, int placey, int placex, int linesize, - const unsigned char* data, int begy, int begx, - int leny, int lenx); - -// Blit a flat array 'data' of RGBA 32-bit values to the ncplane 'nc', offset -// from the upper left by 'placey' and 'placex'. Each row ought occupy -// 'linesize' bytes (this might be greater than lenx * 4 due to padding). A -// subregion of the input can be specified with 'begy'x'begx' and 'leny'x'lenx'. -int ncblit_rgba(struct ncplane* nc, int placey, int placex, int linesize, - const unsigned char* data, int begy, int begx, - int leny, int lenx); -``` - -#### Plane channels API - -Helpers are provided to manipulate an `ncplane`'s `channels` member. They are -all implemented in terms of the lower-level [Channels API](#channels). - -```c -// Get the current channels or attribute word for ncplane 'n'. -uint64_t ncplane_channels(const struct ncplane* n); -uint32_t ncplane_attr(const struct ncplane* n); - -// Extract the 32-bit working background channel from an ncplane. -static inline unsigned -ncplane_bchannel(const struct ncplane* nc){ - return channels_bchannel(ncplane_channels(nc)); -} - -// Extract the 32-bit working foreground channel from an ncplane. -static inline unsigned -ncplane_fchannel(const struct ncplane* nc){ - return channels_fchannel(ncplane_channels(nc)); -} - -// Extract 24 bits of working foreground RGB from an ncplane, shifted to LSBs. -static inline unsigned -ncplane_fg(const struct ncplane* nc){ - return channels_fg(ncplane_channels(nc)); -} - -// Extract 24 bits of working background RGB from an ncplane, shifted to LSBs. -static inline unsigned -ncplane_bg(const struct ncplane* nc){ - return channels_bg(ncplane_channels(nc)); -} - -// Extract 2 bits of foreground alpha from 'struct ncplane', shifted to LSBs. -static inline unsigned -ncplane_fg_alpha(const struct ncplane* nc){ - return channels_fg_alpha(ncplane_channels(nc)); -} - -// Extract 2 bits of background alpha from 'struct ncplane', shifted to LSBs. -static inline unsigned -ncplane_bg_alpha(const struct ncplane* nc){ - return channels_bg_alpha(ncplane_channels(nc)); -} - -// Set the alpha parameters for ncplane 'n'. -int ncplane_set_fg_alpha(struct ncplane* n, int alpha); -int ncplane_set_bg_alpha(struct ncplane* n, int alpha); - -// Extract 24 bits of foreground RGB from 'n', split into subcomponents. -static inline unsigned -ncplane_fg_rgb(const struct ncplane* n, unsigned* r, unsigned* g, unsigned* - return channels_fg_rgb(ncplane_channels(n), r, g, b); -} - -// Extract 24 bits of background RGB from 'n', split into subcomponents. -static inline unsigned -ncplane_bg_rgb(const struct ncplane* n, unsigned* r, unsigned* g, unsigned* - return channels_bg_rgb(ncplane_channels(n), r, g, b); -} - -// Set the current fore/background color using RGB specifications. If the -// terminal does not support directly-specified 3x8b cells (24-bit "TrueColor", -// indicated by the "RGB" terminfo capability), the provided values will be -// interpreted in some lossy fashion. None of r, g, or b may exceed 255. -// "HP-like" terminals require setting foreground and background at the same -// time using "color pairs"; notcurses will manage color pairs transparently. -int ncplane_set_fg_rgb(struct ncplane* n, int r, int g, int b); -int ncplane_set_bg_rgb(struct ncplane* n, int r, int g, int b); - -// Same, but clipped to [0..255]. -void ncplane_set_bg_rgb_clipped(struct ncplane* n, int r, int g, int b); -void ncplane_set_fg_rgb_clipped(struct ncplane* n, int r, int g, int b); - -// Same, but with rgb assembled into a channel (i.e. lower 24 bits). -int ncplane_set_fg(struct ncplane* n, unsigned channel); -int ncplane_set_bg(struct ncplane* n, unsigned channel); - -// Use the default color for the foreground/background. -void ncplane_set_fg_default(struct ncplane* n); -void ncplane_set_bg_default(struct ncplane* n); - -int ncplane_set_fg_palindex(struct ncplane* n, int idx); -int ncplane_set_bg_palindex(struct ncplane* n, int idx); -``` - -### Cells - -Unlike the `notcurses` or `ncplane` objects, the definition of `cell` is -available to the user. It is somewhat ironic, then, that the user typically -needn't (and shouldn't) use `cell`s directly. Use a `cell` when the EGC being -output is used several times. In this case, time otherwise spent running -`cell_load()` (which tokenizes and verifies EGCs) can be saved. It can also be -useful to use a `cell` when the same styling is used in a discontinuous manner. - -```c -// A cell corresponds to a single character cell on some plane, which can be -// occupied by a single grapheme cluster (some root spacing glyph, along with -// possible combining characters, which might span multiple columns). At any -// cell, we can have a theoretically arbitrarily long UTF-8 string, a foreground -// color, a background color, and an attribute set. Valid grapheme cluster -// contents include: -// -// * A NUL terminator, -// * A single control character, followed by a NUL terminator, -// * At most one spacing character, followed by zero or more nonspacing -// characters, followed by a NUL terminator. -// -// Multi-column characters can only have a single style/color throughout. -// -// Each cell occupies 16 static bytes (128 bits). The surface is thus ~1.6MB -// for a (pretty large) 500x200 terminal. At 80x43, it's less than 64KB. -// Dynamic requirements can add up to 16MB to an ncplane, but such large pools -// are unlikely in common use. -// -// We implement some small alpha compositing. Foreground and background both -// have two bits of inverted alpha. The actual grapheme written to a cell is -// the topmost non-zero grapheme. If its alpha is 00, its foreground color is -// used unchanged. If its alpha is 10, its foreground color is derived entirely -// from cells underneath it. Otherwise, the result will be a composite. -// Likewise for the background. If the bottom of a coordinate's zbuffer is -// reached with a cumulative alpha of zero, the default is used. In this way, -// a terminal configured with transparent background can be supported through -// multiple occluding ncplanes. A foreground alpha of 11 requests high-contrast -// text (relative to the computed background). A background alpha of 11 is -// currently forbidden. -// -// Default color takes precedence over palette or RGB, and cannot be used with -// transparency. Indexed palette takes precedence over RGB. It cannot -// meaningfully set transparency, but it can be mixed into a cascading color. -// RGB is used if neither default terminal colors nor palette indexing are in -// play, and fully supports all transparency options. -typedef struct cell { - // These 32 bits are either a single-byte, single-character grapheme cluster - // (values 0--0x7f), or an offset into a per-ncplane attached pool of - // varying-length UTF-8 grapheme clusters. This pool may thus be up to 32MB. - uint32_t gcluster; // 4B -> 4B - // NCSTYLE_* attributes (16 bits) + 8 foreground palette index bits + 8 - // background palette index bits. palette index bits are used only if the - // corresponding default color bit *is not* set, and the corresponding - // palette index bit *is* set. - uint32_t attrword; // + 4B -> 8B - // (channels & 0x8000000000000000ull): left half of wide character - // (channels & 0x4000000000000000ull): foreground is *not* "default color" - // (channels & 0x3000000000000000ull): foreground alpha (2 bits) - // (channels & 0x0800000000000000ull): foreground uses palette index - // (channels & 0x0700000000000000ull): reserved, must be 0 - // (channels & 0x00ffffff00000000ull): foreground in 3x8 RGB (rrggbb) - // (channels & 0x0000000080000000ull): right half of wide character - // (channels & 0x0000000040000000ull): background is *not* "default color" - // (channels & 0x0000000030000000ull): background alpha (2 bits) - // (channels & 0x0000000008000000ull): background uses palette index - // (channels & 0x0000000007000000ull): reserved, must be 0 - // (channels & 0x0000000000ffffffull): background in 3x8 RGB (rrggbb) - // At render time, these 24-bit values are quantized down to terminal - // capabilities, if necessary. There's a clear path to 10-bit support should - // we one day need it, but keep things cagey for now. "default color" is - // best explained by color(3NCURSES). ours is the same concept. until the - // "not default color" bit is set, any color you load will be ignored. - uint64_t channels; // + 8B == 16B -} cell; - -#define CELL_WIDEASIAN_MASK 0x8000000080000000ull -#define CELL_BGDEFAULT_MASK 0x0000000040000000ull -#define CELL_FGDEFAULT_MASK (CELL_BGDEFAULT_MASK << 32u) -#define CELL_BG_MASK 0x0000000000ffffffull -#define CELL_FG_MASK (CELL_BG_MASK << 32u) -#define CELL_BG_PALETTE 0x0000000008000000ull -#define CELL_FG_PALETTE (CELL_BG_PALETTE << 32u) -#define CELL_ALPHA_MASK 0x0000000030000000ull -#define CELL_ALPHA_SHIFT 28u -#define CELL_ALPHA_HIGHCONTRAST 3 -#define CELL_ALPHA_TRANSPARENT 2 -#define CELL_ALPHA_BLEND 1 -#define CELL_ALPHA_OPAQUE 0 -``` - -`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_INITIALIZER(c, a, chan) { .gcluster = (c), .attrword = (a), .channels = (chan), } - -static inline void -cell_init(cell* c){ - memset(c, 0, sizeof(*c)); -} -``` - -A `cell` has three fundamental elements: - -* The EGC displayed at this coordinate, encoded in UTF-8. If the EGC is a - single ASCII character (value less than 0x80), it is stored inline in - the `cell`'s `gcluster` field. Otherwise, `gcluster`'s top 24 bits - are a 128-biased offset into the associated `ncplane`'s egcpool. This - implies that `cell`s are associated with `ncplane`s once prepared. -* The Curses-style attributes of the text. -* The 52 bits of foreground and background RGBA (2x8/8/8/2), plus a few flags. - -The EGC should be loaded using `cell_load()`. Either a single NUL-terminated -EGC can be provided, or a string composed of multiple EGCs. In the latter case, -the first EGC from the string is loaded. Remember, backing storage for the EGC -is provided by the `ncplane` passed to `cell_load()`; if this `ncplane` is -destroyed (or even erased), the `cell` cannot safely be used. If you're done -using the `cell` before being done with the `ncplane`, call `cell_release()` -to free up the EGC resources. - -```c -// Breaks the UTF-8 string in 'gcluster' down, setting up the cell 'c'. Returns -// the number of bytes copied out of 'gcluster', or -1 on failure. The styling -// of the cell is left untouched, but any resources are released. -int cell_load(struct ncplane* n, cell* c, const char* gcluster); - -// cell_load(), plus blast the styling with 'attr' and 'channels'. -static inline int -cell_prime(struct ncplane* n, cell* c, const char* gcluster, - uint32_t attr, uint64_t channels){ - c->attrword = attr; - c->channels = channels; - int ret = cell_load(n, c, gcluster); - return ret; -} - -// Duplicate 'c' into 'targ'. Not intended for external use; exposed for the -// benefit of unit tests. -int cell_duplicate(struct ncplane* n, cell* targ, const cell* c); - -// Release resources held by the cell 'c'. -void cell_release(struct ncplane* n, cell* c); - -#define NCSTYLE_MASK 0xffff0000ul -#define NCSTYLE_STANDOUT 0x00800000ul -#define NCSTYLE_UNDERLINE 0x00400000ul -#define NCSTYLE_REVERSE 0x00200000ul -#define NCSTYLE_BLINK 0x00100000ul -#define NCSTYLE_DIM 0x00080000ul -#define NCSTYLE_BOLD 0x00040000ul -#define NCSTYLE_INVIS 0x00020000ul -#define NCSTYLE_PROTECT 0x00010000ul -#define NCSTYLE_ITALIC 0x01000000ul - - -// Set the specified style bits for the cell 'c', whether they're actively -// supported or not. -static inline void -cell_styles_set(cell* c, unsigned stylebits){ - c->attrword = (c->attrword & ~NCSTYLE_MASK) | ((stylebits & NCSTYLE_MASK)); -} - -// Extract the style bits from the cell's attrword. -static inline unsigned -cell_styles(const cell* c){ - return c->attrword & NCSTYLE_MASK; -} - -// Add the specified styles (in the LSBs) to the cell's existing spec, whether -// they're actively supported or not. -static inline void -cell_styles_on(cell* c, unsigned stylebits){ - c->attrword |= (stylebits & NCSTYLE_MASK; -} - -// Remove the specified styles (in the LSBs) from the cell's existing spec. -static inline void -cell_styles_off(cell* c, unsigned stylebits){ - c->attrword &= ~(stylebits & NCSTYLE_MASK); -} - -// does the cell contain an East Asian Wide codepoint? -static inline bool -cell_double_wide_p(const cell* c){ - return (c->channels & CELL_WIDEASIAN_MASK); -} - -// is the cell simple (a lone ASCII character, encoded as such)? -static inline bool -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 -cell_egc_idx(const cell* c){ - return c->gcluster - 0x80; -} - -// return a pointer to the NUL-terminated EGC referenced by 'c'. this pointer -// is invalidated by any further operation on the plane 'n', so...watch out! -const char* cell_extended_gcluster(const struct ncplane* n, const cell* c); - -// load up six cells with the EGCs necessary to draw a box. returns 0 on -// success, -1 on error. on error, any cells this function might -// have loaded before the error are cell_release()d. There must be at least -// six EGCs in gcluster. -static inline int -cells_load_box(struct ncplane* n, uint32_t attrs, uint64_t channels, - cell* ul, cell* ur, cell* ll, cell* lr, - cell* hl, cell* vl, const char* gclusters){ - int ulen; - if((ulen = cell_prime(n, ul, gclusters, attrs, channels)) > 0){ - if((ulen = cell_prime(n, ur, gclusters += ulen, attrs, channels)) > 0){ - if((ulen = cell_prime(n, ll, gclusters += ulen, attrs, channels)) > 0){ - if((ulen = cell_prime(n, lr, gclusters += ulen, attrs, channels)) > 0){ - if((ulen = cell_prime(n, hl, gclusters += ulen, attrs, channels)) > 0){ - if((ulen = cell_prime(n, vl, gclusters += ulen, attrs, channels)) > 0){ - return 0; - } - cell_release(n, hl); - } - cell_release(n, lr); - } - cell_release(n, ll); - } - cell_release(n, ur); - } - cell_release(n, ul); - } - return -1; -} - - -static inline int -cells_rounded_box(struct ncplane* n, uint32_t attr, uint64_t channels, - cell* ul, cell* ur, cell* ll, cell* lr, cell* hl, cell* vl){ - return cells_load_box(n, attr, channels, ul, ur, ll, lr, hl, vl, "╭╮╰╯─│"); -} - -static inline int -cells_double_box(struct ncplane* n, uint32_t attr, uint64_t channels, - cell* ul, cell* ur, cell* ll, cell* lr, cell* hl, cell* vl){ - return cells_load_box(n, attr, channels, ul, ur, ll, lr, hl, vl, "╔╗╚╝═║"); -} -``` - -#### Cell channels API - -Helpers are provided to manipulate a `cell`'s `channels` member. They are all -implemented in terms of the lower-level [Channels API](#channels). - -```c -// Extract the 32-bit background channel from a cell. -static inline unsigned -cell_bchannel(const cell* cl){ - return channels_bchannel(cl->channels); -} - -// Extract the 32-bit foreground channel from a cell. -static inline unsigned -cell_fchannel(const cell* cl){ - return channels_fchannel(cl->channels); -} - -// Extract 24 bits of foreground RGB from 'cell', shifted to LSBs. -static inline unsigned -cell_fg(const cell* cl){ - return channels_fg(cl->channels); -} - -// Extract 24 bits of background RGB from 'cell', shifted to LSBs. -static inline unsigned -cell_bg(const cell* cl){ - return channels_bg(cl->channels); -} - -// Extract 2 bits of foreground alpha from 'cell', shifted to LSBs. -static inline unsigned -cell_fg_alpha(const cell* cl){ - return channels_fg_alpha(cl->channels); -} - -// Extract 2 bits of background alpha from 'cell', shifted to LSBs. -static inline unsigned -cell_bg_alpha(const cell* cl){ - return channels_bg_alpha(cl->channels); -} - -// Extract 24 bits of foreground RGB from 'cell', split into subcell. -static inline unsigned -cell_fg_rgb(const cell* cl, unsigned* r, unsigned* g, unsigned* b){ - return channels_fg_rgb(cl->channels, r, g, b); -} - -// Extract 24 bits of background RGB from 'cell', split into subcell. -static inline unsigned -cell_bg_rgb(const cell* cl, unsigned* r, unsigned* g, unsigned* b){ - return channels_bg_rgb(cl->channels, r, g, b); -} - -// Set the r, g, and b cell for the foreground component of this 64-bit -// 'cell' variable, and mark it as not using the default color. -static inline int -cell_set_fg_rgb(cell* cl, int r, int g, int b){ - return channels_set_fg_rgb(&cl->channels, r, g, b); -} - -// Same, but clipped to [0..255]. -static inline void -cell_set_fg_rgb_clipped(cell* cl, int r, int g, int b){ - channels_set_fg_rgb_clipped(&cl->channels, r, g, b); -} - -// Same, but with an assembled 24-bit RGB value. -static inline int -cell_set_fg(cell* c, uint32_t channel){ - return channels_set_fg(&c->channels, channel); -} - -// Set the r, g, and b cell for the background component of this 64-bit -// 'cell' variable, and mark it as not using the default color. -static inline int -cell_set_bg_rgb(cell* cl, int r, int g, int b){ - return channels_set_bg_rgb(&cl->channels, r, g, b); -} - -// Same, but clipped to [0..255]. -static inline void -cell_set_bg_rgb_clipped(cell* cl, int r, int g, int b){ - channels_set_bg_rgb_clipped(&cl->channels, r, g, b); -} - -// Same, but with an assembled 24-bit RGB value. -static inline int -cell_set_bg(cell* c, uint32_t channel){ - return channels_set_bg(&c->channels, channel); -} - -static inline int -cell_set_fg_alpha(cell* c, int alpha){ - return channels_set_fg_alpha(&c->channels, alpha); -} - -static inline int -cell_set_bg_alpha(cell* c, int alpha){ - return channels_set_bg_alpha(&c->channels, alpha); -} - -// Is the foreground using the "default foreground color"? -static inline bool -cell_fg_default_p(const cell* cl){ - return channels_fg_default_p(cl->channels); -} - -// Is the background using the "default background color"? The "default -// background color" must generally be used to take advantage of -// terminal-effected transparency. -static inline bool -cell_bg_default_p(const cell* cl){ - return channels_bg_default_p(cl->channels); -} - -// Use the default color for the foreground. -static inline void -cell_set_fg_default(cell* c){ - channels_set_fg_default(&c->channels); -} - -// Use the default color for the background. -static inline void -cell_set_bg_default(cell* c){ - channels_set_bg_default(&c->channels); -} - -``` - -### Reels - -ncreels are a complex UI abstraction offered by notcurses, derived from my -similar work in [outcurses](https://github.com/dankamongmen/ncreels#ncreels). - -The ncreel is a UI abstraction supported by notcurses in which -dynamically-created and -destroyed toplevel entities (referred to as tablets) -are arranged as if on a cylinder, allowing for infinite scrolling -(infinite scrolling can be disabled, resulting in a rectangle rather than a -cylinder). This works naturally with keyboard navigation, mouse scrolling wheels, -and touchpads (including the capacitive touchscreens of modern cell phones). -The term "reel" derives from slot machines. An ncreel initially has -no tablets; at any given time thereafter, it has zero or more tablets, and if -there is at least one tablet, one tablet is focused (and on-screen). If the -last tablet is removed, no tablet is focused. A tablet can support navigation -within the tablet, in which case there is an in-tablet focus for the focused -tablet, which can also move among elements within the tablet. - -The ncreel object tracks the size of the screen, the size, number, -information depth, and order of tablets, and the foci. It also draws the -optional borders around tablets and the optional border of the reel itself. It -knows nothing about the actual content of a tablet, save the number of lines it -occupies at each information depth. The typical control flow is that an -application receives events (from the UI or other event sources), and calls -into notcurses saying e.g. "Tablet 2 now has 40 valid lines of information". -notcurses might then call back into the application, asking it to draw some -line(s) from some tablet(s) at some particular coordinate of that tablet's -plane. Finally, control returns to the application, and the cycle starts anew. - -Each tablet might be wholly, partially, or not on-screen. notcurses always -places as much of the focused tablet as is possible on-screen (if the focused -tablet has more lines than the actual reel does, it cannot be wholly on-screen. -In this case, the focused subelements of the tablet are always on-screen). The -placement of the focused tablet depends on how it was reached (when moving to -the next tablet, offscreen tablets are brought onscreen at the bottom. When -moving to the previous tablet, offscreen tablets are brought onscreen at the -top. When moving to an arbitrary tablet which is neither the next nor previous -tablet, it will be placed in the center). - -The controlling application can, at any time, - -* Insert a new tablet somewhere in the reel (possibly off-screen) -* Delete a (possibly off-screen) tablet from the reel -* Change focus to the next or previous tablet, bringing it on-screen if it is off -* Change focus to some arbitrary other tablet, bringing it on-screen if it is off -* Expand or collapse the information depth of a tablet -* Change the content of a tablet, updating it if it is on-screen - * Remove content from a tablet, possibly resizing it, and possibly changing focus within the tablet - * Add content to the tablet, possibly resizing it, and possibly creating focus within the tablet -* Navigate within the focused tablet -* Create or destroy new planes atop the ncreel -* Indicate that the screen has been resized or needs be redrawn - -A special case arises when moving among the tablets of a reel having multiple -tablets, all of which fit entirely on-screen, and infinite scrolling is in use. -Normally, upon moving to the next tablet from the bottommost tablet, the -(offscreen) next tablet is pulled up into the bottom of the reel (the reverse -is true when moving to the previous tablet from the topmost). When all tablets -are onscreen with infinite scrolling, there are two possibilities: either the -focus scrolls (moving from the bottom tablet to the top tablet, for instance), -or the reel scrolls (preserving order among the tablets, but changing their -order on-screen). In this latter case, moving to the next tablet from the -bottommost tablet results in the tablet which is gaining focus being brought to -the bottom of the screen from the top, and all other tablets moving up on the -screen. Moving to the previous tablet from the topmost tablet results in the -bottommost tablet moving to the top of the screen, and all other tablets moving -down. This behavior matches the typical behavior precisely, and avoids a rude -UI discontinuity when the tablets grow to fill the entire screen (or shrink to -not fill it). If it is not desired, however, scrolling of focus can be -configured instead. - -```c -// An ncreel is a notcurses region devoted to displaying zero or more -// line-oriented, contained planes ("tablets") between which the user may -// navigate. If at least one tablet exists, there is an active tablet. As much -// of the active tablet as is possible is always displayed. If there is space -// left over, other tablets are included in the display. Tablets can come and go -// at any time, and can grow or shrink at any time. -// -// This structure is amenable to line- and page-based navigation via keystrokes, -// scrolling gestures, trackballs, scrollwheels, touchpads, and verbal commands. - -typedef struct ncreel_options { - // require this many rows and columns (including borders). otherwise, a - // message will be displayed stating that a larger terminal is necessary, and - // input will be queued. if 0, no minimum will be enforced. may not be - // negative. note that ncreel_create() does not return error if given a - // plane smaller than these minima; it instead patiently waits for the - // screen to get bigger. - int min_supported_cols; - int min_supported_rows; - - // use no more than this many rows and columns (including borders). may not be - // less than the corresponding minimum. 0 means no maximum. - int max_supported_cols; - int max_supported_rows; - - // desired offsets within the surrounding WINDOW (top right bottom left) upon - // creation / resize. an ncreel_move() operation updates these. - int toff, roff, boff, loff; - // is scrolling infinite (can one move down or up forever, or is an end - // reached?). if true, 'circular' specifies how to handle the special case of - // an incompletely-filled reel. - bool infinitescroll; - // is navigation circular (does moving down from the last tablet move to the - // first, and vice versa)? only meaningful when infinitescroll is true. if - // infinitescroll is false, this must be false. - bool circular; - // notcurses can draw a border around the ncreel, and also around the - // component tablets. inhibit borders by setting all valid bits in the masks. - // partially inhibit borders by setting individual bits in the masks. the - // appropriate attr and pair values will be used to style the borders. - // focused and non-focused tablets can have different styles. you can instead - // draw your own borders, or forgo borders entirely. - unsigned bordermask; // bitfield; 1s will not be drawn (see bordermaskbits) - uint64_t borderchan; // attributes used for ncreel border - unsigned tabletmask; // bitfield; same as bordermask but for tablet borders - uint64_t tabletchan; // tablet border styling channel - uint64_t focusedchan;// focused tablet border styling channel - uint64_t bgchannel; // background colors -} ncreel_options; - -struct nctablet; -struct ncreel; - -// Create an ncreel according to the provided specifications. Returns NULL on -// failure. 'nc' must be a valid plane, to which offsets are relative. Note that -// there might not be enough room for the specified offsets, in which case the -// ncreel will be clipped on the bottom and right. A minimum number of rows -// and columns can be enforced via popts. efd, if non-negative, is an eventfd -// that ought be written to whenever ncreel_touch() updates a tablet (this -// is useful in the case of nonblocking input). -struct ncreel* ncreel_create(struct ncplane* nc, const ncreel_options* popts, int efd); - -// Returns the ncplane on which this ncreel lives. -struct ncplane* ncreel_plane(struct ncreel* pr); - -// Tablet draw callback, provided a tablet (from which the ncplane and userptr -// may be extracted), the first column that may be used, the first row that may -// be used, the first column that may not be used, the first row that may not -// be used, and a bool indicating whether output ought be clipped at the top -// (true) or bottom (false). Rows and columns are zero-indexed, and both are -// relative to the tablet's plane. -// -// Regarding clipping: it is possible that the tablet is only partially -// displayed on the screen. If so, it is either partially present on the top of -// the screen, or partially present at the bottom. In the former case, the top -// is clipped (cliptop will be true), and output ought start from the end. In -// the latter case, cliptop is false, and output ought start from the beginning. -// -// Returns the number of lines of output, which ought be less than or equal to -// maxy - begy, and non-negative (negative values might be used in the future). -typedef int (*tabletcb)(struct nctablet* t, int begx, int begy, int maxx, - int maxy, bool cliptop); - -// Add a new tablet to the provided ncreel, having the callback object -// opaque. Neither, either, or both of after and before may be specified. If -// neither is specified, the new tablet can be added anywhere on the reel. If -// one or the other is specified, the tablet will be added before or after the -// specified tablet. If both are specified, the tablet will be added to the -// resulting location, assuming it is valid (after->next == before->prev); if -// it is not valid, or there is any other error, NULL will be returned. -struct nctablet* ncreel_add(struct ncreel* pr, struct nctablet* after, - struct nctablet* before, tabletcb cb, void* opaque); - -// Return the number of tablets. -int ncreel_tabletcount(const struct ncreel* pr); - -// Indicate that the specified tablet has been updated in a way that would -// change its display. This will trigger some non-negative number of callbacks -// (though not in the caller's context). -int ncreel_touch(struct ncreel* pr, struct nctablet* t); - -// Delete the tablet specified by t from the ncreel specified by pr. Returns -// -1 if the tablet cannot be found. -int ncreel_del(struct ncreel* pr, struct nctablet* t); - -// Delete the active tablet. Returns -1 if there are no tablets. -int ncreel_del_focused(struct ncreel* pr); - -// Move to the specified location within the containing plane. -int ncreel_move(struct ncreel* pr, int x, int y); - -// Redraw the ncreel in its entirety, for instance after -// clearing the screen due to external corruption, or a SIGWINCH. -int ncreel_redraw(struct ncreel* pr); - -// Return the focused tablet, if any tablets are present. This is not a copy; -// be careful to use it only for the duration of a critical section. -struct nctablet* ncreel_focused(struct ncreel* pr); - -// Change focus to the next tablet, if one exists -struct nctablet* ncreel_next(struct ncreel* pr); - -// Change focus to the previous tablet, if one exists -struct nctablet* ncreel_prev(struct ncreel* pr); - -// Destroy an ncreel allocated with ncreel_create(). Does not destroy the -// underlying plane. Returns non-zero on failure. -int ncreel_destroy(struct ncreel* pr); - -// Returns a pointer to a user pointer associated with this nctablet. -void* nctablet_userptr(struct nctablet* t); - -// Access the ncplane associated with this tablet, if one exists. -struct ncplane* nctablet_ncplane(struct nctablet* t); -``` - -#### ncreel examples - -Let's say we have a screen of 11 lines, and 3 tablets of one line each. Both -a screen border and tablet borders are in use. The tablets are A, B, and C. -No gap is in use between tablets. Xs indicate focus. If B currently has focus, -and the next tablet is selected, the result would be something like: - -``` - ------------- ------------- - | --------- | | --------- | - | | A | | | | A | | - | --------- | | --------- | - | --------- | ---- "next tablet" ---> | --------- | - | |XX B XX| | | | B | | - | --------- | | --------- | - | --------- | | --------- | - | | C | | | |XX C XX| | - | --------- | | --------- | - ------------- ------------- -``` - -If instead the previous tablet had been selected, we would of course get: - -``` - ------------- ------------- - | --------- | | --------- | - | | A | | | |XX A XX| | - | --------- | | --------- | - | --------- | ---- "prev tablet" ---> | --------- | - | |XX B XX| | | | B | | - | --------- | | --------- | - | --------- | | --------- | - | | C | | | | C | | - | --------- | | --------- | - ------------- ------------- -``` - -If A instead has the focus, choosing the "next tablet" is trivial: the tablets -do not change, and focus shifts to B. If we choose the "previous tablet", there -are three possibilities: - -* Finite scrolling: No change. The tablets stay in place. A remains focused. - -``` - ------------- ------------- - | --------- | | --------- | - | |XX A XX| | | |XX A XX| | - | --------- | | --------- | - | --------- | ---- "prev tablet" ---> | --------- | - | | B | | (finite scroll) | | B | | - | --------- | | --------- | - | --------- | | --------- | - | | C | | | | C | | - | --------- | | --------- | - ------------- ------------- -``` - -* Infinite scrolling with rotation: Focus shifts to C, which moves to the top: - -``` - ------------- ------------- - | --------- | | --------- | - | |XX A XX| | | |XX C XX| | - | --------- | | --------- | - | --------- | ---- "prev tablet" ---> | --------- | - | | B | | (infinite scroll with | | A | | - | --------- | rotation) | --------- | - | --------- | | --------- | - | | C | | | | B | | - | --------- | | --------- | - ------------- ------------- -``` - -* Infinite scrolling with focus rotation: Focus shifts to C, and moves to the bottom: - -``` - ------------- ------------- - | --------- | | --------- | - | |XX A XX| | | | A | | - | --------- | | --------- | - | --------- | ---- "prev tablet" ---> | --------- | - | | B | | (infinite scroll with | | B | | - | --------- | focus rotation) | --------- | - | --------- | | --------- | - | | C | | | |XX C XX| | - | --------- | | --------- | - ------------- ------------- -``` - -Now imagine us to have the same 3 tablets, but each is now 4 lines. It is -impossible to have two of these tablets wholly onscreen at once, let alone all -three. If we started with A focused and at the top, the result after all three -tablets have grown will be: - -``` - ------------- ------------- - | --------- | | --------- | A remains at the top, and - | |XX A XX| | | |XXXXXXX| | is wholly on-screen. B is - | --------- | | |XX A XX| | below it, but we can show - | --------- | ---- "grow tablet" ---> | |XXXXXXX| | only the first two lines. - | | B | | A (focused) | |XXXXXXX| | C has been pushed - | --------- | | --------- | off-screen. - | --------- | | --------- | - | | C | | | | | | - | --------- | | | B | | - ------------- ------------- -``` - -When a tablet is enlarged, it grows towards the nearest boundary, unless that -would result in the focused tablet being moved, in which case the growing -tablet instead grows in the other direction (if the tablet is in the middle -of the screen exactly, it grows down). There is one exception to this rule: if -the tablets are not making full use of the screen, growth is always down (the -screen is always filled from the top), even if it moves the focused tablet. - -A 12-line screen has three tablets: A (2 lines), B (1 line), C (1 line), filling -the screen exactly. B is focused, and grows two lines: - -``` - ------------- ------------- - | --------- | | --------- | B grows down, since it is - | | A | | | | A | | closer to the bottom (3 - | | | | | | | | lines) than the top (4 - | --------- | ---- "grow tablet" ---> | --------- | lines). C is pushed almost - | --------- | B (focused) | --------- | entirely off-screen. A is - | |XX B XX| | | |XXXXXXX| | untouched. - | --------- | | |XX B XX| | - | --------- | | |XXXXXXX| | - | | C | | | --------- | - | --------- | | --------- | - ------------- ------------- -``` - -Starting with the same situation, A grows by 2 lines instead: - -``` - ------------- ------------- - | --------- | | | | | A grows up. It would have - | | A | | | | A | | grown down, but that would - | | | | | | | | have moved B, which has - | --------- | ---- "grow tablet" ---> | --------- | the focus. B and C remain - | --------- | A (not focused) | --------- | where they are; A moves - | |XX B XX| | | |XX B XX| | partially off-screen. - | --------- | | --------- | - | --------- | | --------- | - | | C | | | | C | | - | --------- | | --------- | - ------------- ------------- -``` - -If we started with the same situation, and B grew by 7 lines, it would first -push C entirely off-screen (B would then have four lines of text), and then -push A off-screen. B would then have eight lines of text, the maximum on a -12-line screen with both types of borders. - -### Widgets - -Selectors: - -``` - ╭──────────────────────────╮ - │This is the primary header│ -╭──────────────────────this is the secondary header──────╮ -│ │ -│ option1 Long text #1 │ -│ option2 Long text #2 │ -│ option3 Long text #3 │ -│ option4 Long text #4 │ -│ option5 Long text #5 │ -│ option6 Long text #6 │ -│ │ -╰────────────────────────────────────here's the footer───╯ -``` - -Multiselectors: - -``` - ╭────────────────────────────────────────────────────────────────╮ - │ this is truly an awfully long example of a MULTISELECTOR title │ -╭─────┴─────────────────────────────pick one (you will die regardless)─┤ -│ ↑ │ -│ ☐ 1 Across the Atlantic Ocean, there was a place called North America│ -│ ☐ 2 Discovered by an Italian in the employ of the queen of Spain │ -│ ☒ 3 Colonized extensively by the Spanish and the French │ -│ ☐ 4 Developed into a rich nation by Dutch-supplied African slaves │ -│ ☐ 5 And thus became the largest English-speaking nation on earth │ -│ ☐ 6 Namely, the United States of America │ -│ ☐ 7 The inhabitants of the United States called themselves Yankees │ -│ ☒ 8 For some reason │ -│ ☐ 9 And, eventually noticing the rest of the world was there, │ -│ ☐ 10 Decided to rule it. │ -│ ↓ │ -╰─────────────────────────press q to exit (there is sartrev("no exit")─╯ -``` - -Menus: - -``` - Schwarzgerät File Help -xxxxxxxxxxxxxxxx╭─────────────╮xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -xxxxxxxxxxxxxxxx│New Ctrl+n│xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -xxxxxxxxxxxxxxxx│Open Ctrl+o│xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -xxxxxxxxxxxxxxxx│Close Ctrl+c│xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -xxxxxxxxxxxxxxxx├─────────────┤xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -xxxxxxxxxxxxxxxx│Quit Ctrl+q│xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -xxxxxxxxxxxxxxxx╰─────────────╯xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -``` - -### Channels - -A channel encodes 24 bits of RGB color, using 8 bits for each component. It -additionally provides 2 bits of alpha channel, a bit for selecting terminal -default colors, and a bit to indicate whether it describes a Wide East Asian -character. The remaining four bits are reserved. Typically two channels are -bound together in a 64-bit unsigned integer (`uint64_t`), with eight bits -currently going unused. There is such a double-channel in every `cell` and -`ncplane` object. - -Usually, the higher-level `ncplane` and `cell` functionality ought be used. It -will sometimes be necessary, however, to muck with channels at their lowest -level. The channel API facilitates such muckery. All channel-related `ncplane` -and `cell` functionality is implemented in terms of this API. - -```c -// Extract the 8-bit red component from a 32-bit channel. -static inline unsigned -channel_r(unsigned channel){ - return (channel & 0xff0000u) >> 16u; -} - -// Extract the 8-bit green component from a 32-bit channel. -static inline unsigned -channel_g(unsigned channel){ - return (channel & 0x00ff00u) >> 8u; -} - -// Extract the 8-bit blue component from a 32-bit channel. -static inline unsigned -channel_b(unsigned channel){ - return (channel & 0x0000ffu); -} - -// Extract the three 8-bit R/G/B components from a 32-bit channel. -static inline unsigned -channel_rgb(unsigned channel, unsigned* r, unsigned* g, unsigned* b){ - *r = channel_r(channel); - *g = channel_g(channel); - *b = channel_b(channel); - return channel; -} - -// Set the three 8-bit components of a 32-bit channel, and mark it as not using -// the default color. Retain the other bits unchanged. -static inline int -channel_set_rgb(unsigned* channel, int r, int g, int b){ - if(r >= 256 || g >= 256 || b >= 256){ - return -1; - } - if(r < 0 || g < 0 || b < 0){ - return -1; - } - unsigned c = (r << 16u) | (g << 8u) | b; - c |= CELL_BGDEFAULT_MASK; - const uint64_t mask = CELL_BGDEFAULT_MASK | CELL_BG_MASK; - *channel = (*channel & ~mask) | c; - return 0; -} - -// Same, but provide an assembled, packed 24 bits of rgb. -static inline int -channel_set(unsigned* channel, unsigned rgb){ - if(rgb > 0xffffffu){ - return -1; - } - *channel = (*channel & ~CELL_BG_MASK) | CELL_BGDEFAULT_MASK | rgb; - return 0; -} - -// Extract the 2-bit alpha component from a 32-bit channel. -static inline unsigned -channel_alpha(unsigned channel){ - return (channel & CELL_ALPHA_MASK) >> CELL_ALPHA_SHIFT; -} - -// Set the 2-bit alpha component of the 32-bit channel. -static inline int -channel_set_alpha(unsigned* channel, int alpha){ - if(alpha < CELL_ALPHA_OPAQUE || alpha > CELL_ALPHA_TRANS){ - return -1; - } - *channel = (alpha << CELL_ALPHA_SHIFT) | (*channel & ~CELL_ALPHA_MASK); - return 0; -} - -// Is this channel using the "default color" rather than its RGB? -static inline bool -channel_default_p(unsigned channel){ - return !(channel & CELL_BGDEFAULT_MASK); -} - -// Mark the channel as using its default color. -static inline unsigned -channel_set_default(unsigned* channel){ - return *channel &= ~CELL_BGDEFAULT_MASK; -} - -// Extract the 32-bit background channel from a channel pair. -static inline unsigned -channels_bchannel(uint64_t channels){ - return channels & 0xfffffffflu; -} - -// Extract the 32-bit foreground channel from a channel pair. -static inline unsigned -channels_fchannel(uint64_t channels){ - return channels_bchannel(channels >> 32u); -} - -// Extract 24 bits of foreground RGB from 'channels', shifted to LSBs. -static inline unsigned -channels_fg(uint64_t channels){ - return channels_fchannel(channels) & CELL_BG_MASK; -} - -// Extract 24 bits of background RGB from 'channels', shifted to LSBs. -static inline unsigned -channels_bg(uint64_t channels){ - return channels_bchannel(channels) & CELL_BG_MASK; -} - -// Extract 2 bits of foreground alpha from 'channels', shifted to LSBs. -static inline unsigned -channels_fg_alpha(uint64_t channels){ - return channel_alpha(channels_fchannel(channels)); -} - -// Extract 2 bits of background alpha from 'channels', shifted to LSBs. -static inline unsigned -channels_bg_alpha(uint64_t channels){ - return channel_alpha(channels_bchannel(channels)); -} - -// Extract 24 bits of foreground RGB from 'channels', split into subchannels. -static inline unsigned -channels_fg_rgb(uint64_t channels, unsigned* r, unsigned* g, unsigned* b){ - return channel_rgb(channels_fchannel(channels), r, g, b); -} - -// Extract 24 bits of background RGB from 'channels', split into subchannels. -static inline unsigned -channels_bg_rgb(uint64_t channels, unsigned* r, unsigned* g, unsigned* b){ - return channel_rgb(channels_bchannel(channels), r, g, b); -} - -// Set the r, g, and b channels for the foreground component of this 64-bit -// 'channels' variable, and mark it as not using the default color. -static inline int -channels_set_fg_rgb(uint64_t* channels, int r, int g, int b){ - unsigned channel = channels_fchannel(*channels); - if(channel_set_rgb(&channel, r, g, b) < 0){ - return -1; - } - *channels = ((uint64_t)channel << 32llu) | (*channels & 0xffffffffllu); - return 0; -} - -// Set the r, g, and b channels for the background component of this 64-bit -// 'channels' variable, and mark it as not using the default color. -static inline int -channels_set_bg_rgb(uint64_t* channels, int r, int g, int b){ - unsigned channel = channels_bchannel(*channels); - if(channel_set_rgb(&channel, r, g, b) < 0){ - return -1; - } - *channels = (*channels & 0xffffffff00000000llu) | channel; - return 0; -} - -// Same, but set an assembled 32 bit channel at once. -static inline int -channels_set_fg(uint64_t* channels, unsigned rgb){ - unsigned channel = channels_fchannel(*channels); - if(channel_set(&channel, rgb) < 0){ - return -1; - } - *channels = ((uint64_t)channel << 32llu) | (*channels & 0xffffffffllu); - return 0; -} - -static inline int -channels_set_bg(uint64_t* channels, unsigned rgb){ - unsigned channel = channels_bchannel(*channels); - if(channel_set(&channel, rgb) < 0){ - return -1; - } - *channels = (*channels & 0xffffffff00000000llu) | channel; - return 0; -} - -// Set the 2-bit alpha component of the foreground channel. -static inline int -channels_set_fg_alpha(uint64_t* channels, int alpha){ - unsigned channel = channels_fchannel(*channels); - if(channel_set_alpha(&channel, alpha) < 0){ - return -1; - } - *channels = ((uint64_t)channel << 32llu) | (*channels & 0xffffffffllu); - return 0; -} - -// Set the 2-bit alpha component of the background channel. -static inline int -channels_set_bg_alpha(uint64_t* channels, int alpha){ - if(alpha == CELL_ALPHA_HIGHCONTRAST){ // forbidden for background alpha - return -1; - } - unsigned channel = channels_bchannel(*channels); - if(channel_set_alpha(&channel, alpha) < 0){ - return -1; - } - *channels = (*channels & 0xffffffff00000000llu) | channel; - return 0; -} - -// Is the foreground using the "default foreground color"? -static inline bool -channels_fg_default_p(uint64_t channels){ - return channel_default_p(channels_fchannel(channels)); -} - -// Is the background using the "default background color"? The "default -// background color" must generally be used to take advantage of -// terminal-effected transparency. -static inline bool -channels_bg_default_p(uint64_t channels){ - return channel_default_p(channels_bchannel(channels)); -} - -// Mark the foreground channel as using its default color. -static inline uint64_t -channels_set_fg_default(uint64_t* channels){ - unsigned channel = channels_fchannel(*channels); - channel_set_default(&channel); - *channels = ((uint64_t)channel << 32llu) | (*channels & 0xffffffffllu); - return *channels; -} - -// Mark the foreground channel as using its default color. -static inline uint64_t -channels_set_bg_default(uint64_t* channels){ - unsigned channel = channels_bchannel(*channels); - channel_set_default(&channel); - *channels = (*channels & 0xffffffff00000000llu) | channel; - return *channels; -} -``` - ## Included tools Six binaries are installed as part of notcurses: diff --git a/USAGE.md b/USAGE.md new file mode 100644 index 000000000..88f3e058c --- /dev/null +++ b/USAGE.md @@ -0,0 +1,2325 @@ +# Usage + +A full API reference [is available](https://nick-black.com/notcurses/). Manual +pages ought have been installed along with notcurses. This document is a +secondary reference, and should not be considered authoritative. For a more +unified commentary, consider the [paperback](https://www.amazon.com/dp/B086PNVNC9) +(also available as a free PDF from https://nick-black.com). + +A program wishing to use Notcurses will need to link it, ideally using the +output of `pkg-config --libs notcurses`. It is advised to compile with the +output of `pkg-config --cflags notcurses`. If using CMake, a support file is +provided, and can be accessed as `notcurses`. + +Before calling into Notcurses—and usually as one of the first calls of the +program—be sure to call `setlocale(3)` with an appropriate UTF-8 locale. It is +usually appropriate to use `setlocale(LC_ALL, "")`, relying on the user to +properly set the `LANG` environment variable. Notcurses will refuse to start if +`nl_langinfo(3)` doesn't indicate `ANSI_X3.4-1968` or `UTF-8`. In addition, it +is wise to mask most signals early in the program, before any threads are +spawned (this is particularly critical for `SIGWINCH`). + +Notcurses requires an available `terminfo(5)` definition appropriate for the +terminal. It is usually appropriate to pass `NULL` in the `termtype` field of a +`notcurses_options` struct, relying on the user to properly set the `TERM` +environment variable. This variable is usually set by the terminal itself. It +might be necessary to manually select a higher-quality definition for your +terminal, i.e. `xterm-direct` as opposed to `xterm` or `xterm-256color`. + +Each terminal can be prepared via a call to `notcurses_init()`, which is +supplied a struct of type `notcurses_options`: + +```c +// Get a human-readable string describing the running Notcurses version. +const char* notcurses_version(void); + +struct cell; // a coordinate on an ncplane: an EGC plus styling +struct ncplane; // a drawable Notcurses surface, composed of cells +struct notcurses; // Notcurses state for a given terminal, composed of ncplanes + +// These log levels consciously map cleanly to those of libav; Notcurses itself +// does not use this full granularity. The log level does not affect the opening +// and closing banners, which can be disabled via the notcurses_option struct's +// 'suppress_banner'. Note that if stderr is connected to the same terminal on +// which we're rendering, any kind of logging will disrupt the output. +typedef enum { + NCLOGLEVEL_SILENT, // default. print nothing once fullscreen service begins + NCLOGLEVEL_PANIC, // print diagnostics immediately related to crashing + NCLOGLEVEL_FATAL, // we're hanging around, but we've had a horrible fault + NCLOGLEVEL_ERROR, // we can't keep doin' this, but we can do other things + NCLOGLEVEL_WARNING, // you probably don't want what's happening to happen + NCLOGLEVEL_INFO, // "standard information" + NCLOGLEVEL_VERBOSE, // "detailed information" + NCLOGLEVEL_DEBUG, // this is honestly a bit much + NCLOGLEVEL_TRACE, // there's probably a better way to do what you want +} ncloglevel_e; + +// Configuration for notcurses_init(). +typedef struct notcurses_options { + // The name of the terminfo database entry describing this terminal. If NULL, + // the environment variable TERM is used. Failure to open the terminal + // definition will result in failure to initialize notcurses. + const char* termtype; + // If smcup/rmcup capabilities are indicated, notcurses defaults to making + // use of the "alternate screen". This flag inhibits use of smcup/rmcup. + bool inhibit_alternate_screen; + // By default, we hide the cursor if possible. This flag inhibits use of + // the civis capability, retaining the cursor. + bool retain_cursor; + // We typically install a signal handler for SIGINT and SIGQUIT that restores + // the screen, and then calls the old signal handler. Set this to inhibit + // registration of any signal handlers. + bool no_quit_sighandlers; + // We typically install a signal handler for SIGWINCH that generates a resize + // event in the notcurses_getc() queue. Set this to inhibit the handler. + bool no_winch_sighandler; + // Notcurses typically prints version info in notcurses_init() and + // performance info in notcurses_stop(). This inhibits that output. + bool suppress_banner; + // If non-NULL, notcurses_render() will write each rendered frame to this + // FILE* in addition to outfp. This is used primarily for debugging. + FILE* renderfp; + // Progressively higher log levels result in more logging to stderr. By + // default, nothing is printed to stderr once fullscreen service begins. + ncloglevel_e loglevel; + // Desirable margins. If all are 0 (default), we will render to the entirety + // of the screen. If the screen is too small, we do what we can--this is + // strictly best-effort. Absolute coordinates are relative to the rendering + // area ((0, 0) is always the origin of the rendering area). + int margin_t, margin_r, margin_b, margin_l; +} notcurses_options; + +// Initialize a notcurses context on the connected terminal at 'fp'. 'fp' must +// be a tty. You'll usually want stdout. Returns NULL on error, including any +// failure initializing terminfo. +struct notcurses* notcurses_init(const notcurses_options* opts, FILE* fp); + +// Destroy a notcurses context. +int notcurses_stop(struct notcurses* nc); +``` + +`notcurses_stop` should be called before exiting your program to restore the +terminal settings and free resources. + +notcurses does not typically generate diagnostics (aside from the intro banner +and outro performance summary). When `stderr` is connected to the same terminal +to which graphics are being printed, printing to stderr will corrupt the output. +Setting `loglevel` to a value higher than `NCLOGLEVEL_SILENT` will cause +diagnostics to be printed to `stderr`: you could ensure `stderr` is redirected +if you make use of this functionality. + +It's probably wise to export `inhibit_alternate_screen` to the user (e.g. via +command line option or environment variable). Developers and motivated users +might appreciate the ability to manipulate `loglevel` and `renderfp`. The +remaining options are typically of use only to application authors. + +The notcurses API draws almost entirely into the virtual buffers of `ncplane`s. +Only upon a call to `notcurses_render` will the visible terminal display be +updated to reflect the changes: + +```c +// Make the physical screen match the virtual screen. Changes made to the +// virtual screen (i.e. most other calls) will not be visible until after a +// successful call to notcurses_render(). +int notcurses_render(struct notcurses* nc); + +// Retrieve the contents of the specified cell as last rendered. The EGC is +// returned, or NULL on error. This EGC must be free()d by the caller. The +// attrword and channels are written to 'attrword' and 'channels', respectively. +char* notcurses_at_yx(struct notcurses* nc, int yoff, int xoff, + uint32_t* attrword, uint64_t* channels); +``` + +One `ncplane` is guaranteed to exist: the "standard plane". The user cannot +move, resize, reparent, or destroy the standard plane (it *can* be erased). +Its dimensions always match notcurses's conception of the visible terminal. A +handle on the standard plane can be acquired with two top-level functions: + +```c +// Get a reference to the standard plane (one matching our current idea of the +// terminal size) for this terminal. The standard plane always exists, and its +// origin is always at the uppermost, leftmost cell of the screen. +struct ncplane* notcurses_stdplane(struct notcurses* nc); +const struct ncplane* notcurses_stdplane_const(const struct notcurses* nc); + +// notcurses_stdplane(), plus free bonus dimensions written to non-NULL y/x! +static inline struct ncplane* +notcurses_stddim_yx(struct notcurses* nc, int* restrict y, int* restrict x){ + struct ncplane* s = notcurses_stdplane(nc); // can't fail + ncplane_dim_yx(s, y, x); // accepts NULL + return s; +} +``` + +A reference to the standard plane *is* persistent across a screen resize, as are +any indexes into its egcpool, but its framebuffer *is not* necessarily +persistent across a screen resize. Thankfully, you shouldn't have a reference +to its framebuffer, and thus only the change to its dimensions can really catch +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); + +// Return our current idea of the terminal dimensions in rows and cols. +static inline void +notcurses_term_dim_yx(const struct notcurses* n, int* restrict rows, + int* restrict cols){ + ncplane_dim_yx(notcurses_stdplane_const(n), rows, cols); +} + +// Refresh the physical screen to match what was last rendered (i.e., without +// reflecting any changes since the last call to notcurses_render()). This is +// primarily useful if the screen is externally corrupted, or if an +// NCKEY_RESIZE event has been read and you're not ready to render. +int notcurses_refresh(struct notcurses* n, int* restrict y, int* restrict x); + +// Returns a 16-bit bitmask in the LSBs of supported curses-style attributes +// (NCSTYLE_UNDERLINE, NCSTYLE_BOLD, etc.) The attribute is only +// indicated as supported if the terminal can support it together with color. +// For more information, see the "ncv" capability in terminfo(5). +unsigned notcurses_supported_styles(const struct notcurses* nc); + +// Returns the number of simultaneous colors claimed to be supported, or 1 if +// there is no color support. Note that several terminal emulators advertise +// more colors than they actually support, downsampling internally. +int notcurses_palette_size(const struct notcurses* nc); + +// Can we fade? Fading requires either the "rgb" or "ccc" terminfo capability. +bool notcurses_canfade(const struct notcurses* nc); + +// Can we load images/videos? This requires being built against FFmpeg. +bool notcurses_canopen(const struct notcurses* nc); + +// Can we change colors in the hardware palette? Requires "ccc" and "initc". +bool notcurses_canchangecolors(const struct notcurses* nc); +``` + +## Direct mode + +"Direct mode" makes a limited subset of notcurses is available for manipulating +typical scrolling or file-backed output. These functions output directly and +immediately to the provided `FILE*`, and `notcurses_render()` is neither +supported nor necessary for such an instance. Use `ncdirect_init()` to create a +direct mode context: + +```c +struct ncdirect; // minimal state for a terminal + +// Initialize a direct-mode notcurses context on the connected terminal at 'fp'. +// 'fp' must be a tty. You'll usually want stdout. Direct mode supportes a +// limited subset of notcurses routines which directly affect 'fp', and neither +// supports nor requires notcurses_render(). This can be used to add color and +// styling to text in the standard output paradigm. Returns NULL on error, +// including any failure initializing terminfo. +struct ncdirect* ncdirect_init(const char* termtype, FILE* fp); + +// Release 'nc' and any associated resources. 0 on success, non-0 on failure. +int ncdirect_stop(struct ncdirect* nc); +``` + +This context must be destroyed using `ncdirect_stop()`. The following functions +are available for direct mode: + +```c +int ncdirect_fg(struct ncdirect* nc, unsigned rgb); +int ncdirect_bg(struct ncdirect* nc, unsigned rgb); + +static inline int +ncdirect_bg_rgb8(struct ncdirect* nc, unsigned r, unsigned g, unsigned b){ + if(r > 255 || g > 255 || b > 255){ + return -1; + } + return ncdirect_bg(nc, (r << 16u) + (g << 8u) + b); +} + +static inline int +ncdirect_fg_rgb8(struct ncdirect* nc, unsigned r, unsigned g, unsigned b){ + if(r > 255 || g > 255 || b > 255){ + return -1; + } + return ncdirect_fg(nc, (r << 16u) + (g << 8u) + b); +} + +// Get the current number of columns/rows. +int ncdirect_dim_x(const struct ncdirect* nc); +int ncdirect_dim_y(const struct ncdirect* nc); + +int ncdirect_fg_default(struct ncdirect* nc); +int ncdirect_bg_default(struct ncdirect* nc); +int ncdirect_styles_set(struct ncdirect* n, unsigned stylebits); +int ncdirect_styles_on(struct ncdirect* n, unsigned stylebits); +int ncdirect_styles_off(struct ncdirect* n, unsigned stylebits); +int ncdirect_clear(struct ncdirect* nc); // clear the screen + +// Move the cursor in direct mode. -1 to retain current location on that axis. +int ncdirect_cursor_move_yx(struct ncdirect* n, int y, int x); +int ncdirect_cursor_enable(struct ncdirect* nc); +int ncdirect_cursor_disable(struct ncdirect* nc); + +// Relative moves. num < 0 is an error. +int ncdirect_cursor_up(struct ncdirect* nc, int num); +int ncdirect_cursor_left(struct ncdirect* nc, int num); +int ncdirect_cursor_right(struct ncdirect* nc, int num); +int ncdirect_cursor_down(struct ncdirect* nc, int num); +``` + +## Alignment + +Most functions that generate output can be aligned relative to an ncplane. +Alignment currently comes in three forms: `NCALIGN_LEFT`, `NCALIGN_CENTER`, and +`NCALIGN_RIGHT`. + +```c +// Alignment within the ncplane. Left/right-justified, or centered. +typedef enum { + NCALIGN_LEFT, + NCALIGN_CENTER, + NCALIGN_RIGHT, +} ncalign_e; + +// Return the column at which 'c' cols ought start in order to be aligned +// according to 'align' within ncplane 'n'. Returns INT_MAX on invalid 'align'. +// Undefined behavior on negative 'c'. +static inline int +ncplane_align(const struct ncplane* n, ncalign_e align, int c){ + if(align == NCALIGN_LEFT){ + return 0; + } + int cols; + ncplane_dim_yx(n, NULL, &cols); + if(align == NCALIGN_CENTER){ + return (cols - c) / 2; + }else if(align == NCALIGN_RIGHT){ + return cols - c; + } + return INT_MAX; +} +``` + +## Input + +Input can currently be taken only from `stdin`, but on the plus side, stdin +needn't be a terminal device (unlike the ttyfp `FILE*` passed to `notcurses_init()`). +Generalized input ought happen soon. There is only one input queue per `struct +notcurses`. + +Like NCURSES, notcurses will watch for escape sequences, check them against the +terminfo database, and return them as special keys (we hijack the Private Use +Area for special keys, specifically Supplementary Private Use Area B (u100000 +through u10ffffd). Unlike NCURSES, the fundamental unit of input is the +UTF8-encoded Unicode codepoint. Note, however, that only one codepoint is +returned at a time (as opposed to an entire EGC). + +It is generally possible for a false positive to occur, wherein keypresses +intended to be distinct are combined into an escape sequence. False negatives +where an intended escape sequence are read as an ESC key followed by distinct +keystrokes are also possible. NCURSES provides the `ESCDELAY` variable to +control timing. notcurses brooks no delay; all characters of an escape sequence +must be readable without delay for it to be interpreted as such. + +```c +// 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 +// keypress, i.e. anything that doesn't correspond to a Unicode codepoint (e.g. +// arrow keys, function keys, screen resize events, etc.). These are mapped +// into Unicode's Supplementary Private Use Area-B, starting at U+100000. +// +// notcurses_getc() and notcurses_getc_nblock() are both nonblocking. +// notcurses_getc_blocking() blocks until a codepoint or special key is read, +// or until interrupted by a signal. +// +// In the case of a valid read, a 32-bit Unicode codepoint is returned. 0 is +// returned to indicate that no input was available, but only by +// notcurses_getc(). Otherwise (including on EOF) (char32_t)-1 is returned. + +#define suppuabize(w) ((w) + 0x100000) + +// Special composed key definitions. These values are added to 0x100000. +#define NCKEY_INVALID suppuabize(0) +#define NCKEY_RESIZE suppuabize(1) // generated internally in response to SIGWINCH +#define NCKEY_UP suppuabize(2) +#define NCKEY_RIGHT suppuabize(3) +#define NCKEY_DOWN suppuabize(4) +#define NCKEY_LEFT suppuabize(5) +#define NCKEY_INS suppuabize(6) +#define NCKEY_DEL suppuabize(7) +#define NCKEY_BACKSPACE suppuabize(8) // backspace (sometimes) +#define NCKEY_PGDOWN suppuabize(9) +#define NCKEY_PGUP suppuabize(10) +#define NCKEY_HOME suppuabize(11) +#define NCKEY_END suppuabize(12) +#define NCKEY_F00 suppuabize(20) +#define NCKEY_F01 suppuabize(21) +#define NCKEY_F02 suppuabize(22) +#define NCKEY_F03 suppuabize(23) +#define NCKEY_F04 suppuabize(24) +// ... up to 100 function keys, egads +#define NCKEY_ENTER suppuabize(121) +#define NCKEY_CLS suppuabize(122) // "clear-screen or erase" +#define NCKEY_DLEFT suppuabize(123) // down + left on keypad +#define NCKEY_DRIGHT suppuabize(124) +#define NCKEY_ULEFT suppuabize(125) // up + left on keypad +#define NCKEY_URIGHT suppuabize(126) +#define NCKEY_CENTER suppuabize(127) // the most truly neutral of keypresses +#define NCKEY_BEGIN suppuabize(128) +#define NCKEY_CANCEL suppuabize(129) +#define NCKEY_CLOSE suppuabize(130) +#define NCKEY_COMMAND suppuabize(131) +#define NCKEY_COPY suppuabize(132) +#define NCKEY_EXIT suppuabize(133) +#define NCKEY_PRINT suppuabize(134) +#define NCKEY_REFRESH suppuabize(135) +// Mouse events. We try to encode some details into the char32_t (i.e. which +// button was pressed), but some is embedded in the ncinput event. The release +// event is generic across buttons; callers must maintain state, if they care. +#define NCKEY_BUTTON1 suppuabize(201) +#define NCKEY_BUTTON2 suppuabize(202) +#define NCKEY_BUTTON3 suppuabize(203) +// ... up to 11 mouse buttons +#define NCKEY_RELEASE suppuabize(212) + +// Is this char32_t a Supplementary Private Use Area-B codepoint? +static inline bool +nckey_supppuab_p(char32_t w){ + return w >= 0x100000 && w <= 0x10fffd; +} + +// Is the event a synthesized mouse event? +static inline bool +nckey_mouse_p(char32_t r){ + return r >= NCKEY_BUTTON1 && r <= NCKEY_RELEASE; +} + +// An input event. Cell coordinates are currently defined only for mouse events. +typedef struct ncinput { + char32_t id; // identifier. Unicode codepoint or synthesized NCKEY event + int y; // y cell coordinate of event, -1 for undefined + int x; // x cell coordinate of event, -1 for undefined + bool alt; // was alt held? + bool shift; // was shift held? + bool ctrl; // was ctrl held? + uint64_t seqnum; // input event number +} ncinput; + +// See ppoll(2) for more detail. Provide a NULL 'ts' to block at length, a 'ts' +// of 0 for non-blocking operation, and otherwise a timespec to bound blocking. +// Signals in sigmask (less several we handle internally) will be atomically +// masked and unmasked per ppoll(2). It should generally contain all signals. +// Returns a single Unicode code point, or (char32_t)-1 on error. 'sigmask' may +// be NULL. Returns 0 on a timeout. If an event is processed, the return value +// is the 'id' field from that event. 'ni' may be NULL. +char32_t notcurses_getc(struct notcurses* n, const struct timespec* ts, + sigset_t* sigmask, ncinput* ni); + +// 'ni' may be NULL if the caller is uninterested in event details. If no event +// is ready, returns 0. +static inline char32_t +notcurses_getc_nblock(struct notcurses* n, ncinput* ni){ + sigset_t sigmask; + sigfillset(&sigmask); + struct timespec ts = { .tv_sec = 0, .tv_nsec = 0 }; + return notcurses_getc(n, &ts, &sigmask, ni); +} + +// 'ni' may be NULL if the caller is uninterested in event details. Blocks +// until an event is processed or a signal is received. +static inline char32_t +notcurses_getc_blocking(struct notcurses* n, ncinput* ni){ + sigset_t sigmask; + sigemptyset(&sigmask); + return notcurses_getc(n, NULL, &sigmask, ni); +} +``` + +## Mice + +notcurses supports mice, though only through brokers such as X or +[GPM](https://www.nico.schottelius.org/software/gpm/). It does not speak +directly to hardware. Mouse events must be explicitly enabled with a +successful call to `notcurses_mouse_enable()`, and can later be disabled. + +```c +// Enable the mouse in "button-event tracking" mode with focus detection and +// UTF8-style extended coordinates. On failure, -1 is returned. On success, 0 +// is returned, and mouse events will be published to notcurses_getc(). +int notcurses_mouse_enable(struct notcurses* n); + +// Disable mouse events. Any events in the input queue can still be delivered. +int notcurses_mouse_disable(struct notcurses* n); +``` + +"Button-event tracking mode" implies the ability to detect mouse button +presses, and also mouse movement while holding down a mouse button (i.e. to +effect drag-and-drop). Mouse events are returned via the `NCKEY_MOUSE*` values, +with coordinate information in the `ncinput` struct. + +## Planes + +Fundamental to notcurses is a z-buffer of rectilinear virtual screens, known +as `ncplane`s. An `ncplane` can be larger than the physical screen, or smaller, +or the same size; it can be entirely contained within the physical screen, or +overlap in part, or lie wholly beyond the boundaries, never to be rendered. +In addition to its framebuffer--a rectilinear matrix of cells +(see [Cells](#cells))--an `ncplane` is defined by: + +* a base cell, used for any cell on the plane without a glyph, +* the egcpool backing its cells, +* a current cursor location, +* a current style, foreground channel, and background channel, +* its geometry, +* a configured user curry (a `void*`), +* its position relative to the visible plane, and +* its z-index. + +If opaque, a `cell` on a higher `ncplane` completely obstructs a corresponding +`cell` from a lower `ncplane` from being seen. An `ncplane` corresponds loosely +to an [NCURSES Panel](https://invisible-island.net/ncurses/ncurses-intro.html#panels), +but is the primary drawing surface of notcurses—there is no object +corresponding to a bare NCURSES `WINDOW`. + +In addition to `ncplane_new()`, an `ncplane` can be created aligned relative +to an existing `ncplane` (including the standard plane) using `ncplane_aligned()`. +When an `ncplane` is no longer needed, free it with `ncplane_destroy()`. To +quickly reset the `ncplane`, use `ncplane_erase()`. + +```c +// Create a new ncplane at the specified offset (relative to the standard plane) +// and the specified size. The number of rows and columns must both be positive. +// This plane is initially at the top of the z-buffer, as if ncplane_move_top() +// had been called on it. The void* 'opaque' can be retrieved (and reset) later. +struct ncplane* ncplane_new(struct notcurses* nc, int rows, int cols, + int yoff, int xoff, void* opaque); + +// Create a new ncplane aligned relative to 'n'. +struct ncplane* ncplane_aligned(struct ncplane* n, int rows, int cols, + int yoff, ncalign_e align, void* opaque); + +// Create a plane bound to plane 'n'. Being bound to 'n' means that 'yoff' and +// 'xoff' are interpreted relative to that plane's origin, and that if that +// plane is moved later, this new plane is moved by the same amount. +struct ncplane* ncplane_bound(struct ncplane* n, int rows, int cols, + int yoff, int xoff, void* opaque); + +// Plane 'n' will be unbound from its parent plane, if it is currently bound, +// and will be made a bound child of 'newparent', if 'newparent' is not NULL. +struct ncplane* ncplane_reparent(struct ncplane* n, struct ncplane* newparent); + +// Duplicate an existing ncplane. The new plane will have the same geometry, +// will duplicate all content, and will start with the same rendering state. +// The new plane will be immediately above the old one on the z axis. +struct ncplane* ncplane_dup(struct ncplane* n, void* opaque); + +// Merge the ncplane 'src' down onto the ncplane 'dst'. This is most rigorously +// defined as "write to 'dst' the frame that would be rendered were the entire +// stack made up only of 'src' and, below it, 'dst', and 'dst' was the entire +// rendering region." Merging is independent of the position of 'src' viz 'dst' +// on the z-axis. If 'src' does not intersect with 'dst', 'dst' will not be +// changed, but it is not an error. The source plane still exists following +// this operation. Do not supply the same plane for both 'src' and 'dst'. +int ncplane_mergedown(struct ncplane* restrict src, struct ncplane* restrict dst); + +// Erase every cell in the ncplane, resetting all attributes to normal, all +// colors to the default color, and all cells to undrawn. All cells associated +// with this ncplane are invalidated, and must not be used after the call, +// excluding the base cell. +void ncplane_erase(struct ncplane* n); +``` + +All planes, including the standard plane, are created with scrolling disabled. +Attempting to print past the end of a line will stop at the plane boundary, +and indicate an error. On a plane 10 columns wide and two rows high, printing +"0123456789" at the origin should succeed, but printing "01234567890" will by +default fail at the eleventh character. In either case, the cursor will be left +at location 0x10; it must be moved before further printing can take place. If +scrolling is enabled, the first row will be filled with 01234546789, the second +row will have 0 written to its first column, and the cursor will end up at 1x1. +Note that it is still an error to manually attempt to move the cursor off-plane, +or to specify off-plane output. Boxes do not scroll; attempting to draw a 2x11 +box on our 2x10 plane will result in an error and no output. When scrolling is +enabled, and output takes place while the cursor is past the end of the last +row, the first row is discarded, all other rows are moved up, the last row is +cleared, and output begins at the beginning of the last row. This does not take +place until output is generated (i.e. it is possible to fill a plane when +scrolling is enabled). + +```c +// All planes are created with scrolling disabled. Scrolling can be dynamically +// controlled with ncplane_set_scrolling(). Returns true if scrolling was +// previously enabled, or false if it was disabled. +bool ncplane_set_scrolling(struct ncplane* n, bool scrollp); +``` + +Planes can be freely resized, though they must retain a positive size in +both dimensions. The powerful `ncplane_resize()` allows resizing an `ncplane`, +retaining all or a portion of the plane's existing content, and translating +the plane in one step. The helper function `ncplane_resize_simple()` allows +resizing an `ncplane` without movement, retaining all possible data. To move +the plane without resizing it or changing its content, use `ncplane_move_yx()`. +It is an error to invoke these functions on the standard plane. + +```c +// Resize the specified ncplane. The four parameters 'keepy', 'keepx', +// 'keepleny', and 'keeplenx' define a subset of the ncplane to keep, +// unchanged. This may be a section of size 0, though none of these four +// parameters may be negative. 'keepx' and 'keepy' are relative to the ncplane. +// They must specify a coordinate within the ncplane's totality. 'yoff' and +// 'xoff' are relative to 'keepy' and 'keepx', and place the upper-left corner +// 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 '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. +int ncplane_resize(struct ncplane* n, int keepy, int keepx, int keepleny, + int keeplenx, int yoff, int xoff, int ylen, int xlen); + +// Resize the plane, retaining what data we can (everything, unless we're +// shrinking in some dimension). Keep the origin where it is. +static inline int +ncplane_resize_simple(struct ncplane* n, int ylen, int xlen){ + int oldy, oldx; + ncplane_dim_yx(n, &oldy, &oldx); // current dimensions of 'n' + int keepleny = oldy > ylen ? ylen : oldy; + int keeplenx = oldx > xlen ? xlen : oldx; + return ncplane_resize(n, 0, 0, keepleny, keeplenx, 0, 0, ylen, xlen); +} + +// Move this plane relative to the standard plane. It is an error to attempt to +// move the standard plane. +int ncplane_move_yx(struct ncplane* n, int y, int x); + +// Get the origin of this ncplane relative to the standard plane. +void ncplane_yx(struct ncplane* n, int* restrict y, int* restrict x); + +// Return the dimensions of this ncplane. +void ncplane_dim_yx(struct ncplane* n, int* restrict rows, int* restrict cols); + +static inline int +ncplane_dim_y(const struct ncplane* n){ + int dimy; + ncplane_dim_yx(n, &dimy, NULL); + return dimy; +} + +static inline int +ncplane_dim_x(const struct ncplane* n){ + int dimx; + ncplane_dim_yx(n, NULL, &dimx); + return dimx; +} + +// provided a coordinate relative to the origin of 'src', map it to the same +// absolute coordinate relative to thte origin of 'dst'. either or both of 'y' +// and 'x' may be NULL. if 'dst' is NULL, it is taken to be the standard plane. +void ncplane_translate(const struct ncplane* src, const struct ncplane* dst, + int* restrict y, int* restrict x); + +// Fed absolute 'y'/'x' coordinates, determine whether that coordinate is +// within the ncplane 'n'. If not, return false. If so, return true. Either +// way, translate the absolute coordinates relative to 'n'. If the point is not +// within 'n', these coordinates will not be within the dimensions of the plane. +bool ncplane_translate_abs(const struct ncplane* n, int* restrict y, int* restrict x); +``` + +If a given cell's glyph is zero, or its foreground channel is fully transparent, +it is considered to have no foreground. A _default_ cell can be chosen for the +`ncplane`, to be consulted in this case. If the base cell's glyph is likewise +zero (or its foreground channel fully transparent), the plane's foreground is +not rendered. Note that the base cell, like every other cell, has its own +foreground and background channels. + +```c +// Set the specified style bits for the ncplane 'n', whether they're actively +// supported or not. +void ncplane_styles_set(struct ncplane* n, unsigned stylebits); + +// Add the specified styles to the ncplane's existing spec. +void ncplane_styles_on(struct ncplane* n, unsigned stylebits); + +// Remove the specified styles from the ncplane's existing spec. +void ncplane_styles_off(struct ncplane* n, unsigned stylebits); + +// Return the current styling for this ncplane. +unsigned ncplane_styles(const struct ncplane* n); + +// Set the ncplane's base cell to this cell. It will be used for purposes of +// rendering anywhere that the ncplane's gcluster is 0. Erasing the ncplane +// does not reset the base cell; this function must be called with a zero 'c'. +int ncplane_set_base_cell(struct ncplane* ncp, const cell* c); + +// Set the ncplane's base cell to this cell. It will be used for purposes of +// rendering anywhere that the ncplane's gcluster is 0. Erasing the ncplane +// does not reset the base cell; this function must be called with an empty +// 'egc'. 'egc' must be a single extended grapheme cluster. +int ncplane_set_base(struct ncplane* ncp, const char* egc, + uint32_t attrword, uint64_t channels); + +// Extract the ncplane's base cell into 'c'. The reference is invalidated if +// 'ncp' is destroyed. +int ncplane_base(struct ncplane* ncp, cell* c); +``` + +`ncplane`s are completely ordered along an imaginary z-axis. Newly-created +`ncplane`s are on the top of the stack. They can be freely reordered. + +```c +// Splice ncplane 'n' out of the z-buffer, and reinsert it at the top or bottom. +int ncplane_move_top(struct ncplane* n); +int ncplane_move_bottom(struct ncplane* n); + +// Splice ncplane 'n' out of the z-buffer, and reinsert it below 'below'. +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'. +int ncplane_move_above(struct ncplane* restrict n, struct ncplane* restrict above); + +// Return the ncplane below this one, or NULL if this is at the stack's bottom. +struct ncplane* ncplane_below(struct ncplane* n); +``` + +Each plane holds a user pointer which can be retrieved and set (or ignored). In +addition, the plane's virtual framebuffer can be accessed (note that this does +not necessarily reflect anything on the actual screen). + +```c +// Retrieve the current contents of the cell under the cursor. The EGC is +// returned, or NULL on error. This EGC must be free()d by the caller. The +// attrword and channels are written to 'attrword' and 'channels', respectively. +char* ncplane_at_cursor(struct ncplane* n, uint32_t* attrword, uint64_t* channels); + +// Retrieve the current contents of the cell under the cursor into 'c'. This +// cell is invalidated if the associated plane is destroyed. +int ncplane_at_cursor_cell(struct ncplane* n, cell* c); + +// Retrieve the current contents of the specified cell. The EGC is returned, or +// NULL on error. This EGC must be free()d by the caller. The attrword and +// channels are written to 'attrword' and 'channels', respectively. +char* ncplane_at_yx(struct ncplane* n, int y, int x, + uint32_t* attrword, uint64_t* channels); + +// Retrieve the current contents of the specified cell into 'c'. This cell is +// invalidated if the associated plane is destroyed. +int ncplane_at_yx_cell(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. +void* ncplane_set_userptr(struct ncplane* n, void* opaque); +void* ncplane_userptr(struct ncplane* n); +``` + +All output is to `ncplane`s. There is no cost in moving the cursor around the +virtual framebuffer. Output that's never rendered still has some memory transfer +cost as the virtual framebuffer is prepared, but new data overwrites it in +memory. + +```c +// Move the cursor to the specified position (the cursor needn't be visible). +// Returns -1 on error, including negative parameters, or ones exceeding the +// plane's dimensions. +int ncplane_cursor_move_yx(struct ncplane* n, int y, int x); + +// Get the current position of the cursor within n. y and/or x may be NULL. +void ncplane_cursor_yx(const struct ncplane* n, int* restrict y, int* restrict x); + +// Replace the cell at the specified coordinates with the provided cell 'c', +// and advance the cursor by the width of the cell (but not past the end of the +// plane). On success, returns the number of columns the cursor was advanced. +// On failure, -1 is returned. +int ncplane_putc_yx(struct ncplane* n, int y, int x, const cell* c); + +// Call ncplane_putc_yx() for the current cursor location. +static inline int +ncplane_putc(struct ncplane* n, const cell* c){ + return ncplane_putc_yx(n, -1, -1, c); +} + +// Replace the cell at the specified coordinates with the provided 7-bit char +// 'c'. Advance the cursor by 1. On success, returns 1. On failure, returns -1. +// This works whether the underlying char is signed or unsigned. +static inline int +ncplane_putsimple_yx(struct ncplane* n, int y, int x, char c){ + cell ce = CELL_INITIALIZER(c, ncplane_attr(n), ncplane_channels(n)); + if(!cell_simple_p(&ce)){ + return -1; + } + return ncplane_putc_yx(n, y, x, &ce); +} + +// Call ncplane_putsimple_yx() at the current cursor location. +static inline int +ncplane_putsimple(struct ncplane* n, char c){ + return ncplane_putsimple_yx(n, -1, -1, c); +} + +// Replace the EGC underneath us, but retain the styling. The current styling +// of the plane will not be changed. +int ncplane_putsimple_stainable(struct ncplane* n, char c); + +// Replace the cell at the specified coordinates with the provided wide char +// 'w'. Advance the cursor by the character's width as reported by wcwidth(). +// On success, returns 1. On failure, returns -1. +static inline int +ncplane_putwc_yx(struct ncplane* n, int y, int x, wchar_t w){ + wchar_t warr[2] = { w, L'\0' }; + return ncplane_putwstr_yx(n, y, x, warr); +} + +// Call ncplane_putwc() at the current cursor position. +static inline int +ncplane_putwc(struct ncplane* n, wchar_t w){ + return ncplane_putwc_yx(n, -1, -1, w); +} + +// Replace the cell at the specified coordinates with the provided EGC, and +// advance the cursor by the width of the cluster (but not past the end of the +// plane). On success, returns the number of columns the cursor was advanced. +// On failure, -1 is returned. The number of bytes converted from gclust is +// written to 'sbytes' if non-NULL. +int ncplane_putegc_yx(struct ncplane* n, int y, int x, const char* gclust, int* sbytes); + +// Call ncplane_putegc() at the current cursor location. +static inline int +ncplane_putegc(struct ncplane* n, const char* gclust, int* sbytes){ + return ncplane_putegc_yx(n, -1, -1, gclust, sbytes); +} + +// Replace the EGC underneath us, but retain the styling. The current styling +// of the plane will not be changed. +int ncplane_putegc_stainable(struct ncplane* n, const char* gclust, int* sbytes); + +#define WCHAR_MAX_UTF8BYTES 6 + +// ncplane_putegc(), but following a conversion from wchar_t to UTF-8 multibyte. +static inline int +ncplane_putwegc(struct ncplane* n, const wchar_t* gclust, int* sbytes){ + // maximum of six UTF8-encoded bytes per wchar_t + const size_t mbytes = (wcslen(gclust) * WCHAR_MAX_UTF8BYTES) + 1; + char* mbstr = (char*)malloc(mbytes); // need cast for c++ callers + if(mbstr == NULL){ + return -1; + } + size_t s = wcstombs(mbstr, gclust, mbytes); + if(s == (size_t)-1){ + free(mbstr); + return -1; + } + int ret = ncplane_putegc(n, mbstr, sbytes); + free(mbstr); + return ret; +} + +// Call ncplane_putwegc() after successfully moving to y, x. +static inline int +ncplane_putwegc_yx(struct ncplane* n, int y, int x, const wchar_t* gclust, + int* sbytes){ + if(ncplane_cursor_move_yx(n, y, x)){ + return -1; + } + return ncplane_putwegc(n, gclust, sbytes); +} + +// Replace the EGC underneath us, but retain the styling. The current styling +// of the plane will not be changed. +int ncplane_putwegc_stainable(struct ncplane* n, const wchar_t* gclust, int* sbytes); + +// Write a series of EGCs to the current location, using the current style. +// They will be interpreted as a series of columns (according to the definition +// of ncplane_putc()). Advances the cursor by some positive number of cells +// (though not beyond the end of the plane); this number is returned on success. +// On error, a non-positive number is returned, indicating the number of cells +// which were written before the error. +static inline int +ncplane_putstr_yx(struct ncplane* n, int y, int x, const char* gclusters); + +static inline int +ncplane_putstr(struct ncplane* n, const char* gclustarr){ + return ncplane_putstr_yx(n, -1, -1, gclustarr); +} + +int ncplane_putstr_aligned(struct ncplane* n, int y, ncalign_e align, const char* s); + +// ncplane_putstr(), but following a conversion from wchar_t to UTF-8 multibyte. +static inline int +ncplane_putwstr_yx(struct ncplane* n, int y, int x, const wchar_t* gclustarr){ + // maximum of six UTF8-encoded bytes per wchar_t + const size_t mbytes = (wcslen(gclustarr) * WCHAR_MAX_UTF8BYTES) + 1; + char* mbstr = (char*)malloc(mbytes); // need cast for c++ callers + if(mbstr == NULL){ + return -1; + } + size_t s = wcstombs(mbstr, gclustarr, mbytes); + if(s == (size_t)-1){ + free(mbstr); + return -1; + } + int ret = ncplane_putstr_yx(n, y, x, mbstr); + free(mbstr); + return ret; +} + +static inline int +ncplane_putwstr_aligned(struct ncplane* n, int y, ncalign_e align, + const wchar_t* gclustarr){ + int width = wcswidth(gclustarr, INT_MAX); + int xpos = ncplane_align(n, align, width); + return ncplane_putwstr_yx(n, y, xpos, gclustarr); +} + +static inline int +ncplane_putwstr(struct ncplane* n, const wchar_t* gclustarr){ + return ncplane_putwstr_yx(n, -1, -1, gclustarr); +} + +// The ncplane equivalents of printf(3) and vprintf(3). +int ncplane_vprintf_aligned(struct ncplane* n, int y, ncalign_e align, + const char* format, va_list ap); + +int ncplane_vprintf_yx(struct ncplane* n, int y, int x, + const char* format, va_list ap); + +static inline int +ncplane_vprintf(struct ncplane* n, const char* format, va_list ap){ + return ncplane_vprintf_yx(n, -1, -1, format, ap); +} + +static inline int +ncplane_printf(struct ncplane* n, const char* format, ...) + __attribute__ ((format (printf, 2, 3))); + +static inline int +ncplane_printf(struct ncplane* n, const char* format, ...){ + va_list va; + va_start(va, format); + int ret = ncplane_vprintf(n, format, va); + va_end(va); + return ret; +} + +static inline int +ncplane_printf_aligned(struct ncplane* n, int y, ncalign_e align, + const char* format, ...) + __attribute__ ((format (printf, 4, 5))); + +static inline int +ncplane_printf_yx(struct ncplane* n, int y, int x, const char* format, ...){ + va_list va; + va_start(va, format); + int ret = ncplane_vprintf_yx(n, y, x, format, va); + va_end(va); + return ret; +} + +static inline int +ncplane_printf_yx(struct ncplane* n, int y, int x, const char* format, ...) + __attribute__ ((format (printf, 4, 5))); + +static inline int +ncplane_printf_aligned(struct ncplane* n, int y, ncalign_e align, const char* format, ...){ + va_list va; + va_start(va, format); + int ret = ncplane_vprintf_aligned(n, y, align, format, va); + va_end(va); + return ret; +} +``` + +Lines and boxes can be drawn, interpolating their colors between their two +endpoints. For a line of a single color, be sure to specify the same channels +on both sides. Boxes allow fairly detailed specification of how they're drawn. + +```c +// Draw horizontal or vertical lines using the specified cell, starting at the +// current cursor position. The cursor will end at the cell following the last +// cell output (even, perhaps counter-intuitively, when drawing vertical +// lines), just as if ncplane_putc() was called at that spot. Return the +// number of cells drawn on success. On error, return the negative number of +// cells drawn. +int ncplane_hline_interp(struct ncplane* n, const cell* c, int len, + uint64_t c1, uint64_t c2); + +static inline int +ncplane_hline(struct ncplane* n, const cell* c, int len){ + return ncplane_hline_interp(n, c, len, c->channels, c->channels); +} + +int ncplane_vline_interp(struct ncplane* n, const cell* c, int len, + uint64_t c1, uint64_t c2); + +static inline int +ncplane_vline(struct ncplane* n, const cell* c, int len){ + return ncplane_vline_interp(n, c, len, c->channels, c->channels); +} + +// Draw a box with its upper-left corner at the current cursor position, and its +// lower-right corner at 'ystop'x'xstop'. The 6 cells provided are used to draw the +// upper-left, ur, ll, and lr corners, then the horizontal and vertical lines. +// 'ctlword' is defined in the least significant byte, where bits [7, 4] are a +// gradient mask, and [3, 0] are a border mask: +// * 7, 3: top +// * 6, 2: right +// * 5, 1: bottom +// * 4, 0: left +// If the gradient bit is not set, the styling from the hl/vl cells is used for +// the horizontal and vertical lines, respectively. If the gradient bit is set, +// the color is linearly interpolated between the two relevant corner cells. +// +// By default, vertexes are drawn whether their connecting edges are drawn or +// not. The value of the bits corresponding to NCBOXCORNER_MASK control this, +// and are interpreted as the number of connecting edges necessary to draw a +// given corner. At 0 (the default), corners are always drawn. At 3, corners +// are never drawn (as at most 2 edges can touch a box's corner). + +#define NCBOXMASK_TOP 0x0001 +#define NCBOXMASK_RIGHT 0x0002 +#define NCBOXMASK_BOTTOM 0x0004 +#define NCBOXMASK_LEFT 0x0008 +#define NCBOXGRAD_TOP 0x0010 +#define NCBOXGRAD_RIGHT 0x0020 +#define NCBOXGRAD_BOTTOM 0x0040 +#define NCBOXGRAD_LEFT 0x0080 +#define NCBOXCORNER_MASK 0x0300 +#define NCBOXCORNER_SHIFT 8u + +int ncplane_box(struct ncplane* n, const cell* ul, const cell* ur, + const cell* ll, const cell* lr, const cell* hline, + const cell* vline, int ystop, int xstop, + unsigned ctlword); + +// Draw a box with its upper-left corner at the current cursor position, having +// dimensions 'ylen'x'xlen'. See ncplane_box() for more information. The +// minimum box size is 2x2, and it cannot be drawn off-screen. +static inline int +ncplane_box_sized(struct ncplane* n, const cell* ul, const cell* ur, + const cell* ll, const cell* lr, const cell* hline, + const cell* vline, int ylen, int xlen, unsigned ctlword){ + int y, x; + ncplane_cursor_yx(n, &y, &x); + return ncplane_box(n, ul, ur, ll, lr, hline, vline, y + ylen - 1, + x + xlen - 1, ctlword); +} + +static inline int +ncplane_perimeter(struct ncplane* n, const cell* ul, const cell* ur, + const cell* ll, const cell* lr, const cell* hline, + const cell* vline, unsigned ctlword){ + if(ncplane_cursor_move_yx(n, 0, 0)){ + return -1; + } + int dimy, dimx; + ncplane_dim_yx(n, &dimy, &dimx); + return ncplane_box_sized(n, ul, ur, ll, lr, hline, vline, dimy, dimx, ctlword); +} + +static inline int +ncplane_rounded_box(struct ncplane* n, uint32_t attr, uint64_t channels, + int ystop, int xstop, unsigned ctlword){ + int ret = 0; + 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((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); + return ret; +} + +static inline int +ncplane_rounded_box_sized(struct ncplane* n, uint32_t attr, uint64_t channels, + int ylen, int xlen, unsigned ctlword){ + int y, x; + ncplane_cursor_yx(n, &y, &x); + return ncplane_rounded_box(n, attr, channels, y + ylen - 1, + x + xlen - 1, ctlword); +} + +static inline int +ncplane_double_box(struct ncplane* n, uint32_t attr, uint64_t channels, + int ystop, int xstop, unsigned ctlword){ + int ret = 0; + 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((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); + return ret; +} + +static inline int +ncplane_double_box_sized(struct ncplane* n, uint32_t attr, uint64_t channels, + int ylen, int xlen, unsigned ctlword){ + int y, x; + ncplane_cursor_yx(n, &y, &x); + return ncplane_double_box(n, attr, channels, y + ylen - 1, + x + xlen - 1, ctlword); +} +``` + +Similarly, areas can be filled with a cell. + +```c +// Starting at the specified coordinate, if it has no glyph, 'c' is copied into +// it. We do the same to all cardinally-connected glyphless cells, filling in +// everything behind a boundary. Returns the number of cells polyfilled. An +// invalid initial y, x is an error. Returns the number of cells filled, or +// -1 on error. +int ncplane_polyfill_yx(struct ncplane* n, int y, int x, const cell* c); + +// Draw a gradient with its upper-left corner at the current cursor position, +// stopping at 'ystop'x'xstop'. The glyph composed of 'egc' and 'attrword' is +// used for all cells. The channels specified by 'ul', 'ur', 'll', and 'lr' +// are composed into foreground and background gradients. To do a vertical +// gradient, 'ul' ought equal 'ur' and 'll' ought equal 'lr'. To do a +// horizontal gradient, 'ul' ought equal 'll' and 'ur' ought equal 'ul'. To +// color everything the same, all four channels should be equivalent. The +// resulting alpha values are equal to incoming alpha values. Returns the +// number of cells filled on success, or -1 on failure. +int ncplane_gradient(struct ncplane* n, const char* egc, uint32_t attrword, + uint64_t ul, uint64_t ur, uint64_t ll, uint64_t lr, + int ystop, int xstop); + +// Draw a gradient with its upper-left corner at the current cursor position, +// having dimensions 'ylen'x'xlen'. See ncplane_gradient for more information. +static inline int +ncplane_gradient_sized(struct ncplane* n, const char* egc, uint32_t attrword, + uint64_t ul, uint64_t ur, uint64_t ll, uint64_t lr, + int ylen, int xlen){ + int y, x; + ncplane_cursor_yx(n, &y, &x); + return ncplane_gradient(n, egc, attrword, ul, ur, ll, lr, y + ylen - 1, x + xlen - 1); +} + +// Do a high-resolution gradient using upper blocks and synced backgrounds. +// This doubles the number of vertical gradations, but restricts you to +// half blocks (appearing to be full blocks). Returns the number of cells +// filled on success, or -1 on error. +int ncplane_highgradient(struct ncplane* n, uint32_t ul, uint32_t ur, + uint32_t ll, uint32_t lr, int ystop, int xstop); + +static inline int +ncplane_highgradient_sized(struct ncplane* n, uint64_t ul, uint64_t ur, + uint64_t ll, uint64_t lr, int ylen, int xlen){ + if(ylen < 1 || xlen < 1){ + return -1; + } + int y, x; + ncplane_cursor_yx(n, &y, &x); + return ncplane_highgradient(n, ul, ur, ll, lr, y + ylen - 1, x + xlen - 1); +} + +// Set the given style throughout the specified region, keeping content and +// channels unchanged. Returns the number of cells set, or -1 on failure. +int ncplane_format(struct ncplane* n, int ystop, int xstop, uint32_t attrword); + +// Set the given channels throughout the specified region, keeping content and +// attributes unchanged. Returns the number of cells set, or -1 on failure. +int ncplane_stain(struct ncplane* n, int ystop, int xstop, + uint64_t ul, uint64_t ur, uint64_t ll, uint64_t lr); +``` + +My 14 year-old self would never forgive me if we didn't have sweet palette fades. + +```c +// Called for each delta performed in a fade on ncp. If anything but 0 is returned, +// the fading operation ceases immediately, and that value is propagated out. If provided +// and not NULL, the faders will not themselves call notcurses_render(). +typedef int (*fadecb)(struct notcurses* nc, struct ncplane* ncp, void* curry); + +// Fade the ncplane out over the provided time, calling the specified function +// when done. Requires a terminal which supports truecolor, or at least palette +// modification (if the terminal uses a palette, our ability to fade planes is +// limited, and affected by the complexity of the rest of the screen). It is +// not safe to resize or destroy the plane during the fadeout FIXME. +int ncplane_fadeout(struct ncplane* n, const struct timespec* ts, fadecb fader, void* curry); + +// Fade the ncplane in over the specified time. Load the ncplane with the +// target cells without rendering, then call this function. When it's done, the +// ncplane will have reached the target levels, starting from zeroes. +int ncplane_fadein(struct ncplane* n, const struct timespec* ts, fadecb fader, void* curry); + +// Pulse the plane in and out until the callback returns non-zero, relying on +// the callback 'fader' to initiate rendering. 'ts' defines the half-period +// (i.e. the transition from black to full brightness, or back again). Proper +// use involves preparing (but not rendering) an ncplane, then calling +// ncplane_pulse(), which will fade in from black to the specified colors. +int ncplane_pulse(struct ncplane* n, const struct timespec* ts, fadecb fader, void* curry); +``` + +Finally, a raw stream of RGBA or BGRx data can be blitted directly to an ncplane: + +```c +// Blit a flat array 'data' of BGRx 32-bit values to the ncplane 'nc', offset +// from the upper left by 'placey' and 'placex'. Each row ought occupy +// 'linesize' bytes (this might be greater than lenx * 4 due to padding). A +// subregion of the input can be specified with 'begy'x'begx' and 'leny'x'lenx'. +int ncblit_bgrx(struct ncplane* nc, int placey, int placex, int linesize, + const unsigned char* data, int begy, int begx, + int leny, int lenx); + +// Blit a flat array 'data' of RGBA 32-bit values to the ncplane 'nc', offset +// from the upper left by 'placey' and 'placex'. Each row ought occupy +// 'linesize' bytes (this might be greater than lenx * 4 due to padding). A +// subregion of the input can be specified with 'begy'x'begx' and 'leny'x'lenx'. +int ncblit_rgba(struct ncplane* nc, int placey, int placex, int linesize, + const unsigned char* data, int begy, int begx, + int leny, int lenx); +``` + +### Plane channels API + +Helpers are provided to manipulate an `ncplane`'s `channels` member. They are +all implemented in terms of the lower-level [Channels API](#channels). + +```c +// Get the current channels or attribute word for ncplane 'n'. +uint64_t ncplane_channels(const struct ncplane* n); +uint32_t ncplane_attr(const struct ncplane* n); + +// Extract the 32-bit working background channel from an ncplane. +static inline unsigned +ncplane_bchannel(const struct ncplane* nc){ + return channels_bchannel(ncplane_channels(nc)); +} + +// Extract the 32-bit working foreground channel from an ncplane. +static inline unsigned +ncplane_fchannel(const struct ncplane* nc){ + return channels_fchannel(ncplane_channels(nc)); +} + +// Extract 24 bits of working foreground RGB from an ncplane, shifted to LSBs. +static inline unsigned +ncplane_fg(const struct ncplane* nc){ + return channels_fg(ncplane_channels(nc)); +} + +// Extract 24 bits of working background RGB from an ncplane, shifted to LSBs. +static inline unsigned +ncplane_bg(const struct ncplane* nc){ + return channels_bg(ncplane_channels(nc)); +} + +// Extract 2 bits of foreground alpha from 'struct ncplane', shifted to LSBs. +static inline unsigned +ncplane_fg_alpha(const struct ncplane* nc){ + return channels_fg_alpha(ncplane_channels(nc)); +} + +// Extract 2 bits of background alpha from 'struct ncplane', shifted to LSBs. +static inline unsigned +ncplane_bg_alpha(const struct ncplane* nc){ + return channels_bg_alpha(ncplane_channels(nc)); +} + +// Set the alpha parameters for ncplane 'n'. +int ncplane_set_fg_alpha(struct ncplane* n, int alpha); +int ncplane_set_bg_alpha(struct ncplane* n, int alpha); + +// Extract 24 bits of foreground RGB from 'n', split into subcomponents. +static inline unsigned +ncplane_fg_rgb(const struct ncplane* n, unsigned* r, unsigned* g, unsigned* + return channels_fg_rgb(ncplane_channels(n), r, g, b); +} + +// Extract 24 bits of background RGB from 'n', split into subcomponents. +static inline unsigned +ncplane_bg_rgb(const struct ncplane* n, unsigned* r, unsigned* g, unsigned* + return channels_bg_rgb(ncplane_channels(n), r, g, b); +} + +// Set the current fore/background color using RGB specifications. If the +// terminal does not support directly-specified 3x8b cells (24-bit "TrueColor", +// indicated by the "RGB" terminfo capability), the provided values will be +// interpreted in some lossy fashion. None of r, g, or b may exceed 255. +// "HP-like" terminals require setting foreground and background at the same +// time using "color pairs"; notcurses will manage color pairs transparently. +int ncplane_set_fg_rgb(struct ncplane* n, int r, int g, int b); +int ncplane_set_bg_rgb(struct ncplane* n, int r, int g, int b); + +// Same, but clipped to [0..255]. +void ncplane_set_bg_rgb_clipped(struct ncplane* n, int r, int g, int b); +void ncplane_set_fg_rgb_clipped(struct ncplane* n, int r, int g, int b); + +// Same, but with rgb assembled into a channel (i.e. lower 24 bits). +int ncplane_set_fg(struct ncplane* n, unsigned channel); +int ncplane_set_bg(struct ncplane* n, unsigned channel); + +// Use the default color for the foreground/background. +void ncplane_set_fg_default(struct ncplane* n); +void ncplane_set_bg_default(struct ncplane* n); + +int ncplane_set_fg_palindex(struct ncplane* n, int idx); +int ncplane_set_bg_palindex(struct ncplane* n, int idx); +``` + +## Cells + +Unlike the `notcurses` or `ncplane` objects, the definition of `cell` is +available to the user. It is somewhat ironic, then, that the user typically +needn't (and shouldn't) use `cell`s directly. Use a `cell` when the EGC being +output is used several times. In this case, time otherwise spent running +`cell_load()` (which tokenizes and verifies EGCs) can be saved. It can also be +useful to use a `cell` when the same styling is used in a discontinuous manner. + +```c +// A cell corresponds to a single character cell on some plane, which can be +// occupied by a single grapheme cluster (some root spacing glyph, along with +// possible combining characters, which might span multiple columns). At any +// cell, we can have a theoretically arbitrarily long UTF-8 string, a foreground +// color, a background color, and an attribute set. Valid grapheme cluster +// contents include: +// +// * A NUL terminator, +// * A single control character, followed by a NUL terminator, +// * At most one spacing character, followed by zero or more nonspacing +// characters, followed by a NUL terminator. +// +// Multi-column characters can only have a single style/color throughout. +// +// Each cell occupies 16 static bytes (128 bits). The surface is thus ~1.6MB +// for a (pretty large) 500x200 terminal. At 80x43, it's less than 64KB. +// Dynamic requirements can add up to 16MB to an ncplane, but such large pools +// are unlikely in common use. +// +// We implement some small alpha compositing. Foreground and background both +// have two bits of inverted alpha. The actual grapheme written to a cell is +// the topmost non-zero grapheme. If its alpha is 00, its foreground color is +// used unchanged. If its alpha is 10, its foreground color is derived entirely +// from cells underneath it. Otherwise, the result will be a composite. +// Likewise for the background. If the bottom of a coordinate's zbuffer is +// reached with a cumulative alpha of zero, the default is used. In this way, +// a terminal configured with transparent background can be supported through +// multiple occluding ncplanes. A foreground alpha of 11 requests high-contrast +// text (relative to the computed background). A background alpha of 11 is +// currently forbidden. +// +// Default color takes precedence over palette or RGB, and cannot be used with +// transparency. Indexed palette takes precedence over RGB. It cannot +// meaningfully set transparency, but it can be mixed into a cascading color. +// RGB is used if neither default terminal colors nor palette indexing are in +// play, and fully supports all transparency options. +typedef struct cell { + // These 32 bits are either a single-byte, single-character grapheme cluster + // (values 0--0x7f), or an offset into a per-ncplane attached pool of + // varying-length UTF-8 grapheme clusters. This pool may thus be up to 32MB. + uint32_t gcluster; // 4B -> 4B + // NCSTYLE_* attributes (16 bits) + 8 foreground palette index bits + 8 + // background palette index bits. palette index bits are used only if the + // corresponding default color bit *is not* set, and the corresponding + // palette index bit *is* set. + uint32_t attrword; // + 4B -> 8B + // (channels & 0x8000000000000000ull): left half of wide character + // (channels & 0x4000000000000000ull): foreground is *not* "default color" + // (channels & 0x3000000000000000ull): foreground alpha (2 bits) + // (channels & 0x0800000000000000ull): foreground uses palette index + // (channels & 0x0700000000000000ull): reserved, must be 0 + // (channels & 0x00ffffff00000000ull): foreground in 3x8 RGB (rrggbb) + // (channels & 0x0000000080000000ull): right half of wide character + // (channels & 0x0000000040000000ull): background is *not* "default color" + // (channels & 0x0000000030000000ull): background alpha (2 bits) + // (channels & 0x0000000008000000ull): background uses palette index + // (channels & 0x0000000007000000ull): reserved, must be 0 + // (channels & 0x0000000000ffffffull): background in 3x8 RGB (rrggbb) + // At render time, these 24-bit values are quantized down to terminal + // capabilities, if necessary. There's a clear path to 10-bit support should + // we one day need it, but keep things cagey for now. "default color" is + // best explained by color(3NCURSES). ours is the same concept. until the + // "not default color" bit is set, any color you load will be ignored. + uint64_t channels; // + 8B == 16B +} cell; + +#define CELL_WIDEASIAN_MASK 0x8000000080000000ull +#define CELL_BGDEFAULT_MASK 0x0000000040000000ull +#define CELL_FGDEFAULT_MASK (CELL_BGDEFAULT_MASK << 32u) +#define CELL_BG_MASK 0x0000000000ffffffull +#define CELL_FG_MASK (CELL_BG_MASK << 32u) +#define CELL_BG_PALETTE 0x0000000008000000ull +#define CELL_FG_PALETTE (CELL_BG_PALETTE << 32u) +#define CELL_ALPHA_MASK 0x0000000030000000ull +#define CELL_ALPHA_SHIFT 28u +#define CELL_ALPHA_HIGHCONTRAST 3 +#define CELL_ALPHA_TRANSPARENT 2 +#define CELL_ALPHA_BLEND 1 +#define CELL_ALPHA_OPAQUE 0 +``` + +`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_INITIALIZER(c, a, chan) { .gcluster = (c), .attrword = (a), .channels = (chan), } + +static inline void +cell_init(cell* c){ + memset(c, 0, sizeof(*c)); +} +``` + +A `cell` has three fundamental elements: + +* The EGC displayed at this coordinate, encoded in UTF-8. If the EGC is a + single ASCII character (value less than 0x80), it is stored inline in + the `cell`'s `gcluster` field. Otherwise, `gcluster`'s top 24 bits + are a 128-biased offset into the associated `ncplane`'s egcpool. This + implies that `cell`s are associated with `ncplane`s once prepared. +* The Curses-style attributes of the text. +* The 52 bits of foreground and background RGBA (2x8/8/8/2), plus a few flags. + +The EGC should be loaded using `cell_load()`. Either a single NUL-terminated +EGC can be provided, or a string composed of multiple EGCs. In the latter case, +the first EGC from the string is loaded. Remember, backing storage for the EGC +is provided by the `ncplane` passed to `cell_load()`; if this `ncplane` is +destroyed (or even erased), the `cell` cannot safely be used. If you're done +using the `cell` before being done with the `ncplane`, call `cell_release()` +to free up the EGC resources. + +```c +// Breaks the UTF-8 string in 'gcluster' down, setting up the cell 'c'. Returns +// the number of bytes copied out of 'gcluster', or -1 on failure. The styling +// of the cell is left untouched, but any resources are released. +int cell_load(struct ncplane* n, cell* c, const char* gcluster); + +// cell_load(), plus blast the styling with 'attr' and 'channels'. +static inline int +cell_prime(struct ncplane* n, cell* c, const char* gcluster, + uint32_t attr, uint64_t channels){ + c->attrword = attr; + c->channels = channels; + int ret = cell_load(n, c, gcluster); + return ret; +} + +// Duplicate 'c' into 'targ'. Not intended for external use; exposed for the +// benefit of unit tests. +int cell_duplicate(struct ncplane* n, cell* targ, const cell* c); + +// Release resources held by the cell 'c'. +void cell_release(struct ncplane* n, cell* c); + +#define NCSTYLE_MASK 0xffff0000ul +#define NCSTYLE_STANDOUT 0x00800000ul +#define NCSTYLE_UNDERLINE 0x00400000ul +#define NCSTYLE_REVERSE 0x00200000ul +#define NCSTYLE_BLINK 0x00100000ul +#define NCSTYLE_DIM 0x00080000ul +#define NCSTYLE_BOLD 0x00040000ul +#define NCSTYLE_INVIS 0x00020000ul +#define NCSTYLE_PROTECT 0x00010000ul +#define NCSTYLE_ITALIC 0x01000000ul + + +// Set the specified style bits for the cell 'c', whether they're actively +// supported or not. +static inline void +cell_styles_set(cell* c, unsigned stylebits){ + c->attrword = (c->attrword & ~NCSTYLE_MASK) | ((stylebits & NCSTYLE_MASK)); +} + +// Extract the style bits from the cell's attrword. +static inline unsigned +cell_styles(const cell* c){ + return c->attrword & NCSTYLE_MASK; +} + +// Add the specified styles (in the LSBs) to the cell's existing spec, whether +// they're actively supported or not. +static inline void +cell_styles_on(cell* c, unsigned stylebits){ + c->attrword |= (stylebits & NCSTYLE_MASK; +} + +// Remove the specified styles (in the LSBs) from the cell's existing spec. +static inline void +cell_styles_off(cell* c, unsigned stylebits){ + c->attrword &= ~(stylebits & NCSTYLE_MASK); +} + +// does the cell contain an East Asian Wide codepoint? +static inline bool +cell_double_wide_p(const cell* c){ + return (c->channels & CELL_WIDEASIAN_MASK); +} + +// is the cell simple (a lone ASCII character, encoded as such)? +static inline bool +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 +cell_egc_idx(const cell* c){ + return c->gcluster - 0x80; +} + +// return a pointer to the NUL-terminated EGC referenced by 'c'. this pointer +// is invalidated by any further operation on the plane 'n', so...watch out! +const char* cell_extended_gcluster(const struct ncplane* n, const cell* c); + +// load up six cells with the EGCs necessary to draw a box. returns 0 on +// success, -1 on error. on error, any cells this function might +// have loaded before the error are cell_release()d. There must be at least +// six EGCs in gcluster. +static inline int +cells_load_box(struct ncplane* n, uint32_t attrs, uint64_t channels, + cell* ul, cell* ur, cell* ll, cell* lr, + cell* hl, cell* vl, const char* gclusters){ + int ulen; + if((ulen = cell_prime(n, ul, gclusters, attrs, channels)) > 0){ + if((ulen = cell_prime(n, ur, gclusters += ulen, attrs, channels)) > 0){ + if((ulen = cell_prime(n, ll, gclusters += ulen, attrs, channels)) > 0){ + if((ulen = cell_prime(n, lr, gclusters += ulen, attrs, channels)) > 0){ + if((ulen = cell_prime(n, hl, gclusters += ulen, attrs, channels)) > 0){ + if((ulen = cell_prime(n, vl, gclusters += ulen, attrs, channels)) > 0){ + return 0; + } + cell_release(n, hl); + } + cell_release(n, lr); + } + cell_release(n, ll); + } + cell_release(n, ur); + } + cell_release(n, ul); + } + return -1; +} + + +static inline int +cells_rounded_box(struct ncplane* n, uint32_t attr, uint64_t channels, + cell* ul, cell* ur, cell* ll, cell* lr, cell* hl, cell* vl){ + return cells_load_box(n, attr, channels, ul, ur, ll, lr, hl, vl, "╭╮╰╯─│"); +} + +static inline int +cells_double_box(struct ncplane* n, uint32_t attr, uint64_t channels, + cell* ul, cell* ur, cell* ll, cell* lr, cell* hl, cell* vl){ + return cells_load_box(n, attr, channels, ul, ur, ll, lr, hl, vl, "╔╗╚╝═║"); +} +``` + +### Cell channels API + +Helpers are provided to manipulate a `cell`'s `channels` member. They are all +implemented in terms of the lower-level [Channels API](#channels). + +```c +// Extract the 32-bit background channel from a cell. +static inline unsigned +cell_bchannel(const cell* cl){ + return channels_bchannel(cl->channels); +} + +// Extract the 32-bit foreground channel from a cell. +static inline unsigned +cell_fchannel(const cell* cl){ + return channels_fchannel(cl->channels); +} + +// Extract 24 bits of foreground RGB from 'cell', shifted to LSBs. +static inline unsigned +cell_fg(const cell* cl){ + return channels_fg(cl->channels); +} + +// Extract 24 bits of background RGB from 'cell', shifted to LSBs. +static inline unsigned +cell_bg(const cell* cl){ + return channels_bg(cl->channels); +} + +// Extract 2 bits of foreground alpha from 'cell', shifted to LSBs. +static inline unsigned +cell_fg_alpha(const cell* cl){ + return channels_fg_alpha(cl->channels); +} + +// Extract 2 bits of background alpha from 'cell', shifted to LSBs. +static inline unsigned +cell_bg_alpha(const cell* cl){ + return channels_bg_alpha(cl->channels); +} + +// Extract 24 bits of foreground RGB from 'cell', split into subcell. +static inline unsigned +cell_fg_rgb(const cell* cl, unsigned* r, unsigned* g, unsigned* b){ + return channels_fg_rgb(cl->channels, r, g, b); +} + +// Extract 24 bits of background RGB from 'cell', split into subcell. +static inline unsigned +cell_bg_rgb(const cell* cl, unsigned* r, unsigned* g, unsigned* b){ + return channels_bg_rgb(cl->channels, r, g, b); +} + +// Set the r, g, and b cell for the foreground component of this 64-bit +// 'cell' variable, and mark it as not using the default color. +static inline int +cell_set_fg_rgb(cell* cl, int r, int g, int b){ + return channels_set_fg_rgb(&cl->channels, r, g, b); +} + +// Same, but clipped to [0..255]. +static inline void +cell_set_fg_rgb_clipped(cell* cl, int r, int g, int b){ + channels_set_fg_rgb_clipped(&cl->channels, r, g, b); +} + +// Same, but with an assembled 24-bit RGB value. +static inline int +cell_set_fg(cell* c, uint32_t channel){ + return channels_set_fg(&c->channels, channel); +} + +// Set the r, g, and b cell for the background component of this 64-bit +// 'cell' variable, and mark it as not using the default color. +static inline int +cell_set_bg_rgb(cell* cl, int r, int g, int b){ + return channels_set_bg_rgb(&cl->channels, r, g, b); +} + +// Same, but clipped to [0..255]. +static inline void +cell_set_bg_rgb_clipped(cell* cl, int r, int g, int b){ + channels_set_bg_rgb_clipped(&cl->channels, r, g, b); +} + +// Same, but with an assembled 24-bit RGB value. +static inline int +cell_set_bg(cell* c, uint32_t channel){ + return channels_set_bg(&c->channels, channel); +} + +static inline int +cell_set_fg_alpha(cell* c, int alpha){ + return channels_set_fg_alpha(&c->channels, alpha); +} + +static inline int +cell_set_bg_alpha(cell* c, int alpha){ + return channels_set_bg_alpha(&c->channels, alpha); +} + +// Is the foreground using the "default foreground color"? +static inline bool +cell_fg_default_p(const cell* cl){ + return channels_fg_default_p(cl->channels); +} + +// Is the background using the "default background color"? The "default +// background color" must generally be used to take advantage of +// terminal-effected transparency. +static inline bool +cell_bg_default_p(const cell* cl){ + return channels_bg_default_p(cl->channels); +} + +// Use the default color for the foreground. +static inline void +cell_set_fg_default(cell* c){ + channels_set_fg_default(&c->channels); +} + +// Use the default color for the background. +static inline void +cell_set_bg_default(cell* c){ + channels_set_bg_default(&c->channels); +} + +``` + +## Reels + +ncreels are a complex UI abstraction offered by notcurses, derived from my +similar work in [outcurses](https://github.com/dankamongmen/ncreels#ncreels). + +The ncreel is a UI abstraction supported by notcurses in which +dynamically-created and -destroyed toplevel entities (referred to as tablets) +are arranged as if on a cylinder, allowing for infinite scrolling +(infinite scrolling can be disabled, resulting in a rectangle rather than a +cylinder). This works naturally with keyboard navigation, mouse scrolling wheels, +and touchpads (including the capacitive touchscreens of modern cell phones). +The term "reel" derives from slot machines. An ncreel initially has +no tablets; at any given time thereafter, it has zero or more tablets, and if +there is at least one tablet, one tablet is focused (and on-screen). If the +last tablet is removed, no tablet is focused. A tablet can support navigation +within the tablet, in which case there is an in-tablet focus for the focused +tablet, which can also move among elements within the tablet. + +The ncreel object tracks the size of the screen, the size, number, +information depth, and order of tablets, and the foci. It also draws the +optional borders around tablets and the optional border of the reel itself. It +knows nothing about the actual content of a tablet, save the number of lines it +occupies at each information depth. The typical control flow is that an +application receives events (from the UI or other event sources), and calls +into notcurses saying e.g. "Tablet 2 now has 40 valid lines of information". +notcurses might then call back into the application, asking it to draw some +line(s) from some tablet(s) at some particular coordinate of that tablet's +plane. Finally, control returns to the application, and the cycle starts anew. + +Each tablet might be wholly, partially, or not on-screen. notcurses always +places as much of the focused tablet as is possible on-screen (if the focused +tablet has more lines than the actual reel does, it cannot be wholly on-screen. +In this case, the focused subelements of the tablet are always on-screen). The +placement of the focused tablet depends on how it was reached (when moving to +the next tablet, offscreen tablets are brought onscreen at the bottom. When +moving to the previous tablet, offscreen tablets are brought onscreen at the +top. When moving to an arbitrary tablet which is neither the next nor previous +tablet, it will be placed in the center). + +The controlling application can, at any time, + +* Insert a new tablet somewhere in the reel (possibly off-screen) +* Delete a (possibly off-screen) tablet from the reel +* Change focus to the next or previous tablet, bringing it on-screen if it is off +* Change focus to some arbitrary other tablet, bringing it on-screen if it is off +* Expand or collapse the information depth of a tablet +* Change the content of a tablet, updating it if it is on-screen + * Remove content from a tablet, possibly resizing it, and possibly changing focus within the tablet + * Add content to the tablet, possibly resizing it, and possibly creating focus within the tablet +* Navigate within the focused tablet +* Create or destroy new planes atop the ncreel +* Indicate that the screen has been resized or needs be redrawn + +A special case arises when moving among the tablets of a reel having multiple +tablets, all of which fit entirely on-screen, and infinite scrolling is in use. +Normally, upon moving to the next tablet from the bottommost tablet, the +(offscreen) next tablet is pulled up into the bottom of the reel (the reverse +is true when moving to the previous tablet from the topmost). When all tablets +are onscreen with infinite scrolling, there are two possibilities: either the +focus scrolls (moving from the bottom tablet to the top tablet, for instance), +or the reel scrolls (preserving order among the tablets, but changing their +order on-screen). In this latter case, moving to the next tablet from the +bottommost tablet results in the tablet which is gaining focus being brought to +the bottom of the screen from the top, and all other tablets moving up on the +screen. Moving to the previous tablet from the topmost tablet results in the +bottommost tablet moving to the top of the screen, and all other tablets moving +down. This behavior matches the typical behavior precisely, and avoids a rude +UI discontinuity when the tablets grow to fill the entire screen (or shrink to +not fill it). If it is not desired, however, scrolling of focus can be +configured instead. + +```c +// An ncreel is a notcurses region devoted to displaying zero or more +// line-oriented, contained planes ("tablets") between which the user may +// navigate. If at least one tablet exists, there is an active tablet. As much +// of the active tablet as is possible is always displayed. If there is space +// left over, other tablets are included in the display. Tablets can come and go +// at any time, and can grow or shrink at any time. +// +// This structure is amenable to line- and page-based navigation via keystrokes, +// scrolling gestures, trackballs, scrollwheels, touchpads, and verbal commands. + +typedef struct ncreel_options { + // require this many rows and columns (including borders). otherwise, a + // message will be displayed stating that a larger terminal is necessary, and + // input will be queued. if 0, no minimum will be enforced. may not be + // negative. note that ncreel_create() does not return error if given a + // plane smaller than these minima; it instead patiently waits for the + // screen to get bigger. + int min_supported_cols; + int min_supported_rows; + + // use no more than this many rows and columns (including borders). may not be + // less than the corresponding minimum. 0 means no maximum. + int max_supported_cols; + int max_supported_rows; + + // desired offsets within the surrounding WINDOW (top right bottom left) upon + // creation / resize. an ncreel_move() operation updates these. + int toff, roff, boff, loff; + // is scrolling infinite (can one move down or up forever, or is an end + // reached?). if true, 'circular' specifies how to handle the special case of + // an incompletely-filled reel. + bool infinitescroll; + // is navigation circular (does moving down from the last tablet move to the + // first, and vice versa)? only meaningful when infinitescroll is true. if + // infinitescroll is false, this must be false. + bool circular; + // notcurses can draw a border around the ncreel, and also around the + // component tablets. inhibit borders by setting all valid bits in the masks. + // partially inhibit borders by setting individual bits in the masks. the + // appropriate attr and pair values will be used to style the borders. + // focused and non-focused tablets can have different styles. you can instead + // draw your own borders, or forgo borders entirely. + unsigned bordermask; // bitfield; 1s will not be drawn (see bordermaskbits) + uint64_t borderchan; // attributes used for ncreel border + unsigned tabletmask; // bitfield; same as bordermask but for tablet borders + uint64_t tabletchan; // tablet border styling channel + uint64_t focusedchan;// focused tablet border styling channel + uint64_t bgchannel; // background colors +} ncreel_options; + +struct nctablet; +struct ncreel; + +// Create an ncreel according to the provided specifications. Returns NULL on +// failure. 'nc' must be a valid plane, to which offsets are relative. Note that +// there might not be enough room for the specified offsets, in which case the +// ncreel will be clipped on the bottom and right. A minimum number of rows +// and columns can be enforced via popts. efd, if non-negative, is an eventfd +// that ought be written to whenever ncreel_touch() updates a tablet (this +// is useful in the case of nonblocking input). +struct ncreel* ncreel_create(struct ncplane* nc, const ncreel_options* popts, int efd); + +// Returns the ncplane on which this ncreel lives. +struct ncplane* ncreel_plane(struct ncreel* pr); + +// Tablet draw callback, provided a tablet (from which the ncplane and userptr +// may be extracted), the first column that may be used, the first row that may +// be used, the first column that may not be used, the first row that may not +// be used, and a bool indicating whether output ought be clipped at the top +// (true) or bottom (false). Rows and columns are zero-indexed, and both are +// relative to the tablet's plane. +// +// Regarding clipping: it is possible that the tablet is only partially +// displayed on the screen. If so, it is either partially present on the top of +// the screen, or partially present at the bottom. In the former case, the top +// is clipped (cliptop will be true), and output ought start from the end. In +// the latter case, cliptop is false, and output ought start from the beginning. +// +// Returns the number of lines of output, which ought be less than or equal to +// maxy - begy, and non-negative (negative values might be used in the future). +typedef int (*tabletcb)(struct nctablet* t, int begx, int begy, int maxx, + int maxy, bool cliptop); + +// Add a new tablet to the provided ncreel, having the callback object +// opaque. Neither, either, or both of after and before may be specified. If +// neither is specified, the new tablet can be added anywhere on the reel. If +// one or the other is specified, the tablet will be added before or after the +// specified tablet. If both are specified, the tablet will be added to the +// resulting location, assuming it is valid (after->next == before->prev); if +// it is not valid, or there is any other error, NULL will be returned. +struct nctablet* ncreel_add(struct ncreel* pr, struct nctablet* after, + struct nctablet* before, tabletcb cb, void* opaque); + +// Return the number of tablets. +int ncreel_tabletcount(const struct ncreel* pr); + +// Indicate that the specified tablet has been updated in a way that would +// change its display. This will trigger some non-negative number of callbacks +// (though not in the caller's context). +int ncreel_touch(struct ncreel* pr, struct nctablet* t); + +// Delete the tablet specified by t from the ncreel specified by pr. Returns +// -1 if the tablet cannot be found. +int ncreel_del(struct ncreel* pr, struct nctablet* t); + +// Delete the active tablet. Returns -1 if there are no tablets. +int ncreel_del_focused(struct ncreel* pr); + +// Move to the specified location within the containing plane. +int ncreel_move(struct ncreel* pr, int x, int y); + +// Redraw the ncreel in its entirety, for instance after +// clearing the screen due to external corruption, or a SIGWINCH. +int ncreel_redraw(struct ncreel* pr); + +// Return the focused tablet, if any tablets are present. This is not a copy; +// be careful to use it only for the duration of a critical section. +struct nctablet* ncreel_focused(struct ncreel* pr); + +// Change focus to the next tablet, if one exists +struct nctablet* ncreel_next(struct ncreel* pr); + +// Change focus to the previous tablet, if one exists +struct nctablet* ncreel_prev(struct ncreel* pr); + +// Destroy an ncreel allocated with ncreel_create(). Does not destroy the +// underlying plane. Returns non-zero on failure. +int ncreel_destroy(struct ncreel* pr); + +// Returns a pointer to a user pointer associated with this nctablet. +void* nctablet_userptr(struct nctablet* t); + +// Access the ncplane associated with this tablet, if one exists. +struct ncplane* nctablet_ncplane(struct nctablet* t); +``` + +### ncreel examples + +Let's say we have a screen of 11 lines, and 3 tablets of one line each. Both +a screen border and tablet borders are in use. The tablets are A, B, and C. +No gap is in use between tablets. Xs indicate focus. If B currently has focus, +and the next tablet is selected, the result would be something like: + +``` + ------------- ------------- + | --------- | | --------- | + | | A | | | | A | | + | --------- | | --------- | + | --------- | ---- "next tablet" ---> | --------- | + | |XX B XX| | | | B | | + | --------- | | --------- | + | --------- | | --------- | + | | C | | | |XX C XX| | + | --------- | | --------- | + ------------- ------------- +``` + +If instead the previous tablet had been selected, we would of course get: + +``` + ------------- ------------- + | --------- | | --------- | + | | A | | | |XX A XX| | + | --------- | | --------- | + | --------- | ---- "prev tablet" ---> | --------- | + | |XX B XX| | | | B | | + | --------- | | --------- | + | --------- | | --------- | + | | C | | | | C | | + | --------- | | --------- | + ------------- ------------- +``` + +If A instead has the focus, choosing the "next tablet" is trivial: the tablets +do not change, and focus shifts to B. If we choose the "previous tablet", there +are three possibilities: + +* Finite scrolling: No change. The tablets stay in place. A remains focused. + +``` + ------------- ------------- + | --------- | | --------- | + | |XX A XX| | | |XX A XX| | + | --------- | | --------- | + | --------- | ---- "prev tablet" ---> | --------- | + | | B | | (finite scroll) | | B | | + | --------- | | --------- | + | --------- | | --------- | + | | C | | | | C | | + | --------- | | --------- | + ------------- ------------- +``` + +* Infinite scrolling with rotation: Focus shifts to C, which moves to the top: + +``` + ------------- ------------- + | --------- | | --------- | + | |XX A XX| | | |XX C XX| | + | --------- | | --------- | + | --------- | ---- "prev tablet" ---> | --------- | + | | B | | (infinite scroll with | | A | | + | --------- | rotation) | --------- | + | --------- | | --------- | + | | C | | | | B | | + | --------- | | --------- | + ------------- ------------- +``` + +* Infinite scrolling with focus rotation: Focus shifts to C, and moves to the bottom: + +``` + ------------- ------------- + | --------- | | --------- | + | |XX A XX| | | | A | | + | --------- | | --------- | + | --------- | ---- "prev tablet" ---> | --------- | + | | B | | (infinite scroll with | | B | | + | --------- | focus rotation) | --------- | + | --------- | | --------- | + | | C | | | |XX C XX| | + | --------- | | --------- | + ------------- ------------- +``` + +Now imagine us to have the same 3 tablets, but each is now 4 lines. It is +impossible to have two of these tablets wholly onscreen at once, let alone all +three. If we started with A focused and at the top, the result after all three +tablets have grown will be: + +``` + ------------- ------------- + | --------- | | --------- | A remains at the top, and + | |XX A XX| | | |XXXXXXX| | is wholly on-screen. B is + | --------- | | |XX A XX| | below it, but we can show + | --------- | ---- "grow tablet" ---> | |XXXXXXX| | only the first two lines. + | | B | | A (focused) | |XXXXXXX| | C has been pushed + | --------- | | --------- | off-screen. + | --------- | | --------- | + | | C | | | | | | + | --------- | | | B | | + ------------- ------------- +``` + +When a tablet is enlarged, it grows towards the nearest boundary, unless that +would result in the focused tablet being moved, in which case the growing +tablet instead grows in the other direction (if the tablet is in the middle +of the screen exactly, it grows down). There is one exception to this rule: if +the tablets are not making full use of the screen, growth is always down (the +screen is always filled from the top), even if it moves the focused tablet. + +A 12-line screen has three tablets: A (2 lines), B (1 line), C (1 line), filling +the screen exactly. B is focused, and grows two lines: + +``` + ------------- ------------- + | --------- | | --------- | B grows down, since it is + | | A | | | | A | | closer to the bottom (3 + | | | | | | | | lines) than the top (4 + | --------- | ---- "grow tablet" ---> | --------- | lines). C is pushed almost + | --------- | B (focused) | --------- | entirely off-screen. A is + | |XX B XX| | | |XXXXXXX| | untouched. + | --------- | | |XX B XX| | + | --------- | | |XXXXXXX| | + | | C | | | --------- | + | --------- | | --------- | + ------------- ------------- +``` + +Starting with the same situation, A grows by 2 lines instead: + +``` + ------------- ------------- + | --------- | | | | | A grows up. It would have + | | A | | | | A | | grown down, but that would + | | | | | | | | have moved B, which has + | --------- | ---- "grow tablet" ---> | --------- | the focus. B and C remain + | --------- | A (not focused) | --------- | where they are; A moves + | |XX B XX| | | |XX B XX| | partially off-screen. + | --------- | | --------- | + | --------- | | --------- | + | | C | | | | C | | + | --------- | | --------- | + ------------- ------------- +``` + +If we started with the same situation, and B grew by 7 lines, it would first +push C entirely off-screen (B would then have four lines of text), and then +push A off-screen. B would then have eight lines of text, the maximum on a +12-line screen with both types of borders. + +## Widgets + +Selectors: + +``` + ╭──────────────────────────╮ + │This is the primary header│ +╭──────────────────────this is the secondary header──────╮ +│ │ +│ option1 Long text #1 │ +│ option2 Long text #2 │ +│ option3 Long text #3 │ +│ option4 Long text #4 │ +│ option5 Long text #5 │ +│ option6 Long text #6 │ +│ │ +╰────────────────────────────────────here's the footer───╯ +``` + +Multiselectors: + +``` + ╭────────────────────────────────────────────────────────────────╮ + │ this is truly an awfully long example of a MULTISELECTOR title │ +╭─────┴─────────────────────────────pick one (you will die regardless)─┤ +│ ↑ │ +│ ☐ 1 Across the Atlantic Ocean, there was a place called North America│ +│ ☐ 2 Discovered by an Italian in the employ of the queen of Spain │ +│ ☒ 3 Colonized extensively by the Spanish and the French │ +│ ☐ 4 Developed into a rich nation by Dutch-supplied African slaves │ +│ ☐ 5 And thus became the largest English-speaking nation on earth │ +│ ☐ 6 Namely, the United States of America │ +│ ☐ 7 The inhabitants of the United States called themselves Yankees │ +│ ☒ 8 For some reason │ +│ ☐ 9 And, eventually noticing the rest of the world was there, │ +│ ☐ 10 Decided to rule it. │ +│ ↓ │ +╰─────────────────────────press q to exit (there is sartrev("no exit")─╯ +``` + +Menus: + +``` + Schwarzgerät File Help +xxxxxxxxxxxxxxxx╭─────────────╮xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +xxxxxxxxxxxxxxxx│New Ctrl+n│xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +xxxxxxxxxxxxxxxx│Open Ctrl+o│xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +xxxxxxxxxxxxxxxx│Close Ctrl+c│xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +xxxxxxxxxxxxxxxx├─────────────┤xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +xxxxxxxxxxxxxxxx│Quit Ctrl+q│xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +xxxxxxxxxxxxxxxx╰─────────────╯xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +``` + +## Channels + +A channel encodes 24 bits of RGB color, using 8 bits for each component. It +additionally provides 2 bits of alpha channel, a bit for selecting terminal +default colors, and a bit to indicate whether it describes a Wide East Asian +character. The remaining four bits are reserved. Typically two channels are +bound together in a 64-bit unsigned integer (`uint64_t`), with eight bits +currently going unused. There is such a double-channel in every `cell` and +`ncplane` object. + +Usually, the higher-level `ncplane` and `cell` functionality ought be used. It +will sometimes be necessary, however, to muck with channels at their lowest +level. The channel API facilitates such muckery. All channel-related `ncplane` +and `cell` functionality is implemented in terms of this API. + +```c +// Extract the 8-bit red component from a 32-bit channel. +static inline unsigned +channel_r(unsigned channel){ + return (channel & 0xff0000u) >> 16u; +} + +// Extract the 8-bit green component from a 32-bit channel. +static inline unsigned +channel_g(unsigned channel){ + return (channel & 0x00ff00u) >> 8u; +} + +// Extract the 8-bit blue component from a 32-bit channel. +static inline unsigned +channel_b(unsigned channel){ + return (channel & 0x0000ffu); +} + +// Extract the three 8-bit R/G/B components from a 32-bit channel. +static inline unsigned +channel_rgb(unsigned channel, unsigned* r, unsigned* g, unsigned* b){ + *r = channel_r(channel); + *g = channel_g(channel); + *b = channel_b(channel); + return channel; +} + +// Set the three 8-bit components of a 32-bit channel, and mark it as not using +// the default color. Retain the other bits unchanged. +static inline int +channel_set_rgb(unsigned* channel, int r, int g, int b){ + if(r >= 256 || g >= 256 || b >= 256){ + return -1; + } + if(r < 0 || g < 0 || b < 0){ + return -1; + } + unsigned c = (r << 16u) | (g << 8u) | b; + c |= CELL_BGDEFAULT_MASK; + const uint64_t mask = CELL_BGDEFAULT_MASK | CELL_BG_MASK; + *channel = (*channel & ~mask) | c; + return 0; +} + +// Same, but provide an assembled, packed 24 bits of rgb. +static inline int +channel_set(unsigned* channel, unsigned rgb){ + if(rgb > 0xffffffu){ + return -1; + } + *channel = (*channel & ~CELL_BG_MASK) | CELL_BGDEFAULT_MASK | rgb; + return 0; +} + +// Extract the 2-bit alpha component from a 32-bit channel. +static inline unsigned +channel_alpha(unsigned channel){ + return (channel & CELL_ALPHA_MASK) >> CELL_ALPHA_SHIFT; +} + +// Set the 2-bit alpha component of the 32-bit channel. +static inline int +channel_set_alpha(unsigned* channel, int alpha){ + if(alpha < CELL_ALPHA_OPAQUE || alpha > CELL_ALPHA_TRANS){ + return -1; + } + *channel = (alpha << CELL_ALPHA_SHIFT) | (*channel & ~CELL_ALPHA_MASK); + return 0; +} + +// Is this channel using the "default color" rather than its RGB? +static inline bool +channel_default_p(unsigned channel){ + return !(channel & CELL_BGDEFAULT_MASK); +} + +// Mark the channel as using its default color. +static inline unsigned +channel_set_default(unsigned* channel){ + return *channel &= ~CELL_BGDEFAULT_MASK; +} + +// Extract the 32-bit background channel from a channel pair. +static inline unsigned +channels_bchannel(uint64_t channels){ + return channels & 0xfffffffflu; +} + +// Extract the 32-bit foreground channel from a channel pair. +static inline unsigned +channels_fchannel(uint64_t channels){ + return channels_bchannel(channels >> 32u); +} + +// Extract 24 bits of foreground RGB from 'channels', shifted to LSBs. +static inline unsigned +channels_fg(uint64_t channels){ + return channels_fchannel(channels) & CELL_BG_MASK; +} + +// Extract 24 bits of background RGB from 'channels', shifted to LSBs. +static inline unsigned +channels_bg(uint64_t channels){ + return channels_bchannel(channels) & CELL_BG_MASK; +} + +// Extract 2 bits of foreground alpha from 'channels', shifted to LSBs. +static inline unsigned +channels_fg_alpha(uint64_t channels){ + return channel_alpha(channels_fchannel(channels)); +} + +// Extract 2 bits of background alpha from 'channels', shifted to LSBs. +static inline unsigned +channels_bg_alpha(uint64_t channels){ + return channel_alpha(channels_bchannel(channels)); +} + +// Extract 24 bits of foreground RGB from 'channels', split into subchannels. +static inline unsigned +channels_fg_rgb(uint64_t channels, unsigned* r, unsigned* g, unsigned* b){ + return channel_rgb(channels_fchannel(channels), r, g, b); +} + +// Extract 24 bits of background RGB from 'channels', split into subchannels. +static inline unsigned +channels_bg_rgb(uint64_t channels, unsigned* r, unsigned* g, unsigned* b){ + return channel_rgb(channels_bchannel(channels), r, g, b); +} + +// Set the r, g, and b channels for the foreground component of this 64-bit +// 'channels' variable, and mark it as not using the default color. +static inline int +channels_set_fg_rgb(uint64_t* channels, int r, int g, int b){ + unsigned channel = channels_fchannel(*channels); + if(channel_set_rgb(&channel, r, g, b) < 0){ + return -1; + } + *channels = ((uint64_t)channel << 32llu) | (*channels & 0xffffffffllu); + return 0; +} + +// Set the r, g, and b channels for the background component of this 64-bit +// 'channels' variable, and mark it as not using the default color. +static inline int +channels_set_bg_rgb(uint64_t* channels, int r, int g, int b){ + unsigned channel = channels_bchannel(*channels); + if(channel_set_rgb(&channel, r, g, b) < 0){ + return -1; + } + *channels = (*channels & 0xffffffff00000000llu) | channel; + return 0; +} + +// Same, but set an assembled 32 bit channel at once. +static inline int +channels_set_fg(uint64_t* channels, unsigned rgb){ + unsigned channel = channels_fchannel(*channels); + if(channel_set(&channel, rgb) < 0){ + return -1; + } + *channels = ((uint64_t)channel << 32llu) | (*channels & 0xffffffffllu); + return 0; +} + +static inline int +channels_set_bg(uint64_t* channels, unsigned rgb){ + unsigned channel = channels_bchannel(*channels); + if(channel_set(&channel, rgb) < 0){ + return -1; + } + *channels = (*channels & 0xffffffff00000000llu) | channel; + return 0; +} + +// Set the 2-bit alpha component of the foreground channel. +static inline int +channels_set_fg_alpha(uint64_t* channels, int alpha){ + unsigned channel = channels_fchannel(*channels); + if(channel_set_alpha(&channel, alpha) < 0){ + return -1; + } + *channels = ((uint64_t)channel << 32llu) | (*channels & 0xffffffffllu); + return 0; +} + +// Set the 2-bit alpha component of the background channel. +static inline int +channels_set_bg_alpha(uint64_t* channels, int alpha){ + if(alpha == CELL_ALPHA_HIGHCONTRAST){ // forbidden for background alpha + return -1; + } + unsigned channel = channels_bchannel(*channels); + if(channel_set_alpha(&channel, alpha) < 0){ + return -1; + } + *channels = (*channels & 0xffffffff00000000llu) | channel; + return 0; +} + +// Is the foreground using the "default foreground color"? +static inline bool +channels_fg_default_p(uint64_t channels){ + return channel_default_p(channels_fchannel(channels)); +} + +// Is the background using the "default background color"? The "default +// background color" must generally be used to take advantage of +// terminal-effected transparency. +static inline bool +channels_bg_default_p(uint64_t channels){ + return channel_default_p(channels_bchannel(channels)); +} + +// Mark the foreground channel as using its default color. +static inline uint64_t +channels_set_fg_default(uint64_t* channels){ + unsigned channel = channels_fchannel(*channels); + channel_set_default(&channel); + *channels = ((uint64_t)channel << 32llu) | (*channels & 0xffffffffllu); + return *channels; +} + +// Mark the foreground channel as using its default color. +static inline uint64_t +channels_set_bg_default(uint64_t* channels){ + unsigned channel = channels_bchannel(*channels); + channel_set_default(&channel); + *channels = (*channels & 0xffffffff00000000llu) | channel; + return *channels; +} +``` +