From f5bb5b6143c892148f12a7bb49a214b95b24e2f1 Mon Sep 17 00:00:00 2001 From: Sam El-Husseini Date: Fri, 6 Dec 2019 10:51:50 -0800 Subject: [PATCH] Zelos horizontal tight nesting. (#3490) * Reduce the amount of implicit spacing added by the connection shape by adding negative spacing to the first and last spacer in an output block --- core/renderers/common/constants.js | 14 +++- core/renderers/common/debugger.js | 5 ++ core/renderers/zelos/constants.js | 44 ++++++++++ core/renderers/zelos/drawer.js | 5 ++ core/renderers/zelos/info.js | 126 ++++++++++++++++++++++++---- core/renderers/zelos/path_object.js | 7 ++ 6 files changed, 182 insertions(+), 19 deletions(-) diff --git a/core/renderers/common/constants.js b/core/renderers/common/constants.js index 0ad536f16..a7736fc8d 100644 --- a/core/renderers/common/constants.js +++ b/core/renderers/common/constants.js @@ -430,13 +430,21 @@ Blockly.blockRendering.ConstantProvider = function() { */ this.CURSOR_STROKE_WIDTH = 4; - - /* + /** * Whether text input and colour fields fill up the entire source block. * @type {boolean} * @package */ this.FULL_BLOCK_FIELDS = false; + + /** + * Enum for connection shapes. + * @enum {number} + */ + this.SHAPES = { + PUZZLE: 1, + NOTCH: 2 + }; }; /** @@ -717,6 +725,7 @@ Blockly.blockRendering.ConstantProvider.prototype.makePuzzleTab = function() { var pathDown = makeMainPath(false); return { + type: this.SHAPES.PUZZLE, width: width, height: height, pathDown: pathDown, @@ -746,6 +755,7 @@ Blockly.blockRendering.ConstantProvider.prototype.makeNotch = function() { var pathRight = makeMainPath(-1); return { + type: this.SHAPES.NOTCH, width: width, height: height, pathLeft: pathLeft, diff --git a/core/renderers/common/debugger.js b/core/renderers/common/debugger.js index a429e23ae..207648e24 100644 --- a/core/renderers/common/debugger.js +++ b/core/renderers/common/debugger.js @@ -121,6 +121,11 @@ Blockly.blockRendering.Debug.prototype.drawSpacerElem = function(elem, rowHeight return; } + // Don't render elements with negative spacing. + if (elem.width < 0) { + return; + } + var xPos = elem.xPos; if (isRtl) { xPos = -(xPos + elem.width); diff --git a/core/renderers/zelos/constants.js b/core/renderers/zelos/constants.js index f3a2083ab..a46c6992a 100644 --- a/core/renderers/zelos/constants.js +++ b/core/renderers/zelos/constants.js @@ -159,6 +159,47 @@ Blockly.zelos.ConstantProvider = function() { */ this.CURSOR_RADIUS = 5; + /** + * @enum {number} + * @override + */ + this.SHAPES = { + HEXAGONAL: 1, + ROUND: 2, + SQUARE: 3, + PUZZLE: 4, + NOTCH: 5 + }; + + /** + * Map of output/input shapes and the amount they should cause a block to be + * padded. Outer key is the outer shape, inner key is the inner shape. + * When a block with the outer shape contains an input block with the inner + * shape on its left or right edge, the block elements are aligned such that + * the padding specified is reached. + * @package + */ + this.SHAPE_IN_SHAPE_PADDING = { + 1: { // Outer shape: hexagon. + 0: 5 * this.GRID_UNIT, // Field in hexagon. + 1: 2 * this.GRID_UNIT, // Hexagon in hexagon. + 2: 5 * this.GRID_UNIT, // Round in hexagon. + 3: 5 * this.GRID_UNIT // Square in hexagon. + }, + 2: { // Outer shape: round. + 0: 3 * this.GRID_UNIT, // Field in round. + 1: 3 * this.GRID_UNIT, // Hexagon in round. + 2: 1 * this.GRID_UNIT, // Round in round. + 3: 2 * this.GRID_UNIT // Square in round. + }, + 3: { // Outer shape: square. + 0: 2 * this.GRID_UNIT, // Field in square. + 1: 2 * this.GRID_UNIT, // Hexagon in square. + 2: 2 * this.GRID_UNIT, // Round in square. + 3: 2 * this.GRID_UNIT // Square in square. + } + }; + /** * @override */ @@ -348,6 +389,7 @@ Blockly.zelos.ConstantProvider.prototype.makeHexagonal = function() { } return { + type: this.SHAPES.HEXAGONAL, isDynamic: true, width: function(height) { return height / 2; @@ -386,6 +428,7 @@ Blockly.zelos.ConstantProvider.prototype.makeRounded = function() { } return { + type: this.SHAPES.ROUND, isDynamic: true, width: function(height) { return height / 2; @@ -499,6 +542,7 @@ Blockly.zelos.ConstantProvider.prototype.makeNotch = function() { var pathRight = makeMainPath(-1); return { + type: this.SHAPES.NOTCH, width: width, height: height, pathLeft: pathLeft, diff --git a/core/renderers/zelos/drawer.js b/core/renderers/zelos/drawer.js index f13df6272..a47d40e64 100644 --- a/core/renderers/zelos/drawer.js +++ b/core/renderers/zelos/drawer.js @@ -67,6 +67,11 @@ Blockly.zelos.Drawer.prototype.draw = function() { this.block_.renderingDebugger.drawDebug(this.block_, this.info_); } this.recordSizeOnBlock_(); + if (this.info_.outputConnection) { + // Store the output connection shape type for parent blocks to use during + // rendering. + pathObject.outputShapeType = this.info_.outputConnection.shape.type; + } pathObject.endDrawing(); }; diff --git a/core/renderers/zelos/info.js b/core/renderers/zelos/info.js index 465de104f..27ec71fd2 100644 --- a/core/renderers/zelos/info.js +++ b/core/renderers/zelos/info.js @@ -253,39 +253,130 @@ Blockly.zelos.RenderInfo.prototype.adjustXPosition_ = function() { }; /** - * Finalize the output connection info. In particular set the height of the + * Finalize the output connection info. In particular, set the height of the * output connection to match that of the block. For the right side, add a * right connection shape element and have it match the dimensions of the * output connection. * @protected */ Blockly.zelos.RenderInfo.prototype.finalizeOutputConnection_ = function() { + // Dynamic output connections depend on the height of the block. + if (!this.outputConnection || !this.outputConnection.isDynamicShape) { + return; + } var yCursor = 0; // Determine the block height. for (var i = 0, row; (row = this.rows[i]); i++) { row.yPos = yCursor; yCursor += row.height; } - if (this.outputConnection && this.outputConnection.isDynamicShape) { - // Dynamic output connections depend on the height of the block. Adjust the - // height of the connection. - var connectionHeight = this.outputConnection.shape.height(yCursor); - var connectionWidth = this.outputConnection.shape.width(yCursor); - this.outputConnection.height = connectionHeight; - this.outputConnection.width = connectionWidth; - this.outputConnection.startX = connectionWidth; + // Adjust the height of the output connection. + var connectionHeight = this.outputConnection.shape.height(yCursor); + var connectionWidth = this.outputConnection.shape.width(yCursor); - // Adjust right side measurable. - this.rightSide.height = connectionHeight; - this.rightSide.width = connectionWidth; - this.rightSide.centerline = connectionHeight / 2; - this.rightSide.xPos = this.width + connectionWidth; + this.outputConnection.height = connectionHeight; + this.outputConnection.width = connectionWidth; + this.outputConnection.startX = connectionWidth; - this.startX = connectionWidth; - this.width += connectionWidth * 2; - this.widthWithChildren += connectionWidth * 2; + // Adjust right side measurable. + this.rightSide.height = connectionHeight; + this.rightSide.width = connectionWidth; + this.rightSide.centerline = connectionHeight / 2; + this.rightSide.xPos = this.width + connectionWidth; + + this.startX = connectionWidth; + this.width += connectionWidth * 2; + this.widthWithChildren += connectionWidth * 2; +}; + +/** + * Finalize alignment of elements on the block. In particular, reduce the + * implicit spacing created by the left and right output connection shapes by + * adding setting negative spacing onto the leftmost and rightmost spacers. + * @protected + */ +Blockly.zelos.RenderInfo.prototype.finalizeAlignment_ = function() { + if (!this.outputConnection) { + return; } + var totalNegativeSpacing = 0; + for (var i = 0, row; (row = this.rows[i]); i++) { + if (!Blockly.blockRendering.Types.isInputRow(row)) { + continue; + } + var firstElem = row.elements[1]; + var lastElem = row.elements[row.elements.length - 2]; + var leftNegPadding = this.getNegativeSpacing_(firstElem); + var rightNegPadding = this.getNegativeSpacing_(lastElem); + totalNegativeSpacing = leftNegPadding + rightNegPadding; + var minBlockWidth = this.constants_.MIN_BLOCK_WIDTH + + this.outputConnection.width * 2; + if (this.width - totalNegativeSpacing < minBlockWidth) { + // Maintain a minimum block width, split negative spacing between left + // and right edge. + totalNegativeSpacing = this.width - minBlockWidth; + row.getFirstSpacer().width = -totalNegativeSpacing / 2; + row.getLastSpacer().width = -totalNegativeSpacing / 2; + } else { + row.getFirstSpacer().width = -leftNegPadding; + row.getLastSpacer().width = -rightNegPadding; + } + } + if (totalNegativeSpacing) { + this.width -= totalNegativeSpacing; + this.widthWithChildren -= totalNegativeSpacing; + this.rightSide.xPos -= totalNegativeSpacing; + for (var i = 0, row; (row = this.rows[i]); i++) { + if (Blockly.blockRendering.Types.isTopRow(row) || + Blockly.blockRendering.Types.isBottomRow(row)) { + row.elements[1].width -= totalNegativeSpacing; + } + row.width -= totalNegativeSpacing; + row.widthWithConnectedBlocks -= totalNegativeSpacing; + } + } +}; + +/** + * Calculate the spacing to reduce the left and right edges by based on the + * outer and inner connection shape. + * @param {Blockly.blockRendering.Measurable} elem The first or last element on + * a block. + * @return {number} The amount of spacing to reduce the first or last spacer. + * @protected + */ +Blockly.zelos.RenderInfo.prototype.getNegativeSpacing_ = function(elem) { + if (!elem) { + return 0; + } + var connectionWidth = this.outputConnection.width; + var outerShape = this.outputConnection.shape.type; + var constants = + /** @type {!Blockly.zelos.ConstantProvider} */ (this.constants_); + if (Blockly.blockRendering.Types.isInlineInput(elem)) { + var innerShape = elem.connectedBlock ? + elem.connectedBlock.pathObject.outputShapeType : + elem.shape.type; + // Special case for hexagonal output. + if (outerShape == constants.SHAPES.HEXAGONAL && + outerShape != innerShape) { + return 0; + } + return connectionWidth - + this.constants_.SHAPE_IN_SHAPE_PADDING[outerShape][innerShape]; + } else if (Blockly.blockRendering.Types.isField(elem)) { + // Special case for text inputs. + if (outerShape == constants.SHAPES.ROUND && + elem.field instanceof Blockly.FieldTextInput) { + return connectionWidth - (2.75 * constants.GRID_UNIT); + } + return connectionWidth - + this.constants_.SHAPE_IN_SHAPE_PADDING[outerShape][0]; + } else if (Blockly.blockRendering.Types.isIcon(elem)) { + return this.constants_.SMALL_PADDING; + } + return 0; }; /** @@ -293,5 +384,6 @@ Blockly.zelos.RenderInfo.prototype.finalizeOutputConnection_ = function() { */ Blockly.zelos.RenderInfo.prototype.finalize_ = function() { this.finalizeOutputConnection_(); + this.finalizeAlignment_(); Blockly.zelos.RenderInfo.superClass_.finalize_.call(this); }; diff --git a/core/renderers/zelos/path_object.js b/core/renderers/zelos/path_object.js index d3c7f94ca..e3511d6f4 100644 --- a/core/renderers/zelos/path_object.js +++ b/core/renderers/zelos/path_object.js @@ -74,6 +74,13 @@ Blockly.zelos.PathObject = function(root, style, constants) { * @private */ this.remainingOutlines_ = null; + + /** + * The type of block's output connection shape. This is set when a block with + * an output connection is drawn. + * @package + */ + this.outputShapeType = null; }; Blockly.utils.object.inherits(Blockly.zelos.PathObject, Blockly.blockRendering.PathObject);