From 9d6eabcad45f5e871f22c56aac5bb84f4b2ddb73 Mon Sep 17 00:00:00 2001 From: Lewis Hemens Date: Sun, 23 Feb 2014 21:06:37 +0000 Subject: [PATCH] Added pinch to zoom support for mobile --- js-lib/controller.js | 9 ---- js-lib/input-controller.js | 88 ++++++++++++++++++++++++++++++++++---- 2 files changed, 80 insertions(+), 17 deletions(-) diff --git a/js-lib/controller.js b/js-lib/controller.js index b2021d8..5d4f196 100644 --- a/js-lib/controller.js +++ b/js-lib/controller.js @@ -91,15 +91,6 @@ ascii.Controller.prototype.endAll = function() { this.lastMoveCell = null; }; -/** - * @param {number} delta - */ -ascii.Controller.prototype.handleZoom = function(delta) { - var newzoom = this.view.zoom * (delta > 0 ? 1.1 : 0.9); - newzoom = Math.max(Math.min(newzoom, 5), 0.2); - this.view.setZoom(newzoom); -}; - /** * Installs input bindings for common use cases devices. */ diff --git a/js-lib/input-controller.js b/js-lib/input-controller.js index 6d575a9..bb48af8 100644 --- a/js-lib/input-controller.js +++ b/js-lib/input-controller.js @@ -11,13 +11,22 @@ ascii.DesktopController = function(controller) { this.installBindings(); }; +/** + * @param {number} delta + */ +ascii.DesktopController.prototype.handleZoom = function(delta) { + var newzoom = this.controller.view.zoom * (delta > 0 ? 1.1 : 0.9); + newzoom = Math.max(Math.min(newzoom, 5), 0.2); + this.controller.view.setZoom(newzoom); +}; + /** * Installs input bindings associated with keyboard controls. */ ascii.DesktopController.prototype.installBindings = function() { var canvas = this.controller.view.canvas; $(canvas).bind('mousewheel', function(e) { - this.controller.handleZoom(e.originalEvent.wheelDelta); + this.handleZoom(e.originalEvent.wheelDelta); }.bind(this)); $(canvas).mousedown(function(e) { @@ -53,8 +62,13 @@ ascii.TouchController = function(controller) { /** @type {ascii.Controller} */ this.controller = controller; /** @type {ascii.Vector} */ this.pressVector; + + /** @type {number} */ this.originalZoom; + /** @type {number} */ this.zoomLength; + /** @type {number} */ this.pressTimestamp; /** @type {boolean} */ this.dragStarted = false; + /** @type {boolean} */ this.zoomStarted = false; this.installBindings(); }; @@ -69,12 +83,27 @@ ascii.TouchController.prototype.handlePress = function(position) { // If a drag didn't start, then handle it as a draw. window.setTimeout(function() { - if (!this.dragStarted) { + if (!this.dragStarted && !this.zoomStarted) { this.controller.startDraw(position); } }.bind(this), DRAG_LATENCY); }; +/** + * The multi-touch version of handlePress. + * @param {ascii.Vector} positionOne + * @param {ascii.Vector} positionTwo + */ +ascii.TouchController.prototype.handlePressMulti = + function(positionOne, positionTwo) { + // A second finger as been placed, cancel whatever we were doing. + this.controller.endAll(); + this.zoomStarted = true; + this.dragStarted = false; + this.zoomLength = positionOne.subtract(positionTwo).length(); + this.originalZoom = this.controller.view.zoom; +}; + /** * @param {ascii.Vector} position */ @@ -90,6 +119,30 @@ ascii.TouchController.prototype.handleMove = function(position) { this.controller.handleMove(position); }; +/** + * The multi-touch version of handleMove, effectively only deals with zooming. + * @param {ascii.Vector} positionOne + * @param {ascii.Vector} positionTwo + */ +ascii.TouchController.prototype.handleMoveMulti = + function(positionOne, positionTwo) { + if (this.zoomStarted) { + var newZoom = this.originalZoom * + positionOne.subtract(positionTwo).length() / this.zoomLength; + newZoom = Math.max(Math.min(newZoom, 5), 0.5); + this.controller.view.setZoom(newZoom); + } +}; + +/** + * Ends all current actions, cleans up any state. + */ +ascii.TouchController.prototype.reset = function() { + this.dragStarted = false; + this.zoomStarted = false; + this.pressVector = null; +}; + /** * Installs input bindings associated with touch controls. */ @@ -98,21 +151,40 @@ ascii.TouchController.prototype.installBindings = function() { $(canvas).bind('touchstart', function(e) { e.preventDefault(); - this.handlePress(new ascii.Vector( - e.originalEvent.touches[0].pageX, - e.originalEvent.touches[0].pageY)); + if (e.originalEvent.touches.length == 1) { + this.handlePress(new ascii.Vector( + e.originalEvent.touches[0].pageX, + e.originalEvent.touches[0].pageY)); + } else if (e.originalEvent.touches.length > 1) { + this.handlePressMulti(new ascii.Vector( + e.originalEvent.touches[0].pageX, + e.originalEvent.touches[0].pageY), + new ascii.Vector( + e.originalEvent.touches[1].pageX, + e.originalEvent.touches[1].pageY)); + } }.bind(this)); $(canvas).bind('touchmove', function(e) { e.preventDefault(); - this.handleMove(new ascii.Vector( - e.originalEvent.touches[0].pageX, - e.originalEvent.touches[0].pageY)); + if (e.originalEvent.touches.length == 1) { + this.handleMove(new ascii.Vector( + e.originalEvent.touches[0].pageX, + e.originalEvent.touches[0].pageY)); + } else if (e.originalEvent.touches.length > 1) { + this.handleMoveMulti(new ascii.Vector( + e.originalEvent.touches[0].pageX, + e.originalEvent.touches[0].pageY), + new ascii.Vector( + e.originalEvent.touches[1].pageX, + e.originalEvent.touches[1].pageY)); + } }.bind(this)); // Pass through, no special handling. $(canvas).bind('touchend', function(e) { e.preventDefault(); + this.reset(); this.controller.endAll(); }.bind(this)); };