Updated controller logic to have dynamic drag/draw mode

This commit is contained in:
Lewis Hemens 2014-01-11 17:41:44 +00:00
parent d51221f089
commit 1feea9778c
4 changed files with 77 additions and 21 deletions

View File

@ -31,3 +31,9 @@ ascii.Vector.prototype.add = function(other) {
ascii.Vector.prototype.length = function() { ascii.Vector.prototype.length = function() {
return Math.sqrt(this.x * this.x + this.y * this.y); return Math.sqrt(this.x * this.x + this.y * this.y);
}; };
/** @return {ascii.Vector} */
ascii.Vector.prototype.scale = function(scale) {
return new ascii.Vector(this.x * scale, this.y * scale);
};

View File

@ -7,6 +7,9 @@ goog.provide('ascii.Controller');
goog.require('ascii.Vector'); goog.require('ascii.Vector');
goog.require('ascii.View'); goog.require('ascii.View');
/** @const */ var DRAG_LATENCY = 200; // Milliseconds.
/** @const */ var DRAG_ACCURACY = 5; // Pixels.
/** /**
* @constructor * @constructor
*/ */
@ -14,32 +17,56 @@ ascii.Controller = function(view, state) {
/** @type {ascii.View} */ this.view = view; /** @type {ascii.View} */ this.view = view;
/** @type {ascii.State} */ this.state = state; /** @type {ascii.State} */ this.state = state;
/** @type {ascii.Vector} */ this.dragOrigin;
/** @type {ascii.Vector} */ this.pressVector; /** @type {ascii.Vector} */ this.pressVector;
/** @type {number} */ this.pressTimestamp;
this.installDesktopBindings(); this.installDesktopBindings();
}; };
ascii.Controller.prototype.handlePress = function(x, y) { ascii.Controller.prototype.handlePress = function(x, y) {
this.pressVector = new ascii.Vector(x, y); this.pressVector = new ascii.Vector(x, y);
this.pressTimestamp = $.now();
}; };
ascii.Controller.prototype.handleMove = function(x, y) { ascii.Controller.prototype.handleMove = function(x, y) {
if (this.pressVector != null) { var position = new ascii.Vector(x, y);
// Drag has started.
this.view.offset.x -= (x - this.pressVector.x)/this.view.zoom; if (this.pressVector == null) { return; } // No clicks, so just ignore.
this.view.offset.y -= (y - this.pressVector.y)/this.view.zoom;
this.pressVector = new ascii.Vector(x, y); // 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;
}
// Drag in progress, update the view origin.
if (this.dragOrigin != null) {
this.view.offset = this.dragOrigin.add(
this.pressVector
.subtract(new ascii.Vector(x, y))
.scale(1/this.view.zoom));
}
// Drag wasn't initiated in time, treat this as a drawing event.
if (this.dragOrigin == null && ($.now() - this.pressTimestamp) >= DRAG_LATENCY) {
// TODO: Draw stuff.
this.state.getCell(this.view.screenToCell(position)).value = 'O';
} }
}; };
ascii.Controller.prototype.handleRelease = function(x, y) { ascii.Controller.prototype.handleRelease = function(x, y) {
var position = new ascii.Vector(x, y); var position = new ascii.Vector(x, y);
if (this.pressVector.equals(position)) { // Drag wasn't initiated in time, treat this as a drawing event.
// We should handle this as a 'click' as there was no dragging involved. if (this.dragOrigin == null &&
// Hand off to the state controller, as this will initiate a modification of the diagram itself. ($.now() - this.pressTimestamp) >= DRAG_LATENCY &&
this.state.getCell(this.view.screenToFrame(position)).value = 'P'; position.subtract(this.pressVector).length() > DRAG_ACCURACY) {
// TODO: Draw stuff.
} }
this.pressVector = null; this.pressVector = null;
this.pressTimestamp = 0;
this.dragOrigin = null;
}; };
ascii.Controller.prototype.handleZoom = function(delta) { ascii.Controller.prototype.handleZoom = function(delta) {
@ -62,4 +89,5 @@ ascii.Controller.prototype.installDesktopBindings = function() {
$(this.view.canvas).mousemove(function(e) { $(this.view.canvas).mousemove(function(e) {
controller.handleMove(e.clientX, e.clientY); controller.handleMove(e.clientX, e.clientY);
}); });
$(window).resize(function(e) { controller.view.resizeCanvas() });
}; };

View File

@ -32,10 +32,6 @@ ascii.State = function() {
} }
}; };
/**
* @param {ascii.Vector} vector
* @return {ascii.Cell}
*/
ascii.State.prototype.getCell = function(vector) { ascii.State.prototype.getCell = function(vector) {
return this.cells[Math.round((vector.x-7.5)/15)][Math.round((vector.y+7.5)/15)]; return this.cells[vector.x][vector.y];
}; };

View File

@ -1,20 +1,19 @@
/**
* Functions relating to view operations and management of the screen.
*/
goog.provide('ascii.View'); goog.provide('ascii.View');
goog.require('ascii.Vector'); goog.require('ascii.Vector');
/** @const */ ascii.CHARACTER_PIXELS = 15; /** @const */ ascii.CHARACTER_PIXELS = 15;
/** /**
* Object relating to view operations and management of the screen.
* @constructor * @constructor
*/ */
ascii.View = function(state) { ascii.View = function(state) {
/** @type {Element} */ this.canvas = document.getElementById('ascii-canvas'); /** @type {Element} */ this.canvas = document.getElementById('ascii-canvas');
/** @type {Object} */ this.context = this.canvas.getContext('2d'); /** @type {Object} */ this.context = this.canvas.getContext('2d');
/** @type {number} */ this.zoom = 1; /** @type {number} */ this.zoom = 1;
/** @type {ascii.Vector} */ this.offset = new ascii.Vector(2000, 2000); /** @type {ascii.Vector} */ this.offset = new ascii.Vector(7500, 7500);
/** @type {ascii.State} */ this.state = state; /** @type {ascii.State} */ this.state = state;
this.resizeCanvas(); this.resizeCanvas();
}; };
@ -24,12 +23,18 @@ ascii.View.prototype.resizeCanvas = function() {
this.canvas.height = document.documentElement.clientHeight; this.canvas.height = document.documentElement.clientHeight;
}; };
/**
* Starts the animation loop for the canvas. Should only be called once.
*/
ascii.View.prototype.animate = function() { ascii.View.prototype.animate = function() {
this.render(); this.render();
var view = this; var view = this;
window.requestAnimationFrame(function() { view.animate(); }); window.requestAnimationFrame(function() { view.animate(); });
}; };
/**
* Renders the given state to the canvas.
*/
ascii.View.prototype.render = function() { ascii.View.prototype.render = function() {
this.context.setTransform(1, 0, 0, 1, 0, 0); this.context.setTransform(1, 0, 0, 1, 0, 0);
// Clear the visible area. // Clear the visible area.
@ -38,9 +43,11 @@ ascii.View.prototype.render = function() {
this.context.scale(this.zoom, this.zoom); this.context.scale(this.zoom, this.zoom);
this.context.translate(this.canvas.width/2/this.zoom, this.canvas.height/2/this.zoom); this.context.translate(this.canvas.width/2/this.zoom, this.canvas.height/2/this.zoom);
// TODO: Only render grid lines and cells that are visible.
// Render the grid. // Render the grid.
this.context.lineWidth="1"; this.context.lineWidth="1";
this.context.strokeStyle="#DDDDDD"; this.context.strokeStyle="#EEEEEE";
this.context.beginPath(); this.context.beginPath();
for (var i = 0; i < this.state.cells.length; i++) { for (var i = 0; i < this.state.cells.length; i++) {
this.context.moveTo( this.context.moveTo(
@ -66,8 +73,8 @@ ascii.View.prototype.render = function() {
for (var j = 0; j < this.state.cells[i].length; j++) { for (var j = 0; j < this.state.cells[i].length; j++) {
if (this.state.cells[i][j].value != null) { if (this.state.cells[i][j].value != null) {
this.context.fillText(this.state.cells[i][j].value, this.context.fillText(this.state.cells[i][j].value,
i*ascii.CHARACTER_PIXELS - this.offset.x, i*ascii.CHARACTER_PIXELS - this.offset.x + 3,
j*ascii.CHARACTER_PIXELS - this.offset.y); j*ascii.CHARACTER_PIXELS - this.offset.y - 2);
} }
} }
} }
@ -95,4 +102,23 @@ ascii.View.prototype.frameToScreen = function(vector) {
(vector.y - this.offset.y) * this.zoom + this.canvas.height/2); (vector.y - this.offset.y) * this.zoom + this.canvas.height/2);
}; };
/**
* Given a frame coordinate, return the indices for the nearest cell.
* @param {ascii.Vector} vector
* @return {ascii.Vector}
*/
ascii.View.prototype.frameToCell = function(vector) {
return new ascii.Vector(Math.round((vector.x-7.5)/15), Math.round((vector.y+7.5)/15));
};
/**
* Given a screen coordinate, return the indices for the nearest cell.
* @param {ascii.Vector} vector
* @return {ascii.Vector}
*/
ascii.View.prototype.screenToCell = function(vector) {
return this.frameToCell(this.screenToFrame(vector));
};