Selector widget #166, subtitles #95 (#301)

Get rid of annoying empty line in notcurses-view (and ncvisuals at offsets in general)
Implement most of the Selector widget. Need to add styling and scrolling still. #166
Reenable ubuntu focal build
Subtitles! We decode them, and display them in notcurses-view. If ncvisual_simple_streamer() is provided an extra ncplane, it will use it to display subtitles. #95
We now build Python by default, as things are working much better.
ncplane_set_base() now takes channel, attrword, and EGC, so you can usually avoid having to set up and release a cell. ncplane_set_base_cell() takes over duty from ncplane_set_base() for ease of conversion.
notcurses-demo and notcurses-view now both accept a 0 for delay multiplier, meaning 'go as fast as you possibly can'. Very small multipliers (e.g. 0.00001) no longer cause floating point exceptions.
fading routines no longer cause floating point exceptions on very small timescales.
pull/312/head
Nick Black 4 years ago committed by GitHub
parent 3ee6f44831
commit da0283ac25
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -24,7 +24,7 @@ steps:
#
#steps:
#- name: ubuntu-build
# image: dankamongmen/focal:2020-01-25b
# image: dankamongmen/focal:2020-01-28a
# commands:
# - apt-get update
# - apt-get -y dist-upgrade

@ -13,7 +13,7 @@ include(GNUInstallDirs)
set(NOTCURSES_SHARE ${CMAKE_INSTALL_PREFIX}/share/notcurses)
option(DISABLE_FFMPEG "Disable FFmpeg image/video support" OFF)
option(BUILD_PYTHON "Build Python wrappers" OFF)
option(BUILD_PYTHON "Build Python wrappers" ON)
find_package(PkgConfig REQUIRED)
find_package(Threads REQUIRED)
@ -26,7 +26,8 @@ endif()
find_library(LIBRT rt)
# libnotcurses
add_library(notcurses SHARED src/lib/enmetric.c src/lib/fade.c src/lib/input.c src/lib/libav.c src/lib/notcurses.c src/lib/panelreel.c src/lib/render.c)
file(GLOB NCSRCS CONFIGURE_DEPENDS src/lib/*.c)
add_library(notcurses SHARED ${NCSRCS})
set_target_properties(notcurses PROPERTIES
PUBLIC_HEADER "include/notcurses.h"
VERSION ${PROJECT_VERSION}
@ -445,8 +446,7 @@ if(${BUILD_PYTHON})
OUTPUT
"${CMAKE_CURRENT_BINARY_DIR}/build/pytimestamp"
COMMAND
"${Python3_EXECUTABLE}" ${SETUP_PY} build &&
"${Python3_EXECUTABLE}" ${SETUP_PY} build_ext
"${Python3_EXECUTABLE}" ${SETUP_PY} build_ext -L ${CMAKE_CURRENT_BINARY_DIR} -b ${CMAKE_CURRENT_BINARY_DIR}/python
DEPENDS
${PYSRC} ${SETUP_PY} ${SETUP_CFG} notcurses
COMMENT "Building Python wrappers"
@ -503,8 +503,12 @@ install(TARGETS notcurses
)
if(${BUILD_PYTHON})
if(DEFINED $ENV{DEB_VENDOR})
install(
CODE
"execute_process(COMMAND ${Python3_EXECUTABLE} ${SETUP_PY} install --root=${CMAKE_SOURCE_DIR}/debian/python3-notcurses --install-layout=deb --prefix=${CMAKE_INSTALL_PREFIX} WORKING_DIRECTORY ../python)")
install(
CODE
"execute_process(COMMAND ${Python3_EXECUTABLE} ${SETUP_PY} install --root=${CMAKE_SOURCE_DIR}/debian/python3-notcurses --install-layout=deb --prefix=${CMAKE_INSTALL_PREFIX} WORKING_DIRECTORY ../python)")
else()
install(
CODE
"execute_process(COMMAND ${Python3_EXECUTABLE} ${SETUP_PY} install --prefix=${CMAKE_INSTALL_PREFIX})")
endif()
endif()

@ -711,10 +711,17 @@ void ncplane_styles_off(struct ncplane* n, unsigned stylebits);
// Return the current styling for this ncplane.
unsigned ncplane_styles(struct ncplane* n);
// Set the ncplane's base cell to this cell. If defined, it will be rendered
// anywhere that the ncplane's gcluster is 0. Erasing the ncplane does not
// reset the base cell; this function must instead be called with a zero c.
int ncplane_set_base(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 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, uint64_t channels,
uint32_t attrword, const char* egc);
// Extract the ncplane's base cell into 'c'. The reference is invalidated if
// 'ncp' is destroyed.
@ -1661,15 +1668,38 @@ void ncvisual_destroy(struct ncvisual* ncv);
// It is an error to specify any region beyond the boundaries of the frame.
int ncvisual_render(const struct ncvisual* ncv, int begy, int begx, int leny, int lenx);
// Return the plane to which this ncvisual is bound.
struct ncplane* ncvisual_plane(struct ncvisual* ncv);
// If a subtitle ought be displayed at this time, return a heap-allocated copy
// of the UTF8 text.
char* ncvisual_subtitle(const struct ncvisual* ncv);
// Called for each frame rendered from 'ncv'. If anything but 0 is returned,
// the streaming operation ceases immediately, and that value is propagated out.
typedef int (*streamcb)(struct notcurses* nc, struct ncvisual* ncv, void* curry);
typedef int (*streamcb)(struct notcurses* nc, struct ncvisual* ncv, void*);
// Shut up and display my frames! Provide as an argument to ncvisual_stream().
// If you'd like subtitles to be decoded, provide a ncplane as the curry. If the
// curry is NULL, subtitles will not be displayed.
static inline int
ncvisual_simple_streamer(struct notcurses* nc, struct ncvisual* ncv __attribute__ ((unused)),
void* curry __attribute__ ((unused))){
return notcurses_render(nc);
ncvisual_simple_streamer(struct notcurses* nc, struct ncvisual* ncv, void* curry){
if(notcurses_render(nc)){
return -1;
}
int ret = 0;
if(curry){
// need a cast for C++ callers
struct ncplane* subncp = (struct ncplane*)curry;
char* subtitle = ncvisual_subtitle(ncv);
if(subtitle){
if(ncplane_putstr_yx(subncp, 0, 0, subtitle) < 0){
ret = -1;
}
free(subtitle);
}
}
return ret;
}
// Stream the entirety of the media, according to its own timing. Blocking,
@ -1683,9 +1713,6 @@ ncvisual_simple_streamer(struct notcurses* nc, struct ncvisual* ncv __attribute_
// error to supply 'timescale' less than or equal to 0.
int ncvisual_stream(struct notcurses* nc, struct ncvisual* ncv, int* averr,
float timescale, streamcb streamer, void* curry);
// Return the plane to which this ncvisual is bound.
struct ncplane* ncvisual_plane(struct ncvisual* ncv);
```
### Panelreels

@ -33,9 +33,10 @@
<a href="notcurses_ncvisual.3.html">notcurses_ncvisual</a>—operations on <tt>ncvisual</tt> objects<br/>
<a href="notcurses_output.3.html">notcurses_output</a>—drawing text on <tt>ncplane</tt>s<br/>
<a href="notcurses_palette.3.html">notcurses_palette</a>—operations on notcurses palettes<br/>
<a href="notcurses_panelreel.3.html">notcurses_panelreel</a>a high-level widget for hierarchical data<br/>
<a href="notcurses_panelreel.3.html">notcurses_panelreel</a>—high-level widget for hierarchical data<br/>
<a href="notcurses_render.3.html">notcurses_render</a>—sync the physical display<br/>
<a href="notcurses_resize.3.html">notcurses_resize</a>—resize the standard plane based off screen size<br/>
<a href="notcurses_selector.3.html">notcurses_selector</a>—high-level widget for selecting from a set<br/>
<a href="notcurses_stats.3.html">notcurses_stats</a>—notcurses runtime statistics<br/>
<a href="notcurses_stdplane.3.html">notcurses_stdplane</a>—acquire the standard <tt>ncplane</tt><br/>
<a href="notcurses_stop.3.html">notcurses_stop</a>—shutdown<br/>

@ -44,7 +44,7 @@ At any time, press 'q' to quit. The demo is best run in at least a 80x45 termina
**-p path**: Look in the specified **path** for data files.
**-d delaymult**: Apply a rational multiplier to the standard delay of 1s.
**-d delaymult**: Apply a non-negative rational multiplier to the standard delay of 1s.
**-l loglevel**: Log everything (high log level) or nothing (log level 0) to stderr.

@ -92,6 +92,13 @@ are typically encountered when retrieving data from ncplanes or the rendered
scene (see e.g. **ncplane_at_yx(3)**), or to achieve peak performance when a
particular EGC is heavily reused within a plane.
## Widgets
A few high-level widgets are included, all built atop ncplanes:
* **notcurses_panelreel(3)** for hierarchal display of data
* **notcurses_selector(3)** for selecting one item from a set
## Threads
Notcurses explicitly supports use in multithreaded environments. Most functions
@ -129,7 +136,9 @@ previous action.
**notcurses_output(3)**,
**notcurses_palette(3)**,
**notcurses_panelreel(3)**,
**notcurses_render(3)**, **notcurses_resize(3)**,
**notcurses_render(3)**,
**notcurses_resize(3)**,
**notcurses_selector(3)**,
**notcurses_stats(3)**,
**notcurses_stdplane(3)**, **notcurses_stop(3)**,
**terminfo(5)**, **ascii(7)**, **utf-8(7)**,

@ -22,9 +22,11 @@ notcurses_ncplane - operations on notcurses planes
**void ncplane_yx(const struct ncplane* n, int* restrict y, int* restrict x);**
**int ncplane_set_default(struct ncplane* ncp, const cell* c);**
**int ncplane_set_base_cell(struct ncplane* ncp, const cell* c);**
**int ncplane_default(struct ncplane* ncp, cell* c);**
**int ncplane_set_base(struct ncplane* ncp, uint64_t channels, uint32_t attrword, const char* egc);**
**int ncplane_base(struct ncplane* ncp, cell* c);**
**int ncplane_destroy(struct ncplane* ncp);**

@ -0,0 +1,24 @@
% notcurses_selector(3)
% nick black <nickblack@linux.com>
% v1.1.2
# NAME
notcurses_selector - high level widget for selecting from a set
# SYNOPSIS
**#include <notcurses.h>**
```c
```
# DESCRIPTION
# NOTES
# RETURN VALUES
# SEE ALSO
**notcurses(3)**, **notcurses_ncplane(3)**

@ -644,9 +644,14 @@ namespace ncpp
return map_plane (ncplane_below (plane));
}
bool set_base (Cell &c) const noexcept
bool set_base_cell (Cell &c) const noexcept
{
return ncplane_set_base (plane, c) >= 0;
return ncplane_set_base_cell (plane, c) >= 0;
}
bool set_base (uint64_t channels, uint32_t attrword, const char *egc) const noexcept
{
return ncplane_set_base (plane, channels, attrword, egc) >= 0;
}
bool get_base (Cell &c) const noexcept

@ -66,6 +66,11 @@ namespace ncpp
return ncvisual_stream (get_notcurses (), visual, averr, timescale, streamer, curry);
}
char* subtitle () const noexcept
{
return ncvisual_subtitle (visual);
}
Plane* get_plane () const noexcept;
private:

@ -515,10 +515,17 @@ ncplane_resize_simple(struct ncplane* n, int ylen, int xlen){
// the standard plane.
API int ncplane_destroy(struct ncplane* ncp);
// Set the ncplane's base cell to this cell. If defined, it will be rendered
// anywhere that the ncplane's gcluster is 0. Erasing the ncplane does not
// reset the base cell; this function must instead be called with a zero c.
API int ncplane_set_base(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 a zero 'c'.
API 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.
API int ncplane_set_base(struct ncplane* ncp, uint64_t channels,
uint32_t attrword, const char* egc);
// Extract the ncplane's base cell into 'c'. The reference is invalidated if
// 'ncp' is destroyed.
@ -1774,6 +1781,9 @@ API struct ncvisual* ncvisual_open_plane(struct notcurses* nc, const char* file,
int* averr, int y, int x,
ncscale_e style);
// Return the plane to which this ncvisual is bound.
API struct ncplane* ncvisual_plane(struct ncvisual* ncv);
// Destroy an ncvisual. Rendered elements will not be disrupted, but the visual
// can be neither decoded nor rendered any further.
API void ncvisual_destroy(struct ncvisual* ncv);
@ -1794,15 +1804,35 @@ API struct AVFrame* ncvisual_decode(struct ncvisual* nc, int* averr);
API int ncvisual_render(const struct ncvisual* ncv, int begy, int begx,
int leny, int lenx);
// If a subtitle ought be displayed at this time, return a heap-allocated copy
// of the UTF8 text.
API char* ncvisual_subtitle(const struct ncvisual* ncv);
// Called for each frame rendered from 'ncv'. If anything but 0 is returned,
// the streaming operation ceases immediately, and that value is propagated out.
typedef int (*streamcb)(struct notcurses* nc, struct ncvisual* ncv, void*);
// Shut up and display my frames! Provide as an argument to ncvisual_stream().
// If you'd like subtitles to be decoded, provide a ncplane as the curry. If the
// curry is NULL, subtitles will not be displayed.
static inline int
ncvisual_simple_streamer(struct notcurses* nc, struct ncvisual* ncv __attribute__ ((unused)),
void* curry __attribute__ ((unused))){
return notcurses_render(nc);
ncvisual_simple_streamer(struct notcurses* nc, struct ncvisual* ncv, void* curry){
if(notcurses_render(nc)){
return -1;
}
int ret = 0;
if(curry){
// need a cast for C++ callers
struct ncplane* subncp = (struct ncplane*)curry;
char* subtitle = ncvisual_subtitle(ncv);
if(subtitle){
if(ncplane_putstr_yx(subncp, 0, 0, subtitle) < 0){
ret = -1;
}
free(subtitle);
}
}
return ret;
}
// Stream the entirety of the media, according to its own timing. Blocking,
@ -1818,9 +1848,6 @@ API int ncvisual_stream(struct notcurses* nc, struct ncvisual* ncv,
int* averr, float timescale, streamcb streamer,
void* curry);
// Return the plane to which this ncvisual is bound.
API struct ncplane* ncvisual_plane(struct ncvisual* ncv);
// A panelreel is an notcurses region devoted to displaying zero or more
// line-oriented, contained panels between which the user may navigate. If at
// least one panel exists, there is an active panel. As much of the active
@ -2065,6 +2092,62 @@ API int ncdirect_bg_rgb8(struct ncdirect* nc, unsigned r, unsigned g, unsigned b
API int ncdirect_fg_rgb8(struct ncdirect* nc, unsigned r, unsigned g, unsigned b);
API int ncdirect_stop(struct ncdirect* nc);
// selection widget -- an ncplane with a title header and a body section. the
// body section supports infinite scrolling up and down. the supplied width must
// be large enough to display the header and footer, plus two columns worth of
// borders for both. the supplied body height must be at least 3 rows; four more
// rows will be used in the default configuration. the widget
// looks like: ╭──────────────────────────╮
// │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───╯
//
// At all times, exactly one item is selected.
struct selector_item {
char* option;
char* desc;
};
typedef struct selector_options {
char* title; // title may be NULL, inhibiting riser, saving two rows.
char* secondary; // secondary may be NULL
char* footer; // footer may be NULL
struct selector_item* items; // initial items and descriptions
unsigned itemcount; // number of initial items and descriptions
// maximum number of options to display at once, 0 to use all available space
unsigned maxdisplay;
} selector_options;
struct ncselector;
API struct ncselector* ncselector_create(struct ncplane* n, int y, int x,
const selector_options* opts);
API struct ncselector* ncselector_aligned(struct ncplane* n, int y, ncalign_e align,
const selector_options* opts);
API int ncselector_additem(struct ncselector* n, const struct selector_item* item);
API int ncselector_delitem(struct ncselector* n, const char* item);
// Move up or down in the list. If 'newitem' is not NULL, the newly-selected
// option will be strdup()ed and assigned to '*newitem' (and must be free()d by
// the caller).
API void ncselector_previtem(struct ncselector* n, char** newitem);
API void ncselector_nextitem(struct ncselector* n, char** newitem);
// Destroy the ncselector. If 'item' is not NULL, the last selected option will
// be strdup()ed and assigned to '*item' (and must be free()d by the caller).
API void ncselector_destroy(struct ncselector* n, char** item);
#undef API
#ifdef __cplusplus

@ -6,5 +6,3 @@ cover-package=nose
debug=nose.loader
pdb=1
pdb-failures=1
[build_ext]
library_dirs = '${CMAKE_CURRENT_BINARY_DIR}'

@ -83,7 +83,8 @@ typedef struct ncinput {
int x; // x cell coordinate of event, -1 for undefined
// FIXME modifiers (alt, etc?)
} ncinput;
int ncplane_set_base(struct ncplane* ncp, const cell* c);
int ncplane_set_base_cell(struct ncplane* ncp, const cell* c);
int ncplane_set_base(struct ncplane* ncp, uint64_t channels, uint32_t attrword, const char* egc);
int ncplane_base(struct ncplane* ncp, cell* c);
struct ncplane* notcurses_top(struct notcurses* n);
int notcurses_refresh(struct notcurses* n);
@ -236,6 +237,20 @@ struct ncdirect* notcurses_directmode(const char* termtype, FILE* fp);
int ncdirect_bg_rgb8(struct ncdirect* nc, unsigned r, unsigned g, unsigned b);
int ncdirect_fg_rgb8(struct ncdirect* nc, unsigned r, unsigned g, unsigned b);
int ncdirect_stop(struct ncdirect* nc);
struct ncvisual* ncplane_visual_open(struct ncplane* nc, const char* file, int* averr);
typedef enum {
NCSCALE_NONE,
NCSCALE_SCALE,
NCSCALE_STRETCH,
} ncscale_e;
struct ncvisual* ncvisual_open_plane(struct notcurses* nc, const char* file, int* averr, int y, int x, ncscale_e style);
struct ncplane* ncvisual_plane(struct ncvisual* ncv);
void ncvisual_destroy(struct ncvisual* ncv);
struct AVFrame* ncvisual_decode(struct ncvisual* nc, int* averr);
int ncvisual_render(const struct ncvisual* ncv, int begy, int begx, int leny, int lenx);
char* ncvisual_subtitle(const struct ncvisual* ncv);
typedef int (*streamcb)(struct notcurses* nc, struct ncvisual* ncv, void*);
int ncvisual_stream(struct notcurses* nc, struct ncvisual* ncv, int* averr, float timescale, streamcb streamer, void* curry);
""")
if __name__ == "__main__":

@ -36,8 +36,8 @@ class Ncplane:
def __init__(self, plane):
self.n = plane
def setBase(self, cell):
return lib.ncplane_set_base(self.n, cell.getNccell())
def setBaseCell(self, cell):
return lib.ncplane_set_base_cell(self.n, cell.getNccell())
def getNcplane(self):
return self.n
@ -80,7 +80,7 @@ if __name__ == '__main__':
nc = Notcurses()
c = Cell(nc.stdplane())
c.setBgRGB(0x80, 0xc0, 0x80)
nc.stdplane().setBase(c)
nc.stdplane().setBaseCell(c)
dims = nc.stdplane().getDimensions()
r = 0x80
g = 0x80

@ -25,7 +25,7 @@ int box_demo(struct notcurses* nc){
int ytargbase = (ylen - targy) / 2;
cell c = CELL_SIMPLE_INITIALIZER(' ');
cell_set_bg_default(&c);
ncplane_set_base(n, &c);
ncplane_set_base_cell(n, &c);
cell_release(n, &c);
ncplane_set_fg_rgb(n, 180, 40, 180);
ncplane_set_bg_default(n);

@ -31,7 +31,7 @@ chunli_draw(struct notcurses* nc, const char* ext, int count, const cell* b){
return -1;
}
chuns[i].n = ncvisual_plane(chuns[i].ncv);
ncplane_set_base(chuns[i].n, b);
ncplane_set_base_cell(chuns[i].n, b);
int thisx, thisy;
ncplane_dim_yx(chuns[i].n, &thisy, &thisx);
if(ncplane_move_yx(chuns[i].n, (dimy - thisy) / 2, (dimx - thisx) / 2)){
@ -75,7 +75,7 @@ int chunli_demo(struct notcurses* nc){
return -1;
}
struct ncplane* ncp = ncvisual_plane(ncv);
ncplane_set_base(ncp, &b);
ncplane_set_base_cell(ncp, &b);
if(ncvisual_render(ncv, 0, 0, 0, 0)){
return -1;
}

@ -76,7 +76,7 @@ usage(const char* exe, int status){
fprintf(out, " -l: logging level (%d: silent..%d: manic)\n", NCLOGLEVEL_SILENT, NCLOGLEVEL_TRACE);
fprintf(out, " -H: deploy the HUD\n");
fprintf(out, " -k: keep screen; do not switch to alternate\n");
fprintf(out, " -d: delay multiplier (float)\n");
fprintf(out, " -d: delay multiplier (non-negative float)\n");
fprintf(out, " -f: render to file in addition to stdout\n");
fprintf(out, " -c: constant PRNG seed, useful for benchmarking\n");
fprintf(out, " -p: data file path\n");
@ -227,7 +227,7 @@ handle_opts(int argc, char** argv, notcurses_options* opts, bool* use_hud){
fprintf(stderr, "Couldn't get a float from %s\n", optarg);
usage(*argv, EXIT_FAILURE);
}
if(f <= 0){
if(f < 0){
fprintf(stderr, "Invalid multiplier: %f\n", f);
usage(*argv, EXIT_FAILURE);
}

@ -118,7 +118,7 @@ draw_eagle(struct ncplane* n, const char* sprite){
cell bgc = CELL_TRIVIAL_INITIALIZER;
cell_set_fg_alpha(&bgc, CELL_ALPHA_TRANSPARENT);
cell_set_bg_alpha(&bgc, CELL_ALPHA_TRANSPARENT);
ncplane_set_base(n, &bgc);
ncplane_set_base_cell(n, &bgc);
cell_release(n, &bgc);
size_t s;
int sbytes;

@ -43,7 +43,7 @@ hud_standard_bg(struct ncplane* n){
cell c = CELL_SIMPLE_INITIALIZER(' ');
cell_set_bg_rgb(&c, 0xc0, 0xf0, 0xc0);
cell_set_bg_alpha(&c, CELL_ALPHA_BLEND);
ncplane_set_base(n, &c);
ncplane_set_base_cell(n, &c);
cell_release(n, &c);
return 0;
}
@ -52,7 +52,7 @@ static int
hud_grabbed_bg(struct ncplane* n){
cell c = CELL_SIMPLE_INITIALIZER(' ');
cell_set_bg_rgb(&c, 0x40, 0x90, 0x40);
ncplane_set_base(n, &c);
ncplane_set_base_cell(n, &c);
cell_release(n, &c);
return 0;
}

@ -1,5 +1,23 @@
#include "demo.h"
static int
fader(struct notcurses* nc, struct ncplane* ncp, void* curry){
int* flipmode = curry;
int rows, cols;
ncplane_dim_yx(ncp, &rows, &cols);
for(int x = 5 ; x < cols - 6 ; ++x){
ncplane_set_fg_rgb(ncp, 0xd0, 0xf0, 0xd0);
if(ncplane_putwc_yx(ncp, rows - 3, x, x % 2 == *flipmode % 2 ? L'' : L'') <= 0){
return -1;
}
}
++*flipmode;
if(demo_render(nc)){
return -1;
}
return 0;
}
int intro(struct notcurses* nc){
struct ncplane* ncp;
if((ncp = notcurses_stdplane(nc)) == NULL){
@ -7,7 +25,7 @@ int intro(struct notcurses* nc){
}
cell c = CELL_TRIVIAL_INITIALIZER;
cell_set_bg_rgb(&c, 0x20, 0x20, 0x20);
ncplane_set_base(ncp, &c);
ncplane_set_base_cell(ncp, &c);
int x, y, rows, cols;
ncplane_dim_yx(ncp, &rows, &cols);
cell ul = CELL_TRIVIAL_INITIALIZER, ur = CELL_TRIVIAL_INITIALIZER;
@ -39,7 +57,7 @@ int intro(struct notcurses* nc){
cell_load(ncp, &c, cstr);
cell_set_fg_rgb(&c, 200, 0, 200);
int ys = 200 / (rows - 2);
for(y = 5 ; y < rows - 6 ; ++y){
for(y = 5 ; y < rows - 7 ; ++y){
cell_set_bg_rgb(&c, 0, y * ys , 0);
for(x = 5 ; x < cols - 6 ; ++x){
if(ncplane_putc_yx(ncp, y, x, &c) <= 0){
@ -54,7 +72,7 @@ int intro(struct notcurses* nc){
if(ncplane_cursor_move_yx(ncp, 4, 4)){
return -1;
}
if(ncplane_rounded_box(ncp, 0, channels, rows - 6, cols - 6, 0)){
if(ncplane_rounded_box(ncp, 0, channels, rows - 7, cols - 6, 0)){
return -1;
}
const char s1[] = " Die Welt ist alles, was der Fall ist. ";
@ -74,7 +92,7 @@ int intro(struct notcurses* nc){
}
ncplane_styles_off(ncp, CELL_STYLE_ITALIC);
ncplane_set_fg_rgb(ncp, 0xff, 0xff, 0xff);
if(ncplane_printf_aligned(ncp, rows - 3, NCALIGN_CENTER, "notcurses %s. press 'q' to quit.", notcurses_version()) < 0){
if(ncplane_printf_aligned(ncp, rows - 5, NCALIGN_CENTER, "notcurses %s. press 'q' to quit.", notcurses_version()) < 0){
return -1;
}
ncplane_styles_off(ncp, CELL_STYLE_BOLD);
@ -91,11 +109,17 @@ int intro(struct notcurses* nc){
}
ncplane_styles_off(ncp, CELL_STYLE_BLINK); // heh FIXME replace with pulse
}
if(demo_render(nc)){
return -1;
}
demo_nanosleep(nc, &demodelay);
struct timespec now;
clock_gettime(CLOCK_MONOTONIC_RAW, &now);
uint64_t deadline = timespec_to_ns(&now) + timespec_to_ns(&demodelay) * 2;
int flipmode = 0;
do{
fader(nc, ncp, &flipmode);
const struct timespec iter = { .tv_sec = 0, .tv_nsec = 100000000, };
demo_nanosleep(nc, &iter);
clock_gettime(CLOCK_MONOTONIC_RAW, &now);
}while(timespec_to_ns(&now) < deadline);
struct timespec fade = demodelay;
ncplane_fadeout(ncp, &fade, demo_fader, NULL);
ncplane_fadeout(ncp, &fade, fader, &flipmode);
return 0;
}

@ -111,11 +111,10 @@ static const char* luigis[] = {
static int
draw_luigi(struct ncplane* n, const char* sprite){
cell bgc = CELL_TRIVIAL_INITIALIZER;
cell_set_fg_alpha(&bgc, CELL_ALPHA_TRANSPARENT);
cell_set_bg_alpha(&bgc, CELL_ALPHA_TRANSPARENT);
ncplane_set_base(n, &bgc);
cell_release(n, &bgc);
uint64_t channels = 0;
channels_set_fg_alpha(&channels, CELL_ALPHA_TRANSPARENT);
channels_set_bg_alpha(&channels, CELL_ALPHA_TRANSPARENT);
ncplane_set_base(n, channels, 0, "");
size_t s;
int sbytes;
// optimization so we can elide more color changes, see README's "#perf"
@ -199,10 +198,10 @@ int luigi_demo(struct notcurses* nc){
ncvisual_destroy(wmncv);
return -1;
}
cell b = CELL_TRIVIAL_INITIALIZER;
cell_set_fg_alpha(&b, CELL_ALPHA_TRANSPARENT);
cell_set_bg_alpha(&b, CELL_ALPHA_TRANSPARENT);
ncplane_set_base(ncvisual_plane(wmncv), &b);
uint64_t channels = 0;
channels_set_fg_alpha(&channels, CELL_ALPHA_TRANSPARENT);
channels_set_bg_alpha(&channels, CELL_ALPHA_TRANSPARENT);
ncplane_set_base(ncvisual_plane(wmncv), channels, 0, "");
if(ncvisual_render(wmncv, 0, 0, 0, 0)){
ncvisual_destroy(wmncv);
return -1;

@ -68,9 +68,9 @@ outro_message(struct notcurses* nc, int* rows, int* cols){
}
int xs;
ncplane_yx(non, NULL, &xs);
cell bgcell = CELL_SIMPLE_INITIALIZER(' ');
channels_set_bg_rgb(&bgcell.channels, 0x58, 0x36, 0x58);
if(ncplane_set_base(non, &bgcell) < 0){
uint64_t channels = 0;
channels_set_bg_rgb(&channels, 0x58, 0x36, 0x58);
if(ncplane_set_base(non, channels, 0, " ") < 0){
return NULL;
}
ncplane_dim_yx(non, rows, cols);
@ -117,7 +117,6 @@ outro_message(struct notcurses* nc, int* rows, int* cols){
if(demo_render(nc)){
return NULL;
}
cell_release(non, &bgcell);
*rows = ystart;
*cols = xs;
return non;

@ -113,12 +113,7 @@ fill_chunk(struct ncplane* n, int idx){
return -1;
}
}
cell style;
cell_init(&style);
cell_set_fg_rgb(&style, r, g, b);
cell_prime(n, &style, "", 0, channels);
ncplane_set_base(n, &style);
cell_release(n, &style);
ncplane_set_base(n, channels, 0, "");
return 0;
}

@ -39,7 +39,7 @@ legend(struct notcurses* nc, const char* msg){
cell_set_fg_rgb(&c, 0, 0, 0); // darken surrounding characters by half
cell_set_fg_alpha(&c, CELL_ALPHA_BLEND);
cell_set_bg_alpha(&c, CELL_ALPHA_TRANSPARENT); // don't touch background
ncplane_set_base(n, &c);
ncplane_set_base_cell(n, &c);
ncplane_set_fg(n, 0xd78700);
ncplane_set_bg(n, 0);
if(ncplane_printf_aligned(n, 1, NCALIGN_CENTER, " %s ", msg) < 0){
@ -108,7 +108,7 @@ slidepanel(struct notcurses* nc){
// of the underlying desktop).
cell c = CELL_SIMPLE_INITIALIZER(' ');
struct timespec cur;
ncplane_set_base(n, &c);
ncplane_set_base_cell(n, &c);
clock_gettime(CLOCK_MONOTONIC, &cur);
uint64_t deadlinens = timespec_to_ns(&cur) + DELAYSCALE * timespec_to_ns(&demodelay);
int velx = random() % 4 + 1;
@ -122,7 +122,7 @@ slidepanel(struct notcurses* nc){
ncplane_destroy(l);
cell_load_simple(n, &c, '\0');
ncplane_set_base(n, &c);
ncplane_set_base_cell(n, &c);
clock_gettime(CLOCK_MONOTONIC, &cur);
deadlinens = timespec_to_ns(&cur) + DELAYSCALE * timespec_to_ns(&demodelay);
l = legend(nc, "default background, all opaque, no glyph");
@ -136,7 +136,7 @@ slidepanel(struct notcurses* nc){
// Next, we set our foreground transparent, allowing characters underneath to
// be seen in their natural colors. Our background remains opaque+default.
cell_set_fg_alpha(&c, CELL_ALPHA_TRANSPARENT);
ncplane_set_base(n, &c);
ncplane_set_base_cell(n, &c);
clock_gettime(CLOCK_MONOTONIC, &cur);
deadlinens = timespec_to_ns(&cur) + DELAYSCALE * timespec_to_ns(&demodelay);
l = legend(nc, "default background, fg transparent, no glyph");
@ -151,7 +151,7 @@ slidepanel(struct notcurses* nc){
// glyphs in a blended color, with the default background color.
cell_set_fg(&c, 0x80c080);
cell_set_fg_alpha(&c, CELL_ALPHA_BLEND);
ncplane_set_base(n, &c);
ncplane_set_base_cell(n, &c);
clock_gettime(CLOCK_MONOTONIC, &cur);
l = legend(nc, "default background, fg blended, no glyph");
deadlinens = timespec_to_ns(&cur) + DELAYSCALE * timespec_to_ns(&demodelay);
@ -166,7 +166,7 @@ slidepanel(struct notcurses* nc){
// fixed color, with the default background color.
cell_set_fg(&c, 0x80c080);
cell_set_fg_alpha(&c, CELL_ALPHA_OPAQUE);
ncplane_set_base(n, &c);
ncplane_set_base_cell(n, &c);
clock_gettime(CLOCK_MONOTONIC, &cur);
l = legend(nc, "default background, fg colored opaque, no glyph");
deadlinens = timespec_to_ns(&cur) + DELAYSCALE * timespec_to_ns(&demodelay);
@ -183,7 +183,7 @@ slidepanel(struct notcurses* nc){
cell_set_fg_default(&c);
cell_set_fg_alpha(&c, CELL_ALPHA_TRANSPARENT);
cell_set_bg_alpha(&c, CELL_ALPHA_OPAQUE);
ncplane_set_base(n, &c);
ncplane_set_base_cell(n, &c);
clock_gettime(CLOCK_MONOTONIC, &cur);
l = legend(nc, "default colors, fg transparent, print glyph");
deadlinens = timespec_to_ns(&cur) + DELAYSCALE * timespec_to_ns(&demodelay);
@ -198,7 +198,7 @@ slidepanel(struct notcurses* nc){
// background color from below us.
cell_set_fg_alpha(&c, CELL_ALPHA_TRANSPARENT);
cell_set_bg_alpha(&c, CELL_ALPHA_TRANSPARENT);
ncplane_set_base(n, &c);
ncplane_set_base_cell(n, &c);
clock_gettime(CLOCK_MONOTONIC, &cur);
l = legend(nc, "all transparent, print glyph");
deadlinens = timespec_to_ns(&cur) + DELAYSCALE * timespec_to_ns(&demodelay);
@ -215,7 +215,7 @@ slidepanel(struct notcurses* nc){
cell_set_bg_alpha(&c, CELL_ALPHA_BLEND);
cell_set_fg(&c, 0x80c080);
cell_set_bg(&c, 0x204080);
ncplane_set_base(n, &c);
ncplane_set_base_cell(n, &c);
clock_gettime(CLOCK_MONOTONIC, &cur);
l = legend(nc, "all blended, print glyph");
deadlinens = timespec_to_ns(&cur) + DELAYSCALE * timespec_to_ns(&demodelay);

@ -52,7 +52,7 @@ draw_block(struct ncplane* nn, uint32_t blockstart){
for(z = 0 ; z < CHUNKSIZE ; ++z){
wchar_t w[2] = { blockstart + chunk * CHUNKSIZE + z, L'\0' };
char utf8arr[MB_CUR_MAX * 3 + 1];
if(wcswidth(w, sizeof(w) / sizeof(*w)) >= 1 && iswgraph(w[0])){
if(wcswidth(w, INT_MAX) >= 1 && iswgraph(w[0])){
mbstate_t ps;
memset(&ps, 0, sizeof(ps));
const wchar_t *wptr = w;
@ -174,11 +174,11 @@ int unicodeblocks_demo(struct notcurses* nc){
if(header == NULL){
return -1;
}
cell c = CELL_TRIVIAL_INITIALIZER;
cell_set_fg_alpha(&c, CELL_ALPHA_BLEND);
cell_set_fg(&c, 0x004000);
cell_set_bg(&c, 0x0);
ncplane_set_base(header, &c);
uint64_t channels = 0;
channels_set_fg_alpha(&channels, CELL_ALPHA_BLEND);
channels_set_fg(&channels, 0x004000);
channels_set_bg(&channels, 0x0);
ncplane_set_base(header, channels, 0, "");
for(sindex = 0 ; sindex < sizeof(blocks) / sizeof(*blocks) ; ++sindex){
ncplane_set_bg_rgb(n, 0, 0, 0);
uint32_t blockstart = blocks[sindex].start;

@ -40,8 +40,7 @@ legend(struct notcurses* nc, int dimy, int dimx){
ncplane_set_bg_alpha(n, CELL_ALPHA_TRANSPARENT);
uint64_t channels = 0;
channels_set_bg_alpha(&channels, CELL_ALPHA_TRANSPARENT);
cell c = CELL_INITIALIZER(' ', 0, channels);
ncplane_set_base(n, &c);
ncplane_set_base(n, channels, 0, " ");
ncplane_styles_set(n, CELL_STYLE_BOLD);
ncplane_set_fg_rgb(n, 0xff, 0xff, 0xff);
if(ncplane_putstr_aligned(n, 0, NCALIGN_CENTER, "target launch") <= 0){
@ -122,10 +121,10 @@ int view_demo(struct notcurses* nc){
ncplane_destroy(dsplane);
return -1;
}
cell b = CELL_TRIVIAL_INITIALIZER;
cell_set_fg_alpha(&b, CELL_ALPHA_TRANSPARENT);
cell_set_bg_alpha(&b, CELL_ALPHA_TRANSPARENT);
ncplane_set_base(ncvisual_plane(ncv2), &b);
uint64_t channels = 0;
channels_set_fg_alpha(&channels, CELL_ALPHA_TRANSPARENT);
channels_set_bg_alpha(&channels, CELL_ALPHA_TRANSPARENT);
ncplane_set_base(ncvisual_plane(ncv2), channels, 0, "");
demo_render(nc);
demo_nanosleep(nc, &demodelay);
ncvisual_destroy(ncv);

@ -17,12 +17,11 @@ mathplane(struct notcurses* nc){
const int HEIGHT = 9;
const int WIDTH = dimx;
struct ncplane* n = ncplane_new(nc, HEIGHT, WIDTH, dimy - HEIGHT - 1, dimx - WIDTH, NULL);
cell b = CELL_TRIVIAL_INITIALIZER;
cell_set_fg(&b, 0x2b50c8); // metallic gold, inverted
cell_set_fg_alpha(&b, CELL_ALPHA_BLEND);
cell_set_bg_alpha(&b, CELL_ALPHA_TRANSPARENT);
ncplane_set_base(n, &b);
cell_release(n, &b);
uint64_t channels = 0;
channels_set_fg(&channels, 0x2b50c8); // metallic gold, inverted
channels_set_fg_alpha(&channels, CELL_ALPHA_BLEND);
channels_set_bg_alpha(&channels, CELL_ALPHA_TRANSPARENT);
ncplane_set_base(n, channels, 0, "");
ncplane_set_fg(n, 0xd4af37); // metallic gold
ncplane_set_bg(n, 0x0);
if(n){
@ -47,9 +46,9 @@ lighten(struct ncplane* n, cell* c, int distance, int y, int x){
}
unsigned r, g, b;
cell_fg_rgb(c, &r, &g, &b);
r += rand() % (20 / (5 * distance + 1) + 1);
g += rand() % (20 / (5 * distance + 1) + 1);
b += rand() % (20 / (5 * distance + 1) + 1);
r += rand() % (64 / (2 * distance + 1) + 1);
g += rand() % (64 / (2 * distance + 1) + 1);
b += rand() % (64 / (2 * distance + 1) + 1);
cell_set_fg_rgb_clipped(c, r, g, b);
return ncplane_putc_yx(n, y, x, c);
}
@ -162,14 +161,13 @@ worm_thread(void* vnc){
static int
message(struct ncplane* n, int maxy, int maxx, int num, int total,
int bytes_out, int egs_out, int cols_out){
cell c = CELL_TRIVIAL_INITIALIZER;
cell_set_fg_alpha(&c, CELL_ALPHA_TRANSPARENT);
cell_set_bg_alpha(&c, CELL_ALPHA_TRANSPARENT);
ncplane_set_base(n, &c);
cell_release(n, &c);
uint64_t channels = 0;
channels_set_fg_alpha(&channels, CELL_ALPHA_TRANSPARENT);
channels_set_bg_alpha(&channels, CELL_ALPHA_TRANSPARENT);
ncplane_set_base(n, channels, 0, "");
ncplane_set_fg_rgb(n, 255, 255, 255);
ncplane_set_bg_rgb(n, 32, 64, 32);
uint64_t channels = 0;
channels = 0;
channels_set_fg_rgb(&channels, 255, 255, 255);
channels_set_bg_rgb(&channels, 32, 64, 32);
ncplane_cursor_move_yx(n, 2, 0);

@ -45,10 +45,10 @@ perframecb(struct notcurses* nc, struct ncvisual* ncv __attribute__ ((unused)),
*(struct ncplane**)vnewplane = n;
}
ncplane_dim_yx(n, &dimy, &dimx);
cell c = CELL_SIMPLE_INITIALIZER(' ');
cell_set_fg_alpha(&c, CELL_ALPHA_TRANSPARENT);
cell_set_bg_alpha(&c, CELL_ALPHA_TRANSPARENT);
ncplane_set_base(n, &c);
uint64_t channels = 0;
channels_set_fg_alpha(&channels, CELL_ALPHA_TRANSPARENT);
channels_set_bg_alpha(&channels, CELL_ALPHA_TRANSPARENT);
ncplane_set_base(n, channels, 0, " ");
ncplane_set_bg_alpha(n, CELL_ALPHA_BLEND);
// fg/bg rgbs are set within loop
int x = dimx - (frameno * 2);

@ -3,11 +3,13 @@
#include <wchar.h>
#include <errno.h>
#include <stdio.h>
#include <stddef.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include "notcurses.h"
#ifdef __cplusplus
extern "C" {

@ -92,6 +92,9 @@ ncplane_fadein_internal(ncplane* n, const struct timespec* ts,
}
uint64_t nanosecs_total = ts->tv_sec * NANOSECS_IN_SEC + ts->tv_nsec;
uint64_t nanosecs_step = nanosecs_total / maxsteps;
if(nanosecs_step == 0){
nanosecs_step = 1;
}
struct timespec times;
clock_gettime(CLOCK_MONOTONIC, &times);
// Start time in absolute nanoseconds
@ -171,17 +174,19 @@ int ncplane_fadeout(ncplane* n, const struct timespec* ts, fadecb fader, void* c
if(maxsteps == 0){
maxsteps = 1;
}
uint64_t nanosecs_total = ts->tv_sec * NANOSECS_IN_SEC + ts->tv_nsec;
uint64_t nanosecs_total = timespec_to_ns(ts);
uint64_t nanosecs_step = nanosecs_total / maxsteps;
if(nanosecs_step == 0){
nanosecs_step = 1;
}
struct timespec times;
clock_gettime(CLOCK_MONOTONIC, &times);
// Start time in absolute nanoseconds
uint64_t startns = times.tv_sec * NANOSECS_IN_SEC + times.tv_nsec;
uint64_t startns = timespec_to_ns(&times);
int ret = 0;
do{
unsigned br, bg, bb;
unsigned r, g, b;
clock_gettime(CLOCK_MONOTONIC, &times);
uint64_t curns = times.tv_sec * NANOSECS_IN_SEC + times.tv_nsec;
int iter = (curns - startns) / nanosecs_step + 1;
if(iter > maxsteps){
@ -245,6 +250,7 @@ int ncplane_fadeout(ncplane* n, const struct timespec* ts, fadecb fader, void* c
if(rsleep){
break;
}
clock_gettime(CLOCK_MONOTONIC, &times);
}while(true);
free(pp.channels);
return ret;

@ -1,9 +1,9 @@
#include "internal.h"
#include <ncurses.h> // needed for some definitions, see terminfo(3ncurses)
#include <term.h>
#include <ctype.h>
#include <signal.h>
#include <sys/poll.h>
#include "internal.h"
// CSI (Control Sequence Indicators) originate in the terminal itself, and are
// not reported in their bare form to the user. For our purposes, these usually

@ -1,6 +1,19 @@
#ifndef NOTCURSES_INTERNAL
#define NOTCURSES_INTERNAL
#ifndef DISABLE_FFMPEG
#include <libavutil/error.h>
#include <libavutil/frame.h>
#include <libavutil/pixdesc.h>
#include <libavutil/version.h>
#include <libavutil/imgutils.h>
#include <libavutil/rational.h>
#include <libswscale/swscale.h>
#include <libswscale/version.h>
#include <libavformat/version.h>
#include <libavformat/avformat.h>
#endif
#include <term.h>
#include <time.h>
#include <stdio.h>
@ -15,15 +28,6 @@
#include "version.h"
#include "egcpool.h"
#ifndef DISABLE_FFMPEG
#include <libavutil/error.h>
#include <libavutil/frame.h>
#include <libavutil/pixdesc.h>
#include <libavutil/version.h>
#include <libswscale/version.h>
#include <libavformat/version.h>
#endif
#ifdef __cplusplus
extern "C" {
#endif
@ -68,18 +72,19 @@ typedef struct ncplane {
typedef struct ncvisual {
struct AVFormatContext* fmtctx;
struct AVCodecContext* codecctx;
struct AVCodecContext* codecctx; // video codec context
struct AVCodecContext* subtcodecctx; // subtitle codec context
struct AVFrame* frame;
struct AVFrame* oframe;
struct AVCodec* codec;
struct AVCodec* subtcodec;
struct AVCodecParameters* cparams;
struct AVCodec* subtcodec;
struct AVPacket* packet;
struct AVPacket* subtitle;
struct SwsContext* swsctx;
int packet_outstanding;
int dstwidth, dstheight;
int stream_index; // match against this following av_read_frame()
int sub_stream_index; // subtitle stream index, can be < 0 if no subtitles
float timescale; // scale frame duration by this value
ncplane* ncp;
// if we're creating the plane based off the first frame's dimensions, these
@ -88,6 +93,7 @@ typedef struct ncvisual {
int placex, placey;
ncscale_e style; // none, scale, or stretch
struct notcurses* ncobj; // set iff this ncvisual "owns" its ncplane
AVSubtitle subtitle;
} ncvisual;
// current presentation state of the terminal. it is carried across render
@ -117,6 +123,19 @@ typedef struct renderstate {
bool defaultelidable;
} renderstate;
typedef struct ncselector {
ncplane* ncp; // backing ncplane
unsigned selected; // index of selection
unsigned startdisp; // index of first option displayed
size_t longop; // length of longest option
size_t longdesc; // length of longest description
struct selector_item* items; // list of items and descriptions, heap-copied
unsigned itemcount; // number of pairs in 'items'
char* title; // can be NULL, in which case there's no riser
char* secondary; // can be NULL
char* footer; // can be NULL
} ncselector;
typedef struct ncdirect {
int colors; // number of colors terminfo reported usable for this screen
char* sgr; // set many graphics properties at once

@ -1,11 +1,5 @@
#include <string.h>
#include "version.h"
#ifndef DISABLE_FFMPEG
#include <libavutil/error.h>
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
#include <libavutil/rational.h>
#include <libavformat/avformat.h>
#endif
#include "notcurses.h"
#include "internal.h"
@ -23,8 +17,8 @@ void ncvisual_destroy(ncvisual* ncv){
//avcodec_parameters_free(&ncv->cparams);
sws_freeContext(ncv->swsctx);
av_packet_free(&ncv->packet);
av_packet_free(&ncv->subtitle);
avformat_close_input(&ncv->fmtctx);
avsubtitle_free(&ncv->subtitle);
#endif
if(ncv->ncobj && ncv->ncp){
ncplane_destroy(ncv->ncp);
@ -87,6 +81,51 @@ print_frame_summary(const AVCodecContext* cctx, const AVFrame* f){
f->quality);
}*/
static char*
deass(const char* ass){
// SSA/ASS formats:
// Dialogue: Marked=0,0:02:40.65,0:02:41.79,Wolf main,Cher,0000,0000,0000,,Et les enregistrements de ses ondes delta ?
// FIXME more
if(strncmp(ass, "Dialogue:", strlen("Dialogue:"))){
return NULL;
}
const char* delim = strchr(ass, ',');
int commas = 0; // we want 8
while(delim && commas < 8){
delim = strchr(delim + 1, ',');
++commas;
}
if(!delim){
return NULL;
}
// handle ASS syntax...\i0, \b0, etc.
char* dup = strdup(delim + 1);
char* c = dup;
while(*c){
if(*c == '\\'){
*c = ' ';
++c;
if(*c){
*c = ' ';;
}
}
++c;
}
return dup;
}
char* ncvisual_subtitle(const ncvisual* ncv){
for(unsigned i = 0 ; i < ncv->subtitle.num_rects ; ++i){
const AVSubtitleRect* rect = ncv->subtitle.rects[i];
if(rect->type == SUBTITLE_ASS){
return deass(rect->ass);
}else if(rect->type == SUBTITLE_TEXT) {;
return strdup(rect->text);
}
}
return NULL;
}
AVFrame* ncvisual_decode(ncvisual* nc, int* averr){
bool have_frame = false;
bool unref = false;
@ -106,6 +145,12 @@ AVFrame* ncvisual_decode(ncvisual* nc, int* averr){
return NULL;
}
unref = true;
if(nc->packet->stream_index == nc->sub_stream_index){
int result = 0, ret;
ret = avcodec_decode_subtitle2(nc->subtcodecctx, &nc->subtitle, &result, nc->packet);
if(ret >= 0 && result){
}
}
}while(nc->packet->stream_index != nc->stream_index);
++nc->packet_outstanding;
*averr = avcodec_send_packet(nc->codecctx, nc->packet);
@ -143,6 +188,8 @@ AVFrame* ncvisual_decode(ncvisual* nc, int* averr){
nc->dstwidth = cols;
nc->dstheight = rows * 2;
nc->ncp = ncplane_new(nc->ncobj, rows, cols, nc->placey, nc->placex, NULL);
nc->placey = 0;
nc->placex = 0;
if(nc->ncp == NULL){
*averr = AVERROR(ENOMEM);
return NULL;
@ -215,11 +262,19 @@ ncvisual_open(const char* filename, int* averr){
}
//av_dump_format(ncv->fmtctx, 0, filename, false);
if((*averr = av_find_best_stream(ncv->fmtctx, AVMEDIA_TYPE_SUBTITLE, -1, -1, &ncv->subtcodec, 0)) >= 0){
if((ncv->subtitle = av_packet_alloc()) == NULL){
// fprintf(stderr, "Couldn't allocate subtitles for %s\n", filename);
ncv->sub_stream_index = *averr;
if((ncv->subtcodecctx = avcodec_alloc_context3(ncv->subtcodec)) == NULL){
//fprintf(stderr, "Couldn't allocate decoder for %s\n", filename);
*averr = AVERROR(ENOMEM);
goto err;
}
// FIXME do we need avcodec_parameters_to_context() here?
if((*averr = avcodec_open2(ncv->subtcodecctx, ncv->subtcodec, NULL)) < 0){
//fprintf(stderr, "Couldn't open codec for %s (%s)\n", filename, av_err2str(*averr));
goto err;
}
}else{
ncv->sub_stream_index = -1;
}
if((ncv->packet = av_packet_alloc()) == NULL){
// fprintf(stderr, "Couldn't allocate packet for %s\n", filename);
@ -375,8 +430,9 @@ int ncvisual_render(const ncvisual* ncv, int begy, int begx, int leny, int lenx)
}
}else{
if(memcmp(rgbbase_up, rgbbase_down, 3) == 0){
cell_set_fg_rgb(c, rgbbase_down[0], rgbbase_down[1], rgbbase_down[2]);
cell_set_bg_rgb(c, rgbbase_down[0], rgbbase_down[1], rgbbase_down[2]);
if(cell_load(ncv->ncp, c, " ") <= 0){ // only want the background
if(cell_load(ncv->ncp, c, " ") <= 0){ // only need the background
return -1;
}
}else{
@ -508,6 +564,11 @@ ncvisual* ncvisual_open_plane(notcurses* nc, const char* filename,
return NULL;
}
char* ncvisual_subtitle(const ncvisual* ncv){
(void)ncv;
return NULL;
}
int ncvisual_init(int loglevel){
(void)loglevel;
return 0;

@ -1,3 +1,6 @@
#include "version.h"
#include "egcpool.h"
#include "internal.h"
#include <ncurses.h> // needed for some definitions, see terminfo(3ncurses)
#include <time.h>
#include <term.h>
@ -13,10 +16,6 @@
#include <sys/poll.h>
#include <stdatomic.h>
#include <sys/ioctl.h>
#include "notcurses.h"
#include "internal.h"
#include "version.h"
#include "egcpool.h"
#define ESC "\x1b"
@ -286,8 +285,7 @@ const ncplane* notcurses_stdplane_const(const notcurses* nc){
return nc->stdscr;
}
ncplane* ncplane_new(notcurses* nc, int rows, int cols,
int yoff, int xoff, void* opaque){
ncplane* ncplane_new(notcurses* nc, int rows, int cols, int yoff, int xoff, void* opaque){
ncplane* n = ncplane_create(nc, rows, cols, yoff, xoff);
if(n == NULL){
return n;
@ -906,7 +904,7 @@ notcurses* notcurses_init(const notcurses_options* opts, FILE* outfp){
term_fg_palindex(ret, ret->ttyfp, ret->RGBflag ? 0xe02080 : 3);
if(!ret->RGBflag){ // FIXME
fprintf(ret->ttyfp, "\n Warning! Colors subject to https://github.com/dankamongmen/notcurses/issues/4");
fprintf(ret->ttyfp, "\n Are you specifying a proper DirectColor TERM?\n");
fprintf(ret->ttyfp, "\n Specify a (correct) DirectColor TERM, or COLORTERM.\n");
}else{
/*if((unsigned)ret->colors < (1u << 24u)){
fprintf(ret->ttyfp, "\n Warning! Advertised DirectColor but only %d colors\n", ret->colors);
@ -1118,7 +1116,7 @@ int ncplane_set_bg_palindex(ncplane* n, int idx){
return 0;
}
int ncplane_set_base(ncplane* ncp, const cell* c){
int ncplane_set_base_cell(ncplane* ncp, const cell* c){
int ret = cell_duplicate(ncp, &ncp->basecell, c);
if(ret < 0){
return -1;
@ -1126,6 +1124,10 @@ int ncplane_set_base(ncplane* ncp, const cell* c){
return ret;
}
int ncplane_set_base(ncplane* ncp, uint64_t channels, uint32_t attrword, const char* egc){
return cell_prime(ncp, &ncp->basecell, egc, attrword, channels);
}
int ncplane_base(ncplane* ncp, cell* c){
return cell_duplicate(ncp, c, &ncp->basecell);
}

@ -621,10 +621,7 @@ panelreel* panelreel_create(ncplane* w, const panelreel_options* popts, int efd)
free(pr);
return NULL;
}
cell bgc = CELL_TRIVIAL_INITIALIZER;
bgc.channels = popts->bgchannel;
ncplane_set_base(pr->p, &bgc);
cell_release(pr->p, &bgc);
ncplane_set_base(pr->p, popts->bgchannel, 0, "");
if(panelreel_redraw(pr)){
ncplane_destroy(pr->p);
free(pr);

@ -0,0 +1,226 @@
#include "notcurses.h"
#include "internal.h"
// ideal body width given the ncselector's items and secondary/footer
static size_t
ncselector_body_width(const ncselector* n){
size_t cols = 0;
// the body is the maximum of
// * longop + longdesc + 5
// * secondary + 2
// * footer + 2
if(n->footer && strlen(n->footer) + 2 > cols){
cols = strlen(n->footer) + 2;
}
if(n->secondary && strlen(n->secondary) + 2 > cols){
cols = strlen(n->secondary) + 2;
}
if(n->longop + n->longdesc + 5 > cols){
cols = n->longop + n->longdesc + 5;
}
return cols;
}
// redraw the selector widget in its entirety
static int
ncselector_draw(ncselector* n){
ncplane_erase(n->ncp);
uint64_t channels = 0;
channels_set_fg(&channels, 0x4040f0); // FIXME allow configuration
// if we have a title, we'll draw a riser. the riser is two rows tall, and
// exactly four columns longer than the title, and aligned to the right. we
// draw a rounded box. the body will blow part or all of the bottom away.
int yoff = 0;
if(n->title){
size_t riserwidth = strlen(n->title) + 4;
int offx = ncplane_align(n->ncp, NCALIGN_RIGHT, riserwidth);
ncplane_cursor_move_yx(n->ncp, 0, offx);
ncplane_rounded_box_sized(n->ncp, 0, channels, 3, riserwidth, 0);
ncplane_cursor_move_yx(n->ncp, 1, offx + 2);
ncplane_putstr(n->ncp, n->title); // FIXME allow styling configuration
yoff += 2;
}
size_t bodywidth = ncselector_body_width(n);
int xoff = ncplane_align(n->ncp, NCALIGN_RIGHT, bodywidth);
ncplane_cursor_move_yx(n->ncp, yoff, xoff);
int dimy, dimx;
ncplane_dim_yx(n->ncp, &dimy, &dimx);
ncplane_rounded_box_sized(n->ncp, 0, channels, dimy - yoff, bodywidth, 0);
unsigned printidx = n->startdisp;
int bodyoffset = dimx - bodywidth + 2;
for(yoff += 2 ; yoff < dimy - 2 ; ++yoff){
if(printidx == n->selected){
ncplane_styles_on(n->ncp, CELL_STYLE_REVERSE);
}
ncplane_printf_yx(n->ncp, yoff, bodyoffset, "%*.*s %s", (int)n->longop,
(int)n->longop, n->items[printidx].option,
n->items[printidx].desc);
if(printidx == n->selected){
ncplane_styles_off(n->ncp, CELL_STYLE_REVERSE);
}
++printidx;
}
return notcurses_render(n->ncp->nc);
}
// calculate the necessary dimensions based off properties of the selector and
// the containing screen FIXME should be based on containing ncplane
static int
ncselector_dim_yx(notcurses* nc, const ncselector* n, int* ncdimy, int* ncdimx){
int rows = 0, cols = 0; // desired dimensions
int dimy, dimx; // dimensions of containing screen
notcurses_term_dim_yx(nc, &dimy, &dimx);
if(n->title){ // header adds two rows for riser
rows += 2;
}
// we have a top line, a bottom line, two lines of margin, and must be able
// to display at least one row beyond that, so require five more
rows += 5;
if(rows > dimy){ // insufficient height to display selector
return -1;
}
rows += n->itemcount - 1; // rows necessary to display all options
if(rows > dimy){ // claw excess back
rows = dimy;
}
*ncdimy = rows;
cols = ncselector_body_width(n);
// the riser, if it exists, is header + 4. the cols are the max of these two.
if(n->title && strlen(n->title) + 4 > (size_t)cols){
cols = strlen(n->title) + 4;
}
if(cols > dimx){ // insufficient width to display selector
return -1;
}
*ncdimx = cols;
return 0;
}
ncselector* ncselector_create(ncplane* n, int y, int x, const selector_options* opts){
ncselector* ns = malloc(sizeof(*ns));
ns->title = opts->title ? strdup(opts->title) : NULL;
ns->secondary = opts->secondary ? strdup(opts->secondary) : NULL;
ns->footer = opts->footer ? strdup(opts->footer) : NULL;
ns->selected = 0;
ns->startdisp = 0;
ns->longop = 0;
ns->longdesc = 0;
if(opts->itemcount){
if(!(ns->items = malloc(sizeof(*ns->items) * opts->itemcount))){
free(ns->title); free(ns->secondary); free(ns->footer);
free(n);
return NULL;
}
}else{
ns->items = NULL;
}
for(ns->itemcount = 0 ; ns->itemcount < opts->itemcount ; ++ns->itemcount){
const struct selector_item* src = &opts->items[ns->itemcount];
if(strlen(src->option) > ns->longop){
ns->longop = strlen(src->option);
}
if(strlen(src->desc) > ns->longdesc){
ns->longdesc = strlen(src->desc);
}
ns->items[ns->itemcount].option = strdup(src->option);
ns->items[ns->itemcount].desc = strdup(src->desc);
if(!(ns->items[ns->itemcount].desc && ns->items[ns->itemcount].option)){
free(ns->items[ns->itemcount].option);
free(ns->items[ns->itemcount].desc);
goto freeitems;
}
}
int dimy, dimx;
if(ncselector_dim_yx(n->nc, ns, &dimy, &dimx)){
goto freeitems;
}
if(!(ns->ncp = ncplane_new(n->nc, dimy, dimx, y, x, NULL))){
goto freeitems;
}
ncselector_draw(ns); // deal with error here?
return ns;
freeitems:
while(ns->itemcount--){
free(ns->items[ns->itemcount].option);
free(ns->items[ns->itemcount].desc);
}
free(ns->items);
free(ns->title); free(ns->secondary); free(ns->footer);
free(ns);
return NULL;
}
ncselector* ncselector_aligned(ncplane* n, int y, ncalign_e align, const selector_options* opts);
int ncselector_additem(ncselector* n, const struct selector_item* item){
size_t newsize = sizeof(*n->items) * (n->itemcount + 1);
struct selector_item* items = realloc(n->items, newsize);
if(!items){
return -1;
}
n->items = items;
n->items[n->itemcount].option = strdup(item->option);
n->items[n->itemcount].desc = strdup(item->desc);
++n->itemcount;
return ncselector_draw(n);
}
int ncselector_delitem(ncselector* n, const char* item){
for(unsigned idx = 0 ; idx < n->itemcount ; ++idx){
if(strcmp(n->items[idx].option, item) == 0){ // found it
free(n->items[idx].option);
free(n->items[idx].desc);
if(idx < n->itemcount - 1){
memmove(n->items + idx, n->items + idx + 1, sizeof(*n->items) * (n->itemcount - idx - 1));
}else{
if(idx){
--n->selected;
}
}
--n->itemcount;
return ncselector_draw(n);
}
}
return -1; // wasn't found
}
void ncselector_previtem(ncselector* n, char** newitem){
if(n->selected == 0){
n->selected = n->itemcount;
}
--n->selected;
if(newitem){
*newitem = strdup(n->items[n->selected].option);
}
ncselector_draw(n);
}
void ncselector_nextitem(ncselector* n, char** newitem){
++n->selected;
if(n->selected == n->itemcount){
n->selected = 0;
}
if(newitem){
*newitem = strdup(n->items[n->selected].option);
}
ncselector_draw(n);
}
void ncselector_destroy(ncselector* n, char** item){
if(n){
if(item){
*item = n->items[n->selected].option;
n->items[n->selected].option = NULL;
}
while(n->itemcount--){
free(n->items[n->itemcount].option);
free(n->items[n->itemcount].desc);
}
free(n->items);
free(n->title);
free(n->secondary);
free(n->footer);
free(n);
}
}

@ -28,7 +28,7 @@ int tabletfxn(struct tablet* _t, int begx, int begy, int maxx, int maxy,
p->erase();
Cell c(' ');
c.set_bg((((uintptr_t)t) % 0x1000000) + cliptop + begx + maxx);
p->set_base(c);
p->set_base_cell(c);
p->release(c);
return tctx->getLines() > maxy - begy ? maxy - begy : tctx->getLines();
}

@ -0,0 +1,59 @@
#include <stdio.h>
#include <string.h>
#include <locale.h>
#include <stdlib.h>
#include <notcurses.h>
static struct selector_item items[] = {
{ "first", "this is the first option", },
{ "2nd", "this is the second option", },
{ "3", "third, third, third option am i", },
{ "fourth", "i have another option here", },
{ "five", "golden rings", },
{ "666", "now it is time for me to REIGN IN BLOOD", },
{ "7seven7", "this monkey's gone to heaven", },
{ "8 8 8", "the chinese love me, i'm told", },
{ "nine", "nine, nine, nine 'cause you left me", },
{ "ten", "stunning and brave", },
};
int main(void){
if(!setlocale(LC_ALL, "")){
return EXIT_FAILURE;
}
notcurses_options opts;
memset(&opts, 0, sizeof(opts));
struct notcurses* nc = notcurses_init(&opts, stdout);
if(nc == NULL){
return EXIT_FAILURE;
}
selector_options sopts;
memset(&sopts, 0, sizeof(sopts));
sopts.maxdisplay = 4;
sopts.items = items;
sopts.itemcount = sizeof(items) / sizeof(*items);
sopts.title = "this is an awfully long example of a selector title";
ncplane_set_fg(notcurses_stdplane(nc), 0x40f040);
ncplane_putstr_aligned(notcurses_stdplane(nc), 0, NCALIGN_RIGHT, "selector widget demo");
struct ncselector* ns = ncselector_create(notcurses_stdplane(nc), 3, 0, &sopts);
if(ns == NULL){
notcurses_stop(nc);
return EXIT_FAILURE;
}
notcurses_render(nc);
char32_t keypress;
while((keypress = notcurses_getc_blocking(nc, NULL)) != (char32_t)-1){
switch(keypress){
case NCKEY_UP: case 'k': ncselector_previtem(ns, NULL); break;
case NCKEY_DOWN: case 'j': ncselector_nextitem(ns, NULL); break;
}
if(keypress == 'q'){
break;
}
notcurses_render(nc);
}
if(notcurses_stop(nc)){
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}

@ -26,7 +26,7 @@ void usage(std::ostream& o, const char* name, int exitcode){
o << "usage: " << name << " [ -h ] [ -l loglevel ] [ -d mult ] [ -s scaletype ] files" << '\n';
o << " -l loglevel: integer between 0 and 9, goes to stderr'\n";
o << " -s scaletype: one of 'none', 'scale', or 'stretch'\n";
o << " -d mult: positive floating point scale for frame time" << std::endl;
o << " -d mult: non-negative floating point scale for frame time" << std::endl;
exit(exitcode);
}
@ -37,7 +37,10 @@ timespec_to_ns(const struct timespec* ts){
return ts->tv_sec * NANOSECS_IN_SEC + ts->tv_nsec;
}
// frame count is in the curry. original time is in the ncplane's userptr.
// FIXME internalize this via complex curry
static struct ncplane* subtitle_plane = nullptr;
// frame count is in the curry. original time is in the ncvisual's ncplane's userptr.
int perframe([[maybe_unused]] struct notcurses* _nc, struct ncvisual* ncv, void* vframecount){
NotCurses &nc = NotCurses::get_instance ();
struct timespec* start = static_cast<struct timespec*>(ncplane_userptr(ncvisual_plane(ncv)));
@ -53,7 +56,25 @@ int perframe([[maybe_unused]] struct notcurses* _nc, struct ncvisual* ncv, void*
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
int64_t ns = timespec_to_ns(&now) - timespec_to_ns(start);
stdn->printf(0, NCAlign::Left, "Got frame %05d\u2026", *framecount);
stdn->printf(0, NCAlign::Left, "frame %06d\u2026", *framecount);
char* subtitle = ncvisual_subtitle(ncv);
if(subtitle){
if(!subtitle_plane){
int dimx, dimy;
notcurses_term_dim_yx(_nc, &dimy, &dimx);
subtitle_plane = ncplane_new(_nc, 1, dimx, dimy - 1, 0, nullptr);
uint64_t channels = 0;
channels_set_fg_alpha(&channels, CELL_ALPHA_TRANSPARENT);
channels_set_bg_alpha(&channels, CELL_ALPHA_TRANSPARENT);
ncplane_set_base(subtitle_plane, channels, 0, "");
ncplane_set_fg(subtitle_plane, 0x00ffff);
ncplane_set_bg_alpha(subtitle_plane, CELL_ALPHA_TRANSPARENT);
}else{
ncplane_erase(subtitle_plane);
}
ncplane_printf_yx(subtitle_plane, 0, 0, "%s", subtitle);
free(subtitle);
}
const int64_t h = ns / (60 * 60 * NANOSECS_IN_SEC);
ns -= h * (60 * 60 * NANOSECS_IN_SEC);
const int64_t m = ns / (60 * NANOSECS_IN_SEC);
@ -105,7 +126,7 @@ int handle_opts(int argc, char** argv, notcurses_options& opts, float* timescale
ss << optarg;
float ts;
ss >> ts;
if(ts <= 0){
if(ts < 0){
std::cerr << "Invalid timescale [" << optarg << "] (wanted (0..))\n";
usage(std::cerr, argv[0], EXIT_FAILURE);
}
@ -164,6 +185,9 @@ int main(int argc, char** argv){
std::cerr << "Error decoding " << argv[i] << ": " << errbuf.data() << std::endl;
return EXIT_FAILURE;
}else if(r == 0){
std::unique_ptr<Plane> stdn(nc.get_stdplane());
stdn->printf(0, NCAlign::Center, "press any key to advance");
nc.render();
char32_t ie = nc.getc(true);
if(ie == (char32_t)-1){
break;

@ -32,7 +32,7 @@ SUBCASE("SetItalic") {
cell_styles_set(&c, CELL_STYLE_ITALIC);
REQUIRE(1 == cell_load(n_, &c, "i"));
cell_set_fg_rgb(&c, 255, 255, 255);
ncplane_set_base(n_, &c);
ncplane_set_base_cell(n_, &c);
cell_release(n_, &c);
CHECK(0 == notcurses_render(nc_));
cell_styles_off(&c, CELL_STYLE_ITALIC);
@ -45,7 +45,7 @@ SUBCASE("SetItalic") {
cell_styles_set(&c, CELL_STYLE_BOLD);
REQUIRE(1 == cell_load(n_, &c, "b"));
cell_set_fg_rgb(&c, 255, 255, 255);
ncplane_set_base(n_, &c);
ncplane_set_base_cell(n_, &c);
cell_release(n_, &c);
CHECK(0 == notcurses_render(nc_));
cell_styles_off(&c, CELL_STYLE_BOLD);
@ -58,7 +58,7 @@ SUBCASE("SetItalic") {
cell_styles_set(&c, CELL_STYLE_UNDERLINE);
REQUIRE(1 == cell_load(n_, &c, "u"));
cell_set_fg_rgb(&c, 255, 255, 255);
ncplane_set_base(n_, &c);
ncplane_set_base_cell(n_, &c);
cell_release(n_, &c);
CHECK(0 == notcurses_render(nc_));
cell_styles_off(&c, CELL_STYLE_UNDERLINE);

@ -0,0 +1,72 @@
#include "main.h"
#include <cstring>
#include <iostream>
TEST_CASE("SelectorTest") {
if(getenv("TERM") == nullptr){
return;
}
notcurses_options nopts{};
nopts.inhibit_alternate_screen = true;
nopts.suppress_banner = true;
FILE* outfp_ = fopen("/dev/tty", "wb");
REQUIRE(outfp_);
struct notcurses* nc_ = notcurses_init(&nopts, outfp_);
REQUIRE(nc_);
struct ncplane* n_ = notcurses_stdplane(nc_);
REQUIRE(n_);
REQUIRE(0 == ncplane_cursor_move_yx(n_, 0, 0));
SUBCASE("EmptySelector") {
struct selector_options opts{};
struct ncselector* ncs = ncselector_create(notcurses_stdplane(nc_), 0, 0, &opts);
REQUIRE(nullptr != ncs);
CHECK(0 == notcurses_render(nc_));
ncselector_destroy(ncs, nullptr);
}
SUBCASE("TitledSelector") {
struct selector_options opts{};
opts.title = strdup("hey hey whaddya say");
struct ncselector* ncs = ncselector_create(notcurses_stdplane(nc_), 0, 0, &opts);
REQUIRE(nullptr != ncs);
CHECK(0 == notcurses_render(nc_));
ncselector_destroy(ncs, nullptr);
}
SUBCASE("SecondarySelector") {
struct selector_options opts{};
opts.secondary = strdup("this is not a title, but it's not *not* a title");
struct ncselector* ncs = ncselector_create(notcurses_stdplane(nc_), 0, 0, &opts);
REQUIRE(nullptr != ncs);
CHECK(0 == notcurses_render(nc_));
ncselector_destroy(ncs, nullptr);
}
SUBCASE("FooterSelector") {
struct selector_options opts{};
opts.secondary = strdup("i am a lone footer, little old footer");
struct ncselector* ncs = ncselector_create(notcurses_stdplane(nc_), 0, 0, &opts);
REQUIRE(nullptr != ncs);
CHECK(0 == notcurses_render(nc_));
ncselector_destroy(ncs, nullptr);
}
SUBCASE("PopulatedSelector") {
selector_item items[] = {
{ strdup("op1"), strdup("this is option 1"), },
{ strdup("2ndop"), strdup("this is option #2"), },
{ strdup("tres"), strdup("option the third"), },
};
struct selector_options opts{};
opts.items = items;
opts.itemcount = sizeof(items) / sizeof(*items);
struct ncselector* ncs = ncselector_create(notcurses_stdplane(nc_), 0, 0, &opts);
REQUIRE(nullptr != ncs);
CHECK(0 == notcurses_render(nc_));
ncselector_destroy(ncs, nullptr);
}
CHECK(0 == notcurses_stop(nc_));
CHECK(0 == fclose(outfp_));
}
Loading…
Cancel
Save