From a82bd49d7e38ec61b127e0bf527f50ac282bd3d7 Mon Sep 17 00:00:00 2001 From: picklesrus Date: Fri, 9 Dec 2016 16:19:19 -0800 Subject: [PATCH] Split the scrollbar and flyout out into their own SVG elements. They (#771) * Split the scrollbar and flyout out into their own SVG elements. They are siblings of the workpsace SVG. This paves the way to make performance improvements to workspace dragging. --- core/css.js | 11 ++++ core/flyout.js | 104 +++++++++++++++++++++++++++++----- core/inject.js | 7 +++ core/mutator.js | 12 +++- core/scrollbar.js | 126 +++++++++++++++++++++++++++++------------- core/toolbox.js | 3 +- core/utils.js | 93 ++++++++++++++++++++++++++++++- core/workspace_svg.js | 45 +++++++++++++-- 8 files changed, 339 insertions(+), 62 deletions(-) diff --git a/core/css.js b/core/css.js index a4babe270..ef7434d94 100644 --- a/core/css.js +++ b/core/css.js @@ -136,6 +136,7 @@ Blockly.Css.CONTENT = [ 'background-color: #fff;', 'outline: none;', 'overflow: hidden;', /* IE overflows by default. */ + 'position: absolute;', 'display: block;', '}', @@ -270,6 +271,10 @@ Blockly.Css.CONTENT = [ 'fill: #000;', '}', + '.blocklyFlyout {', + 'position: absolute;', + 'z-index: 20;', + '}', '.blocklyFlyoutButton {', 'fill: #888;', 'cursor: default;', @@ -373,6 +378,12 @@ Blockly.Css.CONTENT = [ 'fill-opacity: .8;', '}', + '.blocklyScrollbarHorizontal, .blocklyScrollbarVertical {', + 'position: absolute;', + 'outline: none;', + 'z-index: 30;', + '}', + '.blocklyScrollbarBackground {', 'opacity: 0;', '}', diff --git a/core/flyout.js b/core/flyout.js index ceeaf211c..3f50fe3f1 100644 --- a/core/flyout.js +++ b/core/flyout.js @@ -176,6 +176,20 @@ Blockly.Flyout.onMouseMoveBlockWrapper_ = null; */ Blockly.Flyout.prototype.autoClose = true; +/** + * Whether the flyout is visible. + * @type {boolean} + * @private + */ +Blockly.Flyout.prototype.isVisible_ = false; + +/** + * Whether the workspace containing this flyout is visible. + * @type {boolean} + * @private + */ +Blockly.Flyout.prototype.containerVisible_ = true; + /** * Corner radius of the flyout background. * @type {number} @@ -260,18 +274,24 @@ Blockly.Flyout.prototype.dragMode_ = Blockly.DRAG_NONE; Blockly.Flyout.prototype.dragAngleRange_ = 70; /** - * Creates the flyout's DOM. Only needs to be called once. + * Creates the flyout's DOM. Only needs to be called once. The flyout can + * either exist as its own element or be a nested inside a separate + * element. + * @param {string} tagName The type of tag to put the flyout in. This + * should be or . * @return {!Element} The flyout's SVG group. */ -Blockly.Flyout.prototype.createDom = function() { +Blockly.Flyout.prototype.createDom = function(tagName) { /* - + - + */ - this.svgGroup_ = Blockly.utils.createSvgElement('g', - {'class': 'blocklyFlyout'}, null); + // Setting style to display:none to start. The toolbox and flyout + // hide/show code will set up proper visibility and size later. + this.svgGroup_ = Blockly.utils.createSvgElement(tagName, + {'class': 'blocklyFlyout', 'style': 'display: none'}, null); this.svgBackground_ = Blockly.utils.createSvgElement('path', {'class': 'blocklyFlyoutBackground'}, this.svgGroup_); this.svgGroup_.appendChild(this.workspace_.createDom()); @@ -469,8 +489,6 @@ Blockly.Flyout.prototype.position = function() { y -= this.height_; } - this.svgGroup_.setAttribute('transform', 'translate(' + x + ',' + y + ')'); - // Record the height for Blockly.Flyout.getMetrics_, or width if the layout is // horizontal. if (this.horizontalLayout_) { @@ -479,8 +497,15 @@ Blockly.Flyout.prototype.position = function() { this.height_ = targetWorkspaceMetrics.viewHeight; } + this.svgGroup_.setAttribute("width", this.width_); + this.svgGroup_.setAttribute("height", this.height_); + var transform = 'translate(' + x + 'px,' + y + 'px)'; + this.svgGroup_.style.transform = transform; + // Update the scrollbar (if one exists). if (this.scrollbar_) { + // Set the scrollbars origin to be the top left of the flyout. + this.scrollbar_.setOrigin(x, y); this.scrollbar_.resize(); } }; @@ -623,7 +648,51 @@ Blockly.Flyout.prototype.wheel_ = function(e) { * @return {boolean} True if visible. */ Blockly.Flyout.prototype.isVisible = function() { - return this.svgGroup_ && this.svgGroup_.style.display == 'block'; + return this.isVisible_; +}; + + /** + * Set whether the flyout is visible. A value of true does not necessarily mean + * that the flyout is shown. It could be hidden because its container is hidden. + * @param {boolean} visible True if visible. + */ +Blockly.Flyout.prototype.setVisible = function(visible) { + var visibilityChanged = (visible != this.isVisible()); + + this.isVisible_ = visible; + if (visibilityChanged) { + this.updateDisplay_(); + } +}; + +/** + * Set whether this flyout's container is visible. + * @param {boolean} visible Whether the container is visible. + */ +Blockly.Flyout.prototype.setContainerVisible = function(visible) { + var visibilityChanged = (visible != this.containerVisible_); + this.containerVisible_ = visible; + if (visibilityChanged) { + this.updateDisplay_(); + } +}; + +/** + * Update the display property of the flyout based whether it thinks it should + * be visible and whether its containing workspace is visible. + * @private + */ +Blockly.Flyout.prototype.updateDisplay_ = function() { + var show = true; + if (!this.containerVisible_) { + show = false; + } else { + show = this.isVisible(); + } + this.svgGroup_.style.display = show ? 'block' : 'none'; + // Update the scrollbar's visiblity too since it should mimic the + // flyout's visibility. + this.scrollbar_.setContainerVisible(show); }; /** @@ -633,7 +702,7 @@ Blockly.Flyout.prototype.hide = function() { if (!this.isVisible()) { return; } - this.svgGroup_.style.display = 'none'; + this.setVisible(false); // Delete all the event listeners. for (var x = 0, listen; listen = this.listeners_[x]; x++) { Blockly.unbindEvent_(listen); @@ -666,7 +735,7 @@ Blockly.Flyout.prototype.show = function(xmlList) { Blockly.Procedures.flyoutCategory(this.workspace_.targetWorkspace); } - this.svgGroup_.style.display = 'block'; + this.setVisible(true); // Create the blocks to be shown in this flyout. var contents = []; var gaps = []; @@ -1131,7 +1200,11 @@ Blockly.Flyout.prototype.placeNewBlock_ = function(originBlock) { } // Figure out where the original block is on the screen, relative to the upper // left corner of the main workspace. - var xyOld = targetWorkspace.getSvgXY(svgRootOld); + if (targetWorkspace.isMutator) { + var xyOld = this.workspace_.getSvgXY(/** @type {!Element} */ (svgRootOld)); + } else { + var xyOld = Blockly.utils.getInjectionDivXY_(svgRootOld); + } // Take into account that the flyout might have been scrolled horizontally // (separately from the main workspace). // Generally a no-op in vertical mode but likely to happen in horizontal @@ -1175,7 +1248,12 @@ Blockly.Flyout.prototype.placeNewBlock_ = function(originBlock) { // upper left corner of the workspace. This may not be the same as the // original block because the flyout's origin may not be the same as the // main workspace's origin. - var xyNew = targetWorkspace.getSvgXY(svgRootNew); + if (targetWorkspace.isMutator) { + var xyNew = targetWorkspace.getSvgXY(/* @type {!Element} */(svgRootNew)); + } else { + var xyNew = Blockly.utils.getInjectionDivXY_(svgRootNew); + } + // Scale the scroll (getSvgXY_ did not do this). xyNew.x += targetWorkspace.scrollX / targetWorkspace.scale - targetWorkspace.scrollX; diff --git a/core/inject.js b/core/inject.js index 58787c0b3..1c5b5c880 100644 --- a/core/inject.js +++ b/core/inject.js @@ -195,6 +195,13 @@ Blockly.createMainWorkspace_ = function(svg, options, blockDragSurface) { var mainWorkspace = new Blockly.WorkspaceSvg(options, blockDragSurface); mainWorkspace.scale = options.zoomOptions.startScale; svg.appendChild(mainWorkspace.createDom('blocklyMainBackground')); + + if (!options.hasCategories && options.languageTree) { + // Add flyout as an that is a sibling of the workspace svg. + var flyout = mainWorkspace.addFlyout_('svg'); + Blockly.utils.insertAfter_(flyout, svg); + } + // A null translation will also apply the correct initial scale. mainWorkspace.translate(0, 0); mainWorkspace.markFocused(); diff --git a/core/mutator.js b/core/mutator.js index ddaa662af..641273e6f 100644 --- a/core/mutator.js +++ b/core/mutator.js @@ -130,8 +130,16 @@ Blockly.Mutator.prototype.createEditor_ = function() { }; this.workspace_ = new Blockly.WorkspaceSvg(workspaceOptions); this.workspace_.isMutator = true; - this.svgDialog_.appendChild( - this.workspace_.createDom('blocklyMutatorBackground')); + + // Mutator flyouts go inside the mutator workspace's rather than in + // a top level svg. Instead of handling scale themselves, mutators + // inherit scale from the parent workspace. + // To fix this, scale needs to be applied at a different level in the dom. + var flyoutSvg = this.workspace_.addFlyout_('g'); + var background = this.workspace_.createDom('blocklyMutatorBackground'); + background.appendChild(flyoutSvg); + this.svgDialog_.appendChild(background); + return this.svgDialog_; }; diff --git a/core/scrollbar.js b/core/scrollbar.js index 332078f00..24b56d8e3 100644 --- a/core/scrollbar.js +++ b/core/scrollbar.js @@ -44,7 +44,7 @@ Blockly.ScrollbarPair = function(workspace) { {'height': Blockly.Scrollbar.scrollbarThickness, 'width': Blockly.Scrollbar.scrollbarThickness, 'class': 'blocklyScrollbarBackground'}, null); - Blockly.Scrollbar.insertAfter_(this.corner_, workspace.getBubbleCanvas()); + Blockly.utils.insertAfter_(this.corner_, workspace.getBubbleCanvas()); }; /** @@ -202,6 +202,8 @@ Blockly.Scrollbar = function(workspace, horizontal, opt_pair) { if (horizontal) { this.svgBackground_.setAttribute('height', Blockly.Scrollbar.scrollbarThickness); + this.outerSvg_.setAttribute('height', + Blockly.Scrollbar.scrollbarThickness); this.svgHandle_.setAttribute('height', Blockly.Scrollbar.scrollbarThickness - 5); this.svgHandle_.setAttribute('y', 2.5); @@ -211,6 +213,8 @@ Blockly.Scrollbar = function(workspace, horizontal, opt_pair) { } else { this.svgBackground_.setAttribute('width', Blockly.Scrollbar.scrollbarThickness); + this.outerSvg_.setAttribute('width', + Blockly.Scrollbar.scrollbarThickness); this.svgHandle_.setAttribute('width', Blockly.Scrollbar.scrollbarThickness - 5); this.svgHandle_.setAttribute('x', 2.5); @@ -224,6 +228,12 @@ Blockly.Scrollbar = function(workspace, horizontal, opt_pair) { this.onMouseDownHandleWrapper_ = Blockly.bindEventWithChecks_(this.svgHandle_, 'mousedown', scrollbar, scrollbar.onMouseDownHandle_); }; +/** + * The coordinate of the upper left corner of the scrollbar SVG. + * @type {goog.math.Coordinate} + * @private + */ +Blockly.Scrollbar.prototype.origin_ = new goog.math.Coordinate(0, 0); /** * The size of the area within which the scrollbar handle can move. @@ -253,6 +263,13 @@ Blockly.Scrollbar.prototype.handlePosition_ = 0; */ Blockly.Scrollbar.prototype.isVisible_ = true; +/** + * Whether the workspace containing this scrollbar is visible. + * @type {boolean} + * @private + */ +Blockly.Scrollbar.prototype.containerVisible_ = true; + /** * Width of vertical scrollbar or height of horizontal scrollbar. * Increase the size of scrollbars on touch devices. @@ -303,7 +320,8 @@ Blockly.Scrollbar.prototype.dispose = function() { Blockly.unbindEvent_(this.onMouseDownHandleWrapper_); this.onMouseDownHandleWrapper_ = null; - goog.dom.removeNode(this.svgGroup_); + goog.dom.removeNode(this.outerSvg_); + this.outerSvg_ = null; this.svgGroup_ = null; this.svgBackground_ = null; this.svgHandle_ = null; @@ -338,9 +356,19 @@ Blockly.Scrollbar.prototype.setHandlePosition = function(newPosition) { */ Blockly.Scrollbar.prototype.setScrollViewSize_ = function(newSize) { this.scrollViewSize_ = newSize; + this.outerSvg_.setAttribute(this.lengthAttribute_, this.scrollViewSize_); this.svgBackground_.setAttribute(this.lengthAttribute_, this.scrollViewSize_); }; +/** ++ * Set whether this scrollbar's container is visible. ++ * @param {boolean} visible Whether the container is visible. ++ */ +Blockly.ScrollbarPair.prototype.setContainerVisible = function(visible) { + this.hScroll.setContainerVisible(visible); + this.vScroll.setContainerVisible(visible); +}; + /** * Set the position of the scrollbar's svg group. * @param {number} x The new x coordinate. @@ -350,8 +378,10 @@ Blockly.Scrollbar.prototype.setPosition = function(x, y) { this.position_.x = x; this.position_.y = y; - this.svgGroup_.setAttribute('transform', - 'translate(' + this.position_.x + ',' + this.position_.y + ')'); + var tempX = this.position_.x + this.origin_.x; + var tempY = this.position_.y + this.origin_.y; + var transform = 'translate(' + tempX + 'px,' + tempY + 'px)'; + this.outerSvg_.style.transform = transform; }; /** @@ -539,23 +569,26 @@ Blockly.Scrollbar.prototype.resizeContentVertical = function(hostMetrics) { */ Blockly.Scrollbar.prototype.createDom_ = function() { /* Create the following DOM: - - - - + + + + + + */ var className = 'blocklyScrollbar' + (this.horizontal_ ? 'Horizontal' : 'Vertical'); - this.svgGroup_ = Blockly.utils.createSvgElement('g', {'class': className}, + this.outerSvg_ = Blockly.utils.createSvgElement('svg', {'class': className}, null); + this.svgGroup_ = Blockly.utils.createSvgElement('g', {}, this.outerSvg_); this.svgBackground_ = Blockly.utils.createSvgElement('rect', {'class': 'blocklyScrollbarBackground'}, this.svgGroup_); var radius = Math.floor((Blockly.Scrollbar.scrollbarThickness - 5) / 2); this.svgHandle_ = Blockly.utils.createSvgElement('rect', {'class': 'blocklyScrollbarHandle', 'rx': radius, 'ry': radius}, this.svgGroup_); - Blockly.Scrollbar.insertAfter_(this.svgGroup_, - this.workspace_.getBubbleCanvas()); + Blockly.utils.insertAfter_(this.outerSvg_, + this.workspace_.getParentSvg()); }; /** @@ -567,29 +600,57 @@ Blockly.Scrollbar.prototype.isVisible = function() { return this.isVisible_; }; +/** + * Set whether the scrollbar's container is visible and update + * display accordingly if visibility has changed. + * @param {boolean} visible Whether the container is visible + */ +Blockly.Scrollbar.prototype.setContainerVisible = function(visible) { + var visibilityChanged = (visible != this.containerVisible_); + + this.containerVisible_ = visible; + if (visibilityChanged) { + this.updateDisplay_(); + } +}; + /** * Set whether the scrollbar is visible. * Only applies to non-paired scrollbars. * @param {boolean} visible True if visible. */ Blockly.Scrollbar.prototype.setVisible = function(visible) { - if (visible == this.isVisible()) { - return; - } + var visibilityChanged = (visible != this.isVisible()); + // Ideally this would also apply to scrollbar pairs, but that's a bigger // headache (due to interactions with the corner square). if (this.pair_) { throw 'Unable to toggle visibility of paired scrollbars.'; } - this.isVisible_ = visible; + if (visibilityChanged) { + this.updateDisplay_(); + } +}; - if (visible) { - this.svgGroup_.setAttribute('display', 'block'); +/** + * Update visibility of scrollbar based on whether it thinks it should + * be visible and whether its containing workspace is visible. + * We cannot rely on the containing workspace being hidden to hide us + * because it is not necessarily our parent in the dom. + */ + Blockly.Scrollbar.prototype.updateDisplay_ = function() { + var show = true; + // Check whether our parent/container is visible. + if (!this.containerVisible_) { + show = false; } else { - // Hide the scrollbar. - this.workspace_.setMetrics({x: 0, y: 0}); - this.svgGroup_.setAttribute('display', 'none'); + show = this.isVisible(); + } + if (show) { + this.outerSvg_.setAttribute('display', 'block'); + } else { + this.outerSvg_.setAttribute('display', 'none'); } }; @@ -613,7 +674,7 @@ Blockly.Scrollbar.prototype.onMouseDownBar_ = function(e) { this.workspace_.getInverseScreenCTM()); var mouseLocation = this.horizontal_ ? mouseXY.x : mouseXY.y; - var handleXY = this.workspace_.getSvgXY(this.svgHandle_); + var handleXY = Blockly.utils.getInjectionDivXY_(this.svgHandle_); var handleStart = this.horizontal_ ? handleXY.x : handleXY.y; var handlePosition = this.handlePosition_; @@ -744,21 +805,12 @@ Blockly.Scrollbar.prototype.set = function(value) { }; /** - * Insert a node after a reference node. - * Contrast with node.insertBefore function. - * @param {!Element} newNode New element to insert. - * @param {!Element} refNode Existing element to precede new node. - * @private + * Set the origin of the upper left of the scrollbar. This if for times + * when the scrollbar is used in an object whose origin isn't the same + * as the main workspace (e.g. in a flyout.) + * @ param {number} x The x coordinate of the scrollbar's origin. + * @ param {number} y The y coordinate of the scrollbar's origin. */ -Blockly.Scrollbar.insertAfter_ = function(newNode, refNode) { - var siblingNode = refNode.nextSibling; - var parentNode = refNode.parentNode; - if (!parentNode) { - throw 'Reference node has no parent.'; - } - if (siblingNode) { - parentNode.insertBefore(newNode, siblingNode); - } else { - parentNode.appendChild(newNode); - } +Blockly.Scrollbar.prototype.setOrigin = function(x, y) { + this.origin_ = new goog.math.Coordinate(x, y); }; diff --git a/core/toolbox.js b/core/toolbox.js index 27b6f4177..03a1e0654 100644 --- a/core/toolbox.js +++ b/core/toolbox.js @@ -182,7 +182,8 @@ Blockly.Toolbox.prototype.init = function() { * @private */ this.flyout_ = new Blockly.Flyout(workspaceOptions); - goog.dom.insertSiblingAfter(this.flyout_.createDom(), workspace.svgGroup_); + goog.dom.insertSiblingAfter(this.flyout_.createDom('svg'), + this.workspace_.getParentSvg()); this.flyout_.init(workspace); this.config_['cleardotPath'] = workspace.options.pathToMedia + '1x1.gif'; diff --git a/core/utils.js b/core/utils.js index f3b65b7c2..4ee21b1c2 100644 --- a/core/utils.js +++ b/core/utils.js @@ -131,10 +131,14 @@ Blockly.utils.getRelativeXY = function(element) { } } - // Third, check for style="transform: translate3d(...)". + // Then check for style = transform: translate(...) or translate3d(...) var style = element.getAttribute('style'); - if (style && style.indexOf('translate3d') > -1) { - var styleComponents = style.match(Blockly.utils.getRelativeXY.XY_3D_REGEX_); + if (style && style.indexOf('translate') > -1) { + var styleComponents = style.match(Blockly.utils.getRelativeXY.XY_2D_REGEX_); + // Try transform3d if 2d transform wasn't there. + if (!styleComponents) { + styleComponents = style.match(Blockly.utils.getRelativeXY.XY_3D_REGEX_); + } if (styleComponents) { xy.x += parseFloat(styleComponents[1]); if (styleComponents[3]) { @@ -145,6 +149,51 @@ Blockly.utils.getRelativeXY = function(element) { return xy; }; +/** + * Return the coordinates of the top-left corner of this element relative to + * the div blockly was injected into. + * @param {!Element} element SVG element to find the coordinates of. If this is + * not a child of the div blockly was injected into, the behaviour is + * undefined. + * @return {!goog.math.Coordinate} Object with .x and .y properties. + */ +Blockly.utils.getInjectionDivXY_ = function(element) { + var x = 0; + var y = 0; + var scale = 1; + while (element) { + var xy = Blockly.utils.getRelativeXY(element); + var scale = Blockly.utils.getScale_(element); + x = (x * scale) + xy.x; + y = (y * scale) + xy.y; + var classes = element.getAttribute('class') || ''; + if ((' ' + classes + ' ').indexOf(' injectionDiv ') != -1) { + break; + } + element = element.parentNode; + } + return new goog.math.Coordinate(x, y); +}; + +/** + * Return the scale of this element. + * @param {!Element} element The element to find the coordinates of. + * @return {!number} number represending the scale applied to the element. + * @private + */ +Blockly.utils.getScale_ = function(element) { + var scale = 1; + var transform = element.getAttribute('transform'); + if (transform) { + var transformComponents = + transform.match(Blockly.utils.getScale_.REGEXP_); + if (transformComponents && transformComponents[0]) { + scale = parseFloat(transformComponents[0]); + } + } + return scale; +}; + /** * Static regex to pull the x,y values out of an SVG translate() directive. * Note that Firefox and IE (9,10) return 'translate(12)' instead of @@ -157,6 +206,15 @@ Blockly.utils.getRelativeXY = function(element) { Blockly.utils.getRelativeXY.XY_REGEX_ = /translate\(\s*([-+\d.e]+)([ ,]\s*([-+\d.e]+)\s*\))?/; + +/** + * Static regex to pull the scale values out of a transform style property. + * Accounts for same exceptions as XY_REGEXP_. + * @type {!RegExp} + * @private + */ +Blockly.utils.getScale_REGEXP_ = /scale\(\s*([-+\d.e]+)\s*\)/; + /** * Static regex to pull the x,y,z values out of a translate3d() style property. * Accounts for same exceptions as XY_REGEXP_. @@ -166,6 +224,15 @@ Blockly.utils.getRelativeXY.XY_REGEX_ = Blockly.utils.getRelativeXY.XY_3D_REGEX_ = /transform:\s*translate3d\(\s*([-+\d.e]+)px([ ,]\s*([-+\d.e]+)\s*)px([ ,]\s*([-+\d.e]+)\s*)px\)?/; +/** + * Static regex to pull the x,y,z values out of a translate3d() style property. + * Accounts for same exceptions as XY_REGEXP_. + * @type {!RegExp} + * @private + */ +Blockly.utils.getRelativeXY.XY_2D_REGEX_ = + /transform:\s*translate\(\s*([-+\d.e]+)px([ ,]\s*([-+\d.e]+)\s*)px\)?/; + /** * Helper method for creating SVG elements. * @param {string} name Element's tag name. @@ -665,3 +732,23 @@ Blockly.utils.is3dSupported = function() { Blockly.utils.is3dSupported.cached_ = has3d !== 'none'; return Blockly.utils.is3dSupported.cached_; }; + +/** + * Insert a node after a reference node. + * Contrast with node.insertBefore function. + * @param {!Element} newNode New element to insert. + * @param {!Element} refNode Existing element to precede new node. + * @private + */ +Blockly.utils.insertAfter_ = function(newNode, refNode) { + var siblingNode = refNode.nextSibling; + var parentNode = refNode.parentNode; + if (!parentNode) { + throw 'Reference node has no parent.'; + } + if (siblingNode) { + parentNode.insertBefore(newNode, siblingNode); + } else { + parentNode.appendChild(newNode); + } +}; diff --git a/core/workspace_svg.js b/core/workspace_svg.js index 9fd384ea8..efdf5c37d 100644 --- a/core/workspace_svg.js +++ b/core/workspace_svg.js @@ -336,8 +336,6 @@ Blockly.WorkspaceSvg.prototype.createDom = function(opt_backgroundClass) { * @private */ this.toolbox_ = new Blockly.Toolbox(this); - } else if (this.options.languageTree) { - this.addFlyout_(); } this.updateGridPattern_(); this.recordDeleteAreas(); @@ -430,10 +428,12 @@ Blockly.WorkspaceSvg.prototype.addZoomControls_ = function(bottom) { }; /** - * Add a flyout. + * Add a flyout element in an element with the given tag name. + * @param {string} tagName What type of tag the flyout belongs in. + * @return {!Element} The element containing the flyout dom. * @private */ -Blockly.WorkspaceSvg.prototype.addFlyout_ = function() { +Blockly.WorkspaceSvg.prototype.addFlyout_ = function(tagName) { var workspaceOptions = { disabledPatternId: this.options.disabledPatternId, parentWorkspace: this, @@ -445,8 +445,28 @@ Blockly.WorkspaceSvg.prototype.addFlyout_ = function() { /** @type {Blockly.Flyout} */ this.flyout_ = new Blockly.Flyout(workspaceOptions); this.flyout_.autoClose = false; - var svgFlyout = this.flyout_.createDom(); - this.svgGroup_.insertBefore(svgFlyout, this.svgBlockCanvas_); + + // Return the element so that callers can place it in their desired + // spot in the dom. For exmaple, mutator flyouts do not go in the same place + // as main workspace flyouts. + return this.flyout_.createDom(tagName); +}; + +/** + * Getter for the flyout associated with this workspace. This flyout may be + * owned by either the toolbox or the workspace, depending on toolbox + * configuration. It will be null if there is no flyout. + * @return {Blockly.Flyout} The flyout on this workspace. + * @package + */ +Blockly.WorkspaceSvg.prototype.getFlyout_ = function() { + if (this.flyout_) { + return this.flyout_; + } + if (this.toolbox_) { + return this.toolbox_.flyout_; + } + return null; }; /** @@ -586,6 +606,19 @@ Blockly.WorkspaceSvg.prototype.getWidth = function() { * @param {boolean} isVisible True if workspace should be visible. */ Blockly.WorkspaceSvg.prototype.setVisible = function(isVisible) { + + // Tell the scrollbar whether its container is visible so it can + // tell when to hide itself. + if (this.scrollbar) { + this.scrollbar.setContainerVisible(isVisible); + } + + // Tell the flyout whether its container is visible so it can + // tell when to hide itself. + if (this.getFlyout_()) { + this.getFlyout_().setContainerVisible(isVisible); + } + this.getParentSvg().style.display = isVisible ? 'block' : 'none'; if (this.toolbox_) { // Currently does not support toolboxes in mutators.