From da0283ac25bc0debe1cd962819a3041e3aa6ebda Mon Sep 17 00:00:00 2001 From: Nick Black Date: Thu, 30 Jan 2020 08:55:01 -0500 Subject: [PATCH] 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. --- .drone.yml | 2 +- CMakeLists.txt | 18 +- README.md | 49 +++-- doc/man/index.html | 3 +- doc/man/man1/notcurses-demo.1.md | 2 +- doc/man/man3/notcurses.3.md | 11 +- doc/man/man3/notcurses_ncplane.3.md | 6 +- doc/man/man3/notcurses_selector.3.md | 24 +++ include/ncpp/Plane.hh | 9 +- include/ncpp/Visual.hh | 5 + include/notcurses.h | 103 +++++++++-- python/setup.cfg.in | 2 - python/src/notcurses/build_notcurses.py | 17 +- python/src/notcurses/notcurses.py | 6 +- src/demo/boxdemo.c | 2 +- src/demo/chunli.c | 4 +- src/demo/demo.c | 4 +- src/demo/eagle.c | 2 +- src/demo/hud.c | 4 +- src/demo/intro.c | 42 ++++- src/demo/luigi.c | 17 +- src/demo/outro.c | 7 +- src/demo/sliding.c | 7 +- src/demo/trans.c | 18 +- src/demo/unicodeblocks.c | 12 +- src/demo/view.c | 11 +- src/demo/witherworm.c | 28 ++- src/demo/xray.c | 8 +- src/lib/egcpool.h | 2 + src/lib/fade.c | 12 +- src/lib/input.c | 2 +- src/lib/internal.h | 43 +++-- src/lib/libav.c | 83 +++++++-- src/lib/notcurses.c | 18 +- src/lib/panelreel.c | 5 +- src/lib/selector.c | 226 ++++++++++++++++++++++++ src/planereel/main.cpp | 2 +- src/poc/selector.c | 59 +++++++ src/view/view.cpp | 32 +++- tests/cell.cpp | 6 +- tests/selector.cpp | 72 ++++++++ 41 files changed, 820 insertions(+), 165 deletions(-) create mode 100644 doc/man/man3/notcurses_selector.3.md create mode 100644 src/lib/selector.c create mode 100644 src/poc/selector.c create mode 100644 tests/selector.cpp 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_)); +}