Dynamic output shapes (#2980)

* Round and triangle dynamic output shapes.
This commit is contained in:
Sam El-Husseini
2019-09-06 13:04:01 -07:00
committed by GitHub
parent f2595b980c
commit 4c8e28c53d
7 changed files with 251 additions and 48 deletions

View File

@@ -180,9 +180,13 @@ Blockly.blockRendering.Drawer.prototype.drawValueInput_ = function(row) {
var input = row.getLastInput();
this.positionExternalValueConnection_(row);
var pathDown = (typeof input.shape.pathDown == "function") ?
input.shape.pathDown(input.height) :
input.shape.pathDown;
this.outlinePath_ +=
Blockly.utils.svgPaths.lineOnAxis('H', input.xPos + input.width) +
input.shape.pathDown +
pathDown +
Blockly.utils.svgPaths.lineOnAxis('v', row.height - input.connectionHeight);
};
@@ -267,10 +271,14 @@ Blockly.blockRendering.Drawer.prototype.drawLeft_ = function() {
if (outputConnection) {
var tabBottom = outputConnection.connectionOffsetY +
outputConnection.height;
var pathUp = (typeof outputConnection.shape.pathUp == "function") ?
outputConnection.shape.pathUp(outputConnection.height) :
outputConnection.shape.pathUp;
// Draw a line up to the bottom of the tab.
this.outlinePath_ +=
Blockly.utils.svgPaths.lineOnAxis('V', tabBottom) +
outputConnection.shape.pathUp;
pathUp;
}
// Close off the path. This draws a vertical line up to the start of the
// block's path, which may be either a rounded or a sharp corner.

View File

@@ -71,6 +71,15 @@ Blockly.blockRendering.OutputConnection = function(connectionModel) {
goog.inherits(Blockly.blockRendering.OutputConnection,
Blockly.blockRendering.Connection);
/**
* Whether or not the connection shape is dynamic. Dynamic shapes get their
* height from the block.
* @return {boolean} True if the connection shape is dynamic.
*/
Blockly.blockRendering.OutputConnection.prototype.isDynamic = function() {
return this.shape.isDynamic;
};
/**
* An object containing information about the space a previous connection takes
* up during rendering.

View File

@@ -38,20 +38,23 @@ goog.require('Blockly.utils.svgPaths');
*/
Blockly.zelos.ConstantProvider = function() {
Blockly.zelos.ConstantProvider.superClass_.constructor.call(this);
this.GRID_UNIT = 4;
var GRID_UNIT = 4;
this.CORNER_RADIUS = 1 * this.GRID_UNIT;
this.CORNER_RADIUS = 1 * GRID_UNIT;
this.NOTCH_WIDTH = 9 * this.GRID_UNIT;
this.NOTCH_WIDTH = 9 * GRID_UNIT;
this.NOTCH_HEIGHT = 2 * this.GRID_UNIT;
this.NOTCH_HEIGHT = 2 * GRID_UNIT;
this.NOTCH_OFFSET_LEFT = 3 * this.GRID_UNIT;
this.NOTCH_OFFSET_LEFT = 3 * GRID_UNIT;
this.MIN_BLOCK_HEIGHT = 12 * GRID_UNIT;
this.MIN_BLOCK_HEIGHT = 12 * this.GRID_UNIT;
this.DARK_PATH_OFFSET = 0;
this.TAB_OFFSET_FROM_TOP = 0;
};
goog.inherits(Blockly.zelos.ConstantProvider,
Blockly.blockRendering.ConstantProvider);
@@ -61,35 +64,78 @@ goog.inherits(Blockly.zelos.ConstantProvider,
*/
Blockly.zelos.ConstantProvider.prototype.init = function() {
Blockly.zelos.ConstantProvider.superClass_.init.call(this);
this.TRIANGLE = this.makeTriangle();
this.HEXAGONAL = this.makeHexagonal();
this.ROUNDED = this.makeRounded();
};
/**
* @return {!Object} An object containing sizing and path information about
* a triangle shape for connections.
* a hexagonal shape for connections.
* @package
*/
Blockly.zelos.ConstantProvider.prototype.makeTriangle = function() {
var width = 20;
var height = 20;
Blockly.zelos.ConstantProvider.prototype.makeHexagonal = function() {
// The 'up' and 'down' versions of the paths are the same, but the Y sign
// flips. Forward and back are the signs to use to move the cursor in the
// direction that the path is being drawn.
function makeMainPath(up) {
function makeMainPath(height, up, right) {
var width = height / 2;
var forward = up ? -1 : 1;
var direction = right ? -1 : 1;
return Blockly.utils.svgPaths.lineTo(-width, forward * height / 2) +
Blockly.utils.svgPaths.lineTo(width, forward * height / 2);
return Blockly.utils.svgPaths.lineTo(-1 * direction * width, forward * height / 2) +
Blockly.utils.svgPaths.lineTo(direction * width, forward * height / 2);
}
var pathUp = makeMainPath(true);
var pathDown = makeMainPath(false);
return {
width: 0,
height: 0,
isDynamic: true,
pathDown: function(height) {
return makeMainPath(height, false, false);
},
pathUp: function(height) {
return makeMainPath(height, true, false);
},
pathRightDown: function(height) {
return makeMainPath(height, false, true);
},
pathRightUp: function(height) {
return makeMainPath(height, false, true);
},
};
};
/**
* @return {!Object} An object containing sizing and path information about
* a rounded shape for connections.
* @package
*/
Blockly.zelos.ConstantProvider.prototype.makeRounded = function() {
// The 'up' and 'down' versions of the paths are the same, but the Y sign
// flips. Forward and back are the signs to use to move the cursor in the
// direction that the path is being drawn.
function makeMainPath(height, up, right) {
var edgeWidth = height / 2;
return Blockly.utils.svgPaths.arc('a', '0 0 ' + (up || right ? 1 : 0), edgeWidth,
Blockly.utils.svgPaths.point(0, (up ? -1 : 1) * edgeWidth * 2));
}
return {
width: width,
height: height,
pathDown: pathDown,
pathUp: pathUp
width: 0,
height: 0,
isDynamic: true,
pathDown: function(height) {
return makeMainPath(height, false, false);
},
pathUp: function(height) {
return makeMainPath(height, true, false);
},
pathRightDown: function(height) {
return makeMainPath(height, false, true);
},
pathRightUp: function(height) {
return makeMainPath(height, false, true);
},
};
};
@@ -104,7 +150,13 @@ Blockly.zelos.ConstantProvider.prototype.shapeFor = function(
case Blockly.OUTPUT_VALUE:
// Includes doesn't work in IE.
if (checks && checks.indexOf('Boolean') != -1) {
return this.TRIANGLE;
return this.HEXAGONAL;
}
if (checks && checks.indexOf('Number') != -1) {
return this.ROUNDED;
}
if (checks && checks.indexOf('String') != -1) {
return this.ROUNDED;
}
return this.PUZZLE_TAB;
case Blockly.PREVIOUS_STATEMENT:

View File

@@ -47,6 +47,21 @@ Blockly.zelos.Drawer = function(block, info) {
goog.inherits(Blockly.zelos.Drawer, Blockly.blockRendering.Drawer);
/**
* @override
*/
Blockly.zelos.Drawer.prototype.drawOutline_ = function() {
if (this.info_.outputConnection &&
this.info_.outputConnection.isDynamic()) {
this.drawFlatTop_();
this.drawRightDynamicConnection_();
this.drawFlatBottom_();
this.drawLeftDynamicConnection_();
} else {
Blockly.zelos.Drawer.superClass_.drawOutline_.call(this);
}
};
/**
* Add steps for the top corner of the block, taking into account
* details such as hats and rounded corners.
@@ -89,24 +104,27 @@ Blockly.zelos.Drawer.prototype.drawBottom_ = function() {
var elems = bottomRow.elements;
this.positionNextConnection_();
this.outlinePath_ +=
Blockly.utils.svgPaths.lineOnAxis('v',
bottomRow.height - bottomRow.descenderHeight -
this.constants_.INSIDE_CORNERS.rightHeight);
var rightCornerYOffset = 0;
var outlinePath = '';
for (var i = elems.length - 1, elem; (elem = elems[i]); i--) {
if (Blockly.blockRendering.Types.isNextConnection(elem)) {
this.outlinePath_ += elem.shape.pathRight;
outlinePath += elem.shape.pathRight;
} else if (Blockly.blockRendering.Types.isLeftSquareCorner(elem)) {
this.outlinePath_ += Blockly.utils.svgPaths.lineOnAxis('H', bottomRow.xPos);
outlinePath += Blockly.utils.svgPaths.lineOnAxis('H', bottomRow.xPos);
} else if (Blockly.blockRendering.Types.isLeftRoundedCorner(elem)) {
this.outlinePath_ += this.constants_.OUTSIDE_CORNERS.bottomLeft;
outlinePath += this.constants_.OUTSIDE_CORNERS.bottomLeft;
} else if (Blockly.blockRendering.Types.isRightRoundedCorner(elem)) {
this.outlinePath_ += this.constants_.OUTSIDE_CORNERS.bottomRight;
outlinePath += this.constants_.OUTSIDE_CORNERS.bottomRight;
rightCornerYOffset = this.constants_.INSIDE_CORNERS.rightHeight;
} else if (Blockly.blockRendering.Types.isSpacer(elem)) {
this.outlinePath_ += Blockly.utils.svgPaths.lineOnAxis('h', elem.width * -1);
outlinePath += Blockly.utils.svgPaths.lineOnAxis('h', elem.width * -1);
}
}
this.outlinePath_ +=
Blockly.utils.svgPaths.lineOnAxis('V',
bottomRow.baseline - rightCornerYOffset);
this.outlinePath_ += outlinePath;
};
/**
@@ -134,3 +152,63 @@ Blockly.zelos.Drawer.prototype.drawRightSideRow_ = function(row) {
Blockly.utils.svgPaths.lineOnAxis('V', row.yPos + row.height);
}
};
/**
* Add steps to draw the right side of an output with a dynamic connection.
* @protected
*/
Blockly.zelos.Drawer.prototype.drawRightDynamicConnection_ = function() {
this.outlinePath_ += this.info_.outputConnection.shape.pathRightDown(
this.info_.outputConnection.height);
};
/**
* Add steps to draw the left side of an output with a dynamic connection.
* @protected
*/
Blockly.zelos.Drawer.prototype.drawLeftDynamicConnection_ = function() {
this.positionOutputConnection_();
this.outlinePath_ += this.info_.outputConnection.shape.pathUp(
this.info_.outputConnection.height);
// Close off the path. This draws a vertical line up to the start of the
// block's path, which may be either a rounded or a sharp corner.
this.outlinePath_ += 'z';
};
/**
* Add steps to draw a flat top row.
* @protected
*/
Blockly.zelos.Drawer.prototype.drawFlatTop_ = function() {
var topRow = this.info_.topRow;
this.positionPreviousConnection_();
this.outlinePath_ +=
Blockly.utils.svgPaths.moveBy(topRow.xPos, this.info_.startY);
this.outlinePath_ += Blockly.utils.svgPaths.lineOnAxis('h', topRow.width);
};
/**
* Add steps to draw a flat bottom row.
* @protected
*/
Blockly.zelos.Drawer.prototype.drawFlatBottom_ = function() {
var bottomRow = this.info_.bottomRow;
this.positionNextConnection_();
this.outlinePath_ +=
Blockly.utils.svgPaths.lineOnAxis('V', bottomRow.baseline);
this.outlinePath_ += Blockly.utils.svgPaths.lineOnAxis('h', -bottomRow.width);
};
/**
* @override
*/
Blockly.zelos.Drawer.prototype.drawInlineInput_ = function(input) {
// Don't draw an inline input.
this.positionInlineInputConnection_(input);
};

View File

@@ -88,6 +88,13 @@ goog.inherits(Blockly.zelos.RenderInfo, Blockly.blockRendering.RenderInfo);
* @override
*/
Blockly.zelos.RenderInfo.prototype.getInRowSpacing_ = function(prev, next) {
if (!prev || !next) {
// No need for padding at the beginning or end of the row if the
// output shape is dynamic.
if (this.outputConnection && this.outputConnection.isDynamic()) {
return this.constants_.NO_PADDING;
}
}
if (!prev) {
// Between an editable field and the beginning of the row.
if (next && Blockly.blockRendering.Types.isField(next) && next.isEditable) {
@@ -315,3 +322,48 @@ Blockly.zelos.RenderInfo.prototype.addAlignmentPadding_ = function(row,
row.width += missingSpace;
}
};
/**
* @override
*/
Blockly.zelos.RenderInfo.prototype.finalize_ = function() {
// Performance note: this could be combined with the draw pass, if the time
// that this takes is excessive. But it shouldn't be, because it only
// accesses and sets properties that already exist on the objects.
var yCursor = 0;
for (var i = 0, row; (row = this.rows[i]); i++) {
row.yPos = yCursor;
yCursor += row.height;
}
// Dynamic output connections depend on the height of the block. Adjust the
// height and width of the connection, and then adjust the startX and width of the
// block accordingly.
var outputConnectionWidth = 0;
if (this.outputConnection && !this.outputConnection.height) {
this.outputConnection.height = yCursor;
outputConnectionWidth = yCursor; // Twice the width to account for the right side.
this.outputConnection.width = outputConnectionWidth / 2;
}
this.startX += outputConnectionWidth / 2;
this.width += outputConnectionWidth;
var widestRowWithConnectedBlocks = 0;
for (var i = 0, row; (row = this.rows[i]); i++) {
row.xPos = this.startX;
widestRowWithConnectedBlocks =
Math.max(widestRowWithConnectedBlocks, row.widthWithConnectedBlocks);
var xCursor = row.xPos;
for (var j = 0, elem; (elem = row.elements[j]); j++) {
elem.xPos = xCursor;
elem.centerline = this.getElemCenterline_(row, elem);
xCursor += elem.width;
}
}
this.widthWithChildren = widestRowWithConnectedBlocks + this.startX;
this.height = yCursor;
this.startY = this.topRow.capline;
this.bottomRow.baseline = yCursor - this.bottomRow.descenderHeight;
};

View File

@@ -70,21 +70,21 @@ Blockly.zelos.TopRow.prototype.populate = function(block) {
};
/**
* Never render a left square corner. Always round.
* Render a round corner unless the block has an output connection.
* @override
*/
Blockly.zelos.TopRow.prototype.hasLeftSquareCorner = function(_block) {
return false;
Blockly.zelos.TopRow.prototype.hasLeftSquareCorner = function(block) {
return !!block.outputConnection;
};
/**
* Returns whether or not the top row has a right square corner.
* @param {!Blockly.BlockSvg} _block The block whose top row this represents.
* @param {!Blockly.BlockSvg} block The block whose top row this represents.
* @returns {boolean} Whether or not the top row has a left square corner.
*/
Blockly.zelos.TopRow.prototype.hasRightSquareCorner = function(_block) {
// Never render a right square corner. Always round.
return false;
Blockly.zelos.TopRow.prototype.hasRightSquareCorner = function(block) {
// Render a round corner unless the block has an output connection.
return !!block.outputConnection;
};
/**
@@ -120,21 +120,21 @@ Blockly.zelos.BottomRow.prototype.populate = function(block) {
};
/**
* Never render a left square corner. Always round.
* Render a round corner unless the block has an output connection.
* @override
*/
Blockly.zelos.BottomRow.prototype.hasLeftSquareCorner = function(_block) {
return false;
Blockly.zelos.BottomRow.prototype.hasLeftSquareCorner = function(block) {
return !!block.outputConnection;
};
/**
* Returns whether or not the bottom row has a right square corner.
* @param {!Blockly.BlockSvg} _block The block whose bottom row this represents.
* @param {!Blockly.BlockSvg} block The block whose bottom row this represents.
* @returns {boolean} Whether or not the bottom row has a left square corner.
*/
Blockly.zelos.BottomRow.prototype.hasRightSquareCorner = function(_block) {
// Never render a right square corner. Always round.
return false;
Blockly.zelos.BottomRow.prototype.hasRightSquareCorner = function(block) {
// Render a round corner unless the block has an output connection.
return !!block.outputConnection;
};
/**

View File

@@ -91,6 +91,7 @@ function start() {
match = location.search.match(/side=([^&]+)/);
var side = match ? match[1] : 'start';
document.forms.options.elements.side.value = side;
var autoimport = !!location.search.match(/autoimport=([^&]+)/);
// Create main workspace.
workspace = Blockly.inject('blocklyDiv',
{
@@ -146,6 +147,9 @@ function start() {
logEvents(false);
}
taChange();
if (autoimport) {
fromXml();
}
}
function addToolboxButtonCallbacks() {