shithub: orca

Download patch

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);