shithub: orca

Download patch

ref: 02660c70bfd16cccf8f39f6e3def4c2689e79d90
parent: 18906ad6dd8a762f8125fb5bbf5cb5759abd86dc
author: cancel <cancel@cancel.fm>
date: Fri Dec 7 15:25:51 EST 2018

Break tui app state out into struct

--- a/tui_main.c
+++ b/tui_main.c
@@ -455,6 +455,224 @@
                   scratch_field, undo_hist, tui_cursor, needs_remarking);
 }
 
+typedef struct {
+  Field field;
+  Field scratch_field;
+  Markmap_reusable markmap_r;
+  Bank bank;
+  Undo_history undo_hist;
+  Oevent_list oevent_list;
+  Oevent_list scratch_oevent_list;
+  Tui_cursor tui_cursor;
+  Piano_bits piano_bits;
+  Usz tick_num;
+  Usz ruler_spacing_y, ruler_spacing_x;
+  Tui_input_mode input_mode;
+  bool is_playing;
+  bool needs_remarking;
+  bool draw_event_list;
+} App_state;
+
+void app_init(App_state* a) {
+  field_init(&a->field);
+  field_init(&a->scratch_field);
+  markmap_reusable_init(&a->markmap_r);
+  bank_init(&a->bank);
+  undo_history_init(&a->undo_hist);
+  tui_cursor_init(&a->tui_cursor);
+  oevent_list_init(&a->oevent_list);
+  oevent_list_init(&a->scratch_oevent_list);
+  a->piano_bits = ORCA_PIANO_BITS_NONE;
+  a->tick_num = 0;
+  a->ruler_spacing_y = 8;
+  a->ruler_spacing_x = 8;
+  a->input_mode = Tui_input_mode_normal;
+  a->is_playing = false;
+  a->needs_remarking = true;
+  a->draw_event_list = false;
+}
+
+void app_deinit(App_state* a) {
+  field_deinit(&a->field);
+  field_deinit(&a->scratch_field);
+  markmap_reusable_deinit(&a->markmap_r);
+  bank_deinit(&a->bank);
+  undo_history_deinit(&a->undo_hist);
+  oevent_list_deinit(&a->oevent_list);
+  oevent_list_deinit(&a->scratch_oevent_list);
+}
+
+void app_draw(App_state* a, WINDOW* 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
+  // (or even after a regular simulation step), the new glyph buffer won't
+  // have had phase 0 of the simulation run, which means the ports and other
+  // flags won't be set on the markmap buffer, so the colors for disabled
+  // cells, ports, etc. won't be set.
+  //
+  // We can just perform a simulation step using the current state, keep the
+  // markmap buffer that it produces, then roll back the glyph buffer to
+  // where it was before. This should produce results similar to having
+  // specialized UI code that looks at each glyph and figures out the ports,
+  // etc.
+  if (a->needs_remarking) {
+    field_resize_raw_if_necessary(&a->scratch_field, a->field.height,
+                                  a->field.width);
+    field_copy(&a->field, &a->scratch_field);
+    markmap_reusable_ensure_size(&a->markmap_r, a->field.height, a->field.width);
+    orca_run(a->scratch_field.buffer, a->markmap_r.buffer, a->field.height,
+             a->field.width, a->tick_num, &a->bank, &a->scratch_oevent_list,
+             a->piano_bits);
+    a->needs_remarking = false;
+  }
+  int win_h, win_w;
+  getmaxyx(win, win_h, win_w);
+  tdraw_field(win, win_h, win_w, 0, 0, a->field.buffer, a->markmap_r.buffer,
+              a->field.height, a->field.width, a->ruler_spacing_y,
+              a->ruler_spacing_x);
+  for (int y = a->field.height; y < win_h - 1; ++y) {
+    wmove(win, y, 0);
+    wclrtoeol(win);
+  }
+  tdraw_tui_cursor(win, win_h, win_w, a->field.buffer, a->field.height,
+                   a->field.width, a->ruler_spacing_y, a->ruler_spacing_x,
+                   a->tui_cursor.y, a->tui_cursor.x, a->input_mode,
+                   a->is_playing);
+  if (win_h > 3) {
+    tdraw_hud(win, win_h - 2, 0, 2, win_w, "noname", a->field.height,
+              a->field.width, a->ruler_spacing_y, a->ruler_spacing_x,
+              a->tick_num, &a->tui_cursor, a->input_mode);
+  }
+  if (a->draw_event_list) {
+    tdraw_oevent_list(win, &a->oevent_list);
+  }
+  wrefresh(win);
+}
+
+void app_move_cursor_relative(App_state* a, Isz delta_y, Isz delta_x) {
+  tui_cursor_move_relative(&a->tui_cursor, a->field.height, a->field.width,
+                           delta_y, delta_x);
+}
+
+void app_resize_grid_relative(App_state* a, Isz delta_y, Isz delta_x) {
+  tui_resize_grid_snap_ruler(&a->field, &a->markmap_r, a->ruler_spacing_y,
+                             a->ruler_spacing_x, delta_y, delta_x, a->tick_num,
+                             &a->scratch_field, &a->undo_hist, &a->tui_cursor,
+                             &a->needs_remarking);
+}
+
+void app_write_character(App_state* a, char c) {
+  undo_history_push(&a->undo_hist, &a->field, a->tick_num);
+  gbuffer_poke(a->field.buffer, a->field.height, a->field.width,
+               a->tui_cursor.y, a->tui_cursor.x, c);
+  // Indicate we want the next simulation step to be run predictavely,
+  // so that we can use the reulsting mark buffer for UI visualization.
+  // This is "expensive", so it could be skipped for non-interactive
+  // input in situations where max throughput is necessary.
+  a->needs_remarking = true;
+  if (a->input_mode == Tui_input_mode_append) {
+    tui_cursor_move_relative(&a->tui_cursor, a->field.height, a->field.width, 0,
+                             1);
+  }
+}
+
+void app_add_piano_bits_for_character(App_state* a, char c) {
+  Piano_bits added_bits = piano_bits_of((Glyph)c);
+  a->piano_bits |= added_bits;
+}
+
+typedef enum {
+  App_input_event_undo,
+  App_input_event_toggle_append_mode,
+  App_input_event_toggle_piano_mode,
+  App_input_event_step_forward,
+  App_input_event_toggle_show_event_list,
+  App_input_event_toggle_play_pause,
+  App_input_event_shrink_ruler_y,
+  App_input_event_grow_ruler_y,
+  App_input_event_shrink_ruler_x,
+  App_input_event_grow_ruler_x,
+  App_input_event_shrink_field_y,
+  App_input_event_grow_field_y,
+  App_input_event_shrink_field_x,
+  App_input_event_grow_field_x,
+} App_input_event;
+
+void app_input_event(App_state* a, App_input_event ev) {
+  switch (ev) {
+  case App_input_event_undo:
+    if (undo_history_count(&a->undo_hist) > 0) {
+      undo_history_pop(&a->undo_hist, &a->field, &a->tick_num);
+      a->needs_remarking = true;
+    }
+    break;
+  case App_input_event_toggle_append_mode:
+    if (a->input_mode == Tui_input_mode_append) {
+      a->input_mode = Tui_input_mode_normal;
+    } else {
+      a->input_mode = Tui_input_mode_append;
+    }
+    break;
+  case App_input_event_toggle_piano_mode:
+    if (a->input_mode == Tui_input_mode_piano) {
+      a->input_mode = Tui_input_mode_normal;
+    } else {
+      a->input_mode = Tui_input_mode_piano;
+    }
+    break;
+  case App_input_event_step_forward:
+    undo_history_push(&a->undo_hist, &a->field, a->tick_num);
+    orca_run(a->field.buffer, a->markmap_r.buffer, a->field.height,
+             a->field.width, a->tick_num, &a->bank, &a->oevent_list,
+             a->piano_bits);
+    ++a->tick_num;
+    a->piano_bits = ORCA_PIANO_BITS_NONE;
+    a->needs_remarking = true;
+    break;
+  case App_input_event_toggle_play_pause:
+    if (a->is_playing) {
+      a->is_playing = false;
+      nodelay(stdscr, FALSE);
+    } else {
+      a->is_playing = true;
+      nodelay(stdscr, TRUE);
+    }
+    break;
+  case App_input_event_toggle_show_event_list:
+    a->draw_event_list = !a->draw_event_list;
+    break;
+  case App_input_event_shrink_ruler_y:
+    if (a->ruler_spacing_y > 4)
+      --a->ruler_spacing_y;
+    break;
+  case App_input_event_grow_ruler_y:
+    if (a->ruler_spacing_y < 16)
+      ++a->ruler_spacing_y;
+    break;
+  case App_input_event_shrink_ruler_x:
+    if (a->ruler_spacing_x > 4)
+      --a->ruler_spacing_x;
+    break;
+  case App_input_event_grow_ruler_x:
+    if (a->ruler_spacing_x < 16)
+      ++a->ruler_spacing_x;
+    break;
+  case App_input_event_shrink_field_y:
+    app_resize_grid_relative(a, -1, 0);
+    break;
+  case App_input_event_grow_field_y:
+    app_resize_grid_relative(a, 1, 0);
+    break;
+  case App_input_event_shrink_field_x:
+    app_resize_grid_relative(a, 0, -1);
+    break;
+  case App_input_event_grow_field_x:
+    app_resize_grid_relative(a, 0, 1);
+    break;
+  }
+}
+
 enum { Argopt_margins = UCHAR_MAX + 1 };
 
 int main(int argc, char** argv) {
@@ -501,12 +719,12 @@
     return 1;
   }
 
-  Field field;
+  App_state app_state;
+  app_init(&app_state);
+
   if (input_file) {
-    field_init(&field);
-    Field_load_error fle = field_load_file(input_file, &field);
+    Field_load_error fle = field_load_file(input_file, &app_state.field);
     if (fle != Field_load_error_ok) {
-      field_deinit(&field);
       char const* errstr = "Unknown";
       switch (fle) {
       case Field_load_error_ok:
@@ -528,26 +746,16 @@
         break;
       }
       fprintf(stderr, "File load error: %s.\n", errstr);
+      app_deinit(&app_state);
       return 1;
     }
   } else {
     input_file = "unnamed";
-    field_init_fill(&field, 25, 57, '.');
+    field_init_fill(&app_state.field, 25, 57, '.');
   }
   // Set up timer lib
   stm_setup();
 
-  Markmap_reusable markmap_r;
-  markmap_reusable_init(&markmap_r);
-  markmap_reusable_ensure_size(&markmap_r, field.height, field.width);
-  mbuffer_clear(markmap_r.buffer, field.height, field.width);
-  Bank bank;
-  bank_init(&bank);
-  Undo_history undo_hist;
-  undo_history_init(&undo_hist);
-  Oevent_list oevent_list;
-  oevent_list_init(&oevent_list);
-
   // Enable UTF-8 by explicitly initializing our locale before initializing
   // ncurses.
   setlocale(LC_ALL, "");
@@ -593,46 +801,10 @@
   int cont_win_h = 0;
   int cont_win_w = 0;
 
-  Field scratch_field;
-  field_init(&scratch_field);
-  Oevent_list scratch_oevent_list;
-  oevent_list_init(&scratch_oevent_list);
-
-  Tui_cursor tui_cursor;
-  tui_cursor_init(&tui_cursor);
-  Tui_input_mode input_mode = Tui_input_mode_normal;
-  Piano_bits piano_bits = ORCA_PIANO_BITS_NONE;
-  Usz tick_num = 0;
-  Usz ruler_spacing_y = 8;
-  Usz ruler_spacing_x = 8;
-  bool is_playing = false;
-  bool needs_remarking = true;
-  bool draw_event_list = false;
-  double bpm = 120.0;
   for (;;) {
     int term_height = getmaxy(stdscr);
     int term_width = getmaxx(stdscr);
     assert(term_height >= 0 && term_width >= 0);
-    // 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
-    // (or even after a regular simulation step), the new glyph buffer won't
-    // have had phase 0 of the simulation run, which means the ports and other
-    // flags won't be set on the markmap buffer, so the colors for disabled
-    // cells, ports, etc. won't be set.
-    //
-    // We can just perform a simulation step using the current state, keep the
-    // markmap buffer that it produces, then roll back the glyph buffer to
-    // where it was before. This should produce results similar to having
-    // specialized UI code that looks at each glyph and figures out the ports,
-    // etc.
-    if (needs_remarking) {
-      field_resize_raw_if_necessary(&scratch_field, field.height, field.width);
-      field_copy(&field, &scratch_field);
-      orca_run(scratch_field.buffer, markmap_r.buffer, field.height,
-               field.width, tick_num, &bank, &scratch_oevent_list, piano_bits);
-      needs_remarking = false;
-    }
     int content_y = 0;
     int content_x = 0;
     int content_h = term_height;
@@ -655,27 +827,9 @@
       cont_win_h = content_h;
       cont_win_w = content_w;
     }
-    tdraw_field(cont_win, content_h, content_w, 0, 0, field.buffer,
-                markmap_r.buffer, field.height, field.width, ruler_spacing_y,
-                ruler_spacing_x);
-    for (int y = field.height; y < content_h - 1; ++y) {
-      wmove(cont_win, y, 0);
-      wclrtoeol(cont_win);
-    }
-    tdraw_tui_cursor(cont_win, cont_win_h, cont_win_w, field.buffer,
-                     field.height, field.width, ruler_spacing_y,
-                     ruler_spacing_x, tui_cursor.y, tui_cursor.x, input_mode,
-                     is_playing);
-    if (content_h > 3) {
-      tdraw_hud(cont_win, content_h - 2, 0, 2, content_w, input_file,
-                field.height, field.width, ruler_spacing_y, ruler_spacing_x,
-                tick_num, &tui_cursor, input_mode);
-    }
-    if (draw_event_list) {
-      tdraw_oevent_list(cont_win, &oevent_list);
-    }
-    wrefresh(cont_win);
 
+    app_draw(&app_state, cont_win);
+
     int key;
     // ncurses gives us ERR if there was no user input. We'll sleep for 0
     // seconds, so that we'll yield CPU time to the OS instead of looping as
@@ -694,121 +848,75 @@
       goto quit;
     case KEY_UP:
     case AND_CTRL('k'):
-      tui_cursor_move_relative(&tui_cursor, field.height, field.width, -1, 0);
+      app_move_cursor_relative(&app_state, -1, 0);
       break;
     case AND_CTRL('j'):
     case KEY_DOWN:
-      tui_cursor_move_relative(&tui_cursor, field.height, field.width, 1, 0);
+      app_move_cursor_relative(&app_state, 1, 0);
       break;
     case KEY_BACKSPACE:
     case AND_CTRL('h'):
     case KEY_LEFT:
-      tui_cursor_move_relative(&tui_cursor, field.height, field.width, 0, -1);
+      app_move_cursor_relative(&app_state, 0, -1);
       break;
     case AND_CTRL('l'):
     case KEY_RIGHT:
-      tui_cursor_move_relative(&tui_cursor, field.height, field.width, 0, 1);
+      app_move_cursor_relative(&app_state, 0, 1);
       break;
     case AND_CTRL('u'):
-      if (undo_history_count(&undo_hist) > 0) {
-        undo_history_pop(&undo_hist, &field, &tick_num);
-        needs_remarking = true;
-      }
+      app_input_event(&app_state, App_input_event_undo);
       break;
     case '[':
-      if (ruler_spacing_x > 4)
-        --ruler_spacing_x;
+      app_input_event(&app_state, App_input_event_shrink_ruler_x);
       break;
     case ']':
-      if (ruler_spacing_x < 16)
-        ++ruler_spacing_x;
+      app_input_event(&app_state, App_input_event_grow_ruler_x);
       break;
     case '{':
-      if (ruler_spacing_y > 4)
-        --ruler_spacing_y;
+      app_input_event(&app_state, App_input_event_shrink_ruler_y);
       break;
     case '}':
-      if (ruler_spacing_y < 16)
-        ++ruler_spacing_y;
+      app_input_event(&app_state, App_input_event_grow_ruler_y);
       break;
     case '(':
-      tui_resize_grid_snap_ruler(
-          &field, &markmap_r, ruler_spacing_y, ruler_spacing_x, 0, -1, tick_num,
-          &scratch_field, &undo_hist, &tui_cursor, &needs_remarking);
+      app_input_event(&app_state, App_input_event_shrink_field_x);
       break;
     case ')':
-      tui_resize_grid_snap_ruler(
-          &field, &markmap_r, ruler_spacing_y, ruler_spacing_x, 0, 1, tick_num,
-          &scratch_field, &undo_hist, &tui_cursor, &needs_remarking);
+      app_input_event(&app_state, App_input_event_grow_field_x);
       break;
     case '_':
-      tui_resize_grid_snap_ruler(
-          &field, &markmap_r, ruler_spacing_y, ruler_spacing_x, -1, 0, tick_num,
-          &scratch_field, &undo_hist, &tui_cursor, &needs_remarking);
+      app_input_event(&app_state, App_input_event_shrink_field_y);
       break;
     case '+':
-      tui_resize_grid_snap_ruler(
-          &field, &markmap_r, ruler_spacing_y, ruler_spacing_x, 1, 0, tick_num,
-          &scratch_field, &undo_hist, &tui_cursor, &needs_remarking);
+      app_input_event(&app_state, App_input_event_grow_field_y);
       break;
     case '\r':
     case KEY_ENTER:
-      if (input_mode == Tui_input_mode_append) {
-        input_mode = Tui_input_mode_normal;
-      } else {
-        input_mode = Tui_input_mode_append;
-      }
+      app_input_event(&app_state, App_input_event_toggle_append_mode);
       break;
     case '/':
-      if (input_mode == Tui_input_mode_piano) {
-        input_mode = Tui_input_mode_normal;
-      } else {
-        input_mode = Tui_input_mode_piano;
-      }
+      app_input_event(&app_state, App_input_event_toggle_piano_mode);
       break;
     case AND_CTRL('f'): {
-      undo_history_push(&undo_hist, &field, tick_num);
-      orca_run(field.buffer, markmap_r.buffer, field.height, field.width,
-               tick_num, &bank, &oevent_list, piano_bits);
-      ++tick_num;
-      piano_bits = ORCA_PIANO_BITS_NONE;
-      needs_remarking = true;
+      app_input_event(&app_state, App_input_event_step_forward);
     } break;
     case AND_CTRL('e'):
-      draw_event_list = !draw_event_list;
+      app_input_event(&app_state, App_input_event_toggle_show_event_list);
       break;
     case ' ':
-      if (is_playing) {
-        is_playing = false;
-        nodelay(stdscr, FALSE);
-      } else {
-        is_playing = true;
-        nodelay(stdscr, TRUE);
-      }
+      app_input_event(&app_state, App_input_event_toggle_play_pause);
       break;
     default:
-      switch (input_mode) {
+      switch (app_state.input_mode) {
       case Tui_input_mode_normal:
       case Tui_input_mode_append: {
         if (key >= '!' && key <= '~') {
-          undo_history_push(&undo_hist, &field, tick_num);
-          gbuffer_poke(field.buffer, field.height, field.width, tui_cursor.y,
-                       tui_cursor.x, (char)key);
-          // Indicate we want the next simulation step to be run predictavely,
-          // so that we can use the reulsting mark buffer for UI visualization.
-          // This is "expensive", so it could be skipped for non-interactive
-          // input in situations where max throughput is necessary.
-          needs_remarking = true;
-          if (input_mode == Tui_input_mode_append) {
-            tui_cursor_move_relative(&tui_cursor, field.height, field.width, 0,
-                                     1);
-          }
+          app_write_character(&app_state, (char)key);
         }
       } break;
       case Tui_input_mode_piano: {
         if (key >= '!' && key <= '~') {
-          Piano_bits added_bits = piano_bits_of((Glyph)key);
-          piano_bits |= added_bits;
+          app_add_piano_bits_for_character(&app_state, (char)key);
         }
       } break;
       }
@@ -829,12 +937,6 @@
     delwin(cont_win);
   }
   endwin();
-  markmap_reusable_deinit(&markmap_r);
-  bank_deinit(&bank);
-  field_deinit(&field);
-  field_deinit(&scratch_field);
-  undo_history_deinit(&undo_hist);
-  oevent_list_deinit(&oevent_list);
-  oevent_list_deinit(&scratch_oevent_list);
+  app_deinit(&app_state);
   return 0;
 }