ncmenu: print item shortcut aligned right #179

This commit is contained in:
nick black 2020-02-03 23:55:19 -05:00
parent 49013433ea
commit 039a390877
No known key found for this signature in database
GPG Key ID: 5F43400C21CBFACC
4 changed files with 79 additions and 10 deletions

View File

@ -2213,7 +2213,6 @@ API void ncselector_destroy(struct ncselector* n, char** item);
struct ncmenu_item {
char* desc; // utf-8 menu item, NULL for horizontal separator
ncinput shortcut; // shortcut, all should be distinct
int shortcut_offset; // used only by library
};
struct ncmenu_section {

View File

@ -124,10 +124,19 @@ typedef struct renderstate {
bool defaultelidable;
} renderstate;
// ncmenu_item and ncmenu_section have internal and (minimal) external forms
typedef struct ncmenu_int_item {
char* desc; // utf-8 menu item, NULL for horizontal separator
ncinput shortcut; // shortcut, all should be distinct
int shortcut_offset; // column offset with desc of shortcut EGC
char* shortdesc; // description of shortcut, can be NULL
int shortdesccols; // columns occupied by shortcut description
} ncmenu_int_item;
typedef struct ncmenu_int_section {
char* name; // utf-8 c string
int itemcount;
struct ncmenu_item* items;
ncmenu_int_item* items; // items, NULL iff itemcount == 0
ncinput shortcut; // shortcut, will be underlined if present in name
int xoff; // column offset from beginning of menu bar
int bodycols; // column width of longest item
@ -137,13 +146,13 @@ typedef struct ncmenu_int_section {
typedef struct ncmenu {
ncplane* ncp;
ncmenu_int_section* sections;
bool bottom; // are we on the bottom (vs top)?
int sectioncount; // must be positive
ncmenu_int_section* sections; // NULL iff sectioncount == 0
int unrolledsection; // currently unrolled section, -1 if none
int headerwidth; // minimum space necessary to display all sections
uint64_t headerchannels; // styling for header
uint64_t sectionchannels; // styling for sections
bool bottom; // are we on the bottom (vs top)?
} ncmenu;
typedef struct ncselector {

View File

@ -17,6 +17,50 @@ free_menu_sections(ncmenu* ncm){
free(ncm->sections);
}
static int
dup_menu_item(ncmenu_int_item* dst, const struct ncmenu_item* src){
#define ALTMOD "Alt+"
#define CTLMOD "Ctrl+"
if((dst->desc = strdup(src->desc)) == NULL){
return -1;
}
if(!src->shortcut.id){
dst->shortdesccols = 0;
dst->shortdesc = NULL;
return 0;
}
size_t bytes = 1; // NUL terminator
if(src->shortcut.alt){
bytes += strlen(ALTMOD);
}
if(src->shortcut.ctrl){
bytes += strlen(CTLMOD);
}
mbstate_t ps;
memset(&ps, 0, sizeof(ps));
size_t shortsize = wcrtomb(NULL, src->shortcut.id, &ps);
if(shortsize == (size_t)-1){
free(dst->desc);
return -1;
}
bytes += shortsize;
char* sdup = malloc(bytes);
int n = snprintf(sdup, bytes, "%s%s", src->shortcut.alt ? ALTMOD : "",
src->shortcut.ctrl ? CTLMOD : "");
if(n < 0 || (size_t)n >= bytes){
free(sdup);
free(dst->desc);
return -1;
}
memset(&ps, 0, sizeof(ps));
wcrtomb(sdup + n, src->shortcut.id, &ps);
dst->shortdesc = sdup;
dst->shortdesccols = mbswidth(dst->shortdesc);
return 0;
#undef CTLMOD
#undef ALTMOD
}
static int
dup_menu_section(ncmenu_int_section* dst, const struct ncmenu_section* src){
// we must reject any empty section
@ -35,7 +79,7 @@ dup_menu_section(ncmenu_int_section* dst, const struct ncmenu_section* src){
}
for(int i = 0 ; i < src->itemcount ; ++i){
if(src->items[i].desc){
if((dst->items[i].desc = strdup(src->items[i].desc)) == NULL){
if(dup_menu_item(&dst->items[i], &src->items[i])){
while(i--){
free(&dst->items[i].desc);
}
@ -43,7 +87,10 @@ dup_menu_section(ncmenu_int_section* dst, const struct ncmenu_section* src){
return -1;
}
gotitem = true;
const int cols = mbswidth(dst->items[i].desc);
int cols = mbswidth(dst->items[i].desc);
if(dst->items[i].shortdesc){
cols += 2 + dst->items[i].shortdesccols; // two spaces minimum
}
if(cols > dst->bodycols){
dst->bodycols = cols;
}
@ -55,6 +102,7 @@ dup_menu_section(ncmenu_int_section* dst, const struct ncmenu_section* src){
}
}else{
dst->items[i].desc = NULL;
dst->items[i].shortdesc = NULL;
}
}
if(!gotitem){
@ -245,11 +293,24 @@ int ncmenu_unroll(ncmenu* n, int sectionidx){
if(cols < 0){
return -1;
}
for(int j = cols + 1 ; j < width - 1 ; ++j){
// we need pad out the remaining columns of this line with spaces. if
// there's a shortcut description, we align it to the right, printing
// spaces only through the start of the aligned description.
int thiswidth = width;
if(sec->items[i].shortdesc){
thiswidth -= sec->items[i].shortdesccols;
}
// print any necessary padding spaces
for(int j = cols + 1 ; j < thiswidth - 1 ; ++j){
if(ncplane_putsimple(n->ncp, ' ') < 0){
return -1;
}
}
if(sec->items[i].shortdesc){
if(ncplane_putstr(n->ncp, sec->items[i].shortdesc) < 0){
return -1;
}
}
if(sec->items[i].shortcut_offset >= 0){
cell cl = CELL_TRIVIAL_INITIALIZER;
if(ncplane_at_yx(n->ncp, ypos, xpos + 1 + sec->items[i].shortcut_offset, &cl) < 0){

View File

@ -52,7 +52,7 @@ TEST_CASE("MenuTest") {
// a section with only separators ought be rejected
SUBCASE("SeparatorSectionReject") {
struct ncmenu_item empty_items[] = {
{ .desc = nullptr, .shortcut = {}, .shortcut_offset = -1, },
{ .desc = nullptr, .shortcut = {}, },
};
struct ncmenu_section sections[] = {
{ .name = strdup("Empty"), .itemcount = 1, .items = empty_items, .shortcut{}, },
@ -68,7 +68,7 @@ TEST_CASE("MenuTest") {
SUBCASE("MenuOneSection") {
struct ncmenu_item file_items[] = {
{ .desc = strdup("I would like a new file"), .shortcut = {}, .shortcut_offset = -1, },
{ .desc = strdup("I would like a new file"), .shortcut = {}, },
};
struct ncmenu_section sections[] = {
{ .name = strdup("File"), .itemcount = sizeof(file_items) / sizeof(*file_items), .items = file_items, .shortcut{}, },
@ -84,7 +84,7 @@ TEST_CASE("MenuTest") {
SUBCASE("VeryLongMenu") {
struct ncmenu_item items[] = {
{ .desc = strdup("Generic menu entry"), .shortcut = {}, .shortcut_offset = -1, },
{ .desc = strdup("Generic menu entry"), .shortcut = {}, },
};
struct ncmenu_section sections[] = {
{ .name = strdup("antidisestablishmentarianism"), .itemcount = sizeof(items) / sizeof(*items), .items = items, .shortcut{}, },