Implement number and string field specs (#230)

* Animate the font-size of text fields

* Allow WidgetDiv to hide immediately, no animation

And add calls to this where the div animation may become out of sync with the field itself.

* Fix CSS colour substitution to replace all

* Add box-shadow to text field

* Fix padding on blocklyHtmlInput

* Add utility for measuring text using canvas

* Grow the input textbox based on the text size

* Fix the text field width to a maximum depending on render mode

* Fix text input field height for scaling

* Position text input field based width

* Animate out width, height, margin of text input

* Fix scale of marginLeft and extra 1px

* Fix height, padding, font-weight on text input field

* Make input field text smaller on truncation per spec

* Re-apply change to Blockly.WidgetDiv.disposeAnimationTimer_

* Add comment to Blockly.Field.maxDisplayLength
This commit is contained in:
Tim Mickel 2016-04-15 16:44:30 -04:00
parent 1b560d7271
commit 33a71b112f
8 changed files with 152 additions and 19 deletions

View file

@ -58,12 +58,31 @@ Blockly.BlockSvg.STATEMENT_BLOCK_SPACE = 3 * Blockly.BlockSvg.GRID_UNIT;
* @const
*/
Blockly.BlockSvg.FIELD_HEIGHT = 8 * Blockly.BlockSvg.GRID_UNIT;
/**
* Width of user inputs
* @const
*/
Blockly.BlockSvg.FIELD_WIDTH = 12 * Blockly.BlockSvg.GRID_UNIT;
/**
* Minimum width of user inputs during editing
* @const
*/
Blockly.BlockSvg.FIELD_WIDTH_MIN_EDIT = 13 * Blockly.BlockSvg.GRID_UNIT;
/**
* Maximum width of user inputs during editing
* @const
*/
Blockly.BlockSvg.FIELD_WIDTH_MAX_EDIT = 24 * Blockly.BlockSvg.GRID_UNIT;
/**
* Maximum height of user inputs during editing
* @const
*/
Blockly.BlockSvg.FIELD_HEIGHT_MAX_EDIT = 10 * Blockly.BlockSvg.GRID_UNIT;
/**
* Top padding of user inputs
* @const

View file

@ -144,12 +144,31 @@ Blockly.BlockSvg.INNER_BOTTOM_LEFT_CORNER =
* @const
*/
Blockly.BlockSvg.FIELD_HEIGHT = 8 * Blockly.BlockSvg.GRID_UNIT;
/**
* Width of user inputs
* @const
*/
Blockly.BlockSvg.FIELD_WIDTH = 12 * Blockly.BlockSvg.GRID_UNIT;
/**
* Minimum width of user inputs during editing
* @const
*/
Blockly.BlockSvg.FIELD_WIDTH_MIN_EDIT = 13 * Blockly.BlockSvg.GRID_UNIT;
/**
* Maximum width of user inputs during editing
* @const
*/
Blockly.BlockSvg.FIELD_WIDTH_MAX_EDIT = Infinity;
/**
* Maximum height of user inputs during editing
* @const
*/
Blockly.BlockSvg.FIELD_HEIGHT_MAX_EDIT = Blockly.BlockSvg.FIELD_WIDTH;
/**
* Play some UI effects (sound, ripple) after a connection has been established.
*/

View file

@ -39,5 +39,7 @@ Blockly.Colours = {
"scrollbarHover": '#BBBBBB',
"textField": "#FFFFFF",
"insertionMarker": "#949494",
"insertionMarkerOpacity": 0.6
"insertionMarkerOpacity": 0.6,
// CSS colours: support RGBA
"fieldShadow": "rgba(0,0,0,0.1)"
};

View file

@ -91,7 +91,11 @@ Blockly.Css.inject = function(hasCss, pathToMedia) {
// been set at run-time injection.
for (var colourProperty in Blockly.Colours) {
if (Blockly.Colours.hasOwnProperty(colourProperty)) {
text = text.replace('$colour_' + colourProperty, Blockly.Colours[colourProperty]);
// Replace all
text = text.replace(
new RegExp('\\$colour\\_' + colourProperty, 'g'),
Blockly.Colours[colourProperty]
);
}
}
// Inject CSS tag.
@ -253,6 +257,11 @@ Blockly.Css.CONTENT = [
'cursor: default;',
'fill: #fff;',
'font-family: sans-serif;',
'font-size: 12pt;',
'font-weight: 600;',
'}',
'.blocklyTextTruncated {',
'font-size: 11pt;',
'}',
@ -334,13 +343,16 @@ Blockly.Css.CONTENT = [
'.blocklyHtmlInput {',
'border: none;',
'font-family: sans-serif;',
'font-size: 12pt;',
'height: 100%;',
'margin: 0;',
'outline: none;',
'padding: 2px 0;',
'box-sizing: border-box;',
'padding: 2px 8px 0 8px;',
'width: 100%;',
'text-align: center;',
'color: $colour_text;',
'font-weight: 600;',
'}',
'.blocklyMainBackground {',

View file

@ -76,9 +76,10 @@ Blockly.Field.prototype.name = undefined;
/**
* Maximum characters of text to display before adding an ellipsis.
* Same for strings and numbers.
* @type {number}
*/
Blockly.Field.prototype.maxDisplayLength = 5;
Blockly.Field.prototype.maxDisplayLength = 4;
/**
* Visible text to display.
@ -354,6 +355,10 @@ Blockly.Field.prototype.updateTextNode_ = function() {
if (text.length > this.maxDisplayLength) {
// Truncate displayed string and add an ellipsis ('...').
text = text.substring(0, this.maxDisplayLength - 2) + '\u2026';
// Add special class for sizing font when truncated
this.textElement_.setAttribute('class', 'blocklyText blocklyTextTruncated');
} else {
this.textElement_.setAttribute('class', 'blocklyText');
}
// Empty the text element.
goog.dom.removeChildren(/** @type {!Element} */ (this.textElement_));

View file

@ -27,8 +27,10 @@
goog.provide('Blockly.FieldTextInput');
goog.require('Blockly.BlockSvg.render');
goog.require('Blockly.Colours');
goog.require('Blockly.Field');
goog.require('Blockly.Msg');
goog.require('Blockly.utils');
goog.require('goog.asserts');
goog.require('goog.dom');
goog.require('goog.userAgent');
@ -51,9 +53,24 @@ Blockly.FieldTextInput = function(text, opt_validator) {
goog.inherits(Blockly.FieldTextInput, Blockly.Field);
/**
* Point size of text. Should match blocklyText's font-size in CSS.
* Point size of text before animation. Must match size in CSS.
*/
Blockly.FieldTextInput.FONTSIZE = 11;
Blockly.FieldTextInput.FONTSIZE_INITIAL = 12;
/**
* Point size of text after animation.
*/
Blockly.FieldTextInput.FONTSIZE_FINAL = 14;
/**
* Length of animations in seconds.
*/
Blockly.FieldTextInput.ANIMATION_TIME = 0.25;
/**
* Padding to use for text measurement for the field during editing, in px.
*/
Blockly.FieldTextInput.TEXT_MEASURE_PADDING_MAGIC = 35;
/**
* Mouse cursor style when over the hotspot that initiates the editor.
@ -125,17 +142,15 @@ Blockly.FieldTextInput.prototype.showEditor_ = function(opt_quietInput) {
return;
}
Blockly.WidgetDiv.show(this, this.sourceBlock_.RTL, this.widgetDispose_());
Blockly.WidgetDiv.show(this, this.sourceBlock_.RTL,
this.widgetDispose_(), this.widgetDisposeAnimationFinished_(),
Blockly.FieldTextInput.ANIMATION_TIME);
var div = Blockly.WidgetDiv.DIV;
// Apply text-input-specific fixed CSS
div.className += ' fieldTextInput';
// Create the input.
var htmlInput = goog.dom.createDom('input', 'blocklyHtmlInput');
htmlInput.setAttribute('spellcheck', this.spellcheck_);
var fontSize =
(Blockly.FieldTextInput.FONTSIZE) + 'pt';
div.style.fontSize = fontSize;
htmlInput.style.fontSize = fontSize;
/** @type {!HTMLInputElement} */
Blockly.FieldTextInput.htmlInput_ = htmlInput;
div.appendChild(htmlInput);
@ -160,6 +175,17 @@ Blockly.FieldTextInput.prototype.showEditor_ = function(opt_quietInput) {
Blockly.bindEvent_(htmlInput, 'keypress', this, this.onHtmlInputChange_);
htmlInput.onWorkspaceChangeWrapper_ = this.resizeEditor_.bind(this);
this.workspace_.addChangeListener(htmlInput.onWorkspaceChangeWrapper_);
// Add animation transition properties
div.style.transition = 'padding ' + Blockly.FieldTextInput.ANIMATION_TIME + 's,' +
'width ' + Blockly.FieldTextInput.ANIMATION_TIME + 's,' +
'height ' + Blockly.FieldTextInput.ANIMATION_TIME + 's,' +
'margin-left ' + Blockly.FieldTextInput.ANIMATION_TIME + 's,' +
'box-shadow ' + Blockly.FieldTextInput.ANIMATION_TIME + 's';
htmlInput.style.transition = 'font-size ' + Blockly.FieldTextInput.ANIMATION_TIME + 's';
// The animated properties themselves
htmlInput.style.fontSize = Blockly.FieldTextInput.FONTSIZE_FINAL + 'pt';
div.style.boxShadow = '0px 0px 0px 4px ' + Blockly.Colours.fieldShadow;
};
/**
@ -229,15 +255,30 @@ Blockly.FieldTextInput.prototype.validate_ = function() {
Blockly.FieldTextInput.prototype.resizeEditor_ = function() {
var scale = this.sourceBlock_.workspace.scale;
var div = Blockly.WidgetDiv.DIV;
var bBox = this.getScaledBBox_();
// The width of this box must be at least FIELD_WIDTH * scale.
// It may be smaller as bBox is based on the content size.
var width = Math.max(bBox.width, Blockly.BlockSvg.FIELD_WIDTH * scale);
// Resize the box based on the measured width of the text, pre-truncation
var textWidth = Blockly.measureText(
Blockly.FieldTextInput.htmlInput_.style.fontSize,
Blockly.FieldTextInput.htmlInput_.style.fontFamily,
Blockly.FieldTextInput.htmlInput_.value
);
// Size drawn in the canvas needs padding and scaling
textWidth += Blockly.FieldTextInput.TEXT_MEASURE_PADDING_MAGIC;
textWidth *= scale;
// The width must be at least FIELD_WIDTH and at most FIELD_WIDTH_MAX_EDIT
var width = Math.max(textWidth, Blockly.BlockSvg.FIELD_WIDTH_MIN_EDIT * scale);
width = Math.min(width, Blockly.BlockSvg.FIELD_WIDTH_MAX_EDIT * scale);
// Add 1px to width and height to account for border (pre-scale)
div.style.width = (width / scale + 1) + 'px';
div.style.height = (bBox.height / scale + 1) + 'px';
div.style.height = (Blockly.BlockSvg.FIELD_HEIGHT_MAX_EDIT + 1) + 'px';
div.style.transform = 'scale(' + scale + ')';
// Use margin-left to animate repositioning of the box (value is unscaled).
// This is the difference between the default position and the positioning
// after growing the box.
var initialWidth = Blockly.BlockSvg.FIELD_WIDTH * scale;
var finalWidth = width;
div.style.marginLeft = -0.5 * (finalWidth - initialWidth) + 'px';
// Add 0.5px to account for slight difference between SVG and CSS border
var borderRadius = this.getBorderRadius() + 0.5;
div.style.borderRadius = borderRadius + 'px';
@ -285,14 +326,14 @@ Blockly.Field.prototype.getBorderRadius = function() {
};
/**
* Close the editor, save the results, and dispose of the editable
* text field's elements.
* Close the editor, save the results, and start animating the disposal of elements.
* @return {!Function} Closure to call on destruction of the WidgetDiv.
* @private
*/
Blockly.FieldTextInput.prototype.widgetDispose_ = function() {
var thisField = this;
return function() {
var div = Blockly.WidgetDiv.DIV;
var htmlInput = Blockly.FieldTextInput.htmlInput_;
// Save the edit (if it validates).
var text = htmlInput.value;
@ -313,7 +354,23 @@ Blockly.FieldTextInput.prototype.widgetDispose_ = function() {
Blockly.unbindEvent_(htmlInput.onKeyPressWrapper_);
thisField.workspace_.removeChangeListener(
htmlInput.onWorkspaceChangeWrapper_);
Blockly.FieldTextInput.htmlInput_ = null;
// Animation of disposal
htmlInput.style.fontSize = Blockly.FieldTextInput.FONTSIZE_INITIAL + 'pt';
div.style.boxShadow = '';
div.style.width = Blockly.BlockSvg.FIELD_WIDTH + 'px';
div.style.height = Blockly.BlockSvg.FIELD_HEIGHT + 'px';
div.style.marginLeft = 0;
};
};
/**
* Final disposal of the text field's elements and properties.
* @return {!Function} Closure to call on finish animation of the WidgetDiv.
* @private
*/
Blockly.FieldTextInput.prototype.widgetDisposeAnimationFinished_ = function() {
return function() {
// Delete style properties.
var style = Blockly.WidgetDiv.DIV.style;
style.width = 'auto';
@ -321,6 +378,10 @@ Blockly.FieldTextInput.prototype.widgetDispose_ = function() {
style.fontSize = '';
// Reset class
Blockly.WidgetDiv.DIV.className = 'blocklyWidgetDiv';
// Reset transitions
Blockly.WidgetDiv.DIV.style.transition = '';
Blockly.FieldTextInput.htmlInput_.style.transition = '';
Blockly.FieldTextInput.htmlInput_ = null;
};
};

View file

@ -636,3 +636,17 @@ Blockly.genUid = function() {
*/
Blockly.genUid.soup_ = '!#%()*+,-./:;=?@[]^_`{|}~' +
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
/**
* Measure some text using a canvas in-memory.
* @param {string} fontSize E.g., '10pt'
* @param {string} fontFamily E.g., 'Arial'
* @param {string} text The actual text to measure
* @return {number} Width of the text in px.
*/
Blockly.measureText = function(fontSize, fontFamily, text) {
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');
context.font = fontSize + fontFamily;
return context.measureText(text).width;
};

View file

@ -57,6 +57,7 @@ function start() {
scrollbarHover: '#0C111A',
insertionMarker: '#FFFFFF',
insertionMarkerOpacity: 0.3,
fieldShadow: 'rgba(255, 255, 255, 0.3)'
}
});
// Restore previously displayed text.