
4 changed files with 426 additions and 79 deletions
@ -0,0 +1,203 @@ |
|||
#include "base.h" |
|||
#include "term_util.h" |
|||
|
|||
void term_util_init_colors() { |
|||
if (has_colors()) { |
|||
// Enable color
|
|||
start_color(); |
|||
use_default_colors(); |
|||
for (int ifg = 0; ifg < Colors_count; ++ifg) { |
|||
for (int ibg = 0; ibg < Colors_count; ++ibg) { |
|||
int res = init_pair((short int)(1 + ifg * Colors_count + ibg), |
|||
(short int)(ifg - 1), (short int)(ibg - 1)); |
|||
(void)res; |
|||
// Might fail on Linux virtual console/terminal for a couple of colors.
|
|||
// Just ignore.
|
|||
#if 0 |
|||
if (res == ERR) { |
|||
endwin(); |
|||
fprintf(stderr, "Error initializing color pair: %d %d\n", ifg - 1, |
|||
ibg - 1); |
|||
exit(1); |
|||
} |
|||
#endif |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
#define ORCA_CONTAINER_OF(ptr, type, member) \ |
|||
((type*)((char*)(1 ? (ptr) : &((type*)0)->member) - offsetof(type, member))) |
|||
|
|||
Qnav_stack qnav_stack; |
|||
|
|||
static struct { int unused; } qmenu_spacer_user_unique; |
|||
|
|||
void qnav_init() { |
|||
qnav_stack.count = 0; |
|||
qnav_stack.stack_changed = false; |
|||
memset(qnav_stack.blocks, 0, sizeof(qnav_stack.blocks)); |
|||
} |
|||
void qnav_deinit() { |
|||
while (qnav_stack.count != 0) |
|||
qnav_stack_pop(); |
|||
} |
|||
void qnav_stack_push(Qnav_type_tag tag, int height, int width, |
|||
Qnav_block* out) { |
|||
#ifndef NDEBUG |
|||
for (Usz i = 0; i < qnav_stack.count; ++i) { |
|||
assert(qnav_stack.blocks[i] != out); |
|||
} |
|||
#endif |
|||
int left; |
|||
if (qnav_stack.count > 0) { |
|||
WINDOW* w = qnav_stack.blocks[qnav_stack.count - 1]->outer_window; |
|||
left = getbegx(w) + getmaxx(w) + 0; |
|||
} else { |
|||
left = 0; |
|||
} |
|||
qnav_stack.blocks[qnav_stack.count] = out; |
|||
++qnav_stack.count; |
|||
out->outer_window = newwin(height + 2, width + 3, 0, left); |
|||
out->content_window = derwin(out->outer_window, height, width, 1, 1); |
|||
out->tag = tag; |
|||
qnav_stack.stack_changed = true; |
|||
} |
|||
Qnav_block* qnav_top_block() { |
|||
if (qnav_stack.count == 0) |
|||
return NULL; |
|||
return qnav_stack.blocks[qnav_stack.count - 1]; |
|||
} |
|||
void qnav_free_block(Qnav_block* qb); |
|||
void qnav_stack_pop() { |
|||
assert(qnav_stack.count > 0); |
|||
if (qnav_stack.count == 0) |
|||
return; |
|||
Qnav_block* qb = qnav_stack.blocks[qnav_stack.count - 1]; |
|||
WINDOW* content_window = qb->content_window; |
|||
WINDOW* outer_window = qb->outer_window; |
|||
qnav_free_block(qb); |
|||
delwin(content_window); |
|||
delwin(outer_window); |
|||
--qnav_stack.count; |
|||
qnav_stack.blocks[qnav_stack.count] = NULL; |
|||
qnav_stack.stack_changed = true; |
|||
} |
|||
void qnav_draw_box(Qnav_block* qb) { box(qb->outer_window, 0, 0); } |
|||
void qnav_draw_title(Qnav_block* qb, char const* title) { |
|||
wmove(qb->outer_window, 0, 2); |
|||
wprintw(qb->outer_window, title); |
|||
} |
|||
|
|||
Qnav_block* qnav_push_message(int height, int width) { |
|||
Qnav_block* qb = malloc(sizeof(Qnav_block)); |
|||
qnav_stack_push(Qnav_type_message, height, width, qb); |
|||
qnav_draw_box(qb); |
|||
return qb; |
|||
} |
|||
|
|||
bool qnav_drive_message(Qnav_block* qb, int key) { |
|||
(void)qb; |
|||
switch (key) { |
|||
case ' ': |
|||
case 27: |
|||
case '\r': |
|||
case KEY_ENTER: |
|||
return true; |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
void qmenu_start(Qmenu* qm) { memset(qm, 0, sizeof(Qmenu)); } |
|||
void qmenu_add_text_item(Qmenu* qm, char const* text, int id) { |
|||
ITEM* item = new_item(text, NULL); |
|||
set_item_userptr(item, (void*)(intptr_t)(id)); |
|||
qm->ncurses_items[qm->items_count] = item; |
|||
++qm->items_count; |
|||
} |
|||
void qmenu_add_spacer(Qmenu* qm) { |
|||
ITEM* item = new_item(" ", NULL); |
|||
item_opts_off(item, O_SELECTABLE); |
|||
set_item_userptr(item, &qmenu_spacer_user_unique); |
|||
qm->ncurses_items[qm->items_count] = item; |
|||
++qm->items_count; |
|||
} |
|||
void qmenu_push_to_nav(Qmenu* qm) { |
|||
qm->ncurses_menu = new_menu(qm->ncurses_items); |
|||
set_menu_mark(qm->ncurses_menu, " > "); |
|||
set_menu_grey(qm->ncurses_menu, A_DIM); |
|||
int menu_min_h, menu_min_w; |
|||
scale_menu(qm->ncurses_menu, &menu_min_h, &menu_min_w); |
|||
qnav_stack_push(Qnav_type_qmenu, menu_min_h, menu_min_w, &qm->nav_block); |
|||
set_menu_win(qm->ncurses_menu, qm->nav_block.outer_window); |
|||
set_menu_sub(qm->ncurses_menu, qm->nav_block.content_window); |
|||
post_menu(qm->ncurses_menu); |
|||
} |
|||
|
|||
void qmenu_free(Qmenu* qm) { |
|||
unpost_menu(qm->ncurses_menu); |
|||
free_menu(qm->ncurses_menu); |
|||
for (Usz i = 0; i < qm->items_count; ++i) { |
|||
free_item(qm->ncurses_items[i]); |
|||
} |
|||
} |
|||
|
|||
void qnav_free_block(Qnav_block* qb) { |
|||
switch (qb->tag) { |
|||
case Qnav_type_message: { |
|||
free(qb); |
|||
} break; |
|||
case Qnav_type_qmenu: { |
|||
Qmenu* qm = ORCA_CONTAINER_OF(qb, Qmenu, nav_block); |
|||
qmenu_free(qm); |
|||
} break; |
|||
} |
|||
} |
|||
|
|||
bool qmenu_drive(Qmenu* qm, int key, Qmenu_action* out_action) { |
|||
switch (key) { |
|||
case 27: { |
|||
out_action->any.type = Qmenu_action_type_canceled; |
|||
return true; |
|||
} |
|||
case ' ': |
|||
case '\r': |
|||
case KEY_ENTER: { |
|||
ITEM* cur = current_item(qm->ncurses_menu); |
|||
out_action->picked.type = Qmenu_action_type_picked; |
|||
out_action->picked.id = cur ? (int)(intptr_t)item_userptr(cur) : 0; |
|||
return true; |
|||
} break; |
|||
case KEY_UP: { |
|||
ITEM* starting = current_item(qm->ncurses_menu); |
|||
menu_driver(qm->ncurses_menu, REQ_UP_ITEM); |
|||
for (;;) { |
|||
ITEM* cur = current_item(qm->ncurses_menu); |
|||
if (!cur || cur == starting) |
|||
break; |
|||
if (item_userptr(cur) != &qmenu_spacer_user_unique) |
|||
break; |
|||
menu_driver(qm->ncurses_menu, REQ_UP_ITEM); |
|||
} |
|||
return false; |
|||
} |
|||
case KEY_DOWN: { |
|||
ITEM* starting = current_item(qm->ncurses_menu); |
|||
menu_driver(qm->ncurses_menu, REQ_DOWN_ITEM); |
|||
for (;;) { |
|||
ITEM* cur = current_item(qm->ncurses_menu); |
|||
if (!cur || cur == starting) |
|||
break; |
|||
if (item_userptr(cur) != &qmenu_spacer_user_unique) |
|||
break; |
|||
menu_driver(qm->ncurses_menu, REQ_DOWN_ITEM); |
|||
} |
|||
return false; |
|||
} |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
Qmenu* qmenu_of(Qnav_block* qb) { |
|||
return ORCA_CONTAINER_OF(qb, Qmenu, nav_block); |
|||
} |
@ -0,0 +1,102 @@ |
|||
#pragma once |
|||
#include <menu.h> |
|||
#include <ncurses.h> |
|||
|
|||
#define CTRL_PLUS(c) ((c)&037) |
|||
|
|||
typedef enum { |
|||
C_natural, |
|||
C_black, |
|||
C_red, |
|||
C_green, |
|||
C_yellow, |
|||
C_blue, |
|||
C_magenta, |
|||
C_cyan, |
|||
C_white, |
|||
} Color_name; |
|||
|
|||
enum { |
|||
Colors_count = C_white + 1, |
|||
}; |
|||
|
|||
enum { |
|||
Cdef_normal = COLOR_PAIR(1), |
|||
}; |
|||
|
|||
typedef enum { |
|||
A_normal = A_NORMAL, |
|||
A_bold = A_BOLD, |
|||
A_dim = A_DIM, |
|||
A_standout = A_STANDOUT, |
|||
A_reverse = A_REVERSE, |
|||
} Term_attr; |
|||
|
|||
ORCA_FORCE_INLINE |
|||
int fg_bg(Color_name fg, Color_name bg) { |
|||
return COLOR_PAIR(1 + fg * Colors_count + bg); |
|||
} |
|||
|
|||
void term_util_init_colors(); |
|||
|
|||
typedef enum { |
|||
Qnav_type_message, |
|||
Qnav_type_qmenu, |
|||
} Qnav_type_tag; |
|||
|
|||
typedef struct { |
|||
Qnav_type_tag tag; |
|||
WINDOW* outer_window; |
|||
WINDOW* content_window; |
|||
} Qnav_block; |
|||
|
|||
typedef struct { |
|||
Qnav_block* blocks[16]; |
|||
Usz count; |
|||
bool stack_changed; |
|||
} Qnav_stack; |
|||
|
|||
typedef struct { |
|||
Qnav_block nav_block; |
|||
MENU* ncurses_menu; |
|||
ITEM* ncurses_items[32]; |
|||
Usz items_count; |
|||
} Qmenu; |
|||
|
|||
typedef enum { |
|||
Qmenu_action_type_canceled, |
|||
Qmenu_action_type_picked, |
|||
} Qmenu_action_type; |
|||
|
|||
typedef struct { |
|||
Qmenu_action_type type; |
|||
} Qmenu_action_any; |
|||
|
|||
typedef struct { |
|||
Qmenu_action_type type; |
|||
int id; |
|||
} Qmenu_action_picked; |
|||
|
|||
typedef union { |
|||
Qmenu_action_any any; |
|||
Qmenu_action_picked picked; |
|||
} Qmenu_action; |
|||
|
|||
void qnav_init(); |
|||
void qnav_deinit(); |
|||
void qnav_draw_box(Qnav_block* qb); |
|||
void qnav_draw_title(Qnav_block* qb, char const* title); |
|||
Qnav_block* qnav_top_block(); |
|||
void qnav_stack_pop(); |
|||
|
|||
Qnav_block* qnav_push_message(int height, int width); |
|||
bool qnav_drive_message(Qnav_block* qb, int key); |
|||
|
|||
void qmenu_start(Qmenu* qm); |
|||
void qmenu_add_text_item(Qmenu* qm, char const* text, int id); |
|||
void qmenu_add_spacer(Qmenu* qm); |
|||
void qmenu_push_to_nav(Qmenu* qm); |
|||
bool qmenu_drive(Qmenu* qm, int key, Qmenu_action* out_action); |
|||
Qmenu* qmenu_of(Qnav_block* qb); |
|||
|
|||
extern Qnav_stack qnav_stack; |
Loading…
Reference in new issue