Merge pull request #3357 from rachel-fenichel/colourer

Move block colouring code from block to the pathObject
This commit is contained in:
Rachel Fenichel
2019-10-30 17:29:46 -07:00
committed by GitHub
22 changed files with 519 additions and 395 deletions

View File

@@ -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']);

View File

@@ -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);
}
};
/**

View File

@@ -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);
}
};

View File

@@ -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();
};
/**

View File

@@ -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.
};

View File

@@ -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));

View File

@@ -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_();
};

View File

@@ -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;
}
}
};

View File

@@ -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);
}
};

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;
};

View File

@@ -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);
};

View File

@@ -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.<string, Blockly.Theme.BlockStyle>}
* @type {!Object.<string, !Blockly.Theme.BlockStyle>}
* @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.

View File

@@ -34,7 +34,7 @@ Blockly.Themes.Deuteranopia = {};
Blockly.Themes.Deuteranopia.defaultBlockStyles = {
"colour_blocks": {
"colourPrimary": "#f2a72c",
"colourSecondary": "#f1c17",
"colourSecondary": "#f1c170",
"colourTertiary": "#da921c"
},
"list_blocks": {

View File

@@ -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);
}
}
};

View File

@@ -149,7 +149,7 @@ Blockly.Warning.prototype.createBubble = function() {
textElement.setAttribute('x', maxWidth + Blockly.Bubble.BORDER_WIDTH);
}
}
this.updateColour();
this.applyColour();
};
/**

View File

@@ -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",

View File

@@ -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 = {

View File

@@ -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);
}

View File

@@ -1249,4 +1249,66 @@ suite('Blocks', function() {
});
});
});
suite('Style', function() {
suite('Headless', function() {
setup(function() {
this.block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
'<block type="empty_block"/>'
), 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(
'<block type="empty_block"/>'
), 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());
});
});
});
});

View File

@@ -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);
});
});
});