123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247 |
- /**
- * @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');
- goog.require('Blockly.Block');
- goog.require('Blockly.ContextMenu');
- goog.require('goog.Timer');
- goog.require('goog.asserts');
- goog.require('goog.dom');
- goog.require('goog.math.Coordinate');
- /**
- * Class for a block's SVG representation.
- * @extends {Blockly.Block}
- * @constructor
- */
- Blockly.BlockSvg = function() {
- // Create core elements for the block.
- this.svgGroup_ = Blockly.createSvgElement('g', {}, null);
- this.svgPathDark_ = Blockly.createSvgElement('path',
- {'class': 'blocklyPathDark', 'transform': 'translate(1,1)'},
- this.svgGroup_);
- this.svgPath_ = Blockly.createSvgElement('path', {'class': 'blocklyPath'},
- this.svgGroup_);
- this.svgPathLight_ = Blockly.createSvgElement('path',
- {'class': 'blocklyPathLight'}, this.svgGroup_);
- this.svgPath_.tooltip = this;
- Blockly.Tooltip.bindMouseEvents(this.svgPath_);
- };
- goog.inherits(Blockly.BlockSvg, Blockly.Block);
- /**
- * Height of this block, not including any statement blocks above or below.
- */
- Blockly.BlockSvg.prototype.height = 0;
- /**
- * Width of this block, including any connected value blocks.
- */
- Blockly.BlockSvg.prototype.width = 0;
- /**
- * Original location of block being dragged.
- * @type {goog.math.Coordinate}
- * @private
- */
- Blockly.BlockSvg.prototype.dragStartXY_ = null;
- /**
- * Constant for identifying rows that are to be rendered inline.
- * Don't collide with Blockly.INPUT_VALUE and friends.
- * @const
- */
- Blockly.BlockSvg.INLINE = -1;
- /**
- * Create and initialize the SVG representation of the block.
- * May be called more than once.
- */
- Blockly.BlockSvg.prototype.initSvg = function() {
- goog.asserts.assert(this.workspace.rendered, 'Workspace is headless.');
- for (var i = 0, input; input = this.inputList[i]; i++) {
- input.init();
- }
- if (this.mutator) {
- this.mutator.createIcon();
- }
- this.updateColour();
- this.updateMovable();
- if (!this.workspace.options.readOnly && !this.eventsInit_) {
- Blockly.bindEvent_(this.getSvgRoot(), 'mousedown', this,
- this.onMouseDown_);
- var thisBlock = this;
- Blockly.bindEvent_(this.getSvgRoot(), 'touchstart', null,
- function(e) {Blockly.longStart_(e, thisBlock);});
- }
- // Bind an onchange function, if it exists.
- if (goog.isFunction(this.onchange) && !this.eventsInit_) {
- this.onchangeWrapper_ = Blockly.bindEvent_(this.workspace.getCanvas(),
- 'blocklyWorkspaceChange', this, this.onchange);
- }
- this.eventsInit_ = true;
- if (!this.getSvgRoot().parentNode) {
- this.workspace.getCanvas().appendChild(this.getSvgRoot());
- }
- };
- /**
- * Select this block. Highlight it visually.
- */
- Blockly.BlockSvg.prototype.select = function() {
- if (Blockly.selected) {
- // Unselect any previously selected block.
- Blockly.selected.unselect();
- }
- Blockly.selected = this;
- this.addSelect();
- Blockly.fireUiEvent(this.workspace.getCanvas(), 'blocklySelectChange');
- };
- /**
- * Unselect this block. Remove its highlighting.
- */
- Blockly.BlockSvg.prototype.unselect = function() {
- Blockly.selected = null;
- this.removeSelect();
- Blockly.fireUiEvent(this.workspace.getCanvas(), 'blocklySelectChange');
- };
- /**
- * Block's mutator icon (if any).
- * @type {Blockly.Mutator}
- */
- Blockly.BlockSvg.prototype.mutator = null;
- /**
- * Block's comment icon (if any).
- * @type {Blockly.Comment}
- */
- Blockly.BlockSvg.prototype.comment = null;
- /**
- * Block's warning icon (if any).
- * @type {Blockly.Warning}
- */
- Blockly.BlockSvg.prototype.warning = null;
- /**
- * Returns a list of mutator, comment, and warning icons.
- * @return {!Array} List of icons.
- */
- Blockly.BlockSvg.prototype.getIcons = function() {
- var icons = [];
- if (this.mutator) {
- icons.push(this.mutator);
- }
- if (this.comment) {
- icons.push(this.comment);
- }
- if (this.warning) {
- icons.push(this.warning);
- }
- return icons;
- };
- /**
- * Wrapper function called when a mouseUp occurs during a drag operation.
- * @type {Array.<!Array>}
- * @private
- */
- Blockly.BlockSvg.onMouseUpWrapper_ = null;
- /**
- * Wrapper function called when a mouseMove occurs during a drag operation.
- * @type {Array.<!Array>}
- * @private
- */
- Blockly.BlockSvg.onMouseMoveWrapper_ = null;
- /**
- * Stop binding to the global mouseup and mousemove events.
- * @private
- */
- Blockly.BlockSvg.terminateDrag_ = function() {
- Blockly.BlockSvg.disconnectUiStop_();
- if (Blockly.BlockSvg.onMouseUpWrapper_) {
- Blockly.unbindEvent_(Blockly.BlockSvg.onMouseUpWrapper_);
- Blockly.BlockSvg.onMouseUpWrapper_ = null;
- }
- if (Blockly.BlockSvg.onMouseMoveWrapper_) {
- Blockly.unbindEvent_(Blockly.BlockSvg.onMouseMoveWrapper_);
- Blockly.BlockSvg.onMouseMoveWrapper_ = null;
- }
- var selected = Blockly.selected;
- if (Blockly.dragMode_ == 2) {
- // Terminate a drag operation.
- if (selected) {
- // Update the connection locations.
- var xy = selected.getRelativeToSurfaceXY();
- var dxy = goog.math.Coordinate.difference(xy, selected.dragStartXY_);
- selected.moveConnections_(dxy.x, dxy.y);
- delete selected.draggedBubbles_;
- selected.setDragging_(false);
- selected.render();
- goog.Timer.callOnce(
- selected.snapToGrid, Blockly.BUMP_DELAY / 2, selected);
- goog.Timer.callOnce(
- selected.bumpNeighbours_, Blockly.BUMP_DELAY, selected);
- // Fire an event to allow scrollbars to resize.
- Blockly.fireUiEvent(window, 'resize');
- selected.workspace.fireChangeEvent();
- }
- }
- Blockly.dragMode_ = 0;
- Blockly.Css.setCursor(Blockly.Css.Cursor.OPEN);
- };
- /**
- * Set parent of this block to be a new block or null.
- * @param {Blockly.BlockSvg} newParent New parent block.
- */
- Blockly.BlockSvg.prototype.setParent = function(newParent) {
- var svgRoot = this.getSvgRoot();
- if (this.parentBlock_ && svgRoot) {
- // Move this block up the DOM. Keep track of x/y translations.
- var xy = this.getRelativeToSurfaceXY();
- this.workspace.getCanvas().appendChild(svgRoot);
- svgRoot.setAttribute('transform', 'translate(' + xy.x + ',' + xy.y + ')');
- }
- Blockly.Field.startCache();
- Blockly.BlockSvg.superClass_.setParent.call(this, newParent);
- Blockly.Field.stopCache();
- if (newParent) {
- var oldXY = this.getRelativeToSurfaceXY();
- newParent.getSvgRoot().appendChild(svgRoot);
- var newXY = this.getRelativeToSurfaceXY();
- // Move the connections to match the child's new position.
- this.moveConnections_(newXY.x - oldXY.x, newXY.y - oldXY.y);
- }
- };
- /**
- * Return the coordinates of the top-left corner of this block relative to the
- * drawing surface's origin (0,0).
- * @return {!goog.math.Coordinate} Object with .x and .y properties.
- */
- Blockly.BlockSvg.prototype.getRelativeToSurfaceXY = function() {
- var x = 0;
- var y = 0;
- var element = this.getSvgRoot();
- if (element) {
- do {
- // Loop through this block and every parent.
- var xy = Blockly.getRelativeXY_(element);
- x += xy.x;
- y += xy.y;
- element = element.parentNode;
- } while (element && element != this.workspace.getCanvas());
- }
- return new goog.math.Coordinate(x, y);
- };
- /**
- * Move a block by a relative offset.
- * @param {number} dx Horizontal offset.
- * @param {number} dy Vertical offset.
- */
- Blockly.BlockSvg.prototype.moveBy = function(dx, dy) {
- var xy = this.getRelativeToSurfaceXY();
- this.getSvgRoot().setAttribute('transform',
- 'translate(' + (xy.x + dx) + ',' + (xy.y + dy) + ')');
- this.moveConnections_(dx, dy);
- Blockly.Realtime.blockChanged(this);
- };
- /**
- * Snap this block to the nearest grid point.
- */
- Blockly.BlockSvg.prototype.snapToGrid = function() {
- if (!this.workspace) {
- return; // Deleted block.
- }
- if (Blockly.dragMode_ != 0) {
- return; // Don't bump blocks during a drag.
- }
- if (this.getParent()) {
- return; // Only snap top-level blocks.
- }
- if (this.isInFlyout) {
- return; // Don't move blocks around in a flyout.
- }
- if (!this.workspace.options.gridOptions ||
- !this.workspace.options.gridOptions['snap']) {
- return; // Config says no snapping.
- }
- var spacing = this.workspace.options.gridOptions['spacing'];
- var half = spacing / 2;
- var xy = this.getRelativeToSurfaceXY();
- var dx = Math.round((xy.x - half) / spacing) * spacing + half - xy.x;
- var dy = Math.round((xy.y - half) / spacing) * spacing + half - xy.y;
- dx = Math.round(dx);
- dy = Math.round(dy);
- if (dx != 0 || dy != 0) {
- this.moveBy(dx, dy);
- }
- };
- /**
- * Returns a bounding box describing the dimensions of this block
- * and any blocks stacked below it.
- * @return {!Object} 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 - 4; // Height of tab.
- width = Math.max(width, nextHeightWidth.width);
- } else if (!this.nextConnection && !this.outputConnection) {
- // Add a bit of margin under blocks with no bottom tab.
- height += 2;
- }
- return {height: height, width: width};
- };
- /**
- * Set whether the block is collapsed or not.
- * @param {boolean} collapsed True if collapsed.
- */
- Blockly.BlockSvg.prototype.setCollapsed = function(collapsed) {
- if (this.collapsed_ == collapsed) {
- return;
- }
- var renderList = [];
- // Show/hide the inputs.
- for (var i = 0, input; input = this.inputList[i]; i++) {
- renderList.push.apply(renderList, input.setVisible(!collapsed));
- }
- var COLLAPSED_INPUT_NAME = '_TEMP_COLLAPSED_INPUT';
- if (collapsed) {
- var icons = this.getIcons();
- for (var i = 0; i < icons.length; i++) {
- icons[i].setVisible(false);
- }
- var text = this.toString(Blockly.COLLAPSE_CHARS);
- this.appendDummyInput(COLLAPSED_INPUT_NAME).appendField(text).init();
- } else {
- this.removeInput(COLLAPSED_INPUT_NAME);
- // Clear any warnings inherited from enclosed blocks.
- this.setWarningText(null);
- }
- Blockly.BlockSvg.superClass_.setCollapsed.call(this, collapsed);
- if (!renderList.length) {
- // No child blocks, just render this block.
- renderList[0] = this;
- }
- if (this.rendered) {
- for (var i = 0, block; block = renderList[i]; i++) {
- block.render();
- }
- // Don't bump neighbours.
- // Although bumping neighbours would make sense, users often collapse
- // all their functions and store them next to each other. Expanding and
- // bumping causes all their definitions to go out of alignment.
- }
- this.workspace.fireChangeEvent();
- };
- /**
- * Open the next (or previous) FieldTextInput.
- * @param {Blockly.Field|Blockly.Block} start Current location.
- * @param {boolean} forward If true go forward, otherwise backward.
- */
- Blockly.BlockSvg.prototype.tab = function(start, forward) {
- // This function need not be efficient since it runs once on a keypress.
- // Create an ordered list of all text fields and connected inputs.
- var list = [];
- for (var i = 0, input; input = this.inputList[i]; i++) {
- for (var j = 0, field; field = input.fieldRow[j]; j++) {
- if (field instanceof Blockly.FieldTextInput) {
- // TODO: Also support dropdown fields.
- list.push(field);
- }
- }
- if (input.connection) {
- var block = input.connection.targetBlock();
- if (block) {
- list.push(block);
- }
- }
- }
- var i = list.indexOf(start);
- if (i == -1) {
- // No start location, start at the beginning or end.
- i = forward ? -1 : list.length;
- }
- var target = list[forward ? i + 1 : i - 1];
- if (!target) {
- // Ran off of list.
- var parent = this.getParent();
- if (parent) {
- parent.tab(this, forward);
- }
- } else if (target instanceof Blockly.Field) {
- target.showEditor_();
- } else {
- target.tab(null, forward);
- }
- };
- /**
- * Handle a mouse-down on an SVG block.
- * @param {!Event} e Mouse down event.
- * @private
- */
- Blockly.BlockSvg.prototype.onMouseDown_ = function(e) {
- if (this.isInFlyout) {
- return;
- }
- this.workspace.markFocused();
- // Update Blockly's knowledge of its own location.
- Blockly.svgResize(this.workspace);
- Blockly.terminateDrag_();
- this.select();
- Blockly.hideChaff();
- if (Blockly.isRightButton(e)) {
- // Right-click.
- this.showContextMenu_(e);
- } else if (!this.isMovable()) {
- // Allow unmovable blocks to be selected and context menued, but not
- // dragged. Let this event bubble up to document, so the workspace may be
- // dragged instead.
- return;
- } else {
- // Left-click (or middle click)
- Blockly.removeAllRanges();
- Blockly.Css.setCursor(Blockly.Css.Cursor.CLOSED);
- this.dragStartXY_ = this.getRelativeToSurfaceXY();
- this.workspace.startDrag(e, this.dragStartXY_.x, this.dragStartXY_.y);
- Blockly.dragMode_ = 1;
- Blockly.BlockSvg.onMouseUpWrapper_ = Blockly.bindEvent_(document,
- 'mouseup', this, this.onMouseUp_);
- Blockly.BlockSvg.onMouseMoveWrapper_ = Blockly.bindEvent_(document,
- 'mousemove', this, this.onMouseMove_);
- // Build a list of bubbles that need to be moved and where they started.
- this.draggedBubbles_ = [];
- var descendants = this.getDescendants();
- for (var i = 0, descendant; descendant = descendants[i]; i++) {
- var icons = descendant.getIcons();
- for (var j = 0; j < icons.length; j++) {
- var data = icons[j].getIconLocation();
- data.bubble = icons[j];
- this.draggedBubbles_.push(data);
- }
- }
- }
- // This event has been handled. No need to bubble up to the document.
- e.stopPropagation();
- };
- /**
- * Handle a mouse-up anywhere in the SVG pane. Is only registered when a
- * block is clicked. We can't use mouseUp on the block since a fast-moving
- * cursor can briefly escape the block before it catches up.
- * @param {!Event} e Mouse up event.
- * @private
- */
- Blockly.BlockSvg.prototype.onMouseUp_ = function(e) {
- var this_ = this;
- Blockly.doCommand(function() {
- Blockly.terminateDrag_();
- if (Blockly.selected && Blockly.highlightedConnection_) {
- // Connect two blocks together.
- Blockly.localConnection_.connect(Blockly.highlightedConnection_);
- if (this_.rendered) {
- // Trigger a connection animation.
- // Determine which connection is inferior (lower in the source stack).
- var inferiorConnection;
- if (Blockly.localConnection_.isSuperior()) {
- inferiorConnection = Blockly.highlightedConnection_;
- } else {
- inferiorConnection = Blockly.localConnection_;
- }
- inferiorConnection.sourceBlock_.connectionUiEffect();
- }
- if (this_.workspace.trashcan) {
- // Don't throw an object in the trash can if it just got connected.
- this_.workspace.trashcan.close();
- }
- } else if (!this_.getParent() && Blockly.selected.isDeletable() &&
- this_.workspace.isDeleteArea(e)) {
- var trashcan = this_.workspace.trashcan;
- if (trashcan) {
- goog.Timer.callOnce(trashcan.close, 100, trashcan);
- }
- Blockly.selected.dispose(false, true);
- // Dropping a block on the trash can will usually cause the workspace to
- // resize to contain the newly positioned block. Force a second resize
- // now that the block has been deleted.
- Blockly.fireUiEvent(window, 'resize');
- }
- if (Blockly.highlightedConnection_) {
- Blockly.highlightedConnection_.unhighlight();
- Blockly.highlightedConnection_ = null;
- }
- Blockly.Css.setCursor(Blockly.Css.Cursor.OPEN);
- });
- };
- /**
- * Load the block's help page in a new window.
- * @private
- */
- Blockly.BlockSvg.prototype.showHelp_ = function() {
- var url = goog.isFunction(this.helpUrl) ? this.helpUrl() : this.helpUrl;
- if (url) {
- window.open(url);
- }
- };
- /**
- * Show the context menu for this block.
- * @param {!Event} e Mouse event.
- * @private
- */
- Blockly.BlockSvg.prototype.showContextMenu_ = function(e) {
- if (this.workspace.options.readOnly || !this.contextMenu) {
- return;
- }
- // Save the current block in a variable for use in closures.
- var block = this;
- var options = [];
- if (this.isDeletable() && this.isMovable() && !block.isInFlyout) {
- // Option to duplicate this block.
- var duplicateOption = {
- text: Blockly.Msg.DUPLICATE_BLOCK,
- enabled: true,
- callback: function() {
- Blockly.duplicate_(block);
- }
- };
- if (this.getDescendants().length > this.workspace.remainingCapacity()) {
- duplicateOption.enabled = false;
- }
- options.push(duplicateOption);
- if (this.isEditable() && !this.collapsed_ &&
- this.workspace.options.comments) {
- // Option to add/remove a comment.
- var commentOption = {enabled: true};
- if (this.comment) {
- commentOption.text = Blockly.Msg.REMOVE_COMMENT;
- commentOption.callback = function() {
- block.setCommentText(null);
- };
- } else {
- commentOption.text = Blockly.Msg.ADD_COMMENT;
- commentOption.callback = function() {
- block.setCommentText('');
- };
- }
- options.push(commentOption);
- }
- // Option to make block inline.
- if (!this.collapsed_) {
- for (var i = 1; i < this.inputList.length; i++) {
- if (this.inputList[i - 1].type != Blockly.NEXT_STATEMENT &&
- this.inputList[i].type != Blockly.NEXT_STATEMENT) {
- // Only display this option if there are two value or dummy inputs
- // next to each other.
- var inlineOption = {enabled: true};
- var isInline = this.getInputsInline();
- inlineOption.text = isInline ?
- Blockly.Msg.EXTERNAL_INPUTS : Blockly.Msg.INLINE_INPUTS;
- inlineOption.callback = function() {
- block.setInputsInline(!isInline);
- };
- options.push(inlineOption);
- break;
- }
- }
- }
- if (this.workspace.options.collapse) {
- // Option to collapse/expand block.
- if (this.collapsed_) {
- var expandOption = {enabled: true};
- expandOption.text = Blockly.Msg.EXPAND_BLOCK;
- expandOption.callback = function() {
- block.setCollapsed(false);
- };
- options.push(expandOption);
- } else {
- var collapseOption = {enabled: true};
- collapseOption.text = Blockly.Msg.COLLAPSE_BLOCK;
- collapseOption.callback = function() {
- block.setCollapsed(true);
- };
- options.push(collapseOption);
- }
- }
- if (this.workspace.options.disable) {
- // Option to disable/enable block.
- var disableOption = {
- text: this.disabled ?
- Blockly.Msg.ENABLE_BLOCK : Blockly.Msg.DISABLE_BLOCK,
- enabled: !this.getInheritedDisabled(),
- callback: function() {
- block.setDisabled(!block.disabled);
- }
- };
- options.push(disableOption);
- }
- // Option to delete this block.
- // Count the number of blocks that are nested in this block.
- var descendantCount = this.getDescendants().length;
- var nextBlock = this.getNextBlock();
- if (nextBlock) {
- // Blocks in the current stack would survive this block's deletion.
- descendantCount -= nextBlock.getDescendants().length;
- }
- var deleteOption = {
- text: descendantCount == 1 ? Blockly.Msg.DELETE_BLOCK :
- Blockly.Msg.DELETE_X_BLOCKS.replace('%1', String(descendantCount)),
- enabled: true,
- callback: function() {
- block.dispose(true, true);
- }
- };
- options.push(deleteOption);
- }
- // Option to get help.
- var url = goog.isFunction(this.helpUrl) ? this.helpUrl() : this.helpUrl;
- var helpOption = {enabled: !!url};
- helpOption.text = Blockly.Msg.HELP;
- helpOption.callback = function() {
- block.showHelp_();
- };
- options.push(helpOption);
- // Allow the block to add or modify options.
- if (this.customContextMenu && !block.isInFlyout) {
- this.customContextMenu(options);
- }
- Blockly.ContextMenu.show(e, options, this.RTL);
- Blockly.ContextMenu.currentBlock = this;
- };
- /**
- * Move the connections for this block and all blocks attached under it.
- * Also update any attached bubbles.
- * @param {number} dx Horizontal offset from current location.
- * @param {number} dy Vertical offset from current location.
- * @private
- */
- Blockly.BlockSvg.prototype.moveConnections_ = function(dx, dy) {
- if (!this.rendered) {
- // Rendering is required to lay out the blocks.
- // This is probably an invisible block attached to a collapsed block.
- return;
- }
- var myConnections = this.getConnections_(false);
- for (var i = 0; i < myConnections.length; i++) {
- myConnections[i].moveBy(dx, dy);
- }
- var icons = this.getIcons();
- for (var i = 0; i < icons.length; i++) {
- icons[i].computeIconLocation();
- }
- // Recurse through all blocks attached under this one.
- for (var i = 0; i < this.childBlocks_.length; i++) {
- this.childBlocks_[i].moveConnections_(dx, dy);
- }
- };
- /**
- * Recursively adds or removes the dragging class to this node and its children.
- * @param {boolean} adding True if adding, false if removing.
- * @private
- */
- Blockly.BlockSvg.prototype.setDragging_ = function(adding) {
- if (adding) {
- this.addDragging();
- } else {
- this.removeDragging();
- }
- // Recurse through all blocks attached under this one.
- for (var i = 0; i < this.childBlocks_.length; i++) {
- this.childBlocks_[i].setDragging_(adding);
- }
- };
- /**
- * Drag this block to follow the mouse.
- * @param {!Event} e Mouse move event.
- * @private
- */
- Blockly.BlockSvg.prototype.onMouseMove_ = function(e) {
- var this_ = this;
- var workspace_ = this.workspace;
- Blockly.doCommand(function() {
- if (e.type == 'mousemove' && e.clientX <= 1 && e.clientY == 0 &&
- e.button == 0) {
- /* HACK:
- Safari Mobile 6.0 and Chrome for Android 18.0 fire rogue mousemove
- events on certain touch actions. Ignore events with these signatures.
- This may result in a one-pixel blind spot in other browsers,
- but this shouldn't be noticeable. */
- e.stopPropagation();
- return;
- }
- Blockly.removeAllRanges();
- var oldXY = this_.getRelativeToSurfaceXY();
- var newXY = workspace_.moveDrag(e);
- var group = this_.getSvgRoot();
- if (Blockly.dragMode_ == 1) {
- // Still dragging within the sticky DRAG_RADIUS.
- var dr = goog.math.Coordinate.distance(oldXY, newXY) * workspace_.scale;
- if (dr > Blockly.DRAG_RADIUS) {
- // Switch to unrestricted dragging.
- Blockly.dragMode_ = 2;
- Blockly.longStop_();
- group.translate_ = '';
- group.skew_ = '';
- if (this_.parentBlock_) {
- // Push this block to the very top of the stack.
- this_.setParent(null);
- this_.disconnectUiEffect();
- }
- this_.setDragging_(true);
- workspace_.recordDeleteAreas();
- }
- }
- if (Blockly.dragMode_ == 2) {
- // Unrestricted dragging.
- var dx = oldXY.x - this_.dragStartXY_.x;
- var dy = oldXY.y - this_.dragStartXY_.y;
- group.translate_ = 'translate(' + newXY.x + ',' + newXY.y + ')';
- group.setAttribute('transform', group.translate_ + group.skew_);
- // Drag all the nested bubbles.
- for (var i = 0; i < this_.draggedBubbles_.length; i++) {
- var commentData = this_.draggedBubbles_[i];
- commentData.bubble.setIconLocation(commentData.x + dx,
- commentData.y + dy);
- }
- // Check to see if any of this block's connections are within range of
- // another block's connection.
- var myConnections = this_.getConnections_(false);
- var closestConnection = null;
- var localConnection = null;
- var radiusConnection = Blockly.SNAP_RADIUS;
- for (var i = 0; i < myConnections.length; i++) {
- var myConnection = myConnections[i];
- var neighbour = myConnection.closest(radiusConnection, dx, dy);
- if (neighbour.connection) {
- closestConnection = neighbour.connection;
- localConnection = myConnection;
- radiusConnection = neighbour.radius;
- }
- }
- // Remove connection highlighting if needed.
- if (Blockly.highlightedConnection_ &&
- Blockly.highlightedConnection_ != closestConnection) {
- Blockly.highlightedConnection_.unhighlight();
- Blockly.highlightedConnection_ = null;
- Blockly.localConnection_ = null;
- }
- // Add connection highlighting if needed.
- if (closestConnection &&
- closestConnection != Blockly.highlightedConnection_) {
- closestConnection.highlight();
- Blockly.highlightedConnection_ = closestConnection;
- Blockly.localConnection_ = localConnection;
- }
- // Provide visual indication of whether the block will be deleted if
- // dropped here.
- if (this_.isDeletable()) {
- workspace_.isDeleteArea(e);
- }
- }
- // This event has been handled. No need to bubble up to the document.
- e.stopPropagation();
- });
- };
- /**
- * Add or remove the UI indicating if this block is movable or not.
- */
- Blockly.BlockSvg.prototype.updateMovable = function() {
- if (this.isMovable()) {
- Blockly.addClass_(/** @type {!Element} */ (this.svgGroup_),
- 'blocklyDraggable');
- } else {
- Blockly.removeClass_(/** @type {!Element} */ (this.svgGroup_),
- 'blocklyDraggable');
- }
- };
- /**
- * Set whether this block is movable or not.
- * @param {boolean} movable True if movable.
- */
- Blockly.BlockSvg.prototype.setMovable = function(movable) {
- Blockly.BlockSvg.superClass_.setMovable.call(this, movable);
- this.updateMovable();
- };
- /**
- * Set whether this block is editable or not.
- * @param {boolean} movable True if editable.
- */
- Blockly.BlockSvg.prototype.setEditable = function(editable) {
- Blockly.BlockSvg.superClass_.setEditable.call(this, editable);
- if (this.rendered) {
- for (var i = 0; i < this.icons_.length; i++) {
- this.icons_[i].updateEditable();
- }
- }
- };
- /**
- * Set whether this block is a shadow block or not.
- * @param {boolean} shadow True if a shadow.
- */
- Blockly.BlockSvg.prototype.setShadow = function(shadow) {
- Blockly.BlockSvg.superClass_.setShadow.call(this, shadow);
- this.updateColour();
- };
- /**
- * Return the root node of the SVG or null if none exists.
- * @return {Element} The root SVG node (probably a group).
- */
- Blockly.BlockSvg.prototype.getSvgRoot = function() {
- return this.svgGroup_;
- };
- // UI constants for rendering blocks.
- /**
- * Horizontal space between elements.
- * @const
- */
- Blockly.BlockSvg.SEP_SPACE_X = 10;
- /**
- * Vertical space between elements.
- * @const
- */
- Blockly.BlockSvg.SEP_SPACE_Y = 10;
- /**
- * Vertical padding around inline elements.
- * @const
- */
- Blockly.BlockSvg.INLINE_PADDING_Y = 5;
- /**
- * Minimum height of a block.
- * @const
- */
- Blockly.BlockSvg.MIN_BLOCK_Y = 25;
- /**
- * Height of horizontal puzzle tab.
- * @const
- */
- Blockly.BlockSvg.TAB_HEIGHT = 20;
- /**
- * Width of horizontal puzzle tab.
- * @const
- */
- Blockly.BlockSvg.TAB_WIDTH = 8;
- /**
- * Width of vertical tab (inc left margin).
- * @const
- */
- Blockly.BlockSvg.NOTCH_WIDTH = 30;
- /**
- * Rounded corner radius.
- * @const
- */
- Blockly.BlockSvg.CORNER_RADIUS = 8;
- /**
- * Do blocks with no previous or output connections have a 'hat' on top?
- * @const
- */
- Blockly.BlockSvg.START_HAT = false;
- /**
- * Path of the top hat's curve.
- * @const
- */
- Blockly.BlockSvg.START_HAT_PATH = 'c 30,-15 70,-15 100,0';
- /**
- * Path of the top hat's curve's highlight in LTR.
- * @const
- */
- Blockly.BlockSvg.START_HAT_HIGHLIGHT_LTR =
- 'c 17.8,-9.2 45.3,-14.9 75,-8.7 M 100.5,0.5';
- /**
- * Path of the top hat's curve's highlight in RTL.
- * @const
- */
- Blockly.BlockSvg.START_HAT_HIGHLIGHT_RTL =
- 'm 25,-8.7 c 29.7,-6.2 57.2,-0.5 75,8.7';
- /**
- * Distance from shape edge to intersect with a curved corner at 45 degrees.
- * Applies to highlighting on around the inside of a curve.
- * @const
- */
- Blockly.BlockSvg.DISTANCE_45_INSIDE = (1 - Math.SQRT1_2) *
- (Blockly.BlockSvg.CORNER_RADIUS - 0.5) + 0.5;
- /**
- * Distance from shape edge to intersect with a curved corner at 45 degrees.
- * Applies to highlighting on around the outside of a curve.
- * @const
- */
- Blockly.BlockSvg.DISTANCE_45_OUTSIDE = (1 - Math.SQRT1_2) *
- (Blockly.BlockSvg.CORNER_RADIUS + 0.5) - 0.5;
- /**
- * SVG path for drawing next/previous notch from left to right.
- * @const
- */
- Blockly.BlockSvg.NOTCH_PATH_LEFT = 'l 6,4 3,0 6,-4';
- /**
- * SVG path for drawing next/previous notch from left to right with
- * highlighting.
- * @const
- */
- Blockly.BlockSvg.NOTCH_PATH_LEFT_HIGHLIGHT = 'l 6,4 3,0 6,-4';
- /**
- * SVG path for drawing next/previous notch from right to left.
- * @const
- */
- Blockly.BlockSvg.NOTCH_PATH_RIGHT = 'l -6,4 -3,0 -6,-4';
- /**
- * SVG path for drawing jagged teeth at the end of collapsed blocks.
- * @const
- */
- Blockly.BlockSvg.JAGGED_TEETH = 'l 8,0 0,4 8,4 -16,8 8,4';
- /**
- * Height of SVG path for jagged teeth at the end of collapsed blocks.
- * @const
- */
- Blockly.BlockSvg.JAGGED_TEETH_HEIGHT = 20;
- /**
- * Width of SVG path for jagged teeth at the end of collapsed blocks.
- * @const
- */
- Blockly.BlockSvg.JAGGED_TEETH_WIDTH = 15;
- /**
- * SVG path for drawing a horizontal puzzle tab from top to bottom.
- * @const
- */
- Blockly.BlockSvg.TAB_PATH_DOWN = 'v 5 c 0,10 -' + Blockly.BlockSvg.TAB_WIDTH +
- ',-8 -' + Blockly.BlockSvg.TAB_WIDTH + ',7.5 s ' +
- Blockly.BlockSvg.TAB_WIDTH + ',-2.5 ' + Blockly.BlockSvg.TAB_WIDTH + ',7.5';
- /**
- * SVG path for drawing a horizontal puzzle tab from top to bottom with
- * highlighting from the upper-right.
- * @const
- */
- Blockly.BlockSvg.TAB_PATH_DOWN_HIGHLIGHT_RTL = 'v 6.5 m -' +
- (Blockly.BlockSvg.TAB_WIDTH * 0.97) + ',3 q -' +
- (Blockly.BlockSvg.TAB_WIDTH * 0.05) + ',10 ' +
- (Blockly.BlockSvg.TAB_WIDTH * 0.3) + ',9.5 m ' +
- (Blockly.BlockSvg.TAB_WIDTH * 0.67) + ',-1.9 v 1.4';
- /**
- * SVG start point for drawing the top-left corner.
- * @const
- */
- Blockly.BlockSvg.TOP_LEFT_CORNER_START =
- 'm 0,' + Blockly.BlockSvg.CORNER_RADIUS;
- /**
- * SVG start point for drawing the top-left corner's highlight in RTL.
- * @const
- */
- Blockly.BlockSvg.TOP_LEFT_CORNER_START_HIGHLIGHT_RTL =
- 'm ' + Blockly.BlockSvg.DISTANCE_45_INSIDE + ',' +
- Blockly.BlockSvg.DISTANCE_45_INSIDE;
- /**
- * SVG start point for drawing the top-left corner's highlight in LTR.
- * @const
- */
- Blockly.BlockSvg.TOP_LEFT_CORNER_START_HIGHLIGHT_LTR =
- 'm 0.5,' + (Blockly.BlockSvg.CORNER_RADIUS - 0.5);
- /**
- * 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 highlight on the rounded top-left corner.
- * @const
- */
- Blockly.BlockSvg.TOP_LEFT_CORNER_HIGHLIGHT =
- 'A ' + (Blockly.BlockSvg.CORNER_RADIUS - 0.5) + ',' +
- (Blockly.BlockSvg.CORNER_RADIUS - 0.5) + ' 0 0,1 ' +
- Blockly.BlockSvg.CORNER_RADIUS + ',0.5';
- /**
- * SVG path for drawing the top-left corner of a statement input.
- * Includes the top notch, a horizontal space, and the rounded inside corner.
- * @const
- */
- Blockly.BlockSvg.INNER_TOP_LEFT_CORNER =
- Blockly.BlockSvg.NOTCH_PATH_RIGHT + ' h -' +
- (Blockly.BlockSvg.NOTCH_WIDTH - 15 - Blockly.BlockSvg.CORNER_RADIUS) +
- ' h -0.5 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 drawing highlight on the top-left corner of a statement
- * input in RTL.
- * @const
- */
- Blockly.BlockSvg.INNER_TOP_LEFT_CORNER_HIGHLIGHT_RTL =
- 'a ' + Blockly.BlockSvg.CORNER_RADIUS + ',' +
- Blockly.BlockSvg.CORNER_RADIUS + ' 0 0,0 ' +
- (-Blockly.BlockSvg.DISTANCE_45_OUTSIDE - 0.5) + ',' +
- (Blockly.BlockSvg.CORNER_RADIUS -
- Blockly.BlockSvg.DISTANCE_45_OUTSIDE);
- /**
- * SVG path for drawing highlight on the bottom-left corner of a statement
- * input in RTL.
- * @const
- */
- Blockly.BlockSvg.INNER_BOTTOM_LEFT_CORNER_HIGHLIGHT_RTL =
- 'a ' + (Blockly.BlockSvg.CORNER_RADIUS + 0.5) + ',' +
- (Blockly.BlockSvg.CORNER_RADIUS + 0.5) + ' 0 0,0 ' +
- (Blockly.BlockSvg.CORNER_RADIUS + 0.5) + ',' +
- (Blockly.BlockSvg.CORNER_RADIUS + 0.5);
- /**
- * SVG path for drawing highlight on the bottom-left corner of a statement
- * input in LTR.
- * @const
- */
- Blockly.BlockSvg.INNER_BOTTOM_LEFT_CORNER_HIGHLIGHT_LTR =
- 'a ' + (Blockly.BlockSvg.CORNER_RADIUS + 0.5) + ',' +
- (Blockly.BlockSvg.CORNER_RADIUS + 0.5) + ' 0 0,0 ' +
- (Blockly.BlockSvg.CORNER_RADIUS -
- Blockly.BlockSvg.DISTANCE_45_OUTSIDE) + ',' +
- (Blockly.BlockSvg.DISTANCE_45_OUTSIDE + 0.5);
- /**
- * Dispose of this block.
- * @param {boolean} healStack If true, then try to heal any gap by connecting
- * the next statement with the previous statement. Otherwise, dispose of
- * all children of this block.
- * @param {boolean} animate If true, show a disposal animation and sound.
- * @param {boolean=} opt_dontRemoveFromWorkspace If true, don't remove this
- * block from the workspace's list of top blocks.
- */
- Blockly.BlockSvg.prototype.dispose = function(healStack, animate,
- opt_dontRemoveFromWorkspace) {
- Blockly.Field.startCache();
- // Terminate onchange event calls.
- if (this.onchangeWrapper_) {
- Blockly.unbindEvent_(this.onchangeWrapper_);
- this.onchangeWrapper_ = null;
- }
- // If this block is being dragged, unlink the mouse events.
- if (Blockly.selected == this) {
- Blockly.terminateDrag_();
- }
- // If this block has a context menu open, close it.
- if (Blockly.ContextMenu.currentBlock == this) {
- Blockly.ContextMenu.hide();
- }
- if (animate && this.rendered) {
- this.unplug(healStack, false);
- this.disposeUiEffect();
- }
- // Stop rerendering.
- this.rendered = false;
- var icons = this.getIcons();
- for (var i = 0; i < icons.length; i++) {
- icons[i].dispose();
- }
- Blockly.BlockSvg.superClass_.dispose.call(this, healStack);
- goog.dom.removeNode(this.svgGroup_);
- // Sever JavaScript to DOM connections.
- this.svgGroup_ = null;
- this.svgPath_ = null;
- this.svgPathLight_ = null;
- this.svgPathDark_ = null;
- Blockly.Field.stopCache();
- };
- /**
- * Play some UI effects (sound, animation) when disposing of a block.
- */
- Blockly.BlockSvg.prototype.disposeUiEffect = function() {
- this.workspace.playAudio('delete');
- var xy = Blockly.getSvgXY_(/** @type {!Element} */ (this.svgGroup_),
- this.workspace);
- // Deeply clone the current block.
- var clone = this.svgGroup_.cloneNode(true);
- clone.translateX_ = xy.x;
- clone.translateY_ = xy.y;
- clone.setAttribute('transform',
- 'translate(' + clone.translateX_ + ',' + clone.translateY_ + ')');
- this.workspace.options.svg.appendChild(clone);
- clone.bBox_ = clone.getBBox();
- // Start the animation.
- Blockly.BlockSvg.disposeUiStep_(clone, this.RTL, new Date(),
- this.workspace.scale);
- };
- /**
- * Animate a cloned block and eventually dispose of it.
- * This is a class method, not an instace method since the original block has
- * been destroyed and is no longer accessible.
- * @param {!Element} clone SVG element to animate and dispose of.
- * @param {boolean} rtl True if RTL, false if LTR.
- * @param {!Date} start Date of animation's start.
- * @param {number} workspaceScale Scale of workspace.
- * @private
- */
- Blockly.BlockSvg.disposeUiStep_ = function(clone, rtl, start, workspaceScale) {
- var ms = (new Date()) - start;
- var percent = ms / 150;
- if (percent > 1) {
- goog.dom.removeNode(clone);
- } else {
- var x = clone.translateX_ +
- (rtl ? -1 : 1) * clone.bBox_.width * workspaceScale / 2 * percent;
- var y = clone.translateY_ + clone.bBox_.height * workspaceScale * percent;
- var scale = (1 - percent) * workspaceScale;
- clone.setAttribute('transform', 'translate(' + x + ',' + y + ')' +
- ' scale(' + scale + ')');
- var closure = function() {
- Blockly.BlockSvg.disposeUiStep_(clone, rtl, start, workspaceScale);
- };
- setTimeout(closure, 10);
- }
- };
- /**
- * Play some UI effects (sound, ripple) after a connection has been established.
- */
- Blockly.BlockSvg.prototype.connectionUiEffect = function() {
- this.workspace.playAudio('click');
- if (this.workspace.scale < 1) {
- return; // Too small to care about visual effects.
- }
- // Determine the absolute coordinates of the inferior block.
- var xy = Blockly.getSvgXY_(/** @type {!Element} */ (this.svgGroup_),
- this.workspace);
- // Offset the coordinates based on the two connection types, fix scale.
- if (this.outputConnection) {
- xy.x += (this.RTL ? 3 : -3) * this.workspace.scale;
- xy.y += 13 * this.workspace.scale;
- } else if (this.previousConnection) {
- xy.x += (this.RTL ? -23 : 23) * this.workspace.scale;
- xy.y += 3 * this.workspace.scale;
- }
- var ripple = Blockly.createSvgElement('circle',
- {'cx': xy.x, 'cy': xy.y, 'r': 0, 'fill': 'none',
- 'stroke': '#888', 'stroke-width': 10},
- this.workspace.options.svg);
- // Start the animation.
- Blockly.BlockSvg.connectionUiStep_(ripple, new Date(), this.workspace.scale);
- };
- /**
- * Expand a ripple around a connection.
- * @param {!Element} ripple Element to animate.
- * @param {!Date} start Date of animation's start.
- * @param {number} workspaceScale Scale of workspace.
- * @private
- */
- Blockly.BlockSvg.connectionUiStep_ = function(ripple, start, workspaceScale) {
- var ms = (new Date()) - start;
- var percent = ms / 150;
- if (percent > 1) {
- goog.dom.removeNode(ripple);
- } else {
- ripple.setAttribute('r', percent * 25 * workspaceScale);
- ripple.style.opacity = 1 - percent;
- var closure = function() {
- Blockly.BlockSvg.connectionUiStep_(ripple, start, workspaceScale);
- };
- Blockly.BlockSvg.disconnectUiStop_.pid_ = setTimeout(closure, 10);
- }
- };
- /**
- * Play some UI effects (sound, animation) when disconnecting a block.
- */
- Blockly.BlockSvg.prototype.disconnectUiEffect = function() {
- this.workspace.playAudio('disconnect');
- if (this.workspace.scale < 1) {
- return; // Too small to care about visual effects.
- }
- // Horizontal distance for bottom of block to wiggle.
- var DISPLACEMENT = 10;
- // Scale magnitude of skew to height of block.
- var height = this.getHeightWidth().height;
- var magnitude = Math.atan(DISPLACEMENT / height) / Math.PI * 180;
- if (!this.RTL) {
- magnitude *= -1;
- }
- // Start the animation.
- Blockly.BlockSvg.disconnectUiStep_(this.svgGroup_, magnitude, new Date());
- };
- /**
- * Animate a brief wiggle of a disconnected block.
- * @param {!Element} group SVG element to animate.
- * @param {number} magnitude Maximum degrees skew (reversed for RTL).
- * @param {!Date} start Date of animation's start.
- * @private
- */
- Blockly.BlockSvg.disconnectUiStep_ = function(group, magnitude, start) {
- var DURATION = 200; // Milliseconds.
- var WIGGLES = 3; // Half oscillations.
- var ms = (new Date()) - start;
- var percent = ms / DURATION;
- if (percent > 1) {
- group.skew_ = '';
- } else {
- var skew = Math.round(Math.sin(percent * Math.PI * WIGGLES) *
- (1 - percent) * magnitude);
- group.skew_ = 'skewX(' + skew + ')';
- var closure = function() {
- Blockly.BlockSvg.disconnectUiStep_(group, magnitude, start);
- };
- Blockly.BlockSvg.disconnectUiStop_.group = group;
- Blockly.BlockSvg.disconnectUiStop_.pid = setTimeout(closure, 10);
- }
- group.setAttribute('transform', group.translate_ + group.skew_);
- };
- /**
- * Stop the disconnect UI animation immediately.
- * @private
- */
- Blockly.BlockSvg.disconnectUiStop_ = function() {
- if (Blockly.BlockSvg.disconnectUiStop_.group) {
- clearTimeout(Blockly.BlockSvg.disconnectUiStop_.pid);
- var group = Blockly.BlockSvg.disconnectUiStop_.group
- group.skew_ = '';
- group.setAttribute('transform', group.translate_);
- Blockly.BlockSvg.disconnectUiStop_.group = null;
- }
- };
- /**
- * PID of disconnect UI animation. There can only be one at a time.
- * @type {number}
- */
- Blockly.BlockSvg.disconnectUiStop_.pid = 0;
- /**
- * SVG group of wobbling block. There can only be one at a time.
- * @type {Element}
- */
- Blockly.BlockSvg.disconnectUiStop_.group = null;
- /**
- * Change the colour of a block.
- */
- Blockly.BlockSvg.prototype.updateColour = function() {
- if (this.disabled) {
- // Disabled blocks don't have colour.
- return;
- }
- var hexColour = Blockly.makeColour(this.getColour());
- var rgb = goog.color.hexToRgb(hexColour);
- if (this.isShadow()) {
- rgb = goog.color.lighten(rgb, 0.6);
- hexColour = goog.color.rgbArrayToHex(rgb);
- this.svgPathLight_.style.display = 'none';
- this.svgPathDark_.setAttribute('fill', hexColour);
- } else {
- this.svgPathLight_.style.display = '';
- var hexLight = goog.color.rgbArrayToHex(goog.color.lighten(rgb, 0.3));
- var hexDark = goog.color.rgbArrayToHex(goog.color.darken(rgb, 0.2));
- this.svgPathLight_.setAttribute('stroke', hexLight);
- this.svgPathDark_.setAttribute('fill', hexDark);
- }
- this.svgPath_.setAttribute('fill', hexColour);
- 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);
- }
- }
- };
- /**
- * Enable or disable a block.
- */
- Blockly.BlockSvg.prototype.updateDisabled = function() {
- var hasClass = Blockly.hasClass_(/** @type {!Element} */ (this.svgGroup_),
- 'blocklyDisabled');
- if (this.disabled || this.getInheritedDisabled()) {
- if (!hasClass) {
- Blockly.addClass_(/** @type {!Element} */ (this.svgGroup_),
- 'blocklyDisabled');
- this.svgPath_.setAttribute('fill',
- 'url(#' + this.workspace.options.disabledPatternId + ')');
- }
- } else {
- if (hasClass) {
- Blockly.removeClass_(/** @type {!Element} */ (this.svgGroup_),
- 'blocklyDisabled');
- this.updateColour();
- }
- }
- var children = this.getChildren();
- for (var i = 0, child; child = children[i]; i++) {
- child.updateDisabled();
- }
- };
- /**
- * Returns the comment on this block (or '' if none).
- * @return {string} Block's comment.
- */
- Blockly.BlockSvg.prototype.getCommentText = function() {
- if (this.comment) {
- var comment = this.comment.getText();
- // Trim off trailing whitespace.
- return comment.replace(/\s+$/, '').replace(/ +\n/g, '\n');
- }
- return '';
- };
- /**
- * Set this block's comment text.
- * @param {?string} text The text, or null to delete.
- */
- Blockly.BlockSvg.prototype.setCommentText = function(text) {
- var changedState = false;
- if (goog.isString(text)) {
- if (!this.comment) {
- this.comment = new Blockly.Comment(this);
- changedState = true;
- }
- this.comment.setText(/** @type {string} */ (text));
- } else {
- if (this.comment) {
- this.comment.dispose();
- changedState = true;
- }
- }
- if (changedState && this.rendered) {
- this.render();
- // Adding or removing a comment icon will cause the block to change shape.
- this.bumpNeighbours_();
- }
- };
- /**
- * Set this block's warning text.
- * @param {?string} text The text, or null to delete.
- * @param {string=} opt_id An optional ID for the warning text to be able to
- * maintain multiple warnings.
- */
- Blockly.BlockSvg.prototype.setWarningText = function(text, opt_id) {
- if (!this.setWarningText.pid_) {
- // Create a database of warning PIDs.
- // Only runs once per block (and only those with warnings).
- this.setWarningText.pid_ = Object.create(null);
- }
- var id = opt_id || '';
- if (!id) {
- // Kill all previous pending processes, this edit supercedes them all.
- for (var n in this.setWarningText.pid_) {
- clearTimeout(this.setWarningText.pid_[n]);
- delete this.setWarningText.pid_[n];
- }
- } else if (this.setWarningText.pid_[id]) {
- // Only queue up the latest change. Kill any earlier pending process.
- clearTimeout(this.setWarningText.pid_[id]);
- delete this.setWarningText.pid_[id];
- }
- if (Blockly.dragMode_ == 2) {
- // Don't change the warning text during a drag.
- // Wait until the drag finishes.
- var thisBlock = this;
- this.setWarningText.pid_[id] = setTimeout(function() {
- if (thisBlock.workspace) { // Check block wasn't deleted.
- delete thisBlock.setWarningText.pid_[id];
- thisBlock.setWarningText(text, id);
- }
- }, 100);
- return;
- }
- if (this.isInFlyout) {
- text = null;
- }
- // Bubble up to add a warning on top-most collapsed block.
- var parent = this.getSurroundParent();
- var collapsedParent = null;
- while (parent) {
- if (parent.isCollapsed()) {
- collapsedParent = parent;
- }
- parent = parent.getSurroundParent();
- }
- if (collapsedParent) {
- collapsedParent.setWarningText(text, 'collapsed ' + this.id + ' ' + id);
- }
- var changedState = false;
- if (goog.isString(text)) {
- if (!this.warning) {
- this.warning = new Blockly.Warning(this);
- changedState = true;
- }
- this.warning.setText(/** @type {string} */ (text), id);
- } else {
- // Dispose all warnings if no id is given.
- if (this.warning && !id) {
- this.warning.dispose();
- changedState = true;
- } else if (this.warning) {
- var oldText = this.warning.getText();
- this.warning.setText('', id);
- var newText = this.warning.getText();
- if (!newText) {
- this.warning.dispose();
- }
- changedState = oldText == newText;
- }
- }
- if (changedState && this.rendered) {
- this.render();
- // Adding or removing a warning icon will cause the block to change shape.
- this.bumpNeighbours_();
- }
- };
- /**
- * Give this block a mutator dialog.
- * @param {Blockly.Mutator} mutator A mutator dialog instance or null to remove.
- */
- Blockly.BlockSvg.prototype.setMutator = function(mutator) {
- if (this.mutator && this.mutator !== mutator) {
- this.mutator.dispose();
- }
- if (mutator) {
- mutator.block_ = this;
- this.mutator = mutator;
- if (this.rendered) {
- mutator.createIcon();
- }
- }
- };
- /**
- * Set whether the block is disabled or not.
- * @param {boolean} disabled True if disabled.
- */
- Blockly.BlockSvg.prototype.setDisabled = function(disabled) {
- if (this.disabled == disabled) {
- return;
- }
- Blockly.BlockSvg.superClass_.setDisabled.call(this, disabled);
- if (this.rendered) {
- this.updateDisabled();
- }
- this.workspace.fireChangeEvent();
- };
- /**
- * Select this block. Highlight it visually.
- */
- Blockly.BlockSvg.prototype.addSelect = function() {
- Blockly.addClass_(/** @type {!Element} */ (this.svgGroup_),
- 'blocklySelected');
- // Move the selected block to the top of the stack.
- this.svgGroup_.parentNode.appendChild(this.svgGroup_);
- };
- /**
- * Unselect this block. Remove its highlighting.
- */
- Blockly.BlockSvg.prototype.removeSelect = function() {
- Blockly.removeClass_(/** @type {!Element} */ (this.svgGroup_),
- 'blocklySelected');
- };
- /**
- * Adds the dragging class to this block.
- * Also disables the highlights/shadows to improve performance.
- */
- Blockly.BlockSvg.prototype.addDragging = function() {
- Blockly.addClass_(/** @type {!Element} */ (this.svgGroup_),
- 'blocklyDragging');
- };
- /**
- * Removes the dragging class from this block.
- */
- Blockly.BlockSvg.prototype.removeDragging = function() {
- Blockly.removeClass_(/** @type {!Element} */ (this.svgGroup_),
- 'blocklyDragging');
- };
- /**
- * 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();
- for (var i = 0; i < icons.length; i++) {
- 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.
- var inputRows = this.renderCompute_(cursorX);
- this.renderDraw_(cursorX, inputRows);
- 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.fireUiEvent(window, 'resize');
- }
- }
- Blockly.Field.stopCache();
- Blockly.Realtime.blockChanged(this);
- };
- /**
- * 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 to start the fields.
- * @return {number} X-coordinate of the end of the field row (plus a gap).
- * @private
- */
- Blockly.BlockSvg.prototype.renderFields_ =
- function(fieldList, cursorX, cursorY) {
- cursorY += Blockly.BlockSvg.INLINE_PADDING_Y;
- if (this.RTL) {
- cursorX = -cursorX;
- }
- for (var t = 0, field; field = fieldList[t]; t++) {
- var root = field.getSvgRoot();
- if (!root) {
- continue;
- }
- if (this.RTL) {
- cursorX -= field.renderSep + field.renderWidth;
- root.setAttribute('transform',
- 'translate(' + cursorX + ',' + cursorY + ')');
- if (field.renderWidth) {
- cursorX -= Blockly.BlockSvg.SEP_SPACE_X;
- }
- } else {
- root.setAttribute('transform',
- 'translate(' + (cursorX + field.renderSep) + ',' + cursorY + ')');
- if (field.renderWidth) {
- cursorX += field.renderSep + field.renderWidth +
- Blockly.BlockSvg.SEP_SPACE_X;
- }
- }
- }
- 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 = [];
- inputRows.rightEdge = iconWidth + Blockly.BlockSvg.SEP_SPACE_X * 2;
- if (this.previousConnection || this.nextConnection) {
- inputRows.rightEdge = Math.max(inputRows.rightEdge,
- Blockly.BlockSvg.NOTCH_WIDTH + Blockly.BlockSvg.SEP_SPACE_X);
- }
- 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;
- var isInline = this.getInputsInline() && !this.isCollapsed();
- for (var i = 0, input; input = inputList[i]; i++) {
- if (!input.isVisible()) {
- continue;
- }
- var row;
- if (!isInline || !lastType ||
- lastType == Blockly.NEXT_STATEMENT ||
- input.type == Blockly.NEXT_STATEMENT) {
- // Create new row.
- lastType = input.type;
- row = [];
- if (isInline && input.type != Blockly.NEXT_STATEMENT) {
- row.type = Blockly.BlockSvg.INLINE;
- } else {
- row.type = input.type;
- }
- row.height = 0;
- inputRows.push(row);
- } else {
- row = inputRows[inputRows.length - 1];
- }
- row.push(input);
- // Compute minimum input size.
- input.renderHeight = Blockly.BlockSvg.MIN_BLOCK_Y;
- // The width is currently only needed for inline value inputs.
- if (isInline && input.type == Blockly.INPUT_VALUE) {
- input.renderWidth = Blockly.BlockSvg.TAB_WIDTH +
- Blockly.BlockSvg.SEP_SPACE_X * 1.25;
- } else {
- input.renderWidth = 0;
- }
- // Expand input size if there is a connection.
- if (input.connection && input.connection.targetConnection) {
- var linkedBlock = input.connection.targetBlock();
- var bBox = linkedBlock.getHeightWidth();
- input.renderHeight = Math.max(input.renderHeight, bBox.height);
- input.renderWidth = Math.max(input.renderWidth, bBox.width);
- }
- // Blocks have a one pixel shadow that should sometimes overhang.
- if (!isInline && i == inputList.length - 1) {
- // Last value input should overhang.
- input.renderHeight--;
- } else if (!isInline && input.type == Blockly.INPUT_VALUE &&
- inputList[i + 1] && inputList[i + 1].type == Blockly.NEXT_STATEMENT) {
- // Value input above statement input should overhang.
- input.renderHeight--;
- }
- 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;
- 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);
- }
- }
- }
- // Make inline rows a bit thicker in order to enclose the values.
- for (var y = 0, row; row = inputRows[y]; y++) {
- row.thicker = false;
- if (row.type == Blockly.BlockSvg.INLINE) {
- for (var z = 0, input; input = row[z]; z++) {
- if (input.type == Blockly.INPUT_VALUE) {
- row.height += 2 * Blockly.BlockSvg.INLINE_PADDING_Y;
- row.thicker = true;
- break;
- }
- }
- }
- }
- // Compute the statement edge.
- // This is the width of a block where statements are nested.
- inputRows.statementEdge = 2 * Blockly.BlockSvg.SEP_SPACE_X +
- fieldStatementWidth;
- // Compute the preferred right edge. Inline blocks may extend beyond.
- // This is the width of the block where external inputs connect.
- if (hasStatement) {
- inputRows.rightEdge = Math.max(inputRows.rightEdge,
- inputRows.statementEdge + Blockly.BlockSvg.NOTCH_WIDTH);
- }
- if (hasValue) {
- inputRows.rightEdge = Math.max(inputRows.rightEdge, fieldValueWidth +
- Blockly.BlockSvg.SEP_SPACE_X * 2 + Blockly.BlockSvg.TAB_WIDTH);
- } else if (hasDummy) {
- inputRows.rightEdge = Math.max(inputRows.rightEdge, fieldValueWidth +
- Blockly.BlockSvg.SEP_SPACE_X * 2);
- }
- inputRows.hasValue = hasValue;
- inputRows.hasStatement = hasStatement;
- inputRows.hasDummy = hasDummy;
- return inputRows;
- };
- /**
- * 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 and bottom left corners be rounded or square?
- if (this.outputConnection) {
- this.squareTopLeftCorner_ = true;
- this.squareBottomLeftCorner_ = true;
- } else {
- this.squareTopLeftCorner_ = false;
- this.squareBottomLeftCorner_ = false;
- // If this block is in the middle of a stack, square the corners.
- if (this.previousConnection) {
- var prevBlock = this.previousConnection.targetBlock();
- if (prevBlock && prevBlock.getNextBlock() == this) {
- this.squareTopLeftCorner_ = true;
- }
- } else if (Blockly.BlockSvg.START_HAT) {
- // No output or previous connection.
- this.squareTopLeftCorner_ = true;
- this.startHat_ = true;
- inputRows.rightEdge = Math.max(inputRows.rightEdge, 100);
- }
- var nextBlock = this.getNextBlock();
- if (nextBlock) {
- this.squareBottomLeftCorner_ = true;
- }
- }
- // Fetch the block's coordinates on the surface for use in anchoring
- // the connections.
- var connectionsXY = this.getRelativeToSurfaceXY();
- // Assemble the block's path.
- var steps = [];
- var inlineSteps = [];
- // The highlighting applies to edges facing the upper-left corner.
- // Since highlighting is a two-pixel wide border, it would normally overhang
- // the edge of the block by a pixel. So undersize all measurements by a pixel.
- var highlightSteps = [];
- var highlightInlineSteps = [];
- this.renderDrawTop_(steps, highlightSteps, connectionsXY,
- inputRows.rightEdge);
- var cursorY = this.renderDrawRight_(steps, highlightSteps, inlineSteps,
- highlightInlineSteps, connectionsXY, inputRows, iconWidth);
- this.renderDrawBottom_(steps, highlightSteps, connectionsXY, cursorY);
- this.renderDrawLeft_(steps, highlightSteps, connectionsXY, cursorY);
- var pathString = steps.join(' ') + '\n' + inlineSteps.join(' ');
- this.svgPath_.setAttribute('d', pathString);
- this.svgPathDark_.setAttribute('d', pathString);
- pathString = highlightSteps.join(' ') + '\n' + highlightInlineSteps.join(' ');
- this.svgPathLight_.setAttribute('d', pathString);
- if (this.RTL) {
- // Mirror the block's path.
- this.svgPath_.setAttribute('transform', 'scale(-1 1)');
- this.svgPathLight_.setAttribute('transform', 'scale(-1 1)');
- this.svgPathDark_.setAttribute('transform', 'translate(1,1) scale(-1 1)');
- }
- };
- /**
- * Render the top edge of the block.
- * @param {!Array.<string>} steps Path of block outline.
- * @param {!Array.<string>} highlightSteps Path of block highlights.
- * @param {!Object} connectionsXY Location of block.
- * @param {number} rightEdge Minimum width of block.
- * @private
- */
- Blockly.BlockSvg.prototype.renderDrawTop_ =
- function(steps, highlightSteps, connectionsXY, rightEdge) {
- // Position the cursor at the top-left starting point.
- if (this.squareTopLeftCorner_) {
- steps.push('m 0,0');
- highlightSteps.push('m 0.5,0.5');
- if (this.startHat_) {
- steps.push(Blockly.BlockSvg.START_HAT_PATH);
- highlightSteps.push(this.RTL ?
- Blockly.BlockSvg.START_HAT_HIGHLIGHT_RTL :
- Blockly.BlockSvg.START_HAT_HIGHLIGHT_LTR);
- }
- } else {
- steps.push(Blockly.BlockSvg.TOP_LEFT_CORNER_START);
- highlightSteps.push(this.RTL ?
- Blockly.BlockSvg.TOP_LEFT_CORNER_START_HIGHLIGHT_RTL :
- Blockly.BlockSvg.TOP_LEFT_CORNER_START_HIGHLIGHT_LTR);
- // Top-left rounded corner.
- steps.push(Blockly.BlockSvg.TOP_LEFT_CORNER);
- highlightSteps.push(Blockly.BlockSvg.TOP_LEFT_CORNER_HIGHLIGHT);
- }
- // Top edge.
- if (this.previousConnection) {
- steps.push('H', Blockly.BlockSvg.NOTCH_WIDTH - 15);
- highlightSteps.push('H', Blockly.BlockSvg.NOTCH_WIDTH - 15);
- steps.push(Blockly.BlockSvg.NOTCH_PATH_LEFT);
- highlightSteps.push(Blockly.BlockSvg.NOTCH_PATH_LEFT_HIGHLIGHT);
- // Create previous block connection.
- var connectionX = connectionsXY.x + (this.RTL ?
- -Blockly.BlockSvg.NOTCH_WIDTH : Blockly.BlockSvg.NOTCH_WIDTH);
- var connectionY = connectionsXY.y;
- this.previousConnection.moveTo(connectionX, connectionY);
- // This connection will be tightened when the parent renders.
- }
- steps.push('H', rightEdge);
- highlightSteps.push('H', rightEdge - 0.5);
- this.width = rightEdge;
- };
- /**
- * Render the right edge of the block.
- * @param {!Array.<string>} steps Path of block outline.
- * @param {!Array.<string>} highlightSteps Path of block highlights.
- * @param {!Array.<string>} inlineSteps Inline block outlines.
- * @param {!Array.<string>} highlightInlineSteps Inline block highlights.
- * @param {!Object} connectionsXY Location of block.
- * @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, highlightSteps,
- inlineSteps, highlightInlineSteps, connectionsXY, inputRows, iconWidth) {
- var cursorX;
- var cursorY = 0;
- var connectionX, connectionY;
- for (var y = 0, row; row = inputRows[y]; y++) {
- cursorX = Blockly.BlockSvg.SEP_SPACE_X;
- if (y == 0) {
- cursorX += this.RTL ? -iconWidth : iconWidth;
- }
- highlightSteps.push('M', (inputRows.rightEdge - 0.5) + ',' +
- (cursorY + 0.5));
- if (this.isCollapsed()) {
- // Jagged right edge.
- var input = row[0];
- var fieldX = cursorX;
- var fieldY = cursorY;
- this.renderFields_(input.fieldRow, fieldX, fieldY);
- steps.push(Blockly.BlockSvg.JAGGED_TEETH);
- highlightSteps.push('h 8');
- var remainder = row.height - Blockly.BlockSvg.JAGGED_TEETH_HEIGHT;
- steps.push('v', remainder);
- if (this.RTL) {
- highlightSteps.push('v 3.9 l 7.2,3.4 m -14.5,8.9 l 7.3,3.5');
- highlightSteps.push('v', remainder - 0.7);
- }
- this.width += Blockly.BlockSvg.JAGGED_TEETH_WIDTH;
- } else if (row.type == Blockly.BlockSvg.INLINE) {
- // Inline inputs.
- for (var x = 0, input; input = row[x]; x++) {
- var fieldX = cursorX;
- var fieldY = cursorY;
- if (row.thicker) {
- // Lower the field slightly.
- fieldY += Blockly.BlockSvg.INLINE_PADDING_Y;
- }
- // TODO: Align inline field rows (left/right/centre).
- cursorX = this.renderFields_(input.fieldRow, fieldX, fieldY);
- if (input.type != Blockly.DUMMY_INPUT) {
- cursorX += input.renderWidth + Blockly.BlockSvg.SEP_SPACE_X;
- }
- if (input.type == Blockly.INPUT_VALUE) {
- inlineSteps.push('M', (cursorX - Blockly.BlockSvg.SEP_SPACE_X) +
- ',' + (cursorY + Blockly.BlockSvg.INLINE_PADDING_Y));
- inlineSteps.push('h', Blockly.BlockSvg.TAB_WIDTH - 2 -
- input.renderWidth);
- inlineSteps.push(Blockly.BlockSvg.TAB_PATH_DOWN);
- inlineSteps.push('v', input.renderHeight + 1 -
- Blockly.BlockSvg.TAB_HEIGHT);
- inlineSteps.push('h', input.renderWidth + 2 -
- Blockly.BlockSvg.TAB_WIDTH);
- inlineSteps.push('z');
- if (this.RTL) {
- // Highlight right edge, around back of tab, and bottom.
- highlightInlineSteps.push('M',
- (cursorX - Blockly.BlockSvg.SEP_SPACE_X - 2.5 +
- Blockly.BlockSvg.TAB_WIDTH - input.renderWidth) + ',' +
- (cursorY + Blockly.BlockSvg.INLINE_PADDING_Y + 0.5));
- highlightInlineSteps.push(
- Blockly.BlockSvg.TAB_PATH_DOWN_HIGHLIGHT_RTL);
- highlightInlineSteps.push('v',
- input.renderHeight - Blockly.BlockSvg.TAB_HEIGHT + 2.5);
- highlightInlineSteps.push('h',
- input.renderWidth - Blockly.BlockSvg.TAB_WIDTH + 2);
- } else {
- // Highlight right edge, bottom.
- highlightInlineSteps.push('M',
- (cursorX - Blockly.BlockSvg.SEP_SPACE_X + 0.5) + ',' +
- (cursorY + Blockly.BlockSvg.INLINE_PADDING_Y + 0.5));
- highlightInlineSteps.push('v', input.renderHeight + 1);
- highlightInlineSteps.push('h', Blockly.BlockSvg.TAB_WIDTH - 2 -
- input.renderWidth);
- // Short highlight glint at bottom of tab.
- highlightInlineSteps.push('M',
- (cursorX - input.renderWidth - Blockly.BlockSvg.SEP_SPACE_X +
- 0.9) + ',' + (cursorY + Blockly.BlockSvg.INLINE_PADDING_Y +
- Blockly.BlockSvg.TAB_HEIGHT - 0.7));
- highlightInlineSteps.push('l',
- (Blockly.BlockSvg.TAB_WIDTH * 0.46) + ',-2.1');
- }
- // Create inline input connection.
- if (this.RTL) {
- connectionX = connectionsXY.x - cursorX -
- Blockly.BlockSvg.TAB_WIDTH + Blockly.BlockSvg.SEP_SPACE_X +
- input.renderWidth + 1;
- } else {
- connectionX = connectionsXY.x + cursorX +
- Blockly.BlockSvg.TAB_WIDTH - Blockly.BlockSvg.SEP_SPACE_X -
- input.renderWidth - 1;
- }
- connectionY = connectionsXY.y + cursorY +
- Blockly.BlockSvg.INLINE_PADDING_Y + 1;
- input.connection.moveTo(connectionX, connectionY);
- if (input.connection.targetConnection) {
- input.connection.tighten_();
- }
- }
- }
- cursorX = Math.max(cursorX, inputRows.rightEdge);
- this.width = Math.max(this.width, cursorX);
- steps.push('H', cursorX);
- highlightSteps.push('H', cursorX - 0.5);
- steps.push('v', row.height);
- if (this.RTL) {
- highlightSteps.push('v', row.height - 1);
- }
- } else if (row.type == Blockly.INPUT_VALUE) {
- // External input.
- var input = row[0];
- var fieldX = cursorX;
- var fieldY = cursorY;
- if (input.align != Blockly.ALIGN_LEFT) {
- var fieldRightX = inputRows.rightEdge - input.fieldWidth -
- Blockly.BlockSvg.TAB_WIDTH - 2 * Blockly.BlockSvg.SEP_SPACE_X;
- if (input.align == Blockly.ALIGN_RIGHT) {
- fieldX += fieldRightX;
- } else if (input.align == Blockly.ALIGN_CENTRE) {
- fieldX += fieldRightX / 2;
- }
- }
- this.renderFields_(input.fieldRow, fieldX, fieldY);
- steps.push(Blockly.BlockSvg.TAB_PATH_DOWN);
- var v = row.height - Blockly.BlockSvg.TAB_HEIGHT;
- steps.push('v', v);
- if (this.RTL) {
- // Highlight around back of tab.
- highlightSteps.push(Blockly.BlockSvg.TAB_PATH_DOWN_HIGHLIGHT_RTL);
- highlightSteps.push('v', v + 0.5);
- } else {
- // Short highlight glint at bottom of tab.
- highlightSteps.push('M', (inputRows.rightEdge - 5) + ',' +
- (cursorY + Blockly.BlockSvg.TAB_HEIGHT - 0.7));
- highlightSteps.push('l', (Blockly.BlockSvg.TAB_WIDTH * 0.46) +
- ',-2.1');
- }
- // Create external input connection.
- connectionX = connectionsXY.x +
- (this.RTL ? -inputRows.rightEdge - 1 : inputRows.rightEdge + 1);
- connectionY = connectionsXY.y + cursorY;
- input.connection.moveTo(connectionX, connectionY);
- if (input.connection.targetConnection) {
- input.connection.tighten_();
- this.width = Math.max(this.width, inputRows.rightEdge +
- input.connection.targetBlock().getHeightWidth().width -
- Blockly.BlockSvg.TAB_WIDTH + 1);
- }
- } else if (row.type == Blockly.DUMMY_INPUT) {
- // External naked field.
- var input = row[0];
- var fieldX = cursorX;
- var fieldY = cursorY;
- if (input.align != Blockly.ALIGN_LEFT) {
- var fieldRightX = inputRows.rightEdge - input.fieldWidth -
- 2 * Blockly.BlockSvg.SEP_SPACE_X;
- if (inputRows.hasValue) {
- fieldRightX -= Blockly.BlockSvg.TAB_WIDTH;
- }
- if (input.align == Blockly.ALIGN_RIGHT) {
- fieldX += fieldRightX;
- } else if (input.align == Blockly.ALIGN_CENTRE) {
- fieldX += fieldRightX / 2;
- }
- }
- this.renderFields_(input.fieldRow, fieldX, fieldY);
- steps.push('v', row.height);
- if (this.RTL) {
- highlightSteps.push('v', row.height - 1);
- }
- } else if (row.type == Blockly.NEXT_STATEMENT) {
- // Nested statement.
- var input = row[0];
- if (y == 0) {
- // If the first input is a statement stack, add a small row on top.
- steps.push('v', Blockly.BlockSvg.SEP_SPACE_Y);
- if (this.RTL) {
- highlightSteps.push('v', Blockly.BlockSvg.SEP_SPACE_Y - 1);
- }
- cursorY += Blockly.BlockSvg.SEP_SPACE_Y;
- }
- var fieldX = cursorX;
- var fieldY = cursorY;
- if (input.align != Blockly.ALIGN_LEFT) {
- var fieldRightX = inputRows.statementEdge - input.fieldWidth -
- 2 * Blockly.BlockSvg.SEP_SPACE_X;
- if (input.align == Blockly.ALIGN_RIGHT) {
- fieldX += fieldRightX;
- } else if (input.align == Blockly.ALIGN_CENTRE) {
- fieldX += fieldRightX / 2;
- }
- }
- this.renderFields_(input.fieldRow, fieldX, fieldY);
- cursorX = inputRows.statementEdge + Blockly.BlockSvg.NOTCH_WIDTH;
- steps.push('H', cursorX);
- steps.push(Blockly.BlockSvg.INNER_TOP_LEFT_CORNER);
- steps.push('v', row.height - 2 * Blockly.BlockSvg.CORNER_RADIUS);
- steps.push(Blockly.BlockSvg.INNER_BOTTOM_LEFT_CORNER);
- steps.push('H', inputRows.rightEdge);
- if (this.RTL) {
- highlightSteps.push('M',
- (cursorX - Blockly.BlockSvg.NOTCH_WIDTH +
- Blockly.BlockSvg.DISTANCE_45_OUTSIDE) +
- ',' + (cursorY + Blockly.BlockSvg.DISTANCE_45_OUTSIDE));
- highlightSteps.push(
- Blockly.BlockSvg.INNER_TOP_LEFT_CORNER_HIGHLIGHT_RTL);
- highlightSteps.push('v',
- row.height - 2 * Blockly.BlockSvg.CORNER_RADIUS);
- highlightSteps.push(
- Blockly.BlockSvg.INNER_BOTTOM_LEFT_CORNER_HIGHLIGHT_RTL);
- highlightSteps.push('H', inputRows.rightEdge - 0.5);
- } else {
- highlightSteps.push('M',
- (cursorX - Blockly.BlockSvg.NOTCH_WIDTH +
- Blockly.BlockSvg.DISTANCE_45_OUTSIDE) + ',' +
- (cursorY + row.height - Blockly.BlockSvg.DISTANCE_45_OUTSIDE));
- highlightSteps.push(
- Blockly.BlockSvg.INNER_BOTTOM_LEFT_CORNER_HIGHLIGHT_LTR);
- highlightSteps.push('H', inputRows.rightEdge - 0.5);
- }
- // Create statement connection.
- connectionX = connectionsXY.x + (this.RTL ? -cursorX : cursorX + 1);
- connectionY = connectionsXY.y + cursorY + 1;
- input.connection.moveTo(connectionX, connectionY);
- if (input.connection.targetConnection) {
- input.connection.tighten_();
- this.width = Math.max(this.width, inputRows.statementEdge +
- input.connection.targetBlock().getHeightWidth().width);
- }
- if (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('v', Blockly.BlockSvg.SEP_SPACE_Y);
- if (this.RTL) {
- highlightSteps.push('v', Blockly.BlockSvg.SEP_SPACE_Y - 1);
- }
- cursorY += Blockly.BlockSvg.SEP_SPACE_Y;
- }
- }
- cursorY += row.height;
- }
- if (!inputRows.length) {
- cursorY = Blockly.BlockSvg.MIN_BLOCK_Y;
- steps.push('V', cursorY);
- if (this.RTL) {
- highlightSteps.push('V', cursorY - 1);
- }
- }
- return cursorY;
- };
- /**
- * Render the bottom edge of the block.
- * @param {!Array.<string>} steps Path of block outline.
- * @param {!Array.<string>} highlightSteps Path of block highlights.
- * @param {!Object} connectionsXY Location of block.
- * @param {number} cursorY Height of block.
- * @private
- */
- Blockly.BlockSvg.prototype.renderDrawBottom_ =
- function(steps, highlightSteps, connectionsXY, cursorY) {
- this.height = cursorY + 1; // Add one for the shadow.
- if (this.nextConnection) {
- steps.push('H', (Blockly.BlockSvg.NOTCH_WIDTH + (this.RTL ? 0.5 : - 0.5)) +
- ' ' + Blockly.BlockSvg.NOTCH_PATH_RIGHT);
- // Create next block connection.
- var connectionX;
- if (this.RTL) {
- connectionX = connectionsXY.x - Blockly.BlockSvg.NOTCH_WIDTH;
- } else {
- connectionX = connectionsXY.x + Blockly.BlockSvg.NOTCH_WIDTH;
- }
- var connectionY = connectionsXY.y + cursorY + 1;
- this.nextConnection.moveTo(connectionX, connectionY);
- if (this.nextConnection.targetConnection) {
- this.nextConnection.tighten_();
- }
- this.height += 4; // Height of tab.
- }
- // Should the bottom-left corner be rounded or square?
- if (this.squareBottomLeftCorner_) {
- steps.push('H 0');
- if (!this.RTL) {
- highlightSteps.push('M', '0.5,' + (cursorY - 0.5));
- }
- } else {
- steps.push('H', Blockly.BlockSvg.CORNER_RADIUS);
- steps.push('a', Blockly.BlockSvg.CORNER_RADIUS + ',' +
- Blockly.BlockSvg.CORNER_RADIUS + ' 0 0,1 -' +
- Blockly.BlockSvg.CORNER_RADIUS + ',-' +
- Blockly.BlockSvg.CORNER_RADIUS);
- if (!this.RTL) {
- highlightSteps.push('M', Blockly.BlockSvg.DISTANCE_45_INSIDE + ',' +
- (cursorY - Blockly.BlockSvg.DISTANCE_45_INSIDE));
- highlightSteps.push('A', (Blockly.BlockSvg.CORNER_RADIUS - 0.5) + ',' +
- (Blockly.BlockSvg.CORNER_RADIUS - 0.5) + ' 0 0,1 ' +
- '0.5,' + (cursorY - Blockly.BlockSvg.CORNER_RADIUS));
- }
- }
- };
- /**
- * Render the left edge of the block.
- * @param {!Array.<string>} steps Path of block outline.
- * @param {!Array.<string>} highlightSteps Path of block highlights.
- * @param {!Object} connectionsXY Location of block.
- * @param {number} cursorY Height of block.
- * @private
- */
- Blockly.BlockSvg.prototype.renderDrawLeft_ =
- function(steps, highlightSteps, connectionsXY, cursorY) {
- if (this.outputConnection) {
- // Create output connection.
- this.outputConnection.moveTo(connectionsXY.x, connectionsXY.y);
- // This connection will be tightened when the parent renders.
- steps.push('V', Blockly.BlockSvg.TAB_HEIGHT);
- steps.push('c 0,-10 -' + Blockly.BlockSvg.TAB_WIDTH + ',8 -' +
- Blockly.BlockSvg.TAB_WIDTH + ',-7.5 s ' + Blockly.BlockSvg.TAB_WIDTH +
- ',2.5 ' + Blockly.BlockSvg.TAB_WIDTH + ',-7.5');
- if (this.RTL) {
- highlightSteps.push('M', (Blockly.BlockSvg.TAB_WIDTH * -0.25) + ',8.4');
- highlightSteps.push('l', (Blockly.BlockSvg.TAB_WIDTH * -0.45) + ',-2.1');
- } else {
- highlightSteps.push('V', Blockly.BlockSvg.TAB_HEIGHT - 1.5);
- highlightSteps.push('m', (Blockly.BlockSvg.TAB_WIDTH * -0.92) +
- ',-0.5 q ' + (Blockly.BlockSvg.TAB_WIDTH * -0.19) +
- ',-5.5 0,-11');
- highlightSteps.push('m', (Blockly.BlockSvg.TAB_WIDTH * 0.92) +
- ',1 V 0.5 H 1');
- }
- this.width += Blockly.BlockSvg.TAB_WIDTH;
- } else if (!this.RTL) {
- if (this.squareTopLeftCorner_) {
- // Statement block in a stack.
- highlightSteps.push('V', 0.5);
- } else {
- highlightSteps.push('V', Blockly.BlockSvg.CORNER_RADIUS);
- }
- }
- steps.push('z');
- };
|