diff --git a/core/blockly.js b/core/blockly.js index ec1829833..fcfa0b95d 100644 --- a/core/blockly.js +++ b/core/blockly.js @@ -689,8 +689,10 @@ Blockly.setTheme = function(theme) { this.theme_ = theme; var ws = Blockly.getMainWorkspace(); + //update blocks in workspace this.updateBlockStyles_(ws.getAllBlocks()); + //update blocks in the flyout if (!ws.toolbox_ && ws.flyout_ && ws.flyout_.workspace_) { this.updateBlockStyles_(ws.flyout_.workspace_.getAllBlocks()); } @@ -698,6 +700,11 @@ Blockly.setTheme = function(theme) { ws.refreshToolboxSelection(); } + //update colours on the categories + if (ws.toolbox_) { + ws.toolbox_.updateColourFromTheme(); + } + var event = new Blockly.Events.Ui(null, 'themeChanged'); event.workspaceId = ws.id; Blockly.Events.fire(event); diff --git a/core/theme.js b/core/theme.js index cdc495f19..c4d3dddfa 100644 --- a/core/theme.js +++ b/core/theme.js @@ -28,11 +28,14 @@ goog.provide('Blockly.Theme'); /** * Class for a theme. * @param {Object.} blockStyles A map from style - * names (strings) to objects with style attributes. + * names (strings) to objects with style attributes relating to blocks. + * @param {Object.} categoryStyles A map from style + * names (strings) to objects with style attributes relating to categories. * @constructor */ -Blockly.Theme = function(blockStyles) { +Blockly.Theme = function(blockStyles, categoryStyles) { this.blockStyles_ = blockStyles; + this.categoryStyles_ = categoryStyles; }; /** @@ -71,3 +74,21 @@ Blockly.Theme.prototype.getBlockStyle = function(blockStyleName) { Blockly.Theme.prototype.setBlockStyle = function(blockStyleName, blockStyle) { this.blockStyles_[blockStyleName] = blockStyle; }; + +/** + * Gets the CategoryStyle for the given category style name. + * @param{String} categoryStyleName The name of the block style. + * @return {Blockly.CategoryStyle} The style with the block style name. + */ +Blockly.Theme.prototype.getCategoryStyle = function(categoryStyleName) { + return this.categoryStyles_[categoryStyleName]; +}; + +/** + * Overrides or adds a style to the categoryStyles map. + * @param{String} categoryStyleName The name of the category style. + * @param{Blockly.CategoryStyle} categoryStyle The category style +*/ +Blockly.Theme.prototype.setCategoryStyle = function(categoryStyleName, categoryStyle) { + this.categoryStyles_[categoryStyleName] = categoryStyle; +}; diff --git a/core/toolbox.js b/core/toolbox.js index 685b9be0e..8c49c360c 100644 --- a/core/toolbox.js +++ b/core/toolbox.js @@ -332,25 +332,22 @@ Blockly.Toolbox.prototype.syncTrees_ = function(treeIn, treeOut, pathToMedia) { openNode = newOpenNode; } } - // Decode the colour for any potential message references - // (eg. `%{BKY_MATH_HUE}`). - var colour = Blockly.utils.replaceMessageReferences( - childIn.getAttribute('colour')); - if (colour === null || colour === '') { - // No attribute. No colour. - childOut.hexColour = ''; - } else if (/^#[0-9a-fA-F]{6}$/.test(colour)) { - childOut.hexColour = colour; - this.hasColours_ = true; - } else if (typeof colour === 'number' || - (typeof colour === 'string' && !isNaN(Number(colour)))) { - childOut.hexColour = Blockly.hueToRgb(Number(colour)); - this.hasColours_ = true; - } else { + + var styleName = childIn.getAttribute('style'); + var colour = childIn.getAttribute('colour'); + + if (colour && styleName) { childOut.hexColour = ''; console.warn('Toolbox category "' + categoryName + - '" has unrecognized colour attribute: ' + colour); + '" can not have both a style and a colour'); } + else if (styleName) { + this.setColourFromStyle_(styleName, childOut, categoryName); + } + else { + this.setColour_(colour, childOut, categoryName); + } + if (childIn.getAttribute('expanded') == 'true') { if (childOut.blocks.length) { // This is a category that directly contains blocks. @@ -395,6 +392,103 @@ Blockly.Toolbox.prototype.syncTrees_ = function(treeIn, treeOut, pathToMedia) { return openNode; }; +/** + * Sets the colour on the category. + * @param {number|string} colourValue HSV hue value (0 to 360), #RRGGBB string, + * or a message reference string pointing to one of those two values. + * @param {Blockly.Toolbox.TreeNode} childOut The child to set the hexColour on. + * @param {string} categoryName Name of the toolbox category. + * @private + */ +Blockly.Toolbox.prototype.setColour_ = function(colourValue, childOut, categoryName){ + // Decode the colour for any potential message references + // (eg. `%{BKY_MATH_HUE}`). + var colour = Blockly.utils.replaceMessageReferences(colourValue); + if (colour === null || colour === '') { + // No attribute. No colour. + childOut.hexColour = ''; + } else if (/^#[0-9a-fA-F]{6}$/.test(colour)) { + childOut.hexColour = colour; + this.hasColours_ = true; + } else if (typeof colour === 'number' || + (typeof colour === 'string' && !isNaN(Number(colour)))) { + childOut.hexColour = Blockly.hueToRgb(Number(colour)); + this.hasColours_ = true; + } else { + childOut.hexColour = ''; + console.warn('Toolbox category "' + categoryName + + '" has unrecognized colour attribute: ' + colour); + } +}; + +/** + * Retrieves and sets the colour for the category using the style name. + * The category colour is set from the colour style attribute. + * @param {string} styleName Name of the style. + * @param {!Blockly.Toolbox.TreeNode} childOut The child to set the hexColour on. + * @param {string} categoryName Name of the toolbox category. + */ +Blockly.Toolbox.prototype.setColourFromStyle_ = function( + styleName, childOut, categoryName){ + childOut.styleName = styleName; + if (styleName && Blockly.getTheme()) { + var style = Blockly.getTheme().getCategoryStyle(styleName); + if (style && style.colour) { + this.setColour_(style.colour, childOut, categoryName); + } + else { + console.warn('Style "' + styleName + '" must exist and contain a colour value'); + } + } +}; + +/** + * Recursively updates all the category colours using the category style name. + * @param {Blockly.Toolbox.TreeNode=} opt_tree Starting point of tree. + * Defaults to the root node. + * @private + */ +Blockly.Toolbox.prototype.updateColourFromTheme_ = function(opt_tree) { + var tree = opt_tree || this.tree_; + if (tree) { + var children = tree.getChildren(false); + for (var i = 0, child; child = children[i]; i++) { + if (child.styleName) { + this.setColourFromStyle_(child.styleName, child, ''); + this.addColour_(); + } + this.updateColourFromTheme_(child); + } + } +}; + +/** + * Updates the category colours and background colour of selected categories. + */ +Blockly.Toolbox.prototype.updateColourFromTheme = function() { + var tree = this.tree_; + if (tree) { + this.updateColourFromTheme_(tree); + this.updateSelectedItemColour_(tree); + } +}; + +/** + * Updates the background colour of the selected category. + * @param {!Blockly.Toolbox.TreeNode} tree Starting point of tree. + * Defaults to the root node. + * @private + */ +Blockly.Toolbox.prototype.updateSelectedItemColour_ = function(tree) { + var selectedItem = tree.selectedItem_; + if (selectedItem) { + var hexColour = selectedItem.hexColour || '#57e'; + selectedItem.getRowElement().style.backgroundColor = hexColour; + tree.toolbox_.addColour_(selectedItem); + } +}; + + /** * Recursively add colours to this toolbox. * @param {Blockly.Toolbox.TreeNode=} opt_tree Starting point of tree. diff --git a/tests/playground.html b/tests/playground.html index 7e1ac4395..95a40e153 100644 --- a/tests/playground.html +++ b/tests/playground.html @@ -412,7 +412,7 @@ h1 { Variables category uses untyped variable blocks. See https://developers.google.com/blockly/guides/create-custom-blocks/variables#untyped_variable_blocks for more information. -->