diff --git a/blockly_uncompressed.js b/blockly_uncompressed.js index c3b5961e8..5426d79fa 100644 --- a/blockly_uncompressed.js +++ b/blockly_uncompressed.js @@ -74,7 +74,7 @@ goog.addDependency("../../core/inject.js", ['Blockly.inject'], ['Blockly.BlockDr goog.addDependency("../../core/input.js", ['Blockly.Input'], ['Blockly.Connection', 'Blockly.FieldLabel']); goog.addDependency("../../core/insertion_marker_manager.js", ['Blockly.InsertionMarkerManager'], ['Blockly.blockAnimations', 'Blockly.Events']); goog.addDependency("../../core/keyboard_nav/action.js", ['Blockly.Action'], []); -goog.addDependency("../../core/keyboard_nav/ast_node.js", ['Blockly.ASTNode'], []); +goog.addDependency("../../core/keyboard_nav/ast_node.js", ['Blockly.ASTNode'], ['Blockly.utils.Coordinate']); goog.addDependency("../../core/keyboard_nav/cursor.js", ['Blockly.Cursor'], []); goog.addDependency("../../core/keyboard_nav/cursor_svg.js", ['Blockly.CursorSvg'], ['Blockly.Cursor', 'Blockly.utils.object']); goog.addDependency("../../core/keyboard_nav/flyout_cursor.js", ['Blockly.FlyoutCursor'], ['Blockly.Cursor', 'Blockly.utils.object']); @@ -92,8 +92,9 @@ goog.addDependency("../../core/renderers/common/block_rendering.js", ['Blockly.b goog.addDependency("../../core/renderers/common/constants.js", ['Blockly.blockRendering.ConstantProvider'], ['Blockly.utils.svgPaths']); goog.addDependency("../../core/renderers/common/debugger.js", ['Blockly.blockRendering.Debug'], ['Blockly.blockRendering.BottomRow', 'Blockly.blockRendering.InputRow', 'Blockly.blockRendering.Measurable', 'Blockly.blockRendering.RenderInfo', 'Blockly.blockRendering.Row', 'Blockly.blockRendering.SpacerRow', 'Blockly.blockRendering.TopRow', 'Blockly.blockRendering.Types']); goog.addDependency("../../core/renderers/common/drawer.js", ['Blockly.blockRendering.Drawer'], ['Blockly.blockRendering.BottomRow', 'Blockly.blockRendering.InputRow', 'Blockly.blockRendering.Measurable', 'Blockly.blockRendering.RenderInfo', 'Blockly.blockRendering.Row', 'Blockly.blockRendering.SpacerRow', 'Blockly.blockRendering.TopRow', 'Blockly.blockRendering.Types', 'Blockly.utils.svgPaths']); +goog.addDependency("../../core/renderers/common/i_path_object.js", ['Blockly.blockRendering.IPathObject'], []); goog.addDependency("../../core/renderers/common/info.js", ['Blockly.blockRendering.RenderInfo'], ['Blockly.blockRendering.BottomRow', 'Blockly.blockRendering.ExternalValueInput', 'Blockly.blockRendering.Hat', 'Blockly.blockRendering.InlineInput', 'Blockly.blockRendering.InRowSpacer', 'Blockly.blockRendering.InputRow', 'Blockly.blockRendering.Measurable', 'Blockly.blockRendering.NextConnection', 'Blockly.blockRendering.OutputConnection', 'Blockly.blockRendering.PreviousConnection', 'Blockly.blockRendering.RoundCorner', 'Blockly.blockRendering.Row', 'Blockly.blockRendering.SpacerRow', 'Blockly.blockRendering.StatementInput', 'Blockly.blockRendering.SquareCorner', 'Blockly.blockRendering.TopRow', 'Blockly.blockRendering.Types']); -goog.addDependency("../../core/renderers/common/path_object.js", ['Blockly.blockRendering.IPathObject', 'Blockly.blockRendering.PathObject'], ['Blockly.utils.dom']); +goog.addDependency("../../core/renderers/common/path_object.js", ['Blockly.blockRendering.PathObject'], ['Blockly.blockRendering.IPathObject', 'Blockly.Theme', 'Blockly.utils.dom']); goog.addDependency("../../core/renderers/common/renderer.js", ['Blockly.blockRendering.Renderer'], ['Blockly.blockRendering.ConstantProvider', 'Blockly.blockRendering.Drawer', 'Blockly.blockRendering.IPathObject', 'Blockly.blockRendering.PathObject', 'Blockly.blockRendering.RenderInfo', 'Blockly.CursorSvg']); goog.addDependency("../../core/renderers/geras/constants.js", ['Blockly.geras.ConstantProvider'], ['Blockly.blockRendering.ConstantProvider', 'Blockly.utils.object']); goog.addDependency("../../core/renderers/geras/drawer.js", ['Blockly.geras.Drawer'], ['Blockly.blockRendering.ConstantProvider', 'Blockly.blockRendering.Drawer', 'Blockly.geras.Highlighter', 'Blockly.geras.PathObject', 'Blockly.geras.RenderInfo', 'Blockly.utils.object', 'Blockly.utils.svgPaths']); @@ -101,7 +102,7 @@ goog.addDependency("../../core/renderers/geras/highlight_constants.js", ['Blockl goog.addDependency("../../core/renderers/geras/highlighter.js", ['Blockly.geras.Highlighter'], ['Blockly.blockRendering.BottomRow', 'Blockly.blockRendering.InputRow', 'Blockly.blockRendering.Measurable', 'Blockly.blockRendering.RenderInfo', 'Blockly.blockRendering.Row', 'Blockly.blockRendering.SpacerRow', 'Blockly.blockRendering.TopRow', 'Blockly.blockRendering.Types', 'Blockly.utils.svgPaths']); goog.addDependency("../../core/renderers/geras/info.js", ['Blockly.geras', 'Blockly.geras.RenderInfo'], ['Blockly.blockRendering.BottomRow', 'Blockly.blockRendering.InputRow', 'Blockly.blockRendering.Measurable', 'Blockly.blockRendering.NextConnection', 'Blockly.blockRendering.OutputConnection', 'Blockly.blockRendering.PreviousConnection', 'Blockly.blockRendering.RenderInfo', 'Blockly.blockRendering.BottomRow', 'Blockly.blockRendering.InputRow', 'Blockly.blockRendering.Measurable', 'Blockly.blockRendering.NextConnection', 'Blockly.blockRendering.OutputConnection', 'Blockly.blockRendering.PreviousConnection', 'Blockly.blockRendering.Types', 'Blockly.blockRendering.ExternalValueInput', 'Blockly.geras.InlineInput', 'Blockly.geras.StatementInput', 'Blockly.utils.object']); goog.addDependency("../../core/renderers/geras/measurables/inputs.js", ['Blockly.geras.InlineInput', 'Blockly.geras.StatementInput'], ['Blockly.utils.object']); -goog.addDependency("../../core/renderers/geras/path_object.js", ['Blockly.geras.PathObject'], ['Blockly.blockRendering.IPathObject', 'Blockly.utils.dom']); +goog.addDependency("../../core/renderers/geras/path_object.js", ['Blockly.geras.PathObject'], ['Blockly.blockRendering.IPathObject', 'Blockly.utils.dom', 'Blockly.utils.object']); goog.addDependency("../../core/renderers/geras/renderer.js", ['Blockly.geras.Renderer'], ['Blockly.blockRendering', 'Blockly.blockRendering.Renderer', 'Blockly.geras.ConstantProvider', 'Blockly.geras.Drawer', 'Blockly.geras.HighlightConstantProvider', 'Blockly.geras.PathObject', 'Blockly.geras.RenderInfo', 'Blockly.utils.object']); goog.addDependency("../../core/renderers/measurables/base.js", ['Blockly.blockRendering.Measurable'], ['Blockly.blockRendering.Types']); goog.addDependency("../../core/renderers/measurables/connections.js", ['Blockly.blockRendering.Connection', 'Blockly.blockRendering.NextConnection', 'Blockly.blockRendering.OutputConnection', 'Blockly.blockRendering.PreviousConnection'], ['Blockly.blockRendering.Measurable', 'Blockly.blockRendering.Types', 'Blockly.utils.object']); @@ -122,7 +123,7 @@ goog.addDependency("../../core/renderers/zelos/measurables/rows.js", ['Blockly.z goog.addDependency("../../core/renderers/zelos/renderer.js", ['Blockly.zelos.Renderer'], ['Blockly.blockRendering', 'Blockly.blockRendering.Renderer', 'Blockly.utils.object', 'Blockly.zelos.ConstantProvider', 'Blockly.zelos.Drawer', 'Blockly.zelos.RenderInfo']); goog.addDependency("../../core/requires.js", ['Blockly.requires'], ['Blockly', 'Blockly.Comment', 'Blockly.HorizontalFlyout', 'Blockly.VerticalFlyout', 'Blockly.FlyoutButton', 'Blockly.Generator', 'Blockly.Toolbox', 'Blockly.Trashcan', 'Blockly.VariablesDynamic', 'Blockly.ZoomControls', 'Blockly.Mutator', 'Blockly.FieldAngle', 'Blockly.FieldCheckbox', 'Blockly.FieldColour', 'Blockly.FieldDropdown', 'Blockly.FieldLabelSerializable', 'Blockly.FieldImage', 'Blockly.FieldTextInput', 'Blockly.FieldMultilineInput', 'Blockly.FieldNumber', 'Blockly.FieldVariable', 'Blockly.geras.Renderer', 'Blockly.Themes.Classic', 'Blockly.Themes.Dark', 'Blockly.Themes.Deuteranopia', 'Blockly.Themes.HighContrast', 'Blockly.Themes.Tritanopia']); goog.addDependency("../../core/scrollbar.js", ['Blockly.Scrollbar', 'Blockly.ScrollbarPair'], ['Blockly.Touch', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.dom']); -goog.addDependency("../../core/theme.js", ['Blockly.Theme'], []); +goog.addDependency("../../core/theme.js", ['Blockly.Theme'], ['Blockly.utils.colour']); goog.addDependency("../../core/theme/classic.js", ['Blockly.Themes.Classic'], ['Blockly.Theme']); goog.addDependency("../../core/theme/dark.js", ['Blockly.Themes.Dark'], ['Blockly.Theme']); goog.addDependency("../../core/theme/deuteranopia.js", ['Blockly.Themes.Deuteranopia'], ['Blockly.Theme']); diff --git a/core/block.js b/core/block.js index c61bc1280..9c1cdb38f 100644 --- a/core/block.js +++ b/core/block.js @@ -252,30 +252,14 @@ Blockly.Block.prototype.hue_ = null; /** * Colour of the block in '#RRGGBB' format. * @type {string} - * @private + * @protected */ Blockly.Block.prototype.colour_ = '#000000'; -/** - * Secondary colour of the block. - * Colour of shadow blocks. - * @type {?string} - * @private - */ -Blockly.Block.prototype.colourSecondary_ = null; - -/** - * Tertiary colour of the block. - * Colour of the block's border. - * @type {?string} - * @private - */ -Blockly.Block.prototype.colourTertiary_ = null; - /** * Name of the block style. * @type {?string} - * @private + * @protected */ Blockly.Block.prototype.styleName_ = null; @@ -908,60 +892,6 @@ Blockly.Block.prototype.getColour = function() { return this.colour_; }; -/** - * Get the secondary colour of a block. - * @return {?string} #RRGGBB string. - */ -Blockly.Block.prototype.getColourSecondary = function() { - return this.colourSecondary_; -}; - -/** - * Get the tertiary colour of a block. - * @return {?string} #RRGGBB string. - */ -Blockly.Block.prototype.getColourTertiary = function() { - return this.colourTertiary_; -}; - -/** - * Get the shadow colour of a block. - * @return {?string} #RRGGBB string. - */ -Blockly.Block.prototype.getColourShadow = function() { - var colourSecondary = this.getColourSecondary(); - if (colourSecondary) { - return colourSecondary; - } - return Blockly.utils.colour.blend('#fff', this.getColour(), 0.6); -}; - -/** - * Get the border colour(s) of a block. - * @return {{colourDark, colourLight, colourBorder}} An object containing - * colour values for the border(s) of the block. If the block is using a - * style the colourBorder will be defined and equal to the tertiary colour - * of the style (#RRGGBB string). Otherwise the colourDark and colourLight - * attributes will be defined (#RRGGBB strings). - * @package - */ -Blockly.Block.prototype.getColourBorder = function() { - var colourTertiary = this.getColourTertiary(); - if (colourTertiary) { - return { - colourBorder: colourTertiary, - colourLight: null, - colourDark: null - }; - } - var colour = this.getColour(); - return { - colourBorder: null, - colourLight: Blockly.utils.colour.blend('#fff', colour, 0.3), - colourDark: Blockly.utils.colour.blend('#000', colour, 0.2) - }; -}; - /** * Get the name of the block style. * @return {?string} Name of the block style. @@ -984,48 +914,17 @@ Blockly.Block.prototype.getHue = function() { * or a message reference string pointing to one of those two values. */ Blockly.Block.prototype.setColour = function(colour) { - var dereferenced = (typeof colour == 'string') ? - Blockly.utils.replaceMessageReferences(colour) : colour; - - var hue = Number(dereferenced); - if (!isNaN(hue) && 0 <= hue && hue <= 360) { - this.hue_ = hue; - this.colour_ = Blockly.hueToHex(hue); - } else { - var hex = Blockly.utils.colour.parse(dereferenced); - if (hex) { - this.colour_ = hex; - // Only store hue if colour is set as a hue. - this.hue_ = null; - } else { - var errorMsg = 'Invalid colour: "' + dereferenced + '"'; - if (colour != dereferenced) { - errorMsg += ' (from "' + colour + '")'; - } - throw Error(errorMsg); - } - } + var parsed = Blockly.utils.colour.parseBlockColour(colour); + this.hue_ = parsed.hue; + this.colour_ = parsed.hex; }; /** * Set the style and colour values of a block. * @param {string} blockStyleName Name of the block style - * @throws {Error} if the block style does not exist. */ Blockly.Block.prototype.setStyle = function(blockStyleName) { - var theme = this.workspace.getTheme(); - var blockStyle = theme.getBlockStyle(blockStyleName); this.styleName_ = blockStyleName; - - if (blockStyle) { - this.colourSecondary_ = blockStyle['colourSecondary']; - this.colourTertiary_ = blockStyle['colourTertiary']; - this.hat = blockStyle.hat; - // Set colour will trigger an updateColour() on a block_svg - this.setColour(blockStyle['colourPrimary']); - } else { - throw Error('Invalid style name: ' + blockStyleName); - } }; /** diff --git a/core/block_svg.js b/core/block_svg.js index b404e27fb..2a2a83c9c 100644 --- a/core/block_svg.js +++ b/core/block_svg.js @@ -63,22 +63,20 @@ Blockly.BlockSvg = function(workspace, prototypeName, opt_id) { this.svgGroup_ = Blockly.utils.dom.createSvgElement('g', {}, null); this.svgGroup_.translate_ = ''; + /** + * A block style object. + * @type {!Blockly.Theme.BlockStyle} + */ + this.style = workspace.getTheme().getBlockStyle(null); + /** * The renderer's path object. * @type {Blockly.blockRendering.IPathObject} * @package */ - this.pathObject = - workspace.getRenderer().makePathObject(this.svgGroup_); - - // The next three paths are set only for backwards compatibility reasons. - /** - * The dark path of the block. - * @type {SVGElement} - * @private - */ - this.svgPathDark_ = this.pathObject.svgPathDark || null; + 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} @@ -267,7 +265,7 @@ Blockly.BlockSvg.prototype.initSvg = function() { for (var i = 0; i < icons.length; i++) { icons[i].createIcon(); } - this.updateColour(); + this.applyColour(); this.updateMovable(); var svg = this.getSvgRoot(); if (!this.workspace.options.readOnly && !this.eventsInit_ && svg) { @@ -939,7 +937,7 @@ Blockly.BlockSvg.prototype.setEditable = function(editable) { */ Blockly.BlockSvg.prototype.setShadow = function(shadow) { Blockly.BlockSvg.superClass_.setShadow.call(this, shadow); - this.updateColour(); + this.applyColour(); }; /** @@ -1027,75 +1025,33 @@ Blockly.BlockSvg.prototype.dispose = function(healStack, animate) { this.svgGroup_ = null; this.svgPath_ = null; this.svgPathLight_ = null; - this.svgPathDark_ = null; Blockly.utils.dom.stopTextWidthCache(); }; /** * Change the colour of a block. + * @package */ -Blockly.BlockSvg.prototype.updateColour = function() { +Blockly.BlockSvg.prototype.applyColour = function() { if (!this.isEnabled()) { // Disabled blocks don't have colour. return; } - if (this.isShadow()) { - this.setShadowColour_(); - } else { - this.setBorderColour_(); - this.svgPath_.setAttribute('fill', this.getColour()); - } + this.pathObject.applyColour(this.isShadow()); var icons = this.getIcons(); for (var i = 0; i < icons.length; i++) { - icons[i].updateColour(); + icons[i].applyColour(); } for (var x = 0, input; input = this.inputList[x]; x++) { for (var y = 0, field; field = input.fieldRow[y]; y++) { - field.updateColour(); + field.applyColour(); } } }; -/** - * Sets the colour of the border. - * Removes the light and dark paths if a border colour is defined. - * @private - */ -Blockly.BlockSvg.prototype.setBorderColour_ = function() { - var borderColours = this.getColourBorder(); - if (borderColours.colourBorder) { - this.svgPathLight_.style.display = 'none'; - this.svgPathDark_.style.display = 'none'; - - this.svgPath_.setAttribute('stroke', borderColours.colourBorder); - } else { - this.svgPathLight_.style.display = ''; - this.svgPathDark_.style.display = ''; - this.svgPath_.setAttribute('stroke', 'none'); - - this.svgPathLight_.setAttribute('stroke', borderColours.colourLight); - this.svgPathDark_.setAttribute('fill', borderColours.colourDark); - } -}; - -/** - * Sets the colour of shadow blocks. - * @return {?string} The background colour of the block. - * @private - */ -Blockly.BlockSvg.prototype.setShadowColour_ = function() { - var shadowColour = this.getColourShadow() || ''; - - this.svgPathLight_.style.display = 'none'; - this.svgPathDark_.setAttribute('fill', shadowColour); - this.svgPath_.setAttribute('stroke', 'none'); - this.svgPath_.setAttribute('fill', shadowColour); - return shadowColour; -}; - /** * Enable or disable a block. */ @@ -1111,7 +1067,7 @@ Blockly.BlockSvg.prototype.updateDisabled = function() { var removed = Blockly.utils.dom.removeClass( /** @type {!Element} */ (this.svgGroup_), 'blocklyDisabled'); if (removed) { - this.updateColour(); + this.applyColour(); } } var children = this.getChildren(false); @@ -1337,8 +1293,16 @@ Blockly.BlockSvg.prototype.setDeleteStyle = function(enable) { } }; + // Overrides of functions on Blockly.Block that take into account whether the // block has been rendered. +/** + * Get the colour of a block. + * @return {string} #RRGGBB string. + */ +Blockly.BlockSvg.prototype.getColour = function() { + return this.style.colourPrimary; +}; /** * Change the colour of a block. @@ -1346,9 +1310,34 @@ Blockly.BlockSvg.prototype.setDeleteStyle = function(enable) { */ Blockly.BlockSvg.prototype.setColour = function(colour) { Blockly.BlockSvg.superClass_.setColour.call(this, colour); + var styleObj = this.workspace.getTheme().getBlockStyleForColour(this.colour_); - if (this.rendered) { - this.updateColour(); + this.pathObject.setStyle(styleObj.style); + this.style = styleObj.style; + this.styleName_ = styleObj.name; + + this.applyColour(); +}; + +/** + * Set the style and colour values of a block. + * @param {string} blockStyleName Name of the block style + * @throws {Error} if the block style does not exist. + */ +Blockly.BlockSvg.prototype.setStyle = function(blockStyleName) { + var blockStyle = this.workspace.getTheme().getBlockStyle(blockStyleName); + this.styleName_ = blockStyleName; + + if (blockStyle) { + this.hat = blockStyle.hat; + this.pathObject.setStyle(blockStyle); + // Set colour to match Block. + this.colour_ = blockStyle.colourPrimary; + this.style = blockStyle; + + this.applyColour(); + } else { + throw Error('Invalid style name: ' + blockStyleName); } }; diff --git a/core/comment.js b/core/comment.js index 31f3942bc..680b6ca95 100644 --- a/core/comment.js +++ b/core/comment.js @@ -247,7 +247,7 @@ Blockly.Comment.prototype.createEditableBubble_ = function() { // Expose this comment's block's ID on its top-level SVG group. this.bubble_.setSvgId(this.block_.id); this.bubble_.registerResizeEvent(this.onBubbleResize_.bind(this)); - this.updateColour(); + this.applyColour(); }; /** diff --git a/core/field.js b/core/field.js index ee7cc7d17..0b13b7bdb 100644 --- a/core/field.js +++ b/core/field.js @@ -603,10 +603,10 @@ Blockly.Field.prototype.getSvgRoot = function() { /** * Updates the field to match the colour/style of the block. Should only be - * called by BlockSvg.updateColour(). + * called by BlockSvg.applyColour(). * @package */ -Blockly.Field.prototype.updateColour = function() { +Blockly.Field.prototype.applyColour = function() { // Non-abstract sub-classes may wish to implement this. See FieldDropdown. }; diff --git a/core/field_angle.js b/core/field_angle.js index eb26c4597..3a550bcbb 100644 --- a/core/field_angle.js +++ b/core/field_angle.js @@ -269,9 +269,8 @@ Blockly.FieldAngle.prototype.showEditor_ = function() { var editor = this.dropdownCreate_(); Blockly.DropDownDiv.getContentDiv().appendChild(editor); - var border = this.sourceBlock_.getColourBorder(); - border = border.colourBorder || border.colourLight; - Blockly.DropDownDiv.setColour(this.sourceBlock_.getColour(), border); + Blockly.DropDownDiv.setColour(this.sourceBlock_.style.colourPrimary, + this.sourceBlock_.style.colourTertiary); Blockly.DropDownDiv.showPositionedByField( this, this.dropdownDispose_.bind(this)); diff --git a/core/field_date.js b/core/field_date.js index ebbfbc562..a39ca849e 100644 --- a/core/field_date.js +++ b/core/field_date.js @@ -129,9 +129,9 @@ Blockly.FieldDate.prototype.render_ = function() { * Updates the field's colours to match those of the block. * @package */ -Blockly.FieldDate.prototype.updateColour = function() { - this.todayColour_ = this.sourceBlock_.getColour(); - this.selectedColour_ = this.sourceBlock_.getColourShadow(); +Blockly.FieldDate.prototype.applyColour = function() { + this.todayColour_ = this.sourceBlock_.style.colourPrimary; + this.selectedColour_ = this.sourceBlock_.style.colourSecondary; this.updateEditor_(); }; diff --git a/core/field_dropdown.js b/core/field_dropdown.js index 3a67f6be4..5693d107c 100644 --- a/core/field_dropdown.js +++ b/core/field_dropdown.js @@ -468,13 +468,13 @@ Blockly.FieldDropdown.prototype.doValueUpdate_ = function(newValue) { * Updates the dropdown arrow to match the colour/style of the block. * @package */ -Blockly.FieldDropdown.prototype.updateColour = function() { +Blockly.FieldDropdown.prototype.applyColour = function() { // Update arrow's colour. if (this.sourceBlock_ && this.arrow_) { if (this.sourceBlock_.isShadow()) { - this.arrow_.style.fill = this.sourceBlock_.getColourShadow(); + this.arrow_.style.fill = this.sourceBlock_.style.colourSecondary; } else { - this.arrow_.style.fill = this.sourceBlock_.getColour(); + this.arrow_.style.fill = this.sourceBlock_.style.colourPrimary; } } }; diff --git a/core/icon.js b/core/icon.js index bf2c9043a..5fb7cf9c7 100644 --- a/core/icon.js +++ b/core/icon.js @@ -139,9 +139,9 @@ Blockly.Icon.prototype.iconClick_ = function(e) { /** * Change the colour of the associated bubble to match its block. */ -Blockly.Icon.prototype.updateColour = function() { +Blockly.Icon.prototype.applyColour = function() { if (this.isVisible()) { - this.bubble_.setColour(this.block_.getColour()); + this.bubble_.setColour(this.block_.style.colourPrimary); } }; diff --git a/core/mutator.js b/core/mutator.js index 46ed7070d..061420dbc 100644 --- a/core/mutator.js +++ b/core/mutator.js @@ -322,7 +322,7 @@ Blockly.Mutator.prototype.setVisible = function(visible) { this.resizeBubble_(); // When the mutator's workspace changes, update the source block. this.workspace_.addChangeListener(this.workspaceChanged_.bind(this)); - this.updateColour(); + this.applyColour(); } else { // Dispose of the bubble. this.svgDialog_ = null; diff --git a/core/renderers/common/i_path_object.js b/core/renderers/common/i_path_object.js new file mode 100644 index 000000000..fd81f8e03 --- /dev/null +++ b/core/renderers/common/i_path_object.js @@ -0,0 +1,57 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview The interface for an object that owns a block's rendering SVG + * elements. + * @author fenichel@google.com (Rachel Fenichel) + */ + +'use strict'; + +goog.provide('Blockly.blockRendering.IPathObject'); + +goog.requireType('Blockly.Theme'); + + +/** + * An interface for a block's path object. + * @param {!SVGElement} _root The root SVG element. + * @interface + */ +Blockly.blockRendering.IPathObject = function(_root) {}; + +/** + * Apply the stored colours to the block's path, taking into account whether + * the paths belong to a shadow block. + * @param {boolean} isShadow True if the block is a shadow block. + * @package + */ +Blockly.blockRendering.IPathObject.prototype.applyColour; + +/** + * Update the style. + * @param {!Blockly.Theme.BlockStyle} blockStyle The block style to use. + * @package + */ +Blockly.blockRendering.IPathObject.prototype.setStyle; + +/** + * Flip the SVG paths in RTL. + * @package + */ +Blockly.blockRendering.IPathObject.prototype.flipRTL; diff --git a/core/renderers/common/path_object.js b/core/renderers/common/path_object.js index 07b1aa343..493245289 100644 --- a/core/renderers/common/path_object.js +++ b/core/renderers/common/path_object.js @@ -22,19 +22,13 @@ 'use strict'; -goog.provide('Blockly.blockRendering.IPathObject'); goog.provide('Blockly.blockRendering.PathObject'); +goog.require('Blockly.blockRendering.IPathObject'); +goog.require('Blockly.Theme'); goog.require('Blockly.utils.dom'); -/** - * An interface for a block's path object. - * @param {!SVGElement} _root The root SVG element. - * @interface - */ -Blockly.blockRendering.IPathObject = function(_root) {}; - /** * An object that handles creating and setting each of the SVG elements * used by the renderer. @@ -67,13 +61,11 @@ Blockly.blockRendering.PathObject = function(root) { {'class': 'blocklyPathLight'}, this.svgRoot); /** - * The dark path of the block. - * @type {SVGElement} + * The style object to use when colouring block paths. + * @type {!Blockly.Theme.BlockStyle} * @package */ - this.svgPathDark = Blockly.utils.dom.createSvgElement('path', - {'class': 'blocklyPathDark', 'transform': 'translate(1,1)'}, - this.svgRoot); + this.style = Blockly.Theme.createBlockStyle('#000000'); }; /** @@ -84,7 +76,6 @@ Blockly.blockRendering.PathObject = function(root) { Blockly.blockRendering.PathObject.prototype.setPaths = function(pathString) { this.svgPath.setAttribute('d', pathString); this.svgPathLight.style.display = 'none'; - this.svgPathDark.style.display = 'none'; }; /** @@ -95,3 +86,28 @@ Blockly.blockRendering.PathObject.prototype.flipRTL = function() { // Mirror the block's path. this.svgPath.setAttribute('transform', 'scale(-1 1)'); }; + +/** + * Apply the stored colours to the block's path, taking into account whether + * the paths belong to a shadow block. + * @param {boolean} isShadow True if the block is a shadow block. + * @package + */ +Blockly.blockRendering.PathObject.prototype.applyColour = function(isShadow) { + if (isShadow) { + this.svgPath.setAttribute('stroke', 'none'); + this.svgPath.setAttribute('fill', this.style.colourSecondary); + } else { + this.svgPath.setAttribute('stroke', this.style.colourTertiary); + this.svgPath.setAttribute('fill', this.style.colourPrimary); + } +}; + +/** + * Set the style. + * @param {!Blockly.Theme.BlockStyle} blockStyle The block style to use. + * @package + */ +Blockly.blockRendering.PathObject.prototype.setStyle = function(blockStyle) { + this.style = blockStyle; +}; diff --git a/core/renderers/geras/path_object.js b/core/renderers/geras/path_object.js index 416cb54a9..42e995b85 100644 --- a/core/renderers/geras/path_object.js +++ b/core/renderers/geras/path_object.js @@ -25,7 +25,9 @@ goog.provide('Blockly.geras.PathObject'); goog.require('Blockly.blockRendering.IPathObject'); +goog.require('Blockly.Theme'); goog.require('Blockly.utils.dom'); +goog.require('Blockly.utils.object'); /** @@ -66,6 +68,20 @@ Blockly.geras.PathObject = function(root) { */ this.svgPathLight = Blockly.utils.dom.createSvgElement('path', {'class': 'blocklyPathLight'}, this.svgRoot); + + /** + * The colour of the dark path on the block in '#RRGGBB' format. + * @type {string} + * @package + */ + this.colourDark = '#000000'; + + /** + * The style object to use when colouring block paths. + * @type {!Blockly.Theme.BlockStyle} + * @package + */ + this.style = Blockly.Theme.createBlockStyle('#000000'); }; /** @@ -90,3 +106,36 @@ Blockly.geras.PathObject.prototype.flipRTL = function() { this.svgPathLight.setAttribute('transform', 'scale(-1 1)'); this.svgPathDark.setAttribute('transform', 'translate(1,1) scale(-1 1)'); }; + +/** + * Apply the stored colours to the block's path, taking into account whether + * the paths belong to a shadow block. + * @param {boolean} isShadow True if the block is a shadow block. + * @package + */ +Blockly.geras.PathObject.prototype.applyColour = function(isShadow) { + if (isShadow) { + this.svgPathLight.style.display = 'none'; + this.svgPathDark.setAttribute('fill', this.style.colourSecondary); + this.svgPath.setAttribute('stroke', 'none'); + this.svgPath.setAttribute('fill', this.style.colourSecondary); + } else { + this.svgPathLight.style.display = ''; + this.svgPathDark.style.display = ''; + this.svgPath.setAttribute('stroke', 'none'); + this.svgPathLight.setAttribute('stroke', this.style.colourTertiary); + this.svgPathDark.setAttribute('fill', this.colourDark); + this.svgPath.setAttribute('fill', this.style.colourPrimary); + } +}; + +/** + * Set the style. + * @param {!Blockly.Theme.BlockStyle} blockStyle The block style to use. + * @package + */ +Blockly.geras.PathObject.prototype.setStyle = function(blockStyle) { + this.style = blockStyle; + this.colourDark = + Blockly.utils.colour.blend('#000', this.style.colourPrimary, 0.2); +}; diff --git a/core/theme.js b/core/theme.js index 58adea0e2..c086d3714 100644 --- a/core/theme.js +++ b/core/theme.js @@ -22,6 +22,7 @@ goog.provide('Blockly.Theme'); +goog.require('Blockly.utils.colour'); /** * Class for a theme. @@ -37,10 +38,13 @@ goog.provide('Blockly.Theme'); Blockly.Theme = function(blockStyles, categoryStyles, opt_componentStyles) { /** * The block styles map. - * @type {!Object.} + * @type {!Object.} * @private */ - this.blockStyles_ = blockStyles; + this.blockStyles_ = {}; + + // Make sure all styles are valid before inserting them into the map. + this.setAllBlockStyles(blockStyles); /** * The category styles map. @@ -97,22 +101,88 @@ Blockly.Theme.prototype.getAllBlockStyles = function() { /** * Gets the BlockStyle for the given block style name. - * @param {string} blockStyleName The name of the block style. - * @return {Blockly.Theme.BlockStyle|undefined} The named block style. + * @param {?string} blockStyleName The name of the block style. + * @return {!Blockly.Theme.BlockStyle} The named block style, or a default style + * if no style with the given name was found. */ Blockly.Theme.prototype.getBlockStyle = function(blockStyleName) { - return this.blockStyles_[blockStyleName]; + return this.blockStyles_[blockStyleName || ''] || + Blockly.Theme.createBlockStyle('#000000'); }; /** * Overrides or adds a style to the blockStyles map. * @param {string} blockStyleName The name of the block style. * @param {Blockly.Theme.BlockStyle} blockStyle The block style. -*/ + */ Blockly.Theme.prototype.setBlockStyle = function(blockStyleName, blockStyle) { + blockStyle = Blockly.Theme.validatedBlockStyle(blockStyle); this.blockStyles_[blockStyleName] = blockStyle; }; +/** + * Get or create a block style based on a single colour value. Generate a name + * for the style based on the colour. + * @param {string} colour #RRGGBB colour string. + * @return {{style: !Blockly.Theme.BlockStyle, name: string}} An object + * containing the style and an autogenerated name for that style. + * @package + */ +Blockly.Theme.prototype.getBlockStyleForColour = function(colour) { + var name = 'auto_' + colour; + if (!this.blockStyles_[name]) { + this.blockStyles_[name] = Blockly.Theme.createBlockStyle(colour); + } + return {style: this.blockStyles_[name], name: name}; +}; + +/** + * Create a block style object based on the given colour. + * @param {string} colour #RRGGBB colour string. + * @return {!Blockly.Theme.BlockStyle} A populated block style based on the + * given colour. + * @package + */ +Blockly.Theme.createBlockStyle = function(colour) { + return { + colourPrimary: colour, + colourSecondary: Blockly.utils.colour.blend('#fff', colour, 0.6) || colour, + colourTertiary: Blockly.utils.colour.blend('#fff', colour, 0.3) || colour, + hat: '' + }; +}; + +/** + * Get a full block style object based on the input style object. Populate + * any missing values. + * @param {Blockly.Theme.BlockStyle} blockStyle A full or partial block + * style object. + * @return {!Blockly.Theme.BlockStyle} A full block style object, with all + * required properties populated. + * @package + */ +Blockly.Theme.validatedBlockStyle = function(blockStyle) { + // Make a new object with all of the same properties. + var valid = {}; + if (blockStyle) { + Blockly.utils.object.mixin(valid, blockStyle); + } + + // Validate required properties. + var parsedColour = Blockly.utils.colour.parseBlockColour( + valid.colourPrimary || '#000'); + valid.colourPrimary = parsedColour.hex; + valid.colourSecondary = valid.colourSecondary ? + Blockly.utils.colour.parseBlockColour(valid.colourSecondary).hex : + Blockly.utils.colour.blend('#fff', valid.colourPrimary, 0.6); + valid.colourTertiary = valid.colourTertiary ? + Blockly.utils.colour.parseBlockColour(valid.colourTertiary).hex : + Blockly.utils.colour.blend('#fff', valid.colourPrimary, 0.3); + + valid.hat = valid.hat || ''; + return valid; +}; + /** * Gets the CategoryStyle for the given category style name. * @param {string} categoryStyleName The name of the category style. diff --git a/core/theme/deuteranopia.js b/core/theme/deuteranopia.js index a2c18ab84..05b4c9664 100644 --- a/core/theme/deuteranopia.js +++ b/core/theme/deuteranopia.js @@ -34,7 +34,7 @@ Blockly.Themes.Deuteranopia = {}; Blockly.Themes.Deuteranopia.defaultBlockStyles = { "colour_blocks": { "colourPrimary": "#f2a72c", - "colourSecondary": "#f1c17", + "colourSecondary": "#f1c170", "colourTertiary": "#da921c" }, "list_blocks": { diff --git a/core/utils/colour.js b/core/utils/colour.js index 928fc935b..810bd5637 100644 --- a/core/utils/colour.js +++ b/core/utils/colour.js @@ -211,3 +211,41 @@ Blockly.utils.colour.names = { 'white': '#ffffff', 'yellow': '#ffff00' }; + +/** + * Parse a block colour from a number or string, as provided in a block + * definition. + * @param {number|string} colour HSV hue value (0 to 360), #RRGGBB string, + * or a message reference string pointing to one of those two values. + * @return {{hue: ?number, hex: string}} An object containing the colour as + * a #RRGGBB string, and the hue if the input was an HSV hue value. + * @throws {Error} If the colour cannot be parsed. + */ +Blockly.utils.colour.parseBlockColour = function(colour) { + var dereferenced = (typeof colour == 'string') ? + Blockly.utils.replaceMessageReferences(colour) : colour; + + var hue = Number(dereferenced); + if (!isNaN(hue) && 0 <= hue && hue <= 360) { + return { + hue: hue, + hex: Blockly.utils.colour.hsvToHex(hue, Blockly.HSV_SATURATION, + Blockly.HSV_VALUE * 255) + }; + } else { + var hex = Blockly.utils.colour.parse(dereferenced); + if (hex) { + // Only store hue if colour is set as a hue. + return { + hue: null, + hex: hex + }; + } else { + var errorMsg = 'Invalid colour: "' + dereferenced + '"'; + if (colour != dereferenced) { + errorMsg += ' (from "' + colour + '")'; + } + throw Error(errorMsg); + } + } +}; diff --git a/core/warning.js b/core/warning.js index fe9f8c4f7..fde0bd50c 100644 --- a/core/warning.js +++ b/core/warning.js @@ -149,7 +149,7 @@ Blockly.Warning.prototype.createBubble = function() { textElement.setAttribute('x', maxWidth + Blockly.Bubble.BORDER_WIDTH); } } - this.updateColour(); + this.applyColour(); }; /** diff --git a/tests/blocks/test_blocks.js b/tests/blocks/test_blocks.js index 6f9e0af33..bf79c2f9d 100644 --- a/tests/blocks/test_blocks.js +++ b/tests/blocks/test_blocks.js @@ -29,7 +29,7 @@ Blockly.defineBlocksWithJsonArray([ // BEGIN JSON EXTRACT "message0": "stack block", "previousStatement": null, "nextStatement": null, - "style": "math_blocks" + "colour": "120" }, { "type": "test_basic_dummy", diff --git a/tests/jsunit/block_test.js b/tests/jsunit/block_test.js index f8fba5bed..a6588c353 100644 --- a/tests/jsunit/block_test.js +++ b/tests/jsunit/block_test.js @@ -253,28 +253,6 @@ function test_block_row_unplug_multi_inputs_child() { } } -function test_set_style() { - blockTest_setUp(); - var styleStub = { - getBlockStyle: function() { - return { - "colourPrimary": "#ffffff", - "colourSecondary": "#aabbcc", - "colourTertiary": "#ddeeff" - }; - } - }; - mockControl_ = setUpMockMethod(workspace, 'getTheme', null, [styleStub]); - var blockA = workspace.newBlock('row_block'); - blockA.setStyle('styleOne'); - - assertEquals('#ffffff', blockA.colour_); - assertEquals('#aabbcc', blockA.colourSecondary_); - assertEquals('#ddeeff', blockA.colourTertiary_); - - blockTest_tearDown(); -} - function test_set_style_throw_exception() { blockTest_setUp(); var styleStub = { diff --git a/tests/jsunit/theme_test.js b/tests/jsunit/theme_test.js deleted file mode 100644 index 22ba32c2e..000000000 --- a/tests/jsunit/theme_test.js +++ /dev/null @@ -1,153 +0,0 @@ -/** - * @license - * Copyright 2018 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - /** - * @fileoverview Tests for Blockly.Style - * @author aschmiedt@google.com (Abby Schmiedt) - */ -'use strict'; - -function defineThemeTestBlocks() { - Blockly.defineBlocksWithJsonArray([{ - "type": "stack_block", - "message0": "", - "previousStatement": null, - "nextStatement": null - }, - { - "type": "row_block", - "message0": "%1", - "args0": [ - { - "type": "input_value", - "name": "INPUT" - } - ], - "output": null - }]); -}; - -function undefineThemeTestBlocks() { - delete Blockly.Blocks['stack_block']; - delete Blockly.Blocks['row_block']; -} - - -function createBlockStyles() { - return { - "styleOne": { - "colourPrimary": "colour1", - "colourSecondary":"colour2", - "colourTertiary":"colour3" - } - }; -} - -function createMultipleBlockStyles() { - return { - "styleOne": { - "colourPrimary": "colour1", - "colourSecondary":"colour2", - "colourTertiary":"colour3" - }, - "styleTwo": { - "colourPrimary": "colour1", - "colourSecondary":"colour2", - "colourTertiary":"colour3" - } - }; -} - -function test_setAllBlockStyles() { - var theme = new Blockly.Theme(createBlockStyles()); - stringifyAndCompare(createBlockStyles(), theme.blockStyles_); - theme.setAllBlockStyles(createMultipleBlockStyles()); - stringifyAndCompare(createMultipleBlockStyles(), theme.blockStyles_); -} - -function test_getAllBlockStyles() { - var theme = new Blockly.Theme(createMultipleBlockStyles()); - var allBlocks = theme.getAllBlockStyles(); - stringifyAndCompare(createMultipleBlockStyles(), allBlocks); - -} - -function test_getBlockStyles() { - var theme = new Blockly.Theme(createBlockStyles()); - var blockStyle = theme.getBlockStyle('styleOne'); - - stringifyAndCompare(blockStyle, createBlockStyles().styleOne); -} - -function test_setBlockStyleUpdate() { - var theme = new Blockly.Theme(createBlockStyles()); - var blockStyle = createBlockStyles(); - blockStyle.styleOne.colourPrimary = 'somethingElse'; - - theme.setBlockStyle('styleOne', blockStyle.styleOne); - - stringifyAndCompare(theme.blockStyles_, blockStyle); -} - -function test_setBlockStyleAdd() { - var theme = new Blockly.Theme(createBlockStyles()); - var blockStyle = createMultipleBlockStyles(); - - theme.setBlockStyle('styleTwo', blockStyle.styleTwo); - - stringifyAndCompare(theme.blockStyles_, blockStyle); -} - -function test_setTheme() { - defineThemeTestBlocks(); - var blockStyles = createBlockStyles(); - var workspace = new Blockly.WorkspaceSvg({}); - var blockA = workspace.newBlock('stack_block'); - var blocks = [blockA]; - - blockA.setStyle = function() {this.styleName_ = 'styleTwo'}; - var callCount = 1; - workspace.refreshToolboxSelection = function() { - return ++callCount; - }; - blockA.styleName_ = 'styleOne'; - - var mockControl_ = setUpMockMethod(Blockly, 'getMainWorkspace', null, [workspace]); - - workspace.setTheme(blockStyles); - - //Checks that the theme was set correctly on Blockly namespace - stringifyAndCompare(workspace.getTheme(), blockStyles); - - //Checks that the setTheme function was called on the block - assertEquals(blockA.getStyleName(), 'styleTwo'); - - //check that the toolbox refreshed method was called - assertEquals(workspace.refreshToolboxSelection(), 3); - - assertEquals(Blockly.Events.FIRE_QUEUE_.pop().element, 'theme'); - - undefineThemeTestBlocks(); - - mockControl_.restore(); -} - -function stringifyAndCompare(val1, val2) { - var stringVal1 = JSON.stringify(val1); - var stringVal2 = JSON.stringify(val2); - assertEquals(stringVal1, stringVal2); -} diff --git a/tests/mocha/block_test.js b/tests/mocha/block_test.js index 27b496fcb..b01e96989 100644 --- a/tests/mocha/block_test.js +++ b/tests/mocha/block_test.js @@ -1249,4 +1249,66 @@ suite('Blocks', function() { }); }); }); + + suite('Style', function() { + suite('Headless', function() { + setup(function() { + this.block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom( + '' + ), this.workspace); + }); + test('Set colour', function() { + this.block.setColour('20'); + assertEquals(this.block.getColour(), '#a5745b'); + assertEquals(this.block.colour_, this.block.getColour()); + assertEquals(this.block.hue_, '20'); + }); + test('Set style', function() { + this.block.setStyle('styleOne'); + assertEquals(this.block.getStyleName(), 'styleOne'); + assertEquals(this.block.hue_, null); + // Calling setStyle does not update the colour on a headless block. + assertEquals(this.block.getColour(), '#000000'); + }); + }); + suite('Rendered', function() { + setup(function() { + this.workspace = Blockly.inject('blocklyDiv', {}); + this.block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom( + '' + ), this.workspace); + this.workspace.setTheme(new Blockly.Theme({ + "styleOne" : { + "colourPrimary": "#000000", + "colourSecondary": "#999999", + "colourTertiary": "#4d4d4d", + "hat": '' + } + }), {}); + }); + teardown(function() { + this.workspace.dispose(); + }); + test('Set colour hue', function() { + this.block.setColour('20'); + assertEquals(this.block.getStyleName(), 'auto_#a5745b'); + assertEquals(this.block.getColour(), '#a5745b'); + assertEquals(this.block.colour_, this.block.getColour()); + assertEquals(this.block.hue_, '20'); + }); + test('Set colour hex', function() { + this.block.setColour('#000000'); + assertEquals(this.block.getStyleName(), 'auto_#000000'); + assertEquals(this.block.getColour(), '#000000'); + assertEquals(this.block.colour_, this.block.getColour()); + assertEquals(this.block.hue_, null); + }); + test('Set style', function() { + this.block.setStyle('styleOne'); + assertEquals(this.block.getStyleName(), 'styleOne'); + assertEquals(this.block.getColour(), '#000000'); + assertEquals(this.block.colour_, this.block.getColour()); + }); + }); + }); }); diff --git a/tests/mocha/theme_test.js b/tests/mocha/theme_test.js index 4f17b730b..e6d5f7592 100644 --- a/tests/mocha/theme_test.js +++ b/tests/mocha/theme_test.js @@ -52,9 +52,10 @@ suite('Theme', function() { function createBlockStyles() { return { "styleOne": { - "colourPrimary": "colour1", - "colourSecondary":"colour2", - "colourTertiary":"colour3" + "colourPrimary": "#aaaaaa", + "colourSecondary": "#bbbbbb", + "colourTertiary": "#cccccc", + "hat": 'cap' } }; } @@ -62,14 +63,16 @@ suite('Theme', function() { function createMultipleBlockStyles() { return { "styleOne": { - "colourPrimary": "colour1", - "colourSecondary":"colour2", - "colourTertiary":"colour3" + "colourPrimary": "#aaaaaa", + "colourSecondary": "#bbbbbb", + "colourTertiary": "#cccccc", + "hat": 'cap' }, "styleTwo": { - "colourPrimary": "colour1", - "colourSecondary":"colour2", - "colourTertiary":"colour3" + "colourPrimary": "#000000", + "colourSecondary": "#999999", + "colourTertiary": "#4d4d4d", + "hat": '' } }; } @@ -103,7 +106,7 @@ suite('Theme', function() { test('Set BlockStyle Update', function() { var theme = new Blockly.Theme(createBlockStyles()); var blockStyle = createBlockStyles(); - blockStyle.styleOne.colourPrimary = 'somethingElse'; + blockStyle.styleOne.colourPrimary = '#00ff00'; theme.setBlockStyle('styleOne', blockStyle.styleOne); @@ -152,4 +155,120 @@ suite('Theme', function() { stub.restore(); }); + suite('Validate block styles', function() { + test('Null', function() { + var inputStyle = null; + var expectedOutput = { + "colourPrimary": "#000000", + "colourSecondary": "#999999", + "colourTertiary": "#4d4d4d", + "hat": '' + }; + stringifyAndCompare( + Blockly.Theme.validatedBlockStyle(inputStyle), expectedOutput); + }); + + test('Empty', function() { + var inputStyle = {}; + var expectedOutput = { + "colourPrimary": "#000000", + "colourSecondary": "#999999", + "colourTertiary": "#4d4d4d", + "hat": '' + }; + stringifyAndCompare( + Blockly.Theme.validatedBlockStyle(inputStyle), expectedOutput); + }); + + test('Incomplete hex', function() { + var inputStyle = { + "colourPrimary": "#012345" + }; + var expectedOutput = { + "colourPrimary": "#012345", + "colourSecondary": "#99a7b5", + "colourTertiary": "#4d657d", + "hat": '' + }; + stringifyAndCompare( + Blockly.Theme.validatedBlockStyle(inputStyle), expectedOutput); + }); + + test('Complete hex', function() { + var inputStyle = { + "colourPrimary": "#aaaaaa", + "colourSecondary": "#bbbbbb", + "colourTertiary": "#cccccc", + "hat": 'cap' + }; + var expectedOutput = { + "colourPrimary": "#aaaaaa", + "colourSecondary": "#bbbbbb", + "colourTertiary": "#cccccc", + "hat": 'cap' + }; + stringifyAndCompare( + Blockly.Theme.validatedBlockStyle(inputStyle), expectedOutput); + }); + + test('Complete hue', function() { + var inputStyle = { + "colourPrimary": "20", + "colourSecondary": "40", + "colourTertiary": "60", + }; + var expectedOutput = { + "colourPrimary": "#a5745b", + "colourSecondary": "#a58c5b", + "colourTertiary": "#a5a55b", + "hat": '' + }; + stringifyAndCompare( + Blockly.Theme.validatedBlockStyle(inputStyle), expectedOutput); + }); + + test('Incomplete hue', function() { + var inputStyle = { + "colourPrimary": "20", + }; + var expectedOutput = { + "colourPrimary": "#a5745b", + "colourSecondary": "#dbc7bd", + "colourTertiary": "#c09e8c", + "hat": '' + }; + stringifyAndCompare( + Blockly.Theme.validatedBlockStyle(inputStyle), expectedOutput); + }); + + test('Complete css colour name', function() { + var inputStyle = { + "colourPrimary": "red", + "colourSecondary": "white", + "colourTertiary": "blue" + }; + var expectedOutput = { + "colourPrimary": "#ff0000", + "colourSecondary": "#ffffff", + "colourTertiary": "#0000ff", + "hat": '' + }; + stringifyAndCompare( + Blockly.Theme.validatedBlockStyle(inputStyle), expectedOutput); + }); + + test('Incomplete css colour name', function() { + var inputStyle = { + "colourPrimary": "black", + }; + var expectedOutput = { + "colourPrimary": "#000000", + "colourSecondary": "#999999", + "colourTertiary": "#4d4d4d", + "hat": '' + }; + stringifyAndCompare( + Blockly.Theme.validatedBlockStyle(inputStyle), expectedOutput); + }); + }); });