From 9fcc532977dc783b1203f4ef033e485c04c75542 Mon Sep 17 00:00:00 2001 From: Sam El-Husseini Date: Mon, 25 Nov 2019 15:54:24 -0800 Subject: [PATCH] [zelos] Cross browser pixel perfect text input (#3466) * Align text and widget div text input pixel perfect on all browsers --- core/css.js | 1 + core/field.js | 25 +++++++------ core/field_textinput.js | 60 ++++++++++++++++++++++-------- core/renderers/common/constants.js | 19 ++++++++-- core/renderers/zelos/constants.js | 38 +++++++++++++++++++ core/utils/useragent.js | 2 + 6 files changed, 114 insertions(+), 31 deletions(-) diff --git a/core/css.js b/core/css.js index e664d766c..f560b8717 100644 --- a/core/css.js +++ b/core/css.js @@ -393,6 +393,7 @@ Blockly.Css.CONTENT = [ 'width: 100%;', 'text-align: center;', 'display: block;', + 'box-sizing: border-box;', '}', /* Edge and IE introduce a close icon when the input value is longer than a diff --git a/core/field.js b/core/field.js index 903911458..e14e0255b 100644 --- a/core/field.js +++ b/core/field.js @@ -344,22 +344,23 @@ Blockly.Field.prototype.createBorderRect_ = function() { Blockly.Field.prototype.createTextElement_ = function() { var xOffset = this.borderRect_ ? this.constants_.FIELD_BORDER_RECT_X_PADDING : 0; - this.size_.height = Math.max(this.size_.height, - this.constants_.FIELD_TEXT_BASELINE_CENTER ? - this.constants_.FIELD_TEXT_HEIGHT : - this.constants_.FIELD_TEXT_BASELINE_Y); + 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': this.size_.height / 2, + 'y': baselineCenter ? this.size_.height / 2 : baselineY, + 'dy': this.constants_.FIELD_TEXT_Y_OFFSET, 'x': xOffset }, this.fieldGroup_)); - if (this.constants_.FIELD_TEXT_BASELINE_CENTER) { + if (baselineCenter) { this.textElement_.setAttribute('dominant-baseline', 'central'); - } else { - this.textElement_.setAttribute('dy', - this.constants_.FIELD_TEXT_BASELINE_Y - this.size_.height / 2); } this.textContent_ = document.createTextNode(''); this.textElement_.appendChild(this.textContent_); @@ -421,7 +422,7 @@ Blockly.Field.prototype.dispose = function() { * Add or remove the UI indicating if this field is editable or not. */ Blockly.Field.prototype.updateEditable = function() { - var group = this.getClickTarget_(); + var group = this.fieldGroup_; if (!this.EDITABLE || !group) { return; } @@ -673,9 +674,9 @@ Blockly.Field.prototype.getSize = function() { */ Blockly.Field.prototype.getScaledBBox = function() { var bBox = this.borderRect_.getBBox(); - var scaledHeight = bBox.height * this.sourceBlock_.workspace.scale; - var scaledWidth = bBox.width * this.sourceBlock_.workspace.scale; var xy = this.getAbsoluteXY_(); + var scaledWidth = bBox.width * this.sourceBlock_.workspace.scale; + var scaledHeight = bBox.height * this.sourceBlock_.workspace.scale; return { top: xy.y, bottom: xy.y + scaledHeight, diff --git a/core/field_textinput.js b/core/field_textinput.js index 8150f26de..19127c14f 100644 --- a/core/field_textinput.js +++ b/core/field_textinput.js @@ -146,10 +146,6 @@ Blockly.FieldTextInput.prototype.configure_ = function(config) { * @override */ Blockly.FieldTextInput.prototype.initView = function() { - this.size_.height = Math.max(this.constants_.FIELD_BORDER_RECT_HEIGHT, - this.constants_.FIELD_TEXT_BASELINE_CENTER ? - this.constants_.FIELD_TEXT_HEIGHT : - this.constants_.FIELD_TEXT_BASELINE_Y); if (this.constants_.FULL_BLOCK_FIELDS) { // Step one: figure out if this is the only field on this block. // Rendering is quite different in that case. @@ -181,9 +177,6 @@ Blockly.FieldTextInput.prototype.initView = function() { this.createBorderRect_(); } this.createTextElement_(); - if (this.constants_.FIELD_TEXT_BASELINE_CENTER) { - this.textElement_.setAttribute('dominant-baseline', 'central'); - } }; /** @@ -354,23 +347,33 @@ Blockly.FieldTextInput.prototype.widgetCreate_ = function() { var htmlInput = /** @type {HTMLInputElement} */ (document.createElement('input')); htmlInput.className = 'blocklyHtmlInput'; htmlInput.setAttribute('spellcheck', this.spellcheck_); + var scale = this.workspace_.scale; var fontSize = - (this.constants_.FIELD_TEXT_FONTSIZE * this.workspace_.scale) + 'pt'; + (this.constants_.FIELD_TEXT_FONTSIZE * scale) + 'pt'; div.style.fontSize = fontSize; htmlInput.style.fontSize = fontSize; var borderRadius = - (Blockly.FieldTextInput.BORDERRADIUS * this.workspace_.scale) + 'px'; + (Blockly.FieldTextInput.BORDERRADIUS * scale) + 'px'; if (this.fullBlockClickTarget_) { var bBox = this.getScaledBBox(); + // Override border radius. borderRadius = (bBox.bottom - bBox.top) / 2 + 'px'; // Pull stroke colour from the existing shadow block - var strokeColour = this.sourceBlock_.style.colourTertiary; - div.style.borderColor = strokeColour; + var strokeColour = this.sourceBlock_.getParent() ? + this.sourceBlock_.getParent().style.colourTertiary : + this.sourceBlock_.style.colourTertiary; + htmlInput.style.border = (1 * scale) + 'px solid ' + strokeColour; + div.style.borderRadius = borderRadius; + div.style.transition = 'box-shadow 0.25s ease 0s'; + if (this.constants_.FIELD_TEXTINPUT_BOX_SHADOW) { + div.style.boxShadow = 'rgba(255, 255, 255, 0.3) 0px 0px 0px ' + + 4 * scale + 'px'; + } } - htmlInput.style.borderRadius = borderRadius; + div.appendChild(htmlInput); htmlInput.value = htmlInput.defaultValue = this.getEditorText_(this.value_); @@ -406,6 +409,8 @@ Blockly.FieldTextInput.prototype.widgetDispose_ = function() { style.width = 'auto'; style.height = 'auto'; style.fontSize = ''; + style.transition = ''; + style.boxShadow = ''; this.htmlInput_ = null; Blockly.utils.dom.removeClass(this.getClickTarget_(), 'editing'); @@ -634,16 +639,39 @@ Blockly.FieldTextInput.prototype.getValueFromEditorText_ = function(text) { * @override */ Blockly.FieldTextInput.prototype.getScaledBBox = function() { - if (this.fullBlockClickTarget_) { - var xy = this.getClickTarget_().getBoundingClientRect(); + if (!this.borderRect_) { + // Browsers are inconsistent in what they return for a bounding box. + // - Webkit / Blink: fill-box / object bounding box + // - Gecko / Triden / EdgeHTML: stroke-box + var bBox = this.sourceBlock_.getHeightWidth(); + var scale = this.sourceBlock_.workspace.scale; + var xy = this.getAbsoluteXY_(); + var scaledWidth = bBox.width * scale; + var scaledHeight = bBox.height * scale; + + if (Blockly.utils.userAgent.GECKO) { + xy.x += 1.5 * scale; + xy.y += 1.5 * scale; + scaledWidth += 1 * scale; + scaledHeight += 1 * scale; + } else { + if (!Blockly.utils.userAgent.EDGE && !Blockly.utils.userAgent.IE) { + xy.x -= 0.5 * scale; + xy.y -= 0.5 * scale; + } + scaledWidth += 1 * scale; + scaledHeight += 1 * scale; + } } else { var xy = this.borderRect_.getBoundingClientRect(); + var scaledWidth = xy.width; + var scaledHeight = xy.height; } return { top: xy.y, - bottom: xy.y + xy.height, + bottom: xy.y + scaledHeight, left: xy.x, - right: xy.x + xy.width + right: xy.x + scaledWidth }; }; diff --git a/core/renderers/common/constants.js b/core/renderers/common/constants.js index 97b76a7b4..ae0c1bb33 100644 --- a/core/renderers/common/constants.js +++ b/core/renderers/common/constants.js @@ -168,7 +168,7 @@ Blockly.blockRendering.ConstantProvider = function() { * Height of text. * @type {number} */ - this.FIELD_TEXT_HEIGHT = 13; + this.FIELD_TEXT_HEIGHT = 16; /** * Text font weight. Should match blocklyText's font-weight in CSS. @@ -207,12 +207,18 @@ Blockly.blockRendering.ConstantProvider = function() { this.FIELD_BORDER_RECT_Y_PADDING = 3; /** - * Field text baseline. This is only used if `FIELD_TEXT_BASELINE_CENTER` is - * set to false. + * 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} @@ -232,6 +238,13 @@ Blockly.blockRendering.ConstantProvider = function() { */ this.FIELD_DROPDOWN_SVG_ARROW = false; + /** + * Whether or not to show a box shadow around the widget div. This is only a + * feature of full block fields. + * @type {boolean} + */ + this.FIELD_TEXTINPUT_BOX_SHADOW = false; + /** * Whether or not the colour field should display its colour value on the * entire block. diff --git a/core/renderers/zelos/constants.js b/core/renderers/zelos/constants.js index 03115ca63..2195c1096 100644 --- a/core/renderers/zelos/constants.js +++ b/core/renderers/zelos/constants.js @@ -169,6 +169,24 @@ Blockly.zelos.ConstantProvider = function() { */ 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 */ @@ -178,6 +196,16 @@ Blockly.zelos.ConstantProvider = function() { * @override */ this.FIELD_BORDER_RECT_X_PADDING = 2 * this.GRID_UNIT; + + /** + * @override + */ + this.FIELD_BORDER_RECT_Y_PADDING = 1 * this.GRID_UNIT; + + /** + * @override + */ + this.FIELD_BORDER_RECT_HEIGHT = 8 * this.GRID_UNIT; /** * @override @@ -214,6 +242,16 @@ Blockly.zelos.ConstantProvider = function() { 'AuNTYtLjU2LDkuMzEtMC41Niw5Ljg3LDBhMS40NCwxLjQ0LDAsMCwxLDAsMkw3LjM3LDcuMz' + 'dBMS40MywxLjQzLDAsMCwxLDYuMzYsNy43OVoiIGZpbGw9IiNmZmYiLz48L3N2Zz4='; + /** + * @override + */ + this.FIELD_TEXTINPUT_BOX_SHADOW = true; + + /** + * @override + */ + this.FIELD_TEXT_Y_OFFSET = Blockly.utils.userAgent.CHROME ? -.45 : 0; + /** * @override */ diff --git a/core/utils/useragent.js b/core/utils/useragent.js index 0f72c7e1d..f34d73e74 100644 --- a/core/utils/useragent.js +++ b/core/utils/useragent.js @@ -51,6 +51,8 @@ goog.require('Blockly.utils.global'); // Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.44 // (KHTML, like Gecko) JavaFX/8.0 Safari/537.44 Blockly.utils.userAgent.JAVA_FX = has('JavaFX'); + Blockly.utils.userAgent.CHROME = (has('Chrome') || has('CriOS')) && + !Blockly.utils.userAgent.EDGE; // Engines. Logic from: // https://github.com/google/closure-library/blob/master/closure/goog/labs/useragent/engine.js