Use column width in selector calculations #302

This commit is contained in:
nick black 2020-01-31 19:51:14 -05:00
parent 5982707f9e
commit fad612bd2f
No known key found for this signature in database
GPG Key ID: 5F43400C21CBFACC
6 changed files with 53 additions and 29 deletions

View File

@ -2136,6 +2136,8 @@ API int ncdirect_stop(struct ncdirect* nc);
struct selector_item {
char* option;
char* desc;
size_t opcolumns; // filled in by library
size_t desccolumns; // filled in by library
};
typedef struct selector_options {

View File

@ -128,13 +128,16 @@ typedef struct ncselector {
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
int longop; // columns occupied by longest option
int longdesc; // columns occupied by 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
int titlecols; // columns occupied by title
char* secondary; // can be NULL
int secondarycols; // columns occupied by secondary
char* footer; // can be NULL
int footercols; // columns occupied by footer
uint64_t opchannels; // option channels
uint64_t descchannels; // description channels
uint64_t titlechannels; // title channels

View File

@ -4,16 +4,16 @@
// ideal body width given the ncselector's items and secondary/footer
static size_t
ncselector_body_width(const ncselector* n){
size_t cols = 0;
int 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->footercols + 2 > cols){
cols = n->footercols + 2;
}
if(n->secondary && strlen(n->secondary) + 2 > cols){
cols = strlen(n->secondary) + 2;
if(n->secondarycols + 2 > cols){
cols = n->secondarycols + 2;
}
if(n->longop + n->longdesc + 5 > cols){
cols = n->longop + n->longdesc + 5;
@ -32,7 +32,7 @@ ncselector_draw(ncselector* n){
// 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;
size_t riserwidth = n->titlecols + 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, n->boxchannels, 3, riserwidth, 0);
@ -49,13 +49,13 @@ ncselector_draw(ncselector* n){
ncplane_rounded_box_sized(n->ncp, 0, n->boxchannels, dimy - yoff, bodywidth, 0);
if(n->secondary){
// FIXME move it to the left a bit *iff* there's room to do so
int xloc = ncplane_align(n->ncp, NCALIGN_RIGHT, strlen(n->secondary) + 1);
int xloc = ncplane_align(n->ncp, NCALIGN_RIGHT, n->secondarycols + 1);
n->ncp->channels = n->footchannels;
ncplane_putstr_yx(n->ncp, yoff, xloc, n->secondary);
}
if(n->footer){
// FIXME move it to the left a bit *iff* there's room to do so
int xloc = ncplane_align(n->ncp, NCALIGN_RIGHT, strlen(n->footer) + 2);
int xloc = ncplane_align(n->ncp, NCALIGN_RIGHT, n->footercols + 2);
n->ncp->channels = n->footchannels;
ncplane_putstr_yx(n->ncp, dimy - 1, xloc, n->footer);
}
@ -108,8 +108,8 @@ ncselector_dim_yx(notcurses* nc, const ncselector* n, int* ncdimy, int* ncdimx){
*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(n->titlecols + 4 > cols){
cols = n->titlecols + 4;
}
if(cols > dimx){ // insufficient width to display selector
return -1;
@ -124,8 +124,11 @@ ncselector* ncselector_create(ncplane* n, int y, int x, const selector_options*
}
ncselector* ns = malloc(sizeof(*ns));
ns->title = opts->title ? strdup(opts->title) : NULL;
ns->titlecols = opts->title ? mbswidth(opts->title) : 0;
ns->secondary = opts->secondary ? strdup(opts->secondary) : NULL;
ns->secondarycols = opts->secondary ? mbswidth(opts->secondary) : 0;
ns->footer = opts->footer ? strdup(opts->footer) : NULL;
ns->footercols = opts->footer ? mbswidth(opts->footer) : 0;
ns->selected = opts->defidx;
ns->startdisp = opts->defidx >= opts->maxdisplay ? opts->defidx - opts->maxdisplay + 1 : 0;
ns->longop = 0;
@ -148,11 +151,11 @@ ncselector* ncselector_create(ncplane* n, int y, int x, const selector_options*
}
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(mbswidth(src->option) > ns->longop){
ns->longop = mbswidth(src->option);
}
if(strlen(src->desc) > ns->longdesc){
ns->longdesc = strlen(src->desc);
if(mbswidth(src->desc) > ns->longdesc){
ns->longdesc = mbswidth(src->desc);
}
ns->items[ns->itemcount].option = strdup(src->option);
ns->items[ns->itemcount].desc = strdup(src->desc);

View File

@ -12,7 +12,7 @@ static struct selector_item items[] = {
{ "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", },
{ "8 8 8", "the chinese 平仮名平平仮名仮名love me, i'm told", },
{ "nine", "nine, nine, nine 'cause you left me", },
{ "ten", "stunning and brave", },
};

16
src/poc/wcwidth.c Normal file
View File

@ -0,0 +1,16 @@
#include <stdio.h>
#include <stdlib.h>
#include <locale.h>
#include <wchar.h>
int main(void){
if(!setlocale(LC_ALL, "")){
return EXIT_FAILURE;
}
for(int i = 0 ; i < 128 ; ++i){
wchar_t w = i;
printf("width('%02x'): %d\t", i, wcwidth(w));
}
printf("\n");
return EXIT_SUCCESS;
}

View File

@ -79,9 +79,9 @@ TEST_CASE("SelectorTest") {
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"), },
{ strdup("op1"), strdup("this is option 1"), 0, 0, },
{ strdup("2ndop"), strdup("this is option #2"), 0, 0, },
{ strdup("tres"), strdup("option the third"), 0, 0, },
};
struct selector_options opts{};
opts.items = items;
@ -116,9 +116,9 @@ TEST_CASE("SelectorTest") {
SUBCASE("SelectorMovement") {
selector_item items[] = {
{ strdup("op1"), strdup("this is option 1"), },
{ strdup("2ndop"), strdup("this is option #2"), },
{ strdup("tres"), strdup("option the third"), },
{ strdup("op1"), strdup("this is option 1"), 0, 0, },
{ strdup("2ndop"), strdup("this is option #2"), 0, 0, },
{ strdup("tres"), strdup("option the third"), 0, 0, },
};
struct selector_options opts{};
opts.items = items;
@ -158,9 +158,9 @@ TEST_CASE("SelectorTest") {
// 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"), },
{ strdup("op1"), strdup("this is option 1"), 0, 0, },
{ strdup("2ndop"), strdup("this is option #2"), 0, 0, },
{ strdup("tres"), strdup("option the third"), 0, 0, },
};
struct selector_options opts{};
opts.maxdisplay = 1;
@ -206,9 +206,9 @@ TEST_CASE("SelectorTest") {
// 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"), },
{ strdup("op1"), strdup("this is option 1"), 0, 0, },
{ strdup("2ndop"), strdup("this is option #2"), 0, 0, },
{ strdup("tres"), strdup("option the third"), 0, 0, },
};
struct selector_options opts{};
opts.maxdisplay = 2;