shithub: orca

Download patch

ref: bda14805d755dff131f72099222ee88984658f92
parent: c2049c27466735380b7f752de3b680ec1cfefbf8
author: cancel <cancel@cancel.fm>
date: Mon Dec 10 15:41:25 EST 2018

Add grid scrolling and drawing cursor selection

--- a/tui_main.c
+++ b/tui_main.c
@@ -196,30 +196,119 @@
   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;
   }
-  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);
+  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);
+  }
 }
 
 typedef struct Undo_node {
@@ -335,47 +424,68 @@
   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 (pos_y >= term_h || pos_x >= term_w)
+  if (draw_y >= draw_h || draw_x >= draw_w)
     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];
+  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;
   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);
+      chbuffer[ix] = (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);
-    }
+    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) {
   if (height == 0 || width == 0)
     return;
@@ -518,6 +628,8 @@
   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 @@
   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 @@
 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 @@
   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 @@
   }
 }
 
+#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);
@@ -1337,6 +1484,42 @@
       // flush lap time -- quick hack to prevent time before hitting spacebar
       // 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 <= '~') {