mirror of
https://github.com/dankamongmen/notcurses
synced 2025-03-10 01:29:05 -04:00
ncmenu: print item shortcut aligned right #179
This commit is contained in:
parent
49013433ea
commit
039a390877
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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){
|
||||
|
@ -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{}, },
|
||||
|
Loading…
x
Reference in New Issue
Block a user