/** * Different modes of control. * @const */ var Mode = { NONE: 0, DRAG: 1, DRAW: 2 }; /** * 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; this.installBindings(); }; /** * @param {ascii.Vector} position */ ascii.Controller.prototype.startDraw = function(position) { this.mode = Mode.DRAW; this.drawFunction.start(this.view.screenToCell(position)); }; /** * @param {ascii.Vector} position */ ascii.Controller.prototype.startDrag = function(position) { this.mode = Mode.DRAG; this.dragOrigin = position; this.dragOriginCell = this.view.offset; }; /** * @param {ascii.Vector} position */ ascii.Controller.prototype.handleMove = function(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; }; /** * Ends the current operation. */ ascii.Controller.prototype.endAll = function() { if (this.mode == Mode.DRAW) { this.drawFunction.end(); } // 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)); $('#use-lines-button').click(function(e) { $('.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(); 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(); } 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 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; } } 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); } };