mirror of
https://github.com/dankamongmen/notcurses.git
synced 2024-11-04 06:00:30 +00:00
menu: highlight (bold+uline) section shortcuts
This commit is contained in:
parent
4c8adb0072
commit
3821b66bc7
@ -2219,9 +2219,7 @@ struct ncmenu_section {
|
||||
char* name; // utf-8 c string
|
||||
int itemcount;
|
||||
struct ncmenu_item* items;
|
||||
int xoff; // used only in library copy, ignored in request
|
||||
int bodycols; // used only in library copy, ignored in request
|
||||
int itemselected; // used only in library copy, ignored in request
|
||||
ncinput shortcut; // shortcut, will be underlined if present in name
|
||||
};
|
||||
|
||||
typedef struct ncmenu_options {
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
#include <wctype.h>
|
||||
#include <stdbool.h>
|
||||
#include <pthread.h>
|
||||
#include "notcurses.h"
|
||||
@ -123,9 +124,20 @@ typedef struct renderstate {
|
||||
bool defaultelidable;
|
||||
} renderstate;
|
||||
|
||||
typedef struct ncmenu_int_section {
|
||||
char* name; // utf-8 c string
|
||||
int itemcount;
|
||||
struct ncmenu_item* items;
|
||||
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
|
||||
int itemselected; // current item selected, -1 for no selection
|
||||
int shortcut_offset; // column offset within name of shortcut EGC
|
||||
} ncmenu_int_section;
|
||||
|
||||
typedef struct ncmenu {
|
||||
ncplane* ncp;
|
||||
struct ncmenu_section* sections;
|
||||
ncmenu_int_section* sections;
|
||||
bool bottom; // are we on the bottom (vs top)?
|
||||
int sectioncount; // must be positive
|
||||
int unrolledsection; // currently unrolled section, -1 if none
|
||||
@ -259,6 +271,31 @@ typedef struct notcurses {
|
||||
|
||||
void sigwinch_handler(int signo);
|
||||
|
||||
// Search the provided multibyte (UTF8) string 's' for the provided unicode
|
||||
// codepoint 'cp'. If found, return the column offset of the EGC in which the
|
||||
// codepoint appears in 'col', and the byte offset as the return value. If not
|
||||
// found, -1 is returned, and 'col' is meaningless.
|
||||
static inline int
|
||||
mbstr_find_codepoint(const char* s, char32_t cp, int* col){
|
||||
mbstate_t ps;
|
||||
memset(&ps, 0, sizeof(ps));
|
||||
size_t bytes = 0;
|
||||
size_t r;
|
||||
wchar_t w;
|
||||
*col = 0;
|
||||
while((r = mbrtowc(&w, s + bytes, MB_CUR_MAX, &ps)) != (size_t)-1 && r != (size_t)-2){
|
||||
if(r == 0){
|
||||
break;
|
||||
}
|
||||
if(towlower(cp) == (char32_t)towlower(w)){
|
||||
return bytes;
|
||||
}
|
||||
*col += wcwidth(w);
|
||||
bytes += r;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// load all known special keys from terminfo, and build the input sequence trie
|
||||
int prep_special_keys(notcurses* nc);
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
#include "internal.h"
|
||||
|
||||
static void
|
||||
free_menu_section(struct ncmenu_section* ms){
|
||||
free_menu_section(ncmenu_int_section* ms){
|
||||
for(int i = 0 ; i < ms->itemcount ; ++i){
|
||||
free(ms->items[i].desc);
|
||||
}
|
||||
@ -18,7 +18,7 @@ free_menu_sections(ncmenu* ncm){
|
||||
}
|
||||
|
||||
static int
|
||||
dup_menu_section(struct ncmenu_section* dst, const struct ncmenu_section* src){
|
||||
dup_menu_section(ncmenu_int_section* dst, const struct ncmenu_section* src){
|
||||
// we must reject any empty section
|
||||
if(src->itemcount == 0 || src->items == NULL){
|
||||
return -1;
|
||||
@ -70,8 +70,10 @@ dup_menu_sections(ncmenu* ncm, const ncmenu_options* opts, int* totalwidth, int*
|
||||
}
|
||||
int maxheight = 0;
|
||||
int maxwidth = *totalwidth;
|
||||
int xoff = 2;
|
||||
for(int i = 0 ; i < opts->sectioncount ; ++i){
|
||||
int cols = mbswidth(opts->sections[i].name);
|
||||
ncm->sections[i].xoff = xoff;
|
||||
if(cols < 0 || (ncm->sections[i].name = strdup(opts->sections[i].name)) == NULL){
|
||||
while(i--){
|
||||
free_menu_section(&ncm->sections[i]);
|
||||
@ -95,6 +97,13 @@ dup_menu_sections(ncmenu* ncm, const ncmenu_options* opts, int* totalwidth, int*
|
||||
maxwidth = *totalwidth + ncm->sections[i].bodycols + 2;
|
||||
}
|
||||
*totalwidth += cols + 2;
|
||||
memcpy(&ncm->sections[i].shortcut, &opts->sections[i].shortcut, sizeof(ncm->sections[i].shortcut));
|
||||
if(mbstr_find_codepoint(ncm->sections[i].name,
|
||||
ncm->sections[i].shortcut.id,
|
||||
&ncm->sections[i].shortcut_offset) < 0){
|
||||
ncm->sections[i].shortcut_offset = -1;
|
||||
}
|
||||
xoff += cols + 2;
|
||||
}
|
||||
*totalwidth = maxwidth;
|
||||
*totalheight += maxheight + 2; // two rows of border
|
||||
@ -119,8 +128,7 @@ write_header(ncmenu* ncm){ ncm->ncp->channels = ncm->headerchannels;
|
||||
return -1;
|
||||
}
|
||||
for(int i = 0 ; i < ncm->sectioncount ; ++i){
|
||||
ncm->sections[i].xoff = xoff;
|
||||
if(ncplane_putstr(ncm->ncp, ncm->sections[i].name) < 0){
|
||||
if(ncplane_putstr_yx(ncm->ncp, ypos, xoff, ncm->sections[i].name) < 0){
|
||||
return -1;
|
||||
}
|
||||
if(ncplane_putc(ncm->ncp, &c) < 0){
|
||||
@ -129,12 +137,24 @@ write_header(ncmenu* ncm){ ncm->ncp->channels = ncm->headerchannels;
|
||||
if(ncplane_putc(ncm->ncp, &c) < 0){
|
||||
return -1;
|
||||
}
|
||||
if(ncm->sections[i].shortcut_offset >= 0){
|
||||
cell cl = CELL_TRIVIAL_INITIALIZER;
|
||||
if(ncplane_at_yx(ncm->ncp, ypos, xoff + ncm->sections[i].shortcut_offset, &cl) < 0){
|
||||
return -1;
|
||||
}
|
||||
cell_styles_on(&cl, NCSTYLE_UNDERLINE|NCSTYLE_BOLD);
|
||||
if(ncplane_putc_yx(ncm->ncp, ypos, xoff + ncm->sections[i].shortcut_offset, &cl) < 0){
|
||||
return -1;
|
||||
}
|
||||
cell_release(ncm->ncp, &cl);
|
||||
}
|
||||
xoff += mbswidth(ncm->sections[i].name) + 2;
|
||||
}
|
||||
while(xoff++ < dimx){
|
||||
if(ncplane_putc(ncm->ncp, &c) < 0){
|
||||
while(xoff < dimx){
|
||||
if(ncplane_putc_yx(ncm->ncp, ypos, xoff, &c) < 0){
|
||||
return -1;
|
||||
}
|
||||
++xoff;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@ -144,7 +164,7 @@ ncmenu* ncmenu_create(notcurses* nc, const ncmenu_options* opts){
|
||||
return NULL;
|
||||
}
|
||||
int totalheight = 1;
|
||||
int totalwidth = 1; // start with one character margin on the left
|
||||
int totalwidth = 2; // start with two-character margin on the left
|
||||
ncmenu* ret = malloc(sizeof(*ret));
|
||||
ret->sectioncount = opts->sectioncount;
|
||||
ret->sections = NULL;
|
||||
@ -205,7 +225,7 @@ int ncmenu_unroll(ncmenu* n, int sectionidx){
|
||||
if(ncplane_rounded_box_sized(n->ncp, 0, n->headerchannels, height, width, 0)){
|
||||
return -1;
|
||||
}
|
||||
const struct ncmenu_section* sec = &n->sections[sectionidx];
|
||||
const ncmenu_int_section* sec = &n->sections[sectionidx];
|
||||
for(int i = 0 ; i < sec->itemcount ; ++i){
|
||||
++ypos;
|
||||
if(sec->items[i].desc){
|
||||
@ -282,7 +302,6 @@ int ncmenu_nextitem(ncmenu* n){
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
// FIXME can't allow any section to be all NULLs or we'll infintitely loop
|
||||
do{
|
||||
if(++n->sections[n->unrolledsection].itemselected == n->sections[n->unrolledsection].itemcount){
|
||||
n->sections[n->unrolledsection].itemselected = 0;
|
||||
|
@ -67,15 +67,15 @@ int main(void){
|
||||
{ .desc = "Restart", },
|
||||
};
|
||||
struct ncmenu_item file_items[] = {
|
||||
{ .desc = "New", },
|
||||
{ .desc = "New", .shortcut = { .id = 'n', .ctrl = true, }, },
|
||||
{ .desc = "Open", },
|
||||
{ .desc = "Close", },
|
||||
{ .desc = NULL, },
|
||||
{ .desc = "Quit", },
|
||||
};
|
||||
struct ncmenu_section sections[] = {
|
||||
{ .name = "Demo", .items = demo_items, },
|
||||
{ .name = "File", .items = file_items, },
|
||||
{ .name = "Demo", .items = demo_items, .shortcut = { .id = 'd', .alt = true, }, },
|
||||
{ .name = "File", .items = file_items, .shortcut = { .id = 'f', .alt = true, }, },
|
||||
};
|
||||
sections[0].itemcount = sizeof(demo_items) / sizeof(*demo_items);
|
||||
sections[1].itemcount = sizeof(file_items) / sizeof(*file_items);
|
||||
|
@ -39,8 +39,7 @@ TEST_CASE("MenuTest") {
|
||||
SUBCASE("EmptySectionReject") {
|
||||
struct ncmenu_options opts{};
|
||||
struct ncmenu_section sections[] = {
|
||||
{ .name = strdup("Empty"), .itemcount = 0, .items = nullptr,
|
||||
.xoff = -1, .bodycols = -1, .itemselected = -1, },
|
||||
{ .name = strdup("Empty"), .itemcount = 0, .items = nullptr, .shortcut{}, },
|
||||
};
|
||||
opts.sections = sections;
|
||||
opts.sectioncount = sizeof(sections) / sizeof(*sections);
|
||||
@ -56,8 +55,7 @@ TEST_CASE("MenuTest") {
|
||||
{ .desc = nullptr, .shortcut = {}, },
|
||||
};
|
||||
struct ncmenu_section sections[] = {
|
||||
{ .name = strdup("Empty"), .itemcount = 1, .items = empty_items,
|
||||
.xoff = -1, .bodycols = -1, .itemselected = -1, },
|
||||
{ .name = strdup("Empty"), .itemcount = 1, .items = empty_items, .shortcut{}, },
|
||||
};
|
||||
struct ncmenu_options opts{};
|
||||
opts.sections = sections;
|
||||
@ -73,8 +71,7 @@ TEST_CASE("MenuTest") {
|
||||
{ .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,
|
||||
.xoff = -1, .bodycols = -1, .itemselected = -1, },
|
||||
{ .name = strdup("File"), .itemcount = sizeof(file_items) / sizeof(*file_items), .items = file_items, .shortcut{}, },
|
||||
};
|
||||
struct ncmenu_options opts{};
|
||||
opts.sections = sections;
|
||||
@ -90,16 +87,11 @@ TEST_CASE("MenuTest") {
|
||||
{ .desc = strdup("Generic menu entry"), .shortcut = {}, },
|
||||
};
|
||||
struct ncmenu_section sections[] = {
|
||||
{ .name = strdup("antidisestablishmentarianism"), .itemcount = sizeof(items) / sizeof(*items), .items = items,
|
||||
.xoff = -1, .bodycols = -1, .itemselected = -1, },
|
||||
{ .name = strdup("floccinaucinihilipilification"), .itemcount = sizeof(items) / sizeof(*items), .items = items,
|
||||
.xoff = -1, .bodycols = -1, .itemselected = -1, },
|
||||
{ .name = strdup("pneumonoultramicroscopicsilicovolcanoconiosis"), .itemcount = sizeof(items) / sizeof(*items), .items = items,
|
||||
.xoff = -1, .bodycols = -1, .itemselected = -1, },
|
||||
{ .name = strdup("supercalifragilisticexpialidocious"), .itemcount = sizeof(items) / sizeof(*items), .items = items,
|
||||
.xoff = -1, .bodycols = -1, .itemselected = -1, },
|
||||
{ .name = strdup("Incomprehensibilities"), .itemcount = sizeof(items) / sizeof(*items), .items = items,
|
||||
.xoff = -1, .bodycols = -1, .itemselected = -1, },
|
||||
{ .name = strdup("antidisestablishmentarianism"), .itemcount = sizeof(items) / sizeof(*items), .items = items, .shortcut{}, },
|
||||
{ .name = strdup("floccinaucinihilipilification"), .itemcount = sizeof(items) / sizeof(*items), .items = items, .shortcut{}, },
|
||||
{ .name = strdup("pneumonoultramicroscopicsilicovolcanoconiosis"), .itemcount = sizeof(items) / sizeof(*items), .items = items, .shortcut{}, },
|
||||
{ .name = strdup("supercalifragilisticexpialidocious"), .itemcount = sizeof(items) / sizeof(*items), .items = items, .shortcut{}, },
|
||||
{ .name = strdup("Incomprehensibilities"), .itemcount = sizeof(items) / sizeof(*items), .items = items, .shortcut{}, },
|
||||
};
|
||||
struct ncmenu_options opts{};
|
||||
opts.sections = sections;
|
||||
|
Loading…
Reference in New Issue
Block a user