diff --git a/compile.sh b/compile.sh index 5a29ded..caddd29 100755 --- a/compile.sh +++ b/compile.sh @@ -2,8 +2,9 @@ java -client -jar closure-compiler.jar \ --js js-lib/common.js \ --js js-lib/view.js \ --js js-lib/draw.js \ - --js js-lib/controller.js \ --js js-lib/state.js \ + --js js-lib/controller.js \ + --js js-lib/input-controller.js \ --js js-lib/launch.js \ --warning_level=VERBOSE --formatting=PRETTY_PRINT --language_in=ECMASCRIPT5 --compilation_level=ADVANCED_OPTIMIZATIONS \ --externs=jquery-1.9-externs.js \ diff --git a/js-lib/controller.js b/js-lib/controller.js index f858457..90f5dba 100644 --- a/js-lib/controller.js +++ b/js-lib/controller.js @@ -1,3 +1,13 @@ +/** + * Different modes of control. + * @const + */ +var Mode = { + NONE : 0, + DRAG : 1, + DRAW : 2 +} + /** * Handles user input events and modifies state. * @@ -12,10 +22,9 @@ ascii.Controller = function(view, state) { /** @type {ascii.DrawFunction} */ this.drawFunction = new ascii.DrawBox(state); + /** @type {number} */ this.mode = Mode.NONE; /** @type {ascii.Vector} */ this.dragOrigin; - /** @type {ascii.Vector} */ this.pressVector; - /** @type {ascii.Vector} */ this.lastMoveCell; - /** @type {number} */ this.pressTimestamp; + /** @type {ascii.Vector} */ this.dragOriginCell; this.installBindings(); }; @@ -23,68 +32,59 @@ ascii.Controller = function(view, state) { /** * @param {ascii.Vector} position */ -ascii.Controller.prototype.handlePress = function(position) { - this.pressVector = position; - this.pressTimestamp = $.now(); +ascii.Controller.prototype.startDraw = function(position) { + this.mode = Mode.DRAW; + this.drawFunction.start(this.view.screenToCell(position)); +} - // Check to see if a drag happened in the given allowed time. - window.setTimeout(function() { - if (this.dragOrigin == null && this.pressVector != null) { - this.drawFunction.start(this.view.screenToCell(position)); - } - // TODO: Skip this if release happens before timeout. - }.bind(this), DRAG_LATENCY); -}; +/** + * @param {ascii.Vector} position + */ +ascii.Controller.prototype.startDrag = function(position) { + this.mode = Mode.DRAG; + this.dragOrigin = position; + this.dragOriginCell = this.view.offset; +} /** * @param {ascii.Vector} position */ ascii.Controller.prototype.handleMove = function(position) { - // Update the cursor pointer, depending on the draw function. - this.view.canvas.style.cursor = this.drawFunction.getCursor( - this.view.screenToCell(position)); + var moveCell = this.view.screenToCell(position); - // No clicks, so just ignore. - if (this.pressVector == null) { return; } - - // Initiate a drag if we have moved enough, quickly enough. - if (this.dragOrigin == null && - ($.now() - this.pressTimestamp) < DRAG_LATENCY && - position.subtract(this.pressVector).length() > DRAG_ACCURACY) { - this.dragOrigin = this.view.offset; + // First move event, make sure we don't blow up here. + if (this.lastMoveCell == null) { + this.lastMoveCell = moveCell; } - // Not dragging, so pass the mouse move on, but remove duplicates. - if (this.dragOrigin == null && - ($.now() - this.pressTimestamp) >= DRAG_LATENCY && - (this.lastMoveCell == null || - !this.view.screenToCell(position) - .equals(this.view.screenToCell(this.lastMoveCell)))) { - this.drawFunction.move(this.view.screenToCell(position)); - this.lastMoveCell = position; + // Update the cursor pointer, depending on the draw function. + if (!moveCell.equals(this.lastMoveCell)) { + this.view.canvas.style.cursor = this.drawFunction.getCursor(moveCell); + } + + // In drawing mode, so pass the mouse move on, but remove duplicates. + if (this.mode == Mode.DRAW && !moveCell.equals(this.lastMoveCell)) { + this.drawFunction.move(moveCell); } // Drag in progress, update the view origin. - if (this.dragOrigin != null) { - this.view.setOffset(this.dragOrigin.add( - this.pressVector + if (this.mode == Mode.DRAG) { + this.view.setOffset(this.dragOriginCell.add( + this.dragOrigin .subtract(position) .scale(1 / this.view.zoom))); } + this.lastMoveCell = moveCell; }; -/** - * @param {ascii.Vector} position - */ -ascii.Controller.prototype.handleRelease = function(position) { - // Drag wasn't initiated in time, treat this as a drawing event. - if (this.dragOrigin == null && - ($.now() - this.pressTimestamp) >= DRAG_LATENCY) { - this.drawFunction.end(this.view.screenToCell(position)); +ascii.Controller.prototype.endAll = function() { + if (this.mode = Mode.DRAW) { + this.drawFunction.end(); } - this.pressVector = null; - this.pressTimestamp = 0; + // Cleanup state. + this.mode = Mode.NONE; this.dragOrigin = null; + this.dragOriginCell = null; this.lastMoveCell = null; }; @@ -98,53 +98,13 @@ ascii.Controller.prototype.handleZoom = function(delta) { }; /** - * Installs input bindings for desktop devices. + * Installs input bindings for common use cases devices. */ ascii.Controller.prototype.installBindings = function() { var controller = this; - $(this.view.canvas).bind('mousewheel', function(e) { - controller.handleZoom(e.originalEvent.wheelDelta); - }); - - $(this.view.canvas).mousedown(function(e) { - controller.handlePress(new ascii.Vector(e.clientX, e.clientY)); - }); - - $(this.view.canvas).mouseup(function(e) { - controller.handleRelease(new ascii.Vector(e.clientX, e.clientY)); - }); - - $(this.view.canvas).mouseleave(function(e) { - controller.handleRelease(new ascii.Vector(e.clientX, e.clientY)); - }); - - $(this.view.canvas).mousemove(function(e) { - controller.handleMove(new ascii.Vector(e.clientX, e.clientY)); - }); - $(window).resize(function(e) { controller.view.resizeCanvas() }); - $(this.view.canvas).bind('touchstart', function(e) { - e.preventDefault(); - controller.handlePress(new ascii.Vector( - e.originalEvent.touches[0].pageX, - e.originalEvent.touches[0].pageY)); - }); - - $(this.view.canvas).bind('touchend', function(e) { - e.preventDefault(); - // TODO: This works for now as we don't use a touchend position anywhere. - // Need to track last position from touchmove and use it here. - controller.handleRelease(new ascii.Vector(0, 0)); - }); - - $(this.view.canvas).bind('touchmove', function(e) { - e.preventDefault(); - controller.handleMove(new ascii.Vector( - e.originalEvent.touches[0].pageX, - e.originalEvent.touches[0].pageY)); - }); // TODO: Handle pinch to zoom. $('#buttons > button.tool').click(function(e) { diff --git a/js-lib/draw.js b/js-lib/draw.js index 182e9db..0fb73a5 100644 --- a/js-lib/draw.js +++ b/js-lib/draw.js @@ -53,8 +53,8 @@ ascii.DrawFunction = function() {}; ascii.DrawFunction.prototype.start = function(position) {}; /** Drawing move. @param {ascii.Vector} position */ ascii.DrawFunction.prototype.move = function(position) {}; -/** End of drawing. @param {ascii.Vector} position */ -ascii.DrawFunction.prototype.end = function(position) {}; +/** End of drawing. */ +ascii.DrawFunction.prototype.end = function() {}; /** Cursor for given cell. * @param {ascii.Vector} position * @return {string} @@ -82,7 +82,7 @@ ascii.DrawBox.prototype.move = function(position) { drawLine(this.state, this.startPosition, position, true); drawLine(this.state, this.startPosition, position, false); }; -ascii.DrawBox.prototype.end = function(position) { +ascii.DrawBox.prototype.end = function() { this.state.commitDraw(); }; ascii.DrawBox.prototype.getCursor = function(position) { @@ -115,7 +115,7 @@ ascii.DrawLine.prototype.move = function(position) { drawLine(this.state, this.startPosition, position, clockwise); }; -ascii.DrawLine.prototype.end = function(position) { +ascii.DrawLine.prototype.end = function() { this.state.commitDraw(); }; ascii.DrawLine.prototype.getCursor = function(position) { @@ -140,7 +140,7 @@ ascii.DrawFreeform.prototype.start = function(position) { ascii.DrawFreeform.prototype.move = function(position) { this.state.drawValue(position, this.value); }; -ascii.DrawFreeform.prototype.end = function(position) { +ascii.DrawFreeform.prototype.end = function() { this.state.commitDraw(); }; ascii.DrawFreeform.prototype.getCursor = function(position) { @@ -174,7 +174,7 @@ ascii.DrawText.prototype.start = function(position) { this.state.drawValue(position, currentValue == null ? ERASE_CHAR : currentValue); }; ascii.DrawText.prototype.move = function(position) {}; -ascii.DrawText.prototype.end = function(position) {}; +ascii.DrawText.prototype.end = function() {}; ascii.DrawText.prototype.getCursor = function(position) { return 'text'; }; @@ -259,7 +259,7 @@ ascii.DrawErase.prototype.move = function(position) { } } }; -ascii.DrawErase.prototype.end = function(position) { +ascii.DrawErase.prototype.end = function() { this.state.commitDraw(); }; ascii.DrawErase.prototype.getCursor = function(position) { @@ -340,7 +340,7 @@ ascii.DrawMove.prototype.move = function(position) { } }; -ascii.DrawMove.prototype.end = function(position) { +ascii.DrawMove.prototype.end = function() { this.state.commitDraw(); }; diff --git a/js-lib/input-controller.js b/js-lib/input-controller.js new file mode 100644 index 0000000..0ccfa58 --- /dev/null +++ b/js-lib/input-controller.js @@ -0,0 +1,119 @@ +/** + * Handles desktop inputs, and passes them onto the main controller. + * @constructor + * @param {ascii.Controller} controller + */ +ascii.DesktopController = function(controller) { + /** @type {ascii.Controller} */ this.controller = controller; + + /** @type {boolean} */ this.isDragging = false; + + this.installBindings(); +} + +/** + * @param {ascii.Vector} position + */ +ascii.DesktopController.prototype.handlePress = function(position, e) { + // Can drag by holding either the control or meta (Apple) key. + if (e.ctrlKey || e.metaKey) { + this.controller.startDrag(position); + } else { + this.controller.startDraw(position); + } +}; + +ascii.DesktopController.prototype.installBindings = function() { + var canvas = this.controller.view.canvas; + $(canvas).bind('mousewheel', function(e) { + this.controller.handleZoom(e.originalEvent.wheelDelta); + }.bind(this)); + + $(canvas).mousedown(function(e) { + this.handlePress(new ascii.Vector(e.clientX, e.clientY), e); + }.bind(this)); + + // Pass these events through to the main controller. + $(canvas).mouseup(function(e) { + this.controller.endAll(); + }.bind(this)); + + $(canvas).mouseleave(function(e) { + this.controller.endAll(); + }.bind(this)); + + $(canvas).mousemove(function(e) { + this.controller.handleMove(new ascii.Vector(e.clientX, e.clientY)); + }.bind(this)); +}; + + +/** + * Handles touch inputs, and passes them onto the main controller. + * @constructor + * @param {ascii.Controller} controller + */ +ascii.TouchController = function(controller) { + /** @type {ascii.Controller} */ this.controller = controller; + + /** @type {ascii.Vector} */ this.pressVector; + /** @type {number} */ this.pressTimestamp; + /** @type {boolean} */ this.dragStarted = false; + + this.installBindings(); +} + +/** + * @param {ascii.Vector} position + */ +ascii.TouchController.prototype.handlePress = function(position) { + this.pressVector = position; + this.pressTimestamp = $.now(); + this.dragStarted = false; + + // If a drag didn't start, then handle it as a draw. + window.setTimeout(function() { + if (!this.dragStarted) { + this.controller.startDraw(position); + } + }.bind(this), DRAG_LATENCY); +}; + +/** + * @param {ascii.Vector} position + */ +ascii.TouchController.prototype.handleMove = function(position) { + // Initiate a drag if we have moved enough, quickly enough. + if (!this.dragStarted && + ($.now() - this.pressTimestamp) < DRAG_LATENCY && + position.subtract(this.pressVector).length() > DRAG_ACCURACY) { + this.dragStarted = true; + this.controller.startDrag(position); + } + // Pass on the event. + this.controller.handleMove(position); +}; + +ascii.TouchController.prototype.installBindings = function() { + var canvas = this.controller.view.canvas; + + $(canvas).bind('touchstart', function(e) { + e.preventDefault(); + this.handlePress(new ascii.Vector( + e.originalEvent.touches[0].pageX, + e.originalEvent.touches[0].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)); + }.bind(this)); + + // Pass through, no special handling. + $(canvas).bind('touchend', function(e) { + e.preventDefault(); + this.controller.endAll(); + }.bind(this)); +}; diff --git a/js-lib/launch.js b/js-lib/launch.js index cbfb3c8..03b1c9a 100644 --- a/js-lib/launch.js +++ b/js-lib/launch.js @@ -5,6 +5,8 @@ ascii.launch = function() { var state = new ascii.State(); var view = new ascii.View(state); var controller = new ascii.Controller(view, state); + var touchController = new ascii.TouchController(controller); + var desktopController = new ascii.DesktopController(controller); view.animate(); };