diff --git a/NEWS.md b/NEWS.md index afab4d52d..2e5bbc336 100644 --- a/NEWS.md +++ b/NEWS.md @@ -11,7 +11,9 @@ 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()`. + * Added `ncdirect_hline_interp()`, `ncdirect_vline_interp()`, + `ncdirect_rounded_box()`, `ncdirect_double_box()`, and the ridiculously + flexible `ncdirect_box()`. * 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 4d79ac342..ed1deb8de 100644 --- a/USAGE.md +++ b/USAGE.md @@ -357,6 +357,24 @@ int ncdirect_hline_interp(struct ncdirect* n, const char* egc, int len, int ncdirect_vline_interp(struct ncdirect* n, const char* egc, int len, uint64_t h1, uint64_t h2); +// Draw a box with its upper-left corner at the current cursor position, having +// dimensions |ylen|x|xlen|. See ncplane_box() for more information. The +// minimum box size is 2x2, and it cannot be drawn off-screen. |wchars| is an +// array of 6 wide characters: UL, UR, LL, LR, HL, VL. +int ncdirect_box(struct ncdirect* n, uint64_t ul, uint64_t ur, + uint64_t ll, uint64_t lr, const wchar_t* wchars, + int ylen, int xlen, unsigned ctlword); + +// ncdirect_box() with the rounded box-drawing characters +int ncdirect_rounded_box(struct ncdirect* n, uint64_t ul, uint64_t ur, + uint64_t ll, uint64_t lr, + int ylen, int xlen, unsigned ctlword); + +// ncdirect_box() with the double box-drawing characters +int ncdirect_double_box(struct ncdirect* n, uint64_t ul, uint64_t ur, + uint64_t ll, uint64_t lr, + int ylen, int xlen, unsigned ctlword); + // 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 7668f783c..c4edfa907 100644 --- a/doc/man/man3/notcurses_directmode.3.md +++ b/doc/man/man3/notcurses_directmode.3.md @@ -60,6 +60,16 @@ ncdirect_init - minimal notcurses instances for styling text **bool ncdirect_canutf8(const struct ncdirect* n);** +**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);** + +**int ncdirect_box(struct ncdirect* n, uint64_t ul, uint64_t ur, uint64_t ll, uint64_t lr, const wchar_t* wchars, int ylen, int xlen, unsigned ctlword);** + +**int ncdirect_rounded_box(struct ncdirect* n, uint64_t ul, uint64_t ur, uint64_t ll, uint64_t lr, int ylen, int xlen, unsigned ctlword);** + +**int ncdirect_double_box(struct ncdirect* n, uint64_t ul, uint64_t ur, uint64_t ll, uint64_t lr, int ylen, int xlen, unsigned ctlword);** + **nc_err_e ncdirect_render_image(struct ncdirect* n, const char* filename, ncblitter_e blitter, ncscale_e scale);** # DESCRIPTION diff --git a/include/notcurses/direct.h b/include/notcurses/direct.h index ff615cf28..5acace54e 100644 --- a/include/notcurses/direct.h +++ b/include/notcurses/direct.h @@ -112,6 +112,24 @@ API int ncdirect_hline_interp(struct ncdirect* n, const char* egc, int len, API int ncdirect_vline_interp(struct ncdirect* n, const char* egc, int len, uint64_t h1, uint64_t h2); +// Draw a box with its upper-left corner at the current cursor position, having +// dimensions |ylen|x|xlen|. See ncplane_box() for more information. The +// minimum box size is 2x2, and it cannot be drawn off-screen. |wchars| is an +// array of 6 wide characters: UL, UR, LL, LR, HL, VL. +API int ncdirect_box(struct ncdirect* n, uint64_t ul, uint64_t ur, + uint64_t ll, uint64_t lr, const wchar_t* wchars, + int ylen, int xlen, unsigned ctlword); + +// ncdirect_box() with the rounded box-drawing characters +API int ncdirect_rounded_box(struct ncdirect* n, uint64_t ul, uint64_t ur, + uint64_t ll, uint64_t lr, + int ylen, int xlen, unsigned ctlword); + +// ncdirect_box() with the double box-drawing characters +API int ncdirect_double_box(struct ncdirect* n, uint64_t ul, uint64_t ur, + uint64_t ll, uint64_t lr, + int ylen, int xlen, unsigned ctlword); + // Release 'nc' and any associated resources. 0 on success, non-0 on failure. API int ncdirect_stop(struct ncdirect* nc); diff --git a/python/src/notcurses/build_notcurses.py b/python/src/notcurses/build_notcurses.py index 00b95c151..c23e82a3d 100644 --- a/python/src/notcurses/build_notcurses.py +++ b/python/src/notcurses/build_notcurses.py @@ -503,6 +503,9 @@ 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); +int ncdirect_box(struct ncdirect* n, uint64_t ul, uint64_t ur, uint64_t ll, uint64_t lr, const wchar_t* wchars, int ylen, int xlen, unsigned ctlword); +int ncdirect_rounded_box(struct ncdirect* n, uint64_t ul, uint64_t ur, uint64_t ll, uint64_t lr, int ylen, int xlen, unsigned ctlword); +int ncdirect_double_box(struct ncdirect* n, uint64_t ul, uint64_t ur, uint64_t ll, uint64_t lr, int ylen, int xlen, unsigned ctlword); bool ncdirect_canopen_images(const struct ncdirect* n); bool ncdirect_canutf8(const struct ncdirect* n); 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 ef55976e4..2fa093d61 100644 --- a/src/lib/direct.cpp +++ b/src/lib/direct.cpp @@ -9,7 +9,7 @@ #include "notcurses/direct.h" #include "internal.h" -int ncdirect_putstr(ncdirect* nc, uint64_t channels, const char* egc){ +int ncdirect_putstr(ncdirect* nc, uint64_t channels, const char* utf8){ if(channels_fg_default_p(channels)){ if(ncdirect_fg_default(nc)){ return -1; @@ -24,7 +24,7 @@ int ncdirect_putstr(ncdirect* nc, uint64_t channels, const char* egc){ }else if(ncdirect_bg(nc, channels_bg(channels))){ return -1; } - return fprintf(nc->ttyfp, "%s", egc); + return fprintf(nc->ttyfp, "%s", utf8); } int ncdirect_cursor_up(ncdirect* nc, int num){ @@ -652,6 +652,123 @@ int ncdirect_vline_interp(ncdirect* n, const char* egc, int len, return ret; } +// wchars: wchar_t[6] mapping to UL, UR, BL, BR, HL, VL. +// they cannot be complex EGCs, but only a single wchar_t, alas. +int ncdirect_box(ncdirect* n, uint64_t ul, uint64_t ur, + uint64_t ll, uint64_t lr, const wchar_t* wchars, + int ylen, int xlen, unsigned ctlword){ + if(xlen < 2 || ylen < 2){ + return -1; + } + char hl[WCHAR_MAX_UTF8BYTES + 1]; + char vl[WCHAR_MAX_UTF8BYTES + 1]; + unsigned edges; + edges = !(ctlword & NCBOXMASK_TOP) + !(ctlword & NCBOXMASK_LEFT); + if(edges >= box_corner_needs(ctlword)){ + ncdirect_fg(n, channels_fg(ul)); + ncdirect_bg(n, channels_bg(ul)); + if(fprintf(n->ttyfp, "%lc", wchars[0]) < 0){ + return -1; + } + }else{ + ncdirect_cursor_right(n, 1); + } + mbstate_t ps = {}; + size_t bytes; + if((bytes = wcrtomb(hl, wchars[4], &ps)) == (size_t)-1){ + return -1; + } + hl[bytes] = '\0'; + memset(&ps, 0, sizeof(ps)); + if((bytes = wcrtomb(vl, wchars[5], &ps)) == (size_t)-1){ + return -1; + } + vl[bytes] = '\0'; + if(!(ctlword & NCBOXMASK_TOP)){ // draw top border, if called for + if(xlen > 2){ + if(ncdirect_hline_interp(n, hl, xlen - 2, ul, ur) < 0){ + return -1; + } + } + }else{ + ncdirect_cursor_right(n, xlen - 2); + } + edges = !(ctlword & NCBOXMASK_TOP) + !(ctlword & NCBOXMASK_RIGHT); + if(edges >= box_corner_needs(ctlword)){ + ncdirect_fg(n, channels_fg(ur)); + ncdirect_bg(n, channels_bg(ur)); + if(fprintf(n->ttyfp, "%lc", wchars[1]) < 0){ + return -1; + } + ncdirect_cursor_left(n, xlen); + }else{ + ncdirect_cursor_left(n, xlen - 1); + } + ncdirect_cursor_down(n, 1); + // middle rows (vertical lines) + if(ylen > 2){ + if(!(ctlword & NCBOXMASK_LEFT)){ + if(ncdirect_vline_interp(n, vl, ylen - 2, ul, ll) < 0){ + return -1; + } + ncdirect_cursor_right(n, xlen - 2); + ncdirect_cursor_up(n, ylen - 3); + }else{ + ncdirect_cursor_right(n, xlen - 1); + } + if(!(ctlword & NCBOXMASK_RIGHT)){ + if(ncdirect_vline_interp(n, vl, ylen - 2, ur, lr) < 0){ + return -1; + } + ncdirect_cursor_left(n, xlen); + }else{ + ncdirect_cursor_left(n, xlen - 1); + } + } + ncdirect_cursor_down(n, 1); + // bottom line + edges = !(ctlword & NCBOXMASK_BOTTOM) + !(ctlword & NCBOXMASK_LEFT); + if(edges >= box_corner_needs(ctlword)){ + ncdirect_fg(n, channels_fg(ll)); + ncdirect_bg(n, channels_bg(ll)); + if(fprintf(n->ttyfp, "%lc", wchars[2]) < 0){ + return -1; + } + }else{ + ncdirect_cursor_right(n, 1); + } + if(!(ctlword & NCBOXMASK_BOTTOM)){ + if(xlen > 2){ + if(ncdirect_hline_interp(n, hl, xlen - 2, ll, lr) < 0){ + return -1; + } + } + }else{ + ncdirect_cursor_right(n, xlen - 2); + } + edges = !(ctlword & NCBOXMASK_BOTTOM) + !(ctlword & NCBOXMASK_RIGHT); + if(edges >= box_corner_needs(ctlword)){ + ncdirect_fg(n, channels_fg(lr)); + ncdirect_bg(n, channels_bg(lr)); + if(fprintf(n->ttyfp, "%lc", wchars[3]) < 0){ + return -1; + } + } + return 0; +} + +int ncdirect_rounded_box(ncdirect* n, uint64_t ul, uint64_t ur, + uint64_t ll, uint64_t lr, + int ylen, int xlen, unsigned ctlword){ + return ncdirect_box(n, ul, ur, ll, lr, L"╭╮╰╯─│", ylen, xlen, ctlword); +} + +int ncdirect_double_box(ncdirect* n, uint64_t ul, uint64_t ur, + uint64_t ll, uint64_t lr, + int ylen, int xlen, unsigned ctlword){ + return ncdirect_box(n, ul, ur, ll, lr, L"╔╗╚╝═║", ylen, xlen, ctlword); +} + // Can we load images? This requires being built against FFmpeg/OIIO. bool ncdirect_canopen_images(const ncdirect* n){ (void)n; diff --git a/src/lib/internal.h b/src/lib/internal.h index 4c449602f..f091d3f40 100644 --- a/src/lib/internal.h +++ b/src/lib/internal.h @@ -826,6 +826,12 @@ int ffmpeg_log_level(ncloglevel_e level); int term_setstyle(FILE* out, unsigned cur, unsigned targ, unsigned stylebit, const char* ton, const char* toff); +// how many edges need touch a corner for it to be printed? +static inline unsigned +box_corner_needs(unsigned ctlword){ + return (ctlword & NCBOXCORNER_MASK) >> NCBOXCORNER_SHIFT; +} + #ifdef __cplusplus } #endif diff --git a/src/lib/notcurses.c b/src/lib/notcurses.c index 1e3a8aad0..4a5bf86f5 100644 --- a/src/lib/notcurses.c +++ b/src/lib/notcurses.c @@ -1668,12 +1668,6 @@ int ncplane_vline_interp(ncplane* n, const cell* c, int len, return ret; } -// how many edges need touch a corner for it to be printed? -static inline unsigned -box_corner_needs(unsigned ctlword){ - return (ctlword & NCBOXCORNER_MASK) >> NCBOXCORNER_SHIFT; -} - int ncplane_box(ncplane* n, const cell* ul, const cell* ur, const cell* ll, const cell* lr, const cell* hl, const cell* vl, int ystop, int xstop, diff --git a/src/poc/dirlines.c b/src/poc/dirlines.c index 51c33c8a3..bab3fc0a5 100644 --- a/src/poc/dirlines.c +++ b/src/poc/dirlines.c @@ -34,6 +34,20 @@ int main(void){ } } } + printf("\n"); + uint64_t ul, ur, ll, lr; + ul = ur = ll = lr = 0; + channels_set_fg_rgb(&ul, 0xff, 0x0, 0xff); + channels_set_fg_rgb(&ur, 0x0, 0xff, 0x0); + channels_set_fg_rgb(&ll, 0x0, 0x0, 0xff); + channels_set_fg_rgb(&lr, 0xff, 0x0, 0x0); + if(ncdirect_rounded_box(n, ul, ur, ll, lr, 10, 10, 0) < 0){ + return EXIT_FAILURE; + } + ncdirect_cursor_up(n, 9); + if(ncdirect_double_box(n, ul, ur, ll, lr, 10, 20, 0) < 0){ + return EXIT_FAILURE; + } if(ncdirect_stop(n)){ return EXIT_FAILURE; }