diff --git a/js-lib/common.js b/js-lib/common.js index 5ddac21..e0ce8c2 100644 --- a/js-lib/common.js +++ b/js-lib/common.js @@ -5,24 +5,34 @@ goog.provide('ascii.Vector'); /** * @constructor + * @param {number} x + * @param {number} y */ ascii.Vector = function(x, y) { /** type {Number} */ this.x = x; /** type {Number} */ this.y = y; }; -/** @return {boolean} */ +/** + * @param {ascii.Vector} other + * @return {boolean} + */ ascii.Vector.prototype.equals = function(other) { - return (this.x == other.x) - && (this.y == other.y); + return (this.x == other.x) && (this.y == other.y); }; -/** @return {ascii.Vector} */ +/** + * @param {ascii.Vector} other + * @return {boolean} + */ ascii.Vector.prototype.subtract = function(other) { return new ascii.Vector(this.x - other.x, this.y - other.y); }; -/** @return {ascii.Vector} */ +/** + * @param {ascii.Vector} other + * @return {boolean} + */ ascii.Vector.prototype.add = function(other) { return new ascii.Vector(this.x + other.x, this.y + other.y); }; @@ -32,7 +42,10 @@ ascii.Vector.prototype.length = function() { return Math.sqrt(this.x * this.x + this.y * this.y); }; -/** @return {ascii.Vector} */ +/** + * @param {number} scale + * @return {ascii.Vector} + */ ascii.Vector.prototype.scale = function(scale) { return new ascii.Vector(this.x * scale, this.y * scale); }; diff --git a/js-lib/controller.js b/js-lib/controller.js index af5a952..2488811 100644 --- a/js-lib/controller.js +++ b/js-lib/controller.js @@ -12,6 +12,8 @@ goog.require('ascii.View'); /** * @constructor + * @param {ascii.View} view + * @param {ascii.State} state */ ascii.Controller = function(view, state) { /** @type {ascii.View} */ this.view = view; @@ -28,10 +30,11 @@ ascii.Controller = function(view, state) { this.installTouchBindings(); }; -ascii.Controller.prototype.handlePress = function(x, y) { - var position = new ascii.Vector(x, y); - - this.pressVector = new ascii.Vector(x, y); +/** + * @param {ascii.Vector} position + */ +ascii.Controller.prototype.handlePress = function(position) { + this.pressVector = position; this.pressTimestamp = $.now(); // Check to see if a drag happened in the given allowed time. @@ -43,11 +46,12 @@ ascii.Controller.prototype.handlePress = function(x, y) { }.bind(this), DRAG_LATENCY); }; -ascii.Controller.prototype.handleMove = function(x, y) { - var position = new ascii.Vector(x, y); - +/** + * @param {ascii.Vector} position + */ +ascii.Controller.prototype.handleMove = function(position) { // No clicks, so just ignore. - if (this.pressVector == null) { return; } + if (this.pressVector == null) { return; } // Initiate a drag if we have moved enough, quickly enough. if (this.dragOrigin == null && @@ -66,14 +70,15 @@ ascii.Controller.prototype.handleMove = function(x, y) { if (this.dragOrigin != null) { this.view.offset = this.dragOrigin.add( this.pressVector - .subtract(new ascii.Vector(x, y)) - .scale(1/this.view.zoom)); + .subtract(position) + .scale(1 / this.view.zoom)); } }; -ascii.Controller.prototype.handleRelease = function(x, y) { - var position = new ascii.Vector(x, y); - +/** + * @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) { @@ -84,46 +89,55 @@ ascii.Controller.prototype.handleRelease = function(x, y) { this.dragOrigin = null; }; +/** + * @param {number} delta + */ ascii.Controller.prototype.handleZoom = function(delta) { this.view.zoom *= delta > 0 ? 1.1 : 0.9; this.view.zoom = Math.max(Math.min(this.view.zoom, 5), 0.2); }; +/** + * Installs input bindings for desktop devices. + */ ascii.Controller.prototype.installDesktopBindings = function() { var controller = this; $(this.view.canvas).bind('mousewheel', function(e) { controller.handleZoom(e.originalEvent.wheelDelta); }); $(this.view.canvas).mousedown(function(e) { - controller.handlePress(e.clientX, e.clientY); + controller.handlePress(new ascii.Vector(e.clientX, e.clientY)); }); $(this.view.canvas).mouseup(function(e) { - controller.handleRelease(e.clientX, e.clientY); + controller.handleRelease(new ascii.Vector(e.clientX, e.clientY)); }); $(this.view.canvas).mousemove(function(e) { - controller.handleMove(e.clientX, e.clientY); + controller.handleMove(new ascii.Vector(e.clientX, e.clientY)); }); $(window).resize(function(e) { controller.view.resizeCanvas() }); }; +/** + * Installs input bindings for touch devices. + */ ascii.Controller.prototype.installTouchBindings = function() { var controller = this; - $(this.view.canvas).bind("touchstart", function(e) { + $(this.view.canvas).bind('touchstart', function(e) { e.preventDefault(); - controller.handlePress( + controller.handlePress(new ascii.Vector( e.originalEvent.touches[0].pageX, - e.originalEvent.touches[0].pageY); + e.originalEvent.touches[0].pageY)); }); - $(this.view.canvas).bind("touchend", function(e) { + $(this.view.canvas).bind('touchend', function(e) { e.preventDefault(); - controller.handleRelease( + controller.handleRelease(new ascii.Vector( e.originalEvent.touches[0].pageX, - e.originalEvent.touches[0].pageY); + e.originalEvent.touches[0].pageY)); }); - $(this.view.canvas).bind("touchmove", function(e) { + $(this.view.canvas).bind('touchmove', function(e) { e.preventDefault(); - controller.handleMove( + controller.handleMove(new ascii.Vector( e.originalEvent.touches[0].pageX, - e.originalEvent.touches[0].pageY); + e.originalEvent.touches[0].pageY)); }); }; diff --git a/js-lib/launch.js b/js-lib/launch.js index da0eae3..183b593 100644 --- a/js-lib/launch.js +++ b/js-lib/launch.js @@ -8,7 +8,7 @@ goog.require('ascii.State'); goog.require('ascii.View'); /** - * @private + * Runs the application. */ ascii.launch = function() { var state = new ascii.State(); diff --git a/js-lib/state-controller.js b/js-lib/state-controller.js index 4f8afd5..822e00a 100644 --- a/js-lib/state-controller.js +++ b/js-lib/state-controller.js @@ -1,25 +1,37 @@ -/** - * Handles management of the diagram state. - */ goog.provide('ascii.StateController'); goog.require('ascii.Vector'); /** + * Handles management of the diagram state. Input events are cleaned in the + * parent controller and passed down to this class for dealing with drawing. + * * @constructor + * @param {ascii.State} state */ ascii.StateController = function(state) { /** @type {ascii.State} */ this.state = state; }; +/** + * Handles a press in the context of the drawing frame. + * @param {ascii.Vector} position + */ ascii.StateController.prototype.handleDrawingPress = function(position) { this.state.getCell(position).value = 'O'; }; +/** + * Handles a release in the context of the drawing frame. + * @param {ascii.Vector} position + */ ascii.StateController.prototype.handleDrawingRelease = function(position) { }; - +/** + * Handles a move in the context of the drawing frame. + * @param {ascii.Vector} position + */ ascii.StateController.prototype.handleDrawingMove = function(position) { this.state.getCell(position).value = 'O'; }; diff --git a/js-lib/state.js b/js-lib/state.js index 1dff319..69b35fa 100644 --- a/js-lib/state.js +++ b/js-lib/state.js @@ -1,23 +1,28 @@ -/** - * Classes holding the state of the ascii-diagram. - */ goog.provide('ascii.State'); /** @const */ var MAX_GRID_SIZE = 1000; /** + * An individual cell within the diagram and it's current value. + * * @constructor */ ascii.Cell = function() { - /** @type {string|null} */ // Uses the string "#" for lines. - this.value = null; + /** @type {?string} */ this.value = null; }; +/** + * Sets the cells value. + * + * @param {string} value + */ ascii.Cell.prototype.setValue = function(value) { this.value = value; }; /** + * Holds the entire state of the diagram as a 2D array of cells. + * * @constructor */ ascii.State = function() { @@ -32,6 +37,12 @@ ascii.State = function() { } }; +/** + * Returns the cell at the given coordinates. + * + * @param {ascii.Vector} vector + * @return {asii.Cell} + */ ascii.State.prototype.getCell = function(vector) { return this.cells[vector.x][vector.y]; }; diff --git a/js-lib/view.js b/js-lib/view.js index edafec5..473fdae 100644 --- a/js-lib/view.js +++ b/js-lib/view.js @@ -5,10 +5,11 @@ goog.require('ascii.Vector'); /** @const */ var CHARACTER_PIXELS = 15; /** @const */ var RENDER_PADDING = 20; - /** - * Object relating to view operations and management of the screen. + * Handles view operations, state and management of the screen. + * * @constructor + * @param {ascii.State} state */ ascii.View = function(state) { /** @type {Element} */ this.canvas = document.getElementById('ascii-canvas'); @@ -19,6 +20,9 @@ ascii.View = function(state) { this.resizeCanvas(); }; +/** + * Resizes the canvas, should be called if the viewport size changes. + */ ascii.View.prototype.resizeCanvas = function() { this.canvas.width = document.documentElement.clientWidth; this.canvas.height = document.documentElement.clientHeight; @@ -28,6 +32,7 @@ ascii.View.prototype.resizeCanvas = function() { * Starts the animation loop for the canvas. Should only be called once. */ ascii.View.prototype.animate = function() { + // TODO: Only render incrementally. this.render(); var view = this; window.requestAnimationFrame(function() { view.animate(); }); @@ -44,7 +49,9 @@ ascii.View.prototype.render = function() { context.clearRect(0, 0, this.canvas.width, this.canvas.height); context.scale(this.zoom, this.zoom); - context.translate(this.canvas.width/2/this.zoom, this.canvas.height/2/this.zoom); + context.translate( + this.canvas.width / 2 / this.zoom, + this.canvas.height / 2 / this.zoom); // Only render grid lines and cells that are visible. var startOffset = this.screenToCell(new ascii.Vector( @@ -55,24 +62,24 @@ ascii.View.prototype.render = function() { this.canvas.height + RENDER_PADDING)); // Render the grid. - context.lineWidth="1"; - context.strokeStyle="#EEEEEE"; + context.lineWidth = '1'; + context.strokeStyle = '#EEEEEE'; context.beginPath(); for (var i = startOffset.x; i < endOffset.x; i++) { context.moveTo( - i*CHARACTER_PIXELS - this.offset.x, + i * CHARACTER_PIXELS - this.offset.x, 0 - this.offset.y); context.lineTo( - i*CHARACTER_PIXELS - this.offset.x, - this.state.cells.length*CHARACTER_PIXELS - this.offset.y); + i * CHARACTER_PIXELS - this.offset.x, + this.state.cells.length * CHARACTER_PIXELS - this.offset.y); } for (var j = startOffset.y; j < endOffset.y; j++) { context.moveTo( 0 - this.offset.x, - j*CHARACTER_PIXELS - this.offset.y); + j * CHARACTER_PIXELS - this.offset.y); context.lineTo( - this.state.cells.length*CHARACTER_PIXELS - this.offset.x, - j*CHARACTER_PIXELS - this.offset.y); + this.state.cells.length * CHARACTER_PIXELS - this.offset.x, + j * CHARACTER_PIXELS - this.offset.y); } this.context.stroke(); @@ -82,8 +89,8 @@ ascii.View.prototype.render = function() { for (var j = startOffset.y; j < endOffset.y; j++) { if (this.state.cells[i][j].value != null) { context.fillText(this.state.cells[i][j].value, - i*CHARACTER_PIXELS - this.offset.x + 3, - j*CHARACTER_PIXELS - this.offset.y - 2); + i * CHARACTER_PIXELS - this.offset.x + 3, + j * CHARACTER_PIXELS - this.offset.y - 2); } } } @@ -96,8 +103,8 @@ ascii.View.prototype.render = function() { */ ascii.View.prototype.screenToFrame = function(vector) { return new ascii.Vector( - (vector.x - this.canvas.width/2)/this.zoom + this.offset.x, - (vector.y - this.canvas.height/2)/this.zoom + this.offset.y); + (vector.x - this.canvas.width / 2) / this.zoom + this.offset.x, + (vector.y - this.canvas.height / 2) / this.zoom + this.offset.y); }; /** @@ -107,8 +114,8 @@ ascii.View.prototype.screenToFrame = function(vector) { */ ascii.View.prototype.frameToScreen = function(vector) { return new ascii.Vector( - (vector.x - this.offset.x) * this.zoom + this.canvas.width/2, - (vector.y - this.offset.y) * this.zoom + this.canvas.height/2); + (vector.x - this.offset.x) * this.zoom + this.canvas.width / 2, + (vector.y - this.offset.y) * this.zoom + this.canvas.height / 2); }; /** @@ -118,8 +125,8 @@ ascii.View.prototype.frameToScreen = function(vector) { */ ascii.View.prototype.frameToCell = function(vector) { return new ascii.Vector( - Math.round((vector.x-CHARACTER_PIXELS/2)/CHARACTER_PIXELS), - Math.round((vector.y+CHARACTER_PIXELS/2)/CHARACTER_PIXELS)); + Math.round((vector.x - CHARACTER_PIXELS / 2) / CHARACTER_PIXELS), + Math.round((vector.y + CHARACTER_PIXELS / 2) / CHARACTER_PIXELS)); }; /**