* Update jquer and closure compiler * Move to es6 classes, arrow functions, for/of * Move to es6 style modules * Add .DS_Store to .gitignore * Remove note to update compile.shmaster
@@ -3,3 +3,4 @@ closure-library/* | |||
.settings/* | |||
*~ | |||
_site/* | |||
.DS_Store |
@@ -13,4 +13,3 @@ Goto: http://localhost:8000/index.html | |||
When developing, use the Google JS linter, gjslint. | |||
Adding new source files will require a change to compile.sh. |
@@ -1,13 +1,5 @@ | |||
java -client -jar closure-compiler.jar \ | |||
--js js-lib/common.js \ | |||
--js js-lib/view.js \ | |||
--js js-lib/draw.js \ | |||
--js js-lib/draw-select.js \ | |||
--js js-lib/state.js \ | |||
--js js-lib/controller.js \ | |||
--js js-lib/drive-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 \ | |||
--js js-lib/*.js \ | |||
--warning_level=VERBOSE --formatting=PRETTY_PRINT --language_in=ECMASCRIPT6 --compilation_level=ADVANCED_OPTIMIZATIONS \ | |||
--externs=jquery-3.1-externs.js \ | |||
> js-compiled.js |
@@ -90,7 +90,7 @@ button { | |||
} | |||
.info-description { | |||
vertical-align: text-bottom; | |||
vertical-align: text-bottom; | |||
margin-left: 10px; | |||
display: inline-block; | |||
height: 30px; | |||
@@ -141,7 +141,7 @@ button { | |||
text-align: center; | |||
padding-left: 0px; | |||
} | |||
/* Move file tools to the bottom. */ | |||
#file-tools { | |||
left: 0px; | |||
@@ -557,7 +557,7 @@ textarea { | |||
<div class="dialog-button-bar"> | |||
<button class="close-dialog-button">Close</button> | |||
<button id="import-submit-button">Import</button> | |||
</div> | |||
</div> | |||
</div> | |||
<!-- These dialogs are handled seperately. --> | |||
@@ -587,7 +587,7 @@ textarea { | |||
<canvas id="ascii-canvas"></canvas> | |||
<script src="jquery-1.9.1.min.js"></script> | |||
<script src="jquery-3.1.1.min.js"></script> | |||
<script src="js-compiled.js"></script> | |||
<script src="https://apis.google.com/js/client.js?onload=window.gapiCallback"></script> | |||
@@ -2,241 +2,140 @@ | |||
* Common classes and constants. | |||
*/ | |||
// Define namespace for closure compiler but don't make it a requirement. | |||
try { | |||
goog.provide('ascii'); | |||
throw 1; | |||
} catch (e) { | |||
/** type {Object} */ | |||
window.ascii = window.ascii || {}; | |||
} | |||
/** @const */ var MAX_GRID_WIDTH = 2000; | |||
/** @const */ var MAX_GRID_HEIGHT = 600; | |||
/** @const */ var SPECIAL_VALUE = '+'; | |||
/** @const */ var ALT_SPECIAL_VALUE = '^'; | |||
/** @const */ var SPECIAL_ARROW_LEFT = '<'; | |||
/** @const */ var SPECIAL_ARROW_UP = '^'; | |||
/** @const */ var SPECIAL_ARROW_RIGHT = '>'; | |||
/** @const */ var SPECIAL_ARROW_DOWN = 'v'; | |||
/** @const */ var SPECIAL_VALUES = ['+', '\u2012', '\u2013', '-', '|']; | |||
/** @const */ var ALT_SPECIAL_VALUES = ['>', '<', '^', 'v']; | |||
/** @const */ var ALL_SPECIAL_VALUES = SPECIAL_VALUES.concat(ALT_SPECIAL_VALUES); | |||
/** @const */ var MAX_UNDO = 50; | |||
/** @const */ var SPECIAL_LINE_H = '-'; | |||
/** @const */ var SPECIAL_LINE_V = '|'; | |||
/** @const */ var ERASE_CHAR = '\u2009'; | |||
/** @const */ var DRAG_LATENCY = 150; // Milliseconds. | |||
/** @const */ var DRAG_ACCURACY = 6; // Pixels. | |||
/** @const */ var CHAR_PIXELS_H = 9; | |||
/** @const */ var CHAR_PIXELS_V = 17; | |||
/** @const */ var RENDER_PADDING_CELLS = 3; | |||
/** @const */ var KEY_RETURN = '<enter>'; | |||
/** @const */ var KEY_BACKSPACE = '<backspace>'; | |||
/** @const */ var KEY_COPY = '<copy>'; | |||
/** @const */ var KEY_PASTE = '<paste>'; | |||
/** @const */ var KEY_CUT = '<cut>'; | |||
/** @const */ var KEY_UP = '<up>'; | |||
/** @const */ var KEY_DOWN = '<down>'; | |||
/** @const */ var KEY_LEFT = '<left>'; | |||
/** @const */ var KEY_RIGHT = '<right>'; | |||
// http://stackoverflow.com/questions/4817029/whats-the-best-way-to-detect-a-touch-screen-device-using-javascript | |||
/** @const */ var TOUCH_ENABLED = | |||
'ontouchstart' in window || | |||
'onmsgesturechange' in window; | |||
/** | |||
* Stores a 2D vector. | |||
* | |||
* @constructor | |||
* @param {number} x | |||
* @param {number} y | |||
*/ | |||
ascii.Vector = function(x, y) { | |||
/** type {Number} */ this.x = x; | |||
/** type {Number} */ this.y = y; | |||
}; | |||
/** | |||
* @param {ascii.Vector} other | |||
* @return {boolean} | |||
*/ | |||
ascii.Vector.prototype.equals = function(other) { | |||
return (other != null) && (this.x == other.x) && (this.y == other.y); | |||
}; | |||
/** | |||
* @param {ascii.Vector} other | |||
* @return {ascii.Vector} | |||
*/ | |||
ascii.Vector.prototype.subtract = function(other) { | |||
return new ascii.Vector(this.x - other.x, this.y - other.y); | |||
}; | |||
/** | |||
* @param {ascii.Vector} other | |||
* @return {ascii.Vector} | |||
*/ | |||
ascii.Vector.prototype.add = function(other) { | |||
return new ascii.Vector(this.x + other.x, this.y + other.y); | |||
}; | |||
/** | |||
* @return {ascii.Vector} | |||
*/ | |||
ascii.Vector.prototype.clone = function() { | |||
return new ascii.Vector(this.x, this.y); | |||
}; | |||
/** @return {number} */ | |||
ascii.Vector.prototype.length = function() { | |||
return Math.sqrt(this.x * this.x + this.y * this.y); | |||
}; | |||
/** | |||
* @param {number} scale | |||
* @return {ascii.Vector} | |||
*/ | |||
ascii.Vector.prototype.scale = function(scale) { | |||
return new ascii.Vector(this.x * scale, this.y * scale); | |||
}; | |||
import { ERASE_CHAR, ALL_SPECIAL_VALUES } from './constants'; | |||
import Vector from './vector'; | |||
/** | |||
* Represents a box with normalized position vectors. | |||
* | |||
* @constructor | |||
* @param {ascii.Vector} a | |||
* @param {ascii.Vector} b | |||
*/ | |||
ascii.Box = function(a, b) { | |||
/** type {Number} */ this.startX = Math.min(a.x, b.x); | |||
/** type {Number} */ this.startY = Math.min(a.y, b.y); | |||
/** type {Number} */ this.endX = Math.max(a.x, b.x); | |||
/** type {Number} */ this.endY = Math.max(a.y, b.y); | |||
}; | |||
/** @return {ascii.Vector} */ | |||
ascii.Box.prototype.topLeft = function() { | |||
return new ascii.Vector(this.startX, this.startY); | |||
}; | |||
/** @return {ascii.Vector} */ | |||
ascii.Box.prototype.bottomRight = function() { | |||
return new ascii.Vector(this.endX, this.endY); | |||
}; | |||
/** @return {boolean} */ | |||
ascii.Box.prototype.contains = function(position) { | |||
return position.x >= this.startX && position.x <= this.endX && position.y >= this.startY && position.y <= this.endY; | |||
}; | |||
/** @const */ var DIR_LEFT = new ascii.Vector(-1, 0); | |||
/** @const */ var DIR_RIGHT = new ascii.Vector(1, 0); | |||
/** @const */ var DIR_UP = new ascii.Vector(0, -1); | |||
/** @const */ var DIR_DOWN = new ascii.Vector(0, 1); | |||
/** @const */ var DIRECTIONS = [DIR_LEFT, DIR_RIGHT, DIR_UP, DIR_DOWN]; | |||
export class Box { | |||
/** | |||
* @param {Vector} a | |||
* @param {Vector} b | |||
*/ | |||
constructor(a, b) { | |||
/** type {number} */ this.startX = Math.min(a.x, b.x); | |||
/** type {number} */ this.startY = Math.min(a.y, b.y); | |||
/** type {number} */ this.endX = Math.max(a.x, b.x); | |||
/** type {number} */ this.endY = Math.max(a.y, b.y); | |||
} | |||
/** @return {Vector} */ | |||
topLeft() { | |||
return new Vector(this.startX, this.startY); | |||
} | |||
/** @return {Vector} */ | |||
bottomRight() { | |||
return new Vector(this.endX, this.endY); | |||
} | |||
/** @return {boolean} */ | |||
contains(position) { | |||
return position.x >= this.startX && position.x <= this.endX | |||
&& position.y >= this.startY && position.y <= this.endY; | |||
} | |||
} | |||
/** | |||
* An individual cell within the diagram and it's current value. | |||
* | |||
* @constructor | |||
*/ | |||
ascii.Cell = function() { | |||
/** @type {?string} */ this.value = null; | |||
/** @type {?string} */ this.scratchValue = null; | |||
}; | |||
/** @return {?string} */ | |||
ascii.Cell.prototype.getRawValue = function() { | |||
return (this.scratchValue != null ? this.scratchValue : this.value); | |||
}; | |||
/** @return {boolean} */ | |||
ascii.Cell.prototype.isSpecial = function() { | |||
return ALL_SPECIAL_VALUES.indexOf(this.getRawValue()) != -1; | |||
}; | |||
/** @return {boolean} */ | |||
ascii.Cell.prototype.isEmpty = function() { | |||
return this.value == null && this.scratchValue == null; | |||
}; | |||
/** @return {boolean} */ | |||
ascii.Cell.prototype.hasScratch = function() { | |||
return this.scratchValue != null; | |||
}; | |||
/** @return {boolean} */ | |||
ascii.Cell.prototype.isErase = function() { | |||
return this.scratchValue == ERASE_CHAR; | |||
}; | |||
export class Cell { | |||
constructor() { | |||
/** @type {?string} */ this.value = null; | |||
/** @type {?string} */ this.scratchValue = null; | |||
} | |||
/** @return {?string} */ | |||
getRawValue() { | |||
return (this.scratchValue != null ? this.scratchValue : this.value); | |||
} | |||
/** @return {boolean} */ | |||
isSpecial() { | |||
return ALL_SPECIAL_VALUES.indexOf(this.getRawValue()) != -1; | |||
} | |||
/** @return {boolean} */ | |||
isEmpty() { | |||
return this.value == null && this.scratchValue == null; | |||
} | |||
/** @return {boolean} */ | |||
hasScratch() { | |||
return this.scratchValue != null; | |||
} | |||
/** @return {boolean} */ | |||
isErase() { | |||
return this.scratchValue == ERASE_CHAR; | |||
} | |||
} | |||
/** | |||
* The context for a cell, i.e. the status of the cells around it. | |||
* | |||
* @param {boolean} left | |||
* @param {boolean} right | |||
* @param {boolean} up | |||
* @param {boolean} down | |||
* @constructor | |||
*/ | |||
ascii.CellContext = function(left, right, up, down) { | |||
/** @type {boolean} */ this.left = left; | |||
/** @type {boolean} */ this.right = right; | |||
/** @type {boolean} */ this.up = up; | |||
/** @type {boolean} */ this.down = down; | |||
/** @type {boolean} */ this.leftup = false; | |||
/** @type {boolean} */ this.rightup = false; | |||
/** @type {boolean} */ this.leftdown = false; | |||
/** @type {boolean} */ this.rightdown = false; | |||
}; | |||
/** | |||
* Returns the total number of surrounding special cells. | |||
* @return {number} | |||
*/ | |||
ascii.CellContext.prototype.sum = function() { | |||
return this.left + this.right + this.up + this.down; | |||
}; | |||
/** | |||
* Returns the total number of surrounding special cells. | |||
* @return {number} | |||
*/ | |||
ascii.CellContext.prototype.extendedSum = function() { | |||
return this.left + this.right + this.up + this.down + this.leftup + this.leftdown + this.rightup + this.rightdown; | |||
}; | |||
export class CellContext { | |||
/** | |||
* @param {boolean} left | |||
* @param {boolean} right | |||
* @param {boolean} up | |||
* @param {boolean} down | |||
*/ | |||
constructor(left, right, up, down) { | |||
/** @type {boolean} */ this.left = left; | |||
/** @type {boolean} */ this.right = right; | |||
/** @type {boolean} */ this.up = up; | |||
/** @type {boolean} */ this.down = down; | |||
/** @type {boolean} */ this.leftup = false; | |||
/** @type {boolean} */ this.rightup = false; | |||
/** @type {boolean} */ this.leftdown = false; | |||
/** @type {boolean} */ this.rightdown = false; | |||
} | |||
/** | |||
* Returns the total number of surrounding special cells. | |||
* @return {number} | |||
*/ | |||
sum() { | |||
return this.left + this.right + this.up + this.down; | |||
} | |||
/** | |||
* Returns the total number of surrounding special cells. | |||
* @return {number} | |||
*/ | |||
extendedSum() { | |||
return this.left + this.right + this.up + this.down | |||
+ this.leftup + this.leftdown + this.rightup + this.rightdown; | |||
} | |||
} | |||
/** | |||
* A pair of a vector and a string value. Used in history management. | |||
* @constructor | |||
* @struct | |||
* @param {ascii.Vector} position | |||
* @param {string|null} value | |||
*/ | |||
ascii.MappedValue = function(position, value) { | |||
this.position = position; | |||
this.value = value; | |||
}; | |||
export class MappedValue { | |||
/** | |||
* @param {Vector} position | |||
* @param {string|null} value | |||
*/ | |||
constructor(position, value) { | |||
this.position = position; | |||
this.value = value; | |||
} | |||
} | |||
/** | |||
* A pair of a vector and a cell. Used in history management. | |||
* @constructor | |||
* @struct | |||
* @param {ascii.Vector} position | |||
* @param {ascii.Cell} cell | |||
*/ | |||
ascii.MappedCell = function(position, cell) { | |||
this.position = position; | |||
this.cell = cell; | |||
}; | |||
export class MappedCell { | |||
/** | |||
* @param {Vector} position | |||
* @param {Cell} cell | |||
*/ | |||
constructor(position, cell) { | |||
this.position = position; | |||
this.cell = cell; | |||
} | |||
} |
@@ -0,0 +1,51 @@ | |||
import Vector from './vector'; | |||
export const MAX_GRID_WIDTH = 2000; | |||
export const MAX_GRID_HEIGHT = 600; | |||
export const SPECIAL_VALUE = '+'; | |||
export const ALT_SPECIAL_VALUE = '^'; | |||
export const SPECIAL_ARROW_LEFT = '<'; | |||
export const SPECIAL_ARROW_UP = '^'; | |||
export const SPECIAL_ARROW_RIGHT = '>'; | |||
export const SPECIAL_ARROW_DOWN = 'v'; | |||
export const SPECIAL_VALUES = ['+', '\u2012', '\u2013', '-', '|']; | |||
export const ALT_SPECIAL_VALUES = ['>', '<', '^', 'v']; | |||
export const ALL_SPECIAL_VALUES = SPECIAL_VALUES.concat(ALT_SPECIAL_VALUES); | |||
export const MAX_UNDO = 50; | |||
export const SPECIAL_LINE_H = '-'; | |||
export const SPECIAL_LINE_V = '|'; | |||
export const ERASE_CHAR = '\u2009'; | |||
export const DRAG_LATENCY = 150; // Milliseconds. | |||
export const DRAG_ACCURACY = 6; // Pixels. | |||
export const CHAR_PIXELS_H = 9; | |||
export const CHAR_PIXELS_V = 17; | |||
export const RENDER_PADDING_CELLS = 3; | |||
export const KEY_RETURN = '<enter>'; | |||
export const KEY_BACKSPACE = '<backspace>'; | |||
export const KEY_COPY = '<copy>'; | |||
export const KEY_PASTE = '<paste>'; | |||
export const KEY_CUT = '<cut>'; | |||
export const KEY_UP = '<up>'; | |||
export const KEY_DOWN = '<down>'; | |||
export const KEY_LEFT = '<left>'; | |||
export const KEY_RIGHT = '<right>'; | |||
// http://stackoverflow.com/questions/4817029/whats-the-best-way-to-detect-a-touch-screen-device-using-javascript | |||
export const TOUCH_ENABLED = | |||
'ontouchstart' in window || | |||
'onmsgesturechange' in window; | |||
export const DIR_LEFT = new Vector(-1, 0); | |||
export const DIR_RIGHT = new Vector( 1, 0); | |||
export const DIR_UP = new Vector( 0, -1); | |||
export const DIR_DOWN = new Vector( 0, 1); | |||
export const DIRECTIONS = [DIR_LEFT, DIR_RIGHT, DIR_UP, DIR_DOWN]; |
@@ -1,8 +1,23 @@ | |||
import * as c from './constants'; | |||
import Vector from './vector'; | |||
import View from './view'; | |||
import State from './state'; | |||
import DrawSelect from './draw-select'; | |||
import { | |||
DrawFunction, | |||
DrawBox, | |||
DrawLine, | |||
DrawFreeform, | |||
DrawErase, | |||
DrawMove, | |||
DrawText, | |||
} from './draw'; | |||
/** | |||
* Different modes of control. | |||
* @const | |||
*/ | |||
var Mode = { | |||
const Mode = { | |||
NONE: 0, | |||
DRAG: 1, | |||
DRAW: 2 | |||
@@ -10,253 +25,256 @@ var Mode = { | |||
/** | |||
* Handles user input events and modifies state. | |||
* | |||
* @constructor | |||
* @param {ascii.View} view | |||
* @param {ascii.State} state | |||
*/ | |||
ascii.Controller = function(view, state) { | |||
/** @type {ascii.View} */ this.view = view; | |||
/** @type {ascii.State} */ this.state = 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.dragOriginCell; | |||
export default class Controller { | |||
/** | |||
* @param {View} view | |||
* @param {State} state | |||
*/ | |||
constructor(view, state) { | |||
/** @type {View} */ this.view = view; | |||
/** @type {State} */ this.state = state; | |||
this.installBindings(); | |||
}; | |||
/** | |||
* @param {ascii.Vector} position | |||
*/ | |||
ascii.Controller.prototype.startDraw = function(position) { | |||
this.mode = Mode.DRAW; | |||
this.drawFunction.start(this.view.screenToCell(position)); | |||
}; | |||
/** @type {DrawFunction} */ this.drawFunction = new DrawBox(state); | |||
/** | |||
* @param {ascii.Vector} position | |||
*/ | |||
ascii.Controller.prototype.startDrag = function(position) { | |||
this.mode = Mode.DRAG; | |||
this.dragOrigin = position; | |||
this.dragOriginCell = this.view.offset; | |||
}; | |||
/** @type {number} */ this.mode = Mode.NONE; | |||
/** @type {Vector} */ this.dragOrigin; | |||
/** @type {Vector} */ this.dragOriginCell; | |||
/** | |||
* @param {ascii.Vector} position | |||
*/ | |||
ascii.Controller.prototype.handleMove = function(position) { | |||
var moveCell = this.view.screenToCell(position); | |||
/** @type {Vector} */ this.lastMoveCell = null; | |||
// First move event, make sure we don't blow up here. | |||
if (this.lastMoveCell == null) { | |||
this.lastMoveCell = moveCell; | |||
this.installBindings(); | |||
} | |||
// Update the cursor pointer, depending on the draw function. | |||
if (!moveCell.equals(this.lastMoveCell)) { | |||
this.view.canvas.style.cursor = this.drawFunction.getCursor(moveCell); | |||
/** | |||
* @param {Vector} position | |||
*/ | |||
startDraw(position) { | |||
this.mode = Mode.DRAW; | |||
this.drawFunction.start(this.view.screenToCell(position)); | |||
} | |||
// 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); | |||
/** | |||
* @param {Vector} position | |||
*/ | |||
startDrag(position) { | |||
this.mode = Mode.DRAG; | |||
this.dragOrigin = position; | |||
this.dragOriginCell = this.view.offset; | |||
} | |||
// Drag in progress, update the view origin. | |||
if (this.mode == Mode.DRAG) { | |||
this.view.setOffset(this.dragOriginCell.add( | |||
this.dragOrigin | |||
.subtract(position) | |||
.scale(1 / this.view.zoom))); | |||
/** | |||
* @param {Vector} position | |||
*/ | |||
handleMove(position) { | |||
var moveCell = this.view.screenToCell(position); | |||
// First move event, make sure we don't blow up here. | |||
if (this.lastMoveCell == null) { | |||
this.lastMoveCell = moveCell; | |||
} | |||
// 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.mode == Mode.DRAG) { | |||
this.view.setOffset(this.dragOriginCell.add( | |||
this.dragOrigin | |||
.subtract(position) | |||
.scale(1 / this.view.zoom))); | |||
} | |||
this.lastMoveCell = moveCell; | |||
} | |||
this.lastMoveCell = moveCell; | |||
}; | |||
/** | |||
* Ends the current operation. | |||
*/ | |||
ascii.Controller.prototype.endAll = function() { | |||
if (this.mode == Mode.DRAW) { | |||
this.drawFunction.end(); | |||
/** | |||
* Ends the current operation. | |||
*/ | |||
endAll() { | |||
if (this.mode == Mode.DRAW) { | |||
this.drawFunction.end(); | |||
} | |||
// Cleanup state. | |||
this.mode = Mode.NONE; | |||
this.dragOrigin = null; | |||
this.dragOriginCell = null; | |||
this.lastMoveCell = null; | |||
} | |||
// Cleanup state. | |||
this.mode = Mode.NONE; | |||
this.dragOrigin = null; | |||
this.dragOriginCell = null; | |||
this.lastMoveCell = null; | |||
}; | |||
/** | |||
* Installs input bindings for common use cases devices. | |||
*/ | |||
ascii.Controller.prototype.installBindings = function() { | |||
var controller = this; | |||
$(window).resize(function(e) { controller.view.resizeCanvas() }); | |||
$('#draw-tools > button.tool').click(function(e) { | |||
$('#text-tool-widget').hide(0); | |||
this.handleDrawButton(e.target.id); | |||
}.bind(this)); | |||
$('#file-tools > button.tool').click(function(e) { | |||
this.handleFileButton(e.target.id); | |||
}.bind(this)); | |||
$('button.close-dialog-button').click(function(e) { | |||
$('.dialog').removeClass('visible'); | |||
}.bind(this)); | |||
$('#import-submit-button').click(function(e) { | |||
this.state.clear(); | |||
this.state.fromText($('#import-area').val(), | |||
this.view.screenToCell(new ascii.Vector( | |||
this.view.canvas.width / 2, | |||
this.view.canvas.height / 2))); | |||
this.state.commitDraw(); | |||
$('#import-area').val(''); | |||
$('.dialog').removeClass('visible'); | |||
}.bind(this)); | |||
/** | |||
* Installs input bindings for common use cases devices. | |||
*/ | |||
installBindings() { | |||
$(window).resize(e => { this.view.resizeCanvas() }); | |||
$('#draw-tools > button.tool').click(e => { | |||
$('#text-tool-widget').hide(0); | |||
this.handleDrawButton(e.target.id); | |||
}); | |||
$('#file-tools > button.tool').click(e => { | |||
this.handleFileButton(e.target.id); | |||
}); | |||
$('button.close-dialog-button').click(e => { | |||
$('.dialog').removeClass('visible'); | |||
}); | |||
$('#import-submit-button').click(e => { | |||
this.state.clear(); | |||
this.state.fromText( | |||
/** @type {string} */ | |||
($('#import-area').val()), | |||
this.view.screenToCell(new Vector( | |||
this.view.canvas.width / 2, | |||
this.view.canvas.height / 2))); | |||
this.state.commitDraw(); | |||
$('#import-area').val(''); | |||
$('.dialog').removeClass('visible'); | |||
}); | |||
$('#use-lines-button').click(e => { | |||
$('.dialog').removeClass('visible'); | |||
this.view.setUseLines(true); | |||
}); | |||
$('#use-ascii-button').click(e => { | |||
$('.dialog').removeClass('visible'); | |||
this.view.setUseLines(false); | |||
}); | |||
$(window).keypress(e => { | |||
this.handleKeyPress(e); | |||
}); | |||
$(window).keydown(e => { | |||
this.handleKeyDown(e); | |||
}); | |||
// Bit of a hack, just triggers the text tool to get a new value. | |||
$('#text-tool-input, #freeform-tool-input').keyup(() => { | |||
this.drawFunction.handleKey(''); | |||
}); | |||
$('#text-tool-input, #freeform-tool-input').change(() => { | |||
this.drawFunction.handleKey(''); | |||
}); | |||
$('#text-tool-close').click(() => { | |||
$('#text-tool-widget').hide(); | |||
this.state.commitDraw(); | |||
}); | |||
} | |||
$('#use-lines-button').click(function(e) { | |||
/** | |||
* Handles the buttons in the UI. | |||
* @param {string} id The ID of the element clicked. | |||
*/ | |||
handleDrawButton(id) { | |||
$('#draw-tools > button.tool').removeClass('active'); | |||
$('#' + id).toggleClass('active'); | |||
$('.dialog').removeClass('visible'); | |||
this.view.setUseLines(true); | |||
}.bind(this)); | |||
$('#use-ascii-button').click(function(e) { | |||
$('.dialog').removeClass('visible'); | |||
this.view.setUseLines(false); | |||
}.bind(this)); | |||
$(window).keypress(function(e) { | |||
this.handleKeyPress(e); | |||
}.bind(this)); | |||
$(window).keydown(function(e) { | |||
this.handleKeyDown(e); | |||
}.bind(this)); | |||
// Bit of a hack, just triggers the text tool to get a new value. | |||
$('#text-tool-input, #freeform-tool-input').keyup(function(){ | |||
this.drawFunction.handleKey(''); | |||
}.bind(this)); | |||
$('#text-tool-input, #freeform-tool-input').change(function(){ | |||
this.drawFunction.handleKey(''); | |||
}.bind(this)); | |||
$('#text-tool-close').click(function(){ | |||
$('#text-tool-widget').hide(); | |||
// Install the right draw tool based on button pressed. | |||
if (id == 'box-button') { | |||
this.drawFunction = new DrawBox(this.state); | |||
} | |||
if (id == 'line-button') { | |||
this.drawFunction = new DrawLine(this.state, false); | |||
} | |||
if (id == 'arrow-button') { | |||
this.drawFunction = new DrawLine(this.state, true); | |||
} | |||
if (id == 'freeform-button') { | |||
this.drawFunction = new DrawFreeform(this.state, "X"); | |||
} | |||
if (id == 'erase-button') { | |||
this.drawFunction = new DrawErase(this.state); | |||
} | |||
if (id == 'move-button') { | |||
this.drawFunction = new DrawMove(this.state); | |||
} | |||
if (id == 'text-button') { | |||
this.drawFunction = new DrawText(this.state, this.view); | |||
} | |||
if (id == 'select-button') { | |||
this.drawFunction = new DrawSelect(this.state); | |||
} | |||
this.state.commitDraw(); | |||
}.bind(this)); | |||
}; | |||
/** | |||
* Handles the buttons in the UI. | |||
* @param {string} id The ID of the element clicked. | |||
*/ | |||
ascii.Controller.prototype.handleDrawButton = function(id) { | |||
$('#draw-tools > button.tool').removeClass('active'); | |||
$('#' + id).toggleClass('active'); | |||
$('.dialog').removeClass('visible'); | |||
// Install the right draw tool based on button pressed. | |||
if (id == 'box-button') { | |||
this.drawFunction = new ascii.DrawBox(this.state); | |||
} | |||
if (id == 'line-button') { | |||
this.drawFunction = new ascii.DrawLine(this.state, false); | |||
} | |||
if (id == 'arrow-button') { | |||
this.drawFunction = new ascii.DrawLine(this.state, true); | |||
} | |||
if (id == 'freeform-button') { | |||
this.drawFunction = new ascii.DrawFreeform(this.state, "X"); | |||
} | |||
if (id == 'erase-button') { | |||
this.drawFunction = new ascii.DrawErase(this.state); | |||
} | |||
if (id == 'move-button') { | |||
this.drawFunction = new ascii.DrawMove(this.state); | |||
} | |||
if (id == 'text-button') { | |||
this.drawFunction = new ascii.DrawText(this.state, this.view); | |||
} | |||
if (id == 'select-button') { | |||
this.drawFunction = new ascii.DrawSelect(this.state); | |||
} | |||
this.state.commitDraw(); | |||
this.view.canvas.focus(); | |||
}; | |||
/** | |||
* Handles the buttons in the UI. | |||
* @param {string} id The ID of the element clicked. | |||
*/ | |||
ascii.Controller.prototype.handleFileButton = function(id) { | |||
$('.dialog').removeClass('visible'); | |||
$('#' + id + '-dialog').toggleClass('visible'); | |||
if (id == 'import-button') { | |||
$('#import-area').val(''); | |||
$('#import-area').focus(); | |||
} | |||
if (id == 'export-button') { | |||
$('#export-area').val(this.state.outputText()); | |||
$('#export-area').select(); | |||
this.view.canvas.focus(); | |||
} | |||
if (id == 'clear-button') { | |||
this.state.clear(); | |||
} | |||
if (id == 'undo-button') { | |||
this.state.undo(); | |||
} | |||
if (id == 'redo-button') { | |||
this.state.redo(); | |||
} | |||
}; | |||
/** | |||
* Handles key presses. | |||
* @param {Object} event | |||
*/ | |||
ascii.Controller.prototype.handleKeyPress = function(event) { | |||
if (!event.ctrlKey && !event.metaKey && event.keyCode != 13) { | |||
this.drawFunction.handleKey(String.fromCharCode(event.keyCode)); | |||
/** | |||
* Handles the buttons in the UI. | |||
* @param {string} id The ID of the element clicked. | |||
*/ | |||
handleFileButton(id) { | |||
$('.dialog').removeClass('visible'); | |||
$('#' + id + '-dialog').toggleClass('visible'); | |||
if (id == 'import-button') { | |||
$('#import-area').val(''); | |||
$('#import-area').focus(); | |||
} | |||
if (id == 'export-button') { | |||
$('#export-area').val(this.state.outputText()); | |||
$('#export-area').select(); | |||
} | |||
if (id == 'clear-button') { | |||
this.state.clear(); | |||
} | |||
if (id == 'undo-button') { | |||
this.state.undo(); | |||
} | |||
if (id == 'redo-button') { | |||
this.state.redo(); | |||
} | |||
} | |||
}; | |||
/** | |||
* Handles key down events. | |||
* @param {Object} event | |||
*/ | |||
ascii.Controller.prototype.handleKeyDown = function(event) { | |||
// Override some special characters so that they can be handled in one place. | |||
var specialKeyCode = null; | |||
if (event.ctrlKey || event.metaKey) { | |||
if (event.keyCode == 67) { specialKeyCode = KEY_COPY; } | |||
if (event.keyCode == 86) { specialKeyCode = KEY_PASTE; } | |||
if (event.keyCode == 90) { this.state.undo(); } | |||
if (event.keyCode == 89) { this.state.redo(); } | |||
if (event.keyCode == 88) { specialKeyCode = KEY_CUT; } | |||
/** | |||
* Handles key presses. | |||
* @param {jQuery.Event} event | |||
*/ | |||
handleKeyPress(event) { | |||
if (!event.ctrlKey && !event.metaKey && event.keyCode != 13) { | |||
this.drawFunction.handleKey(String.fromCharCode(event.keyCode)); | |||
} | |||
} | |||
if (event.keyCode == 8) { specialKeyCode = KEY_BACKSPACE; } | |||
if (event.keyCode == 13) { specialKeyCode = KEY_RETURN; } | |||
if (event.keyCode == 38) { specialKeyCode = KEY_UP; } | |||
if (event.keyCode == 40) { specialKeyCode = KEY_DOWN; } | |||
if (event.keyCode == 37) { specialKeyCode = KEY_LEFT; } | |||
if (event.keyCode == 39) { specialKeyCode = KEY_RIGHT; } | |||
if (specialKeyCode != null) { | |||
//event.preventDefault(); | |||
//event.stopPropagation(); | |||
this.drawFunction.handleKey(specialKeyCode); | |||
/** | |||
* Handles key down events. | |||
* @param {jQuery.Event} event | |||
*/ | |||
handleKeyDown(event) { | |||
// Override some special characters so that they can be handled in one place. | |||
var specialKeyCode = null; | |||
if (event.ctrlKey || event.metaKey) { | |||
if (event.keyCode == 67) { specialKeyCode = c.KEY_COPY; } | |||
if (event.keyCode == 86) { specialKeyCode = c.KEY_PASTE; } | |||
if (event.keyCode == 90) { this.state.undo(); } | |||
if (event.keyCode == 89) { this.state.redo(); } | |||
if (event.keyCode == 88) { specialKeyCode = c.KEY_CUT; } | |||
} | |||
if (event.keyCode == 8) { specialKeyCode = c.KEY_BACKSPACE; } | |||
if (event.keyCode == 13) { specialKeyCode = c.KEY_RETURN; } | |||
if (event.keyCode == 38) { specialKeyCode = c.KEY_UP; } | |||
if (event.keyCode == 40) { specialKeyCode = c.KEY_DOWN; } | |||
if (event.keyCode == 37) { specialKeyCode = c.KEY_LEFT; } | |||
if (event.keyCode == 39) { specialKeyCode = c.KEY_RIGHT; } | |||
if (specialKeyCode != null) { | |||
//event.preventDefault(); | |||
//event.stopPropagation(); | |||
this.drawFunction.handleKey(specialKeyCode); | |||
} | |||
} | |||
}; | |||
} | |||
@@ -1,138 +1,147 @@ | |||
import * as c from './constants'; | |||
import Vector from './vector'; | |||
import State from './state'; | |||
import { MappedValue, Box } from './common'; | |||
import { DrawFunction, DrawErase } from './draw'; | |||
/** | |||
* @constructor | |||
* @implements {ascii.DrawFunction} | |||
* @param {ascii.State} state | |||
* @implements {DrawFunction} | |||
*/ | |||
ascii.DrawSelect = function(state) { | |||
this.state = state; | |||
/** @type {ascii.Vector} */ | |||
this.startPosition = null; | |||
/** @type {ascii.Vector} */ | |||
this.endPosition = null; | |||
/** @type {ascii.Vector} */ | |||
this.dragStart = null; | |||
/** @type {ascii.Vector} */ | |||
this.dragEnd = null; | |||
/** @type {boolean} */ | |||
this.finished = true; | |||
export default class DrawSelect { | |||
/** | |||
* @param {State} state | |||
*/ | |||
constructor(state) { | |||
this.state = state; | |||
/** @type {Vector} */ | |||
this.startPosition = null; | |||
/** @type {Vector} */ | |||
this.endPosition = null; | |||
/** @type {Vector} */ | |||
this.dragStart = null; | |||
/** @type {Vector} */ | |||
this.dragEnd = null; | |||
/** @type {Array.<ascii.MappedValue>} */ | |||
this.selectedCells = null; | |||
}; | |||
/** @type {boolean} */ | |||
this.finished = true; | |||
/** @inheritDoc */ | |||
ascii.DrawSelect.prototype.start = function(position) { | |||
// Must be dragging. | |||
if (this.startPosition != null && | |||
this.endPosition != null && | |||
this.getSelectedBox().contains(position)) { | |||
this.dragStart = position; | |||
this.copyArea(); | |||
this.dragMove(position); | |||
} else { | |||
this.startPosition = position; | |||
this.endPosition = null; | |||
this.finished = false; | |||
this.move(position); | |||
/** @type {!Array<MappedValue>} */ | |||
this.selectedCells = []; | |||
} | |||
}; | |||
ascii.DrawSelect.prototype.getSelectedBox = function() { | |||
return new ascii.Box(this.startPosition, this.endPosition); | |||
}; | |||
ascii.DrawSelect.prototype.copyArea = function() { | |||
var nonEmptyCells = this.state.scratchCells.filter(function(value) { | |||
var rawValue = value.cell.getRawValue(); | |||
return value.cell.getRawValue() != null && value.cell.getRawValue() != ERASE_CHAR; | |||
}); | |||
var topLeft = this.getSelectedBox().topLeft(); | |||
this.selectedCells = nonEmptyCells.map(function(value) { | |||
return new ascii.MappedValue(value.position.subtract(topLeft), value.cell.getRawValue()); | |||
}); | |||
}; | |||
/** @inheritDoc */ | |||
start(position) { | |||
// Must be dragging. | |||
if (this.startPosition != null && | |||
this.endPosition != null && | |||
this.getSelectedBox().contains(position)) { | |||
this.dragStart = position; | |||
this.copyArea(); | |||
this.dragMove(position); | |||
} else { | |||
this.startPosition = position; | |||
this.endPosition = null; | |||
this.finished = false; | |||
this.move(position); | |||
} | |||
} | |||
/** @inheritDoc */ | |||
ascii.DrawSelect.prototype.move = function(position) { | |||
if (this.dragStart != null) { | |||
this.dragMove(position); | |||
return; | |||
getSelectedBox() { | |||
return new Box(this.startPosition, this.endPosition); | |||
} | |||
if (this.finished == true) { | |||
return; | |||
copyArea() { | |||
var nonEmptyCells = this.state.scratchCells.filter(function(value) { | |||
var rawValue = value.cell.getRawValue(); | |||
return value.cell.getRawValue() != null && value.cell.getRawValue() != c.ERASE_CHAR; | |||
}); | |||
var topLeft = this.getSelectedBox().topLeft(); | |||
this.selectedCells = nonEmptyCells.map(function(value) { | |||
return new MappedValue(value.position.subtract(topLeft), value.cell.getRawValue()); | |||
}); | |||
} | |||
this.endPosition = position; | |||
this.state.clearDraw(); | |||
var box = new ascii.Box(this.startPosition, position); | |||
/** @inheritDoc */ | |||
move(position) { | |||
if (this.dragStart != null) { | |||
this.dragMove(position); | |||
return; | |||
} | |||
if (this.finished == true) { | |||
return; | |||
} | |||
this.endPosition = position; | |||
this.state.clearDraw(); | |||
var box = new Box(this.startPosition, position); | |||
for (var i = box.startX; i <= box.endX; i++) { | |||
for (var j = box.startY; j <= box.endY; j++) { | |||
var current = new ascii.Vector(i, j); | |||
// Effectively highlights the cell. | |||
var currentValue = this.state.getCell(current).getRawValue(); | |||
this.state.drawValue(current, | |||
currentValue == null ? ERASE_CHAR : currentValue); | |||
for (var i = box.startX; i <= box.endX; i++) { | |||
for (var j = box.startY; j <= box.endY; j++) { | |||
var current = new Vector(i, j); | |||
// Effectively highlights the cell. | |||
var currentValue = this.state.getCell(current).getRawValue(); | |||
this.state.drawValue(current, | |||
currentValue == null ? c.ERASE_CHAR : currentValue); | |||
} | |||
} | |||
} | |||
}; | |||
ascii.DrawSelect.prototype.dragMove = function(position) { | |||
this.dragEnd = position; | |||
this.state.clearDraw(); | |||
var eraser = new ascii.DrawErase(this.state); | |||
eraser.start(this.startPosition); | |||
eraser.move(this.endPosition); | |||
var startPos = this.dragEnd.subtract(this.dragStart).add(this.getSelectedBox().topLeft()); | |||
this.drawSelected(startPos); | |||
}; | |||
dragMove(position) { | |||
this.dragEnd = position; | |||
this.state.clearDraw(); | |||
var eraser = new DrawErase(this.state); | |||
eraser.start(this.startPosition); | |||
eraser.move(this.endPosition); | |||
var startPos = this.dragEnd.subtract(this.dragStart).add(this.getSelectedBox().topLeft()); | |||
this.drawSelected(startPos); | |||
} | |||
ascii.DrawSelect.prototype.drawSelected = function(startPos) { | |||
for (var i in this.selectedCells) { | |||
this.state.drawValue(this.selectedCells[i].position.add(startPos), this.selectedCells[i].value); | |||
drawSelected(startPos) { | |||
for (var { position, value } of this.selectedCells) { | |||
this.state.drawValue(position.add(startPos), value); | |||
} | |||
} | |||
}; | |||
/** @inheritDoc */ | |||
ascii.DrawSelect.prototype.end = function() { | |||
if (this.dragStart != null) { | |||
this.state.commitDraw(); | |||
this.startPosition = null; | |||
this.endPosition = null; | |||
/** @inheritDoc */ | |||
end() { | |||
if (this.dragStart != null) { | |||
this.state.commitDraw(); | |||
this.startPosition = null; | |||
this.endPosition = null; | |||
} | |||
this.dragStart = null; | |||
this.dragEnd = null; | |||
this.finished = true; | |||
} | |||
this.dragStart = null; | |||
this.dragEnd = null; | |||
this.finished = true; | |||
}; | |||
/** @inheritDoc */ | |||
ascii.DrawSelect.prototype.getCursor = function(position) { | |||
if (this.startPosition != null && | |||
this.endPosition != null && | |||
new ascii.Box(this.startPosition, this.endPosition).contains(position)) { | |||
return 'pointer'; | |||
/** @inheritDoc */ | |||
getCursor(position) { | |||
if (this.startPosition != null && | |||
this.endPosition != null && | |||
new Box(this.startPosition, this.endPosition).contains(position)) { | |||
return 'pointer'; | |||
} | |||
return 'default'; | |||
} | |||
return 'default'; | |||
}; | |||
/** @inheritDoc */ | |||
ascii.DrawSelect.prototype.handleKey = function(value) { | |||
if (this.startPosition != null && | |||
this.endPosition != null) { | |||
if (value == KEY_COPY || value == KEY_CUT) { | |||
this.copyArea(); | |||
/** @inheritDoc */ | |||
handleKey(value) { | |||
if (this.startPosition != null && | |||
this.endPosition != null) { | |||
if (value == c.KEY_COPY || value == c.KEY_CUT) { | |||
this.copyArea(); | |||
} | |||
if (value == c.KEY_CUT) { | |||
var eraser = new DrawErase(this.state); | |||
eraser.start(this.startPosition); | |||
eraser.move(this.endPosition); | |||
this.state.commitDraw(); | |||
} | |||
} | |||
if (value == KEY_CUT) { | |||
var eraser = new ascii.DrawErase(this.state); | |||
eraser.start(this.startPosition); | |||
eraser.move(this.endPosition); | |||
if (value == c.KEY_PASTE) { | |||
this.drawSelected(this.startPosition); | |||
this.state.commitDraw(); | |||
} | |||
} | |||
if (value == KEY_PASTE) { | |||
this.drawSelected(this.startPosition); | |||
this.state.commitDraw(); | |||
} | |||
}; | |||
} |
@@ -1,3 +1,8 @@ | |||
import * as c from './constants'; | |||
import State from './state'; | |||
import Vector from './vector'; | |||
import { Box } from './common'; | |||
/** | |||
* All drawing classes and functions. | |||
*/ | |||
@@ -5,16 +10,14 @@ | |||
/** | |||
* Draws a line on the diagram state. | |||
* | |||
* @param {ascii.State} state | |||
* @param {ascii.Vector} startPosition | |||
* @param {ascii.Vector} endPosition | |||
* @param {State} state | |||
* @param {Vector} startPosition | |||
* @param {Vector} endPosition | |||
* @param {boolean} clockwise | |||
* @param {string=} opt_value | |||
* @param {string=} value | |||
*/ | |||
function drawLine(state, startPosition, endPosition, clockwise, opt_value) { | |||
var value = opt_value || SPECIAL_VALUE; | |||
var box = new ascii.Box(startPosition, endPosition); | |||
function drawLine(state, startPosition, endPosition, clockwise, value = c.SPECIAL_VALUE) { | |||
var box = new Box(startPosition, endPosition); | |||
var startX = box.startX; | |||
var startY = box.startY; | |||
var endX = box.endX; | |||
@@ -24,16 +27,16 @@ function drawLine(state, startPosition, endPosition, clockwise, opt_value) { | |||
var midY = clockwise ? startPosition.y : endPosition.y; | |||
while (startX++ < endX) { | |||
var position = new ascii.Vector(startX, midY); | |||
var context = state.getContext(new ascii.Vector(startX, midY)); | |||
var position = new Vector(startX, midY); | |||
var context = state.getContext(new Vector(startX, midY)); | |||
// Don't erase any lines that we cross. | |||
if (value != ' ' || context.up + context.down != 2) { | |||
state.drawValueIncremental(position, value); | |||
} | |||
} | |||
while (startY++ < endY) { | |||
var position = new ascii.Vector(midX, startY); | |||
var context = state.getContext(new ascii.Vector(midX, startY)); | |||
var position = new Vector(midX, startY); | |||
var context = state.getContext(new Vector(midX, startY)); | |||
// Don't erase any lines that we cross. | |||
if (value != ' ' || context.left + context.right != 2) { | |||
state.drawValueIncremental(position, value); | |||
@@ -42,477 +45,492 @@ function drawLine(state, startPosition, endPosition, clockwise, opt_value) { | |||
state.drawValue(startPosition, value); | |||
state.drawValue(endPosition, value); | |||
state.drawValueIncremental(new ascii.Vector(midX, midY), value); | |||
state.drawValueIncremental(new Vector(midX, midY), value); | |||
} | |||
/** | |||
* Common interface for different drawing functions, e.g. box, line, etc. | |||
* @interface | |||
*/ | |||
ascii.DrawFunction = function() {}; | |||
/** Start of drawing. @param {ascii.Vector} position */ | |||
ascii.DrawFunction.prototype.start = function(position) {}; | |||
/** Drawing move. @param {ascii.Vector} position */ | |||
ascii.DrawFunction.prototype.move = function(position) {}; | |||
/** End of drawing. */ | |||
ascii.DrawFunction.prototype.end = function() {}; | |||
/** Cursor for given cell. | |||
* @param {ascii.Vector} position | |||
* @return {string} | |||
*/ | |||
ascii.DrawFunction.prototype.getCursor = function(position) {}; | |||
/** Handle the key with given value being pressed. @param {string} value */ | |||
ascii.DrawFunction.prototype.handleKey = function(value) {}; | |||
export class DrawFunction { | |||
/** Start of drawing. @param {Vector} position */ | |||
start(position) {}; | |||
/** Drawing move. @param {Vector} position */ | |||
move(position) {}; | |||
/** End of drawing. */ | |||
end() {}; | |||
/** Cursor for given cell. | |||
* @param {Vector} position | |||
* @return {string} | |||
*/ | |||
getCursor(position) {}; | |||
/** Handle the key with given value being pressed. @param {string} value */ | |||
handleKey(value) {}; | |||
} | |||
/** | |||
* @constructor | |||
* @implements {ascii.DrawFunction} | |||
* @param {ascii.State} state | |||
* @implements {DrawFunction} | |||
*/ | |||
ascii.DrawBox = function(state) { | |||
this.state = state; | |||
/** @type {ascii.Vector} */ this.startPosition = null; | |||
}; | |||
/** @inheritDoc */ | |||
ascii.DrawBox.prototype.start = function(position) { | |||
this.startPosition = position; | |||
}; | |||
/** @inheritDoc */ | |||
ascii.DrawBox.prototype.move = function(position) { | |||
this.endPosition = position; | |||
this.state.clearDraw(); | |||
drawLine(this.state, this.startPosition, position, true); | |||
drawLine(this.state, this.startPosition, position, false); | |||
}; | |||
/** @inheritDoc */ | |||
ascii.DrawBox.prototype.end = function() { | |||
this.state.commitDraw(); | |||
}; | |||
/** @inheritDoc */ | |||
ascii.DrawBox.prototype.getCursor = function(position) { | |||
return 'crosshair'; | |||
}; | |||
/** @inheritDoc */ | |||
ascii.DrawBox.prototype.handleKey = function(value) {}; | |||
export class DrawBox { | |||
/** | |||
* @param {State} state | |||
*/ | |||
constructor(state) { | |||
this.state = state; | |||
/** @type {Vector} */ this.startPosition = null; | |||
/** @type {Vector} */ this.endPosition = null; | |||
} | |||
/** @inheritDoc */ | |||
start(position) { | |||
this.startPosition = position; | |||
} | |||
/** @inheritDoc */ | |||
move(position) { | |||
this.endPosition = position; | |||
this.state.clearDraw(); | |||
drawLine(this.state, this.startPosition, position, true); | |||
drawLine(this.state, this.startPosition, position, false); | |||
} | |||
/** @inheritDoc */ | |||
end() { | |||
this.state.commitDraw(); | |||
} | |||
/** @inheritDoc */ | |||
getCursor(position) { | |||
return 'crosshair'; | |||
} | |||
/** @inheritDoc */ | |||
handleKey(value) {}; | |||
} | |||
/** | |||
* @constructor | |||
* @implements {ascii.DrawFunction} | |||
* @param {ascii.State} state | |||
* @param {boolean} isArrow | |||
* @implements {DrawFunction} | |||
*/ | |||
ascii.DrawLine = function(state, isArrow) { | |||
this.state = state; | |||
this.isArrow = isArrow; | |||
/** @type {ascii.Vector} */ this.startPosition = null; | |||
}; | |||
/** @inheritDoc */ | |||
ascii.DrawLine.prototype.start = function(position) { | |||
this.startPosition = position; | |||
}; | |||
/** @inheritDoc */ | |||
ascii.DrawLine.prototype.move = function(position) { | |||
this.state.clearDraw(); | |||
// Try to infer line orientation. | |||
// TODO: Split the line into two lines if we can't satisfy both ends. | |||
var startContext = this.state.getContext(this.startPosition); | |||
var endContext = this.state.getContext(position); | |||
var clockwise = (startContext.up && startContext.down) || | |||
(endContext.left && endContext.right); | |||
drawLine(this.state, this.startPosition, position, clockwise); | |||
if (this.isArrow) { | |||
this.state.drawValue(position, ALT_SPECIAL_VALUE); | |||
} | |||
}; | |||
/** @inheritDoc */ | |||
ascii.DrawLine.prototype.end = function() { | |||
this.state.commitDraw(); | |||
}; | |||
/** @inheritDoc */ | |||
ascii.DrawLine.prototype.getCursor = function(position) { | |||
return 'crosshair'; | |||
}; | |||
/** @inheritDoc */ | |||
ascii.DrawLine.prototype.handleKey = function(value) {}; | |||
export class DrawLine { | |||
/** | |||
* @param {State} state | |||
* @param {boolean} isArrow | |||
*/ | |||
constructor(state, isArrow) { | |||
this.state = state; | |||
this.isArrow = isArrow; | |||
/** @type {Vector} */ this.startPosition = null; | |||
} | |||
/** @inheritDoc */ | |||
start(position) { | |||
this.startPosition = position; | |||
} | |||
/** @inheritDoc */ | |||
move(position) { | |||
this.state.clearDraw(); | |||
// Try to infer line orientation. | |||
// TODO: Split the line into two lines if we can't satisfy both ends. | |||
var startContext = this.state.getContext(this.startPosition); | |||
var endContext = this.state.getContext(position); | |||
var clockwise = (startContext.up && startContext.down) || | |||
(endContext.left && endContext.right); | |||
drawLine(this.state, this.startPosition, position, clockwise); | |||
if (this.isArrow) { | |||
this.state.drawValue(position, c.ALT_SPECIAL_VALUE); | |||
} | |||
} | |||
/** @inheritDoc */ | |||
end() { | |||
this.state.commitDraw(); | |||
} | |||
/** @inheritDoc */ | |||
getCursor(position) { | |||
return 'crosshair'; | |||
} | |||
/** @inheritDoc */ | |||
handleKey(value) {}; | |||
} | |||
/** | |||
* @constructor | |||
* @implements {ascii.DrawFunction} | |||
* @param {ascii.State} state | |||
* @param {?string} value | |||
* @implements {DrawFunction} | |||
*/ | |||
ascii.DrawFreeform = function(state, value) { | |||
this.state = state; | |||
this.value = value; | |||
if (TOUCH_ENABLED) { | |||
$('#freeform-tool-input').val(''); | |||
$('#freeform-tool-input').hide(0, function() {$('#freeform-tool-input').show(0, function() {$('#freeform-tool-input').focus();});}); | |||
} | |||
}; | |||
/** @inheritDoc */ | |||
ascii.DrawFreeform.prototype.start = function(position) { | |||
this.state.drawValue(position, this.value); | |||
}; | |||
/** @inheritDoc */ | |||
ascii.DrawFreeform.prototype.move = function(position) { | |||
this.state.drawValue(position, this.value); | |||
}; | |||
/** @inheritDoc */ | |||
ascii.DrawFreeform.prototype.end = function() { | |||
this.state.commitDraw(); | |||
}; | |||
/** @inheritDoc */ | |||
ascii.DrawFreeform.prototype.getCursor = function(position) { | |||
return 'crosshair'; | |||
}; | |||
/** @inheritDoc */ | |||
ascii.DrawFreeform.prototype.handleKey = function(value) { | |||
if (TOUCH_ENABLED) { | |||
this.value = $('#freeform-tool-input').val().substr(0, 1); | |||
$('#freeform-tool-input').blur(); | |||
$('#freeform-tool-input').hide(0); | |||
} | |||
if (value.length == 1) { | |||
// The value is not a special character, so lets use it. | |||
export class DrawFreeform { | |||
/** | |||
* @param {State} state | |||
* @param {?string} value | |||
*/ | |||
constructor(state, value) { | |||
this.state = state; | |||
this.value = value; | |||
if (c.TOUCH_ENABLED) { | |||
$('#freeform-tool-input').val(''); | |||
$('#freeform-tool-input').hide(0, function() {$('#freeform-tool-input').show(0, function() {$('#freeform-tool-input').focus();});}); | |||
} | |||
} | |||
/** @inheritDoc */ | |||
start(position) { | |||
this.state.drawValue(position, this.value); | |||
} | |||
/** @inheritDoc */ | |||
move(position) { | |||
this.state.drawValue(position, this.value); | |||
} | |||
/** @inheritDoc */ | |||
end() { | |||
this.state.commitDraw(); | |||
} | |||
/** @inheritDoc */ | |||
getCursor(position) { | |||
return 'crosshair'; | |||
} | |||
}; | |||
/** @inheritDoc */ | |||
handleKey(value) { | |||
if (c.TOUCH_ENABLED) { | |||
this.value = $('#freeform-tool-input').val().substr(0, 1); | |||
$('#freeform-tool-input').blur(); | |||
$('#freeform-tool-input').hide(0); | |||
} | |||
if (value.length == 1) { | |||
// The value is not a special character, so lets use it. | |||
this.value = value; | |||
} | |||
} | |||
} | |||
/** | |||
* @constructor | |||
* @implements {ascii.DrawFunction} | |||
* @param {ascii.State} state | |||
* @implements {DrawFunction} | |||
*/ | |||
ascii.DrawText = function(state, view) { | |||
this.state = state; | |||
this.startPosition = null; | |||
}; | |||
/** @inheritDoc */ | |||
ascii.DrawText.prototype.start = function(position) { | |||
this.state.commitDraw(); | |||
$('#text-tool-input').val(''); | |||
this.startPosition = position; | |||
// Not working yet, needs fixing so that it can remove the underlying text completely. | |||
//this.loadExistingText(position); | |||
// Effectively highlights the starting cell. | |||
var currentValue = this.state.getCell(this.startPosition).getRawValue(); | |||
this.state.drawValue(this.startPosition, | |||
currentValue == null ? ERASE_CHAR : currentValue); | |||
}; | |||
/** @inheritDoc */ | |||
ascii.DrawText.prototype.move = function(position) {}; | |||
/** @inheritDoc */ | |||
ascii.DrawText.prototype.end = function() { | |||
if (this.startPosition != null) { | |||
this.endPosition = this.startPosition; | |||
export class DrawText { | |||
/** | |||
* @param {State} state | |||
*/ | |||
constructor(state, view) { | |||
this.state = state; | |||
this.startPosition = null; | |||
// Valid end click/press, show the textbox and focus it. | |||
$('#text-tool-widget').hide(0, function() {$('#text-tool-widget').show(0, function() {$('#text-tool-input').focus();});}); | |||
} | |||
}; | |||
/** @inheritDoc */ | |||
ascii.DrawText.prototype.getCursor = function(position) { | |||
return 'pointer'; | |||
}; | |||
/** @inheritDoc */ | |||
ascii.DrawText.prototype.handleKey = function(value) { | |||
var text = $('#text-tool-input').val(); | |||
this.state.clearDraw(); | |||
var x = 0, y = 0; | |||
for(var i = 0; i < text.length; i++) { | |||
if (text[i] == '\n') { | |||
y++; | |||
x = 0; | |||
continue; | |||
this.endPosition = null; | |||
}; | |||
/** @inheritDoc */ | |||
start(position) { | |||
this.state.commitDraw(); | |||
$('#text-tool-input').val(''); | |||
this.startPosition = position; | |||
// Not working yet, needs fixing so that it can remove the underlying text completely. | |||
//this.loadExistingText(position); | |||
// Effectively highlights the starting cell. | |||
var currentValue = this.state.getCell(this.startPosition).getRawValue(); | |||
this.state.drawValue(this.startPosition, | |||
currentValue == null ? c.ERASE_CHAR : currentValue); | |||
} | |||
/** @inheritDoc */ | |||
move(position) {} | |||
/** @inheritDoc */ | |||
end() { | |||
if (this.startPosition != null) { | |||
this.endPosition = this.startPosition; | |||
this.startPosition = null; | |||
// Valid end click/press, show the textbox and focus it. | |||
$('#text-tool-widget').hide(0, function() {$('#text-tool-widget').show(0, function() {$('#text-tool-input').focus();});}); | |||
} | |||
this.state.drawValue(this.endPosition.add(new ascii.Vector(x, y)), text[i]); | |||
x++; | |||
} | |||
}; | |||
/** | |||
* Loads any existing text if it is present. | |||
* TODO: This is horrible, and does not quite work, fix it. | |||
*/ | |||
ascii.DrawText.prototype.loadExistingText = function(position) { | |||
var currentPosition = new ascii.Vector(position.x, position.y); | |||
var cell = this.state.getCell(position); | |||
var spacesCount = 0; | |||
// Go back to find the start of the line. | |||
while ((!cell.isSpecial() && cell.getRawValue() != null) || spacesCount < 1) { | |||
if (cell.getRawValue() == null) { | |||
spacesCount++; | |||
} else if (!cell.isSpecial()) { | |||
spacesCount = 0; | |||
/** @inheritDoc */ | |||
getCursor(position) { | |||
return 'pointer'; | |||
} | |||
/** @inheritDoc */ | |||
handleKey(value) { | |||
var text = $('#text-tool-input').val(); | |||
this.state.clearDraw(); | |||
var x = 0, y = 0; | |||
for(var i = 0; i < text.length; i++) { | |||
if (text[i] == '\n') { | |||
y++; | |||
x = 0; | |||
continue; | |||
} | |||
this.state.drawValue(this.endPosition.add(new Vector(x, y)), text[i]); | |||
x++; | |||
} | |||
currentPosition.x--; | |||
cell = this.state.getCell(currentPosition); | |||
} | |||
this.startPosition = currentPosition.add(new ascii.Vector(spacesCount + 1, 0)); | |||
var text = ''; | |||
spacesCount = 0; | |||
currentPosition = this.startPosition.clone(); | |||
// Go forward to load the text. | |||
while ((!cell.isSpecial() && cell.getRawValue() != null) || spacesCount < 1) { | |||
cell = this.state.getCell(currentPosition); | |||
if (cell.getRawValue() == null) { | |||
spacesCount++; | |||
text += ' '; | |||
} else if (!cell.isSpecial()) { | |||
spacesCount = 0; | |||
text += cell.getRawValue(); | |||
this.state.drawValue(currentPosition, cell.getRawValue()); | |||
} | |||
/** | |||
* Loads any existing text if it is present. | |||
* TODO: This is horrible, and does not quite work, fix it. | |||
*/ | |||
loadExistingText(position) { | |||
var currentPosition = new Vector(position.x, position.y); | |||
var cell = this.state.getCell(position); | |||
var spacesCount = 0; | |||
// Go back to find the start of the line. | |||
while ((!cell.isSpecial() && cell.getRawValue() != null) || spacesCount < 1) { | |||
if (cell.getRawValue() == null) { | |||
spacesCount++; | |||
} else if (!cell.isSpecial()) { | |||
spacesCount = 0; | |||
} | |||
currentPosition.x--; | |||
cell = this.state.getCell(currentPosition); | |||
} | |||
currentPosition.x++; | |||
this.startPosition = currentPosition.add(new Vector(spacesCount + 1, 0)); | |||
var text = ''; | |||
spacesCount = 0; | |||
currentPosition = this.startPosition.clone(); | |||
// Go forward to load the text. | |||
while ((!cell.isSpecial() && cell.getRawValue() != null) || spacesCount < 1) { | |||
cell = this.state.getCell(currentPosition); | |||
if (cell.getRawValue() == null) { | |||
spacesCount++; | |||
text += ' '; | |||
} else if (!cell.isSpecial()) { | |||
spacesCount = 0; | |||
text += cell.getRawValue(); | |||
this.state.drawValue(currentPosition, cell.getRawValue()); | |||
} | |||
currentPosition.x++; | |||
} | |||
$('#text-tool-input').val(text.substr(0, text.length - 1)); | |||
} | |||
$('#text-tool-input').val(text.substr(0, text.length - 1)); | |||
}; | |||
} | |||
/** | |||
* @constructor | |||
* @implements {ascii.DrawFunction} | |||
* @param {ascii.State} state | |||
* @implements {DrawFunction} | |||
*/ | |||
ascii.DrawErase = function(state) { | |||
this.state = state; | |||
this.startPosition = null; | |||
this.endPosition = null; | |||
}; | |||
/** @inheritDoc */ | |||
ascii.DrawErase.prototype.start = function(position) { | |||
this.startPosition = position; | |||
this.move(position); | |||
}; | |||
/** @inheritDoc */ | |||
ascii.DrawErase.prototype.move = function(position) { | |||
this.state.clearDraw(); | |||
this.endPosition = position; | |||
var startX = Math.min(this.startPosition.x, this.endPosition.x); | |||
var startY = Math.min(this.startPosition.y, this.endPosition.y); | |||
var endX = Math.max(this.startPosition.x, this.endPosition.x); | |||
var endY = Math.max(this.startPosition.y, this.endPosition.y); | |||
for (var i = startX; i <= endX; i++) { | |||
for (var j = startY; j <= endY; j++) { | |||
this.state.drawValue(new ascii.Vector(i, j), ERASE_CHAR); | |||
export class DrawErase { | |||
/** | |||
* @param {State} state | |||
*/ | |||
constructor(state) { | |||
this.state = state; | |||
this.startPosition = null; | |||
this.endPosition = null; | |||
} | |||
/** @inheritDoc */ | |||
start(position) { | |||
this.startPosition = position; | |||
this.move(position); | |||
} | |||
/** @inheritDoc */ | |||
move(position) { | |||
this.state.clearDraw(); | |||
this.endPosition = position; | |||
var startX = Math.min(this.startPosition.x, this.endPosition.x); | |||
var startY = Math.min(this.startPosition.y, this.endPosition.y); | |||
var endX = Math.max(this.startPosition.x, this.endPosition.x); | |||
var endY = Math.max(this.startPosition.y, this.endPosition.y); | |||
for (var i = startX; i <= endX; i++) { | |||
for (var j = startY; j <= endY; j++) { | |||
this.state.drawValue(new Vector(i, j), c.ERASE_CHAR); | |||
} | |||
} | |||
} | |||
}; | |||
/** @inheritDoc */ | |||
ascii.DrawErase.prototype.end = function() { | |||
this.state.commitDraw(); | |||
}; | |||
/** @inheritDoc */ | |||
end() { | |||
this.state.commitDraw(); | |||
} | |||
/** @inheritDoc */ | |||
ascii.DrawErase.prototype.getCursor = function(position) { | |||
return 'crosshair'; | |||
}; | |||
/** @inheritDoc */ | |||
getCursor(position) { | |||
return 'crosshair'; | |||
} | |||
/** @inheritDoc */ | |||
ascii.DrawErase.prototype.handleKey = function(value) {}; | |||
/** @inheritDoc */ | |||
handleKey(value) {} | |||
} | |||
/** | |||
* @constructor | |||
* @implements {ascii.DrawFunction} | |||
* @param {ascii.State} state | |||
* @implements {DrawFunction} | |||
*/ | |||
ascii.DrawMove = function(state) { | |||
this.state = state; | |||
this.startPosition = null; | |||
this.ends = null; | |||
}; | |||
/** @inheritDoc */ | |||
ascii.DrawMove.prototype.start = function(position) { | |||
this.startPosition = | |||
TOUCH_ENABLED ? this.snapToNearest(position) : position; | |||
this.ends = null; | |||
// If this isn't a special cell then quit, or things get weird. | |||
if (!this.state.getCell(this.startPosition).isSpecial()) { | |||
return; | |||
} | |||
var context = this.state.getContext(this.startPosition); | |||
var ends = []; | |||
for (var i in DIRECTIONS) { | |||
var midPoints = this.followLine(this.startPosition, DIRECTIONS[i]); | |||
for (var k in midPoints) { | |||
var midPoint = midPoints[k]; | |||
// Clockwise is a lie, it is true if we move vertically first. | |||
var clockwise = (DIRECTIONS[i].x != 0); | |||
var startIsAlt = ALT_SPECIAL_VALUES.indexOf(this.state.getCell(position).getRawValue()) != -1; | |||
var midPointIsAlt = ALT_SPECIAL_VALUES.indexOf(this.state.getCell(midPoint).getRawValue()) != -1; | |||
var midPointContext = this.state.getContext(midPoint); | |||
// Special case, a straight line with no turns. | |||
if (midPointContext.sum() == 1) { | |||
ends.push({position: midPoint, clockwise: clockwise, startIsAlt: startIsAlt, endIsAlt: midPointIsAlt}); | |||
continue; | |||
} | |||
// Continue following lines from the midpoint. | |||
for (var j in DIRECTIONS) { | |||
if (DIRECTIONS[i].add(DIRECTIONS[j]).length() == 0 || | |||
DIRECTIONS[i].add(DIRECTIONS[j]).length() == 2) { | |||
// Don't go back on ourselves, or don't carry on in same direction. | |||
export class DrawMove { | |||
/** | |||
* @param {State} state | |||
*/ | |||
constructor(state) { | |||
this.state = state; | |||
this.startPosition = null; | |||
/** @type {!Array<{position, clockwise, startIsAlt, midPointIsAlt, endIsAlt}>} */ | |||
this.ends = []; | |||
} | |||
/** @inheritDoc */ | |||
start(position) { | |||
this.startPosition = | |||
c.TOUCH_ENABLED ? this.snapToNearest(position) : position; | |||
this.ends = []; | |||
// If this isn't a special cell then quit, or things get weird. | |||
if (!this.state.getCell(this.startPosition).isSpecial()) { | |||
return; | |||
} | |||
var context = this.state.getContext(this.startPosition); | |||
var ends = []; | |||
for (var i of c.DIRECTIONS) { | |||
var midPoints = this.followLine(this.startPosition, i); | |||
for (var midPoint of midPoints) { | |||
// Clockwise is a lie, it is true if we move vertically first. | |||
var clockwise = (i.x != 0); | |||
var startIsAlt = c.ALT_SPECIAL_VALUES.indexOf(this.state.getCell(position).getRawValue()) != -1; | |||
var midPointIsAlt = c.ALT_SPECIAL_VALUES.indexOf(this.state.getCell(midPoint).getRawValue()) != -1; | |||
var midPointContext = this.state.getContext(midPoint); | |||
// Special case, a straight line with no turns. | |||
if (midPointContext.sum() == 1) { | |||
ends.push({position: midPoint, clockwise, startIsAlt, endIsAlt: midPointIsAlt}); | |||
continue; | |||
} | |||
var secondEnds = this.followLine(midPoint, DIRECTIONS[j]); | |||
// Ignore any directions that didn't go anywhere. | |||
if (secondEnds.length == 0) { | |||
continue; | |||
// Continue following lines from the midpoint. | |||
for (var j of c.DIRECTIONS) { | |||
if (i.add(j).length() == 0 || i.add(j).length() == 2) { | |||
// Don't go back on ourselves, or don't carry on in same direction. | |||
continue; | |||
} | |||
var secondEnds = this.followLine(midPoint, j); | |||
// Ignore any directions that didn't go anywhere. | |||
if (secondEnds.length == 0) { | |||
continue; | |||
} | |||
var secondEnd = secondEnds[0]; | |||
var endIsAlt = c.ALT_SPECIAL_VALUES.indexOf(this.state.getCell(secondEnd).getRawValue()) != -1; | |||
// On the second line we don't care about multiple | |||
// junctions, just the last. | |||
ends.push({position: secondEnd, clockwise, startIsAlt, midPointIsAlt, endIsAlt}); | |||
} | |||
var secondEnd = secondEnds[0]; | |||
var endIsAlt = ALT_SPECIAL_VALUES.indexOf(this.state.getCell(secondEnd).getRawValue()) != -1; | |||
// On the second line we don't care about multiple | |||
// junctions, just the last. | |||
ends.push({position: secondEnd, | |||
clockwise: clockwise, startIsAlt: startIsAlt, midPointIsAlt: midPointIsAlt, endIsAlt: endIsAlt}); | |||
} | |||
} | |||
this.ends = ends; | |||
// Redraw the new lines after we have cleared the existing ones. | |||
this.move(this.startPosition); | |||
} | |||
this.ends = ends; | |||
// Redraw the new lines after we have cleared the existing ones. | |||
this.move(this.startPosition); | |||
}; | |||
/** @inheritDoc */ | |||
ascii.DrawMove.prototype.move = function(position) { | |||
this.state.clearDraw(); | |||
// Clear all the lines so we can draw them afresh. | |||
for (var i in this.ends) { | |||
drawLine(this.state, this.startPosition, this.ends[i].position, | |||
this.ends[i].clockwise, ' '); | |||
} | |||
for (var i in this.ends) { | |||
drawLine(this.state, position, this.ends[i].position, | |||
this.ends[i].clockwise); | |||
} | |||
for (var i in this.ends) { | |||
// If the ends or midpoint of the line was a alt character (arrow), need to preserve that. | |||
if (this.ends[i].startIsAlt) { | |||
this.state.drawValue(position, ALT_SPECIAL_VALUE); | |||
/** @inheritDoc */ | |||
move(position) { | |||
this.state.clearDraw(); | |||
// Clear all the lines so we can draw them afresh. | |||
for (var end of this.ends) { | |||
drawLine(this.state, this.startPosition, end.position, end.clockwise, ' '); | |||
} | |||
if (this.ends[i].endIsAlt) { | |||
this.state.drawValue(this.ends[i].position, ALT_SPECIAL_VALUE); | |||
for (var i in this.ends) { | |||
drawLine(this.state, position, end.position, end.clockwise); | |||
} | |||
if (this.ends[i].midPointIsAlt) { | |||
var midX = this.ends[i].clockwise ? this.ends[i].position.x : position.x; | |||
var midY = this.ends[i].clockwise ? position.y : this.ends[i].position.y; | |||
this.state.drawValue(new ascii.Vector(midX, midY), ALT_SPECIAL_VALUE); | |||
for (var end of this.ends) { | |||
// If the ends or midpoint of the line was a alt character (arrow), need to preserve that. | |||
if (end.startIsAlt) { | |||
this.state.drawValue(position, c.ALT_SPECIAL_VALUE); | |||
} | |||
if (end.endIsAlt) { | |||
this.state.drawValue(end.position, c.ALT_SPECIAL_VALUE); | |||
} | |||
if (end.midPointIsAlt) { | |||
var midX = end.clockwise ? end.position.x : position.x; | |||
var midY = end.clockwise ? position.y : end.position.y; | |||
this.state.drawValue(new Vector(midX, midY), c.ALT_SPECIAL_VALUE); | |||
} | |||
} | |||
} | |||
}; | |||
/** @inheritDoc */ | |||
ascii.DrawMove.prototype.end = function() { | |||
this.state.commitDraw(); | |||
}; | |||
/** @inheritDoc */ | |||
end() { | |||
this.state.commitDraw(); | |||
} | |||
/** | |||
* Follows a line in a given direction from the startPosition. | |||
* Returns a list of positions that were line 'junctions'. This is a bit of a | |||
* loose definition, but basically means a point around which we resize things. | |||
* @param {ascii.Vector} startPosition | |||
* @param {ascii.Vector} direction | |||
* @return {Array.<ascii.Vector>} | |||
*/ | |||
ascii.DrawMove.prototype.followLine = function(startPosition, direction) { | |||
var endPosition = startPosition.clone(); | |||
var junctions = []; | |||
while (true) { | |||
var nextEnd = endPosition.add(direction); | |||
if (!this.state.getCell(nextEnd).isSpecial()) { | |||
// Junctions: Right angles and end T-Junctions. | |||
if (!startPosition.equals(endPosition)) { | |||
junctions.push(endPosition); | |||
/** | |||
* Follows a line in a given direction from the startPosition. | |||