block_svg_old.js 75 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247
  1. /**
  2. * @license
  3. * Visual Blocks Editor
  4. *
  5. * Copyright 2012 Google Inc.
  6. * https://developers.google.com/blockly/
  7. *
  8. * Licensed under the Apache License, Version 2.0 (the "License");
  9. * you may not use this file except in compliance with the License.
  10. * You may obtain a copy of the License at
  11. *
  12. * http://www.apache.org/licenses/LICENSE-2.0
  13. *
  14. * Unless required by applicable law or agreed to in writing, software
  15. * distributed under the License is distributed on an "AS IS" BASIS,
  16. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17. * See the License for the specific language governing permissions and
  18. * limitations under the License.
  19. */
  20. /**
  21. * @fileoverview Methods for graphically rendering a block as SVG.
  22. * @author fraser@google.com (Neil Fraser)
  23. */
  24. 'use strict';
  25. goog.provide('Blockly.BlockSvg');
  26. goog.require('Blockly.Block');
  27. goog.require('Blockly.ContextMenu');
  28. goog.require('goog.Timer');
  29. goog.require('goog.asserts');
  30. goog.require('goog.dom');
  31. goog.require('goog.math.Coordinate');
  32. /**
  33. * Class for a block's SVG representation.
  34. * @extends {Blockly.Block}
  35. * @constructor
  36. */
  37. Blockly.BlockSvg = function() {
  38. // Create core elements for the block.
  39. this.svgGroup_ = Blockly.createSvgElement('g', {}, null);
  40. this.svgPathDark_ = Blockly.createSvgElement('path',
  41. {'class': 'blocklyPathDark', 'transform': 'translate(1,1)'},
  42. this.svgGroup_);
  43. this.svgPath_ = Blockly.createSvgElement('path', {'class': 'blocklyPath'},
  44. this.svgGroup_);
  45. this.svgPathLight_ = Blockly.createSvgElement('path',
  46. {'class': 'blocklyPathLight'}, this.svgGroup_);
  47. this.svgPath_.tooltip = this;
  48. Blockly.Tooltip.bindMouseEvents(this.svgPath_);
  49. };
  50. goog.inherits(Blockly.BlockSvg, Blockly.Block);
  51. /**
  52. * Height of this block, not including any statement blocks above or below.
  53. */
  54. Blockly.BlockSvg.prototype.height = 0;
  55. /**
  56. * Width of this block, including any connected value blocks.
  57. */
  58. Blockly.BlockSvg.prototype.width = 0;
  59. /**
  60. * Original location of block being dragged.
  61. * @type {goog.math.Coordinate}
  62. * @private
  63. */
  64. Blockly.BlockSvg.prototype.dragStartXY_ = null;
  65. /**
  66. * Constant for identifying rows that are to be rendered inline.
  67. * Don't collide with Blockly.INPUT_VALUE and friends.
  68. * @const
  69. */
  70. Blockly.BlockSvg.INLINE = -1;
  71. /**
  72. * Create and initialize the SVG representation of the block.
  73. * May be called more than once.
  74. */
  75. Blockly.BlockSvg.prototype.initSvg = function() {
  76. goog.asserts.assert(this.workspace.rendered, 'Workspace is headless.');
  77. for (var i = 0, input; input = this.inputList[i]; i++) {
  78. input.init();
  79. }
  80. if (this.mutator) {
  81. this.mutator.createIcon();
  82. }
  83. this.updateColour();
  84. this.updateMovable();
  85. if (!this.workspace.options.readOnly && !this.eventsInit_) {
  86. Blockly.bindEvent_(this.getSvgRoot(), 'mousedown', this,
  87. this.onMouseDown_);
  88. var thisBlock = this;
  89. Blockly.bindEvent_(this.getSvgRoot(), 'touchstart', null,
  90. function(e) {Blockly.longStart_(e, thisBlock);});
  91. }
  92. // Bind an onchange function, if it exists.
  93. if (goog.isFunction(this.onchange) && !this.eventsInit_) {
  94. this.onchangeWrapper_ = Blockly.bindEvent_(this.workspace.getCanvas(),
  95. 'blocklyWorkspaceChange', this, this.onchange);
  96. }
  97. this.eventsInit_ = true;
  98. if (!this.getSvgRoot().parentNode) {
  99. this.workspace.getCanvas().appendChild(this.getSvgRoot());
  100. }
  101. };
  102. /**
  103. * Select this block. Highlight it visually.
  104. */
  105. Blockly.BlockSvg.prototype.select = function() {
  106. if (Blockly.selected) {
  107. // Unselect any previously selected block.
  108. Blockly.selected.unselect();
  109. }
  110. Blockly.selected = this;
  111. this.addSelect();
  112. Blockly.fireUiEvent(this.workspace.getCanvas(), 'blocklySelectChange');
  113. };
  114. /**
  115. * Unselect this block. Remove its highlighting.
  116. */
  117. Blockly.BlockSvg.prototype.unselect = function() {
  118. Blockly.selected = null;
  119. this.removeSelect();
  120. Blockly.fireUiEvent(this.workspace.getCanvas(), 'blocklySelectChange');
  121. };
  122. /**
  123. * Block's mutator icon (if any).
  124. * @type {Blockly.Mutator}
  125. */
  126. Blockly.BlockSvg.prototype.mutator = null;
  127. /**
  128. * Block's comment icon (if any).
  129. * @type {Blockly.Comment}
  130. */
  131. Blockly.BlockSvg.prototype.comment = null;
  132. /**
  133. * Block's warning icon (if any).
  134. * @type {Blockly.Warning}
  135. */
  136. Blockly.BlockSvg.prototype.warning = null;
  137. /**
  138. * Returns a list of mutator, comment, and warning icons.
  139. * @return {!Array} List of icons.
  140. */
  141. Blockly.BlockSvg.prototype.getIcons = function() {
  142. var icons = [];
  143. if (this.mutator) {
  144. icons.push(this.mutator);
  145. }
  146. if (this.comment) {
  147. icons.push(this.comment);
  148. }
  149. if (this.warning) {
  150. icons.push(this.warning);
  151. }
  152. return icons;
  153. };
  154. /**
  155. * Wrapper function called when a mouseUp occurs during a drag operation.
  156. * @type {Array.<!Array>}
  157. * @private
  158. */
  159. Blockly.BlockSvg.onMouseUpWrapper_ = null;
  160. /**
  161. * Wrapper function called when a mouseMove occurs during a drag operation.
  162. * @type {Array.<!Array>}
  163. * @private
  164. */
  165. Blockly.BlockSvg.onMouseMoveWrapper_ = null;
  166. /**
  167. * Stop binding to the global mouseup and mousemove events.
  168. * @private
  169. */
  170. Blockly.BlockSvg.terminateDrag_ = function() {
  171. Blockly.BlockSvg.disconnectUiStop_();
  172. if (Blockly.BlockSvg.onMouseUpWrapper_) {
  173. Blockly.unbindEvent_(Blockly.BlockSvg.onMouseUpWrapper_);
  174. Blockly.BlockSvg.onMouseUpWrapper_ = null;
  175. }
  176. if (Blockly.BlockSvg.onMouseMoveWrapper_) {
  177. Blockly.unbindEvent_(Blockly.BlockSvg.onMouseMoveWrapper_);
  178. Blockly.BlockSvg.onMouseMoveWrapper_ = null;
  179. }
  180. var selected = Blockly.selected;
  181. if (Blockly.dragMode_ == 2) {
  182. // Terminate a drag operation.
  183. if (selected) {
  184. // Update the connection locations.
  185. var xy = selected.getRelativeToSurfaceXY();
  186. var dxy = goog.math.Coordinate.difference(xy, selected.dragStartXY_);
  187. selected.moveConnections_(dxy.x, dxy.y);
  188. delete selected.draggedBubbles_;
  189. selected.setDragging_(false);
  190. selected.render();
  191. goog.Timer.callOnce(
  192. selected.snapToGrid, Blockly.BUMP_DELAY / 2, selected);
  193. goog.Timer.callOnce(
  194. selected.bumpNeighbours_, Blockly.BUMP_DELAY, selected);
  195. // Fire an event to allow scrollbars to resize.
  196. Blockly.fireUiEvent(window, 'resize');
  197. selected.workspace.fireChangeEvent();
  198. }
  199. }
  200. Blockly.dragMode_ = 0;
  201. Blockly.Css.setCursor(Blockly.Css.Cursor.OPEN);
  202. };
  203. /**
  204. * Set parent of this block to be a new block or null.
  205. * @param {Blockly.BlockSvg} newParent New parent block.
  206. */
  207. Blockly.BlockSvg.prototype.setParent = function(newParent) {
  208. var svgRoot = this.getSvgRoot();
  209. if (this.parentBlock_ && svgRoot) {
  210. // Move this block up the DOM. Keep track of x/y translations.
  211. var xy = this.getRelativeToSurfaceXY();
  212. this.workspace.getCanvas().appendChild(svgRoot);
  213. svgRoot.setAttribute('transform', 'translate(' + xy.x + ',' + xy.y + ')');
  214. }
  215. Blockly.Field.startCache();
  216. Blockly.BlockSvg.superClass_.setParent.call(this, newParent);
  217. Blockly.Field.stopCache();
  218. if (newParent) {
  219. var oldXY = this.getRelativeToSurfaceXY();
  220. newParent.getSvgRoot().appendChild(svgRoot);
  221. var newXY = this.getRelativeToSurfaceXY();
  222. // Move the connections to match the child's new position.
  223. this.moveConnections_(newXY.x - oldXY.x, newXY.y - oldXY.y);
  224. }
  225. };
  226. /**
  227. * Return the coordinates of the top-left corner of this block relative to the
  228. * drawing surface's origin (0,0).
  229. * @return {!goog.math.Coordinate} Object with .x and .y properties.
  230. */
  231. Blockly.BlockSvg.prototype.getRelativeToSurfaceXY = function() {
  232. var x = 0;
  233. var y = 0;
  234. var element = this.getSvgRoot();
  235. if (element) {
  236. do {
  237. // Loop through this block and every parent.
  238. var xy = Blockly.getRelativeXY_(element);
  239. x += xy.x;
  240. y += xy.y;
  241. element = element.parentNode;
  242. } while (element && element != this.workspace.getCanvas());
  243. }
  244. return new goog.math.Coordinate(x, y);
  245. };
  246. /**
  247. * Move a block by a relative offset.
  248. * @param {number} dx Horizontal offset.
  249. * @param {number} dy Vertical offset.
  250. */
  251. Blockly.BlockSvg.prototype.moveBy = function(dx, dy) {
  252. var xy = this.getRelativeToSurfaceXY();
  253. this.getSvgRoot().setAttribute('transform',
  254. 'translate(' + (xy.x + dx) + ',' + (xy.y + dy) + ')');
  255. this.moveConnections_(dx, dy);
  256. Blockly.Realtime.blockChanged(this);
  257. };
  258. /**
  259. * Snap this block to the nearest grid point.
  260. */
  261. Blockly.BlockSvg.prototype.snapToGrid = function() {
  262. if (!this.workspace) {
  263. return; // Deleted block.
  264. }
  265. if (Blockly.dragMode_ != 0) {
  266. return; // Don't bump blocks during a drag.
  267. }
  268. if (this.getParent()) {
  269. return; // Only snap top-level blocks.
  270. }
  271. if (this.isInFlyout) {
  272. return; // Don't move blocks around in a flyout.
  273. }
  274. if (!this.workspace.options.gridOptions ||
  275. !this.workspace.options.gridOptions['snap']) {
  276. return; // Config says no snapping.
  277. }
  278. var spacing = this.workspace.options.gridOptions['spacing'];
  279. var half = spacing / 2;
  280. var xy = this.getRelativeToSurfaceXY();
  281. var dx = Math.round((xy.x - half) / spacing) * spacing + half - xy.x;
  282. var dy = Math.round((xy.y - half) / spacing) * spacing + half - xy.y;
  283. dx = Math.round(dx);
  284. dy = Math.round(dy);
  285. if (dx != 0 || dy != 0) {
  286. this.moveBy(dx, dy);
  287. }
  288. };
  289. /**
  290. * Returns a bounding box describing the dimensions of this block
  291. * and any blocks stacked below it.
  292. * @return {!Object} Object with height and width properties.
  293. */
  294. Blockly.BlockSvg.prototype.getHeightWidth = function() {
  295. var height = this.height;
  296. var width = this.width;
  297. // Recursively add size of subsequent blocks.
  298. var nextBlock = this.getNextBlock();
  299. if (nextBlock) {
  300. var nextHeightWidth = nextBlock.getHeightWidth();
  301. height += nextHeightWidth.height - 4; // Height of tab.
  302. width = Math.max(width, nextHeightWidth.width);
  303. } else if (!this.nextConnection && !this.outputConnection) {
  304. // Add a bit of margin under blocks with no bottom tab.
  305. height += 2;
  306. }
  307. return {height: height, width: width};
  308. };
  309. /**
  310. * Set whether the block is collapsed or not.
  311. * @param {boolean} collapsed True if collapsed.
  312. */
  313. Blockly.BlockSvg.prototype.setCollapsed = function(collapsed) {
  314. if (this.collapsed_ == collapsed) {
  315. return;
  316. }
  317. var renderList = [];
  318. // Show/hide the inputs.
  319. for (var i = 0, input; input = this.inputList[i]; i++) {
  320. renderList.push.apply(renderList, input.setVisible(!collapsed));
  321. }
  322. var COLLAPSED_INPUT_NAME = '_TEMP_COLLAPSED_INPUT';
  323. if (collapsed) {
  324. var icons = this.getIcons();
  325. for (var i = 0; i < icons.length; i++) {
  326. icons[i].setVisible(false);
  327. }
  328. var text = this.toString(Blockly.COLLAPSE_CHARS);
  329. this.appendDummyInput(COLLAPSED_INPUT_NAME).appendField(text).init();
  330. } else {
  331. this.removeInput(COLLAPSED_INPUT_NAME);
  332. // Clear any warnings inherited from enclosed blocks.
  333. this.setWarningText(null);
  334. }
  335. Blockly.BlockSvg.superClass_.setCollapsed.call(this, collapsed);
  336. if (!renderList.length) {
  337. // No child blocks, just render this block.
  338. renderList[0] = this;
  339. }
  340. if (this.rendered) {
  341. for (var i = 0, block; block = renderList[i]; i++) {
  342. block.render();
  343. }
  344. // Don't bump neighbours.
  345. // Although bumping neighbours would make sense, users often collapse
  346. // all their functions and store them next to each other. Expanding and
  347. // bumping causes all their definitions to go out of alignment.
  348. }
  349. this.workspace.fireChangeEvent();
  350. };
  351. /**
  352. * Open the next (or previous) FieldTextInput.
  353. * @param {Blockly.Field|Blockly.Block} start Current location.
  354. * @param {boolean} forward If true go forward, otherwise backward.
  355. */
  356. Blockly.BlockSvg.prototype.tab = function(start, forward) {
  357. // This function need not be efficient since it runs once on a keypress.
  358. // Create an ordered list of all text fields and connected inputs.
  359. var list = [];
  360. for (var i = 0, input; input = this.inputList[i]; i++) {
  361. for (var j = 0, field; field = input.fieldRow[j]; j++) {
  362. if (field instanceof Blockly.FieldTextInput) {
  363. // TODO: Also support dropdown fields.
  364. list.push(field);
  365. }
  366. }
  367. if (input.connection) {
  368. var block = input.connection.targetBlock();
  369. if (block) {
  370. list.push(block);
  371. }
  372. }
  373. }
  374. var i = list.indexOf(start);
  375. if (i == -1) {
  376. // No start location, start at the beginning or end.
  377. i = forward ? -1 : list.length;
  378. }
  379. var target = list[forward ? i + 1 : i - 1];
  380. if (!target) {
  381. // Ran off of list.
  382. var parent = this.getParent();
  383. if (parent) {
  384. parent.tab(this, forward);
  385. }
  386. } else if (target instanceof Blockly.Field) {
  387. target.showEditor_();
  388. } else {
  389. target.tab(null, forward);
  390. }
  391. };
  392. /**
  393. * Handle a mouse-down on an SVG block.
  394. * @param {!Event} e Mouse down event.
  395. * @private
  396. */
  397. Blockly.BlockSvg.prototype.onMouseDown_ = function(e) {
  398. if (this.isInFlyout) {
  399. return;
  400. }
  401. this.workspace.markFocused();
  402. // Update Blockly's knowledge of its own location.
  403. Blockly.svgResize(this.workspace);
  404. Blockly.terminateDrag_();
  405. this.select();
  406. Blockly.hideChaff();
  407. if (Blockly.isRightButton(e)) {
  408. // Right-click.
  409. this.showContextMenu_(e);
  410. } else if (!this.isMovable()) {
  411. // Allow unmovable blocks to be selected and context menued, but not
  412. // dragged. Let this event bubble up to document, so the workspace may be
  413. // dragged instead.
  414. return;
  415. } else {
  416. // Left-click (or middle click)
  417. Blockly.removeAllRanges();
  418. Blockly.Css.setCursor(Blockly.Css.Cursor.CLOSED);
  419. this.dragStartXY_ = this.getRelativeToSurfaceXY();
  420. this.workspace.startDrag(e, this.dragStartXY_.x, this.dragStartXY_.y);
  421. Blockly.dragMode_ = 1;
  422. Blockly.BlockSvg.onMouseUpWrapper_ = Blockly.bindEvent_(document,
  423. 'mouseup', this, this.onMouseUp_);
  424. Blockly.BlockSvg.onMouseMoveWrapper_ = Blockly.bindEvent_(document,
  425. 'mousemove', this, this.onMouseMove_);
  426. // Build a list of bubbles that need to be moved and where they started.
  427. this.draggedBubbles_ = [];
  428. var descendants = this.getDescendants();
  429. for (var i = 0, descendant; descendant = descendants[i]; i++) {
  430. var icons = descendant.getIcons();
  431. for (var j = 0; j < icons.length; j++) {
  432. var data = icons[j].getIconLocation();
  433. data.bubble = icons[j];
  434. this.draggedBubbles_.push(data);
  435. }
  436. }
  437. }
  438. // This event has been handled. No need to bubble up to the document.
  439. e.stopPropagation();
  440. };
  441. /**
  442. * Handle a mouse-up anywhere in the SVG pane. Is only registered when a
  443. * block is clicked. We can't use mouseUp on the block since a fast-moving
  444. * cursor can briefly escape the block before it catches up.
  445. * @param {!Event} e Mouse up event.
  446. * @private
  447. */
  448. Blockly.BlockSvg.prototype.onMouseUp_ = function(e) {
  449. var this_ = this;
  450. Blockly.doCommand(function() {
  451. Blockly.terminateDrag_();
  452. if (Blockly.selected && Blockly.highlightedConnection_) {
  453. // Connect two blocks together.
  454. Blockly.localConnection_.connect(Blockly.highlightedConnection_);
  455. if (this_.rendered) {
  456. // Trigger a connection animation.
  457. // Determine which connection is inferior (lower in the source stack).
  458. var inferiorConnection;
  459. if (Blockly.localConnection_.isSuperior()) {
  460. inferiorConnection = Blockly.highlightedConnection_;
  461. } else {
  462. inferiorConnection = Blockly.localConnection_;
  463. }
  464. inferiorConnection.sourceBlock_.connectionUiEffect();
  465. }
  466. if (this_.workspace.trashcan) {
  467. // Don't throw an object in the trash can if it just got connected.
  468. this_.workspace.trashcan.close();
  469. }
  470. } else if (!this_.getParent() && Blockly.selected.isDeletable() &&
  471. this_.workspace.isDeleteArea(e)) {
  472. var trashcan = this_.workspace.trashcan;
  473. if (trashcan) {
  474. goog.Timer.callOnce(trashcan.close, 100, trashcan);
  475. }
  476. Blockly.selected.dispose(false, true);
  477. // Dropping a block on the trash can will usually cause the workspace to
  478. // resize to contain the newly positioned block. Force a second resize
  479. // now that the block has been deleted.
  480. Blockly.fireUiEvent(window, 'resize');
  481. }
  482. if (Blockly.highlightedConnection_) {
  483. Blockly.highlightedConnection_.unhighlight();
  484. Blockly.highlightedConnection_ = null;
  485. }
  486. Blockly.Css.setCursor(Blockly.Css.Cursor.OPEN);
  487. });
  488. };
  489. /**
  490. * Load the block's help page in a new window.
  491. * @private
  492. */
  493. Blockly.BlockSvg.prototype.showHelp_ = function() {
  494. var url = goog.isFunction(this.helpUrl) ? this.helpUrl() : this.helpUrl;
  495. if (url) {
  496. window.open(url);
  497. }
  498. };
  499. /**
  500. * Show the context menu for this block.
  501. * @param {!Event} e Mouse event.
  502. * @private
  503. */
  504. Blockly.BlockSvg.prototype.showContextMenu_ = function(e) {
  505. if (this.workspace.options.readOnly || !this.contextMenu) {
  506. return;
  507. }
  508. // Save the current block in a variable for use in closures.
  509. var block = this;
  510. var options = [];
  511. if (this.isDeletable() && this.isMovable() && !block.isInFlyout) {
  512. // Option to duplicate this block.
  513. var duplicateOption = {
  514. text: Blockly.Msg.DUPLICATE_BLOCK,
  515. enabled: true,
  516. callback: function() {
  517. Blockly.duplicate_(block);
  518. }
  519. };
  520. if (this.getDescendants().length > this.workspace.remainingCapacity()) {
  521. duplicateOption.enabled = false;
  522. }
  523. options.push(duplicateOption);
  524. if (this.isEditable() && !this.collapsed_ &&
  525. this.workspace.options.comments) {
  526. // Option to add/remove a comment.
  527. var commentOption = {enabled: true};
  528. if (this.comment) {
  529. commentOption.text = Blockly.Msg.REMOVE_COMMENT;
  530. commentOption.callback = function() {
  531. block.setCommentText(null);
  532. };
  533. } else {
  534. commentOption.text = Blockly.Msg.ADD_COMMENT;
  535. commentOption.callback = function() {
  536. block.setCommentText('');
  537. };
  538. }
  539. options.push(commentOption);
  540. }
  541. // Option to make block inline.
  542. if (!this.collapsed_) {
  543. for (var i = 1; i < this.inputList.length; i++) {
  544. if (this.inputList[i - 1].type != Blockly.NEXT_STATEMENT &&
  545. this.inputList[i].type != Blockly.NEXT_STATEMENT) {
  546. // Only display this option if there are two value or dummy inputs
  547. // next to each other.
  548. var inlineOption = {enabled: true};
  549. var isInline = this.getInputsInline();
  550. inlineOption.text = isInline ?
  551. Blockly.Msg.EXTERNAL_INPUTS : Blockly.Msg.INLINE_INPUTS;
  552. inlineOption.callback = function() {
  553. block.setInputsInline(!isInline);
  554. };
  555. options.push(inlineOption);
  556. break;
  557. }
  558. }
  559. }
  560. if (this.workspace.options.collapse) {
  561. // Option to collapse/expand block.
  562. if (this.collapsed_) {
  563. var expandOption = {enabled: true};
  564. expandOption.text = Blockly.Msg.EXPAND_BLOCK;
  565. expandOption.callback = function() {
  566. block.setCollapsed(false);
  567. };
  568. options.push(expandOption);
  569. } else {
  570. var collapseOption = {enabled: true};
  571. collapseOption.text = Blockly.Msg.COLLAPSE_BLOCK;
  572. collapseOption.callback = function() {
  573. block.setCollapsed(true);
  574. };
  575. options.push(collapseOption);
  576. }
  577. }
  578. if (this.workspace.options.disable) {
  579. // Option to disable/enable block.
  580. var disableOption = {
  581. text: this.disabled ?
  582. Blockly.Msg.ENABLE_BLOCK : Blockly.Msg.DISABLE_BLOCK,
  583. enabled: !this.getInheritedDisabled(),
  584. callback: function() {
  585. block.setDisabled(!block.disabled);
  586. }
  587. };
  588. options.push(disableOption);
  589. }
  590. // Option to delete this block.
  591. // Count the number of blocks that are nested in this block.
  592. var descendantCount = this.getDescendants().length;
  593. var nextBlock = this.getNextBlock();
  594. if (nextBlock) {
  595. // Blocks in the current stack would survive this block's deletion.
  596. descendantCount -= nextBlock.getDescendants().length;
  597. }
  598. var deleteOption = {
  599. text: descendantCount == 1 ? Blockly.Msg.DELETE_BLOCK :
  600. Blockly.Msg.DELETE_X_BLOCKS.replace('%1', String(descendantCount)),
  601. enabled: true,
  602. callback: function() {
  603. block.dispose(true, true);
  604. }
  605. };
  606. options.push(deleteOption);
  607. }
  608. // Option to get help.
  609. var url = goog.isFunction(this.helpUrl) ? this.helpUrl() : this.helpUrl;
  610. var helpOption = {enabled: !!url};
  611. helpOption.text = Blockly.Msg.HELP;
  612. helpOption.callback = function() {
  613. block.showHelp_();
  614. };
  615. options.push(helpOption);
  616. // Allow the block to add or modify options.
  617. if (this.customContextMenu && !block.isInFlyout) {
  618. this.customContextMenu(options);
  619. }
  620. Blockly.ContextMenu.show(e, options, this.RTL);
  621. Blockly.ContextMenu.currentBlock = this;
  622. };
  623. /**
  624. * Move the connections for this block and all blocks attached under it.
  625. * Also update any attached bubbles.
  626. * @param {number} dx Horizontal offset from current location.
  627. * @param {number} dy Vertical offset from current location.
  628. * @private
  629. */
  630. Blockly.BlockSvg.prototype.moveConnections_ = function(dx, dy) {
  631. if (!this.rendered) {
  632. // Rendering is required to lay out the blocks.
  633. // This is probably an invisible block attached to a collapsed block.
  634. return;
  635. }
  636. var myConnections = this.getConnections_(false);
  637. for (var i = 0; i < myConnections.length; i++) {
  638. myConnections[i].moveBy(dx, dy);
  639. }
  640. var icons = this.getIcons();
  641. for (var i = 0; i < icons.length; i++) {
  642. icons[i].computeIconLocation();
  643. }
  644. // Recurse through all blocks attached under this one.
  645. for (var i = 0; i < this.childBlocks_.length; i++) {
  646. this.childBlocks_[i].moveConnections_(dx, dy);
  647. }
  648. };
  649. /**
  650. * Recursively adds or removes the dragging class to this node and its children.
  651. * @param {boolean} adding True if adding, false if removing.
  652. * @private
  653. */
  654. Blockly.BlockSvg.prototype.setDragging_ = function(adding) {
  655. if (adding) {
  656. this.addDragging();
  657. } else {
  658. this.removeDragging();
  659. }
  660. // Recurse through all blocks attached under this one.
  661. for (var i = 0; i < this.childBlocks_.length; i++) {
  662. this.childBlocks_[i].setDragging_(adding);
  663. }
  664. };
  665. /**
  666. * Drag this block to follow the mouse.
  667. * @param {!Event} e Mouse move event.
  668. * @private
  669. */
  670. Blockly.BlockSvg.prototype.onMouseMove_ = function(e) {
  671. var this_ = this;
  672. var workspace_ = this.workspace;
  673. Blockly.doCommand(function() {
  674. if (e.type == 'mousemove' && e.clientX <= 1 && e.clientY == 0 &&
  675. e.button == 0) {
  676. /* HACK:
  677. Safari Mobile 6.0 and Chrome for Android 18.0 fire rogue mousemove
  678. events on certain touch actions. Ignore events with these signatures.
  679. This may result in a one-pixel blind spot in other browsers,
  680. but this shouldn't be noticeable. */
  681. e.stopPropagation();
  682. return;
  683. }
  684. Blockly.removeAllRanges();
  685. var oldXY = this_.getRelativeToSurfaceXY();
  686. var newXY = workspace_.moveDrag(e);
  687. var group = this_.getSvgRoot();
  688. if (Blockly.dragMode_ == 1) {
  689. // Still dragging within the sticky DRAG_RADIUS.
  690. var dr = goog.math.Coordinate.distance(oldXY, newXY) * workspace_.scale;
  691. if (dr > Blockly.DRAG_RADIUS) {
  692. // Switch to unrestricted dragging.
  693. Blockly.dragMode_ = 2;
  694. Blockly.longStop_();
  695. group.translate_ = '';
  696. group.skew_ = '';
  697. if (this_.parentBlock_) {
  698. // Push this block to the very top of the stack.
  699. this_.setParent(null);
  700. this_.disconnectUiEffect();
  701. }
  702. this_.setDragging_(true);
  703. workspace_.recordDeleteAreas();
  704. }
  705. }
  706. if (Blockly.dragMode_ == 2) {
  707. // Unrestricted dragging.
  708. var dx = oldXY.x - this_.dragStartXY_.x;
  709. var dy = oldXY.y - this_.dragStartXY_.y;
  710. group.translate_ = 'translate(' + newXY.x + ',' + newXY.y + ')';
  711. group.setAttribute('transform', group.translate_ + group.skew_);
  712. // Drag all the nested bubbles.
  713. for (var i = 0; i < this_.draggedBubbles_.length; i++) {
  714. var commentData = this_.draggedBubbles_[i];
  715. commentData.bubble.setIconLocation(commentData.x + dx,
  716. commentData.y + dy);
  717. }
  718. // Check to see if any of this block's connections are within range of
  719. // another block's connection.
  720. var myConnections = this_.getConnections_(false);
  721. var closestConnection = null;
  722. var localConnection = null;
  723. var radiusConnection = Blockly.SNAP_RADIUS;
  724. for (var i = 0; i < myConnections.length; i++) {
  725. var myConnection = myConnections[i];
  726. var neighbour = myConnection.closest(radiusConnection, dx, dy);
  727. if (neighbour.connection) {
  728. closestConnection = neighbour.connection;
  729. localConnection = myConnection;
  730. radiusConnection = neighbour.radius;
  731. }
  732. }
  733. // Remove connection highlighting if needed.
  734. if (Blockly.highlightedConnection_ &&
  735. Blockly.highlightedConnection_ != closestConnection) {
  736. Blockly.highlightedConnection_.unhighlight();
  737. Blockly.highlightedConnection_ = null;
  738. Blockly.localConnection_ = null;
  739. }
  740. // Add connection highlighting if needed.
  741. if (closestConnection &&
  742. closestConnection != Blockly.highlightedConnection_) {
  743. closestConnection.highlight();
  744. Blockly.highlightedConnection_ = closestConnection;
  745. Blockly.localConnection_ = localConnection;
  746. }
  747. // Provide visual indication of whether the block will be deleted if
  748. // dropped here.
  749. if (this_.isDeletable()) {
  750. workspace_.isDeleteArea(e);
  751. }
  752. }
  753. // This event has been handled. No need to bubble up to the document.
  754. e.stopPropagation();
  755. });
  756. };
  757. /**
  758. * Add or remove the UI indicating if this block is movable or not.
  759. */
  760. Blockly.BlockSvg.prototype.updateMovable = function() {
  761. if (this.isMovable()) {
  762. Blockly.addClass_(/** @type {!Element} */ (this.svgGroup_),
  763. 'blocklyDraggable');
  764. } else {
  765. Blockly.removeClass_(/** @type {!Element} */ (this.svgGroup_),
  766. 'blocklyDraggable');
  767. }
  768. };
  769. /**
  770. * Set whether this block is movable or not.
  771. * @param {boolean} movable True if movable.
  772. */
  773. Blockly.BlockSvg.prototype.setMovable = function(movable) {
  774. Blockly.BlockSvg.superClass_.setMovable.call(this, movable);
  775. this.updateMovable();
  776. };
  777. /**
  778. * Set whether this block is editable or not.
  779. * @param {boolean} movable True if editable.
  780. */
  781. Blockly.BlockSvg.prototype.setEditable = function(editable) {
  782. Blockly.BlockSvg.superClass_.setEditable.call(this, editable);
  783. if (this.rendered) {
  784. for (var i = 0; i < this.icons_.length; i++) {
  785. this.icons_[i].updateEditable();
  786. }
  787. }
  788. };
  789. /**
  790. * Set whether this block is a shadow block or not.
  791. * @param {boolean} shadow True if a shadow.
  792. */
  793. Blockly.BlockSvg.prototype.setShadow = function(shadow) {
  794. Blockly.BlockSvg.superClass_.setShadow.call(this, shadow);
  795. this.updateColour();
  796. };
  797. /**
  798. * Return the root node of the SVG or null if none exists.
  799. * @return {Element} The root SVG node (probably a group).
  800. */
  801. Blockly.BlockSvg.prototype.getSvgRoot = function() {
  802. return this.svgGroup_;
  803. };
  804. // UI constants for rendering blocks.
  805. /**
  806. * Horizontal space between elements.
  807. * @const
  808. */
  809. Blockly.BlockSvg.SEP_SPACE_X = 10;
  810. /**
  811. * Vertical space between elements.
  812. * @const
  813. */
  814. Blockly.BlockSvg.SEP_SPACE_Y = 10;
  815. /**
  816. * Vertical padding around inline elements.
  817. * @const
  818. */
  819. Blockly.BlockSvg.INLINE_PADDING_Y = 5;
  820. /**
  821. * Minimum height of a block.
  822. * @const
  823. */
  824. Blockly.BlockSvg.MIN_BLOCK_Y = 25;
  825. /**
  826. * Height of horizontal puzzle tab.
  827. * @const
  828. */
  829. Blockly.BlockSvg.TAB_HEIGHT = 20;
  830. /**
  831. * Width of horizontal puzzle tab.
  832. * @const
  833. */
  834. Blockly.BlockSvg.TAB_WIDTH = 8;
  835. /**
  836. * Width of vertical tab (inc left margin).
  837. * @const
  838. */
  839. Blockly.BlockSvg.NOTCH_WIDTH = 30;
  840. /**
  841. * Rounded corner radius.
  842. * @const
  843. */
  844. Blockly.BlockSvg.CORNER_RADIUS = 8;
  845. /**
  846. * Do blocks with no previous or output connections have a 'hat' on top?
  847. * @const
  848. */
  849. Blockly.BlockSvg.START_HAT = false;
  850. /**
  851. * Path of the top hat's curve.
  852. * @const
  853. */
  854. Blockly.BlockSvg.START_HAT_PATH = 'c 30,-15 70,-15 100,0';
  855. /**
  856. * Path of the top hat's curve's highlight in LTR.
  857. * @const
  858. */
  859. Blockly.BlockSvg.START_HAT_HIGHLIGHT_LTR =
  860. 'c 17.8,-9.2 45.3,-14.9 75,-8.7 M 100.5,0.5';
  861. /**
  862. * Path of the top hat's curve's highlight in RTL.
  863. * @const
  864. */
  865. Blockly.BlockSvg.START_HAT_HIGHLIGHT_RTL =
  866. 'm 25,-8.7 c 29.7,-6.2 57.2,-0.5 75,8.7';
  867. /**
  868. * Distance from shape edge to intersect with a curved corner at 45 degrees.
  869. * Applies to highlighting on around the inside of a curve.
  870. * @const
  871. */
  872. Blockly.BlockSvg.DISTANCE_45_INSIDE = (1 - Math.SQRT1_2) *
  873. (Blockly.BlockSvg.CORNER_RADIUS - 0.5) + 0.5;
  874. /**
  875. * Distance from shape edge to intersect with a curved corner at 45 degrees.
  876. * Applies to highlighting on around the outside of a curve.
  877. * @const
  878. */
  879. Blockly.BlockSvg.DISTANCE_45_OUTSIDE = (1 - Math.SQRT1_2) *
  880. (Blockly.BlockSvg.CORNER_RADIUS + 0.5) - 0.5;
  881. /**
  882. * SVG path for drawing next/previous notch from left to right.
  883. * @const
  884. */
  885. Blockly.BlockSvg.NOTCH_PATH_LEFT = 'l 6,4 3,0 6,-4';
  886. /**
  887. * SVG path for drawing next/previous notch from left to right with
  888. * highlighting.
  889. * @const
  890. */
  891. Blockly.BlockSvg.NOTCH_PATH_LEFT_HIGHLIGHT = 'l 6,4 3,0 6,-4';
  892. /**
  893. * SVG path for drawing next/previous notch from right to left.
  894. * @const
  895. */
  896. Blockly.BlockSvg.NOTCH_PATH_RIGHT = 'l -6,4 -3,0 -6,-4';
  897. /**
  898. * SVG path for drawing jagged teeth at the end of collapsed blocks.
  899. * @const
  900. */
  901. Blockly.BlockSvg.JAGGED_TEETH = 'l 8,0 0,4 8,4 -16,8 8,4';
  902. /**
  903. * Height of SVG path for jagged teeth at the end of collapsed blocks.
  904. * @const
  905. */
  906. Blockly.BlockSvg.JAGGED_TEETH_HEIGHT = 20;
  907. /**
  908. * Width of SVG path for jagged teeth at the end of collapsed blocks.
  909. * @const
  910. */
  911. Blockly.BlockSvg.JAGGED_TEETH_WIDTH = 15;
  912. /**
  913. * SVG path for drawing a horizontal puzzle tab from top to bottom.
  914. * @const
  915. */
  916. Blockly.BlockSvg.TAB_PATH_DOWN = 'v 5 c 0,10 -' + Blockly.BlockSvg.TAB_WIDTH +
  917. ',-8 -' + Blockly.BlockSvg.TAB_WIDTH + ',7.5 s ' +
  918. Blockly.BlockSvg.TAB_WIDTH + ',-2.5 ' + Blockly.BlockSvg.TAB_WIDTH + ',7.5';
  919. /**
  920. * SVG path for drawing a horizontal puzzle tab from top to bottom with
  921. * highlighting from the upper-right.
  922. * @const
  923. */
  924. Blockly.BlockSvg.TAB_PATH_DOWN_HIGHLIGHT_RTL = 'v 6.5 m -' +
  925. (Blockly.BlockSvg.TAB_WIDTH * 0.97) + ',3 q -' +
  926. (Blockly.BlockSvg.TAB_WIDTH * 0.05) + ',10 ' +
  927. (Blockly.BlockSvg.TAB_WIDTH * 0.3) + ',9.5 m ' +
  928. (Blockly.BlockSvg.TAB_WIDTH * 0.67) + ',-1.9 v 1.4';
  929. /**
  930. * SVG start point for drawing the top-left corner.
  931. * @const
  932. */
  933. Blockly.BlockSvg.TOP_LEFT_CORNER_START =
  934. 'm 0,' + Blockly.BlockSvg.CORNER_RADIUS;
  935. /**
  936. * SVG start point for drawing the top-left corner's highlight in RTL.
  937. * @const
  938. */
  939. Blockly.BlockSvg.TOP_LEFT_CORNER_START_HIGHLIGHT_RTL =
  940. 'm ' + Blockly.BlockSvg.DISTANCE_45_INSIDE + ',' +
  941. Blockly.BlockSvg.DISTANCE_45_INSIDE;
  942. /**
  943. * SVG start point for drawing the top-left corner's highlight in LTR.
  944. * @const
  945. */
  946. Blockly.BlockSvg.TOP_LEFT_CORNER_START_HIGHLIGHT_LTR =
  947. 'm 0.5,' + (Blockly.BlockSvg.CORNER_RADIUS - 0.5);
  948. /**
  949. * SVG path for drawing the rounded top-left corner.
  950. * @const
  951. */
  952. Blockly.BlockSvg.TOP_LEFT_CORNER =
  953. 'A ' + Blockly.BlockSvg.CORNER_RADIUS + ',' +
  954. Blockly.BlockSvg.CORNER_RADIUS + ' 0 0,1 ' +
  955. Blockly.BlockSvg.CORNER_RADIUS + ',0';
  956. /**
  957. * SVG path for drawing the highlight on the rounded top-left corner.
  958. * @const
  959. */
  960. Blockly.BlockSvg.TOP_LEFT_CORNER_HIGHLIGHT =
  961. 'A ' + (Blockly.BlockSvg.CORNER_RADIUS - 0.5) + ',' +
  962. (Blockly.BlockSvg.CORNER_RADIUS - 0.5) + ' 0 0,1 ' +
  963. Blockly.BlockSvg.CORNER_RADIUS + ',0.5';
  964. /**
  965. * SVG path for drawing the top-left corner of a statement input.
  966. * Includes the top notch, a horizontal space, and the rounded inside corner.
  967. * @const
  968. */
  969. Blockly.BlockSvg.INNER_TOP_LEFT_CORNER =
  970. Blockly.BlockSvg.NOTCH_PATH_RIGHT + ' h -' +
  971. (Blockly.BlockSvg.NOTCH_WIDTH - 15 - Blockly.BlockSvg.CORNER_RADIUS) +
  972. ' h -0.5 a ' + Blockly.BlockSvg.CORNER_RADIUS + ',' +
  973. Blockly.BlockSvg.CORNER_RADIUS + ' 0 0,0 -' +
  974. Blockly.BlockSvg.CORNER_RADIUS + ',' +
  975. Blockly.BlockSvg.CORNER_RADIUS;
  976. /**
  977. * SVG path for drawing the bottom-left corner of a statement input.
  978. * Includes the rounded inside corner.
  979. * @const
  980. */
  981. Blockly.BlockSvg.INNER_BOTTOM_LEFT_CORNER =
  982. 'a ' + Blockly.BlockSvg.CORNER_RADIUS + ',' +
  983. Blockly.BlockSvg.CORNER_RADIUS + ' 0 0,0 ' +
  984. Blockly.BlockSvg.CORNER_RADIUS + ',' +
  985. Blockly.BlockSvg.CORNER_RADIUS;
  986. /**
  987. * SVG path for drawing highlight on the top-left corner of a statement
  988. * input in RTL.
  989. * @const
  990. */
  991. Blockly.BlockSvg.INNER_TOP_LEFT_CORNER_HIGHLIGHT_RTL =
  992. 'a ' + Blockly.BlockSvg.CORNER_RADIUS + ',' +
  993. Blockly.BlockSvg.CORNER_RADIUS + ' 0 0,0 ' +
  994. (-Blockly.BlockSvg.DISTANCE_45_OUTSIDE - 0.5) + ',' +
  995. (Blockly.BlockSvg.CORNER_RADIUS -
  996. Blockly.BlockSvg.DISTANCE_45_OUTSIDE);
  997. /**
  998. * SVG path for drawing highlight on the bottom-left corner of a statement
  999. * input in RTL.
  1000. * @const
  1001. */
  1002. Blockly.BlockSvg.INNER_BOTTOM_LEFT_CORNER_HIGHLIGHT_RTL =
  1003. 'a ' + (Blockly.BlockSvg.CORNER_RADIUS + 0.5) + ',' +
  1004. (Blockly.BlockSvg.CORNER_RADIUS + 0.5) + ' 0 0,0 ' +
  1005. (Blockly.BlockSvg.CORNER_RADIUS + 0.5) + ',' +
  1006. (Blockly.BlockSvg.CORNER_RADIUS + 0.5);
  1007. /**
  1008. * SVG path for drawing highlight on the bottom-left corner of a statement
  1009. * input in LTR.
  1010. * @const
  1011. */
  1012. Blockly.BlockSvg.INNER_BOTTOM_LEFT_CORNER_HIGHLIGHT_LTR =
  1013. 'a ' + (Blockly.BlockSvg.CORNER_RADIUS + 0.5) + ',' +
  1014. (Blockly.BlockSvg.CORNER_RADIUS + 0.5) + ' 0 0,0 ' +
  1015. (Blockly.BlockSvg.CORNER_RADIUS -
  1016. Blockly.BlockSvg.DISTANCE_45_OUTSIDE) + ',' +
  1017. (Blockly.BlockSvg.DISTANCE_45_OUTSIDE + 0.5);
  1018. /**
  1019. * Dispose of this block.
  1020. * @param {boolean} healStack If true, then try to heal any gap by connecting
  1021. * the next statement with the previous statement. Otherwise, dispose of
  1022. * all children of this block.
  1023. * @param {boolean} animate If true, show a disposal animation and sound.
  1024. * @param {boolean=} opt_dontRemoveFromWorkspace If true, don't remove this
  1025. * block from the workspace's list of top blocks.
  1026. */
  1027. Blockly.BlockSvg.prototype.dispose = function(healStack, animate,
  1028. opt_dontRemoveFromWorkspace) {
  1029. Blockly.Field.startCache();
  1030. // Terminate onchange event calls.
  1031. if (this.onchangeWrapper_) {
  1032. Blockly.unbindEvent_(this.onchangeWrapper_);
  1033. this.onchangeWrapper_ = null;
  1034. }
  1035. // If this block is being dragged, unlink the mouse events.
  1036. if (Blockly.selected == this) {
  1037. Blockly.terminateDrag_();
  1038. }
  1039. // If this block has a context menu open, close it.
  1040. if (Blockly.ContextMenu.currentBlock == this) {
  1041. Blockly.ContextMenu.hide();
  1042. }
  1043. if (animate && this.rendered) {
  1044. this.unplug(healStack, false);
  1045. this.disposeUiEffect();
  1046. }
  1047. // Stop rerendering.
  1048. this.rendered = false;
  1049. var icons = this.getIcons();
  1050. for (var i = 0; i < icons.length; i++) {
  1051. icons[i].dispose();
  1052. }
  1053. Blockly.BlockSvg.superClass_.dispose.call(this, healStack);
  1054. goog.dom.removeNode(this.svgGroup_);
  1055. // Sever JavaScript to DOM connections.
  1056. this.svgGroup_ = null;
  1057. this.svgPath_ = null;
  1058. this.svgPathLight_ = null;
  1059. this.svgPathDark_ = null;
  1060. Blockly.Field.stopCache();
  1061. };
  1062. /**
  1063. * Play some UI effects (sound, animation) when disposing of a block.
  1064. */
  1065. Blockly.BlockSvg.prototype.disposeUiEffect = function() {
  1066. this.workspace.playAudio('delete');
  1067. var xy = Blockly.getSvgXY_(/** @type {!Element} */ (this.svgGroup_),
  1068. this.workspace);
  1069. // Deeply clone the current block.
  1070. var clone = this.svgGroup_.cloneNode(true);
  1071. clone.translateX_ = xy.x;
  1072. clone.translateY_ = xy.y;
  1073. clone.setAttribute('transform',
  1074. 'translate(' + clone.translateX_ + ',' + clone.translateY_ + ')');
  1075. this.workspace.options.svg.appendChild(clone);
  1076. clone.bBox_ = clone.getBBox();
  1077. // Start the animation.
  1078. Blockly.BlockSvg.disposeUiStep_(clone, this.RTL, new Date(),
  1079. this.workspace.scale);
  1080. };
  1081. /**
  1082. * Animate a cloned block and eventually dispose of it.
  1083. * This is a class method, not an instace method since the original block has
  1084. * been destroyed and is no longer accessible.
  1085. * @param {!Element} clone SVG element to animate and dispose of.
  1086. * @param {boolean} rtl True if RTL, false if LTR.
  1087. * @param {!Date} start Date of animation's start.
  1088. * @param {number} workspaceScale Scale of workspace.
  1089. * @private
  1090. */
  1091. Blockly.BlockSvg.disposeUiStep_ = function(clone, rtl, start, workspaceScale) {
  1092. var ms = (new Date()) - start;
  1093. var percent = ms / 150;
  1094. if (percent > 1) {
  1095. goog.dom.removeNode(clone);
  1096. } else {
  1097. var x = clone.translateX_ +
  1098. (rtl ? -1 : 1) * clone.bBox_.width * workspaceScale / 2 * percent;
  1099. var y = clone.translateY_ + clone.bBox_.height * workspaceScale * percent;
  1100. var scale = (1 - percent) * workspaceScale;
  1101. clone.setAttribute('transform', 'translate(' + x + ',' + y + ')' +
  1102. ' scale(' + scale + ')');
  1103. var closure = function() {
  1104. Blockly.BlockSvg.disposeUiStep_(clone, rtl, start, workspaceScale);
  1105. };
  1106. setTimeout(closure, 10);
  1107. }
  1108. };
  1109. /**
  1110. * Play some UI effects (sound, ripple) after a connection has been established.
  1111. */
  1112. Blockly.BlockSvg.prototype.connectionUiEffect = function() {
  1113. this.workspace.playAudio('click');
  1114. if (this.workspace.scale < 1) {
  1115. return; // Too small to care about visual effects.
  1116. }
  1117. // Determine the absolute coordinates of the inferior block.
  1118. var xy = Blockly.getSvgXY_(/** @type {!Element} */ (this.svgGroup_),
  1119. this.workspace);
  1120. // Offset the coordinates based on the two connection types, fix scale.
  1121. if (this.outputConnection) {
  1122. xy.x += (this.RTL ? 3 : -3) * this.workspace.scale;
  1123. xy.y += 13 * this.workspace.scale;
  1124. } else if (this.previousConnection) {
  1125. xy.x += (this.RTL ? -23 : 23) * this.workspace.scale;
  1126. xy.y += 3 * this.workspace.scale;
  1127. }
  1128. var ripple = Blockly.createSvgElement('circle',
  1129. {'cx': xy.x, 'cy': xy.y, 'r': 0, 'fill': 'none',
  1130. 'stroke': '#888', 'stroke-width': 10},
  1131. this.workspace.options.svg);
  1132. // Start the animation.
  1133. Blockly.BlockSvg.connectionUiStep_(ripple, new Date(), this.workspace.scale);
  1134. };
  1135. /**
  1136. * Expand a ripple around a connection.
  1137. * @param {!Element} ripple Element to animate.
  1138. * @param {!Date} start Date of animation's start.
  1139. * @param {number} workspaceScale Scale of workspace.
  1140. * @private
  1141. */
  1142. Blockly.BlockSvg.connectionUiStep_ = function(ripple, start, workspaceScale) {
  1143. var ms = (new Date()) - start;
  1144. var percent = ms / 150;
  1145. if (percent > 1) {
  1146. goog.dom.removeNode(ripple);
  1147. } else {
  1148. ripple.setAttribute('r', percent * 25 * workspaceScale);
  1149. ripple.style.opacity = 1 - percent;
  1150. var closure = function() {
  1151. Blockly.BlockSvg.connectionUiStep_(ripple, start, workspaceScale);
  1152. };
  1153. Blockly.BlockSvg.disconnectUiStop_.pid_ = setTimeout(closure, 10);
  1154. }
  1155. };
  1156. /**
  1157. * Play some UI effects (sound, animation) when disconnecting a block.
  1158. */
  1159. Blockly.BlockSvg.prototype.disconnectUiEffect = function() {
  1160. this.workspace.playAudio('disconnect');
  1161. if (this.workspace.scale < 1) {
  1162. return; // Too small to care about visual effects.
  1163. }
  1164. // Horizontal distance for bottom of block to wiggle.
  1165. var DISPLACEMENT = 10;
  1166. // Scale magnitude of skew to height of block.
  1167. var height = this.getHeightWidth().height;
  1168. var magnitude = Math.atan(DISPLACEMENT / height) / Math.PI * 180;
  1169. if (!this.RTL) {
  1170. magnitude *= -1;
  1171. }
  1172. // Start the animation.
  1173. Blockly.BlockSvg.disconnectUiStep_(this.svgGroup_, magnitude, new Date());
  1174. };
  1175. /**
  1176. * Animate a brief wiggle of a disconnected block.
  1177. * @param {!Element} group SVG element to animate.
  1178. * @param {number} magnitude Maximum degrees skew (reversed for RTL).
  1179. * @param {!Date} start Date of animation's start.
  1180. * @private
  1181. */
  1182. Blockly.BlockSvg.disconnectUiStep_ = function(group, magnitude, start) {
  1183. var DURATION = 200; // Milliseconds.
  1184. var WIGGLES = 3; // Half oscillations.
  1185. var ms = (new Date()) - start;
  1186. var percent = ms / DURATION;
  1187. if (percent > 1) {
  1188. group.skew_ = '';
  1189. } else {
  1190. var skew = Math.round(Math.sin(percent * Math.PI * WIGGLES) *
  1191. (1 - percent) * magnitude);
  1192. group.skew_ = 'skewX(' + skew + ')';
  1193. var closure = function() {
  1194. Blockly.BlockSvg.disconnectUiStep_(group, magnitude, start);
  1195. };
  1196. Blockly.BlockSvg.disconnectUiStop_.group = group;
  1197. Blockly.BlockSvg.disconnectUiStop_.pid = setTimeout(closure, 10);
  1198. }
  1199. group.setAttribute('transform', group.translate_ + group.skew_);
  1200. };
  1201. /**
  1202. * Stop the disconnect UI animation immediately.
  1203. * @private
  1204. */
  1205. Blockly.BlockSvg.disconnectUiStop_ = function() {
  1206. if (Blockly.BlockSvg.disconnectUiStop_.group) {
  1207. clearTimeout(Blockly.BlockSvg.disconnectUiStop_.pid);
  1208. var group = Blockly.BlockSvg.disconnectUiStop_.group
  1209. group.skew_ = '';
  1210. group.setAttribute('transform', group.translate_);
  1211. Blockly.BlockSvg.disconnectUiStop_.group = null;
  1212. }
  1213. };
  1214. /**
  1215. * PID of disconnect UI animation. There can only be one at a time.
  1216. * @type {number}
  1217. */
  1218. Blockly.BlockSvg.disconnectUiStop_.pid = 0;
  1219. /**
  1220. * SVG group of wobbling block. There can only be one at a time.
  1221. * @type {Element}
  1222. */
  1223. Blockly.BlockSvg.disconnectUiStop_.group = null;
  1224. /**
  1225. * Change the colour of a block.
  1226. */
  1227. Blockly.BlockSvg.prototype.updateColour = function() {
  1228. if (this.disabled) {
  1229. // Disabled blocks don't have colour.
  1230. return;
  1231. }
  1232. var hexColour = Blockly.makeColour(this.getColour());
  1233. var rgb = goog.color.hexToRgb(hexColour);
  1234. if (this.isShadow()) {
  1235. rgb = goog.color.lighten(rgb, 0.6);
  1236. hexColour = goog.color.rgbArrayToHex(rgb);
  1237. this.svgPathLight_.style.display = 'none';
  1238. this.svgPathDark_.setAttribute('fill', hexColour);
  1239. } else {
  1240. this.svgPathLight_.style.display = '';
  1241. var hexLight = goog.color.rgbArrayToHex(goog.color.lighten(rgb, 0.3));
  1242. var hexDark = goog.color.rgbArrayToHex(goog.color.darken(rgb, 0.2));
  1243. this.svgPathLight_.setAttribute('stroke', hexLight);
  1244. this.svgPathDark_.setAttribute('fill', hexDark);
  1245. }
  1246. this.svgPath_.setAttribute('fill', hexColour);
  1247. var icons = this.getIcons();
  1248. for (var i = 0; i < icons.length; i++) {
  1249. icons[i].updateColour();
  1250. }
  1251. // Bump every dropdown to change its colour.
  1252. for (var x = 0, input; input = this.inputList[x]; x++) {
  1253. for (var y = 0, field; field = input.fieldRow[y]; y++) {
  1254. field.setText(null);
  1255. }
  1256. }
  1257. };
  1258. /**
  1259. * Enable or disable a block.
  1260. */
  1261. Blockly.BlockSvg.prototype.updateDisabled = function() {
  1262. var hasClass = Blockly.hasClass_(/** @type {!Element} */ (this.svgGroup_),
  1263. 'blocklyDisabled');
  1264. if (this.disabled || this.getInheritedDisabled()) {
  1265. if (!hasClass) {
  1266. Blockly.addClass_(/** @type {!Element} */ (this.svgGroup_),
  1267. 'blocklyDisabled');
  1268. this.svgPath_.setAttribute('fill',
  1269. 'url(#' + this.workspace.options.disabledPatternId + ')');
  1270. }
  1271. } else {
  1272. if (hasClass) {
  1273. Blockly.removeClass_(/** @type {!Element} */ (this.svgGroup_),
  1274. 'blocklyDisabled');
  1275. this.updateColour();
  1276. }
  1277. }
  1278. var children = this.getChildren();
  1279. for (var i = 0, child; child = children[i]; i++) {
  1280. child.updateDisabled();
  1281. }
  1282. };
  1283. /**
  1284. * Returns the comment on this block (or '' if none).
  1285. * @return {string} Block's comment.
  1286. */
  1287. Blockly.BlockSvg.prototype.getCommentText = function() {
  1288. if (this.comment) {
  1289. var comment = this.comment.getText();
  1290. // Trim off trailing whitespace.
  1291. return comment.replace(/\s+$/, '').replace(/ +\n/g, '\n');
  1292. }
  1293. return '';
  1294. };
  1295. /**
  1296. * Set this block's comment text.
  1297. * @param {?string} text The text, or null to delete.
  1298. */
  1299. Blockly.BlockSvg.prototype.setCommentText = function(text) {
  1300. var changedState = false;
  1301. if (goog.isString(text)) {
  1302. if (!this.comment) {
  1303. this.comment = new Blockly.Comment(this);
  1304. changedState = true;
  1305. }
  1306. this.comment.setText(/** @type {string} */ (text));
  1307. } else {
  1308. if (this.comment) {
  1309. this.comment.dispose();
  1310. changedState = true;
  1311. }
  1312. }
  1313. if (changedState && this.rendered) {
  1314. this.render();
  1315. // Adding or removing a comment icon will cause the block to change shape.
  1316. this.bumpNeighbours_();
  1317. }
  1318. };
  1319. /**
  1320. * Set this block's warning text.
  1321. * @param {?string} text The text, or null to delete.
  1322. * @param {string=} opt_id An optional ID for the warning text to be able to
  1323. * maintain multiple warnings.
  1324. */
  1325. Blockly.BlockSvg.prototype.setWarningText = function(text, opt_id) {
  1326. if (!this.setWarningText.pid_) {
  1327. // Create a database of warning PIDs.
  1328. // Only runs once per block (and only those with warnings).
  1329. this.setWarningText.pid_ = Object.create(null);
  1330. }
  1331. var id = opt_id || '';
  1332. if (!id) {
  1333. // Kill all previous pending processes, this edit supercedes them all.
  1334. for (var n in this.setWarningText.pid_) {
  1335. clearTimeout(this.setWarningText.pid_[n]);
  1336. delete this.setWarningText.pid_[n];
  1337. }
  1338. } else if (this.setWarningText.pid_[id]) {
  1339. // Only queue up the latest change. Kill any earlier pending process.
  1340. clearTimeout(this.setWarningText.pid_[id]);
  1341. delete this.setWarningText.pid_[id];
  1342. }
  1343. if (Blockly.dragMode_ == 2) {
  1344. // Don't change the warning text during a drag.
  1345. // Wait until the drag finishes.
  1346. var thisBlock = this;
  1347. this.setWarningText.pid_[id] = setTimeout(function() {
  1348. if (thisBlock.workspace) { // Check block wasn't deleted.
  1349. delete thisBlock.setWarningText.pid_[id];
  1350. thisBlock.setWarningText(text, id);
  1351. }
  1352. }, 100);
  1353. return;
  1354. }
  1355. if (this.isInFlyout) {
  1356. text = null;
  1357. }
  1358. // Bubble up to add a warning on top-most collapsed block.
  1359. var parent = this.getSurroundParent();
  1360. var collapsedParent = null;
  1361. while (parent) {
  1362. if (parent.isCollapsed()) {
  1363. collapsedParent = parent;
  1364. }
  1365. parent = parent.getSurroundParent();
  1366. }
  1367. if (collapsedParent) {
  1368. collapsedParent.setWarningText(text, 'collapsed ' + this.id + ' ' + id);
  1369. }
  1370. var changedState = false;
  1371. if (goog.isString(text)) {
  1372. if (!this.warning) {
  1373. this.warning = new Blockly.Warning(this);
  1374. changedState = true;
  1375. }
  1376. this.warning.setText(/** @type {string} */ (text), id);
  1377. } else {
  1378. // Dispose all warnings if no id is given.
  1379. if (this.warning && !id) {
  1380. this.warning.dispose();
  1381. changedState = true;
  1382. } else if (this.warning) {
  1383. var oldText = this.warning.getText();
  1384. this.warning.setText('', id);
  1385. var newText = this.warning.getText();
  1386. if (!newText) {
  1387. this.warning.dispose();
  1388. }
  1389. changedState = oldText == newText;
  1390. }
  1391. }
  1392. if (changedState && this.rendered) {
  1393. this.render();
  1394. // Adding or removing a warning icon will cause the block to change shape.
  1395. this.bumpNeighbours_();
  1396. }
  1397. };
  1398. /**
  1399. * Give this block a mutator dialog.
  1400. * @param {Blockly.Mutator} mutator A mutator dialog instance or null to remove.
  1401. */
  1402. Blockly.BlockSvg.prototype.setMutator = function(mutator) {
  1403. if (this.mutator && this.mutator !== mutator) {
  1404. this.mutator.dispose();
  1405. }
  1406. if (mutator) {
  1407. mutator.block_ = this;
  1408. this.mutator = mutator;
  1409. if (this.rendered) {
  1410. mutator.createIcon();
  1411. }
  1412. }
  1413. };
  1414. /**
  1415. * Set whether the block is disabled or not.
  1416. * @param {boolean} disabled True if disabled.
  1417. */
  1418. Blockly.BlockSvg.prototype.setDisabled = function(disabled) {
  1419. if (this.disabled == disabled) {
  1420. return;
  1421. }
  1422. Blockly.BlockSvg.superClass_.setDisabled.call(this, disabled);
  1423. if (this.rendered) {
  1424. this.updateDisabled();
  1425. }
  1426. this.workspace.fireChangeEvent();
  1427. };
  1428. /**
  1429. * Select this block. Highlight it visually.
  1430. */
  1431. Blockly.BlockSvg.prototype.addSelect = function() {
  1432. Blockly.addClass_(/** @type {!Element} */ (this.svgGroup_),
  1433. 'blocklySelected');
  1434. // Move the selected block to the top of the stack.
  1435. this.svgGroup_.parentNode.appendChild(this.svgGroup_);
  1436. };
  1437. /**
  1438. * Unselect this block. Remove its highlighting.
  1439. */
  1440. Blockly.BlockSvg.prototype.removeSelect = function() {
  1441. Blockly.removeClass_(/** @type {!Element} */ (this.svgGroup_),
  1442. 'blocklySelected');
  1443. };
  1444. /**
  1445. * Adds the dragging class to this block.
  1446. * Also disables the highlights/shadows to improve performance.
  1447. */
  1448. Blockly.BlockSvg.prototype.addDragging = function() {
  1449. Blockly.addClass_(/** @type {!Element} */ (this.svgGroup_),
  1450. 'blocklyDragging');
  1451. };
  1452. /**
  1453. * Removes the dragging class from this block.
  1454. */
  1455. Blockly.BlockSvg.prototype.removeDragging = function() {
  1456. Blockly.removeClass_(/** @type {!Element} */ (this.svgGroup_),
  1457. 'blocklyDragging');
  1458. };
  1459. /**
  1460. * Render the block.
  1461. * Lays out and reflows a block based on its contents and settings.
  1462. * @param {boolean=} opt_bubble If false, just render this block.
  1463. * If true, also render block's parent, grandparent, etc. Defaults to true.
  1464. */
  1465. Blockly.BlockSvg.prototype.render = function(opt_bubble) {
  1466. Blockly.Field.startCache();
  1467. this.rendered = true;
  1468. var cursorX = Blockly.BlockSvg.SEP_SPACE_X;
  1469. if (this.RTL) {
  1470. cursorX = -cursorX;
  1471. }
  1472. // Move the icons into position.
  1473. var icons = this.getIcons();
  1474. for (var i = 0; i < icons.length; i++) {
  1475. cursorX = icons[i].renderIcon(cursorX);
  1476. }
  1477. cursorX += this.RTL ?
  1478. Blockly.BlockSvg.SEP_SPACE_X : -Blockly.BlockSvg.SEP_SPACE_X;
  1479. // If there are no icons, cursorX will be 0, otherwise it will be the
  1480. // width that the first label needs to move over by.
  1481. var inputRows = this.renderCompute_(cursorX);
  1482. this.renderDraw_(cursorX, inputRows);
  1483. if (opt_bubble !== false) {
  1484. // Render all blocks above this one (propagate a reflow).
  1485. var parentBlock = this.getParent();
  1486. if (parentBlock) {
  1487. parentBlock.render(true);
  1488. } else {
  1489. // Top-most block. Fire an event to allow scrollbars to resize.
  1490. Blockly.fireUiEvent(window, 'resize');
  1491. }
  1492. }
  1493. Blockly.Field.stopCache();
  1494. Blockly.Realtime.blockChanged(this);
  1495. };
  1496. /**
  1497. * Render a list of fields starting at the specified location.
  1498. * @param {!Array.<!Blockly.Field>} fieldList List of fields.
  1499. * @param {number} cursorX X-coordinate to start the fields.
  1500. * @param {number} cursorY Y-coordinate to start the fields.
  1501. * @return {number} X-coordinate of the end of the field row (plus a gap).
  1502. * @private
  1503. */
  1504. Blockly.BlockSvg.prototype.renderFields_ =
  1505. function(fieldList, cursorX, cursorY) {
  1506. cursorY += Blockly.BlockSvg.INLINE_PADDING_Y;
  1507. if (this.RTL) {
  1508. cursorX = -cursorX;
  1509. }
  1510. for (var t = 0, field; field = fieldList[t]; t++) {
  1511. var root = field.getSvgRoot();
  1512. if (!root) {
  1513. continue;
  1514. }
  1515. if (this.RTL) {
  1516. cursorX -= field.renderSep + field.renderWidth;
  1517. root.setAttribute('transform',
  1518. 'translate(' + cursorX + ',' + cursorY + ')');
  1519. if (field.renderWidth) {
  1520. cursorX -= Blockly.BlockSvg.SEP_SPACE_X;
  1521. }
  1522. } else {
  1523. root.setAttribute('transform',
  1524. 'translate(' + (cursorX + field.renderSep) + ',' + cursorY + ')');
  1525. if (field.renderWidth) {
  1526. cursorX += field.renderSep + field.renderWidth +
  1527. Blockly.BlockSvg.SEP_SPACE_X;
  1528. }
  1529. }
  1530. }
  1531. return this.RTL ? -cursorX : cursorX;
  1532. };
  1533. /**
  1534. * Computes the height and widths for each row and field.
  1535. * @param {number} iconWidth Offset of first row due to icons.
  1536. * @return {!Array.<!Array.<!Object>>} 2D array of objects, each containing
  1537. * position information.
  1538. * @private
  1539. */
  1540. Blockly.BlockSvg.prototype.renderCompute_ = function(iconWidth) {
  1541. var inputList = this.inputList;
  1542. var inputRows = [];
  1543. inputRows.rightEdge = iconWidth + Blockly.BlockSvg.SEP_SPACE_X * 2;
  1544. if (this.previousConnection || this.nextConnection) {
  1545. inputRows.rightEdge = Math.max(inputRows.rightEdge,
  1546. Blockly.BlockSvg.NOTCH_WIDTH + Blockly.BlockSvg.SEP_SPACE_X);
  1547. }
  1548. var fieldValueWidth = 0; // Width of longest external value field.
  1549. var fieldStatementWidth = 0; // Width of longest statement field.
  1550. var hasValue = false;
  1551. var hasStatement = false;
  1552. var hasDummy = false;
  1553. var lastType = undefined;
  1554. var isInline = this.getInputsInline() && !this.isCollapsed();
  1555. for (var i = 0, input; input = inputList[i]; i++) {
  1556. if (!input.isVisible()) {
  1557. continue;
  1558. }
  1559. var row;
  1560. if (!isInline || !lastType ||
  1561. lastType == Blockly.NEXT_STATEMENT ||
  1562. input.type == Blockly.NEXT_STATEMENT) {
  1563. // Create new row.
  1564. lastType = input.type;
  1565. row = [];
  1566. if (isInline && input.type != Blockly.NEXT_STATEMENT) {
  1567. row.type = Blockly.BlockSvg.INLINE;
  1568. } else {
  1569. row.type = input.type;
  1570. }
  1571. row.height = 0;
  1572. inputRows.push(row);
  1573. } else {
  1574. row = inputRows[inputRows.length - 1];
  1575. }
  1576. row.push(input);
  1577. // Compute minimum input size.
  1578. input.renderHeight = Blockly.BlockSvg.MIN_BLOCK_Y;
  1579. // The width is currently only needed for inline value inputs.
  1580. if (isInline && input.type == Blockly.INPUT_VALUE) {
  1581. input.renderWidth = Blockly.BlockSvg.TAB_WIDTH +
  1582. Blockly.BlockSvg.SEP_SPACE_X * 1.25;
  1583. } else {
  1584. input.renderWidth = 0;
  1585. }
  1586. // Expand input size if there is a connection.
  1587. if (input.connection && input.connection.targetConnection) {
  1588. var linkedBlock = input.connection.targetBlock();
  1589. var bBox = linkedBlock.getHeightWidth();
  1590. input.renderHeight = Math.max(input.renderHeight, bBox.height);
  1591. input.renderWidth = Math.max(input.renderWidth, bBox.width);
  1592. }
  1593. // Blocks have a one pixel shadow that should sometimes overhang.
  1594. if (!isInline && i == inputList.length - 1) {
  1595. // Last value input should overhang.
  1596. input.renderHeight--;
  1597. } else if (!isInline && input.type == Blockly.INPUT_VALUE &&
  1598. inputList[i + 1] && inputList[i + 1].type == Blockly.NEXT_STATEMENT) {
  1599. // Value input above statement input should overhang.
  1600. input.renderHeight--;
  1601. }
  1602. row.height = Math.max(row.height, input.renderHeight);
  1603. input.fieldWidth = 0;
  1604. if (inputRows.length == 1) {
  1605. // The first row gets shifted to accommodate any icons.
  1606. input.fieldWidth += this.RTL ? -iconWidth : iconWidth;
  1607. }
  1608. var previousFieldEditable = false;
  1609. for (var j = 0, field; field = input.fieldRow[j]; j++) {
  1610. if (j != 0) {
  1611. input.fieldWidth += Blockly.BlockSvg.SEP_SPACE_X;
  1612. }
  1613. // Get the dimensions of the field.
  1614. var fieldSize = field.getSize();
  1615. field.renderWidth = fieldSize.width;
  1616. field.renderSep = (previousFieldEditable && field.EDITABLE) ?
  1617. Blockly.BlockSvg.SEP_SPACE_X : 0;
  1618. input.fieldWidth += field.renderWidth + field.renderSep;
  1619. row.height = Math.max(row.height, fieldSize.height);
  1620. previousFieldEditable = field.EDITABLE;
  1621. }
  1622. if (row.type != Blockly.BlockSvg.INLINE) {
  1623. if (row.type == Blockly.NEXT_STATEMENT) {
  1624. hasStatement = true;
  1625. fieldStatementWidth = Math.max(fieldStatementWidth, input.fieldWidth);
  1626. } else {
  1627. if (row.type == Blockly.INPUT_VALUE) {
  1628. hasValue = true;
  1629. } else if (row.type == Blockly.DUMMY_INPUT) {
  1630. hasDummy = true;
  1631. }
  1632. fieldValueWidth = Math.max(fieldValueWidth, input.fieldWidth);
  1633. }
  1634. }
  1635. }
  1636. // Make inline rows a bit thicker in order to enclose the values.
  1637. for (var y = 0, row; row = inputRows[y]; y++) {
  1638. row.thicker = false;
  1639. if (row.type == Blockly.BlockSvg.INLINE) {
  1640. for (var z = 0, input; input = row[z]; z++) {
  1641. if (input.type == Blockly.INPUT_VALUE) {
  1642. row.height += 2 * Blockly.BlockSvg.INLINE_PADDING_Y;
  1643. row.thicker = true;
  1644. break;
  1645. }
  1646. }
  1647. }
  1648. }
  1649. // Compute the statement edge.
  1650. // This is the width of a block where statements are nested.
  1651. inputRows.statementEdge = 2 * Blockly.BlockSvg.SEP_SPACE_X +
  1652. fieldStatementWidth;
  1653. // Compute the preferred right edge. Inline blocks may extend beyond.
  1654. // This is the width of the block where external inputs connect.
  1655. if (hasStatement) {
  1656. inputRows.rightEdge = Math.max(inputRows.rightEdge,
  1657. inputRows.statementEdge + Blockly.BlockSvg.NOTCH_WIDTH);
  1658. }
  1659. if (hasValue) {
  1660. inputRows.rightEdge = Math.max(inputRows.rightEdge, fieldValueWidth +
  1661. Blockly.BlockSvg.SEP_SPACE_X * 2 + Blockly.BlockSvg.TAB_WIDTH);
  1662. } else if (hasDummy) {
  1663. inputRows.rightEdge = Math.max(inputRows.rightEdge, fieldValueWidth +
  1664. Blockly.BlockSvg.SEP_SPACE_X * 2);
  1665. }
  1666. inputRows.hasValue = hasValue;
  1667. inputRows.hasStatement = hasStatement;
  1668. inputRows.hasDummy = hasDummy;
  1669. return inputRows;
  1670. };
  1671. /**
  1672. * Draw the path of the block.
  1673. * Move the fields to the correct locations.
  1674. * @param {number} iconWidth Offset of first row due to icons.
  1675. * @param {!Array.<!Array.<!Object>>} inputRows 2D array of objects, each
  1676. * containing position information.
  1677. * @private
  1678. */
  1679. Blockly.BlockSvg.prototype.renderDraw_ = function(iconWidth, inputRows) {
  1680. this.startHat_ = false;
  1681. // Should the top and bottom left corners be rounded or square?
  1682. if (this.outputConnection) {
  1683. this.squareTopLeftCorner_ = true;
  1684. this.squareBottomLeftCorner_ = true;
  1685. } else {
  1686. this.squareTopLeftCorner_ = false;
  1687. this.squareBottomLeftCorner_ = false;
  1688. // If this block is in the middle of a stack, square the corners.
  1689. if (this.previousConnection) {
  1690. var prevBlock = this.previousConnection.targetBlock();
  1691. if (prevBlock && prevBlock.getNextBlock() == this) {
  1692. this.squareTopLeftCorner_ = true;
  1693. }
  1694. } else if (Blockly.BlockSvg.START_HAT) {
  1695. // No output or previous connection.
  1696. this.squareTopLeftCorner_ = true;
  1697. this.startHat_ = true;
  1698. inputRows.rightEdge = Math.max(inputRows.rightEdge, 100);
  1699. }
  1700. var nextBlock = this.getNextBlock();
  1701. if (nextBlock) {
  1702. this.squareBottomLeftCorner_ = true;
  1703. }
  1704. }
  1705. // Fetch the block's coordinates on the surface for use in anchoring
  1706. // the connections.
  1707. var connectionsXY = this.getRelativeToSurfaceXY();
  1708. // Assemble the block's path.
  1709. var steps = [];
  1710. var inlineSteps = [];
  1711. // The highlighting applies to edges facing the upper-left corner.
  1712. // Since highlighting is a two-pixel wide border, it would normally overhang
  1713. // the edge of the block by a pixel. So undersize all measurements by a pixel.
  1714. var highlightSteps = [];
  1715. var highlightInlineSteps = [];
  1716. this.renderDrawTop_(steps, highlightSteps, connectionsXY,
  1717. inputRows.rightEdge);
  1718. var cursorY = this.renderDrawRight_(steps, highlightSteps, inlineSteps,
  1719. highlightInlineSteps, connectionsXY, inputRows, iconWidth);
  1720. this.renderDrawBottom_(steps, highlightSteps, connectionsXY, cursorY);
  1721. this.renderDrawLeft_(steps, highlightSteps, connectionsXY, cursorY);
  1722. var pathString = steps.join(' ') + '\n' + inlineSteps.join(' ');
  1723. this.svgPath_.setAttribute('d', pathString);
  1724. this.svgPathDark_.setAttribute('d', pathString);
  1725. pathString = highlightSteps.join(' ') + '\n' + highlightInlineSteps.join(' ');
  1726. this.svgPathLight_.setAttribute('d', pathString);
  1727. if (this.RTL) {
  1728. // Mirror the block's path.
  1729. this.svgPath_.setAttribute('transform', 'scale(-1 1)');
  1730. this.svgPathLight_.setAttribute('transform', 'scale(-1 1)');
  1731. this.svgPathDark_.setAttribute('transform', 'translate(1,1) scale(-1 1)');
  1732. }
  1733. };
  1734. /**
  1735. * Render the top edge of the block.
  1736. * @param {!Array.<string>} steps Path of block outline.
  1737. * @param {!Array.<string>} highlightSteps Path of block highlights.
  1738. * @param {!Object} connectionsXY Location of block.
  1739. * @param {number} rightEdge Minimum width of block.
  1740. * @private
  1741. */
  1742. Blockly.BlockSvg.prototype.renderDrawTop_ =
  1743. function(steps, highlightSteps, connectionsXY, rightEdge) {
  1744. // Position the cursor at the top-left starting point.
  1745. if (this.squareTopLeftCorner_) {
  1746. steps.push('m 0,0');
  1747. highlightSteps.push('m 0.5,0.5');
  1748. if (this.startHat_) {
  1749. steps.push(Blockly.BlockSvg.START_HAT_PATH);
  1750. highlightSteps.push(this.RTL ?
  1751. Blockly.BlockSvg.START_HAT_HIGHLIGHT_RTL :
  1752. Blockly.BlockSvg.START_HAT_HIGHLIGHT_LTR);
  1753. }
  1754. } else {
  1755. steps.push(Blockly.BlockSvg.TOP_LEFT_CORNER_START);
  1756. highlightSteps.push(this.RTL ?
  1757. Blockly.BlockSvg.TOP_LEFT_CORNER_START_HIGHLIGHT_RTL :
  1758. Blockly.BlockSvg.TOP_LEFT_CORNER_START_HIGHLIGHT_LTR);
  1759. // Top-left rounded corner.
  1760. steps.push(Blockly.BlockSvg.TOP_LEFT_CORNER);
  1761. highlightSteps.push(Blockly.BlockSvg.TOP_LEFT_CORNER_HIGHLIGHT);
  1762. }
  1763. // Top edge.
  1764. if (this.previousConnection) {
  1765. steps.push('H', Blockly.BlockSvg.NOTCH_WIDTH - 15);
  1766. highlightSteps.push('H', Blockly.BlockSvg.NOTCH_WIDTH - 15);
  1767. steps.push(Blockly.BlockSvg.NOTCH_PATH_LEFT);
  1768. highlightSteps.push(Blockly.BlockSvg.NOTCH_PATH_LEFT_HIGHLIGHT);
  1769. // Create previous block connection.
  1770. var connectionX = connectionsXY.x + (this.RTL ?
  1771. -Blockly.BlockSvg.NOTCH_WIDTH : Blockly.BlockSvg.NOTCH_WIDTH);
  1772. var connectionY = connectionsXY.y;
  1773. this.previousConnection.moveTo(connectionX, connectionY);
  1774. // This connection will be tightened when the parent renders.
  1775. }
  1776. steps.push('H', rightEdge);
  1777. highlightSteps.push('H', rightEdge - 0.5);
  1778. this.width = rightEdge;
  1779. };
  1780. /**
  1781. * Render the right edge of the block.
  1782. * @param {!Array.<string>} steps Path of block outline.
  1783. * @param {!Array.<string>} highlightSteps Path of block highlights.
  1784. * @param {!Array.<string>} inlineSteps Inline block outlines.
  1785. * @param {!Array.<string>} highlightInlineSteps Inline block highlights.
  1786. * @param {!Object} connectionsXY Location of block.
  1787. * @param {!Array.<!Array.<!Object>>} inputRows 2D array of objects, each
  1788. * containing position information.
  1789. * @param {number} iconWidth Offset of first row due to icons.
  1790. * @return {number} Height of block.
  1791. * @private
  1792. */
  1793. Blockly.BlockSvg.prototype.renderDrawRight_ = function(steps, highlightSteps,
  1794. inlineSteps, highlightInlineSteps, connectionsXY, inputRows, iconWidth) {
  1795. var cursorX;
  1796. var cursorY = 0;
  1797. var connectionX, connectionY;
  1798. for (var y = 0, row; row = inputRows[y]; y++) {
  1799. cursorX = Blockly.BlockSvg.SEP_SPACE_X;
  1800. if (y == 0) {
  1801. cursorX += this.RTL ? -iconWidth : iconWidth;
  1802. }
  1803. highlightSteps.push('M', (inputRows.rightEdge - 0.5) + ',' +
  1804. (cursorY + 0.5));
  1805. if (this.isCollapsed()) {
  1806. // Jagged right edge.
  1807. var input = row[0];
  1808. var fieldX = cursorX;
  1809. var fieldY = cursorY;
  1810. this.renderFields_(input.fieldRow, fieldX, fieldY);
  1811. steps.push(Blockly.BlockSvg.JAGGED_TEETH);
  1812. highlightSteps.push('h 8');
  1813. var remainder = row.height - Blockly.BlockSvg.JAGGED_TEETH_HEIGHT;
  1814. steps.push('v', remainder);
  1815. if (this.RTL) {
  1816. highlightSteps.push('v 3.9 l 7.2,3.4 m -14.5,8.9 l 7.3,3.5');
  1817. highlightSteps.push('v', remainder - 0.7);
  1818. }
  1819. this.width += Blockly.BlockSvg.JAGGED_TEETH_WIDTH;
  1820. } else if (row.type == Blockly.BlockSvg.INLINE) {
  1821. // Inline inputs.
  1822. for (var x = 0, input; input = row[x]; x++) {
  1823. var fieldX = cursorX;
  1824. var fieldY = cursorY;
  1825. if (row.thicker) {
  1826. // Lower the field slightly.
  1827. fieldY += Blockly.BlockSvg.INLINE_PADDING_Y;
  1828. }
  1829. // TODO: Align inline field rows (left/right/centre).
  1830. cursorX = this.renderFields_(input.fieldRow, fieldX, fieldY);
  1831. if (input.type != Blockly.DUMMY_INPUT) {
  1832. cursorX += input.renderWidth + Blockly.BlockSvg.SEP_SPACE_X;
  1833. }
  1834. if (input.type == Blockly.INPUT_VALUE) {
  1835. inlineSteps.push('M', (cursorX - Blockly.BlockSvg.SEP_SPACE_X) +
  1836. ',' + (cursorY + Blockly.BlockSvg.INLINE_PADDING_Y));
  1837. inlineSteps.push('h', Blockly.BlockSvg.TAB_WIDTH - 2 -
  1838. input.renderWidth);
  1839. inlineSteps.push(Blockly.BlockSvg.TAB_PATH_DOWN);
  1840. inlineSteps.push('v', input.renderHeight + 1 -
  1841. Blockly.BlockSvg.TAB_HEIGHT);
  1842. inlineSteps.push('h', input.renderWidth + 2 -
  1843. Blockly.BlockSvg.TAB_WIDTH);
  1844. inlineSteps.push('z');
  1845. if (this.RTL) {
  1846. // Highlight right edge, around back of tab, and bottom.
  1847. highlightInlineSteps.push('M',
  1848. (cursorX - Blockly.BlockSvg.SEP_SPACE_X - 2.5 +
  1849. Blockly.BlockSvg.TAB_WIDTH - input.renderWidth) + ',' +
  1850. (cursorY + Blockly.BlockSvg.INLINE_PADDING_Y + 0.5));
  1851. highlightInlineSteps.push(
  1852. Blockly.BlockSvg.TAB_PATH_DOWN_HIGHLIGHT_RTL);
  1853. highlightInlineSteps.push('v',
  1854. input.renderHeight - Blockly.BlockSvg.TAB_HEIGHT + 2.5);
  1855. highlightInlineSteps.push('h',
  1856. input.renderWidth - Blockly.BlockSvg.TAB_WIDTH + 2);
  1857. } else {
  1858. // Highlight right edge, bottom.
  1859. highlightInlineSteps.push('M',
  1860. (cursorX - Blockly.BlockSvg.SEP_SPACE_X + 0.5) + ',' +
  1861. (cursorY + Blockly.BlockSvg.INLINE_PADDING_Y + 0.5));
  1862. highlightInlineSteps.push('v', input.renderHeight + 1);
  1863. highlightInlineSteps.push('h', Blockly.BlockSvg.TAB_WIDTH - 2 -
  1864. input.renderWidth);
  1865. // Short highlight glint at bottom of tab.
  1866. highlightInlineSteps.push('M',
  1867. (cursorX - input.renderWidth - Blockly.BlockSvg.SEP_SPACE_X +
  1868. 0.9) + ',' + (cursorY + Blockly.BlockSvg.INLINE_PADDING_Y +
  1869. Blockly.BlockSvg.TAB_HEIGHT - 0.7));
  1870. highlightInlineSteps.push('l',
  1871. (Blockly.BlockSvg.TAB_WIDTH * 0.46) + ',-2.1');
  1872. }
  1873. // Create inline input connection.
  1874. if (this.RTL) {
  1875. connectionX = connectionsXY.x - cursorX -
  1876. Blockly.BlockSvg.TAB_WIDTH + Blockly.BlockSvg.SEP_SPACE_X +
  1877. input.renderWidth + 1;
  1878. } else {
  1879. connectionX = connectionsXY.x + cursorX +
  1880. Blockly.BlockSvg.TAB_WIDTH - Blockly.BlockSvg.SEP_SPACE_X -
  1881. input.renderWidth - 1;
  1882. }
  1883. connectionY = connectionsXY.y + cursorY +
  1884. Blockly.BlockSvg.INLINE_PADDING_Y + 1;
  1885. input.connection.moveTo(connectionX, connectionY);
  1886. if (input.connection.targetConnection) {
  1887. input.connection.tighten_();
  1888. }
  1889. }
  1890. }
  1891. cursorX = Math.max(cursorX, inputRows.rightEdge);
  1892. this.width = Math.max(this.width, cursorX);
  1893. steps.push('H', cursorX);
  1894. highlightSteps.push('H', cursorX - 0.5);
  1895. steps.push('v', row.height);
  1896. if (this.RTL) {
  1897. highlightSteps.push('v', row.height - 1);
  1898. }
  1899. } else if (row.type == Blockly.INPUT_VALUE) {
  1900. // External input.
  1901. var input = row[0];
  1902. var fieldX = cursorX;
  1903. var fieldY = cursorY;
  1904. if (input.align != Blockly.ALIGN_LEFT) {
  1905. var fieldRightX = inputRows.rightEdge - input.fieldWidth -
  1906. Blockly.BlockSvg.TAB_WIDTH - 2 * Blockly.BlockSvg.SEP_SPACE_X;
  1907. if (input.align == Blockly.ALIGN_RIGHT) {
  1908. fieldX += fieldRightX;
  1909. } else if (input.align == Blockly.ALIGN_CENTRE) {
  1910. fieldX += fieldRightX / 2;
  1911. }
  1912. }
  1913. this.renderFields_(input.fieldRow, fieldX, fieldY);
  1914. steps.push(Blockly.BlockSvg.TAB_PATH_DOWN);
  1915. var v = row.height - Blockly.BlockSvg.TAB_HEIGHT;
  1916. steps.push('v', v);
  1917. if (this.RTL) {
  1918. // Highlight around back of tab.
  1919. highlightSteps.push(Blockly.BlockSvg.TAB_PATH_DOWN_HIGHLIGHT_RTL);
  1920. highlightSteps.push('v', v + 0.5);
  1921. } else {
  1922. // Short highlight glint at bottom of tab.
  1923. highlightSteps.push('M', (inputRows.rightEdge - 5) + ',' +
  1924. (cursorY + Blockly.BlockSvg.TAB_HEIGHT - 0.7));
  1925. highlightSteps.push('l', (Blockly.BlockSvg.TAB_WIDTH * 0.46) +
  1926. ',-2.1');
  1927. }
  1928. // Create external input connection.
  1929. connectionX = connectionsXY.x +
  1930. (this.RTL ? -inputRows.rightEdge - 1 : inputRows.rightEdge + 1);
  1931. connectionY = connectionsXY.y + cursorY;
  1932. input.connection.moveTo(connectionX, connectionY);
  1933. if (input.connection.targetConnection) {
  1934. input.connection.tighten_();
  1935. this.width = Math.max(this.width, inputRows.rightEdge +
  1936. input.connection.targetBlock().getHeightWidth().width -
  1937. Blockly.BlockSvg.TAB_WIDTH + 1);
  1938. }
  1939. } else if (row.type == Blockly.DUMMY_INPUT) {
  1940. // External naked field.
  1941. var input = row[0];
  1942. var fieldX = cursorX;
  1943. var fieldY = cursorY;
  1944. if (input.align != Blockly.ALIGN_LEFT) {
  1945. var fieldRightX = inputRows.rightEdge - input.fieldWidth -
  1946. 2 * Blockly.BlockSvg.SEP_SPACE_X;
  1947. if (inputRows.hasValue) {
  1948. fieldRightX -= Blockly.BlockSvg.TAB_WIDTH;
  1949. }
  1950. if (input.align == Blockly.ALIGN_RIGHT) {
  1951. fieldX += fieldRightX;
  1952. } else if (input.align == Blockly.ALIGN_CENTRE) {
  1953. fieldX += fieldRightX / 2;
  1954. }
  1955. }
  1956. this.renderFields_(input.fieldRow, fieldX, fieldY);
  1957. steps.push('v', row.height);
  1958. if (this.RTL) {
  1959. highlightSteps.push('v', row.height - 1);
  1960. }
  1961. } else if (row.type == Blockly.NEXT_STATEMENT) {
  1962. // Nested statement.
  1963. var input = row[0];
  1964. if (y == 0) {
  1965. // If the first input is a statement stack, add a small row on top.
  1966. steps.push('v', Blockly.BlockSvg.SEP_SPACE_Y);
  1967. if (this.RTL) {
  1968. highlightSteps.push('v', Blockly.BlockSvg.SEP_SPACE_Y - 1);
  1969. }
  1970. cursorY += Blockly.BlockSvg.SEP_SPACE_Y;
  1971. }
  1972. var fieldX = cursorX;
  1973. var fieldY = cursorY;
  1974. if (input.align != Blockly.ALIGN_LEFT) {
  1975. var fieldRightX = inputRows.statementEdge - input.fieldWidth -
  1976. 2 * Blockly.BlockSvg.SEP_SPACE_X;
  1977. if (input.align == Blockly.ALIGN_RIGHT) {
  1978. fieldX += fieldRightX;
  1979. } else if (input.align == Blockly.ALIGN_CENTRE) {
  1980. fieldX += fieldRightX / 2;
  1981. }
  1982. }
  1983. this.renderFields_(input.fieldRow, fieldX, fieldY);
  1984. cursorX = inputRows.statementEdge + Blockly.BlockSvg.NOTCH_WIDTH;
  1985. steps.push('H', cursorX);
  1986. steps.push(Blockly.BlockSvg.INNER_TOP_LEFT_CORNER);
  1987. steps.push('v', row.height - 2 * Blockly.BlockSvg.CORNER_RADIUS);
  1988. steps.push(Blockly.BlockSvg.INNER_BOTTOM_LEFT_CORNER);
  1989. steps.push('H', inputRows.rightEdge);
  1990. if (this.RTL) {
  1991. highlightSteps.push('M',
  1992. (cursorX - Blockly.BlockSvg.NOTCH_WIDTH +
  1993. Blockly.BlockSvg.DISTANCE_45_OUTSIDE) +
  1994. ',' + (cursorY + Blockly.BlockSvg.DISTANCE_45_OUTSIDE));
  1995. highlightSteps.push(
  1996. Blockly.BlockSvg.INNER_TOP_LEFT_CORNER_HIGHLIGHT_RTL);
  1997. highlightSteps.push('v',
  1998. row.height - 2 * Blockly.BlockSvg.CORNER_RADIUS);
  1999. highlightSteps.push(
  2000. Blockly.BlockSvg.INNER_BOTTOM_LEFT_CORNER_HIGHLIGHT_RTL);
  2001. highlightSteps.push('H', inputRows.rightEdge - 0.5);
  2002. } else {
  2003. highlightSteps.push('M',
  2004. (cursorX - Blockly.BlockSvg.NOTCH_WIDTH +
  2005. Blockly.BlockSvg.DISTANCE_45_OUTSIDE) + ',' +
  2006. (cursorY + row.height - Blockly.BlockSvg.DISTANCE_45_OUTSIDE));
  2007. highlightSteps.push(
  2008. Blockly.BlockSvg.INNER_BOTTOM_LEFT_CORNER_HIGHLIGHT_LTR);
  2009. highlightSteps.push('H', inputRows.rightEdge - 0.5);
  2010. }
  2011. // Create statement connection.
  2012. connectionX = connectionsXY.x + (this.RTL ? -cursorX : cursorX + 1);
  2013. connectionY = connectionsXY.y + cursorY + 1;
  2014. input.connection.moveTo(connectionX, connectionY);
  2015. if (input.connection.targetConnection) {
  2016. input.connection.tighten_();
  2017. this.width = Math.max(this.width, inputRows.statementEdge +
  2018. input.connection.targetBlock().getHeightWidth().width);
  2019. }
  2020. if (y == inputRows.length - 1 ||
  2021. inputRows[y + 1].type == Blockly.NEXT_STATEMENT) {
  2022. // If the final input is a statement stack, add a small row underneath.
  2023. // Consecutive statement stacks are also separated by a small divider.
  2024. steps.push('v', Blockly.BlockSvg.SEP_SPACE_Y);
  2025. if (this.RTL) {
  2026. highlightSteps.push('v', Blockly.BlockSvg.SEP_SPACE_Y - 1);
  2027. }
  2028. cursorY += Blockly.BlockSvg.SEP_SPACE_Y;
  2029. }
  2030. }
  2031. cursorY += row.height;
  2032. }
  2033. if (!inputRows.length) {
  2034. cursorY = Blockly.BlockSvg.MIN_BLOCK_Y;
  2035. steps.push('V', cursorY);
  2036. if (this.RTL) {
  2037. highlightSteps.push('V', cursorY - 1);
  2038. }
  2039. }
  2040. return cursorY;
  2041. };
  2042. /**
  2043. * Render the bottom edge of the block.
  2044. * @param {!Array.<string>} steps Path of block outline.
  2045. * @param {!Array.<string>} highlightSteps Path of block highlights.
  2046. * @param {!Object} connectionsXY Location of block.
  2047. * @param {number} cursorY Height of block.
  2048. * @private
  2049. */
  2050. Blockly.BlockSvg.prototype.renderDrawBottom_ =
  2051. function(steps, highlightSteps, connectionsXY, cursorY) {
  2052. this.height = cursorY + 1; // Add one for the shadow.
  2053. if (this.nextConnection) {
  2054. steps.push('H', (Blockly.BlockSvg.NOTCH_WIDTH + (this.RTL ? 0.5 : - 0.5)) +
  2055. ' ' + Blockly.BlockSvg.NOTCH_PATH_RIGHT);
  2056. // Create next block connection.
  2057. var connectionX;
  2058. if (this.RTL) {
  2059. connectionX = connectionsXY.x - Blockly.BlockSvg.NOTCH_WIDTH;
  2060. } else {
  2061. connectionX = connectionsXY.x + Blockly.BlockSvg.NOTCH_WIDTH;
  2062. }
  2063. var connectionY = connectionsXY.y + cursorY + 1;
  2064. this.nextConnection.moveTo(connectionX, connectionY);
  2065. if (this.nextConnection.targetConnection) {
  2066. this.nextConnection.tighten_();
  2067. }
  2068. this.height += 4; // Height of tab.
  2069. }
  2070. // Should the bottom-left corner be rounded or square?
  2071. if (this.squareBottomLeftCorner_) {
  2072. steps.push('H 0');
  2073. if (!this.RTL) {
  2074. highlightSteps.push('M', '0.5,' + (cursorY - 0.5));
  2075. }
  2076. } else {
  2077. steps.push('H', Blockly.BlockSvg.CORNER_RADIUS);
  2078. steps.push('a', Blockly.BlockSvg.CORNER_RADIUS + ',' +
  2079. Blockly.BlockSvg.CORNER_RADIUS + ' 0 0,1 -' +
  2080. Blockly.BlockSvg.CORNER_RADIUS + ',-' +
  2081. Blockly.BlockSvg.CORNER_RADIUS);
  2082. if (!this.RTL) {
  2083. highlightSteps.push('M', Blockly.BlockSvg.DISTANCE_45_INSIDE + ',' +
  2084. (cursorY - Blockly.BlockSvg.DISTANCE_45_INSIDE));
  2085. highlightSteps.push('A', (Blockly.BlockSvg.CORNER_RADIUS - 0.5) + ',' +
  2086. (Blockly.BlockSvg.CORNER_RADIUS - 0.5) + ' 0 0,1 ' +
  2087. '0.5,' + (cursorY - Blockly.BlockSvg.CORNER_RADIUS));
  2088. }
  2089. }
  2090. };
  2091. /**
  2092. * Render the left edge of the block.
  2093. * @param {!Array.<string>} steps Path of block outline.
  2094. * @param {!Array.<string>} highlightSteps Path of block highlights.
  2095. * @param {!Object} connectionsXY Location of block.
  2096. * @param {number} cursorY Height of block.
  2097. * @private
  2098. */
  2099. Blockly.BlockSvg.prototype.renderDrawLeft_ =
  2100. function(steps, highlightSteps, connectionsXY, cursorY) {
  2101. if (this.outputConnection) {
  2102. // Create output connection.
  2103. this.outputConnection.moveTo(connectionsXY.x, connectionsXY.y);
  2104. // This connection will be tightened when the parent renders.
  2105. steps.push('V', Blockly.BlockSvg.TAB_HEIGHT);
  2106. steps.push('c 0,-10 -' + Blockly.BlockSvg.TAB_WIDTH + ',8 -' +
  2107. Blockly.BlockSvg.TAB_WIDTH + ',-7.5 s ' + Blockly.BlockSvg.TAB_WIDTH +
  2108. ',2.5 ' + Blockly.BlockSvg.TAB_WIDTH + ',-7.5');
  2109. if (this.RTL) {
  2110. highlightSteps.push('M', (Blockly.BlockSvg.TAB_WIDTH * -0.25) + ',8.4');
  2111. highlightSteps.push('l', (Blockly.BlockSvg.TAB_WIDTH * -0.45) + ',-2.1');
  2112. } else {
  2113. highlightSteps.push('V', Blockly.BlockSvg.TAB_HEIGHT - 1.5);
  2114. highlightSteps.push('m', (Blockly.BlockSvg.TAB_WIDTH * -0.92) +
  2115. ',-0.5 q ' + (Blockly.BlockSvg.TAB_WIDTH * -0.19) +
  2116. ',-5.5 0,-11');
  2117. highlightSteps.push('m', (Blockly.BlockSvg.TAB_WIDTH * 0.92) +
  2118. ',1 V 0.5 H 1');
  2119. }
  2120. this.width += Blockly.BlockSvg.TAB_WIDTH;
  2121. } else if (!this.RTL) {
  2122. if (this.squareTopLeftCorner_) {
  2123. // Statement block in a stack.
  2124. highlightSteps.push('V', 0.5);
  2125. } else {
  2126. highlightSteps.push('V', Blockly.BlockSvg.CORNER_RADIUS);
  2127. }
  2128. }
  2129. steps.push('z');
  2130. };