|
|
@ -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); |
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
|
} |
|
|
|
|