From c78324418547405e8da61bf77341a2db4268fc35 Mon Sep 17 00:00:00 2001 From: nick black Date: Fri, 10 Jul 2020 16:55:49 -0400 Subject: [PATCH] ncdirect_hline_interp(), ncdirect_vline_interp(), dirlines PoC #753 --- NEWS.md | 1 + USAGE.md | 37 +++++++- doc/man/man3/notcurses_directmode.3.md | 2 +- include/notcurses/direct.h | 18 +++- python/src/notcurses/build_notcurses.py | 4 +- src/lib/direct.cpp | 108 +++++++++++++++++++++++- src/poc/dirlines.c | 41 +++++++++ src/poc/vizdirect.c | 2 +- 8 files changed, 198 insertions(+), 15 deletions(-) create mode 100644 src/poc/dirlines.c diff --git a/NEWS.md b/NEWS.md index 311215e7c..afab4d52d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -11,6 +11,7 @@ rearrangements of Notcurses. been purged, as have `min_` and `max_supported_rows` and `_cols`. There is no longer any need to provide a pipe/eventfd. `ncreel_touch()`, `ncreel_del_focused()`, and `ncreel_move()` have been removed. + * Added `ncdirect_hline_interp()` and `ncdirect_vline_interp()`. * 1.6.0 (2020-07-04) * Behavior has changed regarding use of the provided `FILE*` (which, when diff --git a/USAGE.md b/USAGE.md index 9c33689f2..1ac74665f 100644 --- a/USAGE.md +++ b/USAGE.md @@ -47,6 +47,10 @@ supplied a struct of type `notcurses_options`: // Get a human-readable string describing the running Notcurses version. const char* notcurses_version(void); +// Cannot be inline, as we want to get the versions of the actual notcurses +// library we loaded, not what we compile against. +void notcurses_version_components(int* major, int* minor, int* patch, int* tweak); + struct cell; // a coordinate on an ncplane: an EGC plus styling struct ncplane; // a drawable Notcurses surface, composed of cells struct notcurses; // Notcurses state for a given terminal, composed of ncplanes @@ -263,10 +267,10 @@ bool notcurses_cansixel(const struct notcurses* nc); ## Direct mode "Direct mode" makes a limited subset of notcurses is available for manipulating -typical scrolling or file-backed output. These functions output directly and -immediately to the provided `FILE*`, and `notcurses_render()` is neither -supported nor necessary for such an instance. Use `ncdirect_init()` to create a -direct mode context: +typical scrolling or file-backed output. Its functions are exported via +``, and output directly and immediately to the provided +`FILE*`. `notcurses_render()` is neither supported nor necessary for such an +instance. Use `ncdirect_init()` to create a direct mode context: ```c struct ncdirect; // minimal state for a terminal @@ -328,6 +332,31 @@ int ncdirect_cursor_left(struct ncdirect* nc, int num); int ncdirect_cursor_right(struct ncdirect* nc, int num); int ncdirect_cursor_down(struct ncdirect* nc, int num); +// Get the cursor position, when supported. This requires writing to the +// terminal, and then reading from it. If the terminal doesn't reply, or +// doesn't reply in a way we understand, the results might be deleterious. +int ncdirect_cursor_yx(struct ncdirect* n, int* y, int* x); + +// Push or pop the cursor location to the terminal's stack. The depth of this +// stack, and indeed its existence, is terminal-dependent. +int ncdirect_cursor_push(struct ncdirect* n); +int ncdirect_cursor_pop(struct ncdirect* n); + +// Formatted printing (plus alignment relative to the terminal). +int ncdirect_printf_aligned(struct ncdirect* n, int y, ncalign_e align, + const char* fmt, ...) + __attribute__ ((format (printf, 4, 5))); + +// Draw horizontal/vertical lines using the specified channels, interpolating +// between them as we go. The EGC may not use more than one column. For a +// horizontal line, |len| cannot exceed the screen width minus the cursor's +// offset. For a vertical line, it may be as long as you'd like; the screen +// will scroll as necessary. All lines start at the current cursor position. +int ncdirect_hline_interp(struct ncdirect* n, const char* egc, int len, + uint64_t h1, uint64_t h2); +int ncdirect_vline_interp(struct ncdirect* n, const char* egc, int len, + uint64_t h1, uint64_t h2); + // Display an image using the specified blitter and scaling. The image may // be arbitrarily many rows -- the output will scroll -- but will only occupy // the column of the cursor, and those to the right. diff --git a/doc/man/man3/notcurses_directmode.3.md b/doc/man/man3/notcurses_directmode.3.md index 6031310c8..f8c82bee3 100644 --- a/doc/man/man3/notcurses_directmode.3.md +++ b/doc/man/man3/notcurses_directmode.3.md @@ -54,7 +54,7 @@ ncdirect_init - minimal notcurses instances for styling text **int ncdirect_cursor_down(struct ncdirect* nc, int num);** -**int ncdirect_putc(struct ncdirect* nc, uint64_t channels, const char* egc);** +**int ncdirect_putstr(struct ncdirect* nc, uint64_t channels, const char* utf8);** **nc_err_e ncdirect_render_image(struct ncdirect* n, const char* filename, ncblitter_e blitter, ncscale_e scale);** diff --git a/include/notcurses/direct.h b/include/notcurses/direct.h index dca48b49b..334d1a71c 100644 --- a/include/notcurses/direct.h +++ b/include/notcurses/direct.h @@ -31,8 +31,8 @@ API int ncdirect_bg_palindex(struct ncdirect* nc, int pidx); // more colors than they actually support, downsampling internally. API int ncdirect_palette_size(const struct ncdirect* nc); -// Output the EGC |egc| according to the channels |channels|. -API int ncdirect_putc(struct ncdirect* nc, uint64_t channels, const char* egc); +// Output the string |utf8| according to the channels |channels|. +API int ncdirect_putstr(struct ncdirect* nc, uint64_t channels, const char* utf8); static inline int ncdirect_bg_rgb(struct ncdirect* nc, unsigned r, unsigned g, unsigned b){ @@ -87,8 +87,8 @@ API int ncdirect_printf_aligned(struct ncdirect* n, int y, ncalign_e align, __attribute__ ((format (printf, 4, 5))); // Display an image using the specified blitter and scaling. The image may -// // be arbitrarily many rows -- the output will scroll -- but will only occupy -// // the column of the cursor, and those to the right. +// be arbitrarily many rows -- the output will scroll -- but will only occupy +// the column of the cursor, and those to the right. API nc_err_e ncdirect_render_image(struct ncdirect* n, const char* filename, ncalign_e align, ncblitter_e blitter, ncscale_e scale); @@ -99,6 +99,16 @@ API int ncdirect_clear(struct ncdirect* nc); // Release 'nc' and any associated resources. 0 on success, non-0 on failure. API int ncdirect_stop(struct ncdirect* nc); +// Draw horizontal/vertical lines using the specified channels, interpolating +// between them as we go. The EGC may not use more than one column. For a +// horizontal line, |len| cannot exceed the screen width minus the cursor's +// offset. For a vertical line, it may be as long as you'd like; the screen +// will scroll as necessary. All lines start at the current cursor position. +API int ncdirect_hline_interp(struct ncdirect* n, const char* egc, int len, + uint64_t h1, uint64_t h2); +API int ncdirect_vline_interp(struct ncdirect* n, const char* egc, int len, + uint64_t h1, uint64_t h2); + #undef API #ifdef __cplusplus diff --git a/python/src/notcurses/build_notcurses.py b/python/src/notcurses/build_notcurses.py index 18143c514..923d4b2a4 100644 --- a/python/src/notcurses/build_notcurses.py +++ b/python/src/notcurses/build_notcurses.py @@ -484,7 +484,7 @@ struct ncdirect* ncdirect_init(const char* termtype, FILE* fp); int ncdirect_bg_rgb(struct ncdirect* n, unsigned r, unsigned g, unsigned b); int ncdirect_fg_rgb(struct ncdirect* n, unsigned r, unsigned g, unsigned b); int ncdirect_palette_size(const struct ncdirect* nc); -int ncdirect_putc(struct ncdirect* nc, uint64_t channels, const char* egc); +int ncdirect_putstr(struct ncdirect* nc, uint64_t channels, const char* utf8); int ncdirect_fg(struct ncdirect* n, unsigned rgb); int ncdirect_bg(struct ncdirect* n, unsigned rgb); int ncdirect_styles_set(struct ncdirect* n, unsigned stylebits); @@ -501,6 +501,8 @@ int ncdirect_cursor_up(struct ncdirect* nc, int num); int ncdirect_cursor_left(struct ncdirect* nc, int num); int ncdirect_cursor_right(struct ncdirect* nc, int num); int ncdirect_cursor_down(struct ncdirect* nc, int num); +int ncdirect_hline_interp(struct ncdirect* n, const char* egc, int len, uint64_t h1, uint64_t h2); +int ncdirect_vline_interp(struct ncdirect* n, const char* egc, int len, uint64_t h1, uint64_t h2); nc_err_e ncdirect_render_image(struct ncdirect* n, const char* filename, ncalign_e align, ncblitter_e blitter, ncscale_e scale); """) diff --git a/src/lib/direct.cpp b/src/lib/direct.cpp index a4f9d5e4a..acb1b573e 100644 --- a/src/lib/direct.cpp +++ b/src/lib/direct.cpp @@ -9,7 +9,7 @@ #include "notcurses/direct.h" #include "internal.h" -int ncdirect_putc(ncdirect* nc, uint64_t channels, const char* egc){ +int ncdirect_putstr(ncdirect* nc, uint64_t channels, const char* egc){ if(channels_fg_default_p(channels)){ if(ncdirect_fg_default(nc)){ return -1; @@ -474,7 +474,7 @@ int ncdirect_stop(ncdirect* nc){ static inline int ncdirect_style_emit(ncdirect* n, const char* sgr, unsigned stylebits, FILE* out){ - if(sgr == NULL){ + if(sgr == nullptr){ return -1; } int r = term_emit("sgr", tiparm(sgr, stylebits & NCSTYLE_STANDOUT, @@ -536,7 +536,7 @@ int ncdirect_fg_default(ncdirect* nc){ if(nc->bgdefault){ return 0; } - return ncdirect_bg(nc, nc->fgrgb); + return ncdirect_bg(nc, nc->bgrgb); } return -1; } @@ -547,7 +547,107 @@ int ncdirect_bg_default(ncdirect* nc){ if(nc->fgdefault){ return 0; } - return ncdirect_fg(nc, nc->bgrgb); + return ncdirect_fg(nc, nc->fgrgb); } return -1; } + +int ncdirect_hline_interp(ncdirect* n, const char* egc, int len, + uint64_t c1, uint64_t c2){ + unsigned ur, ug, ub; + int r1, g1, b1, r2, g2, b2; + int br1, bg1, bb1, br2, bg2, bb2; + channels_fg_rgb(c1, &ur, &ug, &ub); + r1 = ur; g1 = ug; b1 = ub; + channels_fg_rgb(c2, &ur, &ug, &ub); + r2 = ur; g2 = ug; b2 = ub; + channels_bg_rgb(c1, &ur, &ug, &ub); + br1 = ur; bg1 = ug; bb1 = ub; + channels_bg_rgb(c2, &ur, &ug, &ub); + br2 = ur; bg2 = ug; bb2 = ub; + int deltr = r2 - r1; + int deltg = g2 - g1; + int deltb = b2 - b1; + int deltbr = br2 - br1; + int deltbg = bg2 - bg1; + int deltbb = bb2 - bb1; + int ret; + bool fgdef = false, bgdef = false; + if(channels_fg_default_p(c1) && channels_fg_default_p(c2)){ + fgdef = true; + } + if(channels_bg_default_p(c1) && channels_bg_default_p(c2)){ + bgdef = true; + } + for(ret = 0 ; ret < len ; ++ret){ + int r = (deltr * ret) / len + r1; + int g = (deltg * ret) / len + g1; + int b = (deltb * ret) / len + b1; + int br = (deltbr * ret) / len + br1; + int bg = (deltbg * ret) / len + bg1; + int bb = (deltbb * ret) / len + bb1; + if(!fgdef){ + ncdirect_fg_rgb(n, r, g, b); + } + if(!bgdef){ + ncdirect_bg_rgb(n, br, bg, bb); + } + if(fprintf(n->ttyfp, "%s", egc) < 0){ + break; + } + } + return ret; +} + +int ncdirect_vline_interp(ncdirect* n, const char* egc, int len, + uint64_t c1, uint64_t c2){ + unsigned ur, ug, ub; + int r1, g1, b1, r2, g2, b2; + int br1, bg1, bb1, br2, bg2, bb2; + channels_fg_rgb(c1, &ur, &ug, &ub); + r1 = ur; g1 = ug; b1 = ub; + channels_fg_rgb(c2, &ur, &ug, &ub); + r2 = ur; g2 = ug; b2 = ub; + channels_bg_rgb(c1, &ur, &ug, &ub); + br1 = ur; bg1 = ug; bb1 = ub; + channels_bg_rgb(c2, &ur, &ug, &ub); + br2 = ur; bg2 = ug; bb2 = ub; + int deltr = (r2 - r1) / (len + 1); + int deltg = (g2 - g1) / (len + 1); + int deltb = (b2 - b1) / (len + 1); + int deltbr = (br2 - br1) / (len + 1); + int deltbg = (bg2 - bg1) / (len + 1); + int deltbb = (bb2 - bb1) / (len + 1); + int ret; + bool fgdef = false, bgdef = false; + if(channels_fg_default_p(c1) && channels_fg_default_p(c2)){ + fgdef = true; + } + if(channels_bg_default_p(c1) && channels_bg_default_p(c2)){ + bgdef = true; + } + for(ret = 0 ; ret < len ; ++ret){ + r1 += deltr; + g1 += deltg; + b1 += deltb; + br1 += deltbr; + bg1 += deltbg; + bb1 += deltbb; + uint64_t channels = 0; + if(!fgdef){ + channels_set_fg_rgb(&channels, r1, g1, b1); + } + if(!bgdef){ + channels_set_bg_rgb(&channels, br1, bg1, bb1); + } + if(ncdirect_putstr(n, channels, egc) <= 0){ + break; + } + if(len - ret > 1){ + if(ncdirect_cursor_down(n, 1) || ncdirect_cursor_left(n, 1)){ + break; + } + } + } + return ret; +} diff --git a/src/poc/dirlines.c b/src/poc/dirlines.c new file mode 100644 index 000000000..51c33c8a3 --- /dev/null +++ b/src/poc/dirlines.c @@ -0,0 +1,41 @@ +#include +#include +#include + +int main(void){ + if(!setlocale(LC_ALL, "")){ + return EXIT_FAILURE; + } + struct ncdirect* n = ncdirect_init(NULL, stdout); + putchar('\n'); + for(int i = 0 ; i < 15 ; ++i){ + uint64_t c1 = 0, c2 = 0; + channels_set_fg_rgb(&c1, 0x0, 0x10 * i, 0xff); + channels_set_fg_rgb(&c2, 0x10 * i, 0x0, 0x0); + if(ncdirect_hline_interp(n, "-", i, c1, c2) < i){ + return EXIT_FAILURE; + } + ncdirect_fg_default(n); + ncdirect_bg_default(n); + putchar('\n'); + } + for(int i = 0 ; i < 15 ; ++i){ + uint64_t c1 = 0, c2 = 0; + channels_set_fg_rgb(&c1, 0x0, 0x10 * i, 0xff); + channels_set_fg_rgb(&c2, 0x10 * i, 0x0, 0x0); + if(ncdirect_vline_interp(n, "|", i, c1, c2) < i){ + return EXIT_FAILURE; + } + ncdirect_fg_default(n); + ncdirect_bg_default(n); + if(i < 14){ + if(ncdirect_cursor_up(n, i)){ + return EXIT_FAILURE; + } + } + } + if(ncdirect_stop(n)){ + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} diff --git a/src/poc/vizdirect.c b/src/poc/vizdirect.c index ab216bf0c..18d92b9a3 100644 --- a/src/poc/vizdirect.c +++ b/src/poc/vizdirect.c @@ -1,6 +1,6 @@ #include #include -#include +#include // can we leave what was already on the screen there? (narrator: it seems not) int main(void){