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_)); +}