mirror of
https://github.com/scratchfoundation/scratch-blocks.git
synced 2025-06-06 18:04:18 -04:00
1732 lines
58 KiB
JavaScript
1732 lines
58 KiB
JavaScript
/**
|
|
* @license
|
|
* Visual Blocks Editor
|
|
*
|
|
* Copyright 2012 Google Inc.
|
|
* https://developers.google.com/blockly/
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
/**
|
|
* @fileoverview Methods for graphically rendering a block as SVG.
|
|
* @author fraser@google.com (Neil Fraser)
|
|
*/
|
|
'use strict';
|
|
|
|
goog.provide('Blockly.BlockSvg.render');
|
|
|
|
goog.require('Blockly.BlockSvg');
|
|
goog.require('Blockly.scratchBlocksUtils');
|
|
goog.require('Blockly.utils');
|
|
|
|
|
|
// UI constants for rendering blocks.
|
|
/**
|
|
* Grid unit to pixels conversion
|
|
* @const
|
|
*/
|
|
Blockly.BlockSvg.GRID_UNIT = 4;
|
|
|
|
/**
|
|
* Horizontal space between elements.
|
|
* @const
|
|
*/
|
|
Blockly.BlockSvg.SEP_SPACE_X = 2 * Blockly.BlockSvg.GRID_UNIT;
|
|
|
|
/**
|
|
* Vertical space between elements.
|
|
* @const
|
|
*/
|
|
Blockly.BlockSvg.SEP_SPACE_Y = 2 * Blockly.BlockSvg.GRID_UNIT;
|
|
|
|
/**
|
|
* Minimum width of a block.
|
|
* @const
|
|
*/
|
|
Blockly.BlockSvg.MIN_BLOCK_X = 16 * Blockly.BlockSvg.GRID_UNIT;
|
|
|
|
/**
|
|
* Minimum width of a block with output (reporters).
|
|
* @const
|
|
*/
|
|
Blockly.BlockSvg.MIN_BLOCK_X_OUTPUT = 12 * Blockly.BlockSvg.GRID_UNIT;
|
|
|
|
/**
|
|
* Minimum width of a shadow block with output (single fields).
|
|
* @const
|
|
*/
|
|
Blockly.BlockSvg.MIN_BLOCK_X_SHADOW_OUTPUT = 10 * Blockly.BlockSvg.GRID_UNIT;
|
|
|
|
/**
|
|
* Minimum height of a block.
|
|
* @const
|
|
*/
|
|
Blockly.BlockSvg.MIN_BLOCK_Y = 12 * Blockly.BlockSvg.GRID_UNIT;
|
|
|
|
/**
|
|
* Height of extra row after a statement input.
|
|
* @const
|
|
*/
|
|
Blockly.BlockSvg.EXTRA_STATEMENT_ROW_Y = 8 * Blockly.BlockSvg.GRID_UNIT;
|
|
|
|
/**
|
|
* Minimum width of a C- or E-shaped block.
|
|
* @const
|
|
*/
|
|
Blockly.BlockSvg.MIN_BLOCK_X_WITH_STATEMENT = 40 * Blockly.BlockSvg.GRID_UNIT;
|
|
|
|
/**
|
|
* Minimum height of a shadow block with output and a single field.
|
|
* This is used for shadow blocks that only contain a field - which are smaller than even reporters.
|
|
* @const
|
|
*/
|
|
Blockly.BlockSvg.MIN_BLOCK_Y_SINGLE_FIELD_OUTPUT = 8 * Blockly.BlockSvg.GRID_UNIT;
|
|
|
|
/**
|
|
* Minimum height of a non-shadow block with output, i.e. a reporter.
|
|
* @const
|
|
*/
|
|
Blockly.BlockSvg.MIN_BLOCK_Y_REPORTER = 10 * Blockly.BlockSvg.GRID_UNIT;
|
|
|
|
/**
|
|
* Minimum space for a statement input height.
|
|
* @const
|
|
*/
|
|
Blockly.BlockSvg.MIN_STATEMENT_INPUT_HEIGHT = 6 * Blockly.BlockSvg.GRID_UNIT;
|
|
|
|
/**
|
|
* Width of vertical notch.
|
|
* @const
|
|
*/
|
|
Blockly.BlockSvg.NOTCH_WIDTH = 8 * Blockly.BlockSvg.GRID_UNIT;
|
|
|
|
/**
|
|
* Height of vertical notch.
|
|
* @const
|
|
*/
|
|
Blockly.BlockSvg.NOTCH_HEIGHT = 2 * Blockly.BlockSvg.GRID_UNIT;
|
|
|
|
/**
|
|
* Rounded corner radius.
|
|
* @const
|
|
*/
|
|
Blockly.BlockSvg.CORNER_RADIUS = 1 * Blockly.BlockSvg.GRID_UNIT;
|
|
|
|
/**
|
|
* Minimum width of statement input edge on the left, in px.
|
|
* @const
|
|
*/
|
|
Blockly.BlockSvg.STATEMENT_INPUT_EDGE_WIDTH = 4 * Blockly.BlockSvg.GRID_UNIT;
|
|
|
|
/**
|
|
* Inner space between edge of statement input and notch.
|
|
* @const
|
|
*/
|
|
Blockly.BlockSvg.STATEMENT_INPUT_INNER_SPACE = 2 * Blockly.BlockSvg.GRID_UNIT;
|
|
|
|
/**
|
|
* Height of the top hat.
|
|
* @const
|
|
*/
|
|
Blockly.BlockSvg.START_HAT_HEIGHT = 16;
|
|
|
|
/**
|
|
* Height of the vertical separator line for icons that appear at the left edge
|
|
* of a block, such as extension icons.
|
|
* @const
|
|
*/
|
|
Blockly.BlockSvg.ICON_SEPARATOR_HEIGHT = 10 * Blockly.BlockSvg.GRID_UNIT;
|
|
|
|
/**
|
|
* Path of the top hat's curve.
|
|
* @const
|
|
*/
|
|
Blockly.BlockSvg.START_HAT_PATH = 'c 25,-22 71,-22 96,0';
|
|
|
|
/**
|
|
* SVG path for drawing next/previous notch from left to right.
|
|
* @const
|
|
*/
|
|
Blockly.BlockSvg.NOTCH_PATH_LEFT = (
|
|
'c 2,0 3,1 4,2 ' +
|
|
'l 4,4 ' +
|
|
'c 1,1 2,2 4,2 ' +
|
|
'h 12 ' +
|
|
'c 2,0 3,-1 4,-2 ' +
|
|
'l 4,-4 ' +
|
|
'c 1,-1 2,-2 4,-2'
|
|
);
|
|
|
|
/**
|
|
* SVG path for drawing next/previous notch from right to left.
|
|
* @const
|
|
*/
|
|
Blockly.BlockSvg.NOTCH_PATH_RIGHT = (
|
|
'c -2,0 -3,1 -4,2 ' +
|
|
'l -4,4 ' +
|
|
'c -1,1 -2,2 -4,2 ' +
|
|
'h -12 ' +
|
|
'c -2,0 -3,-1 -4,-2 ' +
|
|
'l -4,-4 ' +
|
|
'c -1,-1 -2,-2 -4,-2'
|
|
);
|
|
|
|
/**
|
|
* Amount of padding before the notch.
|
|
* @const
|
|
*/
|
|
Blockly.BlockSvg.NOTCH_START_PADDING = 3 * Blockly.BlockSvg.GRID_UNIT;
|
|
|
|
/**
|
|
* SVG start point for drawing the top-left corner.
|
|
* @const
|
|
*/
|
|
Blockly.BlockSvg.TOP_LEFT_CORNER_START =
|
|
'm 0,' + Blockly.BlockSvg.CORNER_RADIUS;
|
|
|
|
/**
|
|
* SVG path for drawing the rounded top-left corner.
|
|
* @const
|
|
*/
|
|
Blockly.BlockSvg.TOP_LEFT_CORNER =
|
|
'A ' + Blockly.BlockSvg.CORNER_RADIUS + ',' +
|
|
Blockly.BlockSvg.CORNER_RADIUS + ' 0 0,1 ' +
|
|
Blockly.BlockSvg.CORNER_RADIUS + ',0';
|
|
|
|
/**
|
|
* SVG path for drawing the rounded top-right corner.
|
|
* @const
|
|
*/
|
|
Blockly.BlockSvg.TOP_RIGHT_CORNER =
|
|
'a ' + Blockly.BlockSvg.CORNER_RADIUS + ',' +
|
|
Blockly.BlockSvg.CORNER_RADIUS + ' 0 0,1 ' +
|
|
Blockly.BlockSvg.CORNER_RADIUS + ',' +
|
|
Blockly.BlockSvg.CORNER_RADIUS;
|
|
|
|
/**
|
|
* SVG path for drawing the rounded bottom-right corner.
|
|
* @const
|
|
*/
|
|
Blockly.BlockSvg.BOTTOM_RIGHT_CORNER =
|
|
' a ' + Blockly.BlockSvg.CORNER_RADIUS + ',' +
|
|
Blockly.BlockSvg.CORNER_RADIUS + ' 0 0,1 -' +
|
|
Blockly.BlockSvg.CORNER_RADIUS + ',' +
|
|
Blockly.BlockSvg.CORNER_RADIUS;
|
|
|
|
/**
|
|
* SVG path for drawing the rounded bottom-left corner.
|
|
* @const
|
|
*/
|
|
Blockly.BlockSvg.BOTTOM_LEFT_CORNER =
|
|
'a ' + Blockly.BlockSvg.CORNER_RADIUS + ',' +
|
|
Blockly.BlockSvg.CORNER_RADIUS + ' 0 0,1 -' +
|
|
Blockly.BlockSvg.CORNER_RADIUS + ',-' +
|
|
Blockly.BlockSvg.CORNER_RADIUS;
|
|
|
|
/**
|
|
* SVG path for drawing the top-left corner of a statement input.
|
|
* @const
|
|
*/
|
|
Blockly.BlockSvg.INNER_TOP_LEFT_CORNER =
|
|
' a ' + Blockly.BlockSvg.CORNER_RADIUS + ',' +
|
|
Blockly.BlockSvg.CORNER_RADIUS + ' 0 0,0 -' +
|
|
Blockly.BlockSvg.CORNER_RADIUS + ',' +
|
|
Blockly.BlockSvg.CORNER_RADIUS;
|
|
|
|
/**
|
|
* SVG path for drawing the bottom-left corner of a statement input.
|
|
* Includes the rounded inside corner.
|
|
* @const
|
|
*/
|
|
Blockly.BlockSvg.INNER_BOTTOM_LEFT_CORNER =
|
|
'a ' + Blockly.BlockSvg.CORNER_RADIUS + ',' +
|
|
Blockly.BlockSvg.CORNER_RADIUS + ' 0 0,0 ' +
|
|
Blockly.BlockSvg.CORNER_RADIUS + ',' +
|
|
Blockly.BlockSvg.CORNER_RADIUS;
|
|
|
|
/**
|
|
* SVG path for an empty hexagonal input shape.
|
|
* @const
|
|
*/
|
|
Blockly.BlockSvg.INPUT_SHAPE_HEXAGONAL =
|
|
'M ' + 4 * Blockly.BlockSvg.GRID_UNIT + ',0 ' +
|
|
' h ' + 4 * Blockly.BlockSvg.GRID_UNIT +
|
|
' l ' + 4 * Blockly.BlockSvg.GRID_UNIT + ',' + 4 * Blockly.BlockSvg.GRID_UNIT +
|
|
' l ' + -4 * Blockly.BlockSvg.GRID_UNIT + ',' + 4 * Blockly.BlockSvg.GRID_UNIT +
|
|
' h ' + -4 * Blockly.BlockSvg.GRID_UNIT +
|
|
' l ' + -4 * Blockly.BlockSvg.GRID_UNIT + ',' + -4 * Blockly.BlockSvg.GRID_UNIT +
|
|
' l ' + 4 * Blockly.BlockSvg.GRID_UNIT + ',' + -4 * Blockly.BlockSvg.GRID_UNIT +
|
|
' z';
|
|
|
|
/**
|
|
* Width of empty boolean input shape.
|
|
* @const
|
|
*/
|
|
Blockly.BlockSvg.INPUT_SHAPE_HEXAGONAL_WIDTH = 12 * Blockly.BlockSvg.GRID_UNIT;
|
|
|
|
/**
|
|
* SVG path for an empty square input shape.
|
|
* @const
|
|
*/
|
|
Blockly.BlockSvg.INPUT_SHAPE_SQUARE =
|
|
Blockly.BlockSvg.TOP_LEFT_CORNER_START +
|
|
Blockly.BlockSvg.TOP_LEFT_CORNER +
|
|
' h ' + (12 * Blockly.BlockSvg.GRID_UNIT - 2 * Blockly.BlockSvg.CORNER_RADIUS) +
|
|
Blockly.BlockSvg.TOP_RIGHT_CORNER +
|
|
' v ' + (8 * Blockly.BlockSvg.GRID_UNIT - 2 * Blockly.BlockSvg.CORNER_RADIUS) +
|
|
Blockly.BlockSvg.BOTTOM_RIGHT_CORNER +
|
|
' h ' + (-12 * Blockly.BlockSvg.GRID_UNIT + 2 * Blockly.BlockSvg.CORNER_RADIUS) +
|
|
Blockly.BlockSvg.BOTTOM_LEFT_CORNER +
|
|
' z';
|
|
|
|
/**
|
|
* Width of empty square input shape.
|
|
* @const
|
|
*/
|
|
Blockly.BlockSvg.INPUT_SHAPE_SQUARE_WIDTH = 10 * Blockly.BlockSvg.GRID_UNIT;
|
|
|
|
/**
|
|
* SVG path for an empty round input shape.
|
|
* @const
|
|
*/
|
|
|
|
Blockly.BlockSvg.INPUT_SHAPE_ROUND =
|
|
'M ' + (4 * Blockly.BlockSvg.GRID_UNIT) + ',0' +
|
|
' h ' + (4 * Blockly.BlockSvg.GRID_UNIT) +
|
|
' a ' + (4 * Blockly.BlockSvg.GRID_UNIT) + ' ' +
|
|
(4 * Blockly.BlockSvg.GRID_UNIT) + ' 0 0 1 0 ' + (8 * Blockly.BlockSvg.GRID_UNIT) +
|
|
' h ' + (-4 * Blockly.BlockSvg.GRID_UNIT) +
|
|
' a ' + (4 * Blockly.BlockSvg.GRID_UNIT) + ' ' +
|
|
(4 * Blockly.BlockSvg.GRID_UNIT) + ' 0 0 1 0 -' + (8 * Blockly.BlockSvg.GRID_UNIT) +
|
|
' z';
|
|
|
|
/**
|
|
* Width of empty round input shape.
|
|
* @const
|
|
*/
|
|
Blockly.BlockSvg.INPUT_SHAPE_ROUND_WIDTH = 12 * Blockly.BlockSvg.GRID_UNIT;
|
|
|
|
/**
|
|
* Height of empty input shape.
|
|
* @const
|
|
*/
|
|
Blockly.BlockSvg.INPUT_SHAPE_HEIGHT = 8 * Blockly.BlockSvg.GRID_UNIT;
|
|
|
|
/**
|
|
* Height of user inputs
|
|
* @const
|
|
*/
|
|
Blockly.BlockSvg.FIELD_HEIGHT = 8 * Blockly.BlockSvg.GRID_UNIT;
|
|
|
|
/**
|
|
* Width of user inputs
|
|
* @const
|
|
*/
|
|
Blockly.BlockSvg.FIELD_WIDTH = 6 * Blockly.BlockSvg.GRID_UNIT;
|
|
|
|
/**
|
|
* Editable field padding (left/right of the text).
|
|
* @const
|
|
*/
|
|
Blockly.BlockSvg.EDITABLE_FIELD_PADDING = 6;
|
|
|
|
/**
|
|
* Square box field padding (left/right of the text).
|
|
* @const
|
|
*/
|
|
Blockly.BlockSvg.BOX_FIELD_PADDING = 2 * Blockly.BlockSvg.GRID_UNIT;
|
|
|
|
/**
|
|
* Drop-down arrow padding.
|
|
* @const
|
|
*/
|
|
Blockly.BlockSvg.DROPDOWN_ARROW_PADDING = 2 * Blockly.BlockSvg.GRID_UNIT;
|
|
|
|
/**
|
|
* Minimum width of user inputs during editing
|
|
* @const
|
|
*/
|
|
Blockly.BlockSvg.FIELD_WIDTH_MIN_EDIT = 8 * 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_HEIGHT;
|
|
|
|
/**
|
|
* Top padding of user inputs
|
|
* @const
|
|
*/
|
|
Blockly.BlockSvg.FIELD_TOP_PADDING = 0.5 * Blockly.BlockSvg.GRID_UNIT;
|
|
|
|
/**
|
|
* Corner radius of number inputs
|
|
* @const
|
|
*/
|
|
Blockly.BlockSvg.NUMBER_FIELD_CORNER_RADIUS = 4 * Blockly.BlockSvg.GRID_UNIT;
|
|
|
|
/**
|
|
* Corner radius of text inputs
|
|
* @const
|
|
*/
|
|
Blockly.BlockSvg.TEXT_FIELD_CORNER_RADIUS = 1 * Blockly.BlockSvg.GRID_UNIT;
|
|
|
|
/**
|
|
* Default radius for a field, in px.
|
|
* @const
|
|
*/
|
|
Blockly.BlockSvg.FIELD_DEFAULT_CORNER_RADIUS = 4 * Blockly.BlockSvg.GRID_UNIT;
|
|
|
|
/**
|
|
* Max text display length for a field (per-horizontal/vertical)
|
|
* @const
|
|
*/
|
|
Blockly.BlockSvg.MAX_DISPLAY_LENGTH = Infinity;
|
|
|
|
/**
|
|
* Minimum X of inputs and fields for blocks with a previous connection.
|
|
* Ensures that inputs will not overlap with the top notch of blocks.
|
|
* @const
|
|
*/
|
|
Blockly.BlockSvg.INPUT_AND_FIELD_MIN_X = 12 * Blockly.BlockSvg.GRID_UNIT;
|
|
|
|
/**
|
|
* Vertical padding around inline elements.
|
|
* @const
|
|
*/
|
|
Blockly.BlockSvg.INLINE_PADDING_Y = 1 * Blockly.BlockSvg.GRID_UNIT;
|
|
|
|
/**
|
|
* Point size of text field before animation. Must match size in CSS.
|
|
* See implementation in field_textinput.
|
|
*/
|
|
Blockly.BlockSvg.FIELD_TEXTINPUT_FONTSIZE_INITIAL = 12;
|
|
|
|
/**
|
|
* Point size of text field after animation.
|
|
* See implementation in field_textinput.
|
|
*/
|
|
Blockly.BlockSvg.FIELD_TEXTINPUT_FONTSIZE_FINAL = 12;
|
|
|
|
/**
|
|
* Whether text fields are allowed to expand past their truncated block size.
|
|
* @const{boolean}
|
|
*/
|
|
Blockly.BlockSvg.FIELD_TEXTINPUT_EXPAND_PAST_TRUNCATION = false;
|
|
|
|
/**
|
|
* Whether text fields should animate their positioning.
|
|
* @const{boolean}
|
|
*/
|
|
Blockly.BlockSvg.FIELD_TEXTINPUT_ANIMATE_POSITIONING = false;
|
|
|
|
/**
|
|
* 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, that side is extended by the padding specified.
|
|
* See also: `Blockly.BlockSvg.computeOutputPadding_`.
|
|
*/
|
|
Blockly.BlockSvg.SHAPE_IN_SHAPE_PADDING = {
|
|
1: { // Outer shape: hexagon.
|
|
0: 5 * Blockly.BlockSvg.GRID_UNIT, // Field in hexagon.
|
|
1: 2 * Blockly.BlockSvg.GRID_UNIT, // Hexagon in hexagon.
|
|
2: 5 * Blockly.BlockSvg.GRID_UNIT, // Round in hexagon.
|
|
3: 5 * Blockly.BlockSvg.GRID_UNIT // Square in hexagon.
|
|
},
|
|
2: { // Outer shape: round.
|
|
0: 3 * Blockly.BlockSvg.GRID_UNIT, // Field in round.
|
|
1: 3 * Blockly.BlockSvg.GRID_UNIT, // Hexagon in round.
|
|
2: 1 * Blockly.BlockSvg.GRID_UNIT, // Round in round.
|
|
3: 2 * Blockly.BlockSvg.GRID_UNIT // Square in round.
|
|
},
|
|
3: { // Outer shape: square.
|
|
0: 2 * Blockly.BlockSvg.GRID_UNIT, // Field in square.
|
|
1: 2 * Blockly.BlockSvg.GRID_UNIT, // Hexagon in square.
|
|
2: 2 * Blockly.BlockSvg.GRID_UNIT, // Round in square.
|
|
3: 2 * Blockly.BlockSvg.GRID_UNIT // Square in square.
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Corner radius of the hat on the define block.
|
|
* @const
|
|
*/
|
|
Blockly.BlockSvg.DEFINE_HAT_CORNER_RADIUS = 5 * Blockly.BlockSvg.GRID_UNIT;
|
|
|
|
/**
|
|
* SVG path for drawing the rounded top-left corner.
|
|
* @const
|
|
*/
|
|
Blockly.BlockSvg.TOP_LEFT_CORNER_DEFINE_HAT =
|
|
'a ' + Blockly.BlockSvg.DEFINE_HAT_CORNER_RADIUS + ',' +
|
|
Blockly.BlockSvg.DEFINE_HAT_CORNER_RADIUS + ' 0 0,1 ' +
|
|
Blockly.BlockSvg.DEFINE_HAT_CORNER_RADIUS + ',-' +
|
|
Blockly.BlockSvg.DEFINE_HAT_CORNER_RADIUS;
|
|
|
|
/**
|
|
* SVG path for drawing the rounded top-left corner.
|
|
* @const
|
|
*/
|
|
Blockly.BlockSvg.TOP_RIGHT_CORNER_DEFINE_HAT =
|
|
'a ' + Blockly.BlockSvg.DEFINE_HAT_CORNER_RADIUS + ',' +
|
|
Blockly.BlockSvg.DEFINE_HAT_CORNER_RADIUS + ' 0 0,1 ' +
|
|
Blockly.BlockSvg.DEFINE_HAT_CORNER_RADIUS + ',' +
|
|
Blockly.BlockSvg.DEFINE_HAT_CORNER_RADIUS;
|
|
|
|
/**
|
|
* Padding on the right side of the internal block on the define block.
|
|
* @const
|
|
*/
|
|
Blockly.BlockSvg.DEFINE_BLOCK_PADDING_RIGHT = 2 * Blockly.BlockSvg.GRID_UNIT;
|
|
|
|
/**
|
|
* Change the colour of a block.
|
|
*/
|
|
Blockly.BlockSvg.prototype.updateColour = function() {
|
|
var strokeColour = this.getColourTertiary();
|
|
var renderShadowed = this.isShadow() &&
|
|
!Blockly.scratchBlocksUtils.isShadowArgumentReporter(this);
|
|
|
|
if (renderShadowed && this.parentBlock_) {
|
|
// Pull shadow block stroke colour from parent block's tertiary if possible.
|
|
strokeColour = this.parentBlock_.getColourTertiary();
|
|
// Special case: if we contain a colour field, set to a special stroke colour.
|
|
if (this.inputList[0] &&
|
|
this.inputList[0].fieldRow[0] &&
|
|
(this.inputList[0].fieldRow[0] instanceof Blockly.FieldColour ||
|
|
this.inputList[0].fieldRow[0] instanceof Blockly.FieldColourSlider)) {
|
|
strokeColour = Blockly.Colours.colourPickerStroke;
|
|
}
|
|
}
|
|
|
|
// Render block stroke
|
|
this.svgPath_.setAttribute('stroke', strokeColour);
|
|
|
|
// Render block fill
|
|
if (this.isGlowingBlock_ || renderShadowed) {
|
|
// Use the block's shadow colour if possible.
|
|
if (this.getShadowColour()) {
|
|
var fillColour = this.getShadowColour();
|
|
} else {
|
|
var fillColour = this.getColourSecondary();
|
|
}
|
|
} else {
|
|
var fillColour = this.getColour();
|
|
}
|
|
this.svgPath_.setAttribute('fill', fillColour);
|
|
|
|
// Render opacity
|
|
this.svgPath_.setAttribute('fill-opacity', this.getOpacity());
|
|
|
|
// Update colours of input shapes.
|
|
for (var i = 0, input; input = this.inputList[i]; i++) {
|
|
if (input.outlinePath) {
|
|
input.outlinePath.setAttribute('fill', this.getColourTertiary());
|
|
}
|
|
}
|
|
|
|
// Render icon(s) if applicable
|
|
var icons = this.getIcons();
|
|
for (var i = 0; i < icons.length; i++) {
|
|
icons[i].updateColour();
|
|
}
|
|
|
|
// Bump every dropdown to change its colour.
|
|
for (var x = 0, input; input = this.inputList[x]; x++) {
|
|
for (var y = 0, field; field = input.fieldRow[y]; y++) {
|
|
field.setText(null);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Visual effect to show that if the dragging block is dropped, this block will
|
|
* be replaced. If a shadow block it will disappear. Otherwise it will bump.
|
|
* @param {boolean} add True if highlighting should be added.
|
|
*/
|
|
Blockly.BlockSvg.prototype.highlightForReplacement = function(add) {
|
|
if (add) {
|
|
var replacementGlowFilterId = this.workspace.options.replacementGlowFilterId
|
|
|| 'blocklyReplacementGlowFilter';
|
|
this.svgPath_.setAttribute('filter', 'url(#' + replacementGlowFilterId + ')');
|
|
Blockly.utils.addClass(/** @type {!Element} */ (this.svgGroup_),
|
|
'blocklyReplaceable');
|
|
} else {
|
|
this.svgPath_.removeAttribute('filter');
|
|
Blockly.utils.removeClass(/** @type {!Element} */ (this.svgGroup_),
|
|
'blocklyReplaceable');
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Visual effect to show that if the dragging block is dropped it will connect
|
|
* to this input.
|
|
* @param {Blockly.Connection} conn The connection on the input to highlight.
|
|
* @param {boolean} add True if highlighting should be added.
|
|
*/
|
|
Blockly.BlockSvg.prototype.highlightShapeForInput = function(conn, add) {
|
|
var input = this.getInputWithConnection(conn);
|
|
if (!input) {
|
|
throw 'No input found for the connection';
|
|
}
|
|
if (!input.outlinePath) {
|
|
return;
|
|
}
|
|
if (add) {
|
|
var replacementGlowFilterId = this.workspace.options.replacementGlowFilterId
|
|
|| 'blocklyReplacementGlowFilter';
|
|
input.outlinePath.setAttribute('filter',
|
|
'url(#' + replacementGlowFilterId + ')');
|
|
Blockly.utils.addClass(/** @type {!Element} */ (this.svgGroup_),
|
|
'blocklyReplaceable');
|
|
} else {
|
|
input.outlinePath.removeAttribute('filter');
|
|
Blockly.utils.removeClass(/** @type {!Element} */ (this.svgGroup_),
|
|
'blocklyReplaceable');
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Returns a bounding box describing the dimensions of this block
|
|
* and any blocks stacked below it.
|
|
* @return {!{height: number, width: number}} Object with height and width properties.
|
|
*/
|
|
Blockly.BlockSvg.prototype.getHeightWidth = function() {
|
|
var height = this.height;
|
|
var width = this.width;
|
|
// Recursively add size of subsequent blocks.
|
|
var nextBlock = this.getNextBlock();
|
|
if (nextBlock) {
|
|
var nextHeightWidth = nextBlock.getHeightWidth();
|
|
height += nextHeightWidth.height;
|
|
height -= Blockly.BlockSvg.NOTCH_HEIGHT; // Exclude height of connected notch.
|
|
width = Math.max(width, nextHeightWidth.width);
|
|
}
|
|
return {height: height, width: width};
|
|
};
|
|
|
|
/**
|
|
* Render the block.
|
|
* Lays out and reflows a block based on its contents and settings.
|
|
* @param {boolean=} opt_bubble If false, just render this block.
|
|
* If true, also render block's parent, grandparent, etc. Defaults to true.
|
|
*/
|
|
Blockly.BlockSvg.prototype.render = function(opt_bubble) {
|
|
Blockly.Field.startCache();
|
|
this.rendered = true;
|
|
|
|
var cursorX = Blockly.BlockSvg.SEP_SPACE_X;
|
|
if (this.RTL) {
|
|
cursorX = -cursorX;
|
|
}
|
|
// Move the icons into position.
|
|
var icons = this.getIcons();
|
|
var scratchCommentIcon = null;
|
|
for (var i = 0; i < icons.length; i++) {
|
|
if (icons[i] instanceof Blockly.ScratchBlockComment) {
|
|
// Don't render scratch block comment icon until
|
|
// after the inputs
|
|
scratchCommentIcon = icons[i];
|
|
} else {
|
|
cursorX = icons[i].renderIcon(cursorX);
|
|
}
|
|
}
|
|
cursorX += this.RTL ?
|
|
Blockly.BlockSvg.SEP_SPACE_X : -Blockly.BlockSvg.SEP_SPACE_X;
|
|
// If there are no icons, cursorX will be 0, otherwise it will be the
|
|
// width that the first label needs to move over by.
|
|
|
|
// If this is an extension reporter block, add a horizontal offset.
|
|
if (this.isScratchExtension && this.outputConnection) {
|
|
cursorX += this.RTL ?
|
|
-Blockly.BlockSvg.GRID_UNIT : Blockly.BlockSvg.GRID_UNIT;
|
|
}
|
|
|
|
var inputRows = this.renderCompute_(cursorX);
|
|
this.renderDraw_(cursorX, inputRows);
|
|
this.renderMoveConnections_();
|
|
|
|
this.renderClassify_();
|
|
|
|
// Position the Scratch Block Comment Icon at the end of the block
|
|
if (scratchCommentIcon) {
|
|
var iconX = this.RTL ? -inputRows.rightEdge : inputRows.rightEdge;
|
|
var inputMarginY = inputRows[0].height / 2;
|
|
scratchCommentIcon.renderIcon(iconX, inputMarginY);
|
|
}
|
|
|
|
if (opt_bubble !== false) {
|
|
// Render all blocks above this one (propagate a reflow).
|
|
var parentBlock = this.getParent();
|
|
if (parentBlock) {
|
|
parentBlock.render(true);
|
|
} else {
|
|
// Top-most block. Fire an event to allow scrollbars to resize.
|
|
Blockly.resizeSvgContents(this.workspace);
|
|
}
|
|
}
|
|
Blockly.Field.stopCache();
|
|
};
|
|
|
|
/**
|
|
* Render a list of fields starting at the specified location.
|
|
* @param {!Array.<!Blockly.Field>} fieldList List of fields.
|
|
* @param {number} cursorX X-coordinate to start the fields.
|
|
* @param {number} cursorY Y-coordinate around which fields are centered.
|
|
* @return {number} X-coordinate of the end of the field row (plus a gap).
|
|
* @private
|
|
*/
|
|
Blockly.BlockSvg.prototype.renderFields_ = function(fieldList, cursorX,
|
|
cursorY) {
|
|
if (this.RTL) {
|
|
cursorX = -cursorX;
|
|
}
|
|
for (var t = 0, field; field = fieldList[t]; t++) {
|
|
var root = field.getSvgRoot();
|
|
if (!root) {
|
|
continue;
|
|
}
|
|
// In blocks with a notch, fields should be bumped to a min X,
|
|
// to avoid overlapping with the notch. Label and image fields are
|
|
// excluded.
|
|
if (this.previousConnection && !(field instanceof Blockly.FieldLabel) &&
|
|
!(field instanceof Blockly.FieldImage)) {
|
|
cursorX = this.RTL ?
|
|
Math.min(cursorX, -Blockly.BlockSvg.INPUT_AND_FIELD_MIN_X) :
|
|
Math.max(cursorX, Blockly.BlockSvg.INPUT_AND_FIELD_MIN_X);
|
|
}
|
|
// Offset the field upward by half its height.
|
|
// This vertically centers the fields around cursorY.
|
|
var yOffset = -field.getSize().height / 2;
|
|
|
|
// If this is an extension block, and this field is the first field, and
|
|
// it is an image field, and this block has a previous connection, bump
|
|
// the image down by one grid unit to align it vertically.
|
|
if (this.isScratchExtension && (field === this.inputList[0].fieldRow[0])
|
|
&& (field instanceof Blockly.FieldImage) && this.previousConnection) {
|
|
yOffset += Blockly.BlockSvg.GRID_UNIT;
|
|
}
|
|
|
|
// If this is an extension hat block, adjust the height of the vertical
|
|
// separator without adjusting the field height. The effect is to move
|
|
// the bottom end of the line up one grid unit.
|
|
if (this.isScratchExtension &&
|
|
!this.previousConnection && this.nextConnection &&
|
|
field instanceof Blockly.FieldVerticalSeparator) {
|
|
field.setLineHeight(Blockly.BlockSvg.ICON_SEPARATOR_HEIGHT -
|
|
Blockly.BlockSvg.GRID_UNIT);
|
|
}
|
|
|
|
var translateX, translateY;
|
|
var scale = '';
|
|
if (this.RTL) {
|
|
cursorX -= field.renderSep + field.renderWidth;
|
|
translateX = cursorX;
|
|
translateY = cursorY + yOffset;
|
|
if (field.renderWidth) {
|
|
cursorX -= Blockly.BlockSvg.SEP_SPACE_X;
|
|
}
|
|
} else {
|
|
translateX = cursorX + field.renderSep;
|
|
translateY = cursorY + yOffset;
|
|
if (field.renderWidth) {
|
|
cursorX += field.renderSep + field.renderWidth +
|
|
Blockly.BlockSvg.SEP_SPACE_X;
|
|
}
|
|
}
|
|
if (this.RTL &&
|
|
field instanceof Blockly.FieldImage &&
|
|
field.getFlipRTL()) {
|
|
scale = 'scale(-1 1)';
|
|
translateX += field.renderWidth;
|
|
}
|
|
root.setAttribute('transform',
|
|
'translate(' + translateX + ', ' + translateY + ') ' + scale);
|
|
|
|
// Fields are invisible on insertion marker.
|
|
if (this.isInsertionMarker()) {
|
|
root.setAttribute('display', 'none');
|
|
}
|
|
}
|
|
return this.RTL ? -cursorX : cursorX;
|
|
};
|
|
|
|
/**
|
|
* Computes the height and widths for each row and field.
|
|
* @param {number} iconWidth Offset of first row due to icons.
|
|
* @return {!Array.<!Array.<!Object>>} 2D array of objects, each containing
|
|
* position information.
|
|
* @private
|
|
*/
|
|
Blockly.BlockSvg.prototype.renderCompute_ = function(iconWidth) {
|
|
var inputList = this.inputList;
|
|
var inputRows = [];
|
|
// Block will be drawn from 0 (left edge) to rightEdge, in px.
|
|
inputRows.rightEdge = 0;
|
|
// Drawn from 0 to bottomEdge vertically.
|
|
inputRows.bottomEdge = 0;
|
|
var fieldValueWidth = 0; // Width of longest external value field.
|
|
var fieldStatementWidth = 0; // Width of longest statement field.
|
|
var hasValue = false;
|
|
var hasStatement = false;
|
|
var hasDummy = false;
|
|
var lastType = undefined;
|
|
|
|
// Previously created row, for special-casing row heights on C- and E- shaped blocks.
|
|
var previousRow;
|
|
for (var i = 0, input; input = inputList[i]; i++) {
|
|
if (!input.isVisible()) {
|
|
continue;
|
|
}
|
|
var isSecondInputOnProcedure = this.type == 'procedures_definition' &&
|
|
lastType && lastType == Blockly.NEXT_STATEMENT;
|
|
var row;
|
|
// Don't create a new row for the second dummy input on a procedure block.
|
|
// See github.com/LLK/scratch-blocks/issues/1658
|
|
// In all other cases, statement and value inputs catch all preceding dummy
|
|
// inputs, and cause a line break before following inputs.
|
|
if (!isSecondInputOnProcedure &&
|
|
(!lastType || lastType == Blockly.NEXT_STATEMENT ||
|
|
input.type == Blockly.NEXT_STATEMENT)) {
|
|
lastType = input.type;
|
|
row = this.createRowForInput_(input);
|
|
inputRows.push(row);
|
|
} else {
|
|
row = inputRows[inputRows.length - 1];
|
|
}
|
|
row.push(input);
|
|
|
|
// Compute minimum dimensions for this input.
|
|
input.renderHeight = this.computeInputHeight_(input, row, previousRow);
|
|
input.renderWidth = this.computeInputWidth_(input);
|
|
|
|
// If the input is a statement input, determine if a notch
|
|
// should be drawn at the inner bottom of the C.
|
|
row.statementNotchAtBottom = true;
|
|
if (input.connection && input.connection.type === Blockly.NEXT_STATEMENT) {
|
|
var linkedBlock = input.connection.targetBlock();
|
|
if (linkedBlock && !linkedBlock.lastConnectionInStack()) {
|
|
row.statementNotchAtBottom = false;
|
|
}
|
|
}
|
|
|
|
// Expand input size.
|
|
if (input.connection) {
|
|
var linkedBlock = input.connection.targetBlock();
|
|
var paddedHeight = 0;
|
|
var paddedWidth = 0;
|
|
if (linkedBlock) {
|
|
// A block is connected to the input - use its size.
|
|
var bBox = linkedBlock.getHeightWidth();
|
|
paddedHeight = bBox.height;
|
|
paddedWidth = bBox.width;
|
|
} else {
|
|
// No block connected - use the size of the rendered empty input shape.
|
|
paddedHeight = Blockly.BlockSvg.INPUT_SHAPE_HEIGHT;
|
|
}
|
|
if (input.connection.type === Blockly.INPUT_VALUE) {
|
|
paddedHeight += 2 * Blockly.BlockSvg.INLINE_PADDING_Y;
|
|
}
|
|
if (input.connection.type === Blockly.NEXT_STATEMENT) {
|
|
// Subtract height of notch, only if the last block in the stack has a next connection.
|
|
if (row.statementNotchAtBottom) {
|
|
paddedHeight -= Blockly.BlockSvg.NOTCH_HEIGHT;
|
|
}
|
|
}
|
|
input.renderHeight = Math.max(input.renderHeight, paddedHeight);
|
|
input.renderWidth = Math.max(input.renderWidth, paddedWidth);
|
|
}
|
|
row.height = Math.max(row.height, input.renderHeight);
|
|
input.fieldWidth = 0;
|
|
if (inputRows.length == 1) {
|
|
// The first row gets shifted to accommodate any icons.
|
|
input.fieldWidth += this.RTL ? -iconWidth : iconWidth;
|
|
}
|
|
var previousFieldEditable = false;
|
|
for (var j = 0, field; field = input.fieldRow[j]; j++) {
|
|
if (j != 0) {
|
|
input.fieldWidth += Blockly.BlockSvg.SEP_SPACE_X;
|
|
}
|
|
// Get the dimensions of the field.
|
|
var fieldSize = field.getSize();
|
|
field.renderWidth = fieldSize.width;
|
|
field.renderSep = (previousFieldEditable && field.EDITABLE) ?
|
|
Blockly.BlockSvg.SEP_SPACE_X : 0;
|
|
// See github.com/LLK/scratch-blocks/issues/1658
|
|
if (!isSecondInputOnProcedure) {
|
|
input.fieldWidth += field.renderWidth + field.renderSep;
|
|
}
|
|
row.height = Math.max(row.height, fieldSize.height);
|
|
previousFieldEditable = field.EDITABLE;
|
|
}
|
|
|
|
if (row.type != Blockly.BlockSvg.INLINE) {
|
|
if (row.type == Blockly.NEXT_STATEMENT) {
|
|
hasStatement = true;
|
|
fieldStatementWidth = Math.max(fieldStatementWidth, input.fieldWidth);
|
|
} else {
|
|
if (row.type == Blockly.INPUT_VALUE) {
|
|
hasValue = true;
|
|
} else if (row.type == Blockly.DUMMY_INPUT) {
|
|
hasDummy = true;
|
|
}
|
|
fieldValueWidth = Math.max(fieldValueWidth, input.fieldWidth);
|
|
}
|
|
}
|
|
previousRow = row;
|
|
}
|
|
// Compute padding for output blocks.
|
|
// Data is attached to the row.
|
|
this.computeOutputPadding_(inputRows);
|
|
// Compute the statement edge.
|
|
// This is the width of a block where statements are nested.
|
|
inputRows.statementEdge = Blockly.BlockSvg.STATEMENT_INPUT_EDGE_WIDTH +
|
|
fieldStatementWidth;
|
|
|
|
// Compute the preferred right edge.
|
|
inputRows.rightEdge = this.computeRightEdge_(inputRows.rightEdge,
|
|
hasStatement);
|
|
|
|
// Bottom edge is sum of row heights
|
|
for (var i = 0; i < inputRows.length; i++) {
|
|
inputRows.bottomEdge += inputRows[i].height;
|
|
}
|
|
|
|
inputRows.hasValue = hasValue;
|
|
inputRows.hasStatement = hasStatement;
|
|
inputRows.hasDummy = hasDummy;
|
|
return inputRows;
|
|
};
|
|
|
|
/**
|
|
* Compute the minimum width of this input based on the connection type and
|
|
* outputs.
|
|
* @param {!Blockly.Input} input The input to measure.
|
|
* @return {number} the computed width of this input.
|
|
* @private
|
|
*/
|
|
Blockly.BlockSvg.prototype.computeInputWidth_ = function(input) {
|
|
// Empty input shape widths.
|
|
if (input.type == Blockly.INPUT_VALUE &&
|
|
(!input.connection || !input.connection.isConnected())) {
|
|
switch (input.connection.getOutputShape()) {
|
|
case Blockly.OUTPUT_SHAPE_SQUARE:
|
|
return Blockly.BlockSvg.INPUT_SHAPE_SQUARE_WIDTH;
|
|
case Blockly.OUTPUT_SHAPE_ROUND:
|
|
return Blockly.BlockSvg.INPUT_SHAPE_ROUND_WIDTH;
|
|
case Blockly.OUTPUT_SHAPE_HEXAGONAL:
|
|
return Blockly.BlockSvg.INPUT_SHAPE_HEXAGONAL_WIDTH;
|
|
default:
|
|
return 0;
|
|
}
|
|
} else {
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Compute the minimum height of this input.
|
|
* @param {!Blockly.Input} input The input to measure.
|
|
* @param {!Object} row The row of the block that is currently being measured.
|
|
* @param {!Object} previousRow The previous row of the block, which was just
|
|
* measured.
|
|
* @return {number} the computed height of this input.
|
|
* @private
|
|
*/
|
|
Blockly.BlockSvg.prototype.computeInputHeight_ = function(input, row,
|
|
previousRow) {
|
|
if (this.inputList.length === 1 && this.outputConnection &&
|
|
(this.isShadow() &&
|
|
!Blockly.scratchBlocksUtils.isShadowArgumentReporter(this))) {
|
|
// "Lone" field blocks are smaller.
|
|
return Blockly.BlockSvg.MIN_BLOCK_Y_SINGLE_FIELD_OUTPUT;
|
|
} else if (this.outputConnection) {
|
|
// If this is an extension reporter block, make it taller.
|
|
if (this.isScratchExtension) {
|
|
return Blockly.BlockSvg.MIN_BLOCK_Y_REPORTER + 2 * Blockly.BlockSvg.GRID_UNIT;
|
|
}
|
|
// All other reporters.
|
|
return Blockly.BlockSvg.MIN_BLOCK_Y_REPORTER;
|
|
} else if (row.type == Blockly.NEXT_STATEMENT) {
|
|
// Statement input.
|
|
return Blockly.BlockSvg.MIN_STATEMENT_INPUT_HEIGHT;
|
|
} else if (previousRow && previousRow.type == Blockly.NEXT_STATEMENT) {
|
|
// Extra row for below statement input.
|
|
return Blockly.BlockSvg.EXTRA_STATEMENT_ROW_Y;
|
|
} else {
|
|
// If this is an extension block, and it has a previous connection,
|
|
// make it taller.
|
|
if (this.isScratchExtension && this.previousConnection) {
|
|
return Blockly.BlockSvg.MIN_BLOCK_Y + 2 * Blockly.BlockSvg.GRID_UNIT;
|
|
}
|
|
// All other blocks.
|
|
return Blockly.BlockSvg.MIN_BLOCK_Y;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Create a row for an input and associated fields.
|
|
* @param {!Blockly.Input} input The input that the row is based on.
|
|
* @return {!Object} The new row, with the correct type and default sizing info.
|
|
*/
|
|
Blockly.BlockSvg.prototype.createRowForInput_ = function(input) {
|
|
// Create new row.
|
|
var row = [];
|
|
if (input.type != Blockly.NEXT_STATEMENT) {
|
|
row.type = Blockly.BlockSvg.INLINE;
|
|
} else {
|
|
row.type = input.type;
|
|
}
|
|
row.height = 0;
|
|
// Default padding for a block: same as separators between fields/inputs.
|
|
row.paddingStart = Blockly.BlockSvg.SEP_SPACE_X;
|
|
row.paddingEnd = Blockly.BlockSvg.SEP_SPACE_X;
|
|
return row;
|
|
};
|
|
|
|
/**
|
|
* Compute the preferred right edge of the block.
|
|
* @param {number} curEdge The previously calculated right edge.
|
|
* @param {boolean} hasStatement Whether this block has a statement input.
|
|
* @return {number} The preferred right edge of the block.
|
|
*/
|
|
Blockly.BlockSvg.prototype.computeRightEdge_ = function(curEdge, hasStatement) {
|
|
var edge = curEdge;
|
|
if (this.previousConnection || this.nextConnection) {
|
|
// Blocks with notches
|
|
edge = Math.max(edge, Blockly.BlockSvg.MIN_BLOCK_X);
|
|
} else if (this.outputConnection) {
|
|
if (this.isShadow() &&
|
|
!Blockly.scratchBlocksUtils.isShadowArgumentReporter(this)) {
|
|
// Single-fields
|
|
edge = Math.max(edge, Blockly.BlockSvg.MIN_BLOCK_X_SHADOW_OUTPUT);
|
|
} else {
|
|
// Reporters
|
|
edge = Math.max(edge, Blockly.BlockSvg.MIN_BLOCK_X_OUTPUT);
|
|
}
|
|
}
|
|
if (hasStatement) {
|
|
// Statement blocks (C- or E- shaped) have a longer minimum width.
|
|
edge = Math.max(edge, Blockly.BlockSvg.MIN_BLOCK_X_WITH_STATEMENT);
|
|
}
|
|
|
|
// Ensure insertion markers are at least insertionMarkerMinWidth_ wide.
|
|
if (this.insertionMarkerMinWidth_ > 0) {
|
|
edge = Math.max(edge, this.insertionMarkerMinWidth_);
|
|
}
|
|
return edge;
|
|
};
|
|
|
|
/**
|
|
* For a block with output,
|
|
* determine start and end padding, based on connected inputs.
|
|
* Padding will depend on the shape of the output, the shape of the input,
|
|
* and possibly the size of the input.
|
|
* @param {!Array.<!Array.<!Object>>} inputRows Partially calculated rows.
|
|
*/
|
|
Blockly.BlockSvg.prototype.computeOutputPadding_ = function(inputRows) {
|
|
// Only apply to blocks with outputs and not single fields (shadows).
|
|
if (!this.getOutputShape() || !this.outputConnection ||
|
|
(this.isShadow() &&
|
|
!Blockly.scratchBlocksUtils.isShadowArgumentReporter(this))) {
|
|
return;
|
|
}
|
|
// Blocks with outputs must have single row to be padded.
|
|
if (inputRows.length > 1) {
|
|
return;
|
|
}
|
|
var row = inputRows[0];
|
|
var shape = this.getOutputShape();
|
|
// Reset any padding: it's about to be set.
|
|
row.paddingStart = 0;
|
|
row.paddingEnd = 0;
|
|
// Start row padding: based on first input or first field.
|
|
var firstInput = row[0];
|
|
var firstField = firstInput.fieldRow[0];
|
|
var otherShape;
|
|
// In checking the left/start side, a field takes precedence over any input.
|
|
// That's because a field will be rendered before any value input.
|
|
if (firstField) {
|
|
otherShape = 0; // Field comes first in the row.
|
|
} else {
|
|
// Value input comes first in the row.
|
|
var inputConnection = firstInput.connection;
|
|
if (!inputConnection.targetConnection) {
|
|
// Not connected: use the drawn shape.
|
|
otherShape = inputConnection.getOutputShape();
|
|
} else {
|
|
// Connected: use the connected block's output shape.
|
|
otherShape = inputConnection.targetConnection.getSourceBlock().getOutputShape();
|
|
}
|
|
// Special case for hexagonal output: if the connection is larger height
|
|
// than a standard reporter, add some start padding.
|
|
// https://github.com/LLK/scratch-blocks/issues/376
|
|
if (shape == Blockly.OUTPUT_SHAPE_HEXAGONAL &&
|
|
otherShape != Blockly.OUTPUT_SHAPE_HEXAGONAL) {
|
|
var deltaHeight = firstInput.renderHeight - Blockly.BlockSvg.MIN_BLOCK_Y_REPORTER;
|
|
// One grid unit per level of nesting.
|
|
row.paddingStart += deltaHeight / 2;
|
|
}
|
|
}
|
|
row.paddingStart += Blockly.BlockSvg.SHAPE_IN_SHAPE_PADDING[shape][otherShape];
|
|
// End row padding: based on last input or last field.
|
|
var lastInput = row[row.length - 1];
|
|
// In checking the right/end side, any value input takes precedence over any field.
|
|
// That's because fields are rendered before inputs...the last item
|
|
// in the row will be an input, if one exists.
|
|
if (lastInput.connection) {
|
|
// Value input last in the row.
|
|
var inputConnection = lastInput.connection;
|
|
if (!inputConnection.targetConnection) {
|
|
// Not connected: use the drawn shape.
|
|
otherShape = inputConnection.getOutputShape();
|
|
} else {
|
|
// Connected: use the connected block's output shape.
|
|
otherShape = inputConnection.targetConnection.getSourceBlock().getOutputShape();
|
|
}
|
|
// Special case for hexagonal output: if the connection is larger height
|
|
// than a standard reporter, add some end padding.
|
|
// https://github.com/LLK/scratch-blocks/issues/376
|
|
if (shape == Blockly.OUTPUT_SHAPE_HEXAGONAL &&
|
|
otherShape != Blockly.OUTPUT_SHAPE_HEXAGONAL) {
|
|
var deltaHeight = lastInput.renderHeight - Blockly.BlockSvg.MIN_BLOCK_Y_REPORTER;
|
|
// One grid unit per level of nesting.
|
|
row.paddingEnd += deltaHeight / 2;
|
|
}
|
|
} else {
|
|
// No input in this row - mark as field.
|
|
otherShape = 0;
|
|
}
|
|
row.paddingEnd += Blockly.BlockSvg.SHAPE_IN_SHAPE_PADDING[shape][otherShape];
|
|
};
|
|
|
|
/**
|
|
* Draw the path of the block.
|
|
* Move the fields to the correct locations.
|
|
* @param {number} iconWidth Offset of first row due to icons.
|
|
* @param {!Array.<!Array.<!Object>>} inputRows 2D array of objects, each
|
|
* containing position information.
|
|
* @private
|
|
*/
|
|
Blockly.BlockSvg.prototype.renderDraw_ = function(iconWidth, inputRows) {
|
|
this.startHat_ = false;
|
|
// Should the top left corners be rounded or square?
|
|
// Currently, it is squared only if it's a hat.
|
|
this.squareTopLeftCorner_ = false;
|
|
if (!this.outputConnection && !this.previousConnection) {
|
|
// No output or previous connection.
|
|
this.squareTopLeftCorner_ = true;
|
|
this.startHat_ = true;
|
|
inputRows.rightEdge = Math.max(inputRows.rightEdge, 100);
|
|
}
|
|
|
|
// Amount of space to skip drawing the top and bottom,
|
|
// to make room for the left and right to draw shapes (curves or angles).
|
|
this.edgeShapeWidth_ = 0;
|
|
this.edgeShape_ = null;
|
|
if (this.outputConnection) {
|
|
// Width of the curve/pointy-curve
|
|
var shape = this.getOutputShape();
|
|
if (shape === Blockly.OUTPUT_SHAPE_HEXAGONAL || shape === Blockly.OUTPUT_SHAPE_ROUND) {
|
|
this.edgeShapeWidth_ = inputRows.bottomEdge / 2;
|
|
this.edgeShape_ = shape;
|
|
this.squareTopLeftCorner_ = true;
|
|
}
|
|
}
|
|
|
|
// Assemble the block's path.
|
|
var steps = [];
|
|
|
|
this.renderDrawTop_(steps, inputRows.rightEdge);
|
|
var cursorY = this.renderDrawRight_(steps, inputRows, iconWidth);
|
|
this.renderDrawBottom_(steps, cursorY);
|
|
this.renderDrawLeft_(steps);
|
|
|
|
var pathString = steps.join(' ');
|
|
this.svgPath_.setAttribute('d', pathString);
|
|
|
|
if (this.RTL) {
|
|
// Mirror the block's path.
|
|
// This is awesome.
|
|
this.svgPath_.setAttribute('transform', 'scale(-1 1)');
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Give the block an attribute 'data-shapes' that lists its shape[s], and an
|
|
* attribute 'data-category' with its category.
|
|
* @private
|
|
*/
|
|
Blockly.BlockSvg.prototype.renderClassify_ = function() {
|
|
var shapes = [];
|
|
|
|
if (this.outputConnection) {
|
|
if (this.isShadow_) {
|
|
shapes.push('argument');
|
|
} else {
|
|
shapes.push('reporter');
|
|
}
|
|
if (this.edgeShape_ === Blockly.OUTPUT_SHAPE_HEXAGONAL) {
|
|
shapes.push('boolean');
|
|
} else if (this.edgeShape_ === Blockly.OUTPUT_SHAPE_ROUND) {
|
|
shapes.push('round');
|
|
}
|
|
} else {
|
|
// count the number of statement inputs
|
|
var inputList = this.inputList;
|
|
var statementCount = 0;
|
|
for (var i = 0, input; input = inputList[i]; i++) {
|
|
if (input.connection && input.connection.type === Blockly.NEXT_STATEMENT) {
|
|
statementCount++;
|
|
}
|
|
}
|
|
|
|
if (statementCount) {
|
|
shapes.push('c-block');
|
|
shapes.push('c-' + statementCount);
|
|
}
|
|
if (this.startHat_) {
|
|
shapes.push('hat'); // c-block+hats are possible (e.x. reprter procedures)
|
|
} else if (!statementCount) {
|
|
shapes.push('stack'); //only call it "stack" if it's not a c-block
|
|
}
|
|
if (!this.nextConnection) {
|
|
shapes.push('end');
|
|
}
|
|
}
|
|
|
|
this.svgGroup_.setAttribute('data-shapes', shapes.join(' '));
|
|
|
|
if (this.getCategory()) {
|
|
this.svgGroup_.setAttribute('data-category', this.getCategory());
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Render the top edge of the block.
|
|
* @param {!Array.<string>} steps Path of block outline.
|
|
* @param {number} rightEdge Minimum width of block.
|
|
* @private
|
|
*/
|
|
Blockly.BlockSvg.prototype.renderDrawTop_ = function(steps, rightEdge) {
|
|
if (this.type == Blockly.PROCEDURES_DEFINITION_BLOCK_TYPE) {
|
|
steps.push('m 0, 0');
|
|
steps.push(Blockly.BlockSvg.TOP_LEFT_CORNER_DEFINE_HAT);
|
|
} else {
|
|
// Position the cursor at the top-left starting point.
|
|
if (this.squareTopLeftCorner_) {
|
|
steps.push('m 0,0');
|
|
if (this.startHat_) {
|
|
steps.push(Blockly.BlockSvg.START_HAT_PATH);
|
|
}
|
|
// Skip space for the output shape
|
|
if (this.edgeShapeWidth_) {
|
|
steps.push('m ' + this.edgeShapeWidth_ + ',0');
|
|
}
|
|
} else {
|
|
steps.push(Blockly.BlockSvg.TOP_LEFT_CORNER_START);
|
|
// Top-left rounded corner.
|
|
steps.push(Blockly.BlockSvg.TOP_LEFT_CORNER);
|
|
}
|
|
|
|
// Top edge.
|
|
if (this.previousConnection) {
|
|
// Space before the notch
|
|
steps.push('H', Blockly.BlockSvg.NOTCH_START_PADDING);
|
|
steps.push(Blockly.BlockSvg.NOTCH_PATH_LEFT);
|
|
// Create previous block connection.
|
|
var connectionX = (this.RTL ?
|
|
-Blockly.BlockSvg.NOTCH_WIDTH : Blockly.BlockSvg.NOTCH_WIDTH);
|
|
this.previousConnection.setOffsetInBlock(connectionX, 0);
|
|
}
|
|
}
|
|
this.width = rightEdge;
|
|
};
|
|
|
|
/**
|
|
* Render the right edge of the block.
|
|
* @param {!Array.<string>} steps Path of block outline.
|
|
* @param {!Array.<!Array.<!Object>>} inputRows 2D array of objects, each
|
|
* containing position information.
|
|
* @param {number} iconWidth Offset of first row due to icons.
|
|
* @return {number} Height of block.
|
|
* @private
|
|
*/
|
|
Blockly.BlockSvg.prototype.renderDrawRight_ = function(steps,
|
|
inputRows, iconWidth) {
|
|
var cursorX = 0;
|
|
var cursorY = 0;
|
|
var connectionX, connectionY;
|
|
for (var y = 0, row; row = inputRows[y]; y++) {
|
|
cursorX = row.paddingStart;
|
|
if (y == 0) {
|
|
cursorX += this.RTL ? -iconWidth : iconWidth;
|
|
}
|
|
|
|
if (row.type == Blockly.BlockSvg.INLINE) {
|
|
// Inline inputs.
|
|
for (var x = 0, input; input = row[x]; x++) {
|
|
// Align fields vertically within the row.
|
|
// Moves the field to half of the row's height.
|
|
// In renderFields_, the field is further centered
|
|
// by its own rendered height.
|
|
var fieldY = cursorY + row.height / 2;
|
|
|
|
var fieldX = Blockly.BlockSvg.getAlignedCursor_(cursorX, input,
|
|
inputRows.rightEdge);
|
|
|
|
cursorX = this.renderFields_(input.fieldRow, fieldX, fieldY);
|
|
if (input.type == Blockly.INPUT_VALUE) {
|
|
// Create inline input connection.
|
|
// In blocks with a notch, inputs should be bumped to a min X,
|
|
// to avoid overlapping with the notch.
|
|
if (this.previousConnection) {
|
|
cursorX = Math.max(cursorX, Blockly.BlockSvg.INPUT_AND_FIELD_MIN_X);
|
|
}
|
|
connectionX = this.RTL ? -cursorX : cursorX;
|
|
// Attempt to center the connection vertically.
|
|
var connectionYOffset = row.height / 2;
|
|
connectionY = cursorY + connectionYOffset;
|
|
input.connection.setOffsetInBlock(connectionX, connectionY);
|
|
this.renderInputShape_(input, cursorX, cursorY + connectionYOffset);
|
|
cursorX += input.renderWidth + Blockly.BlockSvg.SEP_SPACE_X;
|
|
}
|
|
}
|
|
// Remove final separator and replace it with right-padding.
|
|
cursorX -= Blockly.BlockSvg.SEP_SPACE_X;
|
|
cursorX += row.paddingEnd;
|
|
// Update right edge for all inputs, such that all rows
|
|
// stretch to be at least the size of all previous rows.
|
|
inputRows.rightEdge = Math.max(cursorX, inputRows.rightEdge);
|
|
// Move to the right edge
|
|
cursorX = Math.max(cursorX, inputRows.rightEdge);
|
|
this.width = Math.max(this.width, cursorX);
|
|
if (!this.edgeShape_) {
|
|
// Include corner radius in drawing the horizontal line.
|
|
steps.push('H', cursorX - Blockly.BlockSvg.CORNER_RADIUS - this.edgeShapeWidth_);
|
|
steps.push(Blockly.BlockSvg.TOP_RIGHT_CORNER);
|
|
} else {
|
|
// Don't include corner radius - no corner (edge shape drawn).
|
|
steps.push('H', cursorX - this.edgeShapeWidth_);
|
|
}
|
|
// Subtract CORNER_RADIUS * 2 to account for the top right corner
|
|
// and also the bottom right corner. Only move vertically the non-corner length.
|
|
if (!this.edgeShape_) {
|
|
steps.push('v', row.height - Blockly.BlockSvg.CORNER_RADIUS * 2);
|
|
}
|
|
} else if (row.type == Blockly.NEXT_STATEMENT) {
|
|
// Nested statement.
|
|
var input = row[0];
|
|
var fieldX = cursorX;
|
|
// Align fields vertically within the row.
|
|
// In renderFields_, the field is further centered by its own height.
|
|
var fieldY = cursorY;
|
|
fieldY += Blockly.BlockSvg.MIN_STATEMENT_INPUT_HEIGHT;
|
|
this.renderFields_(input.fieldRow, fieldX, fieldY);
|
|
// Move to the start of the notch.
|
|
cursorX = inputRows.statementEdge + Blockly.BlockSvg.NOTCH_WIDTH;
|
|
|
|
if (this.type == Blockly.PROCEDURES_DEFINITION_BLOCK_TYPE) {
|
|
this.renderDefineBlock_(steps, inputRows, input, row, cursorY);
|
|
} else {
|
|
Blockly.BlockSvg.drawStatementInputFromTopRight_(steps, cursorX,
|
|
inputRows.rightEdge, row);
|
|
}
|
|
|
|
// Create statement connection.
|
|
connectionX = this.RTL ? -cursorX : cursorX;
|
|
input.connection.setOffsetInBlock(connectionX, cursorY);
|
|
if (input.connection.isConnected()) {
|
|
this.width = Math.max(this.width, inputRows.statementEdge +
|
|
input.connection.targetBlock().getHeightWidth().width);
|
|
}
|
|
if (this.type != Blockly.PROCEDURES_DEFINITION_BLOCK_TYPE &&
|
|
(y == inputRows.length - 1 ||
|
|
inputRows[y + 1].type == Blockly.NEXT_STATEMENT)) {
|
|
// If the final input is a statement stack, add a small row underneath.
|
|
// Consecutive statement stacks are also separated by a small divider.
|
|
steps.push(Blockly.BlockSvg.TOP_RIGHT_CORNER);
|
|
steps.push('v', Blockly.BlockSvg.EXTRA_STATEMENT_ROW_Y - 2 * Blockly.BlockSvg.CORNER_RADIUS);
|
|
cursorY += Blockly.BlockSvg.EXTRA_STATEMENT_ROW_Y;
|
|
}
|
|
}
|
|
cursorY += row.height;
|
|
}
|
|
this.drawEdgeShapeRight_(steps);
|
|
if (!inputRows.length) {
|
|
cursorY = Blockly.BlockSvg.MIN_BLOCK_Y;
|
|
steps.push('V', cursorY);
|
|
}
|
|
return cursorY;
|
|
};
|
|
|
|
/**
|
|
* Render the input shapes.
|
|
* If there's a connected block, hide the input shape.
|
|
* Otherwise, draw and set the position of the input shape.
|
|
* @param {!Blockly.Input} input Input to be rendered.
|
|
* @param {Number} x X offset of input.
|
|
* @param {Number} y Y offset of input.
|
|
*/
|
|
Blockly.BlockSvg.prototype.renderInputShape_ = function(input, x, y) {
|
|
var inputShape = input.outlinePath;
|
|
if (!inputShape) {
|
|
// No input shape for this input - e.g., the block is an insertion marker.
|
|
return;
|
|
}
|
|
// Input shapes are only visibly rendered on non-connected slots.
|
|
if (input.connection.targetConnection) {
|
|
inputShape.setAttribute('style', 'visibility: hidden');
|
|
} else {
|
|
var inputShapeX = 0, inputShapeY = 0;
|
|
var inputShapeInfo =
|
|
Blockly.BlockSvg.getInputShapeInfo_(input.connection.getOutputShape());
|
|
if (this.RTL) {
|
|
inputShapeX = -x - inputShapeInfo.width;
|
|
} else {
|
|
inputShapeX = x;
|
|
}
|
|
inputShapeY = y - (Blockly.BlockSvg.INPUT_SHAPE_HEIGHT / 2);
|
|
inputShape.setAttribute('d', inputShapeInfo.path);
|
|
inputShape.setAttribute('transform',
|
|
'translate(' + inputShapeX + ',' + inputShapeY + ')');
|
|
inputShape.setAttribute('data-argument-type', inputShapeInfo.argType);
|
|
inputShape.setAttribute('style', 'visibility: visible');
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Render the bottom edge of the block.
|
|
* @param {!Array.<string>} steps Path of block outline.
|
|
* @param {number} cursorY Height of block.
|
|
* @private
|
|
*/
|
|
Blockly.BlockSvg.prototype.renderDrawBottom_ = function(steps, cursorY) {
|
|
this.height = cursorY;
|
|
if (!this.edgeShape_) {
|
|
steps.push(Blockly.BlockSvg.BOTTOM_RIGHT_CORNER);
|
|
}
|
|
if (this.nextConnection) {
|
|
// Move to the right-side of the notch.
|
|
var notchStart = (
|
|
Blockly.BlockSvg.NOTCH_WIDTH +
|
|
Blockly.BlockSvg.NOTCH_START_PADDING +
|
|
Blockly.BlockSvg.CORNER_RADIUS
|
|
);
|
|
steps.push('H', notchStart, ' ');
|
|
steps.push(Blockly.BlockSvg.NOTCH_PATH_RIGHT);
|
|
// Create next block connection.
|
|
var connectionX = this.RTL ? -Blockly.BlockSvg.NOTCH_WIDTH :
|
|
Blockly.BlockSvg.NOTCH_WIDTH;
|
|
this.nextConnection.setOffsetInBlock(connectionX, cursorY);
|
|
// Include height of notch in block height.
|
|
this.height += Blockly.BlockSvg.NOTCH_HEIGHT;
|
|
}
|
|
// Bottom horizontal line
|
|
if (!this.edgeShape_) {
|
|
steps.push('H', Blockly.BlockSvg.CORNER_RADIUS);
|
|
// Bottom left corner
|
|
steps.push(Blockly.BlockSvg.BOTTOM_LEFT_CORNER);
|
|
} else {
|
|
steps.push('H', this.edgeShapeWidth_);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Render the left edge of the block.
|
|
* @param {!Array.<string>} steps Path of block outline.
|
|
* @param {number} cursorY Height of block.
|
|
* @private
|
|
*/
|
|
Blockly.BlockSvg.prototype.renderDrawLeft_ = function(steps) {
|
|
if (this.outputConnection) {
|
|
// Scratch-style reporters have output connection y at half block height.
|
|
this.outputConnection.setOffsetInBlock(0, this.height / 2);
|
|
}
|
|
if (this.edgeShape_) {
|
|
// Draw the left-side edge shape.
|
|
if (this.edgeShape_ === Blockly.OUTPUT_SHAPE_ROUND) {
|
|
// Draw a rounded arc.
|
|
steps.push('a ' + this.edgeShapeWidth_ + ' ' + this.edgeShapeWidth_ + ' 0 0 1 0 -' + this.edgeShapeWidth_ * 2);
|
|
} else if (this.edgeShape_ === Blockly.OUTPUT_SHAPE_HEXAGONAL) {
|
|
// Draw a half-hexagon.
|
|
steps.push('l ' + -this.edgeShapeWidth_ + ' ' + -this.edgeShapeWidth_ +
|
|
' l ' + this.edgeShapeWidth_ + ' ' + -this.edgeShapeWidth_);
|
|
}
|
|
}
|
|
steps.push('z');
|
|
};
|
|
|
|
/**
|
|
* Draw the edge shape (rounded or hexagonal) on the right side of a block with
|
|
* an output.
|
|
* @param {!Array.<string>} steps Path of block outline.
|
|
* @private
|
|
*/
|
|
Blockly.BlockSvg.prototype.drawEdgeShapeRight_ = function(steps) {
|
|
if (this.edgeShape_) {
|
|
// Draw the right-side edge shape.
|
|
if (this.edgeShape_ === Blockly.OUTPUT_SHAPE_ROUND) {
|
|
// Draw a rounded arc.
|
|
steps.push('a ' + this.edgeShapeWidth_ + ' ' + this.edgeShapeWidth_ +
|
|
' 0 0 1 0 ' + this.edgeShapeWidth_ * 2);
|
|
} else if (this.edgeShape_ === Blockly.OUTPUT_SHAPE_HEXAGONAL) {
|
|
// Draw an half-hexagon.
|
|
steps.push('l ' + this.edgeShapeWidth_ + ' ' + this.edgeShapeWidth_ +
|
|
' l ' + -this.edgeShapeWidth_ + ' ' + this.edgeShapeWidth_);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Position an new block correctly, so that it doesn't move the existing block
|
|
* when connected to it.
|
|
* @param {!Blockly.Block} newBlock The block to position - either the first
|
|
* block in a dragged stack or an insertion marker.
|
|
* @param {!Blockly.Connection} newConnection The connection on the new block's
|
|
* stack - either a connection on newBlock, or the last NEXT_STATEMENT
|
|
* connection on the stack if the stack's being dropped before another
|
|
* block.
|
|
* @param {!Blockly.Connection} existingConnection The connection on the
|
|
* existing block, which newBlock should line up with.
|
|
*/
|
|
Blockly.BlockSvg.prototype.positionNewBlock = function(newBlock, newConnection,
|
|
existingConnection) {
|
|
// We only need to position the new block if it's before the existing one,
|
|
// otherwise its position is set by the previous block.
|
|
if (newConnection.type == Blockly.NEXT_STATEMENT) {
|
|
var dx = existingConnection.x_ - newConnection.x_;
|
|
var dy = existingConnection.y_ - newConnection.y_;
|
|
|
|
newBlock.moveBy(dx, dy);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Draw the outline of a statement input, starting at the top right corner.
|
|
* @param {!Array.<string>} steps Path of block outline.
|
|
* @param {number} cursorX The x position of the start of the notch at the top
|
|
* of the input.
|
|
* @param {number} rightEdge The far right edge of the block, which determines
|
|
* how wide the statement input is.
|
|
* @param {!Array.<!Object>} row An object containing information about the
|
|
* current row, including its height and whether it should have a notch at
|
|
* the bottom.
|
|
* @private
|
|
*/
|
|
Blockly.BlockSvg.drawStatementInputFromTopRight_ = function(steps, cursorX,
|
|
rightEdge, row) {
|
|
Blockly.BlockSvg.drawStatementInputTop_(steps, cursorX);
|
|
steps.push('v', row.height - 2 * Blockly.BlockSvg.CORNER_RADIUS);
|
|
Blockly.BlockSvg.drawStatementInputBottom_(steps, rightEdge, row);
|
|
};
|
|
|
|
/**
|
|
* Draw the top of the outline of a statement input, starting at the top right
|
|
* corner.
|
|
* @param {!Array.<string>} steps Path of block outline.
|
|
* @param {number} cursorX The x position of the start of the notch at the top
|
|
* of the input.
|
|
* @private
|
|
*/
|
|
Blockly.BlockSvg.drawStatementInputTop_ = function(steps, cursorX) {
|
|
steps.push(Blockly.BlockSvg.BOTTOM_RIGHT_CORNER);
|
|
steps.push('H', cursorX + Blockly.BlockSvg.STATEMENT_INPUT_INNER_SPACE +
|
|
2 * Blockly.BlockSvg.CORNER_RADIUS);
|
|
steps.push(Blockly.BlockSvg.NOTCH_PATH_RIGHT);
|
|
steps.push('h', '-' + Blockly.BlockSvg.STATEMENT_INPUT_INNER_SPACE);
|
|
steps.push(Blockly.BlockSvg.INNER_TOP_LEFT_CORNER);
|
|
};
|
|
|
|
/**
|
|
* Draw the bottom of the outline of a statement input, starting at the inner
|
|
* left corner.
|
|
* @param {!Array.<string>} steps Path of block outline.
|
|
* @param {number} rightEdge The far right edge of the block, which determines
|
|
* how wide the statement input is.
|
|
* @param {!Array.<!Object>} row An object containing information about the
|
|
* current row, including its height and whether it should have a notch at
|
|
* the bottom.
|
|
* @private
|
|
*/
|
|
Blockly.BlockSvg.drawStatementInputBottom_ = function(steps, rightEdge, row) {
|
|
steps.push(Blockly.BlockSvg.INNER_BOTTOM_LEFT_CORNER);
|
|
if (row.statementNotchAtBottom) {
|
|
steps.push('h ', Blockly.BlockSvg.STATEMENT_INPUT_INNER_SPACE);
|
|
steps.push(Blockly.BlockSvg.NOTCH_PATH_LEFT);
|
|
}
|
|
steps.push('H', rightEdge - Blockly.BlockSvg.CORNER_RADIUS);
|
|
};
|
|
|
|
/**
|
|
* Render part of the hat and the right side of the define block to fully wrap
|
|
* the connected statement block.
|
|
* Scratch-specific.
|
|
* @param {!Array.<string>} steps Path of block outline.
|
|
* @param {!Array.<!Array.<!Object>>} inputRows 2D array of objects, each
|
|
* containing position information.
|
|
* @param {!Blockly.Input} input The input that is currently being rendered.
|
|
* @param {!Array.<!Object>} row An object containing information about the
|
|
* current row, including its height and whether it should have a notch at
|
|
* the bottom.
|
|
* @param {number} cursorY The y position of the start of this row. Used to
|
|
* position the following dummy input's fields.
|
|
* @private
|
|
*/
|
|
Blockly.BlockSvg.prototype.renderDefineBlock_ = function(steps, inputRows,
|
|
input, row, cursorY) {
|
|
// Following text shows up as a dummy input after the statement input, which
|
|
// we are forcing to stay inline with the statement input instead of letting
|
|
// it drop to a new line.
|
|
var hasFollowingText = row.length == 2;
|
|
|
|
// Figure out where the right side of the block is.
|
|
var rightSide = inputRows.rightEdge;
|
|
if (input.connection && input.connection.targetBlock()) {
|
|
rightSide = inputRows.statementEdge +
|
|
input.connection.targetBlock().getHeightWidth().width +
|
|
Blockly.BlockSvg.DEFINE_BLOCK_PADDING_RIGHT;
|
|
} else {
|
|
// Handles the case where block is being rendered as an insertion marker
|
|
rightSide = Math.max(Blockly.BlockSvg.MIN_BLOCK_X_WITH_STATEMENT, rightSide)
|
|
+ Blockly.BlockSvg.DEFINE_BLOCK_PADDING_RIGHT;
|
|
}
|
|
rightSide -= Blockly.BlockSvg.DEFINE_HAT_CORNER_RADIUS;
|
|
|
|
if (hasFollowingText) {
|
|
var followingTextInput = row[1];
|
|
var fieldStart = rightSide + 3 * Blockly.BlockSvg.SEP_SPACE_X;
|
|
rightSide += followingTextInput.fieldRow[0].getSize().width;
|
|
rightSide += 2 * Blockly.BlockSvg.SEP_SPACE_X;
|
|
|
|
// Align fields vertically within the row.
|
|
// In renderFields_, the field is further centered by its own height.
|
|
// The dummy input's fields did not get laid out normally because we're
|
|
// forcing them to stay inline with a statement input.
|
|
var fieldY = cursorY;
|
|
fieldY += Blockly.BlockSvg.MIN_STATEMENT_INPUT_HEIGHT;
|
|
this.renderFields_(followingTextInput.fieldRow, fieldStart, fieldY);
|
|
}
|
|
// Draw the top and the right corner of the hat.
|
|
steps.push('H', rightSide);
|
|
steps.push(Blockly.BlockSvg.TOP_RIGHT_CORNER_DEFINE_HAT);
|
|
row.height += 3 * Blockly.BlockSvg.GRID_UNIT;
|
|
// Draw the right side of the block around the statement input.
|
|
steps.push('v', row.height);
|
|
// row.height will be used to update the cursor in the calling function.
|
|
row.height += Blockly.BlockSvg.GRID_UNIT;
|
|
|
|
};
|
|
|
|
/**
|
|
* Get some information about the input shape to draw, based on the type of the
|
|
* connection.
|
|
* @param {number} shape An enum representing the shape of the connection we're
|
|
* drawing around.
|
|
* @return {!Object} An object containing an SVG path, a string representation
|
|
* of the argument type, and a width.
|
|
* @private
|
|
*/
|
|
Blockly.BlockSvg.getInputShapeInfo_ = function(shape) {
|
|
var inputShapePath = null;
|
|
var inputShapeArgType = null;
|
|
var inputShapeWidth = 0;
|
|
|
|
switch (shape) {
|
|
case Blockly.OUTPUT_SHAPE_HEXAGONAL:
|
|
inputShapePath = Blockly.BlockSvg.INPUT_SHAPE_HEXAGONAL;
|
|
inputShapeWidth = Blockly.BlockSvg.INPUT_SHAPE_HEXAGONAL_WIDTH;
|
|
inputShapeArgType = 'boolean';
|
|
break;
|
|
case Blockly.OUTPUT_SHAPE_ROUND:
|
|
inputShapePath = Blockly.BlockSvg.INPUT_SHAPE_ROUND;
|
|
inputShapeWidth = Blockly.BlockSvg.INPUT_SHAPE_ROUND_WIDTH;
|
|
inputShapeArgType = 'round';
|
|
break;
|
|
case Blockly.OUTPUT_SHAPE_SQUARE:
|
|
default: // If the input connection is not connected, draw a hole shape.
|
|
inputShapePath = Blockly.BlockSvg.INPUT_SHAPE_SQUARE;
|
|
inputShapeWidth = Blockly.BlockSvg.INPUT_SHAPE_SQUARE_WIDTH;
|
|
inputShapeArgType = 'square';
|
|
break;
|
|
}
|
|
return {
|
|
path: inputShapePath,
|
|
argType: inputShapeArgType,
|
|
width: inputShapeWidth
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Get the correct cursor position for the given input, based on alignment,
|
|
* the total size of the block, and the size of the input.
|
|
* @param {number} cursorX The minimum x value of the cursor.
|
|
* @param {!Blockly.Input} input The input to align the fields for.
|
|
* @param {number} rightEdge The maximum width of the block. Right-aligned
|
|
* fields are positioned based on this number.
|
|
* @return {number} The new cursor position.
|
|
* @private
|
|
*/
|
|
Blockly.BlockSvg.getAlignedCursor_ = function(cursorX, input, rightEdge) {
|
|
// Align inline field rows (left/right/centre).
|
|
if (input.align === Blockly.ALIGN_RIGHT) {
|
|
cursorX += rightEdge - input.fieldWidth -
|
|
(2 * Blockly.BlockSvg.SEP_SPACE_X);
|
|
} else if (input.align === Blockly.ALIGN_CENTRE) {
|
|
cursorX = Math.max(cursorX, rightEdge / 2 - input.fieldWidth / 2);
|
|
}
|
|
return cursorX;
|
|
};
|
|
|
|
/**
|
|
* Update all of the connections on this block with the new locaitons calculated
|
|
* in renderCompute, and move all of the connected blocks based on the new
|
|
* connection locations.
|
|
* @private
|
|
*/
|
|
Blockly.BlockSvg.prototype.renderMoveConnections_ = function() {
|
|
var blockTL = this.getRelativeToSurfaceXY();
|
|
// Don't tighten previous or output connections because they are inferior.
|
|
if (this.previousConnection) {
|
|
this.previousConnection.moveToOffset(blockTL);
|
|
}
|
|
if (this.outputConnection) {
|
|
this.outputConnection.moveToOffset(blockTL);
|
|
}
|
|
|
|
for (var i = 0; i < this.inputList.length; i++) {
|
|
var conn = this.inputList[i].connection;
|
|
if (conn) {
|
|
conn.moveToOffset(blockTL);
|
|
if (conn.isConnected()) {
|
|
conn.tighten_();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (this.nextConnection) {
|
|
this.nextConnection.moveToOffset(blockTL);
|
|
if (this.nextConnection.isConnected()) {
|
|
this.nextConnection.tighten_();
|
|
}
|
|
}
|
|
};
|