Support arrow-keys

Change-Id: I4cfd99001ea99888c8781e018f67c644d1d6e3d1
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/403597
Reviewed-by: Florin Malita <fmalita@chromium.org>
diff --git a/modules/canvaskit/npm_build/extra.html b/modules/canvaskit/npm_build/extra.html
index 834dbd0..ef5e1e5 100644
--- a/modules/canvaskit/npm_build/extra.html
+++ b/modules/canvaskit/npm_build/extra.html
@@ -32,7 +32,7 @@
 
 <h2> Paragraph </h2>
 <canvas id=para1 width=600 height=600></canvas>
-<canvas id=para2 width=600 height=600></canvas>
+<canvas id=para2 width=600 height=600 tabindex='-1'></canvas>
 
 <h2> CanvasKit can serialize/deserialize .skp files</h2>
 <canvas id=skp width=500 height=500></canvas>
@@ -395,7 +395,7 @@
 
       function mid(a, b) { return (a + b) * 0.5; }
 
-      function runs_pos_to_index(runs, x, y) {
+      function runs_x_to_index(runs, x) {
           for (const r of runs) {
               for (let i = 1; i < r.offsets.length; i += 1) {
                   if (x < r.positions[i*2]) {
@@ -417,7 +417,7 @@
           }
           for (const l of lines) {
               if (y <= l.bottom) {
-                  return runs_pos_to_index(l.runs, x, y);
+                  return runs_x_to_index(l.runs, x);
               }
           }
           return text.length;
@@ -439,15 +439,22 @@
                 return r.positions[i*2];
             }
         }
+        return null;
+    }
+
+    function lines_index_to_line_index(lines, index) {
+        let i = 0;
+        for (const l of lines) {
+            if (index <= l.textRange.last) {
+                return i;
+            }
+            i += 1;
+        }
+        return lines.length-1;
     }
 
     function lines_index_to_line(lines, index) {
-        for (const l of lines) {
-            if (index <= l.textRange.last) {
-                return l;
-            }
-        }
-        return lines[lines.length-1];
+        return lines[lines_index_to_line_index(lines, index)];
     }
 
     function lines_index_to_x(lines, index) {
@@ -481,7 +488,58 @@
           return path;
       }
 
-      let INDEX = 0;
+      let editor = {
+          _text: null,
+          _lines: null,
+          _cursor: null,
+          _width: 1e20,
+          _index: { start: 0, end: 0 },
+
+          init: function(text, lines, cursor, width) {
+              this._text = text;
+              this._lines = lines;
+              this._cursor = cursor;
+              this._width = width;
+          },
+          setIndex: function(i) {
+              this._index.start = this._index.end = i;
+              const l = lines_index_to_line(this._lines, i);
+              const x = runs_index_to_x(l.runs, i);
+              this._cursor.place(x, l.top, l.bottom);
+          },
+          setIndices: function(a, b) {
+              if (a > b) { [a, b] = [b, a]; }
+              this._index.start = a;
+              this._index.end = b;
+              this._cursor.setPath(lines_indices_to_path(this._lines, a, b, fm, this._width));
+          },
+          moveDX: function(dx) {
+              let index;
+              if (this._index.start == this._index.end) {
+                  // just adjust and pin
+                  index = Math.max(Math.min(this._index.start + dx, this._text.length), 0);
+              } else {
+                  // 'deselect' the region, and turn it into just a single index
+                  index = dx < 0 ? this._index.start : this._index.end;
+              }
+              this.setIndex(index);
+          },
+          moveDY: function(dy) {
+              let index = (dy < 0) ? this._index.start : this._index.end;
+              const i = lines_index_to_line_index(this._lines, index);
+              if (dy < 0 && i == 0) {
+                  index = 0;
+              } else if (dy > 0 && i == this._lines.length - 1) {
+                  index = this._text.length;
+              } else {
+                  const x = runs_index_to_x(this._lines[i].runs, index);
+                  // todo: statefully track "original" x when an up/down sequence started,
+                  //       so we can avoid drift.
+                  index = runs_x_to_index(this._lines[i+dy].runs, x);
+              }
+              this.setIndex(index);
+          },
+      };
       let lines;
 
       function drawFrame(canvas) {
@@ -489,6 +547,7 @@
         canvas.drawParagraph(paragraph, 0, 0);
         if (!lines) {
             lines = paragraph.getShapedLines();
+            editor.init(text, lines, cursor, WIDTH);
         }
 
         if (mouse.isActive()) {
@@ -496,12 +555,9 @@
             const a = lines_pos_to_index(lines, pos[0], pos[1]);
             const b = lines_pos_to_index(lines, pos[2], pos[3]);
             if (a == b) {
-                INDEX = a;
-                const l = lines_index_to_line(lines, INDEX);
-                const x = runs_index_to_x(l.runs, INDEX);
-                cursor.place(x, l.top, l.bottom);
+                editor.setIndex(a);
             } else {
-                cursor.setPath(lines_indices_to_path(lines, a, b, fm, WIDTH));
+                editor.setIndices(a, b);
             }
         }
 
@@ -537,9 +593,25 @@
         }
       };
 
+      function keyhandler(e) {
+          switch (e.key) {
+              case 'ArrowLeft':  editor.moveDX(-1); break;
+              case 'ArrowRight': editor.moveDX(1); break;
+              case 'ArrowUp':
+                e.preventDefault();
+                editor.moveDY(-1);
+                break;
+              case 'ArrowDown':
+                e.preventDefault();
+                editor.moveDY(1);
+                break;
+          }
+      }
+
       document.getElementById('para2').addEventListener('pointermove', interact);
       document.getElementById('para2').addEventListener('pointerdown', interact);
       document.getElementById('para2').addEventListener('pointerup', interact);
+      document.getElementById('para2').addEventListener('keydown', keyhandler);
       return surface;
     }