diff --git a/core/block_svg.js b/core/block_svg.js index c8ac13956..36b9dac54 100644 --- a/core/block_svg.js +++ b/core/block_svg.js @@ -75,23 +75,6 @@ Blockly.BlockSvg = function(workspace, prototypeName, opt_id) { */ this.pathObject = workspace.getRenderer().makePathObject(this.svgGroup_); - // The next two paths are set only for backwards compatibility reasons. - /** - * The primary path of the block. - * @type {SVGElement} - * @private - */ - this.svgPath_ = this.pathObject.svgPath || null; - - /** - * The light path of the block. - * @type {SVGElement} - * @private - */ - this.svgPathLight_ = this.pathObject.svgPathLight || null; - - this.svgPath_.tooltip = this; - /** @type {boolean} */ this.rendered = false; @@ -114,7 +97,9 @@ Blockly.BlockSvg = function(workspace, prototypeName, opt_id) { this.useDragSurface_ = Blockly.utils.is3dSupported() && !!workspace.getBlockDragSurface(); - Blockly.Tooltip.bindMouseEvents(this.svgPath_); + var svgPath = this.pathObject.svgPath; + svgPath.tooltip = this; + Blockly.Tooltip.bindMouseEvents(svgPath); Blockly.BlockSvg.superClass_.constructor.call(this, workspace, prototypeName, opt_id); @@ -1022,8 +1007,6 @@ Blockly.BlockSvg.prototype.dispose = function(healStack, animate) { blockWorkspace.resizeContents(); // Sever JavaScript to DOM connections. this.svgGroup_ = null; - this.svgPath_ = null; - this.svgPathLight_ = null; Blockly.utils.dom.stopTextWidthCache(); }; @@ -1059,14 +1042,13 @@ Blockly.BlockSvg.prototype.updateDisabled = function() { var added = Blockly.utils.dom.addClass( /** @type {!Element} */ (this.svgGroup_), 'blocklyDisabled'); if (added) { - this.svgPath_.setAttribute('fill', - 'url(#' + this.workspace.options.disabledPatternId + ')'); + this.pathObject.setDisabled(true, this.isShadow()); } } else { var removed = Blockly.utils.dom.removeClass( /** @type {!Element} */ (this.svgGroup_), 'blocklyDisabled'); if (removed) { - this.applyColour(); + this.pathObject.setDisabled(false, this.isShadow()); } } var children = this.getChildren(false); @@ -1253,14 +1235,7 @@ Blockly.BlockSvg.prototype.setHighlighted = function(highlighted) { if (!this.rendered) { return; } - if (highlighted) { - this.svgPath_.setAttribute('filter', - 'url(#' + this.workspace.options.embossFilterId + ')'); - this.svgPathLight_.style.display = 'none'; - } else { - this.svgPath_.setAttribute('filter', 'none'); - this.svgPathLight_.style.display = 'inline'; - } + this.pathObject.setHighlighted(highlighted); }; /** diff --git a/core/bubble.js b/core/bubble.js index c3713d4f8..4e139bdc3 100644 --- a/core/bubble.js +++ b/core/bubble.js @@ -232,7 +232,8 @@ Blockly.Bubble.prototype.createDom_ = function(content, hasResize) { */ this.bubbleGroup_ = Blockly.utils.dom.createSvgElement('g', {}, null); var filter = - {'filter': 'url(#' + this.workspace_.options.embossFilterId + ')'}; + {'filter': 'url(#' + + this.workspace_.getRenderer().getConstants().embossFilterId + ')'}; if (Blockly.utils.userAgent.JAVA_FX) { // Multiple reports that JavaFX can't handle filters. // https://github.com/google/blockly/issues/99 @@ -240,7 +241,8 @@ Blockly.Bubble.prototype.createDom_ = function(content, hasResize) { } var bubbleEmboss = Blockly.utils.dom.createSvgElement('g', filter, this.bubbleGroup_); - this.bubbleArrow_ = Blockly.utils.dom.createSvgElement('path', {}, bubbleEmboss); + this.bubbleArrow_ = Blockly.utils.dom.createSvgElement('path', {}, + bubbleEmboss); this.bubbleBack_ = Blockly.utils.dom.createSvgElement('rect', { 'class': 'blocklyDraggable', diff --git a/core/inject.js b/core/inject.js index 805200e75..be46c684c 100644 --- a/core/inject.js +++ b/core/inject.js @@ -139,73 +139,6 @@ Blockly.createDom_ = function(container, options) { // instances on a page. Browser behaviour becomes undefined otherwise. // https://neil.fraser.name/news/2015/11/01/ var rnd = String(Math.random()).substring(2); - /* - - - - - - - - - */ - var embossFilter = Blockly.utils.dom.createSvgElement('filter', - {'id': 'blocklyEmbossFilter' + rnd}, defs); - Blockly.utils.dom.createSvgElement('feGaussianBlur', - {'in': 'SourceAlpha', 'stdDeviation': 1, 'result': 'blur'}, embossFilter); - var feSpecularLighting = Blockly.utils.dom.createSvgElement('feSpecularLighting', - { - 'in': 'blur', - 'surfaceScale': 1, - 'specularConstant': 0.5, - 'specularExponent': 10, - 'lighting-color': 'white', - 'result': 'specOut' - }, - embossFilter); - Blockly.utils.dom.createSvgElement('fePointLight', - {'x': -5000, 'y': -10000, 'z': 20000}, feSpecularLighting); - Blockly.utils.dom.createSvgElement('feComposite', - { - 'in': 'specOut', - 'in2': 'SourceAlpha', - 'operator': 'in', - 'result': 'specOut' - }, embossFilter); - Blockly.utils.dom.createSvgElement('feComposite', - { - 'in': 'SourceGraphic', - 'in2': 'specOut', - 'operator': 'arithmetic', - 'k1': 0, - 'k2': 1, - 'k3': 1, - 'k4': 0 - }, embossFilter); - options.embossFilterId = embossFilter.id; - /* - - - - - */ - var disabledPattern = Blockly.utils.dom.createSvgElement('pattern', - { - 'id': 'blocklyDisabledPattern' + rnd, - 'patternUnits': 'userSpaceOnUse', - 'width': 10, - 'height': 10 - }, defs); - Blockly.utils.dom.createSvgElement('rect', - {'width': 10, 'height': 10, 'fill': '#aaa'}, disabledPattern); - Blockly.utils.dom.createSvgElement('path', - {'d': 'M 0 0 L 10 10 M 10 0 L 0 10', 'stroke': '#cc0'}, disabledPattern); - options.disabledPatternId = disabledPattern.id; options.gridPattern = Blockly.Grid.createDom(rnd, options.gridOptions, defs); return svg; diff --git a/core/mutator.js b/core/mutator.js index 3b1a1ff4b..006aea0b8 100644 --- a/core/mutator.js +++ b/core/mutator.js @@ -154,7 +154,6 @@ Blockly.Mutator.prototype.createEditor_ = function() { // If you want to enable disabling, also remove the // event filter from workspaceChanged_ . disable: false, - disabledPatternId: this.block_.workspace.options.disabledPatternId, languageTree: quarkXml, parentWorkspace: this.block_.workspace, pathToMedia: this.block_.workspace.options.pathToMedia, diff --git a/core/renderers/common/constants.js b/core/renderers/common/constants.js index bfc753f09..6aa0863f6 100644 --- a/core/renderers/common/constants.js +++ b/core/renderers/common/constants.js @@ -23,6 +23,7 @@ goog.provide('Blockly.blockRendering.ConstantProvider'); +goog.require('Blockly.utils.dom'); goog.require('Blockly.utils.svgPaths'); @@ -134,6 +135,34 @@ Blockly.blockRendering.ConstantProvider = function() { * @const */ this.JAGGED_TEETH_WIDTH = 6; + + /** + * The ID of the emboss filter, or the empty string if no filter is set. + * @type {string} + * @package + */ + this.embossFilterId = ''; + + /** + * The element to use for highlighting, or null if not set. + * @type {SVGElement} + * @private + */ + this.embossFilter_ = null; + + /** + * The ID of the disabled pattern, or the empty string if no pattern is set. + * @type {string} + * @package + */ + this.disabledPatternId = ''; + + /** + * The element to use for disabled blocks, or null if not set. + * @type {SVGElement} + * @private + */ + this.disabledPattern_ = null; }; /** @@ -180,6 +209,20 @@ Blockly.blockRendering.ConstantProvider.prototype.init = function() { this.OUTSIDE_CORNERS = this.makeOutsideCorners(); }; +/** + * Dispose of this constants provider. + * Delete all DOM elements that this provider created. + * @package + */ +Blockly.blockRendering.ConstantProvider.prototype.dispose = function() { + if (this.embossFilter_) { + Blockly.utils.dom.removeNode(this.embossFilter_); + } + if (this.disabledPattern_) { + Blockly.utils.dom.removeNode(this.disabledPattern_); + } +}; + /** * @return {!Object} An object containing sizing and path information about * collapsed block indicators. @@ -398,3 +441,91 @@ Blockly.blockRendering.ConstantProvider.prototype.shapeFor = function( throw Error('Unknown connection type'); } }; + +/** + * Create any DOM elements that this renderer needs (filters, patterns, etc). + * @param {!SVGElement} svg The root of the workspace's SVG. + * @package + */ +Blockly.blockRendering.ConstantProvider.prototype.createDom = function(svg) { + /* + + ... filters go here ... + + */ + var defs = Blockly.utils.dom.createSvgElement('defs', {}, svg); + // Each filter/pattern needs a unique ID for the case of multiple Blockly + // instances on a page. Browser behaviour becomes undefined otherwise. + // https://neil.fraser.name/news/2015/11/01/ + var rnd = String(Math.random()).substring(2); + /* + + + + + + + + + */ + var embossFilter = Blockly.utils.dom.createSvgElement('filter', + {'id': 'blocklyEmbossFilter' + rnd}, defs); + Blockly.utils.dom.createSvgElement('feGaussianBlur', + {'in': 'SourceAlpha', 'stdDeviation': 1, 'result': 'blur'}, embossFilter); + var feSpecularLighting = Blockly.utils.dom.createSvgElement('feSpecularLighting', + { + 'in': 'blur', + 'surfaceScale': 1, + 'specularConstant': 0.5, + 'specularExponent': 10, + 'lighting-color': 'white', + 'result': 'specOut' + }, + embossFilter); + Blockly.utils.dom.createSvgElement('fePointLight', + {'x': -5000, 'y': -10000, 'z': 20000}, feSpecularLighting); + Blockly.utils.dom.createSvgElement('feComposite', + { + 'in': 'specOut', + 'in2': 'SourceAlpha', + 'operator': 'in', + 'result': 'specOut' + }, embossFilter); + Blockly.utils.dom.createSvgElement('feComposite', + { + 'in': 'SourceGraphic', + 'in2': 'specOut', + 'operator': 'arithmetic', + 'k1': 0, + 'k2': 1, + 'k3': 1, + 'k4': 0 + }, embossFilter); + this.embossFilterId = embossFilter.id; + this.embossFilter_ = embossFilter; + + /* + + + + + */ + var disabledPattern = Blockly.utils.dom.createSvgElement('pattern', + { + 'id': 'blocklyDisabledPattern' + rnd, + 'patternUnits': 'userSpaceOnUse', + 'width': 10, + 'height': 10 + }, defs); + Blockly.utils.dom.createSvgElement('rect', + {'width': 10, 'height': 10, 'fill': '#aaa'}, disabledPattern); + Blockly.utils.dom.createSvgElement('path', + {'d': 'M 0 0 L 10 10 M 10 0 L 0 10', 'stroke': '#cc0'}, disabledPattern); + this.disabledPatternId = disabledPattern.id; + this.disabledPattern_ = disabledPattern; +}; diff --git a/core/renderers/common/i_path_object.js b/core/renderers/common/i_path_object.js index fd81f8e03..8905a4bd4 100644 --- a/core/renderers/common/i_path_object.js +++ b/core/renderers/common/i_path_object.js @@ -25,15 +25,18 @@ goog.provide('Blockly.blockRendering.IPathObject'); +goog.requireType('Blockly.blockRendering.ConstantProvider'); goog.requireType('Blockly.Theme'); /** * An interface for a block's path object. * @param {!SVGElement} _root The root SVG element. + * @param {!Blockly.blockRendering.ConstantProvider} _constants The renderer's + * constants. * @interface */ -Blockly.blockRendering.IPathObject = function(_root) {}; +Blockly.blockRendering.IPathObject = function(_root, _constants) {}; /** * Apply the stored colours to the block's path, taking into account whether @@ -55,3 +58,17 @@ Blockly.blockRendering.IPathObject.prototype.setStyle; * @package */ Blockly.blockRendering.IPathObject.prototype.flipRTL; + +/** + * Set whether the block shows a highlight or not. Block highlighting is + * often used to visually mark blocks currently being executed. + * @param {boolean} highlighted True if highlighted. + */ +Blockly.blockRendering.IPathObject.prototype.setHighlighted; + +/** + * Set whether the block shows a disable pattern or not. + * @param {boolean} disabled True if disabled. + * @param {boolean} isShadow True if the block is a shadow block. + */ +Blockly.blockRendering.IPathObject.prototype.setDisabled; diff --git a/core/renderers/common/path_object.js b/core/renderers/common/path_object.js index 493245289..a850686ff 100644 --- a/core/renderers/common/path_object.js +++ b/core/renderers/common/path_object.js @@ -24,6 +24,7 @@ goog.provide('Blockly.blockRendering.PathObject'); +goog.require('Blockly.blockRendering.ConstantProvider'); goog.require('Blockly.blockRendering.IPathObject'); goog.require('Blockly.Theme'); goog.require('Blockly.utils.dom'); @@ -33,11 +34,18 @@ goog.require('Blockly.utils.dom'); * An object that handles creating and setting each of the SVG elements * used by the renderer. * @param {!SVGElement} root The root SVG element. + * @param {!Blockly.blockRendering.ConstantProvider} constants The renderer's + * constants. * @constructor * @implements {Blockly.blockRendering.IPathObject} * @package */ -Blockly.blockRendering.PathObject = function(root) { +Blockly.blockRendering.PathObject = function(root, constants) { + /** + * The renderer's constant provider. + * @type {!Blockly.blockRendering.ConstantProvider} + */ + this.constants_ = constants; this.svgRoot = root; /** @@ -111,3 +119,33 @@ Blockly.blockRendering.PathObject.prototype.applyColour = function(isShadow) { Blockly.blockRendering.PathObject.prototype.setStyle = function(blockStyle) { this.style = blockStyle; }; + +/** + * Set whether the block shows a highlight or not. Block highlighting is + * often used to visually mark blocks currently being executed. + * @param {boolean} highlighted True if highlighted. + */ +Blockly.blockRendering.PathObject.prototype.setHighlighted = function( + highlighted) { + if (highlighted) { + this.svgPath.setAttribute('filter', + 'url(#' + this.constants_.embossFilterId + ')'); + } else { + this.svgPath.setAttribute('filter', 'none'); + } +}; + +/** + * Set whether the block shows a disable pattern or not. + * @param {boolean} disabled True if disabled. + * @param {boolean} isShadow True if the block is a shadow block. + */ +Blockly.blockRendering.PathObject.prototype.setDisabled = function(disabled, + isShadow) { + if (disabled) { + this.svgPath.setAttribute('fill', + 'url(#' + this.constants_.disabledPatternId + ')'); + } else { + this.applyColour(isShadow); + } +}; diff --git a/core/renderers/common/renderer.js b/core/renderers/common/renderer.js index 49a83943a..c6f16677c 100644 --- a/core/renderers/common/renderer.js +++ b/core/renderers/common/renderer.js @@ -122,7 +122,9 @@ Blockly.blockRendering.Renderer.prototype.makeCursorDrawer = function( * @package */ Blockly.blockRendering.Renderer.prototype.makePathObject = function(root) { - return new Blockly.blockRendering.PathObject(root); + return new Blockly.blockRendering.PathObject(root, + /** @type {!Blockly.blockRendering.ConstantProvider} */ (this.constants_)); + }; /** diff --git a/core/renderers/geras/path_object.js b/core/renderers/geras/path_object.js index 701bc324a..8225268fc 100644 --- a/core/renderers/geras/path_object.js +++ b/core/renderers/geras/path_object.js @@ -24,7 +24,9 @@ goog.provide('Blockly.geras.PathObject'); +goog.require('Blockly.blockRendering.ConstantProvider'); goog.require('Blockly.blockRendering.IPathObject'); +goog.require('Blockly.geras.ConstantProvider'); goog.require('Blockly.Theme'); goog.require('Blockly.utils.dom'); goog.require('Blockly.utils.object'); @@ -34,11 +36,19 @@ goog.require('Blockly.utils.object'); * An object that handles creating and setting each of the SVG elements * used by the renderer. * @param {!SVGElement} root The root SVG element. + * @param {!Blockly.blockRendering.ConstantProvider} constants The renderer's + * constants. * @constructor * @implements {Blockly.blockRendering.IPathObject} * @package */ -Blockly.geras.PathObject = function(root) { +Blockly.geras.PathObject = function(root, constants) { + /** + * The renderer's constant provider. + * @type {!Blockly.geras.ConstantProvider} + */ + this.constants_ = /** @type {!Blockly.geras.ConstantProvider} */ (constants); + this.svgRoot = root; // The order of creation for these next three matters, because that @@ -140,3 +150,33 @@ Blockly.geras.PathObject.prototype.setStyle = function(blockStyle) { Blockly.utils.colour.blend('#000', this.style.colourPrimary, 0.2) || this.colourDark; }; + +/** + * Set whether the block shows a highlight or not. Block highlighting is + * often used to visually mark blocks currently being executed. + * @param {boolean} highlighted True if highlighted. + */ +Blockly.geras.PathObject.prototype.setHighlighted = function(highlighted) { + if (highlighted) { + this.svgPath.setAttribute('filter', + 'url(#' + this.constants_.embossFilterId + ')'); + this.svgPathLight.style.display = 'none'; + } else { + this.svgPath.setAttribute('filter', 'none'); + this.svgPathLight.style.display = 'inline'; + } +}; + +/** + * Set whether the block shows a disable pattern or not. + * @param {boolean} disabled True if disabled. + * @param {boolean} isShadow True if the block is a shadow block. + */ +Blockly.geras.PathObject.prototype.setDisabled = function(disabled, isShadow) { + if (disabled) { + this.svgPath.setAttribute('fill', + 'url(#' + this.constants_.disabledPatternId + ')'); + } else { + this.applyColour(isShadow); + } +}; diff --git a/core/renderers/geras/renderer.js b/core/renderers/geras/renderer.js index fcd5e516d..0a43bf18e 100644 --- a/core/renderers/geras/renderer.js +++ b/core/renderers/geras/renderer.js @@ -103,7 +103,8 @@ Blockly.geras.Renderer.prototype.makeDrawer_ = function(block, info) { * @override */ Blockly.geras.Renderer.prototype.makePathObject = function(root) { - return new Blockly.geras.PathObject(root); + return new Blockly.geras.PathObject(root, + /** @type {!Blockly.geras.ConstantProvider} */ (this.getConstants())); }; /** diff --git a/core/toolbox.js b/core/toolbox.js index 59d2c2044..8b939eb61 100644 --- a/core/toolbox.js +++ b/core/toolbox.js @@ -179,7 +179,6 @@ Blockly.Toolbox.prototype.init = function() { Blockly.Touch.clearTouchIdentifier(); // Don't block future drags. }, /* opt_noCaptureIdentifier */ false, /* opt_noPreventDefault */ true); var workspaceOptions = /** @type {!Blockly.Options} */ ({ - disabledPatternId: workspace.options.disabledPatternId, parentWorkspace: workspace, RTL: workspace.RTL, oneBasedIndex: workspace.options.oneBasedIndex, diff --git a/core/trashcan.js b/core/trashcan.js index b676626a2..7eba792ea 100644 --- a/core/trashcan.js +++ b/core/trashcan.js @@ -62,7 +62,6 @@ Blockly.Trashcan = function(workspace) { // Create flyout options. var flyoutWorkspaceOptions = /** @type {!Blockly.Options} */ ({ scrollbars: true, - disabledPatternId: this.workspace_.options.disabledPatternId, parentWorkspace: this.workspace_, RTL: this.workspace_.RTL, oneBasedIndex: this.workspace_.options.oneBasedIndex, diff --git a/core/workspace_svg.js b/core/workspace_svg.js index 4876dd0e0..bf3f344d6 100644 --- a/core/workspace_svg.js +++ b/core/workspace_svg.js @@ -676,6 +676,7 @@ Blockly.WorkspaceSvg.prototype.createDom = function(opt_backgroundClass) { var svgMarker = this.marker_.getDrawer().createDom(); this.svgGroup_.appendChild(svgMarker); + this.getRenderer().getConstants().createDom(this.svgGroup_); return this.svgGroup_; }; @@ -735,6 +736,8 @@ Blockly.WorkspaceSvg.prototype.dispose = function() { this.grid_ = null; } + this.renderer_.getConstants().dispose(); + if (this.themeManager_) { this.themeManager_.unsubscribe(this.svgBackground_); } @@ -808,7 +811,6 @@ Blockly.WorkspaceSvg.prototype.addZoomControls = function() { */ Blockly.WorkspaceSvg.prototype.addFlyout = function(tagName) { var workspaceOptions = /** @type {!Blockly.Options} */ ({ - disabledPatternId: this.options.disabledPatternId, parentWorkspace: this, RTL: this.RTL, oneBasedIndex: this.options.oneBasedIndex,