From 3991b3e53a13f5722f3d5b73acfcfe7027980014 Mon Sep 17 00:00:00 2001 From: nick black Date: Thu, 30 Jan 2020 19:37:21 -0500 Subject: [PATCH] selector: implement maxdisplay. unit tests! --- include/notcurses.h | 2 ++ src/lib/internal.h | 1 + src/lib/selector.c | 12 ++++++- tests/selector.cpp | 84 +++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 96 insertions(+), 3 deletions(-) diff --git a/include/notcurses.h b/include/notcurses.h index 3983ea7c9..c91331f06 100644 --- a/include/notcurses.h +++ b/include/notcurses.h @@ -2141,6 +2141,8 @@ API int ncselector_delitem(struct ncselector* n, const char* item); // Return a copy of the currently-selected option. NULL if there are no items. API char* ncselector_selected(const struct ncselector* n); +API struct ncplane* ncselector_plane(struct ncselector* n); + // 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). diff --git a/src/lib/internal.h b/src/lib/internal.h index 26a3b4785..5fab38bf4 100644 --- a/src/lib/internal.h +++ b/src/lib/internal.h @@ -127,6 +127,7 @@ typedef struct ncselector { ncplane* ncp; // backing ncplane unsigned selected; // index of selection unsigned startdisp; // index of first option displayed + unsigned maxdisplay; // max number of items to display, 0 -> no limit 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 diff --git a/src/lib/selector.c b/src/lib/selector.c index ee4b6fc8a..984f7503c 100644 --- a/src/lib/selector.c +++ b/src/lib/selector.c @@ -48,7 +48,11 @@ ncselector_draw(ncselector* n){ ncplane_rounded_box_sized(n->ncp, 0, channels, dimy - yoff, bodywidth, 0); unsigned printidx = n->startdisp; int bodyoffset = dimx - bodywidth + 2; + unsigned printed = 0; for(yoff += 2 ; yoff < dimy - 2 ; ++yoff){ + if(n->maxdisplay && printed == n->maxdisplay){ + break; + } if(printidx == n->selected){ ncplane_styles_on(n->ncp, CELL_STYLE_REVERSE); } @@ -59,6 +63,7 @@ ncselector_draw(ncselector* n){ ncplane_styles_off(n->ncp, CELL_STYLE_REVERSE); } ++printidx; + ++printed; } return notcurses_render(n->ncp->nc); } @@ -79,7 +84,7 @@ ncselector_dim_yx(notcurses* nc, const ncselector* n, int* ncdimy, int* ncdimx){ if(rows > dimy){ // insufficient height to display selector return -1; } - rows += n->itemcount - 1; // rows necessary to display all options + rows += (!n->maxdisplay || n->maxdisplay > n->itemcount ? n->itemcount : n->maxdisplay) - 1; // rows necessary to display all options if(rows > dimy){ // claw excess back rows = dimy; } @@ -104,6 +109,7 @@ ncselector* ncselector_create(ncplane* n, int y, int x, const selector_options* ns->selected = 0; ns->startdisp = 0; ns->longop = 0; + ns->maxdisplay = opts->maxdisplay; ns->longdesc = 0; if(opts->itemcount){ if(!(ns->items = malloc(sizeof(*ns->items) * opts->itemcount))){ @@ -185,6 +191,10 @@ int ncselector_delitem(ncselector* n, const char* item){ return -1; // wasn't found } +ncplane* ncselector_plane(ncselector* n){ + return n->ncp; +} + char* ncselector_selected(const ncselector* n){ if(n->itemcount == 0){ return NULL; diff --git a/tests/selector.cpp b/tests/selector.cpp index b06833b1c..931654848 100644 --- a/tests/selector.cpp +++ b/tests/selector.cpp @@ -23,6 +23,12 @@ TEST_CASE("SelectorTest") { REQUIRE(nullptr != ncs); CHECK(0 == notcurses_render(nc_)); CHECK(nullptr == ncselector_selected(ncs)); + struct ncplane* ncsp = ncselector_plane(ncs); + REQUIRE(nullptr != ncsp); + int dimy, dimx; + ncplane_dim_yx(ncsp, &dimy, &dimx); + CHECK(4 == dimy); + CHECK(5 == dimx); ncselector_destroy(ncs, nullptr); } @@ -32,6 +38,12 @@ TEST_CASE("SelectorTest") { struct ncselector* ncs = ncselector_create(notcurses_stdplane(nc_), 0, 0, &opts); REQUIRE(nullptr != ncs); CHECK(0 == notcurses_render(nc_)); + struct ncplane* ncsp = ncselector_plane(ncs); + REQUIRE(nullptr != ncsp); + int dimy, dimx; + ncplane_dim_yx(ncsp, &dimy, &dimx); + CHECK(6 == dimy); + CHECK(strlen(opts.title) + 4 == dimx); ncselector_destroy(ncs, nullptr); } @@ -41,15 +53,27 @@ TEST_CASE("SelectorTest") { struct ncselector* ncs = ncselector_create(notcurses_stdplane(nc_), 0, 0, &opts); REQUIRE(nullptr != ncs); CHECK(0 == notcurses_render(nc_)); + struct ncplane* ncsp = ncselector_plane(ncs); + REQUIRE(nullptr != ncsp); + int dimy, dimx; + ncplane_dim_yx(ncsp, &dimy, &dimx); + CHECK(4 == dimy); + CHECK(strlen(opts.secondary) + 2 == dimx); ncselector_destroy(ncs, nullptr); } SUBCASE("FooterSelector") { struct selector_options opts{}; - opts.secondary = strdup("i am a lone footer, little old footer"); + opts.footer = 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_)); + struct ncplane* ncsp = ncselector_plane(ncs); + REQUIRE(nullptr != ncsp); + int dimy, dimx; + ncplane_dim_yx(ncsp, &dimy, &dimx); + CHECK(4 == dimy); + CHECK(strlen(opts.footer) + 2 == dimx); ncselector_destroy(ncs, nullptr); } @@ -65,6 +89,12 @@ TEST_CASE("SelectorTest") { struct ncselector* ncs = ncselector_create(notcurses_stdplane(nc_), 0, 0, &opts); REQUIRE(nullptr != ncs); CHECK(0 == notcurses_render(nc_)); + struct ncplane* ncsp = ncselector_plane(ncs); + REQUIRE(nullptr != ncsp); + int dimy, dimx; + ncplane_dim_yx(ncsp, &dimy, &dimx); + CHECK(7 == dimy); + CHECK(15 < dimx); ncselector_destroy(ncs, nullptr); } @@ -95,29 +125,79 @@ TEST_CASE("SelectorTest") { 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_)); auto sel = ncselector_selected(ncs); REQUIRE(nullptr != sel); CHECK(0 == strcmp(sel, items[0].option)); free(sel); + CHECK(0 == notcurses_render(nc_)); ncselector_nextitem(ncs, &sel); REQUIRE(nullptr != sel); CHECK(0 == strcmp(sel, items[1].option)); free(sel); + CHECK(0 == notcurses_render(nc_)); ncselector_previtem(ncs, &sel); REQUIRE(nullptr != sel); CHECK(0 == strcmp(sel, items[0].option)); free(sel); + CHECK(0 == notcurses_render(nc_)); // wrap around from the top to bottom... ncselector_previtem(ncs, &sel); REQUIRE(nullptr != sel); CHECK(0 == strcmp(sel, items[2].option)); free(sel); + CHECK(0 == notcurses_render(nc_)); // ...and back to the top ncselector_nextitem(ncs, &sel); REQUIRE(nullptr != sel); CHECK(0 == strcmp(sel, items[0].option)); free(sel); + CHECK(0 == notcurses_render(nc_)); + ncselector_destroy(ncs, nullptr); + } + + // Provide three items, limited to 1 shown at a time + SUBCASE("ScrollingSelectorOne") { + 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.maxdisplay = 1; + 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_)); + auto sel = ncselector_selected(ncs); + REQUIRE(nullptr != sel); + struct ncplane* ncsp = ncselector_plane(ncs); + REQUIRE(nullptr != ncsp); + int dimy, dimx; + ncplane_dim_yx(ncsp, &dimy, &dimx); + CHECK(5 == dimy); + ncselector_destroy(ncs, nullptr); + } + + // Provide three items, limited to 2 shown at a time + SUBCASE("ScrollingSelectorTwo") { + 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.maxdisplay = 2; + 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_)); + struct ncplane* ncsp = ncselector_plane(ncs); + REQUIRE(nullptr != ncsp); + int dimy, dimx; + ncplane_dim_yx(ncsp, &dimy, &dimx); + CHECK(6 == dimy); ncselector_destroy(ncs, nullptr); }