Dynamic fonts (#3698)

* Piping themes into the renderer and ensuring fields update their size information when constants change.
This commit is contained in:
Sam El-Husseini
2020-02-20 17:16:01 -08:00
committed by GitHub
parent 8524da903b
commit eb40ca44ba
21 changed files with 495 additions and 224 deletions

View File

@@ -310,10 +310,6 @@ Blockly.Field.prototype.initModel = function() {
* @protected
*/
Blockly.Field.prototype.createBorderRect_ = function() {
this.size_.height =
Math.max(this.size_.height, this.constants_.FIELD_BORDER_RECT_HEIGHT);
this.size_.width =
Math.max(this.size_.width, this.constants_.FIELD_BORDER_RECT_X_PADDING * 2);
this.borderRect_ = /** @type {!SVGRectElement} **/
(Blockly.utils.dom.createSvgElement('rect',
{
@@ -334,24 +330,12 @@ Blockly.Field.prototype.createBorderRect_ = function() {
* @protected
*/
Blockly.Field.prototype.createTextElement_ = function() {
var xOffset = this.borderRect_ ?
this.constants_.FIELD_BORDER_RECT_X_PADDING : 0;
var baselineCenter = this.constants_.FIELD_TEXT_BASELINE_CENTER;
var baselineY = this.constants_.FIELD_TEXT_BASELINE_Y;
this.size_.height = Math.max(this.size_.height, baselineCenter ?
this.constants_.FIELD_TEXT_HEIGHT : baselineY);
if (this.size_.height > this.constants_.FIELD_TEXT_HEIGHT) {
baselineY += (this.size_.height - baselineY) / 2;
}
this.textElement_ = /** @type {!SVGTextElement} **/
(Blockly.utils.dom.createSvgElement('text',
{
'class': 'blocklyText',
'y': baselineCenter ? this.size_.height / 2 : baselineY,
'dy': this.constants_.FIELD_TEXT_Y_OFFSET,
'x': xOffset
}, this.fieldGroup_));
if (baselineCenter) {
if (this.constants_.FIELD_TEXT_BASELINE_CENTER) {
this.textElement_.setAttribute('dominant-baseline', 'central');
}
this.textContent_ = document.createTextNode('');
@@ -587,8 +571,8 @@ Blockly.Field.prototype.applyColour = function() {
Blockly.Field.prototype.render_ = function() {
if (this.textContent_) {
this.textContent_.nodeValue = this.getDisplayText_();
this.updateSize_();
}
this.updateSize_();
};
/**
@@ -619,22 +603,73 @@ Blockly.Field.prototype.updateWidth = function() {
/**
* Updates the size of the field based on the text.
* @param {number=} opt_margin margin to use when positioning the text element.
* @protected
*/
Blockly.Field.prototype.updateSize_ = function() {
var textWidth = Blockly.utils.dom.getFastTextWidth(
/** @type {!SVGTextElement} */ (this.textElement_),
this.constants_.FIELD_TEXT_FONTSIZE,
this.constants_.FIELD_TEXT_FONTWEIGHT,
this.constants_.FIELD_TEXT_FONTFAMILY);
var totalWidth = textWidth;
if (this.borderRect_) {
totalWidth += this.constants_.FIELD_BORDER_RECT_X_PADDING * 2;
this.borderRect_.setAttribute('width', totalWidth);
Blockly.Field.prototype.updateSize_ = function(opt_margin) {
var constants = this.constants_;
var xOffset = opt_margin != undefined ? opt_margin :
(this.borderRect_ ? this.constants_.FIELD_BORDER_RECT_X_PADDING : 0);
var totalWidth = xOffset * 2;
var totalHeight = constants.FIELD_TEXT_HEIGHT;
var contentWidth = 0;
if (this.textElement_) {
contentWidth = Blockly.utils.dom.getFastTextWidth(this.textElement_,
constants.FIELD_TEXT_FONTSIZE,
constants.FIELD_TEXT_FONTWEIGHT,
constants.FIELD_TEXT_FONTFAMILY);
totalWidth += contentWidth;
}
if (this.borderRect_) {
totalHeight = Math.max(totalHeight, constants.FIELD_BORDER_RECT_HEIGHT);
}
this.size_.height = totalHeight;
this.size_.width = totalWidth;
this.positionTextElement_(xOffset, contentWidth);
this.positionBorderRect_();
};
/**
* Position a field's text element after a size change. This handles both LTR
* and RTL positioning.
* @param {number} xOffset x offset to use when positioning the text element.
* @param {number} contentWidth The content width.
* @protected
*/
Blockly.Field.prototype.positionTextElement_ = function(xOffset, contentWidth) {
if (!this.textElement_) {
return;
}
var constants = this.constants_;
var halfHeight = this.size_.height / 2;
this.textElement_.setAttribute('x', this.sourceBlock_.RTL ?
this.size_.width - contentWidth - xOffset : xOffset);
this.textElement_.setAttribute('y', constants.FIELD_TEXT_BASELINE_CENTER ?
halfHeight : halfHeight - constants.FIELD_TEXT_HEIGHT / 2 +
constants.FIELD_TEXT_BASELINE);
};
/**
* Position a field's border rect after a size change.
* @protected
*/
Blockly.Field.prototype.positionBorderRect_ = function() {
if (!this.borderRect_) {
return;
}
this.borderRect_.setAttribute('width', this.size_.width);
this.borderRect_.setAttribute('height', this.size_.height);
this.borderRect_.setAttribute('rx',
this.constants_.FIELD_BORDER_RECT_RADIUS);
this.borderRect_.setAttribute('ry',
this.constants_.FIELD_BORDER_RECT_RADIUS);
};
/**
* Returns the height and width of the field.
*

View File

@@ -82,15 +82,6 @@ Blockly.FieldCheckbox.prototype.SERIALIZABLE = true;
*/
Blockly.FieldCheckbox.prototype.CURSOR = 'default';
/**
* Used to tell if the field needs to be rendered the next time the block is
* rendered. Checkbox fields are statically sized, and only need to be
* rendered at initialization.
* @type {boolean}
* @protected
*/
Blockly.FieldCheckbox.prototype.isDirty_ = false;
/**
* Configure the field based on the given map of options.
* @param {!Object} config A map of options to configure the field based on.
@@ -108,19 +99,30 @@ Blockly.FieldCheckbox.prototype.configure_ = function(config) {
* @package
*/
Blockly.FieldCheckbox.prototype.initView = function() {
this.size_.width = this.constants_.FIELD_CHECKBOX_DEFAULT_WIDTH;
Blockly.FieldCheckbox.superClass_.initView.call(this);
this.textElement_.setAttribute('x', this.constants_.FIELD_CHECKBOX_X_OFFSET);
this.textElement_.setAttribute('y', this.constants_.FIELD_CHECKBOX_Y_OFFSET);
this.textElement_.removeAttribute('dominant-baseline');
Blockly.utils.dom.addClass(this.textElement_, 'blocklyCheckbox');
this.textContent_.nodeValue =
this.checkChar_ || Blockly.FieldCheckbox.CHECK_CHAR;
Blockly.utils.dom.addClass(
/** @type {!SVGTextElement} **/ (this.textElement_), 'blocklyCheckbox');
this.textElement_.style.display = this.value_ ? 'block' : 'none';
};
/**
* @override
*/
Blockly.FieldCheckbox.prototype.render_ = function() {
if (this.textContent_) {
this.textContent_.nodeValue = this.getDisplayText_();
}
this.updateSize_(this.constants_.FIELD_CHECKBOX_X_OFFSET);
};
/**
* @override
*/
Blockly.FieldCheckbox.prototype.getDisplayText_ = function() {
return this.checkChar_ || Blockly.FieldCheckbox.CHECK_CHAR;
};
/**
* Set the character used for the check mark.
* @param {?string} character The character to use for the check mark, or

View File

@@ -564,10 +564,8 @@ Blockly.FieldDropdown.prototype.render_ = function() {
} else {
this.renderSelectedText_();
}
if (this.borderRect_) {
this.borderRect_.setAttribute('height', this.size_.height);
this.borderRect_.setAttribute('width', this.size_.width);
}
this.positionBorderRect_();
};
/**
@@ -588,14 +586,13 @@ Blockly.FieldDropdown.prototype.renderSelectedImage_ = function(imageJson) {
// Height and width include the border rect.
var hasBorder = !!this.borderRect_;
this.size_.height = Math.max(
var height = Math.max(
hasBorder ? this.constants_.FIELD_DROPDOWN_BORDER_RECT_HEIGHT : 0,
imageHeight + Blockly.FieldDropdown.IMAGE_Y_PADDING);
var halfHeight = this.size_.height / 2;
var xPadding = hasBorder ? this.constants_.FIELD_BORDER_RECT_X_PADDING : 0;
var arrowWidth = 0;
if (this.svgArrow_) {
arrowWidth = this.positionSVGArrow_(imageWidth + xPadding, halfHeight -
arrowWidth = this.positionSVGArrow_(imageWidth + xPadding, height / 2 -
this.constants_.FIELD_DROPDOWN_SVG_ARROW_SIZE / 2);
} else {
arrowWidth = Blockly.utils.dom.getFastTextWidth(
@@ -605,19 +602,20 @@ Blockly.FieldDropdown.prototype.renderSelectedImage_ = function(imageJson) {
this.constants_.FIELD_TEXT_FONTFAMILY);
}
this.size_.width = imageWidth + arrowWidth + xPadding * 2;
this.size_.height = height;
var arrowX = 0;
if (this.sourceBlock_.RTL) {
var imageX = xPadding + arrowWidth;
var arrowX = xPadding - 1;
this.imageElement_.setAttribute('x', imageX);
this.textElement_.setAttribute('x', arrowX);
} else {
var arrowX = imageWidth + arrowWidth + xPadding + 1;
arrowX = imageWidth + arrowWidth;
this.textElement_.setAttribute('text-anchor', 'end');
this.textElement_.setAttribute('x', arrowX);
this.imageElement_.setAttribute('x', xPadding);
}
this.imageElement_.setAttribute('y', halfHeight - imageHeight / 2);
this.imageElement_.setAttribute('y', height / 2 - imageHeight / 2);
this.positionTextElement_(arrowX + xPadding, imageWidth + arrowWidth);
};
/**
@@ -633,10 +631,9 @@ Blockly.FieldDropdown.prototype.renderSelectedText_ = function() {
// Height and width include the border rect.
var hasBorder = !!this.borderRect_;
this.size_.height = Math.max(
var height = Math.max(
hasBorder ? this.constants_.FIELD_DROPDOWN_BORDER_RECT_HEIGHT : 0,
this.constants_.FIELD_TEXT_HEIGHT);
var halfHeight = this.size_.height / 2;
var textWidth = Blockly.utils.dom.getFastTextWidth(this.textElement_,
this.constants_.FIELD_TEXT_FONTSIZE,
this.constants_.FIELD_TEXT_FONTWEIGHT,
@@ -644,20 +641,13 @@ Blockly.FieldDropdown.prototype.renderSelectedText_ = function() {
var xPadding = hasBorder ? this.constants_.FIELD_BORDER_RECT_X_PADDING : 0;
var arrowWidth = 0;
if (this.svgArrow_) {
arrowWidth = this.positionSVGArrow_(textWidth + xPadding, halfHeight -
arrowWidth = this.positionSVGArrow_(textWidth + xPadding, height / 2 -
this.constants_.FIELD_DROPDOWN_SVG_ARROW_SIZE / 2);
}
this.size_.width = textWidth + arrowWidth + xPadding * 2;
this.size_.height = height;
this.textElement_.setAttribute('x', this.sourceBlock_.RTL ?
this.size_.width - textWidth - xPadding : xPadding);
this.textElement_.setAttribute('y', halfHeight);
if (!this.constants_.FIELD_TEXT_BASELINE_CENTER) {
this.textElement_.setAttribute('dy',
this.constants_.FIELD_TEXT_BASELINE_Y -
this.constants_.FIELD_TEXT_HEIGHT / 2 +
this.constants_.FIELD_TEXT_Y_OFFSET);
}
this.positionTextElement_(xPadding, textWidth);
};
/**

View File

@@ -186,6 +186,13 @@ Blockly.FieldImage.prototype.initView = function() {
}
};
/**
* @override
*/
Blockly.FieldImage.prototype.updateSize_ = function() {
// NOP
};
/**
* Ensure that the input value (the source URL) is a string.
* @param {*=} opt_newValue The input value.

View File

@@ -60,13 +60,6 @@ Blockly.utils.object.inherits(Blockly.FieldMultilineInput,
Blockly.FieldTextInput);
/**
* The default height of a single line of text.
* @type {number}
* @const
*/
Blockly.FieldMultilineInput.LINE_HEIGHT = 20;
/**
* Construct a FieldMultilineInput from a JSON arg object,
* dereferencing any string table references.
@@ -143,14 +136,16 @@ Blockly.FieldMultilineInput.prototype.render_ = function() {
var lines = this.getDisplayText_().split('\n');
var y = 0;
for (var i = 0; i < lines.length; i++) {
var lineHeight = this.constants_.FIELD_TEXT_HEIGHT +
this.constants_.FIELD_BORDER_RECT_Y_PADDING;
var span = Blockly.utils.dom.createSvgElement('text', {
'class': 'blocklyText blocklyMultilineText',
x: this.constants_.FIELD_BORDER_RECT_X_PADDING,
y: y + this.constants_.FIELD_BORDER_RECT_Y_PADDING,
dy: Blockly.FieldMultilineInput.LINE_HEIGHT / 2
dy: this.constants_.FIELD_TEXT_BASELINE
}, this.textGroup_);
span.appendChild(document.createTextNode(lines[i]));
y += Blockly.FieldMultilineInput.LINE_HEIGHT;
y += lineHeight;
}
this.updateSize_();
@@ -191,34 +186,19 @@ Blockly.FieldMultilineInput.prototype.updateSize_ = function() {
if (textWidth > totalWidth) {
totalWidth = textWidth;
}
totalHeight += Blockly.FieldMultilineInput.LINE_HEIGHT;
totalHeight += this.constants_.FIELD_TEXT_HEIGHT +
(i > 0 ? this.constants_.FIELD_BORDER_RECT_Y_PADDING : 0);
}
if (this.borderRect_) {
totalHeight += this.constants_.FIELD_BORDER_RECT_Y_PADDING * 2;
totalWidth += this.constants_.FIELD_BORDER_RECT_X_PADDING * 2;
this.borderRect_.setAttribute('width', totalWidth);
this.borderRect_.setAttribute('height', totalHeight);
}
this.size_.width = totalWidth;
this.size_.height = totalHeight;
};
/**
* Resize the editor to fit the text.
* @protected
*/
Blockly.FieldMultilineInput.prototype.resizeEditor_ = function() {
var div = Blockly.WidgetDiv.DIV;
var bBox = this.getScaledBBox();
div.style.width = bBox.right - bBox.left + 'px';
div.style.height = bBox.bottom - bBox.top + 'px';
// In RTL mode block fields and LTR input fields the left edge moves,
// whereas the right edge is fixed. Reposition the editor.
var x = this.sourceBlock_.RTL ? bBox.right - div.offsetWidth : bBox.left;
var xy = new Blockly.utils.Coordinate(x, bBox.top);
div.style.left = xy.x + 'px';
div.style.top = xy.y + 'px';
this.positionBorderRect_();
};
/**
@@ -230,7 +210,8 @@ Blockly.FieldMultilineInput.prototype.widgetCreate_ = function() {
var div = Blockly.WidgetDiv.DIV;
var scale = this.workspace_.scale;
var htmlInput = /** @type {HTMLTextAreaElement} */ (document.createElement('textarea'));
var htmlInput =
/** @type {HTMLTextAreaElement} */ (document.createElement('textarea'));
htmlInput.className = 'blocklyHtmlInput blocklyHtmlTextAreaInput';
htmlInput.setAttribute('spellcheck', this.spellcheck_);
var fontSize = (this.constants_.FIELD_TEXT_FONTSIZE * scale) + 'pt';
@@ -238,11 +219,13 @@ Blockly.FieldMultilineInput.prototype.widgetCreate_ = function() {
htmlInput.style.fontSize = fontSize;
var borderRadius = (Blockly.FieldTextInput.BORDERRADIUS * scale) + 'px';
htmlInput.style.borderRadius = borderRadius;
var padding = this.constants_.FIELD_BORDER_RECT_X_PADDING * scale;
htmlInput.style.paddingLeft = padding + 'px';
htmlInput.style.width = 'calc(100% - ' + padding + 'px)';
htmlInput.style.lineHeight =
(Blockly.FieldMultilineInput.LINE_HEIGHT * scale) + 'px';
var paddingX = this.constants_.FIELD_BORDER_RECT_X_PADDING * scale;
var paddingY = this.constants_.FIELD_BORDER_RECT_Y_PADDING * scale / 2;
htmlInput.style.padding = paddingY + 'px ' + paddingX + 'px ' + paddingY +
'px ' + paddingX + 'px';
var lineHeight = this.constants_.FIELD_TEXT_HEIGHT +
this.constants_.FIELD_BORDER_RECT_Y_PADDING;
htmlInput.style.lineHeight = (lineHeight * scale) + 'px';
div.appendChild(htmlInput);

View File

@@ -46,6 +46,8 @@ Blockly.Flyout = function(workspaceOptions) {
*/
this.workspace_ = new Blockly.WorkspaceSvg(workspaceOptions);
this.workspace_.isFlyout = true;
// Keep the workspace visibility consistent with the flyout's visibility.
this.workspace_.setVisible(this.isVisible_);
/**
* Is RTL vs LTR.

View File

@@ -86,9 +86,14 @@ Blockly.FlyoutButton = function(workspace, targetWorkspace, xml, isLabel) {
};
/**
* The margin around the text in the button.
* The horizontal margin around the text in the button.
*/
Blockly.FlyoutButton.MARGIN = 5;
Blockly.FlyoutButton.MARGIN_X = 5;
/**
* The vertical margin around the text in the button.
*/
Blockly.FlyoutButton.MARGIN_Y = 2;
/**
* The width of the button's rect.
@@ -153,11 +158,18 @@ Blockly.FlyoutButton.prototype.createDom = function() {
'flyoutForegroundColour', 'fill');
}
this.width = Blockly.utils.dom.getTextWidth(svgText);
this.height = 20; // Can't compute it :(
var fontSize = Blockly.utils.style.getComputedStyle(svgText, 'fontSize');
var fontWeight = Blockly.utils.style.getComputedStyle(svgText, 'fontWeight');
var fontFamily = Blockly.utils.style.getComputedStyle(svgText, 'fontFamily');
this.width = Blockly.utils.dom.getFastTextWidthWithSizeString(svgText,
fontSize, fontWeight, fontFamily);
var fontMetrics = Blockly.utils.dom.measureFontMetrics(text, fontSize,
fontWeight, fontFamily);
this.height = fontMetrics.height;
if (!this.isLabel_) {
this.width += 2 * Blockly.FlyoutButton.MARGIN;
this.width += 2 * Blockly.FlyoutButton.MARGIN_X;
this.height += 2 * Blockly.FlyoutButton.MARGIN_Y;
shadow.setAttribute('width', this.width);
shadow.setAttribute('height', this.height);
}
@@ -165,7 +177,8 @@ Blockly.FlyoutButton.prototype.createDom = function() {
rect.setAttribute('height', this.height);
svgText.setAttribute('x', this.width / 2);
svgText.setAttribute('y', this.height - Blockly.FlyoutButton.MARGIN);
svgText.setAttribute('y', this.height / 2 - fontMetrics.height / 2 +
fontMetrics.baseline);
this.updateTransform_();

View File

@@ -79,16 +79,17 @@ Blockly.blockRendering.stopDebugger = function() {
/**
* Initialize anything needed for rendering (constants, etc).
* @param {!string} name Name of the renderer to initialize.
* @param {!Blockly.Theme} theme The workspace theme object.
* @return {!Blockly.blockRendering.Renderer} The new instance of a renderer.
* Already initialized.
* @package
*/
Blockly.blockRendering.init = function(name) {
Blockly.blockRendering.init = function(name, theme) {
if (!Blockly.blockRendering.rendererMap_[name]) {
throw Error('Renderer not registered: ', name);
}
var renderer = (/** @type {!Blockly.blockRendering.Renderer} */ (
new Blockly.blockRendering.rendererMap_[name](name)));
renderer.init();
renderer.init(theme);
return renderer;
};

View File

@@ -26,6 +26,20 @@ goog.require('Blockly.utils.userAgent');
*/
Blockly.blockRendering.ConstantProvider = function() {
/**
* A placeholder value for number constants that are dynamically set.
* @type {number}
* @protected
*/
this.DYNAMICALLY_SET_ = -1;
/**
* A placeholder value for string constants that are dynamically set.
* @type {string}
* @protected
*/
this.DYNAMICALLY_SET_STRING_ = '';
/**
* The size of an empty spacer.
* @type {number}
@@ -231,28 +245,39 @@ Blockly.blockRendering.ConstantProvider = function() {
this.JAGGED_TEETH_WIDTH = 6;
/**
* Point size of text.
* Point size of text. This constant is dynamically set in
* ``setFontConstants_`` to the size of the font used by the renderer/theme.
* @type {number}
*/
this.FIELD_TEXT_FONTSIZE = 11;
this.FIELD_TEXT_FONTSIZE = this.DYNAMICALLY_SET_;
/**
* Height of text.
* Height of text. This constant is dynamically set in ``setFontConstants_``
* to be the height of the text based on the font used.
* @type {number}
*/
this.FIELD_TEXT_HEIGHT = 16;
this.FIELD_TEXT_HEIGHT = this.DYNAMICALLY_SET_;
/**
* Text font weight.
* @type {string}
* Text baseline. This constant is dynamically set in ``setFontConstants_``
* to be the baseline of the text based on the font used.
* @type {number}
*/
this.FIELD_TEXT_FONTWEIGHT = 'normal';
this.FIELD_TEXT_BASELINE = this.DYNAMICALLY_SET_;
/**
* Text font family.
* Text font weight. This constant is dynamically set in
* ``setFontConstants_`` to the weight of the font used by the renderer/theme.
* @type {string}
*/
this.FIELD_TEXT_FONTFAMILY = 'sans-serif';
this.FIELD_TEXT_FONTWEIGHT = this.DYNAMICALLY_SET_STRING_;
/**
* Text font family. This constant is dynamically set in
* ``setFontConstants_`` to the family of the font used by the renderer/theme.
* @type {string}
*/
this.FIELD_TEXT_FONTFAMILY = this.DYNAMICALLY_SET_STRING_;
/**
* A field's border rect corner radius.
@@ -285,19 +310,6 @@ Blockly.blockRendering.ConstantProvider = function() {
*/
this.FIELD_BORDER_RECT_COLOUR = '#fff';
/**
* Field text baseline.
* This is only used if `FIELD_TEXT_BASELINE_CENTER` is false.
* @type {number}
*/
this.FIELD_TEXT_BASELINE_Y = Blockly.utils.userAgent.GECKO ? 12 : 13.09;
/**
* An text offset adjusting the Y position of text after positioning.
* @type {number}
*/
this.FIELD_TEXT_Y_OFFSET = 0;
/**
* A field's text element's dominant baseline.
* @type {boolean}
@@ -392,18 +404,6 @@ Blockly.blockRendering.ConstantProvider = function() {
*/
this.FIELD_CHECKBOX_X_OFFSET = this.FIELD_BORDER_RECT_X_PADDING - 3;
/**
* A checkbox field's Y offset.
* @type {number}
*/
this.FIELD_CHECKBOX_Y_OFFSET = 14;
/**
* A checkbox field's default width.
* @type {number}
*/
this.FIELD_CHECKBOX_DEFAULT_WIDTH = 15;
/**
* A random identifier used to ensure a unique ID is used for each
* filter/pattern for the case of multiple Blockly instances on a page.
@@ -440,6 +440,13 @@ Blockly.blockRendering.ConstantProvider = function() {
*/
this.disabledPattern_ = null;
/**
* The <style> element to use for injecting renderer specific CSS.
* @type {HTMLStyleElement}
* @private
*/
this.cssNode_ = null;
/**
* Cursor colour.
* @type {string}
@@ -555,7 +562,7 @@ Blockly.blockRendering.ConstantProvider.prototype.init = function() {
* @param {!Blockly.Theme} theme The current workspace theme.
* @package
*/
Blockly.blockRendering.ConstantProvider.prototype.refreshTheme = function(
Blockly.blockRendering.ConstantProvider.prototype.setTheme = function(
theme) {
/**
@@ -569,6 +576,62 @@ Blockly.blockRendering.ConstantProvider.prototype.refreshTheme = function(
for (var key in blockStyles) {
this.blockStyles[key] = this.validatedBlockStyle_(blockStyles[key]);
}
this.setDynamicProperties_(theme);
};
/**
* Sets dynamic properties that depent on other values or theme properties.
* @param {!Blockly.Theme} theme The current workspace theme.
* @protected
*/
Blockly.blockRendering.ConstantProvider.prototype.setDynamicProperties_ =
function(theme) {
/* eslint-disable indent */
this.setFontConstants_(theme);
}; /* eslint-enable indent */
/**
* Get an object representing the default font styles specified by the renderer.
* @return {!Blockly.Theme.FontStyle} A theme font style.
* @protected
*/
Blockly.blockRendering.ConstantProvider.prototype.getDefaultFontStyle_ =
function() {
/* eslint-disable indent */
return {
'weight': 'normal',
'size': 11,
'family': 'sans-serif'
};
}; /* eslint-enable indent */
/**
* Set constants related to fonts.
* @param {!Blockly.Theme} theme The current workspace theme.
* @protected
*/
Blockly.blockRendering.ConstantProvider.prototype.setFontConstants_ = function(
theme) {
var defaultFontStyle = this.getDefaultFontStyle_();
this.FIELD_TEXT_FONTFAMILY =
theme.fontStyle && theme.fontStyle['family'] != undefined ?
theme.fontStyle['family'] : defaultFontStyle['family'];
this.FIELD_TEXT_FONTWEIGHT =
theme.fontStyle && theme.fontStyle['weight'] != undefined ?
theme.fontStyle['weight'] : defaultFontStyle['weight'];
this.FIELD_TEXT_FONTSIZE =
theme.fontStyle && theme.fontStyle['size'] != undefined ?
theme.fontStyle['size'] : defaultFontStyle['size'];
var fontMetrics = Blockly.utils.dom.measureFontMetrics('Hg',
this.FIELD_TEXT_FONTSIZE + 'pt',
this.FIELD_TEXT_FONTWEIGHT,
this.FIELD_TEXT_FONTFAMILY);
this.FIELD_TEXT_HEIGHT = fontMetrics.height;
this.FIELD_TEXT_BASELINE = fontMetrics.baseline;
};
/**
@@ -598,7 +661,9 @@ Blockly.blockRendering.ConstantProvider.prototype.getBlockStyleForColour =
Blockly.blockRendering.ConstantProvider.prototype.getBlockStyle = function(
blockStyleName) {
return this.blockStyles[blockStyleName || ''] ||
this.createBlockStyle_('#000000');
(blockStyleName && blockStyleName.indexOf('auto_') == 0 ?
this.getBlockStyleForColour(blockStyleName.substring(5)).style :
this.createBlockStyle_('#000000'));
};
/**
@@ -689,6 +754,9 @@ Blockly.blockRendering.ConstantProvider.prototype.dispose = function() {
if (this.disabledPattern_) {
Blockly.utils.dom.removeNode(this.disabledPattern_);
}
if (this.cssNode_) {
Blockly.utils.dom.removeNode(this.cssNode_);
}
};
/**
@@ -915,9 +983,12 @@ Blockly.blockRendering.ConstantProvider.prototype.shapeFor = function(
/**
* Create any DOM elements that this renderer needs (filters, patterns, etc).
* @param {!SVGElement} svg The root of the workspace's SVG.
* @param {string} rendererName Name of the renderer.
* @package
*/
Blockly.blockRendering.ConstantProvider.prototype.createDom = function(svg) {
Blockly.blockRendering.ConstantProvider.prototype.createDom = function(svg,
rendererName) {
this.injectCSS_(rendererName);
/*
<defs>
... filters go here ...
@@ -999,9 +1070,9 @@ Blockly.blockRendering.ConstantProvider.prototype.createDom = function(svg) {
/**
* Inject renderer specific CSS into the page.
* @param {string} name Name of the renderer.
* @package
* @protected
*/
Blockly.blockRendering.ConstantProvider.prototype.injectCSS = function(
Blockly.blockRendering.ConstantProvider.prototype.injectCSS_ = function(
name) {
var cssArray = this.getCSS_(name);
var cssNodeId = 'blockly-renderer-style-' + name;
@@ -1011,11 +1082,13 @@ Blockly.blockRendering.ConstantProvider.prototype.injectCSS = function(
}
var text = cssArray.join('\n');
// Inject CSS tag at start of head.
var cssNode = document.createElement('style');
var cssNode =
/** @type {!HTMLStyleElement} */ (document.createElement('style'));
cssNode.id = cssNodeId;
var cssTextNode = document.createTextNode(text);
cssNode.appendChild(cssTextNode);
document.head.insertBefore(cssNode, document.head.firstChild);
this.cssNode_ = cssNode;
};
/**

View File

@@ -24,10 +24,12 @@ goog.require('Blockly.blockRendering.Types');
/**
* An object that renders rectangles and dots for debugging rendering code.
* @param {!Blockly.blockRendering.ConstantProvider} constants The renderer's
* constants.
* @package
* @constructor
*/
Blockly.blockRendering.Debug = function() {
Blockly.blockRendering.Debug = function(constants) {
/**
* An array of SVG elements that have been created by this object.
* @type {Array.<!SVGElement>}
@@ -42,6 +44,13 @@ Blockly.blockRendering.Debug = function() {
* @private
*/
this.svgRoot_ = null;
/**
* The renderer's constant provider.
* @type {!Blockly.blockRendering.ConstantProvider}
* @private
*/
this.constants_ = constants;
};
/**
@@ -163,6 +172,23 @@ Blockly.blockRendering.Debug.prototype.drawRenderedElem = function(elem, isRtl)
'stroke-width': '1px'
},
this.svgRoot_));
if (Blockly.blockRendering.Types.isField(elem) &&
elem.field instanceof Blockly.FieldLabel) {
var baseline = this.constants_.FIELD_TEXT_BASELINE;
this.debugElements_.push(Blockly.utils.dom.createSvgElement('rect',
{
'class': 'rowRenderingRect blockRenderDebug',
'x': xPos,
'y': yPos + baseline,
'width': elem.width,
'height': '0.1px',
'stroke': 'red',
'fill': 'none',
'stroke-width': '0.5px'
},
this.svgRoot_));
}
}

View File

@@ -48,13 +48,29 @@ Blockly.blockRendering.Renderer = function(name) {
/**
* Initialize the renderer.
* @param {!Blockly.Theme} theme The workspace theme object.
* @package
*/
Blockly.blockRendering.Renderer.prototype.init = function() {
Blockly.blockRendering.Renderer.prototype.init = function(theme) {
this.constants_ = this.makeConstants_();
this.constants_.setTheme(theme);
this.constants_.init();
};
/**
* Refresh the renderer after a theme change.
* @param {!SVGElement} svg The root of the workspace's SVG.
* @param {!Blockly.Theme} theme The workspace theme object.
* @package
*/
Blockly.blockRendering.Renderer.prototype.refresh = function(svg, theme) {
var constants = this.getConstants();
constants.dispose();
constants.setTheme(theme);
constants.init();
constants.createDom(svg, this.name);
};
/**
* Create a new instance of the renderer's constant provider.
* @return {!Blockly.blockRendering.ConstantProvider} The constant provider.
@@ -96,7 +112,7 @@ Blockly.blockRendering.Renderer.prototype.makeDebugger_ = function() {
if (!Blockly.blockRendering.Debug) {
throw Error('Missing require for Blockly.blockRendering.Debug');
}
return new Blockly.blockRendering.Debug();
return new Blockly.blockRendering.Debug(this.getConstants());
};
/**

View File

@@ -46,6 +46,14 @@ Blockly.geras.HighlightConstantProvider = function(constants) {
*/
this.START_POINT = Blockly.utils.svgPaths.moveBy(this.OFFSET, this.OFFSET);
};
/**
* Initialize shape objects based on the constants set in the constructor.
* @package
*/
Blockly.geras.HighlightConstantProvider.prototype.init = function() {
/**
* An object containing sizing and path information about inside corner
* highlights.

View File

@@ -48,9 +48,18 @@ Blockly.utils.object.inherits(Blockly.geras.Renderer,
* @package
* @override
*/
Blockly.geras.Renderer.prototype.init = function() {
Blockly.geras.Renderer.superClass_.init.call(this);
Blockly.geras.Renderer.prototype.init = function(theme) {
Blockly.geras.Renderer.superClass_.init.call(this, theme);
this.highlightConstants_ = this.makeHighlightConstants_();
this.highlightConstants_.init();
};
/**
* @override
*/
Blockly.geras.Renderer.prototype.refresh = function(svg, theme) {
Blockly.geras.Renderer.superClass_.refresh.call(this, svg, theme);
this.getHighlightConstants().init();
};
/**

View File

@@ -234,34 +234,6 @@ Blockly.zelos.ConstantProvider = function() {
*/
this.FULL_BLOCK_FIELDS = true;
/**
* @override
*/
this.FIELD_TEXT_FONTSIZE = 3 * this.GRID_UNIT;
/**
* @override
*/
this.FIELD_TEXT_FONTWEIGHT = 'bold';
/**
* @override
*/
this.FIELD_TEXT_FONTFAMILY =
'"Helvetica Neue", "Segoe UI", Helvetica, sans-serif';
/**
* @override
*/
this.FIELD_TEXT_HEIGHT = 13.1;
/**
* Used by positioning text on IE and Edge as they don't support
* dominant-baseline:center.
* @override
*/
this.FIELD_TEXT_BASELINE_Y = 13.1;
/**
* @override
*/
@@ -275,7 +247,7 @@ Blockly.zelos.ConstantProvider = function() {
/**
* @override
*/
this.FIELD_BORDER_RECT_Y_PADDING = 1 * this.GRID_UNIT;
this.FIELD_BORDER_RECT_Y_PADDING = 1.625 * this.GRID_UNIT;
/**
* @override
@@ -312,11 +284,6 @@ Blockly.zelos.ConstantProvider = function() {
*/
this.FIELD_TEXTINPUT_BOX_SHADOW = true;
/**
* @override
*/
this.FIELD_TEXT_Y_OFFSET = Blockly.utils.userAgent.CHROME ? -.45 : 0;
/**
* @override
*/
@@ -335,18 +302,7 @@ Blockly.zelos.ConstantProvider = function() {
/**
* @override
*/
this.FIELD_CHECKBOX_X_OFFSET = this.FIELD_BORDER_RECT_X_PADDING - 3;
/**
* @override
*/
this.FIELD_CHECKBOX_Y_OFFSET = 22;
/**
* @override
*/
this.FIELD_CHECKBOX_DEFAULT_WIDTH = 6 * this.GRID_UNIT;
this.FIELD_CHECKBOX_X_OFFSET = 1 * this.GRID_UNIT;
/**
* The maximum width of a dynamic connection shape.
@@ -387,6 +343,30 @@ Blockly.zelos.ConstantProvider = function() {
Blockly.utils.object.inherits(Blockly.zelos.ConstantProvider,
Blockly.blockRendering.ConstantProvider);
/**
* @override
*/
Blockly.zelos.ConstantProvider.prototype.getDefaultFontStyle_ = function() {
var fontStyle =
Blockly.zelos.ConstantProvider.superClass_.getDefaultFontStyle_.call(this);
fontStyle['weight'] = 'bold';
fontStyle['size'] = 3 * this.GRID_UNIT;
fontStyle['family'] = '"Helvetica Neue", "Segoe UI", Helvetica, sans-serif';
return fontStyle;
};
/**
* @override
*/
Blockly.zelos.ConstantProvider.prototype.setFontConstants_ = function(theme) {
Blockly.zelos.ConstantProvider.superClass_.setFontConstants_.call(this,
theme);
this.FIELD_BORDER_RECT_HEIGHT = this.FIELD_TEXT_HEIGHT +
this.FIELD_BORDER_RECT_Y_PADDING * 2;
this.FIELD_DROPDOWN_BORDER_RECT_HEIGHT = this.FIELD_BORDER_RECT_HEIGHT;
};
/**
* @override
*/
@@ -396,7 +376,8 @@ Blockly.zelos.ConstantProvider.prototype.init = function() {
this.ROUNDED = this.makeRounded();
this.SQUARED = this.makeSquared();
this.STATEMENT_INPUT_NOTCH_OFFSET += this.INSIDE_CORNERS.rightWidth;
this.STATEMENT_INPUT_NOTCH_OFFSET = this.NOTCH_OFFSET_LEFT +
this.INSIDE_CORNERS.rightWidth;
};
/**
@@ -759,8 +740,10 @@ Blockly.zelos.ConstantProvider.prototype.generateTertiaryColour_ = function(
/**
* @override
*/
Blockly.zelos.ConstantProvider.prototype.createDom = function(svg) {
Blockly.zelos.ConstantProvider.superClass_.createDom.call(this, svg);
Blockly.zelos.ConstantProvider.prototype.createDom = function(svg,
rendererName) {
Blockly.zelos.ConstantProvider.superClass_.createDom.call(this, svg,
rendererName);
/*
<defs>
... filters go here ...

View File

@@ -57,27 +57,43 @@ Blockly.Theme = function(name, blockStyles, categoryStyles,
* @private
*/
this.componentStyles_ = opt_componentStyles || Object.create(null);
/**
* The font style.
* @type {?Blockly.Theme.FontStyle}
*/
this.fontStyle = null;
};
/**
* A block style.
* @typedef {{
* colourPrimary:string,
* colourSecondary:string,
* colourTertiary:string,
* hat:string
* }}
*/
* colourPrimary:string,
* colourSecondary:string,
* colourTertiary:string,
* hat:string
* }}
*/
Blockly.Theme.BlockStyle;
/**
* A category style.
* @typedef {{
* colour:string
* }}
*/
* colour:string
* }}
*/
Blockly.Theme.CategoryStyle;
/**
* A font style.
* @typedef {{
* family:string?,
* weight:string?,
* size:number?
* }}
*/
Blockly.Theme.FontStyle;
/**
* Overrides or adds a style to the blockStyles map.
* @param {string} blockStyleName The name of the block style.
@@ -97,6 +113,14 @@ Blockly.Theme.prototype.setCategoryStyle = function(categoryStyleName,
this.categoryStyles[categoryStyleName] = categoryStyle;
};
/**
* Configure a theme's font style.
* @param {Blockly.Theme.FontStyle} fontStyle The font style.
*/
Blockly.Theme.prototype.setFontStyle = function(fontStyle) {
this.fontStyle = fontStyle;
};
/**
* Gets the style for a given Blockly UI component. If the style value is a
* string, we attempt to find the value of any named references.

View File

@@ -107,3 +107,9 @@ Blockly.Themes.HighContrast =
new Blockly.Theme('highcontrast',
Blockly.Themes.HighContrast.defaultBlockStyles,
Blockly.Themes.HighContrast.categoryStyles);
Blockly.Themes.HighContrast.setFontStyle({
'family': null, // Use default font-family
'weight': null, // Use default font-weight
'size': 16
});

View File

@@ -94,6 +94,10 @@ Blockly.ThemeManager.prototype.setTheme = function(theme) {
// Refresh all subscribed workspaces.
for (var i = 0, workspace; (workspace = this.subscribedWorkspaces_[i]); i++) {
workspace.refreshTheme();
// Re-render if workspace is visible
if (workspace.isVisible()) {
workspace.setVisible(true);
}
}
// Refresh all registered Blockly UI components.

View File

@@ -280,6 +280,24 @@ Blockly.utils.dom.getTextWidth = function(textElement) {
*/
Blockly.utils.dom.getFastTextWidth = function(textElement,
fontSize, fontWeight, fontFamily) {
return Blockly.utils.dom.getFastTextWidthWithSizeString(textElement,
fontSize + 'pt', fontWeight, fontFamily);
};
/**
* Gets the width of a text element using a faster method than `getTextWidth`.
* This method requires that we know the text element's font family and size in
* advance. Similar to `getTextWidth`, we cache the width we compute.
* This method is similar to ``getFastTextWidth`` but expects the font size
* parameter to be a string.
* @param {!Element} textElement An SVG 'text' element.
* @param {string} fontSize The font size to use.
* @param {string} fontWeight The font weight to use.
* @param {string} fontFamily The font family to use.
* @return {number} Width of element.
*/
Blockly.utils.dom.getFastTextWidthWithSizeString = function(textElement,
fontSize, fontWeight, fontFamily) {
var text = textElement.textContent;
var key = text + '\n' + textElement.className.baseVal;
var width;
@@ -305,7 +323,7 @@ Blockly.utils.dom.getFastTextWidth = function(textElement,
}
// Set the desired font size and family.
Blockly.utils.dom.canvasContext_.font =
fontWeight + ' ' + fontSize + 'pt ' + fontFamily;
fontWeight + ' ' + fontSize + ' ' + fontFamily;
// Measure the text width using the helper canvas context.
width = Blockly.utils.dom.canvasContext_.measureText(text).width;
@@ -316,3 +334,41 @@ Blockly.utils.dom.getFastTextWidth = function(textElement,
}
return width;
};
/**
* Measure a font's metrics. The height and baseline values.
* @param {string} text Text to measure the font dimensions of.
* @param {string} fontSize The font size to use.
* @param {string} fontWeight The font weight to use.
* @param {string} fontFamily The font family to use.
* @return {{height: number, baseline: number}} Font measurements.
*/
Blockly.utils.dom.measureFontMetrics = function(text, fontSize, fontWeight,
fontFamily) {
var span = document.createElement('span');
span.setAttribute('style', 'display: inline-block;');
span.style.font = fontWeight + ' ' + fontSize + ' ' + fontFamily;
span.textContent = text;
var block = document.createElement('div');
block.setAttribute('style',
'display: inline-block; width: 1px; height: 0px;');
var div = document.createElement('div');
div.setAttribute('style', 'line-height: 0;');
div.appendChild(span);
div.appendChild(block);
document.body.appendChild(div);
try {
var result = {};
block.style.verticalAlign = 'baseline';
result.baseline = block.offsetTop - span.offsetTop;
block.style.verticalAlign = 'bottom';
result.height = block.offsetTop - span.offsetTop;
} finally {
document.body.removeChild(div);
}
return result;
};

View File

@@ -128,13 +128,15 @@ Blockly.WorkspaceSvg = function(options,
this.options.parentWorkspace.getThemeManager() :
new Blockly.ThemeManager(this,
this.options.theme || Blockly.Themes.Classic);
this.themeManager_.subscribeWorkspace(this);
/**
* The block renderer used for rendering blocks on this workspace.
* @type {!Blockly.blockRendering.Renderer}
* @private
*/
this.renderer_ = Blockly.blockRendering.init(this.options.renderer || 'geras');
this.renderer_ = Blockly.blockRendering.init(this.options.renderer || 'geras',
this.getTheme());
/**
* Cached parent SVG.
@@ -143,9 +145,6 @@ Blockly.WorkspaceSvg = function(options,
*/
this.cachedParentSvg_ = null;
this.themeManager_.subscribeWorkspace(this);
this.renderer_.getConstants().refreshTheme(this.getTheme());
/**
* True if keyboard accessibility mode is on, false otherwise.
* @type {boolean}
@@ -516,7 +515,9 @@ Blockly.WorkspaceSvg.prototype.setTheme = function(theme) {
* @package
*/
Blockly.WorkspaceSvg.prototype.refreshTheme = function() {
this.getRenderer().getConstants().refreshTheme(this.getTheme());
if (this.svgGroup_) {
this.getRenderer().refresh(this.svgGroup_, this.getTheme());
}
// Update all blocks in workspace that have a style name.
this.updateBlockStyles_(this.getAllBlocks(false).filter(
@@ -743,8 +744,7 @@ Blockly.WorkspaceSvg.prototype.createDom = function(opt_backgroundClass) {
new Blockly.Marker());
var constants = this.getRenderer().getConstants();
constants.injectCSS(this.getRenderer().name);
constants.createDom(this.svgGroup_);
constants.createDom(this.svgGroup_, this.getRenderer().name);
return this.svgGroup_;
};
@@ -903,6 +903,7 @@ Blockly.WorkspaceSvg.prototype.addFlyout = function(tagName) {
this.flyout_ = new Blockly.VerticalFlyout(workspaceOptions);
}
this.flyout_.autoClose = false;
this.flyout_.getWorkspace().setVisible(true);
// Return the element so that callers can place it in their desired
// spot in the DOM. For example, mutator flyouts do not go in the same place
@@ -1149,6 +1150,10 @@ Blockly.WorkspaceSvg.prototype.getWidth = function() {
* @param {boolean} isVisible True if workspace should be visible.
*/
Blockly.WorkspaceSvg.prototype.setVisible = function(isVisible) {
this.isVisible_ = isVisible;
if (!this.svgGroup_) {
return;
}
// Tell the scrollbar whether its container is visible so it can
// tell when to hide itself.
@@ -1181,7 +1186,6 @@ Blockly.WorkspaceSvg.prototype.setVisible = function(isVisible) {
} else {
Blockly.hideChaff(true);
}
this.isVisible_ = isVisible;
};
/**

View File

@@ -164,11 +164,15 @@ suite('Checkbox Fields', function() {
suite('Check Character', function() {
function assertCharacter(field, char) {
field.fieldGroup_ = Blockly.utils.dom.createSvgElement('g', {}, null);
field.sourceBlock_ = {
RTL: false
};
field.constants_ = {
FIELD_CHECKBOX_X_OFFSET: 2,
FIELD_CHECKBOX_Y_OFFSET: 2
};
field.initView();
field.render_();
chai.assert(field.textContent_.nodeValue, char);
}
test('Constant', function() {

View File

@@ -347,6 +347,18 @@ function changeTheme() {
}
}
function changeRenderingConstant(value) {
var type = document.getElementById('rendering-constant-selector').value;
if (type == 'fontSize') {
var fontStyle = {
'size': value
};
workspace.getTheme().setFontStyle(fontStyle);
}
// Refresh theme.
workspace.setTheme(workspace.getTheme());
}
function setBackgroundColour() {
// Set background colour to differentiate server vs local copy.
if (location.protocol == 'file:') {
@@ -563,6 +575,10 @@ var spaghettiXml = [
.blockRenderDebug {
display: none;
}
.zelos-renderer .blocklyFlyoutButton .blocklyText {
font-size: 1.5rem;
}
</style>
</head>
<body onload="start()">
@@ -631,6 +647,15 @@ var spaghettiXml = [
<input type="button" value="Airstrike!" onclick="airstrike(100)">
<input type="button" value="Spaghetti!" onclick="spaghetti(8)">
</p>
<p>
Rendering: &nbsp;
<select id="rendering-constant-selector">
<option value="fontSize">Font Size</option>
</select>
<input type="range" min="3" max="50" value="4"
oninput="changeRenderingConstant(this.value)"
onchange="changeRenderingConstant(this.value)" />
</p>
<ul class="playgroundToggleOptions">
<li>
<label for="logCheck">Log events:</label>