Browse Source

Add grid scrolling and drawing cursor selection

master
cancel 6 years ago
parent
commit
bda14805d7
  1. 303
      tui_main.c

303
tui_main.c

@ -196,30 +196,119 @@ void tui_cursor_move_relative(Tui_cursor* tc, Usz field_h, Usz field_w,
tc->x = (Usz)x0;
}
void tdraw_tui_cursor(WINDOW* win, int win_h, int win_w, Glyph const* gbuffer,
Usz field_h, Usz field_w, Usz ruler_spacing_y,
Usz ruler_spacing_x, Usz cursor_y, Usz cursor_x,
Usz cursor_h, Usz cursor_w, Tui_input_mode input_mode,
bool is_playing) {
(void)ruler_spacing_y;
(void)ruler_spacing_x;
(void)cursor_h;
(void)cursor_w;
void tdraw_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,
Tui_input_mode input_mode, bool is_playing) {
(void)input_mode;
if (cursor_y >= field_h || cursor_x >= field_w || (int)cursor_y >= win_h ||
(int)cursor_x >= win_w)
if (cursor_y >= field_h || cursor_x >= field_w)
return;
Glyph beneath = gbuffer[cursor_y * field_w + cursor_x];
char displayed;
if (beneath == '.') {
displayed = is_playing ? '@' : '~';
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;
int 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 {
displayed = beneath;
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)((int)(chbuffer[ix] & A_CHARTEXT) | curs_attr);
}
waddchnstr(win, chbuffer, (int)num);
}
chtype ch =
(chtype)(displayed | (A_reverse | A_bold | fg_bg(C_yellow, C_natural)));
wmove(win, (int)cursor_y, (int)cursor_x);
waddchnstr(win, &ch, 1);
}
typedef struct Undo_node {
@ -335,45 +424,66 @@ void tdraw_hud(WINDOW* win, int win_y, int win_x, int height, int width,
wclrtoeol(win);
}
void tdraw_field(WINDOW* win, int term_h, int term_w, int pos_y, int pos_x,
Glyph const* gbuffer, Mark const* mbuffer, Usz field_h,
Usz field_w, Usz ruler_spacing_y, Usz ruler_spacing_x) {
void tdraw_glyphs_grid(WINDOW* win, int draw_y, int draw_x, int draw_h,
int draw_w, Glyph const* restrict gbuffer,
Mark const* restrict mbuffer, Usz field_h, Usz field_w,
Usz offset_y, Usz offset_x, Usz ruler_spacing_y,
Usz ruler_spacing_x) {
assert(draw_y >= 0 && draw_x >= 0);
assert(draw_h >= 0 && draw_w >= 0);
enum { Bufcount = 4096 };
if (field_w > Bufcount)
chtype chbuffer[Bufcount];
// todo buffer limit
if (offset_y >= field_h || offset_x >= field_w)
return;
if (draw_y >= draw_h || draw_x >= draw_w)
return;
if (pos_y >= term_h || pos_x >= term_w)
Usz rows = (Usz)(draw_h - draw_y);
if (field_h - offset_y < rows)
rows = field_h - offset_y;
Usz cols = (Usz)(draw_w - draw_x);
if (field_w - offset_x < cols)
cols = field_w - offset_x;
if (rows == 0 || cols == 0)
return;
Usz num_y = (Usz)term_h - (Usz)pos_y;
Usz num_x = (Usz)term_w - (Usz)pos_x;
if (field_h < num_y)
num_y = field_h;
if (field_w < num_x)
num_x = field_w;
chtype buffer[Bufcount];
bool use_rulers = ruler_spacing_y != 0 && ruler_spacing_x != 0;
for (Usz y = 0; y < num_y; ++y) {
Glyph const* gline = gbuffer + y * field_w;
Mark const* mline = mbuffer + y * field_w;
bool use_y_ruler = use_rulers && y % ruler_spacing_y == 0;
for (Usz x = 0; x < num_x; ++x) {
Glyph g = gline[x];
Mark m = mline[x];
(void)use_rulers;
for (Usz iy = 0; iy < rows; ++iy) {
Usz line_offset = (offset_y + iy) * field_w + offset_x;
Glyph const* g_row = gbuffer + line_offset;
Mark const* m_row = mbuffer + line_offset;
bool use_y_ruler = use_rulers && (iy + offset_y) % ruler_spacing_y == 0;
for (Usz ix = 0; ix < cols; ++ix) {
Glyph g = g_row[ix];
Mark m = m_row[ix];
int attrs = term_attrs_of_cell(g, m);
if (g == '.') {
if (use_y_ruler && x % ruler_spacing_x == 0)
if (use_y_ruler && (ix + offset_x) % ruler_spacing_x == 0)
g = '+';
}
buffer[x] = (chtype)((int)g | attrs);
}
wmove(win, pos_y + (int)y, pos_x);
waddchnstr(win, buffer, (int)num_x);
// Trying to clear to eol with 0 chars remaining on line will clear whole
// line from start
if (pos_x + (int)num_x != term_w) {
wmove(win, pos_y + (int)y, pos_x + (int)num_x);
wclrtoeol(win);
chbuffer[ix] = (chtype)((int)g | attrs);
}
wmove(win, draw_y + (int)iy, draw_x);
waddchnstr(win, chbuffer, (int)cols);
}
}
void tdraw_glyphs_grid_scrolled(WINDOW* win, int draw_y, int draw_x, int draw_h,
int draw_w, Glyph const* restrict gbuffer,
Mark const* restrict mbuffer, Usz field_h,
Usz field_w, int scroll_y, int scroll_x,
Usz ruler_spacing_y, Usz ruler_spacing_x) {
if (scroll_y < 0) {
draw_y += -scroll_y;
scroll_y = 0;
}
if (scroll_x < 0) {
draw_x += -scroll_x;
scroll_x = 0;
}
tdraw_glyphs_grid(win, draw_y, draw_x, draw_h, draw_w, gbuffer, mbuffer,
field_h, field_w, (Usz)scroll_y, (Usz)scroll_x,
ruler_spacing_y, ruler_spacing_x);
}
void tui_cursor_confine(Tui_cursor* tc, Usz height, Usz width) {
@ -518,6 +628,8 @@ typedef struct {
char const* filename;
Oosc_dev* oosc_dev;
Midi_mode const* midi_mode;
int grid_scroll_y;
int grid_scroll_x;
bool needs_remarking;
bool is_draw_dirty;
bool is_playing;
@ -546,6 +658,8 @@ void app_init(App_state* a) {
a->filename = NULL;
a->oosc_dev = NULL;
a->midi_mode = NULL;
a->grid_scroll_y = 0;
a->grid_scroll_x = 0;
a->needs_remarking = true;
a->is_draw_dirty = false;
a->is_playing = false;
@ -775,6 +889,7 @@ static double ms_to_sec(double ms) { return ms / 1000.0; }
void app_force_draw_dirty(App_state* a) { a->is_draw_dirty = true; }
void app_draw(App_state* 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
@ -804,17 +919,14 @@ void app_draw(App_state* a, WINDOW* win) {
int hud_height = 2;
bool draw_hud = win_h > hud_height + 1;
int grid_h = draw_hud ? win_h - 2 : win_h;
tdraw_field(win, grid_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, grid_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->tui_cursor.h,
a->tui_cursor.w, a->input_mode, a->is_playing);
tdraw_glyphs_grid_scrolled(win, 0, 0, grid_h, win_w, a->field.buffer,
a->markmap_r.buffer, a->field.height,
a->field.width, a->grid_scroll_y, a->grid_scroll_x,
a->ruler_spacing_y, a->ruler_spacing_x);
tdraw_grid_cursor(win, 0, 0, grid_h, win_w, a->field.buffer, a->field.height,
a->field.width, a->grid_scroll_y, a->grid_scroll_x,
a->tui_cursor.y, a->tui_cursor.x, a->tui_cursor.h,
a->tui_cursor.w, a->input_mode, a->is_playing);
if (draw_hud) {
char const* filename = a->filename ? a->filename : "";
tdraw_hud(win, win_h - hud_height, 0, hud_height, win_w, filename,
@ -841,6 +953,41 @@ void app_adjust_bpm(App_state* a, Isz delta_bpm) {
}
}
#if 0
int scroll_offset_on_axis_for_visible_index(int win_len, int cont_len, int pos,
int pad) {
assert(win_len >= 1 && cont_len >= 1 && pos >= 0 && pad >= 0);
if (win_len < 1 || cont_len < 1 || pos < 0 || pad < 0)
return 0;
if (cont_len <= win_len) return 0;
if (pad * 2 >= win_len)
pad = (win_len - 1) / 2;
//if (pos + pad >
}
int padded_scrollguy(int win_len, int cont_len, int vis_target, int cur_scroll, int pad) {
}
void scroll_offset_for_visible_cell(int win_h, int win_w, int cont_h,
int cont_w, int pos_y, int pos_x, int pad_y,
int pad_x, int* out_y, int* out_x) {
assert(win_h >= 1 && win_w >= 1 && cont_h >= 1 && cont_w >= 1 && pad_y >= 0 &&
pad_x >= 0 && pos_y >= 0 && pos_x >= 0);
if (win_h < 1 || win_w < 1 || cont_h < 1 || cont_w < 1 || pad_y < 0 ||
pad_x < 0 || pos_x < 0 || pos_y < 0) {
*out_y = 0;
*out_x = 0;
return;
}
if (pad_y * 2 >= win_h) {
pad_y = (win_h - 1) / 2;
}
if (pad_x * 2 >= win_x) {
pad_x = (win_x - 1) / 2;
}
}
#endif
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);
@ -1338,6 +1485,42 @@ int main(int argc, char** argv) {
// to play being applied as actual playback time
stm_laptime(&last_time);
break;
case KEY_F(1):
app_state.grid_scroll_x -= 1;
app_state.is_draw_dirty = true;
break;
case KEY_F(2):
app_state.grid_scroll_x += 1;
app_state.is_draw_dirty = true;
break;
case KEY_F(3):
app_state.grid_scroll_y -= 1;
app_state.is_draw_dirty = true;
break;
case KEY_F(4):
app_state.grid_scroll_y += 1;
app_state.is_draw_dirty = true;
break;
case KEY_F(5):
if (app_state.tui_cursor.w > 0) {
--app_state.tui_cursor.w;
app_state.is_draw_dirty = true;
}
break;
case KEY_F(6):
++app_state.tui_cursor.w;
app_state.is_draw_dirty = true;
break;
case KEY_F(7):
if (app_state.tui_cursor.h > 0) {
--app_state.tui_cursor.h;
app_state.is_draw_dirty = true;
}
break;
case KEY_F(8):
++app_state.tui_cursor.h;
app_state.is_draw_dirty = true;
break;
default:
if (key >= '!' && key <= '~') {
app_input_character(&app_state, (char)key);

Loading…
Cancel
Save