diff --git a/core/block_svg.js b/core/block_svg.js index a5c4b1dad..d322eb56c 100644 --- a/core/block_svg.js +++ b/core/block_svg.js @@ -117,6 +117,22 @@ Blockly.BlockSvg = function(workspace, prototypeName, opt_id) { if (this.svgGroup_.dataset) { this.svgGroup_.dataset.id = this.id; } + + /** + * Holds the cursors svg element when the cursor is attached to the block. + * This is null if there is no cursor on the block. + * @type {SVGElement} + * @private + */ + this.cursorSvg_ = null; + + /** + * Holds the markers svg element when the marker is attached to the block. + * This is null if there is no marker on the block. + * @type {SVGElement} + * @private + */ + this.markerSvg_ = null; }; Blockly.utils.object.inherits(Blockly.BlockSvg, Blockly.Block); @@ -1606,3 +1622,39 @@ Blockly.BlockSvg.prototype.updateConnectionLocations_ = function() { } } }; + +/** + * Add the cursor svg to this block's svg group. + * @param {SVGElement} cursorSvg The svg root of the cursor to be added to the + * block svg group. + * @package + */ +Blockly.BlockSvg.prototype.setCursorSvg = function(cursorSvg) { + if (!cursorSvg) { + this.cursorSvg_ = null; + return; + } + + this.svgGroup_.appendChild(cursorSvg); + this.cursorSvg_ = cursorSvg; +}; + +/** + * Add the marker svg to this block's svg group. + * @param {SVGElement} markerSvg The svg root of the marker to be added to the + * block svg group. + * @package + */ +Blockly.BlockSvg.prototype.setMarkerSvg = function(markerSvg) { + if (!markerSvg) { + this.markerSvg_ = null; + return; + } + + if (this.cursorSvg_) { + this.svgGroup_.insertBefore(markerSvg, this.cursorSvg_); + } else { + this.svgGroup_.appendChild(markerSvg); + } + this.markerSvg_ = markerSvg; +}; diff --git a/core/field.js b/core/field.js index e78d9d10e..b4651ec7a 100644 --- a/core/field.js +++ b/core/field.js @@ -80,6 +80,23 @@ Blockly.Field = function(value, opt_validator, opt_config) { * @protected */ this.size_ = new Blockly.utils.Size(0, 0); + + /** + * Holds the cursors svg element when the cursor is attached to the field. + * This is null if there is no cursor on the field. + * @type {SVGElement} + * @private + */ + this.cursorSvg_ = null; + + /** + * Holds the markers svg element when the marker is attached to the field. + * This is null if there is no marker on the field. + * @type {SVGElement} + * @private + */ + this.markerSvg_ = null; + opt_config && this.configure_(opt_config); this.setValue(value); opt_validator && this.setValidator(opt_validator); @@ -906,3 +923,35 @@ Blockly.Field.prototype.getParentInput = function() { Blockly.Field.prototype.onBlocklyAction = function(_action) { return false; }; + +/** + * Add the cursor svg to this fields svg group. + * @param {SVGElement} cursorSvg The svg root of the cursor to be added to the + * field group. + * @package + */ +Blockly.Field.prototype.setCursorSvg = function(cursorSvg) { + if (!cursorSvg) { + this.cursorSvg_ = null; + return; + } + + this.fieldGroup_.appendChild(cursorSvg); + this.cursorSvg_ = cursorSvg; +}; + +/** + * Add the marker svg to this fields svg group. + * @param {SVGElement} markerSvg The svg root of the marker to be added to the + * field group. + * @package + */ +Blockly.Field.prototype.setMarkerSvg = function(markerSvg) { + if (!markerSvg) { + this.markerSvg_ = null; + return; + } + + this.fieldGroup_.appendChild(markerSvg); + this.markerSvg_ = markerSvg; +}; diff --git a/core/gesture.js b/core/gesture.js index 3d7a5665d..42930bb05 100644 --- a/core/gesture.js +++ b/core/gesture.js @@ -500,7 +500,7 @@ Blockly.Gesture.prototype.doStart = function(e) { if (this.targetBlock_) { if (!this.targetBlock_.isInFlyout && e.shiftKey) { Blockly.navigation.enableKeyboardAccessibility(); - this.creatorWorkspace_.cursor.setLocation( + this.creatorWorkspace_.getCursor().setLocation( Blockly.navigation.getTopNode(this.targetBlock_)); } else { this.targetBlock_.select(); diff --git a/core/keyboard_nav/ast_node.js b/core/keyboard_nav/ast_node.js index c48df37d5..9fa49cb42 100644 --- a/core/keyboard_nav/ast_node.js +++ b/core/keyboard_nav/ast_node.js @@ -56,7 +56,7 @@ Blockly.ASTNode = function(type, location, opt_params) { * @type {boolean} * @private */ - this.isConnection_ = Blockly.ASTNode.isConnectionType(type); + this.isConnection_ = Blockly.ASTNode.isConnectionType_(type); /** * The location of the AST node. @@ -103,9 +103,9 @@ Blockly.ASTNode.DEFAULT_OFFSET_Y = -20; * Whether an AST node of the given type points to a connection. * @param {string} type The type to check. One of Blockly.ASTNode.types. * @return {boolean} True if a node of the given type points to a connection. - * @package + * @private */ -Blockly.ASTNode.isConnectionType = function(type) { +Blockly.ASTNode.isConnectionType_ = function(type) { switch (type) { case Blockly.ASTNode.types.PREVIOUS: case Blockly.ASTNode.types.NEXT: @@ -161,11 +161,7 @@ Blockly.ASTNode.createInputNode = function(input) { if (!input) { return null; } - var params = { - "input": input - }; - return new Blockly.ASTNode(Blockly.ASTNode.types.INPUT, input.connection, - params); + return new Blockly.ASTNode(Blockly.ASTNode.types.INPUT, input.connection); }; /** @@ -216,8 +212,6 @@ Blockly.ASTNode.prototype.processParams_ = function(params) { } if (params['wsCoordinate']) { this.wsCoordinate_ = params['wsCoordinate']; - } else if (params['input']) { - this.parentInput_ = params['input']; } }; @@ -250,16 +244,6 @@ Blockly.ASTNode.prototype.getWsCoordinate = function() { return this.wsCoordinate_; }; -/** - * Get the parent input of the location. - * @return {Blockly.Input} The parent input of the location or null if the node - * is not input type. - * @package - */ -Blockly.ASTNode.prototype.getParentInput = function() { - return this.parentInput_; -}; - /** * Whether the node points to a connection. * @return {boolean} [description] diff --git a/core/keyboard_nav/cursor.js b/core/keyboard_nav/cursor.js index bcfd8cd3c..17f1b5b50 100644 --- a/core/keyboard_nav/cursor.js +++ b/core/keyboard_nav/cursor.js @@ -106,10 +106,12 @@ Blockly.Cursor.prototype.next = function() { if (!curNode) { return null; } - var newNode = curNode.next(); - if (newNode && newNode.getType() === Blockly.ASTNode.types.NEXT) { - newNode = newNode.next() || newNode; + var newNode = curNode.next(); + while (newNode && newNode.next() && + (newNode.getType() === Blockly.ASTNode.types.NEXT || + newNode.getType() === Blockly.ASTNode.types.BLOCK)) { + newNode = newNode.next(); } if (newNode) { @@ -128,6 +130,12 @@ Blockly.Cursor.prototype.in = function() { if (!curNode) { return null; } + // If we are on a previous or output connection, go to the block level before + // performing next operation. + if (curNode.getType() === Blockly.ASTNode.types.PREVIOUS || + curNode.getType() === Blockly.ASTNode.types.OUTPUT) { + curNode = curNode.next(); + } var newNode = curNode.in(); if (newNode && newNode.getType() === Blockly.ASTNode.types.OUTPUT) { @@ -152,8 +160,10 @@ Blockly.Cursor.prototype.prev = function() { } var newNode = curNode.prev(); - if (newNode && newNode.getType() === Blockly.ASTNode.types.NEXT) { - newNode = newNode.prev() || newNode; + while (newNode && newNode.prev() && + (newNode.getType() === Blockly.ASTNode.types.NEXT || + newNode.getType() === Blockly.ASTNode.types.BLOCK)) { + newNode = newNode.prev(); } if (newNode) { diff --git a/core/keyboard_nav/cursor_svg.js b/core/keyboard_nav/cursor_svg.js index a3da835d5..e0731da4a 100644 --- a/core/keyboard_nav/cursor_svg.js +++ b/core/keyboard_nav/cursor_svg.js @@ -32,7 +32,7 @@ goog.require('Blockly.utils.object'); /** * Class for a cursor. - * @param {!Blockly.Workspace} workspace The workspace the cursor belongs to. + * @param {!Blockly.WorkspaceSvg} workspace The workspace the cursor belongs to. * @param {boolean=} opt_marker True if the cursor is a marker. A marker is used * to save a location and is an immovable cursor. False or undefined if the * cursor is not a marker. @@ -41,7 +41,7 @@ goog.require('Blockly.utils.object'); Blockly.CursorSvg = function(workspace, opt_marker) { /** * The workspace the cursor belongs to. - * @type {!Blockly.Workspace} + * @type {!Blockly.WorkspaceSvg} * @private */ this.workspace_ = workspace; @@ -50,11 +50,19 @@ Blockly.CursorSvg = function(workspace, opt_marker) { * True if the cursor should be drawn as a marker, false otherwise. * A marker is drawn as a solid blue line, while the cursor is drawns as a * flashing red one. - * @type {boolean} + * @type {boolean|undefined} * @private */ this.isMarker_ = opt_marker; + /** + * The workspace, field, or block that the cursor svg element should be + * attached to. + * @type {Blockly.WorkspaceSvg|Blockly.Field|Blockly.BlockSvg} + * @private + */ + this.parent_ = null; + /** * The constants necessary to draw the cursor. * @type {Blockly.blockRendering.ConstantProvider} @@ -98,6 +106,22 @@ Blockly.CursorSvg.VERTICAL_PADDING = 5; * @const */ Blockly.CursorSvg.STACK_PADDING = 10; + +/** + * Padding around a block. + * @type {number} + * @const + */ +Blockly.CursorSvg.BLOCK_PADDING = 2; + +/** + * What we multiply the height by to get the height of the cursor. + * Only used for the block and block connections. + * @type {number} + * @const + */ +Blockly.CursorSvg.HEIGHT_MULTIPLIER = 3 / 4; + /** * Cursor color. * @type {string} @@ -124,13 +148,6 @@ Blockly.CursorSvg.CURSOR_CLASS = 'blocklyCursor'; */ Blockly.CursorSvg.MARKER_CLASS = 'blocklyMarker'; -/** - * Parent SVG element. - * This is generally a block's SVG root, unless the cursor is on the workspace. - * @type {Element} - */ -Blockly.CursorSvg.prototype.parent_ = null; - /** * The current SVG element for the cursor. * @type {Element} @@ -139,7 +156,7 @@ Blockly.CursorSvg.prototype.currentCursorSvg = null; /** * Return the root node of the SVG or null if none exists. - * @return {Element} The root SVG node. + * @return {SVGElement} The root SVG node. */ Blockly.CursorSvg.prototype.getSvgRoot = function() { return this.svgGroup_; @@ -147,7 +164,8 @@ Blockly.CursorSvg.prototype.getSvgRoot = function() { /** * Create the DOM element for the cursor. - * @return {!Element} The cursor controls SVG group. + * @return {!SVGElement} The cursor controls SVG group. + * @package */ Blockly.CursorSvg.prototype.createDom = function() { var className = this.isMarker_ ? @@ -163,33 +181,25 @@ Blockly.CursorSvg.prototype.createDom = function() { }; /** - * Set parent of the cursor. This is so that the cursor will be on the correct - * SVG group. - * @param {Element} newParent New parent of the cursor. + * Attaches the svg root of the cursor to the svg group of the parent. + * @param {!Blockly.WorkspaceSvg|!Blockly.Field|!Blockly.BlockSvg} newParent + * The workspace, field, or block that the cursor svg element should be + * attached to. * @private */ Blockly.CursorSvg.prototype.setParent_ = function(newParent) { - var oldParent = this.parent_; - if (newParent == oldParent) { - return; - } - var svgRoot = this.getSvgRoot(); - var cursorNode = null; - - if (newParent) { - if (this.isMarker_) { - // Put the marker before the cursor so the cursor does not get covered. - for (var i = 0, childNode; childNode = newParent.childNodes[i]; i++) { - if (Blockly.utils.dom.hasClass(childNode , Blockly.CursorSvg.CURSOR_CLASS)) { - cursorNode = childNode; - } - } - newParent.insertBefore(svgRoot, cursorNode); - } else { - newParent.appendChild(svgRoot); + if (this.isMarker_) { + if (this.parent_) { + this.parent_.setMarkerSvg(null); } - this.parent_ = newParent; + newParent.setMarkerSvg(this.getSvgRoot()); + } else { + if (this.parent_) { + this.parent_.setCursorSvg(null); + } + newParent.setCursorSvg(this.getSvgRoot()); } + this.parent_ = newParent; }; /**************************/ @@ -197,107 +207,112 @@ Blockly.CursorSvg.prototype.setParent_ = function(newParent) { /**************************/ /** - * Show the cursor using coordinates. + * Show the cursor as a combination of the previous connection and block, + * the output connection and block, or just the block. + * @param {Blockly.BlockSvg} block The block the cursor is currently on. + * @private + */ +Blockly.CursorSvg.prototype.showWithBlockPrevOutput_ = function(block) { + if (!block) { + return; + } + var width = block.width; + var height = block.height; + var cursorHeight = height * Blockly.CursorSvg.HEIGHT_MULTIPLIER; + var cursorOffset = Blockly.CursorSvg.BLOCK_PADDING; + + if (block.previousConnection) { + this.positionPrevious_(width, cursorOffset, cursorHeight); + } else if (block.outputConnection) { + this.positionOutput_(width, height); + } else { + this.positionBlock_(width, cursorOffset, cursorHeight); + } + + this.currentCursorSvg = this.cursorBlock_; + this.setParent_(block); + this.showCurrent_(); +}; + +/** + * Show the visual representation of a workspace coordinate. + * This is a horizontal line. * @param {!Blockly.ASTNode} curNode The node that we want to draw the cursor for. * @private */ Blockly.CursorSvg.prototype.showWithCoordinates_ = function(curNode) { var wsCoordinate = curNode.getWsCoordinate(); this.currentCursorSvg = this.cursorSvgLine_; - this.setParent_(this.workspace_.svgBlockCanvas_); + this.setParent_(this.workspace_); this.positionLine_(wsCoordinate.x, wsCoordinate.y, Blockly.CursorSvg.CURSOR_WIDTH); this.showCurrent_(); }; /** - * Show the cursor using a block. + * Show the visual representation of a field. + * This is a box around the field. * @param {!Blockly.ASTNode} curNode The node that we want to draw the cursor for. * @private */ -Blockly.CursorSvg.prototype.showWithBlock_ = function(curNode) { - var block = curNode.getLocation(); - - this.currentCursorSvg = this.cursorSvgRect_; - this.setParent_(block.getSvgRoot()); - this.positionRect_(0, 0, block.width , block.height); - this.showCurrent_(); -}; - -/** - * Show the cursor using a connection with input or output type. - * @param {!Blockly.ASTNode} curNode The node that we want to draw the cursor for. - * @private - */ -Blockly.CursorSvg.prototype.showWithInputOutput_ = function(curNode) { - var connection = /** @type {Blockly.Connection} */ - (curNode.getLocation()); - this.currentCursorSvg = this.cursorInputOutput_; - var path = Blockly.utils.svgPaths.moveTo(0, 0) + - this.constants_.shapeFor(connection).pathDown; - this.cursorInputOutput_.setAttribute('d', path); - this.setParent_(connection.getSourceBlock().getSvgRoot()); - this.positionInputOutput_(connection); - this.showCurrent_(); -}; - -/** - * Show the cursor using a next connection. - * @param {!Blockly.ASTNode} curNode The node that we want to draw the cursor for. - * @private - */ -Blockly.CursorSvg.prototype.showWithNext_ = function(curNode) { - var connection = curNode.getLocation(); - var targetBlock = connection.getSourceBlock(); - var x = 0; - var y = connection.getOffsetInBlock().y; - var width = targetBlock.getHeightWidth().width; - - this.currentCursorSvg = this.cursorSvgLine_; - this.setParent_(connection.getSourceBlock().getSvgRoot()); - this.positionLine_(x, y, width); - this.showCurrent_(); -}; - -/** - * Show the cursor using a previous connection. - * @param {!Blockly.ASTNode} curNode The node that we want to draw the cursor for. - * @private - */ -Blockly.CursorSvg.prototype.showWithPrev_ = function(curNode) { - var connection = curNode.getLocation(); - var targetBlock = connection.getSourceBlock(); - var width = targetBlock.getHeightWidth().width; - - this.currentCursorSvg = this.cursorSvgLine_; - this.setParent_(connection.getSourceBlock().getSvgRoot()); - this.positionLine_(0, 0, width); - this.showCurrent_(); -}; - -/** - * Show the cursor using a field. - * @param {!Blockly.ASTNode} curNode The node that we want to draw the cursor for. - * @private - */ Blockly.CursorSvg.prototype.showWithField_ = function(curNode) { - var field = curNode.getLocation(); + var field = /** @type {Blockly.Field} */ (curNode.getLocation()); var width = field.borderRect_.width.baseVal.value; var height = field.borderRect_.height.baseVal.value; this.currentCursorSvg = this.cursorSvgRect_; - this.setParent_(field.getSvgRoot()); + this.setParent_(field); this.positionRect_(0, 0, width, height); this.showCurrent_(); }; /** - * Show the cursor using a stack. - * @param {!Blockly.ASTNode} curNode The node that we want to draw the cursor for. + * Show the visual representation of an input. + * This is a puzzle piece. + * @param {!Blockly.ASTNode} curNode The node that we want to draw the cursor for. + * @private + */ +Blockly.CursorSvg.prototype.showWithInput_ = function(curNode) { + var connection = /** @type {Blockly.Connection} */ + (curNode.getLocation()); + var path = Blockly.utils.svgPaths.moveTo(0,0) + + this.constants_.PUZZLE_TAB.pathDown; + var sourceBlock = /** @type {Blockly.BlockSvg} */ (connection.getSourceBlock()); + this.currentCursorSvg = this.cursorInput_; + this.cursorInput_.setAttribute('d', path); + this.setParent_(sourceBlock); + this.positionInput_(connection); + this.showCurrent_(); +}; + + +/** + * Show the visual representation of a next connection. + * This is a horizontal line. + * @param {!Blockly.ASTNode} curNode The node that we want to draw the cursor for. + * @private + */ +Blockly.CursorSvg.prototype.showWithNext_ = function(curNode) { + var connection = curNode.getLocation(); + var targetBlock = /** @type {Blockly.BlockSvg} */ (connection.getSourceBlock()); + var x = 0; + var y = connection.getOffsetInBlock().y; + var width = targetBlock.getHeightWidth().width; + + this.currentCursorSvg = this.cursorSvgLine_; + this.setParent_(targetBlock); + this.positionLine_(x, y, width); + this.showCurrent_(); +}; + +/** + * Show the visual representation of a stack. + * This is a box with extra padding around the entire stack of blocks. + * @param {!Blockly.ASTNode} curNode The node that we want to draw the cursor for. * @private */ Blockly.CursorSvg.prototype.showWithStack_ = function(curNode) { - var block = curNode.getLocation(); + var block = /** @type {Blockly.BlockSvg} */ (curNode.getLocation()); // Gets the height and width of entire stack. var heightWidth = block.getHeightWidth(); @@ -311,59 +326,12 @@ Blockly.CursorSvg.prototype.showWithStack_ = function(curNode) { var y = -1 * Blockly.CursorSvg.STACK_PADDING / 2; this.currentCursorSvg = this.cursorSvgRect_; - this.setParent_(block.getSvgRoot()); + this.setParent_(block); this.positionRect_(x, y, width, height); this.showCurrent_(); }; - -/**************************/ -/**** Position ****/ -/**************************/ - -/** - * Move and show the cursor at the specified coordinate in workspace units. - * @param {number} x The new x, in workspace units. - * @param {number} y The new y, in workspace units. - * @param {number} width The new width, in workspace units. - * @private - */ -Blockly.CursorSvg.prototype.positionLine_ = function(x, y, width) { - this.cursorSvgLine_.setAttribute('x', x); - this.cursorSvgLine_.setAttribute('y', y); - this.cursorSvgLine_.setAttribute('width', width); -}; - -/** - * Move and show the cursor at the specified coordinate in workspace units. - * @param {number} x The new x, in workspace units. - * @param {number} y The new y, in workspace units. - * @param {number} width The new width, in workspace units. - * @param {number} height The new height, in workspace units. - * @private - */ -Blockly.CursorSvg.prototype.positionRect_ = function(x, y, width, height) { - this.cursorSvgRect_.setAttribute('x', x); - this.cursorSvgRect_.setAttribute('y', y); - this.cursorSvgRect_.setAttribute('width', width); - this.cursorSvgRect_.setAttribute('height', height); -}; - -/** - * Position the cursor for an output connection. - * @param {Blockly.Connection} connection The connection to position cursor around. - * @private - */ -Blockly.CursorSvg.prototype.positionInputOutput_ = function(connection) { - var x = connection.getOffsetInBlock().x; - var y = connection.getOffsetInBlock().y; - - this.cursorInputOutput_.setAttribute('transform', - 'translate(' + x + ',' + y + ')' + - (connection.getSourceBlock().RTL ? ' scale(-1 1)' : '')); -}; - /** * Show the current cursor. * @private @@ -373,18 +341,121 @@ Blockly.CursorSvg.prototype.showCurrent_ = function() { this.currentCursorSvg.style.display = ''; }; +/**************************/ +/**** Position ****/ +/**************************/ + +/** + * Position the cursor for a block. + * Displays an outline of the top half of a rectangle around a block. + * @param {!number} width The width of the block. + * @param {!number} cursorOffset The extra padding for around the block. + * @param {!number} cursorHeight The height of the cursor. + */ +Blockly.CursorSvg.prototype.positionBlock_ = function(width, cursorOffset, cursorHeight) { + var cursorPath = Blockly.utils.svgPaths.moveBy(-1 * cursorOffset, cursorHeight) + + Blockly.utils.svgPaths.lineOnAxis('V', -1 * cursorOffset) + + Blockly.utils.svgPaths.lineOnAxis('H', width + cursorOffset * 2) + + Blockly.utils.svgPaths.lineOnAxis('V', cursorHeight); + this.cursorBlock_.setAttribute('d', cursorPath); +}; + +/** + * Position the cursor for an input connection. + * Displays a filled in puzzle piece. + * @param {!Blockly.Connection} connection The connection to position cursor around. + * @private + */ +Blockly.CursorSvg.prototype.positionInput_ = function(connection) { + var x = connection.getOffsetInBlock().x; + var y = connection.getOffsetInBlock().y; + + this.cursorInput_.setAttribute('transform', + 'translate(' + x + ',' + y + ')' + + (connection.getSourceBlock().RTL ? ' scale(-1 1)' : '')); +}; + +/** + * Move and show the cursor at the specified coordinate in workspace units. + * Displays a horizontal line. + * @param {!number} x The new x, in workspace units. + * @param {!number} y The new y, in workspace units. + * @param {!number} width The new width, in workspace units. + * @private + */ +Blockly.CursorSvg.prototype.positionLine_ = function(x, y, width) { + this.cursorSvgLine_.setAttribute('x', x); + this.cursorSvgLine_.setAttribute('y', y); + this.cursorSvgLine_.setAttribute('width', width); +}; + +/** + * Position the cursor for an output connection. + * Displays a puzzle outline and the top and bottom path. + * @param {!number} width The width of the block. + * @param {!number} height The height of the block. + * @private + */ +Blockly.CursorSvg.prototype.positionOutput_ = function(width, height) { + var cursorPath = Blockly.utils.svgPaths.moveBy(width, 0) + + Blockly.utils.svgPaths.lineOnAxis('h', -1 * (width - this.constants_.PUZZLE_TAB.width)) + + Blockly.utils.svgPaths.lineOnAxis('v', this.constants_.TAB_OFFSET_FROM_TOP) + + this.constants_.PUZZLE_TAB.pathDown + + Blockly.utils.svgPaths.lineOnAxis('V', height) + + Blockly.utils.svgPaths.lineOnAxis('H', width); + this.cursorBlock_.setAttribute('d', cursorPath); +}; + +/** + * Position the cursor for a previous connection. + * Displays a half rectangle with a notch in the top to represent the previous + * conenction. + * @param {!number} width The width of the block. + * @param {!number} cursorOffset The offset of the cursor from around the block. + * @param {!number} cursorHeight The height of the cursor. + * @private + */ +Blockly.CursorSvg.prototype.positionPrevious_ = function(width, cursorOffset, cursorHeight) { + var cursorPath = Blockly.utils.svgPaths.moveBy(-1 * cursorOffset, cursorHeight) + + Blockly.utils.svgPaths.lineOnAxis('V', -1 * cursorOffset) + + Blockly.utils.svgPaths.lineOnAxis('H', this.constants_.NOTCH_OFFSET_LEFT) + + this.constants_.NOTCH.pathLeft + + Blockly.utils.svgPaths.lineOnAxis('H', width + cursorOffset * 2) + + Blockly.utils.svgPaths.lineOnAxis('V', cursorHeight); + this.cursorBlock_.setAttribute('d', cursorPath); + this.cursorInput_.setAttribute('transform', ' scale(-1 1)'); +}; + +/** + * Move and show the cursor at the specified coordinate in workspace units. + * Displays a filled in rectangle. + * @param {!number} x The new x, in workspace units. + * @param {!number} y The new y, in workspace units. + * @param {!number} width The new width, in workspace units. + * @param {!number} height The new height, in workspace units. + * @private + */ +Blockly.CursorSvg.prototype.positionRect_ = function(x, y, width, height) { + this.cursorSvgRect_.setAttribute('x', x); + this.cursorSvgRect_.setAttribute('y', y); + this.cursorSvgRect_.setAttribute('width', width); + this.cursorSvgRect_.setAttribute('height', height); +}; + /** * Hide the cursor. + * @package */ Blockly.CursorSvg.prototype.hide = function() { this.cursorSvgLine_.style.display = 'none'; this.cursorSvgRect_.style.display = 'none'; - this.cursorInputOutput_.style.display = 'none'; + this.cursorInput_.style.display = 'none'; + this.cursorBlock_.style.display = 'none'; }; /** * Update the cursor. - * @param {!Blockly.ASTNode} curNode The node that we want to draw the cursor for. + * @param {Blockly.ASTNode} curNode The node that we want to draw the cursor for. * @package */ Blockly.CursorSvg.prototype.draw = function(curNode) { @@ -392,16 +463,18 @@ Blockly.CursorSvg.prototype.draw = function(curNode) { return; } if (curNode.getType() === Blockly.ASTNode.types.BLOCK) { - this.showWithBlock_(curNode); - // This needs to be the location type because next connections can be input - // type but they need to draw like they are a next statement - } else if (curNode.getLocation().type === Blockly.INPUT_VALUE || - curNode.getType() === Blockly.ASTNode.types.OUTPUT) { - this.showWithInputOutput_(curNode); + var block = /** @type {Blockly.BlockSvg} */ (curNode.getLocation()); + this.showWithBlockPrevOutput_(block); + } else if (curNode.getType() === Blockly.ASTNode.types.OUTPUT) { + var outputBlock = /** @type {Blockly.BlockSvg} */ (curNode.getLocation().getSourceBlock()); + this.showWithBlockPrevOutput_(outputBlock); + } else if (curNode.getLocation().type === Blockly.INPUT_VALUE) { + this.showWithInput_(curNode); } else if (curNode.getLocation().type === Blockly.NEXT_STATEMENT) { this.showWithNext_(curNode); } else if (curNode.getType() === Blockly.ASTNode.types.PREVIOUS) { - this.showWithPrev_(curNode); + var previousBlock = /** @type {Blockly.BlockSvg} */ (curNode.getLocation().getSourceBlock()); + this.showWithBlockPrevOutput_(previousBlock); } else if (curNode.getType() === Blockly.ASTNode.types.FIELD) { this.showWithField_(curNode); } else if (curNode.getType() === Blockly.ASTNode.types.WORKSPACE) { @@ -434,6 +507,7 @@ Blockly.CursorSvg.prototype.createCursorSvg_ = function() { 'height': Blockly.CursorSvg.CURSOR_HEIGHT }, this.svgGroup_); + // A horizontal line used to represent a workspace coordinate or next connection. this.cursorSvgLine_ = Blockly.utils.dom.createSvgElement('rect', { 'x': '0', @@ -445,6 +519,7 @@ Blockly.CursorSvg.prototype.createCursorSvg_ = function() { }, this.cursorSvg_); + // A filled in rectangle used to represent a stack. this.cursorSvgRect_ = Blockly.utils.dom.createSvgElement('rect', { 'class': 'blocklyVerticalCursor', @@ -456,7 +531,8 @@ Blockly.CursorSvg.prototype.createCursorSvg_ = function() { }, this.cursorSvg_); - this.cursorInputOutput_ = Blockly.utils.dom.createSvgElement( + // A filled in puzzle piece used to represent an input value. + this.cursorInput_ = Blockly.utils.dom.createSvgElement( 'path', { 'width': Blockly.CursorSvg.CURSOR_WIDTH, @@ -467,7 +543,22 @@ Blockly.CursorSvg.prototype.createCursorSvg_ = function() { }, this.cursorSvg_); - // Markers don't blink. + // A path used to repreesent a previous connection and a block, an output + // connection and a block, or a block. + this.cursorBlock_ = Blockly.utils.dom.createSvgElement( + 'path', + { + 'width': Blockly.CursorSvg.CURSOR_WIDTH, + 'height': Blockly.CursorSvg.CURSOR_HEIGHT, + 'transform':'', + 'style':'display: none;', + 'fill': 'none', + 'stroke': colour, + 'stroke-width': '4' + }, + this.cursorSvg_); + + // Markers and stack cursors don't blink. if (!this.isMarker_) { Blockly.utils.dom.createSvgElement('animate', { @@ -487,7 +578,17 @@ Blockly.CursorSvg.prototype.createCursorSvg_ = function() { 'values': Blockly.CursorSvg.CURSOR_COLOR + ';transparent;transparent;', 'repeatCount': 'indefinite' }, - this.cursorInputOutput_); + this.cursorInput_); + + Blockly.utils.dom.createSvgElement('animate', + { + 'attributeType': 'XML', + 'attributeName': 'stroke', + 'dur': '1s', + 'values': Blockly.CursorSvg.CURSOR_COLOR + ';transparent;transparent;', + 'repeatCount': 'indefinite' + }, + this.cursorBlock_); } return this.cursorSvg_; @@ -495,6 +596,7 @@ Blockly.CursorSvg.prototype.createCursorSvg_ = function() { /** * Dispose of this cursor. + * @package */ Blockly.CursorSvg.prototype.dispose = function() { if (this.svgGroup_) { diff --git a/core/keyboard_nav/navigation.js b/core/keyboard_nav/navigation.js index f2922f239..6663be6a3 100644 --- a/core/keyboard_nav/navigation.js +++ b/core/keyboard_nav/navigation.js @@ -48,7 +48,7 @@ Blockly.navigation.currentCategory_ = null; * Null by default. * The first argument is one of 'log', 'warn', and 'error'. * The second argument is the message. - * @type {function(string, string)} + * @type {?function(string, string)} * @public */ Blockly.navigation.loggingCallback = null; @@ -99,18 +99,18 @@ Blockly.navigation.actionNames = { /** * Move the marker to the cursor's current location. - * @package + * @private */ -Blockly.navigation.markAtCursor = function() { +Blockly.navigation.markAtCursor_ = function() { Blockly.getMainWorkspace().getMarker().setLocation( Blockly.getMainWorkspace().getCursor().getCurNode()); }; /** * Remove the marker from its current location and hide it. - * @package + * @private */ -Blockly.navigation.removeMark = function() { +Blockly.navigation.removeMark_ = function() { Blockly.getMainWorkspace().getMarker().setLocation(null); Blockly.getMainWorkspace().getMarker().hide(); }; @@ -143,15 +143,16 @@ Blockly.navigation.getTopNode = function(block) { /** * Set the state to the toolbox state and the current category as the first * category. + * @private */ -Blockly.navigation.focusToolbox = function() { - Blockly.navigation.resetFlyout(false /* shouldHide */); +Blockly.navigation.focusToolbox_ = function() { + Blockly.navigation.resetFlyout_(false /* shouldHide */); Blockly.navigation.currentState_ = Blockly.navigation.STATE_TOOLBOX; var workspace = Blockly.getMainWorkspace(); var toolbox = workspace.getToolbox(); if (!Blockly.getMainWorkspace().getMarker().getCurNode()) { - Blockly.navigation.markAtCursor(); + Blockly.navigation.markAtCursor_(); } if (workspace && !Blockly.navigation.currentCategory_) { Blockly.navigation.currentCategory_ = toolbox.tree_.firstChild_; @@ -162,8 +163,9 @@ Blockly.navigation.focusToolbox = function() { /** * Select the next category. * Taken from closure/goog/ui/tree/basenode.js + * @private */ -Blockly.navigation.nextCategory = function() { +Blockly.navigation.nextCategory_ = function() { if (!Blockly.navigation.currentCategory_) { return; } @@ -179,8 +181,9 @@ Blockly.navigation.nextCategory = function() { /** * Select the previous category. * Taken from closure/goog/ui/tree/basenode.js + * @private */ -Blockly.navigation.previousCategory = function() { +Blockly.navigation.previousCategory_ = function() { if (!Blockly.navigation.currentCategory_) { return; } @@ -196,8 +199,9 @@ Blockly.navigation.previousCategory = function() { /** * Go to child category if there is a nested category. * Taken from closure/goog/ui/tree/basenode.js + * @private */ -Blockly.navigation.inCategory = function() { +Blockly.navigation.inCategory_ = function() { if (!Blockly.navigation.currentCategory_) { return; } @@ -211,15 +215,16 @@ Blockly.navigation.inCategory = function() { Blockly.navigation.currentCategory_ = curCategory.getFirstChild(); } } else { - Blockly.navigation.focusFlyout(); + Blockly.navigation.focusFlyout_(); } }; /** * Go to parent category if we are in a child category. * Taken from closure/goog/ui/tree/basenode.js + * @private */ -Blockly.navigation.outCategory = function() { +Blockly.navigation.outCategory_ = function() { if (!Blockly.navigation.currentCategory_) { return; } @@ -245,8 +250,9 @@ Blockly.navigation.outCategory = function() { /** * Change focus to the flyout. + * @private */ -Blockly.navigation.focusFlyout = function() { +Blockly.navigation.focusFlyout_ = function() { var topBlock = null; Blockly.navigation.currentState_ = Blockly.navigation.STATE_FLYOUT; var workspace = Blockly.getMainWorkspace(); @@ -254,7 +260,7 @@ Blockly.navigation.focusFlyout = function() { var flyout = toolbox ? toolbox.flyout_ : workspace.getFlyout(); if (!Blockly.getMainWorkspace().getMarker().getCurNode()) { - Blockly.navigation.markAtCursor(); + Blockly.navigation.markAtCursor_(); } if (flyout && flyout.getWorkspace()) { @@ -270,6 +276,7 @@ Blockly.navigation.focusFlyout = function() { /** * Get the cursor from the flyouts workspace. * @return {Blockly.FlyoutCursor} The flyouts cursor or null if no flyout exists. + * @private */ Blockly.navigation.getFlyoutCursor_ = function() { var workspace = Blockly.getMainWorkspace(); @@ -290,7 +297,7 @@ Blockly.navigation.getFlyoutCursor_ = function() { Blockly.navigation.insertFromFlyout = function() { var flyout = Blockly.getMainWorkspace().getFlyout(); if (!flyout || !flyout.isVisible()) { - Blockly.navigation.warn('Trying to insert from the flyout when the flyout does not ' + + Blockly.navigation.warn_('Trying to insert from the flyout when the flyout does not ' + ' exist or is not visible'); return; } @@ -306,19 +313,20 @@ Blockly.navigation.insertFromFlyout = function() { Blockly.getMainWorkspace().getCursor().setLocation( Blockly.ASTNode.createBlockNode(newBlock)); if (!Blockly.navigation.modify_()) { - Blockly.navigation.warn('Something went wrong while inserting a block from the flyout.'); + Blockly.navigation.warn_('Something went wrong while inserting a block from the flyout.'); } - Blockly.navigation.focusWorkspace(); + Blockly.navigation.focusWorkspace_(); Blockly.getMainWorkspace().getCursor().setLocation(Blockly.navigation.getTopNode(newBlock)); - Blockly.navigation.removeMark(); + Blockly.navigation.removeMark_(); }; /** * Reset flyout information, and optionally close the flyout. * @param {boolean} shouldHide True if the flyout should be hidden. + * @private */ -Blockly.navigation.resetFlyout = function(shouldHide) { +Blockly.navigation.resetFlyout_ = function(shouldHide) { if (Blockly.navigation.getFlyoutCursor_()) { Blockly.navigation.getFlyoutCursor_().hide(); if (shouldHide) { @@ -342,12 +350,12 @@ Blockly.navigation.modifyWarn_ = function() { var cursorNode = Blockly.getMainWorkspace().getCursor().getCurNode(); if (!markerNode) { - Blockly.navigation.warn('Cannot insert with no marked node.'); + Blockly.navigation.warn_('Cannot insert with no marked node.'); return false; } if (!cursorNode) { - Blockly.navigation.warn('Cannot insert with no cursor node.'); + Blockly.navigation.warn_('Cannot insert with no cursor node.'); return false; } var markerType = markerNode.getType(); @@ -355,22 +363,22 @@ Blockly.navigation.modifyWarn_ = function() { // Check the marker for invalid types. if (markerType == Blockly.ASTNode.types.FIELD) { - Blockly.navigation.warn('Should not have been able to mark a field.'); + Blockly.navigation.warn_('Should not have been able to mark a field.'); return false; } else if (markerType == Blockly.ASTNode.types.BLOCK) { - Blockly.navigation.warn('Should not have been able to mark a block.'); + Blockly.navigation.warn_('Should not have been able to mark a block.'); return false; } else if (markerType == Blockly.ASTNode.types.STACK) { - Blockly.navigation.warn('Should not have been able to mark a stack.'); + Blockly.navigation.warn_('Should not have been able to mark a stack.'); return false; } // Check the cursor for invalid types. if (cursorType == Blockly.ASTNode.types.FIELD) { - Blockly.navigation.warn('Cannot attach a field to anything else.'); + Blockly.navigation.warn_('Cannot attach a field to anything else.'); return false; } else if (cursorType == Blockly.ASTNode.types.WORKSPACE) { - Blockly.navigation.warn('Cannot attach a workspace to anything else.'); + Blockly.navigation.warn_('Cannot attach a workspace to anything else.'); return false; } return true; @@ -388,7 +396,7 @@ Blockly.navigation.modifyWarn_ = function() { */ Blockly.navigation.moveBlockToWorkspace_ = function(block, wsNode) { if (block.isShadow()) { - Blockly.navigation.warn('Cannot move a shadow block to the workspace.'); + Blockly.navigation.warn_('Cannot move a shadow block to the workspace.'); return false; } if (block.getParent()) { @@ -419,7 +427,7 @@ Blockly.navigation.modify_ = function() { var markerLoc = markerNode.getLocation(); if (markerNode.isConnection() && cursorNode.isConnection()) { - return Blockly.navigation.connect(cursorLoc, markerLoc); + return Blockly.navigation.connect_(cursorLoc, markerLoc); } else if (markerNode.isConnection() && (cursorType == Blockly.ASTNode.types.BLOCK || cursorType == Blockly.ASTNode.types.STACK)) { @@ -428,7 +436,7 @@ Blockly.navigation.modify_ = function() { var block = Blockly.navigation.getSourceBlock_(cursorNode); return Blockly.navigation.moveBlockToWorkspace_(block, markerNode); } - Blockly.navigation.warn('Unexpected state in Blockly.navigation.modify_.'); + Blockly.navigation.warn_('Unexpected state in Blockly.navigation.modify_.'); return false; }; @@ -445,7 +453,7 @@ Blockly.navigation.disconnectChild_ = function(movingConnection, destConnection) var destBlock = destConnection.getSourceBlock(); if (movingBlock.getRootBlock() == destBlock.getRootBlock()) { - if (movingBlock.getDescendants().indexOf(destBlock) > -1) { + if (movingBlock.getDescendants(false).indexOf(destBlock) > -1) { Blockly.navigation.getInferiorConnection_(destConnection).disconnect(); } else { Blockly.navigation.getInferiorConnection_(movingConnection).disconnect(); @@ -530,9 +538,9 @@ Blockly.navigation.getSuperiorConnection_ = function(connection) { * @param {Blockly.Connection} destConnection The connection to be moved to. * @return {boolean} True if the two connections or their target connections * were connected, false otherwise. - * @package + * @private */ -Blockly.navigation.connect = function(movingConnection, destConnection) { +Blockly.navigation.connect_ = function(movingConnection, destConnection) { if (!movingConnection || !destConnection) { return false; } @@ -558,7 +566,7 @@ Blockly.navigation.connect = function(movingConnection, destConnection) { } catch (e) { // If nothing worked report the error from the original connections. - Blockly.navigation.warn('Connection failed with error: ' + e); + Blockly.navigation.warn_('Connection failed with error: ' + e); } return false; } @@ -598,7 +606,7 @@ Blockly.navigation.insertBlock = function(block, destConnection) { } break; } - Blockly.navigation.warn('This block can not be inserted at the marked location.'); + Blockly.navigation.warn_('This block can not be inserted at the marked location.'); return false; }; @@ -606,17 +614,17 @@ Blockly.navigation.insertBlock = function(block, destConnection) { * Disconnect the connection that the cursor is pointing to, and bump blocks. * This is a no-op if the connection cannot be broken or if the cursor is not * pointing to a connection. - * @package + * @private */ -Blockly.navigation.disconnectBlocks = function() { +Blockly.navigation.disconnectBlocks_ = function() { var curNode = Blockly.getMainWorkspace().getCursor().getCurNode(); if (!curNode.isConnection()) { - Blockly.navigation.log('Cannot disconnect blocks when the cursor is not on a connection'); + Blockly.navigation.log_('Cannot disconnect blocks when the cursor is not on a connection'); return; } var curConnection = curNode.getLocation(); if (!curConnection.isConnected()) { - Blockly.navigation.log('Cannot disconnect unconnected connection'); + Blockly.navigation.log_('Cannot disconnect unconnected connection'); return; } var superiorConnection = @@ -626,7 +634,7 @@ Blockly.navigation.disconnectBlocks = function() { curConnection.isSuperior() ? curConnection.targetConnection : curConnection; if (inferiorConnection.getSourceBlock().isShadow()) { - Blockly.navigation.log('Cannot disconnect a shadow block'); + Blockly.navigation.log_('Cannot disconnect a shadow block'); return; } superiorConnection.disconnect(); @@ -646,14 +654,15 @@ Blockly.navigation.disconnectBlocks = function() { /** * Finds where the cursor should go on the workspace. This is either the top * block or a set position on the workspace. + * @private */ -Blockly.navigation.focusWorkspace = function() { +Blockly.navigation.focusWorkspace_ = function() { Blockly.hideChaff(); var cursor = Blockly.getMainWorkspace().getCursor(); var reset = Blockly.getMainWorkspace().getToolbox() ? true : false; var topBlocks = Blockly.getMainWorkspace().getTopBlocks(); - Blockly.navigation.resetFlyout(reset); + Blockly.navigation.resetFlyout_(reset); Blockly.navigation.currentState_ = Blockly.navigation.STATE_WS; if (topBlocks.length > 0) { cursor.setLocation(Blockly.navigation.getTopNode(topBlocks[0])); @@ -668,8 +677,9 @@ Blockly.navigation.focusWorkspace = function() { /** * Handles hitting the enter key on the workspace. + * @private */ -Blockly.navigation.handleEnterForWS = function() { +Blockly.navigation.handleEnterForWS_ = function() { var cursor = Blockly.getMainWorkspace().getCursor(); var curNode = cursor.getCurNode(); var nodeType = curNode.getType(); @@ -678,11 +688,11 @@ Blockly.navigation.handleEnterForWS = function() { location.showEditor_(); } else if (curNode.isConnection() || nodeType == Blockly.ASTNode.types.WORKSPACE) { - Blockly.navigation.markAtCursor(); + Blockly.navigation.markAtCursor_(); } else if (nodeType == Blockly.ASTNode.types.BLOCK) { - Blockly.navigation.warn('Cannot mark a block.'); + Blockly.navigation.warn_('Cannot mark a block.'); } else if (nodeType == Blockly.ASTNode.types.STACK) { - Blockly.navigation.warn('Cannot mark a stack.'); + Blockly.navigation.warn_('Cannot mark a stack.'); } }; @@ -715,7 +725,6 @@ Blockly.navigation.getSourceBlock_ = function(node) { /** * Before a block is deleted move the cursor to the appropriate position. * @param {!Blockly.Block} deletedBlock The block that is being deleted. - * @package */ Blockly.navigation.moveCursorOnBlockDelete = function(deletedBlock) { if (!Blockly.getMainWorkspace()) { @@ -769,7 +778,7 @@ Blockly.navigation.moveCursorOnBlockMutation = function(mutatedBlock) { /** * Handler for all the keyboard navigation events. - * @param {Event} e The keyboard event. + * @param {!Event} e The keyboard event. * @return {boolean} True if the key was handled false otherwise. */ Blockly.navigation.onKeyPress = function(e) { @@ -825,9 +834,9 @@ Blockly.navigation.handleActions_ = function(action) { return true; } else if (action.name === Blockly.navigation.actionNames.TOOLBOX) { if (!Blockly.getMainWorkspace().getToolbox()) { - Blockly.navigation.focusFlyout(); + Blockly.navigation.focusFlyout_(); } else { - Blockly.navigation.focusToolbox(); + Blockly.navigation.focusToolbox_(); } return true; } else if (Blockly.navigation.currentState_ === Blockly.navigation.STATE_WS) { @@ -864,10 +873,10 @@ Blockly.navigation.workspaceOnAction_ = function(action) { Blockly.navigation.modify_(); return true; case Blockly.navigation.actionNames.MARK: - Blockly.navigation.handleEnterForWS(); + Blockly.navigation.handleEnterForWS_(); return true; case Blockly.navigation.actionNames.DISCONNECT: - Blockly.navigation.disconnectBlocks(); + Blockly.navigation.disconnectBlocks_(); return true; default: return false; @@ -886,7 +895,7 @@ Blockly.navigation.flyoutOnAction_ = function(action) { Blockly.navigation.getFlyoutCursor_().prev(); return true; case Blockly.navigation.actionNames.OUT: - Blockly.navigation.focusToolbox(); + Blockly.navigation.focusToolbox_(); return true; case Blockly.navigation.actionNames.NEXT: Blockly.navigation.getFlyoutCursor_().next(); @@ -895,7 +904,7 @@ Blockly.navigation.flyoutOnAction_ = function(action) { Blockly.navigation.insertFromFlyout(); return true; case Blockly.navigation.actionNames.EXIT: - Blockly.navigation.focusWorkspace(); + Blockly.navigation.focusWorkspace_(); return true; default: return false; @@ -911,19 +920,19 @@ Blockly.navigation.flyoutOnAction_ = function(action) { Blockly.navigation.toolboxOnAction_ = function(action) { switch (action.name) { case Blockly.navigation.actionNames.PREVIOUS: - Blockly.navigation.previousCategory(); + Blockly.navigation.previousCategory_(); return true; case Blockly.navigation.actionNames.OUT: - Blockly.navigation.outCategory(); + Blockly.navigation.outCategory_(); return true; case Blockly.navigation.actionNames.NEXT: - Blockly.navigation.nextCategory(); + Blockly.navigation.nextCategory_(); return true; case Blockly.navigation.actionNames.IN: - Blockly.navigation.inCategory(); + Blockly.navigation.inCategory_(); return true; case Blockly.navigation.actionNames.EXIT: - Blockly.navigation.focusWorkspace(); + Blockly.navigation.focusWorkspace_(); return true; default: return false; @@ -936,7 +945,7 @@ Blockly.navigation.toolboxOnAction_ = function(action) { Blockly.navigation.enableKeyboardAccessibility = function() { if (!Blockly.keyboardAccessibilityMode) { Blockly.keyboardAccessibilityMode = true; - Blockly.navigation.focusWorkspace(); + Blockly.navigation.focusWorkspace_(); } }; @@ -960,7 +969,7 @@ Blockly.navigation.disableKeyboardAccessibility = function() { * @param {string} msg The message to log. * @package */ -Blockly.navigation.log = function(msg) { +Blockly.navigation.log_ = function(msg) { if (Blockly.navigation.loggingCallback) { Blockly.navigation.loggingCallback('log', msg); } else { @@ -970,11 +979,11 @@ Blockly.navigation.log = function(msg) { /** * Navigation warning handler. If loggingCallback is defined, use it. - * Otherwise call Blockly.navigation.warn. + * Otherwise call Blockly.navigation.warn_. * @param {string} msg The warning message. * @package */ -Blockly.navigation.warn = function(msg) { +Blockly.navigation.warn_ = function(msg) { if (Blockly.navigation.loggingCallback) { Blockly.navigation.loggingCallback('warn', msg); } else { @@ -988,7 +997,7 @@ Blockly.navigation.warn = function(msg) { * @param {string} msg The error message. * @package */ -Blockly.navigation.error = function(msg) { +Blockly.navigation.error_ = function(msg) { if (Blockly.navigation.loggingCallback) { Blockly.navigation.loggingCallback('error', msg); } else { diff --git a/core/workspace.js b/core/workspace.js index 8f35183d9..dcc56b787 100644 --- a/core/workspace.js +++ b/core/workspace.js @@ -120,14 +120,14 @@ Blockly.Workspace = function(opt_options) { /** * The cursor used to navigate around the AST for keyboard navigation. * @type {Blockly.Cursor} - * @private + * @protected */ this.cursor_ = null; /** * The marker used to mark a location for keyboard navigation. * @type {Blockly.MarkerCursor} - * @private + * @protected */ this.marker_ = null; diff --git a/core/workspace_svg.js b/core/workspace_svg.js index 406805f22..0eff6fbf5 100644 --- a/core/workspace_svg.js +++ b/core/workspace_svg.js @@ -113,6 +113,22 @@ Blockly.WorkspaceSvg = function(options, this.grid_ = this.options.gridPattern ? new Blockly.Grid(options.gridPattern, options.gridOptions) : null; + /** + * Holds the cursors svg element when the cursor is attached to the workspace. + * This is null if there is no cursor on the workspace. + * @type {SVGElement} + * @private + */ + this.cursorSvg_ = null; + + /** + * Holds the markers svg element when the marker is attached to the workspace. + * This is null if there is no marker on the workspace. + * @type {SVGElement} + * @private + */ + this.markerSvg_ = null; + if (Blockly.Variables && Blockly.Variables.flyoutCategory) { this.registerToolboxCategoryCallback(Blockly.VARIABLE_CATEGORY_NAME, Blockly.Variables.flyoutCategory); @@ -410,32 +426,74 @@ Blockly.WorkspaceSvg.prototype.getRenderer = function() { /** * Sets the cursor for use with keyboard navigation. - * @param {!Blockly.Cursor} cursor The cursor used to move around this workspace. + * @param {Blockly.Cursor} cursor The cursor used to move around this workspace. + * @override */ Blockly.WorkspaceSvg.prototype.setCursor = function(cursor) { if (this.cursor_) { this.cursor_.getDrawer().dispose(); } this.cursor_ = cursor; - this.cursor_.setDrawer(new Blockly.CursorSvg(this, false)); - if (this.svgGroup_) { - this.svgGroup_.appendChild(this.cursor_.getDrawer().createDom()); + if (this.cursor_) { + this.cursor_.setDrawer(new Blockly.CursorSvg(this, false)); + this.setCursorSvg(this.cursor_.getDrawer().createDom()); } }; /** * Sets the marker for use with keyboard navigation. - * @param {!Blockly.MarkerCursor} marker The immovable cursor used to mark a + * @param {Blockly.MarkerCursor} marker The immovable cursor used to mark a * location on the workspace. + * @override */ Blockly.WorkspaceSvg.prototype.setMarker = function(marker) { if (this.marker_) { this.marker_.getDrawer().dispose(); } this.marker_ = marker; - this.marker_.setDrawer(new Blockly.CursorSvg(this, true)); + if (this.marker_) { + this.marker_.setDrawer(new Blockly.CursorSvg(this, true)); + this.setMarkerSvg(this.marker_.getDrawer().createDom()); + } +}; + +/** + * Add the cursor svg to this workspaces svg group. + * @param {SVGElement} cursorSvg The svg root of the cursor to be added to the + * workspace svg group. + * @package + */ +Blockly.WorkspaceSvg.prototype.setCursorSvg = function(cursorSvg) { + if (!cursorSvg) { + this.cursorSvg_ = null; + return; + } + if (this.svgGroup_) { - this.svgGroup_.appendChild(this.marker_.getDrawer().createDom()); + this.svgGroup_.appendChild(cursorSvg); + this.cursorSvg_ = cursorSvg; + } +}; + +/** + * Add the marker svg to this workspaces svg group. + * @param {SVGElement} markerSvg The svg root of the marker to be added to the + * workspace svg group. + * @package + */ +Blockly.WorkspaceSvg.prototype.setMarkerSvg = function(markerSvg) { + if (!markerSvg) { + this.markerSvg_ = null; + return; + } + + if (this.svgGroup_) { + if (this.cursorSvg_) { + this.svgGroup_.insertBefore(markerSvg, this.cursorSvg_); + } else { + this.svgGroup_.appendChild(markerSvg); + } + this.markerSvg_ = markerSvg; } }; @@ -657,8 +715,8 @@ Blockly.WorkspaceSvg.prototype.dispose = function() { this.marker_.getDrawer().dispose(); } - if (this.cursor_) { - this.cursor_.getDrawer().dispose(); + if (this.getCursor()) { + this.getCursor().getDrawer().dispose(); } if (this.audioManager_) { diff --git a/tests/mocha/cursor_test.js b/tests/mocha/cursor_test.js index 9ad8b0c12..161715786 100644 --- a/tests/mocha/cursor_test.js +++ b/tests/mocha/cursor_test.js @@ -102,6 +102,6 @@ suite('Cursor', function() { this.cursor.setLocation(prevConnectionNode); this.cursor.prev(); var curNode = this.cursor.getCurNode(); - assertEquals(curNode.getLocation(), this.blocks.A); + assertEquals(curNode.getLocation(), this.blocks.A.previousConnection); }); }); diff --git a/tests/mocha/gesture_test.js b/tests/mocha/gesture_test.js index 0860bd1c6..1d717a440 100644 --- a/tests/mocha/gesture_test.js +++ b/tests/mocha/gesture_test.js @@ -87,6 +87,8 @@ suite('Gesture', function() { test('Workspace click - Shift click enters accessibility mode', function() { var event = { shiftKey : true, + clientX : 10, + clientY : 10, }; var ws = Blockly.inject('blocklyDiv', {}); diff --git a/tests/mocha/navigation_test.js b/tests/mocha/navigation_test.js index fc6a1fec1..81aeadb76 100644 --- a/tests/mocha/navigation_test.js +++ b/tests/mocha/navigation_test.js @@ -17,7 +17,7 @@ suite('Navigation', function() { }]); var toolbox = document.getElementById('toolbox-categories'); this.workspace = Blockly.inject('blocklyDiv', {toolbox: toolbox}); - Blockly.navigation.focusToolbox(); + Blockly.navigation.focusToolbox_(); this.mockEvent = { getModifierState: function() { return false; @@ -45,7 +45,7 @@ suite('Navigation', function() { // Should be a no-op. test('Next at end', function() { - Blockly.navigation.nextCategory(); + Blockly.navigation.nextCategory_(); this.mockEvent.keyCode = Blockly.utils.KeyCodes.S; var startCategory = Blockly.navigation.currentCategory_; chai.assert.isTrue(Blockly.navigation.onKeyPress(this.mockEvent)); @@ -57,7 +57,7 @@ suite('Navigation', function() { test('Previous', function() { // Go forward one so that we can go back one: - Blockly.navigation.nextCategory(); + Blockly.navigation.nextCategory_(); this.mockEvent.keyCode = Blockly.utils.KeyCodes.W; chai.assert.equal(Blockly.navigation.currentCategory_, this.secondCategory_); @@ -134,8 +134,8 @@ suite('Navigation', function() { }]); var toolbox = document.getElementById('toolbox-categories'); this.workspace = Blockly.inject('blocklyDiv', {toolbox: toolbox}); - Blockly.navigation.focusToolbox(); - Blockly.navigation.focusFlyout(); + Blockly.navigation.focusToolbox_(); + Blockly.navigation.focusFlyout_(); this.mockEvent = { getModifierState: function() { return false; @@ -383,14 +383,14 @@ suite('Navigation', function() { test('Toggle Action On', function() { this.mockEvent.keyCode = 'Control75'; - sinon.stub(Blockly.navigation, 'focusWorkspace'); + sinon.stub(Blockly.navigation, 'focusWorkspace_'); Blockly.keyboardAccessibilityMode = false; var isHandled = Blockly.navigation.onKeyPress(this.mockEvent); chai.assert.isTrue(isHandled); - chai.assert.isTrue(Blockly.navigation.focusWorkspace.calledOnce); + chai.assert.isTrue(Blockly.navigation.focusWorkspace_.calledOnce); chai.assert.isTrue(Blockly.keyboardAccessibilityMode); - Blockly.navigation.focusWorkspace.restore(); + Blockly.navigation.focusWorkspace_.restore(); this.workspace.dispose(); }); @@ -493,8 +493,8 @@ suite('Navigation', function() { var prevNode = Blockly.ASTNode.createConnectionNode(previousConnection); this.workspace.getMarker().setLocation(prevNode); - Blockly.navigation.focusToolbox(); - Blockly.navigation.focusFlyout(); + Blockly.navigation.focusToolbox_(); + Blockly.navigation.focusFlyout_(); Blockly.navigation.insertFromFlyout(); var insertedBlock = this.basicBlock.previousConnection.targetBlock(); @@ -505,8 +505,8 @@ suite('Navigation', function() { }); test('Insert Block from flyout without marking a connection', function() { - Blockly.navigation.focusToolbox(); - Blockly.navigation.focusFlyout(); + Blockly.navigation.focusToolbox_(); + Blockly.navigation.focusFlyout_(); Blockly.navigation.insertFromFlyout(); var numBlocks = this.workspace.getTopBlocks().length; @@ -599,7 +599,7 @@ suite('Navigation', function() { var markedLocation = this.basicBlock2.previousConnection; var cursorLocation = this.basicBlock3.previousConnection; - Blockly.navigation.connect(cursorLocation, markedLocation); + Blockly.navigation.connect_(cursorLocation, markedLocation); chai.assert.equal(this.basicBlock.nextConnection.targetBlock(), this.basicBlock3); chai.assert.equal(this.basicBlock2.previousConnection.targetBlock(), this.basicBlock4); @@ -609,7 +609,7 @@ suite('Navigation', function() { var markedLocation = this.basicBlock3.previousConnection; var cursorLocation = this.basicBlock2.previousConnection; - Blockly.navigation.connect(cursorLocation, markedLocation); + Blockly.navigation.connect_(cursorLocation, markedLocation); chai.assert.equal(this.basicBlock.nextConnection.targetBlock(), this.basicBlock3); chai.assert.equal(this.basicBlock2.previousConnection.targetBlock(), this.basicBlock4); @@ -620,7 +620,7 @@ suite('Navigation', function() { var markedLocation = this.basicBlock2.previousConnection; var cursorLocation = this.basicBlock4.nextConnection; - Blockly.navigation.connect(cursorLocation, markedLocation); + Blockly.navigation.connect_(cursorLocation, markedLocation); chai.assert.equal(this.basicBlock.nextConnection.targetBlock(), this.basicBlock4); chai.assert.equal(this.basicBlock3.nextConnection.targetConnection, null); @@ -630,7 +630,7 @@ suite('Navigation', function() { var markedLocation = this.basicBlock3.previousConnection; var cursorLocation = this.basicBlock2.nextConnection; - Blockly.navigation.connect(cursorLocation, markedLocation); + Blockly.navigation.connect_(cursorLocation, markedLocation); chai.assert.equal(this.basicBlock3.previousConnection.targetBlock(), this.basicBlock2); }); @@ -639,7 +639,7 @@ suite('Navigation', function() { var markedLocation = this.inlineBlock2.inputList[0].connection; var cursorLocation = this.inlineBlock1.outputConnection; - Blockly.navigation.connect(cursorLocation, markedLocation); + Blockly.navigation.connect_(cursorLocation, markedLocation); chai.assert.equal(this.inlineBlock2.outputConnection.targetBlock(), null); chai.assert.equal(this.inlineBlock1.outputConnection.targetBlock(), this.inlineBlock2);