From ee09aafd571a6bdf63cdba2306d092e86329e189 Mon Sep 17 00:00:00 2001 From: Sam El-Husseini Date: Thu, 7 Nov 2019 15:15:49 -0800 Subject: [PATCH] [zelos] Add input outlines on the path object. (#3410) * Add input outlines on the path object. --- core/field.js | 9 ++- core/field_dropdown.js | 6 +- core/renderers/common/path_object.js | 1 + core/renderers/geras/drawer.js | 3 +- core/renderers/measurables/inputs.js | 61 ++++++------------- core/renderers/zelos/constants.js | 12 +++- core/renderers/zelos/drawer.js | 44 +++++++++++++- core/renderers/zelos/info.js | 14 +---- core/renderers/zelos/path_object.js | 91 ++++++++++++++++++++++++++++ core/utils/dom.js | 6 +- tests/rendering/zelos/zelos.html | 7 ++- 11 files changed, 189 insertions(+), 65 deletions(-) diff --git a/core/field.js b/core/field.js index 0b13b7bdb..6a8e21f5b 100644 --- a/core/field.js +++ b/core/field.js @@ -263,6 +263,12 @@ Blockly.Field.prototype.SERIALIZABLE = false; */ Blockly.Field.FONTSIZE = 11; +/** + * Text font weight. Should match blocklyText's font-weight in CSS. + * @const {string} + */ +Blockly.Field.FONTWEIGHT = 'normal'; + /** * Text font family. Should match blocklyText's font-family in CSS. * @const {string} @@ -655,7 +661,8 @@ Blockly.Field.prototype.updateWidth = function() { Blockly.Field.prototype.updateSize_ = function() { var textWidth = Blockly.utils.dom.getFastTextWidth( /** @type {!SVGTextElement} */ (this.textElement_), - Blockly.Field.FONTSIZE, Blockly.Field.FONTFAMILY); + Blockly.Field.FONTSIZE, Blockly.Field.FONTWEIGHT, + Blockly.Field.FONTFAMILY); var totalWidth = textWidth; if (this.borderRect_) { totalWidth += Blockly.Field.X_PADDING; diff --git a/core/field_dropdown.js b/core/field_dropdown.js index 379516720..4a3f9c11d 100644 --- a/core/field_dropdown.js +++ b/core/field_dropdown.js @@ -516,7 +516,8 @@ Blockly.FieldDropdown.prototype.renderSelectedImage_ = function(imageJson) { var arrowWidth = Blockly.utils.dom.getFastTextWidth( /** @type {!SVGTSpanElement} */ (this.arrow_), - Blockly.Field.FONTSIZE, Blockly.Field.FONTFAMILY); + Blockly.Field.FONTSIZE, Blockly.Field.FONTWEIGHT, + Blockly.Field.FONTFAMILY); var imageHeight = Number(imageJson.height); var imageWidth = Number(imageJson.width); @@ -551,7 +552,8 @@ Blockly.FieldDropdown.prototype.renderSelectedText_ = function() { // Height and width include the border rect. this.size_.height = Blockly.Field.BORDER_RECT_DEFAULT_HEIGHT; this.size_.width = Blockly.utils.dom.getFastTextWidth(this.textElement_, - Blockly.Field.FONTSIZE, Blockly.Field.FONTFAMILY) + + Blockly.Field.FONTSIZE, Blockly.Field.FONTWEIGHT, + Blockly.Field.FONTFAMILY) + Blockly.Field.X_PADDING; }; diff --git a/core/renderers/common/path_object.js b/core/renderers/common/path_object.js index 1671f3777..f7ba75373 100644 --- a/core/renderers/common/path_object.js +++ b/core/renderers/common/path_object.js @@ -220,3 +220,4 @@ Blockly.blockRendering.PathObject.prototype.updateInsertionMarker = function( Blockly.blockRendering.PathObject.prototype.updateMovable = function(enable) { this.setClass_('blocklyDraggable', enable); }; + diff --git a/core/renderers/geras/drawer.js b/core/renderers/geras/drawer.js index 4affa11e3..311bd163f 100644 --- a/core/renderers/geras/drawer.js +++ b/core/renderers/geras/drawer.js @@ -26,11 +26,12 @@ goog.provide('Blockly.geras.Drawer'); goog.require('Blockly.blockRendering.ConstantProvider'); goog.require('Blockly.blockRendering.Drawer'); goog.require('Blockly.geras.Highlighter'); -goog.require('Blockly.geras.PathObject'); goog.require('Blockly.geras.RenderInfo'); goog.require('Blockly.utils.object'); goog.require('Blockly.utils.svgPaths'); +goog.requireType('Blockly.geras.PathObject'); + /** * An object that draws a block based on the given rendering information. diff --git a/core/renderers/measurables/inputs.js b/core/renderers/measurables/inputs.js index ccccb8343..a7a80068d 100644 --- a/core/renderers/measurables/inputs.js +++ b/core/renderers/measurables/inputs.js @@ -87,36 +87,26 @@ Blockly.blockRendering.InlineInput = function(constants, input) { constants, input); this.type |= Blockly.blockRendering.Types.INLINE_INPUT; - this.setShapeDimensions( - !this.isDynamicShape ? this.shape.height : 0, - !this.isDynamicShape ? this.shape.width : 0); - - this.connectionOffsetY = this.constants_.TAB_OFFSET_FROM_TOP; -}; -Blockly.utils.object.inherits(Blockly.blockRendering.InlineInput, - Blockly.blockRendering.InputConnection); - - -/** - * Sets properties that depend on the connection shape dimensions. - * @param {number} height Height of the connection shape. - * @param {number} width Width of the connection shape. - */ -Blockly.blockRendering.InlineInput.prototype.setShapeDimensions = function( - height, width) { if (!this.connectedBlock) { this.height = this.constants_.EMPTY_INLINE_INPUT_HEIGHT; - this.width = width + - this.constants_.EMPTY_INLINE_INPUT_PADDING; + this.width = this.constants_.EMPTY_INLINE_INPUT_PADDING; } else { // We allow the dark path to show on the parent block so that the child // block looks embossed. This takes up an extra pixel in both x and y. this.width = this.connectedBlockWidth; this.height = this.connectedBlockHeight; } - this.connectionHeight = height; - this.connectionWidth = width; + + this.connectionHeight = this.shape.height; + this.connectionWidth = !this.isDynamicShape ? this.shape.width : + this.shape.width(this.height); + if (!this.connectedBlock) { + this.width += this.connectionWidth * (this.isDynamicShape ? 2 : 1); + } + this.connectionOffsetY = this.constants_.TAB_OFFSET_FROM_TOP; }; +Blockly.utils.object.inherits(Blockly.blockRendering.InlineInput, + Blockly.blockRendering.InputConnection); /** * An object containing information about the space a statement input takes up @@ -162,34 +152,19 @@ Blockly.blockRendering.ExternalValueInput = function(constants, input) { Blockly.blockRendering.ExternalValueInput.superClass_.constructor.call(this, constants, input); this.type |= Blockly.blockRendering.Types.EXTERNAL_VALUE_INPUT; - - this.setShapeDimensions( - !this.isDynamicShape ? this.shape.height : 0, - !this.isDynamicShape ? this.shape.width : 0); - - this.connectionOffsetY = this.constants_.TAB_OFFSET_FROM_TOP; -}; -Blockly.utils.object.inherits(Blockly.blockRendering.ExternalValueInput, - Blockly.blockRendering.InputConnection); - - -/** - * Sets properties that depend on the connection shape dimensions. - * @param {number} height Height of the connection shape. - * @param {number} width Width of the connection shape. - */ -Blockly.blockRendering.ExternalValueInput.prototype.setShapeDimensions = function( - height, width) { if (!this.connectedBlock) { - this.height = height; + this.height = this.shape.height; } else { this.height = this.connectedBlockHeight - this.constants_.TAB_OFFSET_FROM_TOP - this.constants_.MEDIUM_PADDING; } - this.width = width + + this.width = this.shape.width + this.constants_.EXTERNAL_VALUE_INPUT_PADDING; - this.connectionHeight = height; - this.connectionWidth = width; + this.connectionOffsetY = this.constants_.TAB_OFFSET_FROM_TOP; + this.connectionHeight = this.shape.height; + this.connectionWidth = this.shape.width; }; +Blockly.utils.object.inherits(Blockly.blockRendering.ExternalValueInput, + Blockly.blockRendering.InputConnection); diff --git a/core/renderers/zelos/constants.js b/core/renderers/zelos/constants.js index f12ea22f2..a75255ff4 100644 --- a/core/renderers/zelos/constants.js +++ b/core/renderers/zelos/constants.js @@ -81,6 +81,16 @@ Blockly.zelos.ConstantProvider = function() { */ this.AFTER_STATEMENT_BOTTOM_ROW_MIN_HEIGHT = this.LARGE_PADDING * 2; + /** + * @override + */ + this.EMPTY_INLINE_INPUT_PADDING = 4 * this.GRID_UNIT; + + /** + * @override + */ + this.EMPTY_INLINE_INPUT_HEIGHT = 8 * this.GRID_UNIT; + /** * The ID of the highlight glow filter, or the empty string if no filter is * set. @@ -216,7 +226,7 @@ Blockly.zelos.ConstantProvider.prototype.shapeFor = function( if (checks && checks.indexOf('String') != -1) { return this.ROUNDED; } - return this.PUZZLE_TAB; + return this.ROUNDED; case Blockly.PREVIOUS_STATEMENT: case Blockly.NEXT_STATEMENT: return this.NOTCH; diff --git a/core/renderers/zelos/drawer.js b/core/renderers/zelos/drawer.js index 193665a72..f13df6272 100644 --- a/core/renderers/zelos/drawer.js +++ b/core/renderers/zelos/drawer.js @@ -29,6 +29,8 @@ goog.require('Blockly.blockRendering.Types'); goog.require('Blockly.utils.object'); goog.require('Blockly.zelos.RenderInfo'); +goog.requireType('Blockly.zelos.PathObject'); + /** * An object that draws a block based on the given rendering information. @@ -46,6 +48,28 @@ Blockly.utils.object.inherits(Blockly.zelos.Drawer, Blockly.blockRendering.Drawer); +/** + * @override + */ +Blockly.zelos.Drawer.prototype.draw = function() { + var pathObject = + /** @type {!Blockly.zelos.PathObject} */ (this.block_.pathObject); + pathObject.beginDrawing(); + this.hideHiddenIcons_(); + this.drawOutline_(); + this.drawInternals_(); + + pathObject.setPath(this.outlinePath_ + '\n' + this.inlinePath_); + if (this.info_.RTL) { + pathObject.flipRTL(); + } + if (Blockly.blockRendering.useDebugger) { + this.block_.renderingDebugger.drawDebug(this.block_, this.info_); + } + this.recordSizeOnBlock_(); + pathObject.endDrawing(); +}; + /** * @override */ @@ -143,8 +167,26 @@ Blockly.zelos.Drawer.prototype.drawFlatBottom_ = function() { * @override */ Blockly.zelos.Drawer.prototype.drawInlineInput_ = function(input) { - // Don't draw an inline input. this.positionInlineInputConnection_(input); + + var inputName = input.input.name; + if (input.connectedBlock || this.info_.isInsertionMarker) { + return; + } + + var width = input.width - (input.connectionWidth * 2); + var height = input.height; + var yPos = input.centerline - height / 2; + + var connectionRight = input.xPos + input.connectionWidth; + + var outlinePath = Blockly.utils.svgPaths.moveTo(connectionRight, yPos) + + Blockly.utils.svgPaths.lineOnAxis('h', width) + + input.shape.pathRightDown(input.height) + + Blockly.utils.svgPaths.lineOnAxis('h', -width) + + input.shape.pathUp(input.height) + + 'z'; + this.block_.pathObject.setOutlinePath(inputName, outlinePath); }; /** diff --git a/core/renderers/zelos/info.js b/core/renderers/zelos/info.js index b8a2dfd90..e272bea3e 100644 --- a/core/renderers/zelos/info.js +++ b/core/renderers/zelos/info.js @@ -182,9 +182,9 @@ Blockly.zelos.RenderInfo.prototype.getInRowSpacing_ = function(prev, next) { // Between an editable field and an input. if (prev.isEditable) { if (Blockly.blockRendering.Types.isInlineInput(next)) { - return this.constants_.SMALL_PADDING; + return this.constants_.MEDIUM_PADDING; } else if (Blockly.blockRendering.Types.isExternalInput(next)) { - return this.constants_.SMALL_PADDING; + return this.constants_.MEDIUM_PADDING; } } else { if (Blockly.blockRendering.Types.isInlineInput(next)) { @@ -346,16 +346,6 @@ Blockly.zelos.RenderInfo.prototype.finalize_ = function() { for (var i = 0, row; (row = this.rows[i]); i++) { row.xPos = this.startX; - for (var j = 0, elem; (elem = row.elements[j]); j++) { - if (Blockly.blockRendering.Types.isInlineInput(elem) || - Blockly.blockRendering.Types.isExternalInput(elem)) { - if (elem.isDynamicShape) { - elem.setShapeDimensions(elem.shape.height(elem.height), - elem.shape.width(elem.height)); - } - } - } - widestRowWithConnectedBlocks = Math.max(widestRowWithConnectedBlocks, row.widthWithConnectedBlocks); this.recordElemPositions_(row); diff --git a/core/renderers/zelos/path_object.js b/core/renderers/zelos/path_object.js index b0b3b2629..b2f7ba3c6 100644 --- a/core/renderers/zelos/path_object.js +++ b/core/renderers/zelos/path_object.js @@ -54,6 +54,23 @@ Blockly.zelos.PathObject = function(root, constants) { * @private */ this.svgPathSelected_ = null; + + /** + * The outline paths on the block. + * @type {!Object.} + * @private + */ + this.outlines_ = {}; + + /** + * A set used to determine which outlines were used during a draw pass. The + * set is initialized with a reference to all the outlines in + * `this.outlines_`. Everytime we use an outline during the draw pass, the + * reference is removed from this set. + * @type {Object.} + * @private + */ + this.remainingOutlines_ = null; }; Blockly.utils.object.inherits(Blockly.zelos.PathObject, Blockly.blockRendering.PathObject); @@ -87,3 +104,77 @@ Blockly.zelos.PathObject.prototype.updateSelected = function(enable) { } } }; + +/** + * Method that's called when the drawer is about to draw the block. + * @package + */ +Blockly.zelos.PathObject.prototype.beginDrawing = function() { + this.remainingOutlines_ = {}; + for (var i = 0, keys = Object.keys(this.outlines_), + key; (key = keys[i]); i++) { + // The value set here isn't used anywhere, we are just using the + // object as a Set data structure. + this.remainingOutlines_[key] = 1; + } +}; + +/** + * Method that's called when the drawer is done drawing. + * @package + */ +Blockly.zelos.PathObject.prototype.endDrawing = function() { + // Go through all remaining outlines that were not used this draw pass, and + // remove them. + if (this.remainingOutlines_) { + for (var i = 0, keys = Object.keys(this.remainingOutlines_), + key; (key = keys[i]); i++) { + this.removeOutlinePath_(key); + } + } + this.remainingOutlines_ = null; +}; + +/** + * Set the path generated by the renderer for an outline path on the respective + * outline path SVG element. + * @param {string} name The input name. + * @param {string} pathString The path. + * @package + */ +Blockly.zelos.PathObject.prototype.setOutlinePath = function(name, pathString) { + var outline = this.getOutlinePath_(name); + outline.setAttribute('d', pathString); + outline.setAttribute('fill', this.style.colourTertiary); +}; + +/** + * Create's an outline path for the specified input. + * @param {string} name The input name. + * @return {!SVGElement} The SVG outline path. + * @private + */ +Blockly.zelos.PathObject.prototype.getOutlinePath_ = function(name) { + if (!this.outlines_[name]) { + this.outlines_[name] = Blockly.utils.dom.createSvgElement('path', { + 'class': 'blocklyOutlinePath', + // IE doesn't like paths without the data definition, set empty default + 'd': '' + }, + this.svgRoot); + } + if (this.remainingOutlines_) { + delete this.remainingOutlines_[name]; + } + return this.outlines_[name]; +}; + +/** + * Remove an outline path that is associated with the specified input. + * @param {string} name The input name. + * @private + */ +Blockly.zelos.PathObject.prototype.removeOutlinePath_ = function(name) { + this.outlines_[name].parentNode.removeChild(this.outlines_[name]); + delete this.outlines_[name]; +}; diff --git a/core/utils/dom.js b/core/utils/dom.js index a659cfe08..c2337e595 100644 --- a/core/utils/dom.js +++ b/core/utils/dom.js @@ -285,11 +285,12 @@ Blockly.utils.dom.getTextWidth = function(textElement) { * advance. Similar to `getTextWidth`, we cache the width we compute. * @param {!Element} textElement An SVG 'text' element. * @param {number} 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.getFastTextWidth = function(textElement, - fontSize, fontFamily) { + fontSize, fontWeight, fontFamily) { var text = textElement.textContent; var key = text + '\n' + textElement.className.baseVal; var width; @@ -314,7 +315,8 @@ Blockly.utils.dom.getFastTextWidth = function(textElement, Blockly.utils.dom.canvasContext_ = computeCanvas.getContext('2d'); } // Set the desired font size and family. - Blockly.utils.dom.canvasContext_.font = fontSize + 'pt ' + fontFamily; + Blockly.utils.dom.canvasContext_.font = + fontWeight + ' ' + fontSize + 'pt ' + fontFamily; // Measure the text width using the helper canvas context. width = Blockly.utils.dom.canvasContext_.measureText(text).width; diff --git a/tests/rendering/zelos/zelos.html b/tests/rendering/zelos/zelos.html index a01d04dcc..edac39fbb 100644 --- a/tests/rendering/zelos/zelos.html +++ b/tests/rendering/zelos/zelos.html @@ -7,7 +7,7 @@