Browse Source

Break tui app state out into struct

master
cancel 6 years ago
parent
commit
02660c70bf
  1. 394
      tui_main.c

394
tui_main.c

@ -455,6 +455,224 @@ void tui_resize_grid_snap_ruler(Field* field, Markmap_reusable* markmap,
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 @@ int main(int argc, char** argv) {
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 @@ int main(int argc, char** argv) {
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 main(int argc, char** argv) {
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,26 +827,8 @@ int main(int argc, char** argv) {
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
@ -694,121 +848,75 @@ int main(int argc, char** argv) {
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 @@ quit:
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;
}

Loading…
Cancel
Save