mirror of
https://github.com/scratchfoundation/scratch-blocks.git
synced 2025-08-28 22:10:31 -04:00
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:
parent
1b560d7271
commit
33a71b112f
8 changed files with 152 additions and 19 deletions
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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)"
|
||||
};
|
||||
|
|
16
core/css.js
16
core/css.js
|
@ -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 {',
|
||||
|
|
|
@ -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_));
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -57,6 +57,7 @@ function start() {
|
|||
scrollbarHover: '#0C111A',
|
||||
insertionMarker: '#FFFFFF',
|
||||
insertionMarkerOpacity: 0.3,
|
||||
fieldShadow: 'rgba(255, 255, 255, 0.3)'
|
||||
}
|
||||
});
|
||||
// Restore previously displayed text.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue