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