#include "base.h"#include "field.h"#include "gbuffer.h"#include "osc_out.h"#include "oso.h"#include "sim.h"#include "sysmisc.h"#include "term_util.h"#include "vmio.h"#include <getopt.h>#include <locale.h> #define SOKOL_IMPL#include "sokol_time.h"#undef SOKOL_IMPL #ifdef FEAT_PORTMIDI#include <portmidi.h>#endif #if NCURSES_VERSION_PATCH < 20081122int _nc_has_mouse(void);#define has_mouse _nc_has_mouse#endif #define TIME_DEBUG 0#if TIME_DEBUGstatic int spin_track_timeout = 0;#endif #define staticni ORCA_NOINLINE static staticni void usage(void) { // clang-format offfprintf(stderr,"Usage: orca [options] [file]\n\n""General options:\n"" --undo-limit <number> Set the maximum number of undo steps.\n"" If you plan to work with large files,\n"" set this to a low number.\n"" Default: 100\n"" --initial-size <nxn> When creating a new grid file, use these\n"" starting dimensions.\n"" --bpm <number> Set the tempo (beats per minute).\n"" Default: 120\n"" --seed <number> Set the seed for the random function.\n"" Default: 1\n"" -h or --help Print this message and exit.\n""\n""OSC/MIDI options:\n"" --strict-timing\n"" Attempt to reduce timing jitter of outgoing MIDI and OSC\n"" messages. Uses more CPU time. May have no effect.\n""\n"" --osc-midi-bidule <path>\n"" Set MIDI to be sent via OSC formatted for Plogue Bidule.\n"" The path argument is the path of the Plogue OSC MIDI device.\n"" Example: /OSC_MIDI_0/MIDI\n");} // clang-format on typedef enum { Glyph_class_unknown, Glyph_class_grid, Glyph_class_comment, Glyph_class_uppercase, Glyph_class_lowercase, Glyph_class_movement, Glyph_class_numeric, Glyph_class_bang,} Glyph_class; static Glyph_class glyph_class_of(Glyph glyph) { if (glyph == '.') return Glyph_class_grid; if (glyph >= '0' && glyph <= '9') return Glyph_class_numeric; switch (glyph) { case 'N': case 'n': case 'E': case 'e': case 'S': case 's': case 'W': case 'w': return Glyph_class_movement; case '!': case ':': case ';': case '=': case '%': case '?': case '$': return Glyph_class_lowercase; case '*': return Glyph_class_bang; case '#': return Glyph_class_comment; } if (glyph >= 'A' && glyph <= 'Z') return Glyph_class_uppercase; if (glyph >= 'a' && glyph <= 'z') return Glyph_class_lowercase; return Glyph_class_unknown;} static attr_t term_attrs_of_cell(Glyph g, Mark m) { Glyph_class gclass = glyph_class_of(g); attr_t attr = A_normal; switch (gclass) { case Glyph_class_unknown: attr = A_bold | fg_bg(C_red, C_natural); break; case Glyph_class_grid: attr = A_bold | fg_bg(C_black, C_natural); break; case Glyph_class_comment: attr = A_dim | Cdef_normal; break; case Glyph_class_uppercase: attr = A_normal | fg_bg(C_black, C_cyan); break; case Glyph_class_lowercase: case Glyph_class_movement: case Glyph_class_numeric: attr = A_bold | Cdef_normal; break; case Glyph_class_bang: attr = A_bold | Cdef_normal; break; } if (gclass != Glyph_class_comment) { if ((m & (Mark_flag_lock | Mark_flag_input)) == (Mark_flag_lock | Mark_flag_input)) { // Standard locking input attr = A_normal | Cdef_normal; } else if ((m & Mark_flag_input) == Mark_flag_input) { // Non-locking input attr = A_normal | Cdef_normal; } else if (m & Mark_flag_lock) { // Locked only attr = A_dim | Cdef_normal; } } if (m & Mark_flag_output) { attr = A_reverse; } if (m & Mark_flag_haste_input) { attr = A_bold | fg_bg(C_cyan, C_natural); } return attr;} typedef enum { Ged_input_mode_normal = 0, Ged_input_mode_append, Ged_input_mode_selresize, Ged_input_mode_slide,} Ged_input_mode; typedef struct { Usz y, x, h, w;} Ged_cursor; void ged_cursor_init(Ged_cursor *tc) { tc->x = tc->y = 0; tc->w = tc->h = 1;} static void ged_cursor_move_relative(Ged_cursor *tc, Usz field_h, Usz field_w, Isz delta_y, Isz delta_x) { Isz y0 = (Isz)tc->y + delta_y; Isz x0 = (Isz)tc->x + delta_x; if (y0 >= (Isz)field_h) y0 = (Isz)field_h - 1; if (y0 < 0) y0 = 0; if (x0 >= (Isz)field_w) x0 = (Isz)field_w - 1; if (x0 < 0) x0 = 0; tc->y = (Usz)y0; tc->x = (Usz)x0;} staticni void draw_grid_cursor(WINDOW *win, int draw_y, int draw_x, int draw_h, int draw_w, Glyph const *gbuffer, Usz field_h, Usz field_w, int scroll_y, int scroll_x, Usz cursor_y, Usz cursor_x, Usz cursor_h, Usz cursor_w, Ged_input_mode input_mode, bool is_playing) { (void)input_mode; if (cursor_y >= field_h || cursor_x >= field_w) return; if (scroll_y < 0) { draw_y += -scroll_y; scroll_y = 0; } if (scroll_x < 0) { draw_x += -scroll_x; scroll_x = 0; } Usz offset_y = (Usz)scroll_y; Usz offset_x = (Usz)scroll_x; if (offset_y >= field_h || offset_x >= field_w) return; if (draw_y >= draw_h || draw_x >= draw_w) return; attr_t const curs_attr = A_reverse | A_bold | fg_bg(C_yellow, C_natural); if (offset_y <= cursor_y && offset_x <= cursor_x) { Usz cdraw_y = cursor_y - offset_y + (Usz)draw_y; Usz cdraw_x = cursor_x - offset_x + (Usz)draw_x; if (cdraw_y < (Usz)draw_h && cdraw_x < (Usz)draw_w) { Glyph beneath = gbuffer[cursor_y * field_w + cursor_x]; char displayed; if (beneath == '.') { displayed = is_playing ? '@' : '~'; } else { displayed = beneath; } chtype ch = (chtype)displayed | curs_attr; wmove(win, (int)cdraw_y, (int)cdraw_x); waddchnstr(win, &ch, 1); } } // Early out for selection area that won't have any visual effect if (cursor_h <= 1 && cursor_w <= 1) return; // Now mutate visually selected area under grid to have the selection color // attributes. (This will rewrite the attributes on the cursor character we // wrote above, but if it was the only character that would have been // changed, we already early-outed.) // // We'll do this by reading back the characters on the grid from the curses // window buffer, changing the attributes, then writing it back. This is // easier than pulling the glyphs from the gbuffer, since we already did the // ruler calculations to turn . into +, and we don't need special behavior // for any other attributes (e.g. we don't show a special state for selected // uppercase characters.) // // First, confine cursor selection to the grid field/gbuffer that actually // exists, in case the cursor selection exceeds the area of the field. Usz sel_rows = field_h - cursor_y; if (cursor_h < sel_rows) sel_rows = cursor_h; Usz sel_cols = field_w - cursor_x; if (cursor_w < sel_cols) sel_cols = cursor_w; // Now, confine the selection area to what's visible on screen. Kind of // tricky since we have to handle it being partially visible from any edge on // any axis, and we have to be mindful overflow. Usz vis_sel_y; Usz vis_sel_x; if (offset_y > cursor_y) { vis_sel_y = 0; Usz sub_y = offset_y - cursor_y; if (sub_y > sel_rows) sel_rows = 0; else sel_rows -= sub_y; } else { vis_sel_y = cursor_y - offset_y; } if (offset_x > cursor_x) { vis_sel_x = 0; Usz sub_x = offset_x - cursor_x; if (sub_x > sel_cols) sel_cols = 0; else sel_cols -= sub_x; } else { vis_sel_x = cursor_x - offset_x; } vis_sel_y += (Usz)draw_y; vis_sel_x += (Usz)draw_x; if (vis_sel_y >= (Usz)draw_h || vis_sel_x >= (Usz)draw_w) return; Usz vis_sel_h = (Usz)draw_h - vis_sel_y; Usz vis_sel_w = (Usz)draw_w - vis_sel_x; if (sel_rows < vis_sel_h) vis_sel_h = sel_rows; if (sel_cols < vis_sel_w) vis_sel_w = sel_cols; if (vis_sel_w == 0 || vis_sel_h == 0) return; enum { Bufcount = 4096 }; chtype chbuffer[Bufcount]; if (Bufcount < vis_sel_w) vis_sel_w = Bufcount; for (Usz iy = 0; iy < vis_sel_h; ++iy) { int at_y = (int)(vis_sel_y + iy); int num = mvwinchnstr(win, at_y, (int)vis_sel_x, chbuffer, (int)vis_sel_w); for (int ix = 0; ix < num; ++ix) { chbuffer[ix] = (chtype)((chbuffer[ix] & (A_CHARTEXT | A_ALTCHARSET)) | (chtype)curs_attr); } waddchnstr(win, chbuffer, (int)num); }} typedef struct Undo_node { Field field; Usz tick_num; struct Undo_node *prev, *next;} Undo_node; typedef struct { Undo_node *first, *last; Usz count, limit;} Undo_history; static void undo_history_init(Undo_history *hist, Usz limit) { *hist = (Undo_history){0}; hist->limit = limit;}static void undo_history_deinit(Undo_history *hist) { Undo_node *a = hist->first; while (a) { Undo_node *b = a->next; field_deinit(&a->field); free(a); a = b; }} staticni bool undo_history_push(Undo_history *hist, Field *field, Usz tick_num) { if (hist->limit == 0) return false; Undo_node *new_node; if (hist->count == hist->limit) { new_node = hist->first; if (new_node == hist->last) { hist->first = NULL; hist->last = NULL; } else { hist->first = new_node->next; hist->first->prev = NULL; } } else { new_node = malloc(sizeof(Undo_node)); if (!new_node) return false; ++hist->count; field_init(&new_node->field); } field_copy(field, &new_node->field); new_node->tick_num = tick_num; if (hist->last) { hist->last->next = new_node; new_node->prev = hist->last; } else { hist->first = new_node; hist->last = new_node; new_node->prev = NULL; } new_node->next = NULL; hist->last = new_node; return true;} staticni void undo_history_pop(Undo_history *hist, Field *out_field, Usz *out_tick_num) { Undo_node *last = hist->last; if (!last) return; field_copy(&last->field, out_field); *out_tick_num = last->tick_num; if (hist->first == last) { hist->first = NULL; hist->last = NULL; } else { Undo_node *new_last = last->prev; new_last->next = NULL; hist->last = new_last; } field_deinit(&last->field); free(last); --hist->count;} staticni void undo_history_apply(Undo_history *hist, Field *out_field, Usz *out_tick_num) { Undo_node *last = hist->last; if (!last) return; field_copy(&last->field, out_field); *out_tick_num = last->tick_num;} static Usz undo_history_count(Undo_history *hist) { return hist->count; } staticni void print_activity_indicator(WINDOW *win, Usz activity_counter) { // 7 segments that can each light up as Colors different colors. // This gives us Colors^Segments total configurations. enum { Segments = 7, Colors = 4 }; Usz states = 1; // calculate Colors^Segments for (Usz i = 0; i < Segments; ++i) states *= Colors; // Wrap the counter to the range of displayable configurations. Usz val = activity_counter % states; chtype lamps[Colors];#if 1 // Appearance where segments are always lit lamps[0] = ACS_HLINE | fg_bg(C_black, C_natural) | A_bold; lamps[1] = ACS_HLINE | fg_bg(C_white, C_natural) | A_normal; lamps[2] = ACS_HLINE | A_bold; lamps[3] = lamps[1];#elif 0 // Brighter appearance where segments are always lit lamps[0] = ACS_HLINE | fg_bg(C_black, C_natural) | A_bold; lamps[1] = ACS_HLINE | A_normal; lamps[2] = ACS_HLINE | A_bold; lamps[3] = lamps[1];#else // Appearance where segments can turn off completely lamps[0] = ' '; lamps[1] = ACS_HLINE | fg_bg(C_black, C_natural) | A_bold; lamps[2] = ACS_HLINE | A_normal; lamps[3] = lamps[1];#endif chtype buffer[Segments]; for (Usz i = 0; i < Segments; ++i) { // Instead of a left-to-right, straightforward ascending least-to-most // significant digits display, we'll display it as a spiral. Usz j = i % 2 ? (6 - i / 2) : (i / 2); buffer[j] = lamps[val % Colors]; val = val / Colors; } waddchnstr(win, buffer, Segments); // If you want to see what various combinations of colors and attributes look // like in different terminals.#if 0 waddch(win, 'a' | fg_bg(C_black, C_natural) | A_dim); waddch(win, 'b' | fg_bg(C_black, C_natural) | A_normal); waddch(win, 'c' | fg_bg(C_black, C_natural) | A_bold); waddch(win, 'd' | A_dim); waddch(win, 'e' | A_normal); waddch(win, 'f' | A_bold); waddch(win, 'g' | fg_bg(C_white, C_natural) | A_dim); waddch(win, 'h' | fg_bg(C_white, C_natural) | A_normal); waddch(win, 'i' | fg_bg(C_white, C_natural) | A_bold);#endif} staticni void advance_faketab(WINDOW *win, int offset_x, int tabstop) { if (tabstop < 1) return; int y, x, h, w; getyx(win, y, x); getmaxyx(win, h, w); (void)h; x = ((x + tabstop - 1) / tabstop) * tabstop + offset_x % tabstop; if (w < 1) w = 1; if (x >= w) x = w - 1; wmove(win, y, x);} staticni void draw_hud(WINDOW *win, int win_y, int win_x, int height, int width, char const *filename, Usz field_h, Usz field_w, Usz ruler_spacing_y, Usz ruler_spacing_x, Usz tick_num, Usz bpm, Ged_cursor const *ged_cursor, Ged_input_mode input_mode, Usz activity_counter) { (void)height; (void)width; enum { Tabstop = 8 }; wmove(win, win_y, win_x); wprintw(win, "%zux%zu", field_w, field_h); advance_faketab(win, win_x, Tabstop); wprintw(win, "%zu/%zu", ruler_spacing_x, ruler_spacing_y); advance_faketab(win, win_x, Tabstop); wprintw(win, "%zuf", tick_num); advance_faketab(win, win_x, Tabstop); wprintw(win, "%zu", bpm); advance_faketab(win, win_x, Tabstop); print_activity_indicator(win, activity_counter); wmove(win, win_y + 1, win_x); wprintw(win, "%zu,%zu", ged_cursor->x, ged_cursor->y); advance_faketab(win, win_x, Tabstop); wprintw(win, "%zu:%zu", ged_cursor->w, ged_cursor->h); advance_faketab(win, win_x, Tabstop); switch (input_mode) { case Ged_input_mode_normal: wattrset(win, A_normal); waddstr(win, "insert"); break; case Ged_input_mode_append: wattrset(win, A_bold); waddstr(win, "append"); break; case Ged_input_mode_selresize: wattrset(win, A_bold); waddstr(win, "select"); break; case Ged_input_mode_slide: wattrset(win, A_reverse); waddstr(win, "slide"); break; } advance_faketab(win, win_x, Tabstop);Lines 1–500 of 3884. Load more
#include "base.h" #include "field.h" #include "gbuffer.h" #include "osc_out.h" #include "oso.h" #include "sim.h" #include "sysmisc.h" #include "term_util.h" #include "vmio.h" #include <getopt.h> #include <locale.h> #define SOKOL_IMPL #include "sokol_time.h" #undef SOKOL_IMPL #ifdef FEAT_PORTMIDI #include <portmidi.h> #endif #if NCURSES_VERSION_PATCH < 20081122 int _nc_has_mouse(void); #define has_mouse _nc_has_mouse #endif #define TIME_DEBUG 0 #if TIME_DEBUG static int spin_track_timeout = 0; #endif #define staticni ORCA_NOINLINE static staticni void usage(void) { // clang-format off fprintf(stderr, "Usage: orca [options] [file]\n\n" "General options:\n" " --undo-limit <number> Set the maximum number of undo steps.\n" " If you plan to work with large files,\n" " set this to a low number.\n" " Default: 100\n" " --initial-size <nxn> When creating a new grid file, use these\n" " starting dimensions.\n" " --bpm <number> Set the tempo (beats per minute).\n" " Default: 120\n" " --seed <number> Set the seed for the random function.\n" " Default: 1\n" " -h or --help Print this message and exit.\n" "\n" "OSC/MIDI options:\n" " --strict-timing\n" " Attempt to reduce timing jitter of outgoing MIDI and OSC\n" " messages. Uses more CPU time. May have no effect.\n" "\n" " --osc-midi-bidule <path>\n" " Set MIDI to be sent via OSC formatted for Plogue Bidule.\n" " The path argument is the path of the Plogue OSC MIDI device.\n" " Example: /OSC_MIDI_0/MIDI\n" );} // clang-format on typedef enum { Glyph_class_unknown, Glyph_class_grid, Glyph_class_comment, Glyph_class_uppercase, Glyph_class_lowercase, Glyph_class_movement, Glyph_class_numeric, Glyph_class_bang, } Glyph_class; static Glyph_class glyph_class_of(Glyph glyph) { if (glyph == '.') return Glyph_class_grid; if (glyph >= '0' && glyph <= '9') return Glyph_class_numeric; switch (glyph) { case 'N': case 'n': case 'E': case 'e': case 'S': case 's': case 'W': case 'w': return Glyph_class_movement; case '!': case ':': case ';': case '=': case '%': case '?': case '$': return Glyph_class_lowercase; case '*': return Glyph_class_bang; case '#': return Glyph_class_comment; } if (glyph >= 'A' && glyph <= 'Z') return Glyph_class_uppercase; if (glyph >= 'a' && glyph <= 'z') return Glyph_class_lowercase; return Glyph_class_unknown; } static attr_t term_attrs_of_cell(Glyph g, Mark m) { Glyph_class gclass = glyph_class_of(g); attr_t attr = A_normal; switch (gclass) { case Glyph_class_unknown: attr = A_bold | fg_bg(C_red, C_natural); break; case Glyph_class_grid: attr = A_bold | fg_bg(C_black, C_natural); break; case Glyph_class_comment: attr = A_dim | Cdef_normal; break; case Glyph_class_uppercase: attr = A_normal | fg_bg(C_black, C_cyan); break; case Glyph_class_lowercase: case Glyph_class_movement: case Glyph_class_numeric: attr = A_bold | Cdef_normal; break; case Glyph_class_bang: attr = A_bold | Cdef_normal; break; } if (gclass != Glyph_class_comment) { if ((m & (Mark_flag_lock | Mark_flag_input)) == (Mark_flag_lock | Mark_flag_input)) { // Standard locking input attr = A_normal | Cdef_normal; } else if ((m & Mark_flag_input) == Mark_flag_input) { // Non-locking input attr = A_normal | Cdef_normal; } else if (m & Mark_flag_lock) { // Locked only attr = A_dim | Cdef_normal; } } if (m & Mark_flag_output) { attr = A_reverse; } if (m & Mark_flag_haste_input) { attr = A_bold | fg_bg(C_cyan, C_natural); } return attr; } typedef enum { Ged_input_mode_normal = 0, Ged_input_mode_append, Ged_input_mode_selresize, Ged_input_mode_slide, } Ged_input_mode; typedef struct { Usz y, x, h, w; } Ged_cursor; void ged_cursor_init(Ged_cursor *tc) { tc->x = tc->y = 0; tc->w = tc->h = 1; } static void ged_cursor_move_relative(Ged_cursor *tc, Usz field_h, Usz field_w, Isz delta_y, Isz delta_x) { Isz y0 = (Isz)tc->y + delta_y; Isz x0 = (Isz)tc->x + delta_x; if (y0 >= (Isz)field_h) y0 = (Isz)field_h - 1; if (y0 < 0) y0 = 0; if (x0 >= (Isz)field_w) x0 = (Isz)field_w - 1; if (x0 < 0) x0 = 0; tc->y = (Usz)y0; tc->x = (Usz)x0; } staticni void draw_grid_cursor(WINDOW *win, int draw_y, int draw_x, int draw_h, int draw_w, Glyph const *gbuffer, Usz field_h, Usz field_w, int scroll_y, int scroll_x, Usz cursor_y, Usz cursor_x, Usz cursor_h, Usz cursor_w, Ged_input_mode input_mode, bool is_playing) { (void)input_mode; if (cursor_y >= field_h || cursor_x >= field_w) return; if (scroll_y < 0) { draw_y += -scroll_y; scroll_y = 0; } if (scroll_x < 0) { draw_x += -scroll_x; scroll_x = 0; } Usz offset_y = (Usz)scroll_y; Usz offset_x = (Usz)scroll_x; if (offset_y >= field_h || offset_x >= field_w) return; if (draw_y >= draw_h || draw_x >= draw_w) return; attr_t const curs_attr = A_reverse | A_bold | fg_bg(C_yellow, C_natural); if (offset_y <= cursor_y && offset_x <= cursor_x) { Usz cdraw_y = cursor_y - offset_y + (Usz)draw_y; Usz cdraw_x = cursor_x - offset_x + (Usz)draw_x; if (cdraw_y < (Usz)draw_h && cdraw_x < (Usz)draw_w) { Glyph beneath = gbuffer[cursor_y * field_w + cursor_x]; char displayed; if (beneath == '.') { displayed = is_playing ? '@' : '~'; } else { displayed = beneath; } chtype ch = (chtype)displayed | curs_attr; wmove(win, (int)cdraw_y, (int)cdraw_x); waddchnstr(win, &ch, 1); } } // Early out for selection area that won't have any visual effect if (cursor_h <= 1 && cursor_w <= 1) return; // Now mutate visually selected area under grid to have the selection color // attributes. (This will rewrite the attributes on the cursor character we // wrote above, but if it was the only character that would have been // changed, we already early-outed.) // // We'll do this by reading back the characters on the grid from the curses // window buffer, changing the attributes, then writing it back. This is // easier than pulling the glyphs from the gbuffer, since we already did the // ruler calculations to turn . into +, and we don't need special behavior // for any other attributes (e.g. we don't show a special state for selected // uppercase characters.) // // First, confine cursor selection to the grid field/gbuffer that actually // exists, in case the cursor selection exceeds the area of the field. Usz sel_rows = field_h - cursor_y; if (cursor_h < sel_rows) sel_rows = cursor_h; Usz sel_cols = field_w - cursor_x; if (cursor_w < sel_cols) sel_cols = cursor_w; // Now, confine the selection area to what's visible on screen. Kind of // tricky since we have to handle it being partially visible from any edge on // any axis, and we have to be mindful overflow. Usz vis_sel_y; Usz vis_sel_x; if (offset_y > cursor_y) { vis_sel_y = 0; Usz sub_y = offset_y - cursor_y; if (sub_y > sel_rows) sel_rows = 0; else sel_rows -= sub_y; } else { vis_sel_y = cursor_y - offset_y; } if (offset_x > cursor_x) { vis_sel_x = 0; Usz sub_x = offset_x - cursor_x; if (sub_x > sel_cols) sel_cols = 0; else sel_cols -= sub_x; } else { vis_sel_x = cursor_x - offset_x; } vis_sel_y += (Usz)draw_y; vis_sel_x += (Usz)draw_x; if (vis_sel_y >= (Usz)draw_h || vis_sel_x >= (Usz)draw_w) return; Usz vis_sel_h = (Usz)draw_h - vis_sel_y; Usz vis_sel_w = (Usz)draw_w - vis_sel_x; if (sel_rows < vis_sel_h) vis_sel_h = sel_rows; if (sel_cols < vis_sel_w) vis_sel_w = sel_cols; if (vis_sel_w == 0 || vis_sel_h == 0) return; enum { Bufcount = 4096 }; chtype chbuffer[Bufcount]; if (Bufcount < vis_sel_w) vis_sel_w = Bufcount; for (Usz iy = 0; iy < vis_sel_h; ++iy) { int at_y = (int)(vis_sel_y + iy); int num = mvwinchnstr(win, at_y, (int)vis_sel_x, chbuffer, (int)vis_sel_w); for (int ix = 0; ix < num; ++ix) { chbuffer[ix] = (chtype)((chbuffer[ix] & (A_CHARTEXT | A_ALTCHARSET)) | (chtype)curs_attr); } waddchnstr(win, chbuffer, (int)num); } } typedef struct Undo_node { Field field; Usz tick_num; struct Undo_node *prev, *next; } Undo_node; typedef struct { Undo_node *first, *last; Usz count, limit; } Undo_history; static void undo_history_init(Undo_history *hist, Usz limit) { *hist = (Undo_history){0}; hist->limit = limit; } static void undo_history_deinit(Undo_history *hist) { Undo_node *a = hist->first; while (a) { Undo_node *b = a->next; field_deinit(&a->field); free(a); a = b; } } staticni bool undo_history_push(Undo_history *hist, Field *field, Usz tick_num) { if (hist->limit == 0) return false; Undo_node *new_node; if (hist->count == hist->limit) { new_node = hist->first; if (new_node == hist->last) { hist->first = NULL; hist->last = NULL; } else { hist->first = new_node->next; hist->first->prev = NULL; } } else { new_node = malloc(sizeof(Undo_node)); if (!new_node) return false; ++hist->count; field_init(&new_node->field); } field_copy(field, &new_node->field); new_node->tick_num = tick_num; if (hist->last) { hist->last->next = new_node; new_node->prev = hist->last; } else { hist->first = new_node; hist->last = new_node; new_node->prev = NULL; } new_node->next = NULL; hist->last = new_node; return true; } staticni void undo_history_pop(Undo_history *hist, Field *out_field, Usz *out_tick_num) { Undo_node *last = hist->last; if (!last) return; field_copy(&last->field, out_field); *out_tick_num = last->tick_num; if (hist->first == last) { hist->first = NULL; hist->last = NULL; } else { Undo_node *new_last = last->prev; new_last->next = NULL; hist->last = new_last; } field_deinit(&last->field); free(last); --hist->count; } staticni void undo_history_apply(Undo_history *hist, Field *out_field, Usz *out_tick_num) { Undo_node *last = hist->last; if (!last) return; field_copy(&last->field, out_field); *out_tick_num = last->tick_num; } static Usz undo_history_count(Undo_history *hist) { return hist->count; } staticni void print_activity_indicator(WINDOW *win, Usz activity_counter) { // 7 segments that can each light up as Colors different colors. // This gives us Colors^Segments total configurations. enum { Segments = 7, Colors = 4 }; Usz states = 1; // calculate Colors^Segments for (Usz i = 0; i < Segments; ++i) states *= Colors; // Wrap the counter to the range of displayable configurations. Usz val = activity_counter % states; chtype lamps[Colors]; #if 1 // Appearance where segments are always lit lamps[0] = ACS_HLINE | fg_bg(C_black, C_natural) | A_bold; lamps[1] = ACS_HLINE | fg_bg(C_white, C_natural) | A_normal; lamps[2] = ACS_HLINE | A_bold; lamps[3] = lamps[1]; #elif 0 // Brighter appearance where segments are always lit lamps[0] = ACS_HLINE | fg_bg(C_black, C_natural) | A_bold; lamps[1] = ACS_HLINE | A_normal; lamps[2] = ACS_HLINE | A_bold; lamps[3] = lamps[1]; #else // Appearance where segments can turn off completely lamps[0] = ' '; lamps[1] = ACS_HLINE | fg_bg(C_black, C_natural) | A_bold; lamps[2] = ACS_HLINE | A_normal; lamps[3] = lamps[1]; #endif chtype buffer[Segments]; for (Usz i = 0; i < Segments; ++i) { // Instead of a left-to-right, straightforward ascending least-to-most // significant digits display, we'll display it as a spiral. Usz j = i % 2 ? (6 - i / 2) : (i / 2); buffer[j] = lamps[val % Colors]; val = val / Colors; } waddchnstr(win, buffer, Segments); // If you want to see what various combinations of colors and attributes look // like in different terminals. #if 0 waddch(win, 'a' | fg_bg(C_black, C_natural) | A_dim); waddch(win, 'b' | fg_bg(C_black, C_natural) | A_normal); waddch(win, 'c' | fg_bg(C_black, C_natural) | A_bold); waddch(win, 'd' | A_dim); waddch(win, 'e' | A_normal); waddch(win, 'f' | A_bold); waddch(win, 'g' | fg_bg(C_white, C_natural) | A_dim); waddch(win, 'h' | fg_bg(C_white, C_natural) | A_normal); waddch(win, 'i' | fg_bg(C_white, C_natural) | A_bold); #endif } staticni void advance_faketab(WINDOW *win, int offset_x, int tabstop) { if (tabstop < 1) return; int y, x, h, w; getyx(win, y, x); getmaxyx(win, h, w); (void)h; x = ((x + tabstop - 1) / tabstop) * tabstop + offset_x % tabstop; if (w < 1) w = 1; if (x >= w) x = w - 1; wmove(win, y, x); } staticni void draw_hud(WINDOW *win, int win_y, int win_x, int height, int width, char const *filename, Usz field_h, Usz field_w, Usz ruler_spacing_y, Usz ruler_spacing_x, Usz tick_num, Usz bpm, Ged_cursor const *ged_cursor, Ged_input_mode input_mode, Usz activity_counter) { (void)height; (void)width; enum { Tabstop = 8 }; wmove(win, win_y, win_x); wprintw(win, "%zux%zu", field_w, field_h); advance_faketab(win, win_x, Tabstop); wprintw(win, "%zu/%zu", ruler_spacing_x, ruler_spacing_y); advance_faketab(win, win_x, Tabstop); wprintw(win, "%zuf", tick_num); advance_faketab(win, win_x, Tabstop); wprintw(win, "%zu", bpm); advance_faketab(win, win_x, Tabstop); print_activity_indicator(win, activity_counter); wmove(win, win_y + 1, win_x); wprintw(win, "%zu,%zu", ged_cursor->x, ged_cursor->y); advance_faketab(win, win_x, Tabstop); wprintw(win, "%zu:%zu", ged_cursor->w, ged_cursor->h); advance_faketab(win, win_x, Tabstop); switch (input_mode) { case Ged_input_mode_normal: wattrset(win, A_normal); waddstr(win, "insert"); break; case Ged_input_mode_append: wattrset(win, A_bold); waddstr(win, "append"); break; case Ged_input_mode_selresize: wattrset(win, A_bold); waddstr(win, "select"); break; case Ged_input_mode_slide: wattrset(win, A_reverse); waddstr(win, "slide"); break; } advance_faketab(win, win_x, Tabstop);