[particles] basic zoom/pan

Shift + left click to pan
mousewheel to zoom (centering on mouse coordinates)

Bug: skia:
Change-Id: I3bd0381734266e42e1ad5fe565890af3cf98f739
Reviewed-on: https://skia-review.googlesource.com/c/buildbot/+/201360
Commit-Queue: Kevin Lubick <kjlubick@google.com>
Reviewed-by: Mike Reed <reed@google.com>
Reviewed-by: Joe Gregorio <jcgregorio@google.com>
diff --git a/particles/modules/particles-player-sk/particles-player-sk.js b/particles/modules/particles-player-sk/particles-player-sk.js
index b04c334..af2f994 100644
--- a/particles/modules/particles-player-sk/particles-player-sk.js
+++ b/particles/modules/particles-player-sk/particles-player-sk.js
@@ -14,6 +14,9 @@
 
 const CanvasKitInit = require('../../build/canvaskit/canvaskit.js');
 
+const ZOOM_IN_FACTOR = 1.1; // 10%
+const ZOOM_OUT_FACTOR = 1/ZOOM_IN_FACTOR;
+
 // This element might be loaded from a different site, and that means we need
 // to be careful about how we construct the URL back to the canvas.wasm file.
 // Start by recording the script origin.
@@ -33,7 +36,10 @@
 
 const runningTemplate = (ele) => html`
 <div class=container>
+  <!-- It would be more mobile friendly to use pointermove, but Safari doesn't support it-->
   <canvas id=player
+          @wheel=${ele._wheel}
+          @mousemove=${ele._drag}
           width=${ele._config.width * window.devicePixelRatio}
           height=${ele._config.height * window.devicePixelRatio}
           style='width: ${ele._config.width}px; height: ${ele._config.height}px;'>
@@ -60,6 +66,9 @@
       lastTs:         0, // last time stamp we had a frame
     };
 
+
+    this._lastDrag = null;
+    this._zoomLevel = 1.0;
   }
 
   connectedCallback() {
@@ -72,6 +81,22 @@
     this.render();
   }
 
+  _drag(e) {
+    if (!e.buttons || !e.shiftKey) { // ignore movements unless shift is held
+      this._lastDrag = null;
+      return;
+    }
+    if (this._lastDrag) {
+      const dx = e.clientX - this._lastDrag[0];
+      const dy = e.clientY - this._lastDrag[1];
+
+      const canvas = this._engine.canvas.translate(dx / this._zoomLevel,
+                                                   dy / this._zoomLevel);
+    }
+    this._lastDrag = [e.clientX, e.clientY];
+
+  }
+
   _drawFrame() {
     if (!this._engine.animation || !this._engine.canvas) {
       return;
@@ -139,9 +164,9 @@
 
     this._engine.canvas.clear(this._config.bgcolor);
     // Center the animation
-    this._engine.canvas.translate(this._config.width/2, this._config.height/2);
+    this.resetView();
 
-    this.reset();
+    this.restartAnimation();
 
     this._drawFrame();
   }
@@ -170,8 +195,43 @@
            this, {eventContext: this});
   }
 
-  reset() {
+  resetView() {
+    const ck = this._engine.kit;
+    const canvas = this._engine.canvas;
+    // Reset to identity
+    const tt = canvas.getTotalMatrix();
+    const itt = ck.SkMatrix.invert(tt);
+    canvas.concat(itt);
+    // Zoom to the middle of the animation
+    canvas.translate(this._config.width/2, this._config.height/2);
+  }
+
+  restartAnimation() {
     this._state.time = 0;
     this._state.lastTs = 0;
   }
+
+  _wheel(e) {
+    e.preventDefault();
+    e.stopPropagation();
+
+    let zoom = 0;
+    if (e.deltaY < 0) {
+      zoom = ZOOM_IN_FACTOR;
+    } else {
+      zoom = ZOOM_OUT_FACTOR;
+    }
+    this._zoomLevel *= zoom;
+    const ck = this._engine.kit;
+    const canvas = this._engine.canvas;
+
+    const tt = canvas.getTotalMatrix();
+    const itt = ck.SkMatrix.invert(tt);
+    const pts = [e.clientX, e.clientY];
+    ck.SkMatrix.mapPoints(itt, pts); // Transform DOM pts into canvas space
+
+    let matr = ck.SkMatrix.scaled(zoom, zoom, pts[0], pts[1]);
+    canvas.concat(matr);
+
+  }
 });
\ No newline at end of file
diff --git a/particles/modules/particles-sk/particles-sk.js b/particles/modules/particles-sk/particles-sk.js
index 53c10f2..c841b7b 100644
--- a/particles/modules/particles-sk/particles-sk.js
+++ b/particles/modules/particles-sk/particles-sk.js
@@ -39,6 +39,10 @@
 
 <figcaption>
   particles-wasm
+  <button @click=${ele._resetView}
+          title="Shift + Left click to pan, scroll wheel to zoom">
+    Reset Pan/Zoom
+  </button>
 </figcaption>`;
 
 const jsonEditor = (ele) => {
@@ -64,7 +68,7 @@
   ${ele._state.filename} ${ele._width}x${ele._height} ...
 </button>
 <div class=controls>
-  <button @click=${ele._reset}>Reset</button>
+  <button @click=${ele._restartAnimation}>Restart</button>
   <button id=playpause @click=${ele._playpause}>Pause</button>
   <button ?hidden=${!ele._hasEdits} @click=${ele._applyEdits}>Apply Edits</button>
   <div class=download>
@@ -344,8 +348,12 @@
     this._editorLoaded = true;
   }
 
-  _reset() {
-    this._particlesPlayer && this._particlesPlayer.reset();
+  _resetView() {
+    this._particlesPlayer && this._particlesPlayer.resetView();
+  }
+
+  _restartAnimation() {
+    this._particlesPlayer && this._particlesPlayer.restartAnimation();
   }
 
   _startEdit() {