diff --git a/.drone.yml b/.drone.yml
index b83fd02cc..6cb9659b4 100644
--- a/.drone.yml
+++ b/.drone.yml
@@ -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
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0d76a4e5f..82f4f98b4 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -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()
diff --git a/README.md b/README.md
index e58d82d0c..661be2d0f 100644
--- a/README.md
+++ b/README.md
@@ -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
diff --git a/doc/man/index.html b/doc/man/index.html
index 37cc5ef4f..88bf95879 100644
--- a/doc/man/index.html
+++ b/doc/man/index.html
@@ -33,9 +33,10 @@
notcurses_ncvisual—operations on ncvisual objects
notcurses_output—drawing text on ncplanes
notcurses_palette—operations on notcurses palettes
- notcurses_panelreel—a high-level widget for hierarchical data
+ notcurses_panelreel—high-level widget for hierarchical data
notcurses_render—sync the physical display
notcurses_resize—resize the standard plane based off screen size
+ notcurses_selector—high-level widget for selecting from a set
notcurses_stats—notcurses runtime statistics
notcurses_stdplane—acquire the standard ncplane
notcurses_stop—shutdown
diff --git a/doc/man/man1/notcurses-demo.1.md b/doc/man/man1/notcurses-demo.1.md
index 0b4789aaf..47867442a 100644
--- a/doc/man/man1/notcurses-demo.1.md
+++ b/doc/man/man1/notcurses-demo.1.md
@@ -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.
diff --git a/doc/man/man3/notcurses.3.md b/doc/man/man3/notcurses.3.md
index ad7e02cc6..9744aeedc 100644
--- a/doc/man/man3/notcurses.3.md
+++ b/doc/man/man3/notcurses.3.md
@@ -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)**,
diff --git a/doc/man/man3/notcurses_ncplane.3.md b/doc/man/man3/notcurses_ncplane.3.md
index 8039f2e6f..44798fdf8 100644
--- a/doc/man/man3/notcurses_ncplane.3.md
+++ b/doc/man/man3/notcurses_ncplane.3.md
@@ -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);**
diff --git a/doc/man/man3/notcurses_selector.3.md b/doc/man/man3/notcurses_selector.3.md
new file mode 100644
index 000000000..71b9d2d54
--- /dev/null
+++ b/doc/man/man3/notcurses_selector.3.md
@@ -0,0 +1,24 @@
+% notcurses_selector(3)
+% nick black
+% v1.1.2
+
+# NAME
+
+notcurses_selector - high level widget for selecting from a set
+
+# SYNOPSIS
+
+**#include **
+
+```c
+```
+
+# DESCRIPTION
+
+# NOTES
+
+# RETURN VALUES
+
+# SEE ALSO
+
+**notcurses(3)**, **notcurses_ncplane(3)**
diff --git a/include/ncpp/Plane.hh b/include/ncpp/Plane.hh
index f7ebb6383..73a3fe096 100644
--- a/include/ncpp/Plane.hh
+++ b/include/ncpp/Plane.hh
@@ -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
diff --git a/include/ncpp/Visual.hh b/include/ncpp/Visual.hh
index 2a97629f2..89d7329ab 100644
--- a/include/ncpp/Visual.hh
+++ b/include/ncpp/Visual.hh
@@ -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:
diff --git a/include/notcurses.h b/include/notcurses.h
index 6a778e3fd..0fa9ee001 100644
--- a/include/notcurses.h
+++ b/include/notcurses.h
@@ -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
diff --git a/python/setup.cfg.in b/python/setup.cfg.in
index fec4c9c07..12bf26e39 100644
--- a/python/setup.cfg.in
+++ b/python/setup.cfg.in
@@ -6,5 +6,3 @@ cover-package=nose
debug=nose.loader
pdb=1
pdb-failures=1
-[build_ext]
-library_dirs = '${CMAKE_CURRENT_BINARY_DIR}'
diff --git a/python/src/notcurses/build_notcurses.py b/python/src/notcurses/build_notcurses.py
index c704407f6..3a70e817e 100644
--- a/python/src/notcurses/build_notcurses.py
+++ b/python/src/notcurses/build_notcurses.py
@@ -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__":
diff --git a/python/src/notcurses/notcurses.py b/python/src/notcurses/notcurses.py
index b0b0ba9a3..a84d897fb 100644
--- a/python/src/notcurses/notcurses.py
+++ b/python/src/notcurses/notcurses.py
@@ -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
diff --git a/src/demo/boxdemo.c b/src/demo/boxdemo.c
index 8975d0271..4e1b02e59 100644
--- a/src/demo/boxdemo.c
+++ b/src/demo/boxdemo.c
@@ -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);
diff --git a/src/demo/chunli.c b/src/demo/chunli.c
index 146baf8dc..18db2a50e 100644
--- a/src/demo/chunli.c
+++ b/src/demo/chunli.c
@@ -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;
}
diff --git a/src/demo/demo.c b/src/demo/demo.c
index c7d086359..5e79e8da5 100644
--- a/src/demo/demo.c
+++ b/src/demo/demo.c
@@ -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);
}
diff --git a/src/demo/eagle.c b/src/demo/eagle.c
index 546dc2496..720c9487a 100644
--- a/src/demo/eagle.c
+++ b/src/demo/eagle.c
@@ -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;
diff --git a/src/demo/hud.c b/src/demo/hud.c
index 542922439..5742f4748 100644
--- a/src/demo/hud.c
+++ b/src/demo/hud.c
@@ -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;
}
diff --git a/src/demo/intro.c b/src/demo/intro.c
index ec887512f..b1be55737 100644
--- a/src/demo/intro.c
+++ b/src/demo/intro.c
@@ -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;
}
diff --git a/src/demo/luigi.c b/src/demo/luigi.c
index cd3bf7017..dae488f9c 100644
--- a/src/demo/luigi.c
+++ b/src/demo/luigi.c
@@ -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;
diff --git a/src/demo/outro.c b/src/demo/outro.c
index f4db902e8..b777fabb4 100644
--- a/src/demo/outro.c
+++ b/src/demo/outro.c
@@ -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;
diff --git a/src/demo/sliding.c b/src/demo/sliding.c
index 789c0459f..b3eefd950 100644
--- a/src/demo/sliding.c
+++ b/src/demo/sliding.c
@@ -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;
}
diff --git a/src/demo/trans.c b/src/demo/trans.c
index 38df95c93..1440e2ad4 100644
--- a/src/demo/trans.c
+++ b/src/demo/trans.c
@@ -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);
diff --git a/src/demo/unicodeblocks.c b/src/demo/unicodeblocks.c
index d6b6e366a..284c3faa8 100644
--- a/src/demo/unicodeblocks.c
+++ b/src/demo/unicodeblocks.c
@@ -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;
diff --git a/src/demo/view.c b/src/demo/view.c
index 09d8f8c3f..515919333 100644
--- a/src/demo/view.c
+++ b/src/demo/view.c
@@ -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);
diff --git a/src/demo/witherworm.c b/src/demo/witherworm.c
index 1a001eea3..8a8cb1eed 100644
--- a/src/demo/witherworm.c
+++ b/src/demo/witherworm.c
@@ -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);
diff --git a/src/demo/xray.c b/src/demo/xray.c
index 58b5eaddc..b4469015f 100644
--- a/src/demo/xray.c
+++ b/src/demo/xray.c
@@ -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);
diff --git a/src/lib/egcpool.h b/src/lib/egcpool.h
index 72e4de188..b77b813b9 100644
--- a/src/lib/egcpool.h
+++ b/src/lib/egcpool.h
@@ -3,11 +3,13 @@
#include
#include
+#include
#include
#include
#include
#include
#include
+#include "notcurses.h"
#ifdef __cplusplus
extern "C" {
diff --git a/src/lib/fade.c b/src/lib/fade.c
index 105677642..5de3ba478 100644
--- a/src/lib/fade.c
+++ b/src/lib/fade.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, ×);
// 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, ×);
// Start time in absolute nanoseconds
- uint64_t startns = times.tv_sec * NANOSECS_IN_SEC + times.tv_nsec;
+ uint64_t startns = timespec_to_ns(×);
int ret = 0;
do{
unsigned br, bg, bb;
unsigned r, g, b;
- clock_gettime(CLOCK_MONOTONIC, ×);
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, ×);
}while(true);
free(pp.channels);
return ret;
diff --git a/src/lib/input.c b/src/lib/input.c
index 1924ec2bc..640817fe6 100644
--- a/src/lib/input.c
+++ b/src/lib/input.c
@@ -1,9 +1,9 @@
+#include "internal.h"
#include // needed for some definitions, see terminfo(3ncurses)
#include
#include
#include
#include
-#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
diff --git a/src/lib/internal.h b/src/lib/internal.h
index bd6c3bd3a..26a3b4785 100644
--- a/src/lib/internal.h
+++ b/src/lib/internal.h
@@ -1,6 +1,19 @@
#ifndef NOTCURSES_INTERNAL
#define NOTCURSES_INTERNAL
+#ifndef DISABLE_FFMPEG
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#endif
+
#include
#include
#include
@@ -15,15 +28,6 @@
#include "version.h"
#include "egcpool.h"
-#ifndef DISABLE_FFMPEG
-#include
-#include
-#include
-#include
-#include
-#include
-#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
diff --git a/src/lib/libav.c b/src/lib/libav.c
index 2f10d6962..464b08fee 100644
--- a/src/lib/libav.c
+++ b/src/lib/libav.c
@@ -1,11 +1,5 @@
+#include
#include "version.h"
-#ifndef DISABLE_FFMPEG
-#include
-#include
-#include
-#include
-#include
-#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;
diff --git a/src/lib/notcurses.c b/src/lib/notcurses.c
index 5e60c580d..92b36a0cc 100644
--- a/src/lib/notcurses.c
+++ b/src/lib/notcurses.c
@@ -1,3 +1,6 @@
+#include "version.h"
+#include "egcpool.h"
+#include "internal.h"
#include // needed for some definitions, see terminfo(3ncurses)
#include
#include
@@ -13,10 +16,6 @@
#include
#include
#include
-#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);
}
diff --git a/src/lib/panelreel.c b/src/lib/panelreel.c
index e7dcdbbb3..6d9c73955 100644
--- a/src/lib/panelreel.c
+++ b/src/lib/panelreel.c
@@ -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);
diff --git a/src/lib/selector.c b/src/lib/selector.c
new file mode 100644
index 000000000..f5d850c12
--- /dev/null
+++ b/src/lib/selector.c
@@ -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);
+ }
+}
diff --git a/src/planereel/main.cpp b/src/planereel/main.cpp
index aec1628c3..aa379a980 100644
--- a/src/planereel/main.cpp
+++ b/src/planereel/main.cpp
@@ -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();
}
diff --git a/src/poc/selector.c b/src/poc/selector.c
new file mode 100644
index 000000000..a24f82242
--- /dev/null
+++ b/src/poc/selector.c
@@ -0,0 +1,59 @@
+#include
+#include
+#include
+#include
+#include
+
+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;
+}
diff --git a/src/view/view.cpp b/src/view/view.cpp
index 78e8d7cc5..810908cff 100644
--- a/src/view/view.cpp
+++ b/src/view/view.cpp
@@ -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(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 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;
diff --git a/tests/cell.cpp b/tests/cell.cpp
index 4ee236dac..353a7a805 100644
--- a/tests/cell.cpp
+++ b/tests/cell.cpp
@@ -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);
diff --git a/tests/selector.cpp b/tests/selector.cpp
new file mode 100644
index 000000000..ed3c69055
--- /dev/null
+++ b/tests/selector.cpp
@@ -0,0 +1,72 @@
+#include "main.h"
+#include
+#include
+
+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_));
+}