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() {
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.View');
/** @const */ var DRAG_LATENCY = 200; // Milliseconds.
/** @const */ var DRAG_ACCURACY = 5; // Pixels.
/**
* @constructor
*/
@ -14,32 +17,56 @@ ascii.Controller = function(view, state) {
/** @type {ascii.View} */ this.view = view;
/** @type {ascii.State} */ this.state = state;
/** @type {ascii.Vector} */ this.dragOrigin;
/** @type {ascii.Vector} */ this.pressVector;
/** @type {number} */ this.pressTimestamp;
this.installDesktopBindings();
};
ascii.Controller.prototype.handlePress = function(x, y) {
this.pressVector = new ascii.Vector(x, y);
this.pressTimestamp = $.now();
};
ascii.Controller.prototype.handleMove = function(x, y) {
if (this.pressVector != null) {
// Drag has started.
this.view.offset.x -= (x - this.pressVector.x)/this.view.zoom;
this.view.offset.y -= (y - this.pressVector.y)/this.view.zoom;
this.pressVector = new ascii.Vector(x, y);
var position = new ascii.Vector(x, y);
if (this.pressVector == null) { return; } // No clicks, so just ignore.
// 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) {
var position = new ascii.Vector(x, y);
if (this.pressVector.equals(position)) {
// We should handle this as a 'click' as there was no dragging involved.
// Hand off to the state controller, as this will initiate a modification of the diagram itself.
this.state.getCell(this.view.screenToFrame(position)).value = 'P';
// Drag wasn't initiated in time, treat this as a drawing event.
if (this.dragOrigin == null &&
($.now() - this.pressTimestamp) >= DRAG_LATENCY &&
position.subtract(this.pressVector).length() > DRAG_ACCURACY) {
// TODO: Draw stuff.
}
this.pressVector = null;
this.pressTimestamp = 0;
this.dragOrigin = null;
};
ascii.Controller.prototype.handleZoom = function(delta) {
@ -62,4 +89,5 @@ ascii.Controller.prototype.installDesktopBindings = function() {
$(this.view.canvas).mousemove(function(e) {
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) {
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.require('ascii.Vector');
/** @const */ ascii.CHARACTER_PIXELS = 15;
/**
* Object relating to view operations and management of the screen.
* @constructor
*/
ascii.View = function(state) {
/** @type {Element} */ this.canvas = document.getElementById('ascii-canvas');
/** @type {Object} */ this.context = this.canvas.getContext('2d');
/** @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;
this.resizeCanvas();
};
@ -24,12 +23,18 @@ ascii.View.prototype.resizeCanvas = function() {
this.canvas.height = document.documentElement.clientHeight;
};
/**
* Starts the animation loop for the canvas. Should only be called once.
*/
ascii.View.prototype.animate = function() {
this.render();
var view = this;
window.requestAnimationFrame(function() { view.animate(); });
};
/**
* Renders the given state to the canvas.
*/
ascii.View.prototype.render = function() {
this.context.setTransform(1, 0, 0, 1, 0, 0);
// Clear the visible area.
@ -38,9 +43,11 @@ ascii.View.prototype.render = function() {
this.context.scale(this.zoom, 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.
this.context.lineWidth="1";
this.context.strokeStyle="#DDDDDD";
this.context.strokeStyle="#EEEEEE";
this.context.beginPath();
for (var i = 0; i < this.state.cells.length; i++) {
this.context.moveTo(
@ -66,8 +73,8 @@ ascii.View.prototype.render = function() {
for (var j = 0; j < this.state.cells[i].length; j++) {
if (this.state.cells[i][j].value != null) {
this.context.fillText(this.state.cells[i][j].value,
i*ascii.CHARACTER_PIXELS - this.offset.x,
j*ascii.CHARACTER_PIXELS - this.offset.y);
i*ascii.CHARACTER_PIXELS - this.offset.x + 3,
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);
};
/**
* 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));
};