diff --git a/core/flyout.js b/core/flyout.js index a1bb12523..a63be609c 100644 --- a/core/flyout.js +++ b/core/flyout.js @@ -264,20 +264,8 @@ Blockly.Flyout.prototype.position = function() { if (this.RTL) { edgeWidth *= -1; } - var path = ['M ' + (this.RTL ? this.width_ : 0) + ',0']; - path.push('h', edgeWidth); - path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, - this.RTL ? 0 : 1, - this.RTL ? -this.CORNER_RADIUS : this.CORNER_RADIUS, - this.CORNER_RADIUS); - path.push('v', Math.max(0, metrics.viewHeight - this.CORNER_RADIUS * 2)); - path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, - this.RTL ? 0 : 1, - this.RTL ? this.CORNER_RADIUS : -this.CORNER_RADIUS, - this.CORNER_RADIUS); - path.push('h', -edgeWidth); - path.push('z'); - this.svgBackground_.setAttribute('d', path.join(' ')); + + this.setBackgroundPath_(edgeWidth, metrics.viewHeight); var x = metrics.absoluteLeft; if (this.RTL) { @@ -296,6 +284,37 @@ Blockly.Flyout.prototype.position = function() { } }; +/** + * Create and set the path for the visible boundaries of the flyout. + * @param {number} width The width of the flyout, not including the + * rounded corners. + * @param {number} height The height of the flyout, not including + * rounded corners. + * @private + */ +Blockly.Flyout.prototype.setBackgroundPath_ = function(width, height) { + var path = ['M ' + (this.RTL ? this.width_ : 0) + ',0']; + // Top. + path.push('h', width); + // Rounded corner. + path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, + this.RTL ? 0 : 1, + this.RTL ? -this.CORNER_RADIUS : this.CORNER_RADIUS, + this.CORNER_RADIUS); + // Side closest to the workspace. + path.push('v', Math.max(0, height - this.CORNER_RADIUS * 2)); + // Rounded corner. + path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, + this.RTL ? 0 : 1, + this.RTL ? this.CORNER_RADIUS : -this.CORNER_RADIUS, + this.CORNER_RADIUS); + // Bottom. + path.push('h', -width); + // Side away from the workspace. + path.push('z'); + this.svgBackground_.setAttribute('d', path.join(' ')); +}; + /** * Scroll the flyout to the top. */ @@ -363,18 +382,7 @@ Blockly.Flyout.prototype.hide = function() { */ Blockly.Flyout.prototype.show = function(xmlList) { this.hide(); - // Delete any blocks from a previous showing. - var blocks = this.workspace_.getTopBlocks(false); - for (var i = 0, block; block = blocks[i]; i++) { - if (block.workspace == this.workspace_) { - block.dispose(false, false); - } - } - // Delete any background buttons from a previous showing. - for (var i = 0, rect; rect = this.buttons_[i]; i++) { - goog.dom.removeNode(rect); - } - this.buttons_.length = 0; + this.clearOldBlocks_(); if (xmlList == Blockly.Variables.NAME_TYPE) { // Special category for variables. @@ -406,6 +414,38 @@ Blockly.Flyout.prototype.show = function(xmlList) { } } + this.layoutBlocks_(blocks, gaps, margin); + + // IE 11 is an incompetant browser that fails to fire mouseout events. + // When the mouse is over the background, deselect all blocks. + var deselectAll = function(e) { + var blocks = this.workspace_.getTopBlocks(false); + for (var i = 0, block; block = blocks[i]; i++) { + block.removeSelect(); + } + }; + this.listeners_.push(Blockly.bindEvent_(this.svgBackground_, 'mouseover', + this, deselectAll)); + + this.width_ = 0; + this.reflow(); + + this.filterForCapacity_(); + + // Fire a resize event to update the flyout's scrollbar. + Blockly.fireUiEventNow(window, 'resize'); + this.reflowWrapper_ = this.reflow.bind(this); + this.workspace_.addChangeListener(this.reflowWrapper_); +}; + +/** + * Lay out the blocks in the flyout. + * @param {!Array} blocks The blocks to lay out. + * @param {!Array} gaps The visible gaps between blocks. + * @param {number} margin The margin around the edges of the flyout. + * @private + */ +Blockly.Flyout.prototype.layoutBlocks_ = function(blocks, gaps, margin) { // Lay out the blocks vertically. var cursorY = margin; for (var i = 0, block; block = blocks[i]; i++) { @@ -434,47 +474,57 @@ Blockly.Flyout.prototype.show = function(xmlList) { block.flyoutRect_ = rect; this.buttons_[i] = rect; - if (this.autoClose) { - this.listeners_.push(Blockly.bindEvent_(root, 'mousedown', null, - this.createBlockFunc_(block))); - this.listeners_.push(Blockly.bindEvent_(rect, 'mousedown', null, - this.createBlockFunc_(block))); - } else { - this.listeners_.push(Blockly.bindEvent_(root, 'mousedown', null, - this.blockMouseDown_(block))); - this.listeners_.push(Blockly.bindEvent_(rect, 'mousedown', null, - this.blockMouseDown_(block))); - } - this.listeners_.push(Blockly.bindEvent_(root, 'mouseover', block, - block.addSelect)); - this.listeners_.push(Blockly.bindEvent_(root, 'mouseout', block, - block.removeSelect)); - this.listeners_.push(Blockly.bindEvent_(rect, 'mouseover', block, - block.addSelect)); - this.listeners_.push(Blockly.bindEvent_(rect, 'mouseout', block, - block.removeSelect)); + this.addBlockListeners_(root, block, rect); } +}; - // IE 11 is an incompetant browser that fails to fire mouseout events. - // When the mouse is over the background, deselect all blocks. - var deselectAll = function(e) { - var blocks = this.workspace_.getTopBlocks(false); - for (var i = 0, block; block = blocks[i]; i++) { - block.removeSelect(); +/** + * Delete blocks and background buttons from a previous showing of the flyout. + * @private + */ +Blockly.Flyout.prototype.clearOldBlocks_ = function() { + // Delete any blocks from a previous showing. + var blocks = this.workspace_.getTopBlocks(false); + for (var i = 0, block; block = blocks[i]; i++) { + if (block.workspace == this.workspace_) { + block.dispose(false, false); } - }; - this.listeners_.push(Blockly.bindEvent_(this.svgBackground_, 'mouseover', - this, deselectAll)); + } + // Delete any background buttons from a previous showing. + for (var j = 0, rect; rect = this.buttons_[j]; j++) { + goog.dom.removeNode(rect); + } + this.buttons_.length = 0; +}; - this.width_ = 0; - this.reflow(); - - this.filterForCapacity_(); - - // Fire a resize event to update the flyout's scrollbar. - Blockly.fireUiEventNow(window, 'resize'); - this.reflowWrapper_ = this.reflow.bind(this); - this.workspace_.addChangeListener(this.reflowWrapper_); +/** + * Add listeners to a block that has been added to the flyout. + * @param {!Element} root The root node of the SVG group the block is in. + * @param {!Blockly.Block} block The block to add listeners for. + * @param {!Element} rect The invisible rectangle under the block that acts as + * a button for that block. + * @private + */ +Blockly.Flyout.prototype.addBlockListeners_ = function(root, block, rect) { + if (this.autoClose) { + this.listeners_.push(Blockly.bindEvent_(root, 'mousedown', null, + this.createBlockFunc_(block))); + this.listeners_.push(Blockly.bindEvent_(rect, 'mousedown', null, + this.createBlockFunc_(block))); + } else { + this.listeners_.push(Blockly.bindEvent_(root, 'mousedown', null, + this.blockMouseDown_(block))); + this.listeners_.push(Blockly.bindEvent_(rect, 'mousedown', null, + this.blockMouseDown_(block))); + } + this.listeners_.push(Blockly.bindEvent_(root, 'mouseover', block, + block.addSelect)); + this.listeners_.push(Blockly.bindEvent_(root, 'mouseout', block, + block.removeSelect)); + this.listeners_.push(Blockly.bindEvent_(rect, 'mouseover', block, + block.addSelect)); + this.listeners_.push(Blockly.bindEvent_(rect, 'mouseout', block, + block.removeSelect)); }; /** @@ -656,37 +706,7 @@ Blockly.Flyout.prototype.createBlockFunc_ = function(originBlock) { return; } Blockly.Events.disable(); - // Create the new block by cloning the block in the flyout (via XML). - var xml = Blockly.Xml.blockToDom(originBlock); - var block = Blockly.Xml.domToBlock(xml, workspace); - // Place it in the same spot as the flyout copy. - var svgRootOld = originBlock.getSvgRoot(); - if (!svgRootOld) { - throw 'originBlock is not rendered.'; - } - var xyOld = Blockly.getSvgXY_(svgRootOld, workspace); - // Scale the scroll (getSvgXY_ did not do this). - if (flyout.RTL) { - var width = workspace.getMetrics().viewWidth - flyout.width_; - xyOld.x += width / workspace.scale - width; - } else { - xyOld.x += flyout.workspace_.scrollX / flyout.workspace_.scale - - flyout.workspace_.scrollX; - } - xyOld.y += flyout.workspace_.scrollY / flyout.workspace_.scale - - flyout.workspace_.scrollY; - var svgRootNew = block.getSvgRoot(); - if (!svgRootNew) { - throw 'block is not rendered.'; - } - var xyNew = Blockly.getSvgXY_(svgRootNew, workspace); - // Scale the scroll (getSvgXY_ did not do this). - xyNew.x += workspace.scrollX / workspace.scale - workspace.scrollX; - xyNew.y += workspace.scrollY / workspace.scale - workspace.scrollY; - if (workspace.toolbox_ && !workspace.scrollbar) { - xyNew.x += workspace.toolbox_.width / workspace.scale; - } - block.moveBy(xyOld.x - xyNew.x, xyOld.y - xyNew.y); + var block = flyout.placeNewBlock_(originBlock, workspace); Blockly.Events.enable(); if (Blockly.Events.isEnabled()) { Blockly.Events.setGroup(true); @@ -704,6 +724,48 @@ Blockly.Flyout.prototype.createBlockFunc_ = function(originBlock) { }; }; +/** + * Copy a block from the flyout to the workspace and position it correctly. + * @param {!Blockly.Block} originBlock The flyout block to copy. + * @param {!Blockly.Workspace} workspace The main workspace. + * @return {!Blockly.Block} The new block in the main workspace. + * @private + */ +Blockly.Flyout.prototype.placeNewBlock_ = function(originBlock, workspace) { + // Create the new block by cloning the block in the flyout (via XML). + var xml = Blockly.Xml.blockToDom(originBlock); + var block = Blockly.Xml.domToBlock(xml, workspace); + // Place it in the same spot as the flyout copy. + var svgRootOld = originBlock.getSvgRoot(); + if (!svgRootOld) { + throw 'originBlock is not rendered.'; + } + var xyOld = Blockly.getSvgXY_(svgRootOld, workspace); + // Scale the scroll (getSvgXY_ did not do this). + if (this.RTL) { + var width = workspace.getMetrics().viewWidth - this.width_; + xyOld.x += width / workspace.scale - width; + } else { + xyOld.x += this.workspace_.scrollX / this.workspace_.scale - + this.workspace_.scrollX; + } + xyOld.y += this.workspace_.scrollY / this.workspace_.scale - + this.workspace_.scrollY; + var svgRootNew = block.getSvgRoot(); + if (!svgRootNew) { + throw 'block is not rendered.'; + } + var xyNew = Blockly.getSvgXY_(svgRootNew, workspace); + // Scale the scroll (getSvgXY_ did not do this). + xyNew.x += workspace.scrollX / workspace.scale - workspace.scrollX; + xyNew.y += workspace.scrollY / workspace.scale - workspace.scrollY; + if (workspace.toolbox_ && !workspace.scrollbar) { + xyNew.x += workspace.toolbox_.width / workspace.scale; + } + block.moveBy(xyOld.x - xyNew.x, xyOld.y - xyNew.y); + return block; +}; + /** * Filter the blocks on the flyout to disable the ones that are above the * capacity limit.