bubble.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579
  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 Object representing a UI bubble.
  22. * @author fraser@google.com (Neil Fraser)
  23. */
  24. 'use strict';
  25. goog.provide('Blockly.Bubble');
  26. goog.require('Blockly.Workspace');
  27. goog.require('goog.dom');
  28. goog.require('goog.math');
  29. goog.require('goog.math.Coordinate');
  30. goog.require('goog.userAgent');
  31. /**
  32. * Class for UI bubble.
  33. * @param {!Blockly.WorkspaceSvg} workspace The workspace on which to draw the
  34. * bubble.
  35. * @param {!Element} content SVG content for the bubble.
  36. * @param {Element} shape SVG element to avoid eclipsing.
  37. * @param {!goog.math.Coodinate} anchorXY Absolute position of bubble's anchor
  38. * point.
  39. * @param {?number} bubbleWidth Width of bubble, or null if not resizable.
  40. * @param {?number} bubbleHeight Height of bubble, or null if not resizable.
  41. * @constructor
  42. */
  43. Blockly.Bubble = function(workspace, content, shape, anchorXY,
  44. bubbleWidth, bubbleHeight) {
  45. this.workspace_ = workspace;
  46. this.content_ = content;
  47. this.shape_ = shape;
  48. var angle = Blockly.Bubble.ARROW_ANGLE;
  49. if (this.workspace_.RTL) {
  50. angle = -angle;
  51. }
  52. this.arrow_radians_ = goog.math.toRadians(angle);
  53. var canvas = workspace.getBubbleCanvas();
  54. canvas.appendChild(this.createDom_(content, !!(bubbleWidth && bubbleHeight)));
  55. this.setAnchorLocation(anchorXY);
  56. if (!bubbleWidth || !bubbleHeight) {
  57. var bBox = /** @type {SVGLocatable} */ (this.content_).getBBox();
  58. bubbleWidth = bBox.width + 2 * Blockly.Bubble.BORDER_WIDTH;
  59. bubbleHeight = bBox.height + 2 * Blockly.Bubble.BORDER_WIDTH;
  60. }
  61. this.setBubbleSize(bubbleWidth, bubbleHeight);
  62. // Render the bubble.
  63. this.positionBubble_();
  64. this.renderArrow_();
  65. this.rendered_ = true;
  66. if (!workspace.options.readOnly) {
  67. Blockly.bindEvent_(this.bubbleBack_, 'mousedown', this,
  68. this.bubbleMouseDown_);
  69. if (this.resizeGroup_) {
  70. Blockly.bindEvent_(this.resizeGroup_, 'mousedown', this,
  71. this.resizeMouseDown_);
  72. }
  73. }
  74. };
  75. /**
  76. * Width of the border around the bubble.
  77. */
  78. Blockly.Bubble.BORDER_WIDTH = 6;
  79. /**
  80. * Determines the thickness of the base of the arrow in relation to the size
  81. * of the bubble. Higher numbers result in thinner arrows.
  82. */
  83. Blockly.Bubble.ARROW_THICKNESS = 10;
  84. /**
  85. * The number of degrees that the arrow bends counter-clockwise.
  86. */
  87. Blockly.Bubble.ARROW_ANGLE = 20;
  88. /**
  89. * The sharpness of the arrow's bend. Higher numbers result in smoother arrows.
  90. */
  91. Blockly.Bubble.ARROW_BEND = 4;
  92. /**
  93. * Distance between arrow point and anchor point.
  94. */
  95. Blockly.Bubble.ANCHOR_RADIUS = 8;
  96. /**
  97. * Wrapper function called when a mouseUp occurs during a drag operation.
  98. * @type {Array.<!Array>}
  99. * @private
  100. */
  101. Blockly.Bubble.onMouseUpWrapper_ = null;
  102. /**
  103. * Wrapper function called when a mouseMove occurs during a drag operation.
  104. * @type {Array.<!Array>}
  105. * @private
  106. */
  107. Blockly.Bubble.onMouseMoveWrapper_ = null;
  108. /**
  109. * Function to call on resize of bubble.
  110. * @type {Function}
  111. */
  112. Blockly.Bubble.prototype.resizeCallback_ = null;
  113. /**
  114. * Stop binding to the global mouseup and mousemove events.
  115. * @private
  116. */
  117. Blockly.Bubble.unbindDragEvents_ = function() {
  118. if (Blockly.Bubble.onMouseUpWrapper_) {
  119. Blockly.unbindEvent_(Blockly.Bubble.onMouseUpWrapper_);
  120. Blockly.Bubble.onMouseUpWrapper_ = null;
  121. }
  122. if (Blockly.Bubble.onMouseMoveWrapper_) {
  123. Blockly.unbindEvent_(Blockly.Bubble.onMouseMoveWrapper_);
  124. Blockly.Bubble.onMouseMoveWrapper_ = null;
  125. }
  126. };
  127. /**
  128. * Flag to stop incremental rendering during construction.
  129. * @private
  130. */
  131. Blockly.Bubble.prototype.rendered_ = false;
  132. /**
  133. * Absolute coordinate of anchor point.
  134. * @type {goog.math.Coordinate}
  135. * @private
  136. */
  137. Blockly.Bubble.prototype.anchorXY_ = null;
  138. /**
  139. * Relative X coordinate of bubble with respect to the anchor's centre.
  140. * In RTL mode the initial value is negated.
  141. * @private
  142. */
  143. Blockly.Bubble.prototype.relativeLeft_ = 0;
  144. /**
  145. * Relative Y coordinate of bubble with respect to the anchor's centre.
  146. * @private
  147. */
  148. Blockly.Bubble.prototype.relativeTop_ = 0;
  149. /**
  150. * Width of bubble.
  151. * @private
  152. */
  153. Blockly.Bubble.prototype.width_ = 0;
  154. /**
  155. * Height of bubble.
  156. * @private
  157. */
  158. Blockly.Bubble.prototype.height_ = 0;
  159. /**
  160. * Automatically position and reposition the bubble.
  161. * @private
  162. */
  163. Blockly.Bubble.prototype.autoLayout_ = true;
  164. /**
  165. * Create the bubble's DOM.
  166. * @param {!Element} content SVG content for the bubble.
  167. * @param {boolean} hasResize Add diagonal resize gripper if true.
  168. * @return {!Element} The bubble's SVG group.
  169. * @private
  170. */
  171. Blockly.Bubble.prototype.createDom_ = function(content, hasResize) {
  172. /* Create the bubble. Here's the markup that will be generated:
  173. <g>
  174. <g filter="url(#blocklyEmbossFilter837493)">
  175. <path d="... Z" />
  176. <rect class="blocklyDraggable" rx="8" ry="8" width="180" height="180"/>
  177. </g>
  178. <g transform="translate(165, 165)" class="blocklyResizeSE">
  179. <polygon points="0,15 15,15 15,0"/>
  180. <line class="blocklyResizeLine" x1="5" y1="14" x2="14" y2="5"/>
  181. <line class="blocklyResizeLine" x1="10" y1="14" x2="14" y2="10"/>
  182. </g>
  183. [...content goes here...]
  184. </g>
  185. */
  186. this.bubbleGroup_ = Blockly.createSvgElement('g', {}, null);
  187. var filter =
  188. {'filter': 'url(#' + this.workspace_.options.embossFilterId + ')'};
  189. if (goog.userAgent.getUserAgentString().indexOf('JavaFX') != -1) {
  190. // Multiple reports that JavaFX can't handle filters. UserAgent:
  191. // Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.44
  192. // (KHTML, like Gecko) JavaFX/8.0 Safari/537.44
  193. // https://github.com/google/blockly/issues/99
  194. filter = {};
  195. }
  196. var bubbleEmboss = Blockly.createSvgElement('g',
  197. filter, this.bubbleGroup_);
  198. this.bubbleArrow_ = Blockly.createSvgElement('path', {}, bubbleEmboss);
  199. this.bubbleBack_ = Blockly.createSvgElement('rect',
  200. {'class': 'blocklyDraggable', 'x': 0, 'y': 0,
  201. 'rx': Blockly.Bubble.BORDER_WIDTH, 'ry': Blockly.Bubble.BORDER_WIDTH},
  202. bubbleEmboss);
  203. if (hasResize) {
  204. this.resizeGroup_ = Blockly.createSvgElement('g',
  205. {'class': this.workspace_.RTL ?
  206. 'blocklyResizeSW' : 'blocklyResizeSE'},
  207. this.bubbleGroup_);
  208. var resizeSize = 2 * Blockly.Bubble.BORDER_WIDTH;
  209. Blockly.createSvgElement('polygon',
  210. {'points': '0,x x,x x,0'.replace(/x/g, resizeSize.toString())},
  211. this.resizeGroup_);
  212. Blockly.createSvgElement('line',
  213. {'class': 'blocklyResizeLine',
  214. 'x1': resizeSize / 3, 'y1': resizeSize - 1,
  215. 'x2': resizeSize - 1, 'y2': resizeSize / 3}, this.resizeGroup_);
  216. Blockly.createSvgElement('line',
  217. {'class': 'blocklyResizeLine',
  218. 'x1': resizeSize * 2 / 3, 'y1': resizeSize - 1,
  219. 'x2': resizeSize - 1, 'y2': resizeSize * 2 / 3}, this.resizeGroup_);
  220. } else {
  221. this.resizeGroup_ = null;
  222. }
  223. this.bubbleGroup_.appendChild(content);
  224. return this.bubbleGroup_;
  225. };
  226. /**
  227. * Handle a mouse-down on bubble's border.
  228. * @param {!Event} e Mouse down event.
  229. * @private
  230. */
  231. Blockly.Bubble.prototype.bubbleMouseDown_ = function(e) {
  232. this.promote_();
  233. Blockly.Bubble.unbindDragEvents_();
  234. if (Blockly.isRightButton(e)) {
  235. // No right-click.
  236. e.stopPropagation();
  237. return;
  238. } else if (Blockly.isTargetInput_(e)) {
  239. // When focused on an HTML text input widget, don't trap any events.
  240. return;
  241. }
  242. // Left-click (or middle click)
  243. Blockly.Css.setCursor(Blockly.Css.Cursor.CLOSED);
  244. this.workspace_.startDrag(e, new goog.math.Coordinate(
  245. this.workspace_.RTL ? -this.relativeLeft_ : this.relativeLeft_,
  246. this.relativeTop_));
  247. Blockly.Bubble.onMouseUpWrapper_ = Blockly.bindEvent_(document,
  248. 'mouseup', this, Blockly.Bubble.unbindDragEvents_);
  249. Blockly.Bubble.onMouseMoveWrapper_ = Blockly.bindEvent_(document,
  250. 'mousemove', this, this.bubbleMouseMove_);
  251. Blockly.hideChaff();
  252. // This event has been handled. No need to bubble up to the document.
  253. e.stopPropagation();
  254. };
  255. /**
  256. * Drag this bubble to follow the mouse.
  257. * @param {!Event} e Mouse move event.
  258. * @private
  259. */
  260. Blockly.Bubble.prototype.bubbleMouseMove_ = function(e) {
  261. this.autoLayout_ = false;
  262. var newXY = this.workspace_.moveDrag(e);
  263. this.relativeLeft_ = this.workspace_.RTL ? -newXY.x : newXY.x;
  264. this.relativeTop_ = newXY.y;
  265. this.positionBubble_();
  266. this.renderArrow_();
  267. };
  268. /**
  269. * Handle a mouse-down on bubble's resize corner.
  270. * @param {!Event} e Mouse down event.
  271. * @private
  272. */
  273. Blockly.Bubble.prototype.resizeMouseDown_ = function(e) {
  274. this.promote_();
  275. Blockly.Bubble.unbindDragEvents_();
  276. if (Blockly.isRightButton(e)) {
  277. // No right-click.
  278. e.stopPropagation();
  279. return;
  280. }
  281. // Left-click (or middle click)
  282. Blockly.Css.setCursor(Blockly.Css.Cursor.CLOSED);
  283. this.workspace_.startDrag(e, new goog.math.Coordinate(
  284. this.workspace_.RTL ? -this.width_ : this.width_, this.height_));
  285. Blockly.Bubble.onMouseUpWrapper_ = Blockly.bindEvent_(document,
  286. 'mouseup', this, Blockly.Bubble.unbindDragEvents_);
  287. Blockly.Bubble.onMouseMoveWrapper_ = Blockly.bindEvent_(document,
  288. 'mousemove', this, this.resizeMouseMove_);
  289. Blockly.hideChaff();
  290. // This event has been handled. No need to bubble up to the document.
  291. e.stopPropagation();
  292. };
  293. /**
  294. * Resize this bubble to follow the mouse.
  295. * @param {!Event} e Mouse move event.
  296. * @private
  297. */
  298. Blockly.Bubble.prototype.resizeMouseMove_ = function(e) {
  299. this.autoLayout_ = false;
  300. var newXY = this.workspace_.moveDrag(e);
  301. this.setBubbleSize(this.workspace_.RTL ? -newXY.x : newXY.x, newXY.y);
  302. if (this.workspace_.RTL) {
  303. // RTL requires the bubble to move its left edge.
  304. this.positionBubble_();
  305. }
  306. };
  307. /**
  308. * Register a function as a callback event for when the bubble is resized.
  309. * @param {!Function} callback The function to call on resize.
  310. */
  311. Blockly.Bubble.prototype.registerResizeEvent = function(callback) {
  312. this.resizeCallback_ = callback;
  313. };
  314. /**
  315. * Move this bubble to the top of the stack.
  316. * @private
  317. */
  318. Blockly.Bubble.prototype.promote_ = function() {
  319. var svgGroup = this.bubbleGroup_.parentNode;
  320. svgGroup.appendChild(this.bubbleGroup_);
  321. };
  322. /**
  323. * Notification that the anchor has moved.
  324. * Update the arrow and bubble accordingly.
  325. * @param {!goog.math.Coordinate} xy Absolute location.
  326. */
  327. Blockly.Bubble.prototype.setAnchorLocation = function(xy) {
  328. this.anchorXY_ = xy;
  329. if (this.rendered_) {
  330. this.positionBubble_();
  331. }
  332. };
  333. /**
  334. * Position the bubble so that it does not fall off-screen.
  335. * @private
  336. */
  337. Blockly.Bubble.prototype.layoutBubble_ = function() {
  338. // Compute the preferred bubble location.
  339. var relativeLeft = -this.width_ / 4;
  340. var relativeTop = -this.height_ - Blockly.BlockSvg.MIN_BLOCK_Y;
  341. // Prevent the bubble from being off-screen.
  342. var metrics = this.workspace_.getMetrics();
  343. metrics.viewWidth /= this.workspace_.scale;
  344. metrics.viewLeft /= this.workspace_.scale;
  345. var anchorX = this.anchorXY_.x;
  346. if (this.workspace_.RTL) {
  347. if (anchorX - metrics.viewLeft - relativeLeft - this.width_ <
  348. Blockly.Scrollbar.scrollbarThickness) {
  349. // Slide the bubble right until it is onscreen.
  350. relativeLeft = anchorX - metrics.viewLeft - this.width_ -
  351. Blockly.Scrollbar.scrollbarThickness;
  352. } else if (anchorX - metrics.viewLeft - relativeLeft >
  353. metrics.viewWidth) {
  354. // Slide the bubble left until it is onscreen.
  355. relativeLeft = anchorX - metrics.viewLeft - metrics.viewWidth;
  356. }
  357. } else {
  358. if (anchorX + relativeLeft < metrics.viewLeft) {
  359. // Slide the bubble right until it is onscreen.
  360. relativeLeft = metrics.viewLeft - anchorX;
  361. } else if (metrics.viewLeft + metrics.viewWidth <
  362. anchorX + relativeLeft + this.width_ +
  363. Blockly.BlockSvg.SEP_SPACE_X +
  364. Blockly.Scrollbar.scrollbarThickness) {
  365. // Slide the bubble left until it is onscreen.
  366. relativeLeft = metrics.viewLeft + metrics.viewWidth - anchorX -
  367. this.width_ - Blockly.Scrollbar.scrollbarThickness;
  368. }
  369. }
  370. if (this.anchorXY_.y + relativeTop < metrics.viewTop) {
  371. // Slide the bubble below the block.
  372. var bBox = /** @type {SVGLocatable} */ (this.shape_).getBBox();
  373. relativeTop = bBox.height;
  374. }
  375. this.relativeLeft_ = relativeLeft;
  376. this.relativeTop_ = relativeTop;
  377. };
  378. /**
  379. * Move the bubble to a location relative to the anchor's centre.
  380. * @private
  381. */
  382. Blockly.Bubble.prototype.positionBubble_ = function() {
  383. var left = this.anchorXY_.x;
  384. if (this.workspace_.RTL) {
  385. left -= this.relativeLeft_ + this.width_;
  386. } else {
  387. left += this.relativeLeft_;
  388. }
  389. var top = this.relativeTop_ + this.anchorXY_.y;
  390. this.bubbleGroup_.setAttribute('transform',
  391. 'translate(' + left + ',' + top + ')');
  392. };
  393. /**
  394. * Get the dimensions of this bubble.
  395. * @return {!Object} Object with width and height properties.
  396. */
  397. Blockly.Bubble.prototype.getBubbleSize = function() {
  398. return {width: this.width_, height: this.height_};
  399. };
  400. /**
  401. * Size this bubble.
  402. * @param {number} width Width of the bubble.
  403. * @param {number} height Height of the bubble.
  404. */
  405. Blockly.Bubble.prototype.setBubbleSize = function(width, height) {
  406. var doubleBorderWidth = 2 * Blockly.Bubble.BORDER_WIDTH;
  407. // Minimum size of a bubble.
  408. width = Math.max(width, doubleBorderWidth + 45);
  409. height = Math.max(height, doubleBorderWidth + 20);
  410. this.width_ = width;
  411. this.height_ = height;
  412. this.bubbleBack_.setAttribute('width', width);
  413. this.bubbleBack_.setAttribute('height', height);
  414. if (this.resizeGroup_) {
  415. if (this.workspace_.RTL) {
  416. // Mirror the resize group.
  417. var resizeSize = 2 * Blockly.Bubble.BORDER_WIDTH;
  418. this.resizeGroup_.setAttribute('transform', 'translate(' +
  419. resizeSize + ',' + (height - doubleBorderWidth) + ') scale(-1 1)');
  420. } else {
  421. this.resizeGroup_.setAttribute('transform', 'translate(' +
  422. (width - doubleBorderWidth) + ',' +
  423. (height - doubleBorderWidth) + ')');
  424. }
  425. }
  426. if (this.rendered_) {
  427. if (this.autoLayout_) {
  428. this.layoutBubble_();
  429. }
  430. this.positionBubble_();
  431. this.renderArrow_();
  432. }
  433. // Allow the contents to resize.
  434. if (this.resizeCallback_) {
  435. this.resizeCallback_();
  436. }
  437. };
  438. /**
  439. * Draw the arrow between the bubble and the origin.
  440. * @private
  441. */
  442. Blockly.Bubble.prototype.renderArrow_ = function() {
  443. var steps = [];
  444. // Find the relative coordinates of the center of the bubble.
  445. var relBubbleX = this.width_ / 2;
  446. var relBubbleY = this.height_ / 2;
  447. // Find the relative coordinates of the center of the anchor.
  448. var relAnchorX = -this.relativeLeft_;
  449. var relAnchorY = -this.relativeTop_;
  450. if (relBubbleX == relAnchorX && relBubbleY == relAnchorY) {
  451. // Null case. Bubble is directly on top of the anchor.
  452. // Short circuit this rather than wade through divide by zeros.
  453. steps.push('M ' + relBubbleX + ',' + relBubbleY);
  454. } else {
  455. // Compute the angle of the arrow's line.
  456. var rise = relAnchorY - relBubbleY;
  457. var run = relAnchorX - relBubbleX;
  458. if (this.workspace_.RTL) {
  459. run *= -1;
  460. }
  461. var hypotenuse = Math.sqrt(rise * rise + run * run);
  462. var angle = Math.acos(run / hypotenuse);
  463. if (rise < 0) {
  464. angle = 2 * Math.PI - angle;
  465. }
  466. // Compute a line perpendicular to the arrow.
  467. var rightAngle = angle + Math.PI / 2;
  468. if (rightAngle > Math.PI * 2) {
  469. rightAngle -= Math.PI * 2;
  470. }
  471. var rightRise = Math.sin(rightAngle);
  472. var rightRun = Math.cos(rightAngle);
  473. // Calculate the thickness of the base of the arrow.
  474. var bubbleSize = this.getBubbleSize();
  475. var thickness = (bubbleSize.width + bubbleSize.height) /
  476. Blockly.Bubble.ARROW_THICKNESS;
  477. thickness = Math.min(thickness, bubbleSize.width, bubbleSize.height) / 2;
  478. // Back the tip of the arrow off of the anchor.
  479. var backoffRatio = 1 - Blockly.Bubble.ANCHOR_RADIUS / hypotenuse;
  480. relAnchorX = relBubbleX + backoffRatio * run;
  481. relAnchorY = relBubbleY + backoffRatio * rise;
  482. // Coordinates for the base of the arrow.
  483. var baseX1 = relBubbleX + thickness * rightRun;
  484. var baseY1 = relBubbleY + thickness * rightRise;
  485. var baseX2 = relBubbleX - thickness * rightRun;
  486. var baseY2 = relBubbleY - thickness * rightRise;
  487. // Distortion to curve the arrow.
  488. var swirlAngle = angle + this.arrow_radians_;
  489. if (swirlAngle > Math.PI * 2) {
  490. swirlAngle -= Math.PI * 2;
  491. }
  492. var swirlRise = Math.sin(swirlAngle) *
  493. hypotenuse / Blockly.Bubble.ARROW_BEND;
  494. var swirlRun = Math.cos(swirlAngle) *
  495. hypotenuse / Blockly.Bubble.ARROW_BEND;
  496. steps.push('M' + baseX1 + ',' + baseY1);
  497. steps.push('C' + (baseX1 + swirlRun) + ',' + (baseY1 + swirlRise) +
  498. ' ' + relAnchorX + ',' + relAnchorY +
  499. ' ' + relAnchorX + ',' + relAnchorY);
  500. steps.push('C' + relAnchorX + ',' + relAnchorY +
  501. ' ' + (baseX2 + swirlRun) + ',' + (baseY2 + swirlRise) +
  502. ' ' + baseX2 + ',' + baseY2);
  503. }
  504. steps.push('z');
  505. this.bubbleArrow_.setAttribute('d', steps.join(' '));
  506. };
  507. /**
  508. * Change the colour of a bubble.
  509. * @param {string} hexColour Hex code of colour.
  510. */
  511. Blockly.Bubble.prototype.setColour = function(hexColour) {
  512. this.bubbleBack_.setAttribute('fill', hexColour);
  513. this.bubbleArrow_.setAttribute('fill', hexColour);
  514. };
  515. /**
  516. * Dispose of this bubble.
  517. */
  518. Blockly.Bubble.prototype.dispose = function() {
  519. Blockly.Bubble.unbindDragEvents_();
  520. // Dispose of and unlink the bubble.
  521. goog.dom.removeNode(this.bubbleGroup_);
  522. this.bubbleGroup_ = null;
  523. this.bubbleArrow_ = null;
  524. this.bubbleBack_ = null;
  525. this.resizeGroup_ = null;
  526. this.workspace_ = null;
  527. this.content_ = null;
  528. this.shape_ = null;
  529. };