ref: 9803343f14f87a7f20b24f88add7e384f9d3eb56
parent: 142358974c9ffb67bbc71f31293282b0b11e3343
author: cancel <cancel@cancel.fm>
date: Wed Dec 12 16:55:54 EST 2018
Add start of new menu/nav-stack system
--- /dev/null
+++ b/term_util.c
@@ -1,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);
+}
--- /dev/null
+++ b/term_util.h
@@ -1,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;
--- a/tool
+++ b/tool
@@ -230,7 +230,7 @@
out_exe=cli
;;
orca|tui)
- add source_files osc_out.c tui_main.c
+ add source_files osc_out.c term_util.c tui_main.c
add cc_flags -D_XOPEN_SOURCE_EXTENDED=1
# thirdparty headers (like sokol_time.h) should get -isystem for their
# include dir so that any warnings they generate with our warning flags
@@ -254,7 +254,7 @@
add cc_flags -D_POSIX_C_SOURCE=200809L
;;
esac
- add libraries -lncurses
+ add libraries -lmenu -lncurses
# If we wanted wide chars, use -lncursesw on Linux, and still just
# -lncurses on Mac.
;;
--- a/tui_main.c
+++ b/tui_main.c
@@ -5,16 +5,14 @@
#include "mark.h"
#include "osc_out.h"
#include "sim.h"
+#include "term_util.h"
#include <getopt.h>
#include <locale.h>
-#include <ncurses.h>
#define SOKOL_IMPL
#include "sokol_time.h"
#undef SOKOL_IMPL
-#define CTRL_PLUS(c) ((c)&037)
-
static void usage() {
// clang-format off
fprintf(stderr,
@@ -43,39 +41,6 @@
}
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);
-}
-
-typedef enum {
Glyph_class_unknown,
Glyph_class_grid,
Glyph_class_comment,
@@ -989,7 +954,6 @@
}
void ged_draw(Ged* a, WINDOW* win) {
- werase(win);
// We can predictavely step the next simulation tick and then use the
// resulting markmap buffer for better UI visualization. If we don't do
// this, after loading a fresh file or after the user performs some edit
@@ -1036,7 +1000,6 @@
draw_oevent_list(win, &a->oevent_list);
}
a->is_draw_dirty = false;
- wrefresh(win);
}
void ged_adjust_bpm(Ged* a, Isz delta_bpm) {
@@ -1471,7 +1434,35 @@
return true;
}
+//
+// menu stuff
+//
+
enum {
+ Menu_id_quit = 1,
+ Menu_id_save,
+};
+
+struct {
+ Qmenu qmenu;
+} g_main_menu;
+
+void push_main_menu() {
+ Qmenu* qm = &g_main_menu.qmenu;
+ qmenu_start(qm);
+ qmenu_add_text_item(qm, "Save As...", Menu_id_save);
+ qmenu_add_spacer(qm);
+ qmenu_add_text_item(qm, "Quit", Menu_id_quit);
+ qmenu_push_to_nav(qm);
+ qnav_draw_box(&qm->nav_block);
+ qnav_draw_title(&qm->nav_block, "ORCA");
+}
+
+//
+// main
+//
+
+enum {
Argopt_margins = UCHAR_MAX + 1,
Argopt_osc_server,
Argopt_osc_port,
@@ -1538,6 +1529,7 @@
return 1;
}
+ qnav_init();
Ged ged_state;
ged_init(&ged_state);
@@ -1631,30 +1623,8 @@
// hasn't typed anything. That way we can mix other timers in our code,
// instead of being a slave only to terminal input.
// nodelay(stdscr, TRUE);
+ 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
- }
- }
- }
-
mousemask(ALL_MOUSE_EVENTS | REPORT_MOUSE_POSITION, NULL);
if (has_mouse()) {
// no waiting for distinguishing click from press
@@ -1661,7 +1631,8 @@
mouseinterval(0);
}
- WINDOW* cont_win = NULL;
+ WINDOW* cont_window = NULL;
+
int key = KEY_RESIZE;
wtimeout(stdscr, 0);
U64 last_time = 0;
@@ -1673,9 +1644,26 @@
U64 diff = stm_laptime(&last_time);
ged_apply_delta_secs(&ged_state, stm_sec(diff));
ged_do_stuff(&ged_state);
- if (ged_is_draw_dirty(&ged_state)) {
- ged_draw(&ged_state, cont_win);
+ bool drew_any = false;
+ if (qnav_stack.stack_changed) {
+ werase(stdscr);
+ drew_any = true;
+ qnav_stack.stack_changed = false;
}
+ if (ged_is_draw_dirty(&ged_state) || drew_any) {
+ werase(cont_window);
+ ged_draw(&ged_state, cont_window);
+ wnoutrefresh(cont_window);
+ drew_any = true;
+ }
+ for (Usz i = 0; i < qnav_stack.count; ++i) {
+ Qnav_block* qb = qnav_stack.blocks[i];
+ touchwin(qb->outer_window);
+ wnoutrefresh(qb->outer_window);
+ drew_any = true;
+ }
+ if (drew_any)
+ doupdate();
diff = stm_laptime(&last_time);
ged_apply_delta_secs(&ged_state, stm_sec(diff));
double secs_to_d = ged_secs_to_deadline(&ged_state);
@@ -1696,7 +1684,8 @@
wtimeout(stdscr, new_timeout);
cur_timeout = new_timeout;
}
- } break;
+ goto next_getch;
+ }
case KEY_RESIZE: {
int term_height = getmaxy(stdscr);
int term_width = getmaxx(stdscr);
@@ -1713,23 +1702,25 @@
content_h -= margins_2;
content_w -= margins_2;
}
- if (cont_win == NULL || getmaxy(cont_win) != content_h ||
- getmaxx(cont_win) != content_w) {
- if (cont_win) {
- delwin(cont_win);
+ if (cont_window == NULL || getmaxy(cont_window) != content_h ||
+ getmaxx(cont_window) != content_w) {
+ if (cont_window) {
+ delwin(cont_window);
}
wclear(stdscr);
- cont_win = derwin(stdscr, content_h, content_w, content_y, content_x);
+ cont_window =
+ derwin(stdscr, content_h, content_w, content_y, content_x);
ged_set_window_size(&ged_state, content_h, content_w);
}
- } break;
+ goto next_getch;
+ }
case KEY_MOUSE: {
MEVENT mevent;
- if (cont_win && getmouse(&mevent) == OK) {
+ if (cont_window && getmouse(&mevent) == OK) {
int win_y, win_x;
int win_h, win_w;
- getbegyx(cont_win, win_y, win_x);
- getmaxyx(cont_win, win_h, win_w);
+ getbegyx(cont_window, win_y, win_x);
+ getmaxyx(cont_window, win_h, win_w);
int inwin_y = mevent.y - win_y;
int inwin_x = mevent.x - win_x;
if (inwin_y >= win_h)
@@ -1742,9 +1733,49 @@
inwin_x = 0;
ged_mouse_event(&ged_state, (Usz)inwin_y, (Usz)inwin_x, mevent.bstate);
}
- } break;
+ goto next_getch;
+ }
case CTRL_PLUS('q'):
goto quit;
+ }
+
+ Qnav_block* qb = qnav_top_block();
+ if (qb) {
+ switch (qb->tag) {
+ case Qnav_type_message: {
+ if (qnav_drive_message(qb, key))
+ qnav_stack_pop();
+ } break;
+ case Qnav_type_qmenu: {
+ Qmenu* qm = qmenu_of(qb);
+ Qmenu_action act;
+ if (qmenu_drive(qm, key, &act)) {
+ switch (act.any.type) {
+ case Qmenu_action_type_canceled: {
+ qnav_stack_pop();
+ } break;
+ case Qmenu_action_type_picked: {
+ if (qm == &g_main_menu.qmenu) {
+ switch (act.picked.id) {
+ case Menu_id_quit:
+ goto quit;
+ case Menu_id_save: {
+ Qnav_block* msg = qnav_push_message(3, 30);
+ WINDOW* msgw = msg->content_window;
+ wmove(msgw, 0, 1);
+ wprintw(msgw, "Not yet implemented from this menu");
+ } break;
+ }
+ }
+ } break;
+ }
+ }
+ } break;
+ }
+ goto next_getch;
+ }
+
+ switch (key) {
case KEY_UP:
case CTRL_PLUS('k'):
ged_dir_input(&ged_state, Ged_dir_up);
@@ -1851,6 +1882,15 @@
ged_input_character(&ged_state, '.');
break;
+ case CTRL_PLUS('d'):
+ case KEY_F(1): {
+ if (qnav_top_block()) {
+ qnav_stack_pop();
+ } else {
+ push_main_menu();
+ }
+ } break;
+
case KEY_F(2): {
if (!ged_state.filename)
break;
@@ -1894,6 +1934,7 @@
#endif
break;
}
+ next_getch:
key = wgetch(stdscr);
if (cur_timeout != 0) {
wtimeout(stdscr, 0);
@@ -1902,8 +1943,9 @@
}
quit:
ged_stop_all_sustained_notes(&ged_state);
- if (cont_win) {
- delwin(cont_win);
+ qnav_deinit();
+ if (cont_window) {
+ delwin(cont_window);
}
endwin();
ged_deinit(&ged_state);