2019-11-17 02:38:25 +00:00
|
|
|
# notcurses
|
2019-12-13 22:14:15 +00:00
|
|
|
blingful TUI library for modern terminal emulators. definitely not curses.
|
|
|
|
|
|
|
|
by [nick black](https://nick-black.com/dankwiki/index.php/Hack_on) (<nickblack@linux.com>)
|
2019-11-17 10:04:41 +00:00
|
|
|
|
2019-11-25 05:26:35 +00:00
|
|
|
* [Introduction](#introduction)
|
2019-11-29 03:32:49 +00:00
|
|
|
* [Requirements](#requirements)
|
2019-11-29 06:58:45 +00:00
|
|
|
* [Use](#use)
|
|
|
|
* [Input](#input)
|
2019-11-25 05:26:35 +00:00
|
|
|
* [Planes](#planes)
|
|
|
|
* [Cells](#cells)
|
2019-12-05 05:34:16 +00:00
|
|
|
* [Multimedia](#multimedia)
|
|
|
|
* [Panelreels](#panelreels)
|
2019-12-14 10:33:34 +00:00
|
|
|
* [Channels](#channels)
|
2019-12-04 04:34:08 +00:00
|
|
|
* [Perf](#perf)
|
2019-11-29 06:58:45 +00:00
|
|
|
* [Included tools](#included-tools)
|
2019-11-25 05:26:35 +00:00
|
|
|
* [Differences from NCURSES](#differences-from-ncurses)
|
2019-11-29 03:32:49 +00:00
|
|
|
* [Features missing relative to NCURSES](#features-missing-relative-to-ncurses)
|
2019-12-06 23:33:20 +00:00
|
|
|
* [Adapting NCURSES programs](#adapting-ncurses-programs)
|
2019-12-13 18:00:29 +00:00
|
|
|
* [Supplemental material](#supplemental-material)
|
|
|
|
* [Useful links](#useful-links)
|
|
|
|
* [History](#history)
|
|
|
|
* [Thanks](#thanks)
|
2019-11-25 05:26:35 +00:00
|
|
|
|
2019-11-17 10:04:41 +00:00
|
|
|
[![Build Status](https://drone.dsscaw.com:4443/api/badges/dankamongmen/notcurses/status.svg)](https://drone.dsscaw.com:4443/dankamongmen/notcurses)
|
2019-11-27 01:07:27 +00:00
|
|
|
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
|
2019-11-18 04:09:06 +00:00
|
|
|
|
2019-11-25 05:26:35 +00:00
|
|
|
## Introduction
|
|
|
|
|
2019-11-18 05:05:32 +00:00
|
|
|
* **What it is**: a library facilitating complex TUIs on modern terminal
|
2019-11-23 21:09:37 +00:00
|
|
|
emulators, supporting vivid colors and Unicode to the maximum degree
|
|
|
|
possible. Many tasks delegated to Curses can be achieved using notcurses
|
|
|
|
(and vice versa).
|
2019-11-18 04:09:06 +00:00
|
|
|
|
2019-11-18 05:05:32 +00:00
|
|
|
* **What it is not**: a source-compatible X/Open Curses implementation, nor a
|
2019-11-18 04:09:06 +00:00
|
|
|
replacement for NCURSES on existing systems, nor a widely-ported and -tested
|
|
|
|
bedrock of Open Source, nor a battle-proven, veteran library.
|
|
|
|
|
|
|
|
notcurses abandons the X/Open Curses API bundled as part of the Single UNIX
|
2019-11-19 11:53:50 +00:00
|
|
|
Specification. The latter shows its age, and seems not capable of making use of
|
2019-11-18 05:05:32 +00:00
|
|
|
terminal functionality such as unindexed 24-bit color ("DirectColor", not to be
|
2019-11-23 14:12:00 +00:00
|
|
|
confused with 8-bit indexed 24-bit color, aka "TrueColor" or (by NCURSES) as
|
|
|
|
"extended color"). For some necessary
|
2019-11-18 05:05:32 +00:00
|
|
|
background, consult Thomas E. Dickey's superb and authoritative [NCURSES
|
|
|
|
FAQ](https://invisible-island.net/ncurses/ncurses.faq.html#xterm_16MegaColors).
|
2019-11-23 14:06:54 +00:00
|
|
|
As such, notcurses is not a drop-in Curses replacement. It is almost certainly
|
|
|
|
less portable, and definitely tested on less hardware. Sorry about that.
|
|
|
|
Ultimately, I hope to properly support all terminals *supporting the features
|
|
|
|
necessary for complex TUIs*. I would argue that teletypes etc. are
|
|
|
|
fundamentally unsuitable. Most operating systems seem reasonable targets, but I
|
|
|
|
only have Linux and FreeBSD available for testing.
|
2019-11-21 16:00:40 +00:00
|
|
|
|
2019-11-23 14:07:35 +00:00
|
|
|
Whenever possible, notcurses makes use of the Terminfo library shipped with
|
|
|
|
NCURSES, benefiting greatly from its portability and thoroughness.
|
2019-11-18 04:09:06 +00:00
|
|
|
|
|
|
|
notcurses opens up advanced functionality for the interactive user on
|
|
|
|
workstations, phones, laptops, and tablets, at the expense of e.g.
|
|
|
|
industrial and retail terminals (or even the Linux virtual console,
|
2019-11-18 17:16:26 +00:00
|
|
|
which offers only eight colors and limited glyphs).
|
2019-11-18 04:09:06 +00:00
|
|
|
|
|
|
|
Why use this non-standard library?
|
|
|
|
|
2019-12-14 10:33:34 +00:00
|
|
|
* Thread safety, and efficient use in parallel programs, has been a design
|
|
|
|
consideration from the beginning.
|
2019-11-18 04:09:06 +00:00
|
|
|
|
2019-12-14 10:33:34 +00:00
|
|
|
* A svelter design than that codified by X/Open.
|
|
|
|
* Exported identifiers are prefixed to avoid common namespace collisions.
|
|
|
|
* The library object exports a minimal set of symbols. Where reasonable,
|
|
|
|
`static inline` header-only code is used. This facilitates compiler
|
|
|
|
optimizations, and reduces loader time.
|
2019-11-18 04:09:06 +00:00
|
|
|
|
2019-12-14 10:33:34 +00:00
|
|
|
* All APIs natively suport UTF-8. The `cell` API is based around Unicode's
|
|
|
|
[Extended Grapheme Cluster](https://unicode.org/reports/tr29/) concept.
|
|
|
|
|
|
|
|
* Visual features including images, fonts, video, high-contrast text, sprites,
|
|
|
|
and transparent regions. All APIs natively support 24-bit color, quantized
|
|
|
|
down as necessary for the terminal.
|
2019-11-18 17:16:26 +00:00
|
|
|
|
2019-11-23 14:05:32 +00:00
|
|
|
* It's Apache2-licensed in its entirety, as opposed to the
|
2019-11-23 14:12:00 +00:00
|
|
|
[drama in several acts](https://invisible-island.net/ncurses/ncurses-license.html)
|
2019-11-23 14:05:32 +00:00
|
|
|
that is the NCURSES license (the latter is [summarized](https://invisible-island.net/ncurses/ncurses-license.html#issues_freer)
|
|
|
|
as "a restatement of MIT-X11").
|
|
|
|
|
2019-12-14 10:33:34 +00:00
|
|
|
Much of the above can be had with NCURSES, but they're not what NCURSES was
|
|
|
|
*designed* for. The most fundamental advantage in my mind, though, is
|
2019-12-14 11:40:01 +00:00
|
|
|
that notcurses is of the multithreaded era. On the other hand, if you're
|
|
|
|
targeting industrial or critical applications, or wish to benefit from the
|
|
|
|
time-tested reliability and portability of Curses, you should by all means use
|
|
|
|
that fine library.
|
2019-11-21 14:06:36 +00:00
|
|
|
|
2019-11-25 03:24:41 +00:00
|
|
|
## Requirements
|
|
|
|
|
|
|
|
* A C11 and a C++14 compiler
|
|
|
|
* CMake 3.13.0+
|
2019-12-08 21:48:58 +00:00
|
|
|
* From NCURSES: terminfo 6.1+
|
2019-11-29 03:34:55 +00:00
|
|
|
* From FFMpeg: libswscale 5.0+, libavformat 57.0+, libavutil 56.0+
|
2019-11-25 03:24:41 +00:00
|
|
|
|
2019-11-29 06:58:45 +00:00
|
|
|
## Use
|
2019-11-21 14:06:36 +00:00
|
|
|
|
|
|
|
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 `LC_ALL`
|
2019-12-12 13:13:14 +00:00
|
|
|
locale. It is usually appropriate to use `setlocale(LC_ALL, "")`, relying on
|
2019-12-14 01:53:18 +00:00
|
|
|
the user to properly set the `LANG` environment variable. notcurses will
|
|
|
|
refuse to start if `nl_langinfo(3)` doesn't indicate UTF-8.
|
2019-11-21 14:06:36 +00:00
|
|
|
|
|
|
|
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
|
2019-11-25 02:43:44 +00:00
|
|
|
// Get a human-readable string describing the running notcurses version.
|
2019-11-21 16:03:02 +00:00
|
|
|
const char* notcurses_version(void);
|
|
|
|
|
2019-11-25 02:43:44 +00:00
|
|
|
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
|
|
|
|
|
2019-11-21 14:06:36 +00:00
|
|
|
// 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
|
2019-11-21 16:00:40 +00:00
|
|
|
// use of the "alternate screen". This flag inhibits use of smcup/rmcup.
|
2019-11-21 14:06:36 +00:00
|
|
|
bool inhibit_alternate_screen;
|
2019-11-27 15:43:03 +00:00
|
|
|
// By default, we hide the cursor if possible. This flag inhibits use of
|
|
|
|
// the civis capability, retaining the cursor.
|
|
|
|
bool retain_cursor;
|
2019-11-27 07:08:35 +00:00
|
|
|
// 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;
|
2019-11-29 09:06:02 +00:00
|
|
|
// 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;
|
2019-11-21 14:06:36 +00:00
|
|
|
} notcurses_options;
|
|
|
|
|
2019-12-12 12:59:48 +00:00
|
|
|
// 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 to initialize terminfo.
|
2019-12-13 17:16:05 +00:00
|
|
|
struct notcurses* notcurses_init(const notcurses_options* opts, FILE* fp);
|
2019-11-21 14:06:36 +00:00
|
|
|
|
|
|
|
// 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.
|
2019-11-21 16:11:36 +00:00
|
|
|
|
2019-12-14 12:04:43 +00:00
|
|
|
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:
|
2019-11-21 16:11:36 +00:00
|
|
|
|
|
|
|
```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);
|
|
|
|
```
|
|
|
|
|
2019-12-14 12:04:43 +00:00
|
|
|
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);
|
|
|
|
```
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
2019-12-13 23:02:35 +00:00
|
|
|
Utility functions operating on the toplevel `notcurses` object include:
|
|
|
|
|
|
|
|
```c
|
|
|
|
// Refresh our idea of the terminal's dimensions, reshaping the standard plane
|
|
|
|
// if necessary. Without a call to this function following a terminal resize
|
|
|
|
// (as signaled via SIGWINCH), notcurses_render() might not function properly.
|
2019-12-14 12:04:43 +00:00
|
|
|
// References to ncplanes (and the egcpools underlying cells) remain valid
|
|
|
|
// following a resize operation, but the cursor might have changed position.
|
2019-12-13 23:02:35 +00:00
|
|
|
int notcurses_resize(struct notcurses* n, int* RESTRICT y, int* RESTRICT x);
|
|
|
|
|
|
|
|
// 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.
|
|
|
|
int notcurses_refresh(struct notcurses* n);
|
|
|
|
|
|
|
|
// 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* notcurses_newplane(struct notcurses* nc, int rows, int cols,
|
|
|
|
int yoff, int xoff, void* opaque);
|
|
|
|
|
|
|
|
// Returns a 16-bit bitmask in the LSBs of supported curses-style attributes
|
|
|
|
// (CELL_STYLE_UNDERLINE, CELL_STYLE_BOLD, etc.) The attribute is only
|
|
|
|
// indicated as supported if the terminal can support it together with color.
|
2019-12-14 12:04:43 +00:00
|
|
|
// For more information, see the "ncv" capability in terminfo(5).
|
2019-12-13 23:02:35 +00:00
|
|
|
unsigned notcurses_supported_styles(const struct notcurses* nc);
|
|
|
|
|
2019-12-14 12:04:43 +00:00
|
|
|
// 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.
|
2019-12-13 23:02:35 +00:00
|
|
|
int notcurses_palette_size(const struct notcurses* nc);
|
|
|
|
```
|
|
|
|
|
2019-11-29 06:58:45 +00:00
|
|
|
### Input
|
|
|
|
|
|
|
|
Input can currently be taken only from `stdin`, but on the plus side, stdin
|
2019-12-14 12:04:43 +00:00
|
|
|
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`.
|
2019-11-29 06:58:45 +00:00
|
|
|
|
2019-11-29 08:25:55 +00:00
|
|
|
Like NCURSES, notcurses will watch for escape sequences, check them against the
|
2019-12-10 11:10:01 +00:00
|
|
|
terminfo database, and return them as special keys (we hijack the Private Use
|
2019-12-14 12:04:43 +00:00
|
|
|
Area for special keys, specifically Supplementary Private Use Area B (u100000
|
|
|
|
through u10ffffd). Unlike NCURSES, the fundamental unit of input is the
|
2019-12-10 11:10:01 +00:00
|
|
|
UTF8-encoded Unicode codepoint. Note, however, that only one codepoint is
|
|
|
|
returned at a time (as opposed to an entire EGC).
|
2019-11-29 08:25:55 +00:00
|
|
|
|
2019-12-14 12:04:43 +00:00
|
|
|
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.
|
|
|
|
|
2019-11-29 06:58:45 +00:00
|
|
|
```c
|
2019-11-29 08:25:55 +00:00
|
|
|
// 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
|
2019-12-11 05:47:46 +00:00
|
|
|
// 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.
|
2019-11-29 08:25:55 +00:00
|
|
|
//
|
2019-12-08 20:31:10 +00:00
|
|
|
// 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.
|
2019-11-29 08:25:55 +00:00
|
|
|
//
|
|
|
|
// In the case of a valid read, a positive value is returned corresponding to
|
2019-12-08 20:31:10 +00:00
|
|
|
// the number of bytes in the UTF-8 character, or '1' for all special keys.
|
2019-12-05 05:26:33 +00:00
|
|
|
// 0 is returned to indicate that no input was available, but only by
|
|
|
|
// notcurses_getc(). Otherwise (including on EOF) -1 is returned.
|
2019-12-10 11:10:01 +00:00
|
|
|
|
|
|
|
// is this wide character a Supplementary Private Use Area-B codepoint?
|
|
|
|
static inline bool
|
|
|
|
wchar_supppuab_p(wchar_t w){
|
|
|
|
return w >= 0x100000 && w <= 0x10fffd;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define suppuabize(w) ((w) + 0x100000)
|
|
|
|
|
|
|
|
// Special composed key defintions. These values are added to 0x100000.
|
|
|
|
#define NCKEY_INVALID suppuabize(0)
|
|
|
|
#define NCKEY_RESIZE suppuabize(1) // generated interally 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)
|
2019-12-13 22:18:54 +00:00
|
|
|
#define NCKEY_BACKSPACE suppuabize(8) // backspace (sometimes)
|
2019-12-10 11:10:01 +00:00
|
|
|
#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)
|
|
|
|
#define NCKEY_F05 suppuabize(25)
|
|
|
|
#define NCKEY_F06 suppuabize(26)
|
|
|
|
#define NCKEY_F07 suppuabize(27)
|
|
|
|
#define NCKEY_F08 suppuabize(28)
|
|
|
|
#define NCKEY_F09 suppuabize(29)
|
|
|
|
#define NCKEY_F10 suppuabize(30)
|
2019-12-13 22:18:54 +00:00
|
|
|
// ... leave room for 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)
|
2019-11-29 08:25:55 +00:00
|
|
|
|
2019-12-14 01:10:39 +00:00
|
|
|
// See ppoll(2) for more detail. Provide a NULL 'ts' to block at lenghth, a 'ts'
|
2019-12-05 05:26:33 +00:00
|
|
|
// 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.
|
2019-12-14 01:10:39 +00:00
|
|
|
// Returns a single Unicode code point, or (wchar_t)-1 on error. 'sigmask' may
|
|
|
|
// be NULL.
|
2019-12-13 17:16:05 +00:00
|
|
|
int notcurses_getc(struct notcurses* n, const struct timespec* ts, sigset_t* sigmask);
|
2019-12-05 05:26:33 +00:00
|
|
|
|
|
|
|
static inline int
|
2019-12-10 11:10:01 +00:00
|
|
|
notcurses_getc_nblock(struct notcurses* n){
|
2019-12-05 05:26:33 +00:00
|
|
|
sigset_t sigmask;
|
|
|
|
sigfillset(&sigmask);
|
|
|
|
struct timespec ts = { .tv_sec = 0, .tv_nsec = 0 };
|
2019-12-10 11:10:01 +00:00
|
|
|
return notcurses_getc(n, &ts, &sigmask);
|
2019-12-05 05:26:33 +00:00
|
|
|
}
|
|
|
|
|
2019-12-10 11:10:01 +00:00
|
|
|
static inline wchar_t
|
|
|
|
notcurses_getc_blocking(struct notcurses* n){
|
2019-12-05 05:26:33 +00:00
|
|
|
sigset_t sigmask;
|
|
|
|
sigemptyset(&sigmask);
|
2019-12-10 11:10:01 +00:00
|
|
|
return notcurses_getc(n, NULL, &sigmask);
|
2019-12-05 05:26:33 +00:00
|
|
|
}
|
2019-11-29 06:58:45 +00:00
|
|
|
```
|
|
|
|
|
2019-11-25 05:24:55 +00:00
|
|
|
### Planes
|
2019-11-21 16:11:36 +00:00
|
|
|
|
|
|
|
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.
|
|
|
|
Each `ncplane` has a current writing state (cursor position, foreground and
|
2019-12-13 17:16:53 +00:00
|
|
|
background color, etc.), a backing array of `cell`s, and a z-index. If
|
|
|
|
opaque, a `cell` on a higher `ncplane` completely obstructs a corresponding `cell`
|
2019-11-21 16:11:36 +00:00
|
|
|
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`.
|
2019-11-23 14:05:32 +00:00
|
|
|
|
2019-12-01 03:58:50 +00:00
|
|
|
```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 'keepy' or 'keepx' is
|
|
|
|
// non-zero, both must be non-zero.
|
|
|
|
//
|
|
|
|
// Essentially, the kept material does not move. It serves to anchor the
|
|
|
|
// resized plane. If there is no kept material, the plane can move freely:
|
|
|
|
// it is possible to implement ncplane_move() in terms of ncplane_resize().
|
|
|
|
int ncplane_resize(struct ncplane* n, int keepy, int keepx, int keepleny,
|
|
|
|
int keeplenx, int yoff, int xoff, int ylen, int xlen);
|
|
|
|
|
|
|
|
// Destroy the specified ncplane. None of its contents will be visible after
|
|
|
|
// the next call to notcurses_render(). It is an error to attempt to destroy
|
|
|
|
// the standard plane.
|
|
|
|
int ncplane_destroy(struct ncplane* ncp);
|
|
|
|
|
2019-12-01 04:26:07 +00:00
|
|
|
// Set the ncplane's background cell to this cell. It will be rendered anywhere
|
|
|
|
// that the ncplane's gcluster is 0. The default background is all zeroes.
|
|
|
|
// Erasing the ncplane does not eliminate the background.
|
2019-12-01 20:05:10 +00:00
|
|
|
int ncplane_set_background(struct ncplane* ncp, const cell* c);
|
2019-12-01 04:26:07 +00:00
|
|
|
|
|
|
|
// Extract the ncplane's background cell into 'c'.
|
2019-12-01 20:05:10 +00:00
|
|
|
int ncplane_background(struct ncplane* ncp, cell* c);
|
2019-12-01 04:26:07 +00:00
|
|
|
|
2019-12-01 03:58:50 +00:00
|
|
|
// Move this plane relative to the standard plane. It is an error to attempt to
|
|
|
|
// move the standard plane.
|
2019-12-10 11:02:49 +00:00
|
|
|
int ncplane_move_yx(struct ncplane* n, int y, int x);
|
2019-12-01 03:58:50 +00:00
|
|
|
|
|
|
|
// Get the origin of this plane relative to the standard plane.
|
|
|
|
void ncplane_yx(const struct ncplane* n, int* RESTRICT y, int* RESTRICT x);
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
|
|
|
|
// Retrieve the cell at the cursor location on the specified plane, returning
|
|
|
|
// it in 'c'. This copy is safe to use until the ncplane is destroyed/erased.
|
|
|
|
int ncplane_at_cursor(struct ncplane* n, 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);
|
|
|
|
const void* ncplane_userptr_const(const struct ncplane* n);
|
|
|
|
|
|
|
|
// Returns the dimensions of this ncplane.
|
|
|
|
void ncplane_dim_yx(const struct ncplane* n, int* RESTRICT rows,
|
|
|
|
int* RESTRICT cols);
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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 underneath the cursor 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.
|
2019-12-13 17:16:05 +00:00
|
|
|
int ncplane_putc(struct ncplane* n, const cell* c);
|
2019-12-12 11:38:16 +00:00
|
|
|
|
|
|
|
// Call ncplane_putc() after successfully moving to y, x on the specified plane.
|
|
|
|
static inline int
|
|
|
|
ncplane_putc_yx(struct ncplane* n, int y, int x, const cell* c){
|
|
|
|
if(ncplane_cursor_move_yx(n, y, x)){
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return ncplane_putc(n, c);
|
|
|
|
}
|
2019-12-01 03:58:50 +00:00
|
|
|
|
2019-12-05 05:28:42 +00:00
|
|
|
// Replace the cell underneath the cursor with the provided 7-bit char 'c',
|
|
|
|
// using the specified 'attr' and 'channels' for styling. Advance the cursor by
|
2019-12-12 11:38:16 +00:00
|
|
|
// 1. On success, returns 1. On failure, returns -1. This works whether the
|
|
|
|
// underlying char is signed or unsigned.
|
2019-12-13 17:16:05 +00:00
|
|
|
int ncplane_putsimple(struct ncplane* n, char c, uint32_t attr, uint64_t channels);
|
2019-12-12 11:38:16 +00:00
|
|
|
|
|
|
|
// Call ncplane_simple() after successfully moving to y, x.
|
|
|
|
static inline int
|
|
|
|
ncplane_putsimple_yx(struct ncplane* n, int y, int x, char c,
|
|
|
|
uint32_t attr, uint64_t channels){
|
|
|
|
if(ncplane_cursor_move_yx(n, y, x)){
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return ncplane_putsimple(n, c, attr, channels);
|
|
|
|
}
|
2019-12-05 05:28:42 +00:00
|
|
|
|
|
|
|
// Replace the cell underneath the cursor with the provided EGC, using the
|
|
|
|
// specified 'attr' and 'channels' for styling, 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.
|
2019-12-13 17:16:05 +00:00
|
|
|
int ncplane_putegc(struct ncplane* n, const char* gclust, uint32_t attr,
|
2019-12-05 05:28:42 +00:00
|
|
|
uint64_t channels, int* sbytes);
|
|
|
|
|
2019-12-12 11:38:16 +00:00
|
|
|
// Call ncplane_putegc() after successfully moving to y, x.
|
|
|
|
static inline int
|
|
|
|
ncplane_putegc_yx(struct ncplane* n, int y, int x, const char* gclust, uint32_t attr,
|
|
|
|
uint64_t channels, int* sbytes){
|
|
|
|
if(ncplane_cursor_move_yx(n, y, x)){
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return ncplane_putegc(n, gclust, attr, channels, 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, uint32_t attr,
|
|
|
|
uint64_t channels, 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, attr, channels, 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,
|
|
|
|
uint32_t attr, uint64_t channels, int* sbytes){
|
|
|
|
if(ncplane_cursor_move_yx(n, y, x)){
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return ncplane_putwegc(n, gclust, attr, channels, sbytes);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Write a series of EGCs to the current location, using the current style.
|
2019-12-01 03:58:50 +00:00
|
|
|
// 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.
|
2019-12-13 17:16:05 +00:00
|
|
|
int ncplane_putstr(struct ncplane* n, const char* gclustarr);
|
2019-12-12 11:38:16 +00:00
|
|
|
|
|
|
|
static inline int
|
|
|
|
ncplane_putstr_yx(struct ncplane* n, int y, int x, const char* gclustarr){
|
|
|
|
if(ncplane_cursor_move_yx(n, y, x)){
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return ncplane_putstr(n, gclustarr);
|
|
|
|
}
|
|
|
|
|
|
|
|
// ncplane_putstr(), but following a conversion from wchar_t to UTF-8 multibyte.
|
|
|
|
static inline int
|
|
|
|
ncplane_putwstr(struct ncplane* n, 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(n, mbstr);
|
|
|
|
free(mbstr);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline int
|
|
|
|
ncplane_putwstr_yx(struct ncplane* n, int y, int x, const wchar_t* gclustarr){
|
|
|
|
if(ncplane_cursor_move_yx(n, y, x)){
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return ncplane_putwstr(n, gclustarr);
|
|
|
|
}
|
2019-12-01 03:58:50 +00:00
|
|
|
|
|
|
|
// The ncplane equivalents of printf(3) and vprintf(3).
|
2019-12-14 01:58:44 +00:00
|
|
|
int ncplane_printf(struct ncplane* n, const char* format, ...)
|
|
|
|
__attribute__ ((format (printf, 2, 3)));
|
|
|
|
|
2019-12-13 17:16:05 +00:00
|
|
|
int ncplane_vprintf(struct ncplane* n, const char* format, va_list ap);
|
2019-12-12 11:38:16 +00:00
|
|
|
|
|
|
|
static inline int
|
|
|
|
ncplane_vprintf_yx(struct ncplane* n, int y, int x, const char* format, va_list ap){
|
|
|
|
if(ncplane_cursor_move_yx(n, y, x)){
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return ncplane_vprintf(n, format, ap);
|
|
|
|
}
|
|
|
|
|
2019-12-01 03:58:50 +00:00
|
|
|
|
|
|
|
// 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.
|
2019-12-04 09:25:56 +00:00
|
|
|
int ncplane_hline_interp(struct ncplane* n, const cell* c, int len,
|
2019-12-05 05:28:42 +00:00
|
|
|
uint64_t c1, uint64_t c2);
|
2019-12-04 09:25:56 +00:00
|
|
|
|
|
|
|
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,
|
2019-12-05 05:28:42 +00:00
|
|
|
uint64_t c1, uint64_t c2);
|
2019-12-04 09:25:56 +00:00
|
|
|
|
|
|
|
static inline int
|
|
|
|
ncplane_vline(struct ncplane* n, const cell* c, int len){
|
|
|
|
return ncplane_vline_interp(n, c, len, c->channels, c->channels);
|
|
|
|
}
|
2019-12-01 03:58:50 +00:00
|
|
|
|
|
|
|
// 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.
|
2019-12-04 08:56:27 +00:00
|
|
|
// '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
|
2019-12-14 01:58:44 +00:00
|
|
|
// 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
|
2019-12-04 08:56:27 +00:00
|
|
|
|
2019-12-04 09:25:56 +00:00
|
|
|
int ncplane_box(struct ncplane* n, const cell* ul, const cell* ur,
|
2019-12-05 05:28:42 +00:00
|
|
|
const cell* ll, const cell* lr, const cell* hline,
|
|
|
|
const cell* vline, int ystop, int xstop,
|
|
|
|
unsigned ctlword);
|
2019-12-01 03:58:50 +00:00
|
|
|
|
|
|
|
// 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,
|
2019-12-05 05:28:42 +00:00
|
|
|
const cell* ll, const cell* lr, const cell* hline,
|
|
|
|
const cell* vline, int ylen, int xlen, unsigned ctlword){
|
2019-12-01 03:58:50 +00:00
|
|
|
int y, x;
|
|
|
|
ncplane_cursor_yx(n, &y, &x);
|
2019-12-05 05:28:42 +00:00
|
|
|
return ncplane_box(n, ul, ur, ll, lr, hline, vline, y + ylen - 1,
|
|
|
|
x + xlen - 1, ctlword);
|
2019-12-01 03:58:50 +00:00
|
|
|
}
|
|
|
|
|
2019-12-05 05:34:16 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2019-12-01 03:58:50 +00:00
|
|
|
// 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
|
2019-12-10 11:02:49 +00:00
|
|
|
// with this ncplane are invalidated, and must not be used after the call.
|
2019-12-01 03:58:50 +00:00
|
|
|
void ncplane_erase(struct ncplane* n);
|
|
|
|
|
|
|
|
// Set the current fore/background color using RGB specifications. If the
|
|
|
|
// terminal does not support directly-specified 3x8b cells (24-bit "Direct
|
|
|
|
// Color", 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.
|
2019-12-07 03:45:08 +00:00
|
|
|
int ncplane_set_fg(struct ncplane* n, int r, int g, int b);
|
|
|
|
int ncplane_set_bg(struct ncplane* n, int r, int g, int b);
|
2019-12-01 03:58:50 +00:00
|
|
|
|
|
|
|
// use the default color for the foreground/background
|
|
|
|
void ncplane_fg_default(struct ncplane* n);
|
|
|
|
void ncplane_bg_default(struct ncplane* n);
|
|
|
|
|
|
|
|
// 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);
|
2019-12-05 05:28:42 +00:00
|
|
|
|
|
|
|
// Fade the ncplane out over the provided time, calling the specified function
|
|
|
|
// when done. Requires a terminal which supports direct color, 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);
|
|
|
|
|
|
|
|
// 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);
|
2019-12-01 03:58:50 +00:00
|
|
|
```
|
|
|
|
|
2019-12-12 12:07:04 +00:00
|
|
|
Aligned forms are available for `ncplane_putstr()` and `ncplane_putwstr()`.
|
|
|
|
These forms correctly take double-column glyphs into account.
|
|
|
|
|
|
|
|
```c
|
|
|
|
// Alignment within the ncplane. Left/right-justified, or centered.
|
|
|
|
typedef enum {
|
|
|
|
NCALIGN_LEFT,
|
|
|
|
NCALIGN_CENTER,
|
|
|
|
NCALIGN_RIGHT,
|
|
|
|
} ncalign_e;
|
|
|
|
|
2019-12-13 17:16:05 +00:00
|
|
|
int ncplane_putstr_aligned(struct ncplane* n, int y, const char* s,
|
|
|
|
ncalign_e atype);
|
2019-12-12 12:07:04 +00:00
|
|
|
|
2019-12-13 17:16:05 +00:00
|
|
|
int ncplane_putwstr_aligned(struct ncplane* n, int y,
|
|
|
|
const wchar_t* gclustarr, ncalign_e atype);
|
2019-12-12 12:07:04 +00:00
|
|
|
```
|
|
|
|
|
2019-12-14 10:33:34 +00:00
|
|
|
#### 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'.
|
|
|
|
API uint64_t ncplane_get_channels(const struct ncplane* n);
|
|
|
|
API uint32_t ncplane_get_attr(const struct ncplane* n);
|
|
|
|
|
|
|
|
// Extract the 32-bit working background channel from an ncplane.
|
|
|
|
static inline unsigned
|
|
|
|
ncplane_get_bchannel(const struct ncplane* nc){
|
|
|
|
return channels_get_bchannel(ncplane_get_channels(nc));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Extract the 32-bit working foreground channel from an ncplane.
|
|
|
|
static inline unsigned
|
|
|
|
ncplane_get_fchannel(const struct ncplane* nc){
|
|
|
|
return channels_get_fchannel(ncplane_get_channels(nc));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Extract 24 bits of working foreground RGB from an ncplane, shifted to LSBs.
|
|
|
|
static inline unsigned
|
|
|
|
ncplane_get_fg(const struct ncplane* nc){
|
|
|
|
return channels_get_fg(ncplane_get_channels(nc));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Extract 24 bits of working background RGB from an ncplane, shifted to LSBs.
|
|
|
|
static inline unsigned
|
|
|
|
ncplane_get_bg(const struct ncplane* nc){
|
|
|
|
return channels_get_bg(ncplane_get_channels(nc));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Extract 2 bits of foreground alpha from 'struct ncplane', shifted to LSBs.
|
|
|
|
static inline unsigned
|
|
|
|
ncplane_get_fg_alpha(const struct ncplane* nc){
|
|
|
|
return channels_get_fg_alpha(ncplane_get_channels(nc));
|
|
|
|
}
|
|
|
|
/ Extract 2 bits of background alpha from 'struct ncplane', shifted to LSBs.
|
|
|
|
static inline unsigned
|
|
|
|
ncplane_get_bg_alpha(const struct ncplane* nc){
|
|
|
|
return channels_get_bg_alpha(ncplane_get_channels(nc));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Extract 24 bits of foreground RGB from 'n', split into subcomponents.
|
|
|
|
static inline unsigned
|
|
|
|
ncplane_get_fg_rgb(const struct ncplane* n, unsigned* r, unsigned* g, unsigned*
|
|
|
|
return channels_get_fg_rgb(ncplane_get_channels(n), r, g, b);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Extract 24 bits of background RGB from 'n', split into subcomponents.
|
|
|
|
static inline unsigned
|
|
|
|
ncplane_get_bg_rgb(const struct ncplane* n, unsigned* r, unsigned* g, unsigned*
|
|
|
|
return channels_get_bg_rgb(ncplane_get_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 "Direct
|
|
|
|
// Color", 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 with rgb assembled into a channel (i.e. lower 32 bits).
|
|
|
|
void ncplane_set_fg(struct ncplane* n, uint32_t channel);
|
|
|
|
void ncplane_set_bg(struct ncplane* n, uint32_t 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);
|
|
|
|
```
|
|
|
|
|
2019-11-25 05:24:55 +00:00
|
|
|
### Cells
|
|
|
|
|
2019-11-25 05:28:38 +00:00
|
|
|
Unlike the `notcurses` or `ncplane` objects, the definition of `cell` is
|
2019-12-14 12:04:43 +00:00
|
|
|
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.
|
2019-11-25 05:28:38 +00:00
|
|
|
|
|
|
|
```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.
|
|
|
|
typedef struct cell {
|
|
|
|
// These 32 bits are either a single-byte, single-character grapheme cluster
|
2019-12-10 11:02:49 +00:00
|
|
|
// (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.
|
2019-11-25 05:28:38 +00:00
|
|
|
uint32_t gcluster; // 1 * 4b -> 4b
|
2019-12-10 11:02:49 +00:00
|
|
|
// CELL_STYLE_* attributes (16 bits) + 16 reserved bits
|
2019-11-25 05:28:38 +00:00
|
|
|
uint32_t attrword; // + 4b -> 8b
|
2019-12-14 10:33:34 +00:00
|
|
|
// (channels & 0x8000000000000000ull): left half of wide character
|
2019-11-25 05:28:38 +00:00
|
|
|
// (channels & 0x4000000000000000ull): foreground is *not* "default color"
|
2019-12-10 11:02:49 +00:00
|
|
|
// (channels & 0x3000000000000000ull): foreground alpha (2 bits)
|
|
|
|
// (channels & 0x0f00000000000000ull): reserved, must be 0
|
2019-11-25 05:28:38 +00:00
|
|
|
// (channels & 0x00ffffff00000000ull): foreground in 3x8 RGB (rrggbb)
|
2019-12-14 10:33:34 +00:00
|
|
|
// (channels & 0x0000000080000000ull): right half of wide character
|
2019-11-25 05:28:38 +00:00
|
|
|
// (channels & 0x0000000040000000ull): background is *not* "default color"
|
2019-12-10 11:02:49 +00:00
|
|
|
// (channels & 0x0000000030000000ull): background alpha (2 bits)
|
|
|
|
// (channels & 0x000000000f000000ull): reserved, must be 0
|
2019-11-25 05:28:38 +00:00
|
|
|
// (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;
|
|
|
|
```
|
|
|
|
|
2019-12-14 12:04:43 +00:00
|
|
|
`cell`s must be initialized with `CELL_TRIVIAL_INITIALIZER` or `cell_init()`
|
|
|
|
before any other use (both merely zero out the `cell`).
|
|
|
|
|
|
|
|
```c
|
|
|
|
#define CELL_TRIVIAL_INITIALIZER { .gcluster = '\0', .attrword = 0, .channels = 0, }
|
|
|
|
|
|
|
|
static inline void
|
|
|
|
cell_init(cell* c){
|
|
|
|
memset(c, 0, sizeof(*c));
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
A `cell` has three fundamental elements:
|
2019-11-25 05:24:55 +00:00
|
|
|
|
|
|
|
* 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, and a 16-bit alpha channel.
|
|
|
|
* The 48 bits of foreground and background RGB, 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
|
2019-11-25 07:22:15 +00:00
|
|
|
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.
|
2019-11-25 05:24:55 +00:00
|
|
|
|
|
|
|
```c
|
2019-11-27 15:43:03 +00:00
|
|
|
// 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.
|
2019-11-25 05:24:55 +00:00
|
|
|
int cell_load(struct ncplane* n, cell* c, const char* gcluster);
|
|
|
|
|
2019-12-05 05:34:16 +00:00
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
2019-12-01 20:05:10 +00:00
|
|
|
// 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);
|
|
|
|
|
2019-11-25 05:24:55 +00:00
|
|
|
// Release resources held by the cell 'c'.
|
|
|
|
void cell_release(struct ncplane* n, cell* c);
|
|
|
|
|
2019-12-01 20:05:10 +00:00
|
|
|
#define CELL_STYLE_SHIFT 16u
|
|
|
|
#define CELL_STYLE_MASK 0xffff0000ul
|
|
|
|
#define CELL_ALPHA_MASK 0x0000fffful
|
|
|
|
// these are used for the style bitfield *after* it is shifted
|
|
|
|
#define CELL_STYLE_STANDOUT 0x0001u
|
|
|
|
#define CELL_STYLE_UNDERLINE 0x0002u
|
|
|
|
#define CELL_STYLE_REVERSE 0x0004u
|
|
|
|
#define CELL_STYLE_BLINK 0x0008u
|
|
|
|
#define CELL_STYLE_DIM 0x0010u
|
|
|
|
#define CELL_STYLE_BOLD 0x0020u
|
|
|
|
#define CELL_STYLE_INVIS 0x0040u
|
|
|
|
#define CELL_STYLE_PROTECT 0x0080u
|
|
|
|
#define CELL_STYLE_ITALIC 0x0100u
|
2019-11-25 05:24:55 +00:00
|
|
|
|
|
|
|
// Set the specified style bits for the cell 'c', whether they're actively
|
|
|
|
// supported or not.
|
|
|
|
static inline void
|
2019-12-01 20:05:10 +00:00
|
|
|
cell_styles_set(cell* c, unsigned stylebits){
|
2019-11-25 05:24:55 +00:00
|
|
|
c->attrword = (c->attrword & ~CELL_STYLE_MASK) |
|
|
|
|
((stylebits & 0xffff) << 16u);
|
|
|
|
}
|
|
|
|
|
2019-12-01 20:05:10 +00:00
|
|
|
// Get the style bits, shifted over into the LSBs.
|
|
|
|
static inline unsigned
|
|
|
|
cell_styles(const cell* c){
|
|
|
|
return (c->attrword & CELL_STYLE_MASK) >> 16u;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add the specified styles (in the LSBs) to the cell's existing spec, whether
|
|
|
|
// they're actively supported or not.
|
2019-11-25 05:24:55 +00:00
|
|
|
static inline void
|
2019-12-01 20:05:10 +00:00
|
|
|
cell_styles_on(cell* c, unsigned stylebits){
|
2019-11-25 05:24:55 +00:00
|
|
|
c->attrword |= ((stylebits & 0xffff) << 16u);
|
|
|
|
}
|
|
|
|
|
2019-12-01 20:05:10 +00:00
|
|
|
// Remove the specified styles (in the LSBs) from the cell's existing spec.
|
2019-11-25 05:24:55 +00:00
|
|
|
static inline void
|
2019-12-01 20:05:10 +00:00
|
|
|
cell_styles_off(cell* c, unsigned stylebits){
|
2019-11-25 05:24:55 +00:00
|
|
|
c->attrword &= ~((stylebits & 0xffff) << 16u);
|
|
|
|
}
|
|
|
|
|
2019-12-01 20:05:10 +00:00
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
|
2019-12-05 05:34:16 +00:00
|
|
|
// is the cell simple (a lone ASCII character, encoded as such)?
|
2019-12-01 20:05:10 +00:00
|
|
|
static inline bool
|
|
|
|
cell_simple_p(const cell* c){
|
|
|
|
return c->gcluster < 0x80;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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);
|
2019-12-05 05:34:16 +00:00
|
|
|
|
|
|
|
// 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;
|
2019-12-05 07:01:49 +00:00
|
|
|
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){
|
2019-12-05 05:34:16 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
cell_release(n, hl);
|
|
|
|
}
|
|
|
|
cell_release(n, lr);
|
|
|
|
}
|
|
|
|
cell_release(n, ll);
|
|
|
|
}
|
|
|
|
cell_release(n, ur);
|
|
|
|
}
|
|
|
|
cell_release(n, ul);
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2019-12-05 07:01:49 +00:00
|
|
|
|
2019-12-05 05:34:16 +00:00
|
|
|
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, "╔╗╚╝═║");
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2019-12-14 10:33:34 +00:00
|
|
|
#### 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_get_bchannel(const cell* cl){
|
|
|
|
return channels_get_bchannel(cl->channels);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Extract the 32-bit foreground channel from a cell.
|
|
|
|
static inline unsigned
|
|
|
|
cell_get_fchannel(const cell* cl){
|
|
|
|
return channels_get_fchannel(cl->channels);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Extract 24 bits of foreground RGB from 'cell', shifted to LSBs.
|
|
|
|
static inline unsigned
|
|
|
|
cell_get_fg(const cell* cl){
|
|
|
|
return channels_get_fg(cl->channels);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Extract 24 bits of background RGB from 'cell', shifted to LSBs.
|
|
|
|
static inline unsigned
|
|
|
|
cell_get_bg(const cell* cl){
|
|
|
|
return channels_get_bg(cl->channels);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Extract 2 bits of foreground alpha from 'cell', shifted to LSBs.
|
|
|
|
static inline unsigned
|
|
|
|
cell_get_fg_alpha(const cell* cl){
|
|
|
|
return channels_get_fg_alpha(cl->channels);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Extract 2 bits of background alpha from 'cell', shifted to LSBs.
|
|
|
|
static inline unsigned
|
|
|
|
cell_get_bg_alpha(const cell* cl){
|
|
|
|
return channels_get_bg_alpha(cl->channels);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Extract 24 bits of foreground RGB from 'cell', split into subcell.
|
|
|
|
static inline unsigned
|
|
|
|
cell_get_fg_rgb(const cell* cl, unsigned* r, unsigned* g, unsigned* b){
|
|
|
|
return channels_get_fg_rgb(cl->channels, r, g, b);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Extract 24 bits of background RGB from 'cell', split into subcell.
|
|
|
|
static inline unsigned
|
|
|
|
cell_get_bg_rgb(const cell* cl, unsigned* r, unsigned* g, unsigned* b){
|
|
|
|
return channels_get_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);
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2019-12-05 05:34:16 +00:00
|
|
|
### Multimedia
|
|
|
|
|
|
|
|
Media decoding and scaling is handled by libAV from FFmpeg, resulting in a
|
|
|
|
`notcurses_visual` object. This object generates frames, each one corresponding
|
|
|
|
to a renderable scene on the associated `ncplane`.
|
|
|
|
|
|
|
|
```c
|
|
|
|
// open a visual (image or video), associating it with the specified ncplane.
|
|
|
|
// returns NULL on any error, writing the AVError to 'averr'.
|
|
|
|
struct ncvisual* ncplane_visual_open(struct ncplane* nc, const char* file,
|
|
|
|
int* averr);
|
|
|
|
|
|
|
|
// destroy an ncvisual. rendered elements will not be disrupted, but the visual
|
|
|
|
// can be neither decoded nor rendered any further.
|
|
|
|
void ncvisual_destroy(struct ncvisual* ncv);
|
|
|
|
|
|
|
|
// extract the next frame from an ncvisual. returns NULL on end of file,
|
|
|
|
// writing AVERROR_EOF to 'averr'. returns NULL on a decoding or allocation
|
|
|
|
// error, placing the AVError in 'averr'. this frame is invalidated by a
|
|
|
|
// subsequent call to ncvisual_decode(), and should not be freed by the caller.
|
|
|
|
struct AVFrame* ncvisual_decode(struct ncvisual* nc, int* averr);
|
|
|
|
|
|
|
|
// render the decoded frame to the associated ncplane. the frame will be scaled
|
|
|
|
// to the size of the ncplane at ncplane_visual_open() time.
|
|
|
|
int ncvisual_render(const struct ncvisual* ncv);
|
|
|
|
|
|
|
|
// stream the entirety of the media, according to its own timing.
|
|
|
|
// blocking, obviously. pretty raw; beware.
|
|
|
|
int ncvisual_stream(struct notcurses* nc, struct ncvisual* ncv, int* averr);
|
2019-11-25 05:24:55 +00:00
|
|
|
```
|
|
|
|
|
2019-12-05 05:34:16 +00:00
|
|
|
### Panelreels
|
|
|
|
|
|
|
|
Panelreels are a complex UI abstraction offered by notcurses, derived from my
|
|
|
|
similar work in [outcurses](https://github.com/dankamongmen/outcurses#Panelreels).
|
|
|
|
|
2019-12-14 10:33:34 +00:00
|
|
|
**FIXME**
|
|
|
|
|
|
|
|
### 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_get_r(unsigned channel){
|
|
|
|
return (channel & 0xff0000u) >> 16u;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Extract the 8-bit green component from a 32-bit channel.
|
|
|
|
static inline unsigned
|
|
|
|
channel_get_g(unsigned channel){
|
|
|
|
return (channel & 0x00ff00u) >> 8u;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Extract the 8-bit blue component from a 32-bit channel.
|
|
|
|
static inline unsigned
|
|
|
|
channel_get_b(unsigned channel){
|
|
|
|
return (channel & 0x0000ffu);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Extract the three 8-bit R/G/B components from a 32-bit channel.
|
|
|
|
static inline unsigned
|
|
|
|
channel_get_rgb(unsigned channel, unsigned* r, unsigned* g, unsigned* b){
|
|
|
|
*r = channel_get_r(channel);
|
|
|
|
*g = channel_get_g(channel);
|
|
|
|
*b = channel_get_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;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Extract the 2-bit alpha component from a 32-bit channel.
|
|
|
|
static inline unsigned
|
|
|
|
channel_get_alpha(unsigned channel){
|
|
|
|
return (channel & CELL_BGALPHA_MASK) >> 28u;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set the 2-bit alpha component of the 32-bit channel.
|
|
|
|
static inline int
|
|
|
|
channel_set_alpha(unsigned* channel, int alpha){
|
|
|
|
if(alpha < 0 || alpha > 3){
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
*channel = (alpha << 28u) | (*channel & ~CELL_BGALPHA_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_get_bchannel(uint64_t channels){
|
|
|
|
return channels & 0xfffffffflu;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Extract the 32-bit foreground channel from a channel pair.
|
|
|
|
static inline unsigned
|
|
|
|
channels_get_fchannel(uint64_t channels){
|
|
|
|
return channels_get_bchannel(channels >> 32u);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Extract 24 bits of foreground RGB from 'channels', shifted to LSBs.
|
|
|
|
static inline unsigned
|
|
|
|
channels_get_fg(uint64_t channels){
|
|
|
|
return channels_get_fchannel(channels) & CELL_BG_MASK;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Extract 24 bits of background RGB from 'channels', shifted to LSBs.
|
|
|
|
static inline unsigned
|
|
|
|
channels_get_bg(uint64_t channels){
|
|
|
|
return channels_get_bchannel(channels) & CELL_BG_MASK;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Extract 2 bits of foreground alpha from 'channels', shifted to LSBs.
|
|
|
|
static inline unsigned
|
|
|
|
channels_get_fg_alpha(uint64_t channels){
|
|
|
|
return channel_get_alpha(channels_get_fchannel(channels));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Extract 2 bits of background alpha from 'channels', shifted to LSBs.
|
|
|
|
static inline unsigned
|
|
|
|
channels_get_bg_alpha(uint64_t channels){
|
|
|
|
return channel_get_alpha(channels_get_bchannel(channels));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Extract 24 bits of foreground RGB from 'channels', split into subchannels.
|
|
|
|
static inline unsigned
|
|
|
|
channels_get_fg_rgb(uint64_t channels, unsigned* r, unsigned* g, unsigned* b){
|
|
|
|
return channel_get_rgb(channels_get_fchannel(channels), r, g, b);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Extract 24 bits of background RGB from 'channels', split into subchannels.
|
|
|
|
static inline unsigned
|
|
|
|
channels_get_bg_rgb(uint64_t channels, unsigned* r, unsigned* g, unsigned* b){
|
|
|
|
return channel_get_rgb(channels_get_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_get_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_get_bchannel(*channels);
|
|
|
|
if(channel_set_rgb(&channel, r, g, b) < 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_get_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){
|
|
|
|
unsigned channel = channels_get_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_get_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_get_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_get_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_get_bchannel(*channels);
|
|
|
|
channel_set_default(&channel);
|
|
|
|
*channels = (*channels & 0xffffffff00000000llu) | channel;
|
|
|
|
return *channels;
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
|
2019-12-04 04:34:08 +00:00
|
|
|
### Perf
|
|
|
|
|
|
|
|
notcurses tracks statistics across its operation, and a snapshot can be
|
|
|
|
acquired using the `notcurses_stats()` function. This function cannot fail.
|
|
|
|
|
|
|
|
```c
|
|
|
|
typedef struct ncstats {
|
|
|
|
uint64_t renders; // number of notcurses_render() runs
|
|
|
|
uint64_t render_bytes; // bytes emitted to ttyfp
|
|
|
|
uint64_t render_max_bytes; // max bytes emitted for a frame
|
|
|
|
uint64_t render_min_bytes; // min bytes emitted for a frame
|
|
|
|
uint64_t render_ns; // nanoseconds spent in notcurses_render()
|
|
|
|
int64_t render_max_ns; // max ns spent in notcurses_render()
|
|
|
|
int64_t render_min_ns; // min ns spent in successful notcurses_render()
|
2019-12-13 22:14:15 +00:00
|
|
|
uint64_t fbbytes; // total bytes devoted to all active framebuffers
|
2019-12-04 04:34:08 +00:00
|
|
|
uint64_t fgelisions; // RGB fg elision count
|
|
|
|
uint64_t fgemissions; // RGB fg emissions
|
|
|
|
uint64_t bgelisions; // RGB bg elision count
|
|
|
|
uint64_t bgemissions; // RGB bg emissions
|
|
|
|
uint64_t defaultelisions; // default color was emitted
|
|
|
|
uint64_t defaultemissions; // default color was elided
|
|
|
|
} ncstats;
|
|
|
|
|
|
|
|
// Acquire a snapshot of the notcurses object's stats.
|
2019-12-04 09:25:56 +00:00
|
|
|
void notcurses_stats(const struct notcurses* nc, ncstats* stats);
|
2019-12-04 04:34:08 +00:00
|
|
|
```
|
|
|
|
|
|
|
|
Timings for renderings are across the breadth of `notcurses_render()`: they
|
|
|
|
include all per-render preprocessing, output generation, and dumping of the
|
|
|
|
output (including any sleeping while waiting on the terminal).
|
|
|
|
|
|
|
|
The notcurses rendering algorithm starts by moving the physical cursor to the
|
|
|
|
upper left corner of the visible screen (it does *not* clear the screen
|
|
|
|
beforehand). At each coordinate, it finds the topmost visible `ncplane`. There
|
|
|
|
will always be at least one `ncplane` visible at each coordinate, due to the
|
|
|
|
default plane. Once the plane is determined, the damage map is consulted to see
|
|
|
|
whether the cell need be redrawn. If so, it will be redrawn, and the virtual
|
|
|
|
cursor is updated based on the width of the output. Along the way, notcurses
|
|
|
|
attempts to minimize total amount of data written by eliding unnecessary color
|
|
|
|
and style specifications, and moving the cursor over large unchanged areas.
|
|
|
|
|
|
|
|
The worst case input frame (in terms of output size) is one whose colors change
|
|
|
|
from coordinate to coordinate, uses multiple combining characters within each
|
2019-12-06 20:46:56 +00:00
|
|
|
grapheme cluster, and has a large geometry. Peculiarities of the terminal
|
2019-12-04 04:34:08 +00:00
|
|
|
make it impossible to comment more meaningfully regarding delay.
|
|
|
|
|
2019-12-10 11:59:35 +00:00
|
|
|
Using the "default color" as only one of the foreground or background requires
|
|
|
|
emitting the `op` escape followed by the appropriate escape for changing the
|
|
|
|
fore- or background (since `op` changes both at once). If you're printing full
|
|
|
|
block characters, it's for this reason better to give them all the same
|
|
|
|
meaningless background color than to leave the background on the default. If
|
|
|
|
you're printing spaces, you likewise want a meaningless foreground color. For
|
2019-12-13 18:00:29 +00:00
|
|
|
a long string of such cells, eliding these ops can be a nice savings. See
|
|
|
|
[Issue #131](https://github.com/dankamongmen/notcurses/issues/131), though;
|
|
|
|
I'll likely natively handle this within `notcurses_render()` soon.
|
2019-12-10 11:59:35 +00:00
|
|
|
|
2019-11-29 06:58:45 +00:00
|
|
|
## Included tools
|
|
|
|
|
|
|
|
Four binaries are built as part of notcurses:
|
|
|
|
* `notcurses-demo`: some demonstration code
|
|
|
|
* `notcurses-view`: renders visual media (images/videos)
|
|
|
|
* `notcurses-input`: decode and print keypresses
|
2019-12-14 00:22:11 +00:00
|
|
|
* `notcurses-tester`: unit testing
|
2019-11-29 06:58:45 +00:00
|
|
|
|
2019-11-23 14:05:32 +00:00
|
|
|
## Differences from NCURSES
|
|
|
|
|
|
|
|
The biggest difference, of course, is that notcurses is not an implementation
|
|
|
|
of X/Open (aka XSI) Curses, nor part of SUS4-2018.
|
|
|
|
|
|
|
|
The detailed differences between notcurses and NCURSES probably can't be fully
|
|
|
|
enumerated, and if they could, no one would want to read it. With that said,
|
|
|
|
some design decisions might surprise NCURSES programmers:
|
|
|
|
|
|
|
|
* The screen is not cleared on entry.
|
|
|
|
* There is no distinct `PANEL` type. The z-buffer is a fundamental property,
|
|
|
|
and all drawable surfaces are ordered along the z axis. There is no
|
|
|
|
equivalent to `update_panels()`.
|
|
|
|
* Scrolling is disabled by default, and cannot be globally enabled.
|
2019-11-23 21:03:38 +00:00
|
|
|
* The Curses `cchar_t` has a fixed-size array of `wchar_t`. The notcurses
|
|
|
|
`cell` instead supports a UTF-8 encoded extended grapheme cluster of
|
|
|
|
arbitrary length. The only supported charsets are `C` and `UTF-8`. notcurses
|
|
|
|
does not generally make use of `wchar_t`.
|
2019-11-23 14:05:32 +00:00
|
|
|
* The hardware cursor is disabled by default, when supported (`civis` capability).
|
|
|
|
* Echoing of input is disabled by default, and `cbreak` mode is used by default.
|
|
|
|
* Colors are always specified as 24 bits in 3 components (RGB). If necessary,
|
|
|
|
these will be quantized for the actual terminal. There are no "color pairs".
|
|
|
|
* There is no distinct "pad" concept (these are NCURSES `WINDOW`s created with
|
|
|
|
the `newpad()` function). All drawable surfaces can exceed the display size.
|
|
|
|
* Multiple threads can freely call into notcurses, so long as they're not
|
|
|
|
accessing the same data. In particular, it is always safe to concurrently
|
|
|
|
mutate different ncplanes in different threads.
|
|
|
|
* NCURSES has thread-ignorant and thread-semi-safe versions, trace-enabled and
|
|
|
|
traceless versions, and versions with and without support for wide characters.
|
2019-11-23 21:09:37 +00:00
|
|
|
notcurses is one library: no tracing, UTF-8, thread safety.
|
2019-12-05 05:26:33 +00:00
|
|
|
* There is no `ESCDELAY` concept; notcurses expects that all bytes of a
|
2019-12-13 18:00:29 +00:00
|
|
|
keyboard escape sequence arrive at the same time. This improves latency
|
2019-12-05 05:26:33 +00:00
|
|
|
and simplifies the API.
|
2019-12-14 00:22:11 +00:00
|
|
|
* It is an error in NCURSES to print to the bottommost, rightmost coordinate of
|
|
|
|
the screen when scrolling is disabled (because the cursor cannot be advanced).
|
|
|
|
Failure to advance the cursor does not result in an error in notcurses (but
|
|
|
|
attempting to print at the cursor when it has been advanced off the plane
|
|
|
|
*does*).
|
2019-11-23 14:05:32 +00:00
|
|
|
|
|
|
|
### Features missing relative to NCURSES
|
|
|
|
|
|
|
|
This isn't "features currently missing", but rather "features I do not intend
|
|
|
|
to implement".
|
|
|
|
|
2019-11-23 14:12:00 +00:00
|
|
|
* There is no immediate-output mode (`immedok()`, `echochar()` etc.).
|
|
|
|
`ncplane_putc()` followed by `notcurses_render()` ought be just as fast as
|
|
|
|
`echochar()`.
|
2019-11-23 14:05:32 +00:00
|
|
|
* There is no support for soft labels (`slk_init()`, etc.).
|
|
|
|
* There is no concept of subwindows which share memory with their parents.
|
|
|
|
* There is no tracing functionality ala `trace(3NCURSES)`. Superior external
|
|
|
|
tracing solutions exist, such as `bpftrace`.
|
2019-11-29 03:08:26 +00:00
|
|
|
|
2019-12-06 23:33:20 +00:00
|
|
|
### Adapting NCURSES programs
|
|
|
|
|
|
|
|
First off, ask whether you really want to do such a thing. NCURSES and the
|
|
|
|
Curses API it implements are far more portable and better-tested than notcurses
|
|
|
|
is ever likely to be. Will the program really benefit from notcurses's advanced
|
|
|
|
features? If not, it's probably best left as it is.
|
|
|
|
|
|
|
|
Otherwise, most NCURSES concepts have clear partners in notcurses. Any functions
|
|
|
|
making implicit use of `stdscr` ought be replaced with their explicit
|
|
|
|
equivalents. `stdscr` ought then be replaced with the result of
|
|
|
|
`notcurses_stdplane()` (the standard plane). `PANEL`s become `ncplane`s; the
|
|
|
|
Panels API is otherwise pretty close. Anything writing a bare character will
|
|
|
|
become a simple `cell`; multibyte or wide characters become complex `cell`s.
|
|
|
|
Color no longer uses "color pairs". You can either hack together a simple table
|
|
|
|
mapping your colors to RGB values and color pairs to foreground and background
|
|
|
|
indices into said table.
|
|
|
|
|
|
|
|
I have adapted two large (~5k lines of C UI code each) from NCURSES to
|
2019-12-07 03:45:08 +00:00
|
|
|
notcurses, and found it a fairly painless process. It was helpful to introduce
|
|
|
|
a shim layer, e.g. `compat_mvwprintw` for NCURSES's `mvwprintw`:
|
|
|
|
|
|
|
|
```c
|
|
|
|
static int
|
|
|
|
compat_mvwprintw(struct ncplane* nc, int y, int x, const char* fmt, ...){
|
|
|
|
if(ncplane_cursor_move_yx(nc, y, x)){
|
|
|
|
return ERR;
|
|
|
|
}
|
|
|
|
va_list va;
|
|
|
|
va_start(va, fmt);
|
|
|
|
if(ncplane_vprintf(nc, fmt, va) < 0){
|
|
|
|
va_end(va);
|
|
|
|
return ERR;
|
|
|
|
}
|
|
|
|
va_end(va);
|
|
|
|
return OK;
|
|
|
|
}
|
|
|
|
```
|
2019-12-06 23:33:20 +00:00
|
|
|
|
2019-11-29 10:54:50 +00:00
|
|
|
## Environment notes
|
|
|
|
|
|
|
|
* If your terminal has an option about default interpretation of "ambiguous-width
|
|
|
|
characters" (this is actually a technical term from Unicode), ensure it is
|
|
|
|
set to **Wide**, not narrow.
|
|
|
|
|
|
|
|
* If you can disable BiDi in your terminal, do so while running notcurses
|
|
|
|
applications, until I have that handled better. notcurses doesn't recognize
|
|
|
|
the BiDi state machine transitions, and thus merrily continues writing
|
|
|
|
left-to-right. ﷽
|
|
|
|
|
2019-12-14 10:33:34 +00:00
|
|
|
* The unit tests assume dimensions of at least 80x25. They might work in a
|
|
|
|
smaller terminal. They might not. Don't file bugs on it.
|
|
|
|
|
2019-12-13 18:00:29 +00:00
|
|
|
## Supplemental material
|
|
|
|
|
|
|
|
### Useful links
|
2019-11-29 03:08:26 +00:00
|
|
|
|
|
|
|
* [BiDi in Terminal Emulators](https://terminal-wg.pages.freedesktop.org/bidi/)
|
|
|
|
* [The Xterm FAQ](https://invisible-island.net/xterm/xterm.faq.html)
|
|
|
|
* [The NCURSES FAQ](https://invisible-island.net/ncurses/ncurses.faq.html)
|
|
|
|
* [ECMA-35 Character Code Structure and Extension Techniques](https://www.ecma-international.org/publications/standards/Ecma-035.htm) (ISO/IEC 2022)
|
|
|
|
* [ECMA-43 8-bit Coded Character Set Structure and Rules](https://www.ecma-international.org/publications/standards/Ecma-043.htm)
|
|
|
|
* [ECMA-48 Control Functions for Coded Character Sets](https://www.ecma-international.org/publications/standards/Ecma-048.htm) (ISO/IEC 6429)
|
|
|
|
* [Unicode 12.1 Full Emoji List](https://unicode.org/emoji/charts/full-emoji-list.html)
|
2019-12-02 15:39:48 +00:00
|
|
|
* [Unicode Standard Annex #29 Text Segmentation](http://www.unicode.org/reports/tr29)
|
2019-11-29 03:08:26 +00:00
|
|
|
* [Unicode Standard Annex #15 Normalization Forms](https://unicode.org/reports/tr15/)
|
2019-11-29 09:57:09 +00:00
|
|
|
* [The TTY demystified](http://www.linusakesson.net/programming/tty/)
|
2019-12-14 10:33:34 +00:00
|
|
|
* [Dark Corners of Unicode](https://eev.ee/blog/2015/09/12/dark-corners-of-unicode/)
|
|
|
|
* [UTF-8 Decoder Capability and Stress Test](https://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt)
|
2019-12-13 18:00:29 +00:00
|
|
|
|
|
|
|
### History
|
|
|
|
|
2019-12-14 02:00:47 +00:00
|
|
|
* 2019-12-05: notcurses [0.4.0 "TRAP MUSIC ALL NIGHT LONG"](https://github.com/dankamongmen/notcurses/releases/tag/v0.4.0),
|
|
|
|
the first generally usable notcurses. I prepare a [demo](https://www.youtube.com/watch?v=eEv2YRyiEVM),
|
2019-12-13 18:00:29 +00:00
|
|
|
and release it on YouTube.
|
|
|
|
* November 2019: I begin work on [Outcurses](https://github.com/dankamongmen/outcurses).
|
|
|
|
Outcurses is a collection of routines atop NCURSES, including Panelreels.
|
|
|
|
I study the history of NCURSES, primarily using Thomas E. Dickey's FAQ and
|
|
|
|
the mailing list archives.
|
|
|
|
* 2019-11-14: I file [Ourcurses issue #56](https://github.com/dankamongmen/outcurses/issues/56)
|
|
|
|
regarding use of DirectColor in outcurses. This is partially inspired by
|
|
|
|
Lexi Summer Hale's essay [everything you ever wanted to know about terminals](http://xn--rpa.cc/irl/term.html).
|
|
|
|
I get into contact with Thomas E. Dickey and confirm that what I'm hoping
|
|
|
|
to do doesn't really fit in with the codified Curses API.
|
|
|
|
* 2019-11-16: I make the [first commit](https://github.com/dankamongmen/notcurses/commit/635d7039d79e4f94ba645e8cb601e3a6d82a6b30)
|
|
|
|
to notcurses.
|
|
|
|
* September 2019: I extracted fade routines from Growlight and Omphalos, and
|
|
|
|
offered them to NCURSES as extensions. They are not accepted, which is
|
|
|
|
understandable. I mention that I intend to extract Panelreels, and offer to
|
|
|
|
include them in the CDK (Curses Development Kit). [Growlight issue #43](https://github.com/dankamongmen/growlight/issues/43)
|
|
|
|
is created regarding this extraction. A few minor patches go into NCURSES.
|
|
|
|
* 2011, 2013: I develop [Growlight](https://github.com/dankamongmen/growlight)
|
|
|
|
and [Omphalos](https://github.com/dankamongmen/omphalos), complicated TUIs
|
|
|
|
making extensive use of NCURSES.
|
|
|
|
|
|
|
|
### Thanks
|
|
|
|
|
|
|
|
* Notcurses could never be what it is without decades of tireless, likely
|
|
|
|
thankless work by Thomas E. Dickey on NCURSES. His FAQ is a model of
|
|
|
|
engineering history. He exemplifies documentation excellence and
|
|
|
|
conservative, thoughtful stewardship. The Open Source community owes
|
|
|
|
Mr. Dickey a great debt.
|
|
|
|
* Justine Tunney, one of my first friends at Google NYC, was always present
|
|
|
|
with support, and pointed out the useful memstream functionality of
|
|
|
|
POSIX, eliminating the need for me to cons up something similar.
|
|
|
|
* I one night read the entirety of Lexi Summer Hale's [essays](http://xn--rpa.cc/irl/index.html),
|
|
|
|
and woke up intending to write notcurses.
|
|
|
|
* Finally, the [demoscene](https://en.wikipedia.org/wiki/Demoscene) and general
|
|
|
|
l33t scene of the 90s and early twenty-first century endlessly inspired a
|
|
|
|
young hax0r. There is great joy in computing; no one will drive us from
|
|
|
|
this paradise Turing has created!
|
|
|
|
|
|
|
|
> “Our fine arts were developed, their types and uses were established, in times
|
|
|
|
very different from the present, by men whose power of action upon things was
|
|
|
|
insignificant in comparison with ours. But the amazing growth of our
|
|
|
|
techniques, the adaptability and precision they have attained, the ideas and
|
|
|
|
habits they are creating, make it a certainty that _profound changes are
|
|
|
|
impending in the ancient craft of the Beautiful_.”
|
|
|
|
|