not really known
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2040 lines
72 KiB

  1. // Copyright (c) 2014-17 Walter Bender
  2. //
  3. // This program is free software; you can redistribute it and/or
  4. // modify it under the terms of the The GNU Affero General Public
  5. // License as published by the Free Software Foundation; either
  6. // version 3 of the License, or (at your option) any later version.
  7. //
  8. // You should have received a copy of the GNU Affero General Public
  9. // License along with this library; if not, write to the Free Software
  10. // Foundation, 51 Franklin Street, Suite 500 Boston, MA 02110-1335 USA
  11. // All things related to palettes
  12. requirejs(['activity/utils']);
  13. const PROTOBLOCKSCALE = 1.0;
  14. const PALETTELEFTMARGIN = 10;
  15. function maxPaletteHeight(menuSize, scale) {
  16. // Palettes don't start at the top of the screen and the last
  17. // block in a palette cannot start at the bottom of the screen,
  18. // hence - 2 * menuSize.
  19. var h = (windowHeight() * canvasPixelRatio()) / scale - (2 * menuSize);
  20. return h - (h % STANDARDBLOCKHEIGHT) + (STANDARDBLOCKHEIGHT / 2);
  21. };
  22. function paletteBlockButtonPush(blocks, name, arg) {
  23. var blk = blocks.makeBlock(name, arg);
  24. return blk;
  25. };
  26. // There are several components to the palette system:
  27. //
  28. // (1) A palette button (in the Palettes.buttons dictionary) is a
  29. // button that envokes a palette; The buttons have artwork associated
  30. // with them: a bitmap and a highlighted bitmap that is shown when the
  31. // mouse is over the button. (The artwork is found in artwork.js.)
  32. //
  33. // loadPaletteButtonHandler is the event handler for palette buttons.
  34. //
  35. // (2) A menu (in the Palettes.dict dictionary) is the palette
  36. // itself. It consists of a title bar (with an icon, label, and close
  37. // button), and individual containers for each protoblock on the
  38. // menu. There is a background behind each protoblock that is part of
  39. // the palette container.
  40. //
  41. // loadPaletteMenuItemHandler is the event handler for the palette menu.
  42. function Palettes () {
  43. this.canvas = null;
  44. this.blocks = null;
  45. this.refreshCanvas = null;
  46. this.stage = null;
  47. this.cellSize = null;
  48. this.scrollDiff = 0;
  49. this.originalSize = 55; // this is the original svg size
  50. this.trashcan = null;
  51. this.initial_x = 55;
  52. this.initial_y = 55;
  53. this.firstTime = true;
  54. this.background = null;
  55. this.upIndicator = null;
  56. this.upIndicatorStatus = false;
  57. this.downIndicator = null;
  58. this.downIndicatorStatus = true;
  59. this.circles = {};
  60. this.palette_text = new createjs.Text('', '20px Arial', '#ff7700');
  61. this.mouseOver = false;
  62. this.activePalette = null;
  63. this.visible = true;
  64. this.scale = 1.0;
  65. this.mobile = false;
  66. this.current = DEFAULTPALETTE;
  67. this.x = null;
  68. this.y = null;
  69. this.container = null;
  70. if (sugarizerCompatibility.isInsideSugarizer()) {
  71. storage = sugarizerCompatibility.data;
  72. } else {
  73. storage = localStorage;
  74. }
  75. // The collection of palettes.
  76. this.dict = {};
  77. this.buttons = {}; // The toolbar button for each palette.
  78. this.init = function () {
  79. this.halfCellSize = Math.floor(this.cellSize / 2);
  80. this.x = 0;
  81. this.y = this.cellSize;
  82. this.container = new createjs.Container();
  83. this.container.snapToPixelEnabled = true;
  84. this.stage.addChild(this.container);
  85. };
  86. this.setCanvas = function (canvas) {
  87. this.canvas = canvas;
  88. return this;
  89. };
  90. this.setStage = function (stage) {
  91. this.stage = stage;
  92. return this;
  93. };
  94. this.setRefreshCanvas = function (refreshCanvas) {
  95. this.refreshCanvas = refreshCanvas;
  96. return this;
  97. };
  98. this.setTrashcan = function (trashcan) {
  99. this.trashcan = trashcan;
  100. return this;
  101. };
  102. this.setSize = function (size) {
  103. this.cellSize = size;
  104. return this;
  105. };
  106. this.setMobile = function (mobile) {
  107. this.mobile = mobile;
  108. if (mobile) {
  109. this._hideMenus();
  110. }
  111. return this;
  112. };
  113. this.setScale = function (scale) {
  114. this.scale = scale;
  115. this._updateButtonMasks();
  116. for (var i in this.dict) {
  117. this.dict[i]._resizeEvent();
  118. }
  119. if (this.downIndicator != null) {
  120. this.downIndicator.y = (windowHeight() / scale) - 27;
  121. }
  122. return this;
  123. };
  124. // We need access to the macro dictionary because we load them.
  125. this.setMacroDictionary = function (obj) {
  126. this.macroDict = obj;
  127. return this;
  128. };
  129. this.menuScrollEvent = function (direction, scrollSpeed) {
  130. var keys = Object.keys(this.buttons);
  131. var diff = direction * scrollSpeed;
  132. if (this.buttons[keys[0]].y + diff > this.cellSize && direction > 0) {
  133. this.upIndicator.visible = false;
  134. this.upIndicatorStatus = this.upIndicator.visible;
  135. this.refreshCanvas();
  136. return;
  137. } else {
  138. this.upIndicatorStatus = this.upIndicator.visible;
  139. this.upIndicator.visible = true;
  140. }
  141. if (this.buttons[last(keys)].y + diff < windowHeight() / this.scale - this.cellSize && direction < 0) {
  142. this.downIndicator.visible = false;
  143. this.downIndicatorStatus = this.downIndicator.visible;
  144. this.refreshCanvas();
  145. return;
  146. } else {
  147. this.downIndicator.visible = true;
  148. this.downIndicatorStatus = this.downIndicator.visible;
  149. }
  150. this.scrollDiff += diff;
  151. for (var name in this.buttons) {
  152. this.buttons[name].y += diff;
  153. this.buttons[name].visible = true;
  154. }
  155. this._updateButtonMasks();
  156. this.refreshCanvas();
  157. };
  158. this._updateButtonMasks = function () {
  159. for (var name in this.buttons) {
  160. var s = new createjs.Shape();
  161. s.graphics.r(0, 0, this.cellSize, windowHeight() / this.scale);
  162. s.x = 0;
  163. s.y = this.cellSize / 2;
  164. this.buttons[name].mask = s;
  165. }
  166. };
  167. this.hidePaletteIconCircles = function () {
  168. if (!sugarizerCompatibility.isInsideSugarizer()) {
  169. hidePaletteNameDisplay(palette_text, this.stage);
  170. }
  171. hideButtonHighlight(this.circles, this.stage);
  172. };
  173. this.makePalettes = function (hide) {
  174. if (this.firstTime) {
  175. var shape = new createjs.Shape();
  176. shape.graphics.f('#a2c5d8').r(0, 0, 55, windowHeight()).ef();
  177. shape.width = 55;
  178. shape.height = windowHeight();
  179. this.stage.addChild(shape);
  180. this.background = shape;
  181. }
  182. function __processUpIcon(palettes, name, bitmap, args) {
  183. bitmap.scaleX = bitmap.scaleY = bitmap.scale = 0.4;
  184. palettes.stage.addChild(bitmap);
  185. bitmap.x = 55;
  186. bitmap.y = 55;
  187. bitmap.visible = false;
  188. palettes.upIndicator = bitmap;
  189. palettes.upIndicator.on('click', function (event) {
  190. palettes.menuScrollEvent(1, 40);
  191. palettes.hidePaletteIconCircles();
  192. });
  193. };
  194. function __processDownIcon(palettes, name, bitmap, args) {
  195. bitmap.scaleX = bitmap.scaleY = bitmap.scale = 0.4;
  196. palettes.stage.addChild(bitmap);
  197. bitmap.x = 55;
  198. bitmap.y = (windowHeight() / palettes.scale) - 27;
  199. bitmap.visible = true;
  200. palettes.downIndicator = bitmap;
  201. palettes.downIndicator.on('click', function (event) {
  202. palettes.menuScrollEvent(-1, 40);
  203. palettes.hidePaletteIconCircles();
  204. });
  205. };
  206. if (this.upIndicator == null && this.firstTime) {
  207. makePaletteBitmap(this, UPICON.replace('#000000', '#FFFFFF'), 'up', __processUpIcon, null);
  208. }
  209. if (this.downbIndicator == null && this.firstTime) {
  210. makePaletteBitmap(this, DOWNICON.replace('#000000', '#FFFFFF'), 'down', __processDownIcon, null);
  211. }
  212. this.firstTime = false;
  213. // Make an icon/button for each palette
  214. var that = this;
  215. function __processButtonIcon(palettes, name, bitmap, args) {
  216. that.buttons[name].addChild(bitmap);
  217. if (that.cellSize != that.originalSize) {
  218. bitmap.scaleX = that.cellSize / that.originalSize;
  219. bitmap.scaleY = that.cellSize / that.originalSize;
  220. }
  221. var hitArea = new createjs.Shape();
  222. hitArea.graphics.beginFill('#FFF').drawEllipse(-that.halfCellSize, -that.halfCellSize, that.cellSize, that.cellSize);
  223. hitArea.x = that.halfCellSize;
  224. hitArea.y = that.halfCellSize;
  225. that.buttons[name].hitArea = hitArea;
  226. that.buttons[name].visible = false;
  227. that.dict[name].makeMenu(true);
  228. that.dict[name]._moveMenu(that.cellSize, that.cellSize);
  229. that.dict[name]._updateMenu(false);
  230. that._loadPaletteButtonHandler(name);
  231. };
  232. for (var name in this.dict) {
  233. if (name in this.buttons) {
  234. this.dict[name]._updateMenu(hide);
  235. } else {
  236. this.buttons[name] = new createjs.Container();
  237. this.buttons[name].snapToPixelEnabled = true;
  238. this.stage.addChild(this.buttons[name]);
  239. this.buttons[name].x = this.x;
  240. this.buttons[name].y = this.y + this.scrollDiff;
  241. this.y += this.cellSize;
  242. makePaletteBitmap(this, PALETTEICONS[name], name, __processButtonIcon, null);
  243. }
  244. }
  245. };
  246. this.showPalette = function (name) {
  247. if (this.mobile) {
  248. return;
  249. }
  250. for (var i in this.dict) {
  251. if (this.dict[i] === this.dict[name]) {
  252. this.dict[name]._resetLayout();
  253. this.dict[name].showMenu(true);
  254. this.dict[name]._showMenuItems(true);
  255. } else {
  256. if (this.dict[i].visible) {
  257. this.dict[i].hideMenu(true);
  258. this.dict[i]._hideMenuItems(false);
  259. }
  260. }
  261. }
  262. };
  263. this._showMenus = function () {
  264. // Show the menu buttons, but not the palettes.
  265. if (this.mobile) {
  266. return;
  267. }
  268. for (var name in this.buttons) {
  269. this.buttons[name].visible = true;
  270. }
  271. if (this.background != null) {
  272. this.background.visible = true;
  273. }
  274. // If the palette indicators were visible, restore them.
  275. if (this.upIndicatorStatus) {
  276. this.upIndicator.visible = true;
  277. }
  278. if (this.downIndicatorStatus && this.downIndicator != null) {
  279. this.downIndicator.visible = true;
  280. }
  281. this.refreshCanvas();
  282. };
  283. this._hideMenus = function () {
  284. // Hide the menu buttons and the palettes themselves.
  285. for (var name in this.buttons) {
  286. this.buttons[name].visible = false;
  287. }
  288. for (var name in this.dict) {
  289. this.dict[name].hideMenu(true);
  290. }
  291. if (this.upIndicator != null) {
  292. this.upIndicator.visible = false;
  293. this.downIndicator.visible = false;
  294. this.background.visible = false;
  295. }
  296. this.refreshCanvas();
  297. };
  298. this.getInfo = function () {
  299. for (var key in this.dict) {
  300. console.log(this.dict[key].getInfo());
  301. }
  302. };
  303. this.updatePalettes = function (showPalette) {
  304. if (showPalette != null) {
  305. this.makePalettes(false);
  306. var myPalettes = this;
  307. setTimeout(function () {
  308. myPalettes.dict[showPalette]._resetLayout();
  309. // Show the action palette after adding/deleting new nameddo blocks.
  310. myPalettes.dict[showPalette].showMenu();
  311. myPalettes.dict[showPalette]._showMenuItems();
  312. myPalettes.refreshCanvas();
  313. }, 100);
  314. } else {
  315. this.makePalettes(true);
  316. this.refreshCanvas();
  317. }
  318. if (this.mobile) {
  319. var that = this;
  320. setTimeout(function () {
  321. that.hide();
  322. for (var i in that.dict) {
  323. if (that.dict[i].visible) {
  324. that.dict[i].hideMenu(true);
  325. that.dict[i]._hideMenuItems(true);
  326. }
  327. }
  328. }, 500);
  329. }
  330. };
  331. this.hide = function () {
  332. this._hideMenus();
  333. this.visible = false;
  334. };
  335. this.show = function () {
  336. if (this.mobile) {
  337. this._hideMenus();
  338. this.visible = false;
  339. } else {
  340. this._showMenus();
  341. this.visible = true;
  342. }
  343. };
  344. this.setBlocks = function (blocks) {
  345. this.blocks = blocks;
  346. return this;
  347. };
  348. this.add = function (name) {
  349. this.dict[name] = new Palette(this, name);
  350. return this;
  351. };
  352. this.remove = function (name) {
  353. if (!(name in this.buttons)) {
  354. console.log('Palette.remove: Cannot find palette ' + name);
  355. return;
  356. }
  357. this.buttons[name].removeAllChildren();
  358. var btnKeys = Object.keys(this.dict);
  359. for (var btnKey = btnKeys.indexOf(name) + 1; btnKey < btnKeys.length; btnKey++) {
  360. this.buttons[btnKeys[btnKey]].y -= this.cellSize;
  361. }
  362. delete this.buttons[name];
  363. delete this.dict[name];
  364. this.y -= this.cellSize;
  365. this.makePalettes(true);
  366. };
  367. this.bringToTop = function () {
  368. // Move all the palettes to the top layer of the stage
  369. for (var name in this.dict) {
  370. this.stage.removeChild(this.dict[name].menuContainer);
  371. this.stage.addChild(this.dict[name].menuContainer);
  372. for (var item in this.dict[name].protoContainers) {
  373. this.stage.removeChild(this.dict[name].protoContainers[item]);
  374. this.stage.addChild(this.dict[name].protoContainers[item]);
  375. }
  376. // console.log('in bring to top');
  377. // this.dict[name]._resetLayout();
  378. }
  379. this.refreshCanvas();
  380. };
  381. this.findPalette = function (x, y) {
  382. for (var name in this.dict) {
  383. var px = this.dict[name].menuContainer.x;
  384. var py = this.dict[name].menuContainer.y;
  385. var height = Math.min(maxPaletteHeight(this.cellSize, this.scale), this.dict[name].y);
  386. if (this.dict[name].menuContainer.visible && px < x &&
  387. x < px + MENUWIDTH && py < y && y < py + height) {
  388. return this.dict[name];
  389. }
  390. }
  391. return null;
  392. };
  393. // Palette Button event handlers
  394. this._loadPaletteButtonHandler = function (name) {
  395. var palettes = this;
  396. var locked = false;
  397. var scrolling = false;
  398. var that = this;
  399. this.buttons[name].on('mousedown', function (event) {
  400. scrolling = true;
  401. var lastY = event.stageY;
  402. palettes.buttons[name].on('pressmove', function (event) {
  403. if (!scrolling) {
  404. return;
  405. }
  406. var diff = event.stageY - lastY;
  407. palettes.menuScrollEvent(diff, 10);
  408. lastY = event.stageY;
  409. });
  410. palettes.buttons[name].on('pressup', function (event) {
  411. scrolling = false;
  412. }, null, true); // once = true
  413. });
  414. // A palette button opens or closes a palette.
  415. this.buttons[name].on('mouseover', function (event) {
  416. palettes.mouseOver = true;
  417. var r = palettes.cellSize / 2;
  418. that.circles = showButtonHighlight(palettes.buttons[name].x + r, palettes.buttons[name].y + r, r, event, palettes.scale, palettes.stage);
  419. /*add tooltip for palette buttons*/
  420. if (!sugarizerCompatibility.isInsideSugarizer()) {
  421. palette_text = new createjs.Text(_(name), '20px Arial', 'black');
  422. palette_text.x = palettes.buttons[name].x + 2.2 * r;
  423. palette_text.y = palettes.buttons[name].y + 5 * r / 8;
  424. palettes.stage.addChild(palette_text);
  425. }
  426. });
  427. this.buttons[name].on('pressup', function (event) {
  428. palettes.mouseOver = false;
  429. if (!sugarizerCompatibility.isInsideSugarizer()) {
  430. hidePaletteNameDisplay(palette_text, palettes.stage);
  431. }
  432. hideButtonHighlight(that.circles, palettes.stage);
  433. });
  434. this.buttons[name].on('mouseout', function (event) {
  435. palettes.mouseOver = false;
  436. if (!sugarizerCompatibility.isInsideSugarizer()) {
  437. hidePaletteNameDisplay(palette_text, palettes.stage);
  438. }
  439. hideButtonHighlight(that.circles, palettes.stage);
  440. });
  441. this.buttons[name].on('click', function (event) {
  442. if (locked) {
  443. return;
  444. }
  445. locked = true;
  446. setTimeout(function () {
  447. locked = false;
  448. }, 500);
  449. palettes.dict[name]._moveMenu(palettes.initial_x, palettes.initial_y);
  450. palettes.showPalette(name);
  451. palettes.refreshCanvas();
  452. });
  453. };
  454. this.removeActionPrototype = function (actionName) {
  455. var blockRemoved = false;
  456. for (var blk = 0; blk < this.dict['action'].protoList.length; blk++) {
  457. var actionBlock = this.dict['action'].protoList[blk];
  458. if (['nameddo', 'namedcalc', 'nameddoArg', 'namedcalcArg'].indexOf(actionBlock.name) !== -1 && (actionBlock.defaults[0] === actionName)) {
  459. // Remove the palette protoList entry for this block.
  460. this.dict['action'].remove(actionBlock, actionName);
  461. // And remove it from the protoBlock dictionary.
  462. if (this.blocks.protoBlockDict['myDo_' + actionName]) {
  463. // console.log('DELETING PROTOBLOCKS FOR ACTION ' + actionName);
  464. delete this.blocks.protoBlockDict['myDo_' + actionName];
  465. } else if (this.blocks.protoBlockDict['myCalc_' + actionName]) {
  466. // console.log('deleting protoblocks for action ' + actionName);
  467. delete this.blocks.protoBlockDict['myCalc_' + actionName];
  468. } else if (this.blocks.protoBlockDict['myDoArg_' + actionName]) {
  469. // console.log('deleting protoblocks for action ' + actionName);
  470. delete this.blocks.protoBlockDict['myDoArg_' + actionName];
  471. } else if (this.blocks.protoBlockDict['myCalcArg_' + actionName]) {
  472. // console.log('deleting protoblocks for action ' + actionName);
  473. delete this.blocks.protoBlockDict['myCalcArg_' + actionName];
  474. }
  475. this.dict['action'].y = 0;
  476. blockRemoved = true;
  477. break;
  478. }
  479. }
  480. // Force an update if a block was removed.
  481. if (blockRemoved) {
  482. this.hide();
  483. this.updatePalettes('action');
  484. if (this.mobile) {
  485. this.hide();
  486. } else {
  487. this.show();
  488. }
  489. }
  490. };
  491. return this;
  492. };
  493. // Kind of a model, but it only keeps a list of SVGs
  494. function PaletteModel(palette, palettes, name) {
  495. this.palette = palette;
  496. this.palettes = palettes;
  497. this.name = name;
  498. this.blocks = [];
  499. this.update = function () {
  500. this.blocks = [];
  501. for (var blk in this.palette.protoList) {
  502. var block = this.palette.protoList[blk];
  503. // Don't show hidden blocks on the menus
  504. if (block.hidden) {
  505. continue;
  506. }
  507. // Create a proto block for each palette entry.
  508. var blkname = block.name;
  509. var modname = blkname;
  510. switch (blkname) {
  511. // Use the name of the action in the label
  512. case 'storein':
  513. modname = 'store in ' + block.defaults[0];
  514. var arg = block.defaults[0];
  515. break;
  516. case 'box':
  517. modname = block.defaults[0];
  518. var arg = block.defaults[0];
  519. break;
  520. case 'namedbox':
  521. if (block.defaults[0] === undefined) {
  522. modname = 'namedbox';
  523. var arg = _('box');
  524. } else {
  525. modname = block.defaults[0];
  526. var arg = block.defaults[0];
  527. }
  528. break;
  529. case 'namedarg':
  530. if (block.defaults[0] === undefined) {
  531. modname = 'namedarg';
  532. var arg = '1';
  533. } else {
  534. modname = block.defaults[0];
  535. var arg = block.defaults[0];
  536. }
  537. break;
  538. case 'nameddo':
  539. if (block.defaults[0] === undefined) {
  540. modname = 'nameddo';
  541. var arg = _('action');
  542. } else {
  543. modname = block.defaults[0];
  544. var arg = block.defaults[0];
  545. }
  546. break;
  547. case 'nameddoArg':
  548. if (block.defaults[0] === undefined) {
  549. modname = 'nameddoArg';
  550. var arg = _('action');
  551. } else {
  552. modname = block.defaults[0];
  553. var arg = block.defaults[0];
  554. }
  555. break;
  556. case 'namedcalc':
  557. if (block.defaults[0] === undefined) {
  558. modname = 'namedcalc';
  559. var arg = _('action');
  560. } else {
  561. modname = block.defaults[0];
  562. var arg = block.defaults[0];
  563. }
  564. break;
  565. case 'namedcalcArg':
  566. if (block.defaults[0] === undefined) {
  567. modname = 'namedcalcArg';
  568. var arg = _('action');
  569. } else {
  570. modname = block.defaults[0];
  571. var arg = block.defaults[0];
  572. }
  573. break;
  574. }
  575. var protoBlock = this.palettes.blocks.protoBlockDict[blkname];
  576. if (protoBlock == null) {
  577. console.log('Could not find block ' + blkname);
  578. continue;
  579. }
  580. var label = '';
  581. // console.log(protoBlock.name);
  582. switch (protoBlock.name) {
  583. case 'text':
  584. label = _('text');
  585. break;
  586. case 'solfege':
  587. label = i18nSolfege('sol');
  588. break;
  589. case 'eastindiansolfege':
  590. label = 'sargam';
  591. break;
  592. case 'notename':
  593. label = 'G';
  594. break;
  595. case 'number':
  596. label = NUMBERBLOCKDEFAULT.toString();
  597. break;
  598. case 'less':
  599. case 'greater':
  600. case 'equal':
  601. // Label should be inside _() when defined.
  602. label = protoBlock.staticLabels[0];
  603. break;
  604. case 'namedarg':
  605. label = 'arg ' + arg;
  606. break;
  607. default:
  608. if (blkname != modname) {
  609. // Override label for do, storein, box, and namedarg
  610. if (blkname === 'storein' && block.defaults[0] === _('box')) {
  611. label = _('store in');
  612. } else {
  613. label = block.defaults[0];
  614. }
  615. } else if (protoBlock.staticLabels.length > 0) {
  616. label = protoBlock.staticLabels[0];
  617. if (label === '') {
  618. if (blkname === 'loadFile') {
  619. label = _('open file')
  620. } else {
  621. label = blkname;
  622. }
  623. }
  624. } else {
  625. label = blkname;
  626. }
  627. }
  628. if (['do', 'nameddo', 'namedbox', 'namedcalc', 'doArg', 'calcArg', 'nameddoArg', 'namedcalcArg'].indexOf(protoBlock.name) != -1 && label != null && label.length > 8) {
  629. label = label.substr(0, 7) + '...';
  630. }
  631. // Don't display the label on image blocks.
  632. if (protoBlock.image) {
  633. label = '';
  634. }
  635. // Finally, the SVGs!
  636. switch (protoBlock.name) {
  637. case 'namedbox':
  638. case 'namedarg':
  639. // so the label will fit
  640. var svg = new SVG();
  641. svg.init();
  642. svg.setScale(protoBlock.scale);
  643. svg.setExpand(60, 0, 0, 0);
  644. svg.setOutie(true);
  645. var artwork = svg.basicBox();
  646. var docks = svg.docks;
  647. break;
  648. case 'nameddo':
  649. // so the label will fit
  650. var svg = new SVG();
  651. svg.init();
  652. svg.setScale(protoBlock.scale);
  653. svg.setExpand(30, 0, 0, 0);
  654. var artwork = svg.basicBlock();
  655. var docks = svg.docks;
  656. break;
  657. default:
  658. var obj = protoBlock.generator();
  659. var artwork = obj[0];
  660. var docks = obj[1];
  661. break;
  662. }
  663. if (protoBlock.disabled) {
  664. artwork = artwork
  665. .replace(/fill_color/g, DISABLEDFILLCOLOR)
  666. .replace(/stroke_color/g, DISABLEDSTROKECOLOR)
  667. .replace('block_label', label);
  668. } else {
  669. artwork = artwork
  670. .replace(/fill_color/g,
  671. PALETTEFILLCOLORS[protoBlock.palette.name])
  672. .replace(/stroke_color/g,
  673. PALETTESTROKECOLORS[protoBlock.palette.name])
  674. .replace('block_label', label);
  675. }
  676. for (var i = 0; i <= protoBlock.args; i++) {
  677. artwork = artwork.replace('arg_label_' + i, protoBlock.staticLabels[i] || '');
  678. }
  679. this.blocks.push({
  680. blk: blk,
  681. name: blkname,
  682. modname: modname,
  683. height: STANDARDBLOCKHEIGHT,
  684. label: label,
  685. artwork: artwork,
  686. artwork64: 'data:image/svg+xml;base64,' + window.btoa(unescape(encodeURIComponent(artwork))),
  687. docks: docks,
  688. image: block.image,
  689. scale: block.scale,
  690. palettename: this.palette.name
  691. });
  692. }
  693. };
  694. };
  695. function PopdownPalette(palettes) {
  696. this.palettes = palettes;
  697. this.models = {};
  698. for (var name in this.palettes.dict) {
  699. this.models[name] = new PaletteModel(this.palettes.dict[name],
  700. this.palettes, name);
  701. };
  702. this.update = function () {
  703. var html = '<div class="back"><h2>' + _('back') + '</h2></div>';
  704. for (var name in this.models) {
  705. html += '<div class="palette">';
  706. var icon = PALETTEICONS[name].replace(/#f{3,6}/gi, PALETTEFILLCOLORS[name]);
  707. //.TRANS: popout: to detach as a separate window
  708. html += format('<h2 data-name="{n}"> \
  709. {i}<span>{n}</span> \
  710. <img class="hide-button" src="header-icons/hide.svg" \
  711. alt="{' + _('hide') + '}" \
  712. title="{' + _('hide') + '}" /> \
  713. <img class="show-button" src="header-icons/show.svg" \
  714. alt="{' + _('show') + '}" \
  715. title="{' + _('show') + '}" /> \
  716. <img class="popout-button" src="header-icons/popout.svg" \
  717. alt="{' + _('popout') + '}" \
  718. title="{' + _('popout') + '}" /> \
  719. </h2>',
  720. {i: icon, n: toTitleCase(_(name))});
  721. html += '<ul>';
  722. this.models[name].update();
  723. var blocks = this.models[name].blocks;
  724. if (BUILTINPALETTES.indexOf(name) > -1)
  725. blocks.reverse();
  726. for (var blk in blocks) {
  727. html += format('<li title="{label}" \
  728. data-blk="{blk}" \
  729. data-palettename="{palettename}" \
  730. data-modname="{modname}"> \
  731. <img src="{artwork64}" alt="{label}" /> \
  732. </li>', blocks[blk]);
  733. }
  734. html += '</div>';
  735. }
  736. document.querySelector('#popdown-palette').innerHTML = html;
  737. var that = this;
  738. document.querySelector('#popdown-palette .back').addEventListener('click', function () {
  739. that.popup();
  740. });
  741. var eles = document.querySelectorAll('#popdown-palette > .palette');
  742. Array.prototype.forEach.call(eles, function (d) {
  743. d.querySelector('h2').addEventListener('click', function () {
  744. if (d.classList.contains('show')) {
  745. d.classList.remove('show');
  746. } else {
  747. d.classList.add('show');
  748. }
  749. });
  750. d.querySelector('.popout-button').addEventListener('click', function () {
  751. that.popup();
  752. that.palettes.showPalette(d.querySelector('h2').dataset.name);
  753. });
  754. });
  755. var eles = document.querySelectorAll('#popdown-palette li');
  756. Array.prototype.forEach.call(eles, function (e) {
  757. e.addEventListener('click', function (event) {
  758. that.popup();
  759. var palette = that.palettes.dict[e.dataset.palettename];
  760. var container = palette.protoContainers[e.dataset.modname];
  761. // console.log(e.dataset.blk + ' ' + e.dataset.modname);
  762. var newBlock = palette._makeBlockFromPalette(palette.protoList[e.dataset.blk], e.dataset.modname, function (newBlock) {
  763. // Move the drag group under the cursor.
  764. that.palettes.blocks.findDragGroup(newBlock);
  765. for (var i in that.palettes.blocks.dragGroup) {
  766. that.palettes.blocks.moveBlockRelative(that.palettes.blocks.dragGroup[i], Math.round(event.clientX / that.palettes.scale) - that.palettes.blocks.stage.x, Math.round(event.clientY / that.palettes.scale) - that.palettes.blocks.stage.y);
  767. }
  768. // Dock with other blocks if needed
  769. that.palettes.blocks.blockMoved(newBlock);
  770. });
  771. });
  772. });
  773. };
  774. this.popdown = function () {
  775. this.update();
  776. document.querySelector('#popdown-palette').classList.add('show');
  777. };
  778. this.popup = function () {
  779. document.querySelector('#popdown-palette').classList.remove('show');
  780. };
  781. };
  782. // Define objects for individual palettes.
  783. function Palette(palettes, name) {
  784. this.palettes = palettes;
  785. this.name = name;
  786. this.model = new PaletteModel(this, palettes, name);
  787. this.visible = false;
  788. this.menuContainer = null;
  789. this.protoList = [];
  790. this.protoContainers = {};
  791. this.background = null;
  792. this.scrollDiff = 0
  793. this.y = 0;
  794. this.size = 0;
  795. this.columns = 0;
  796. this.draggingProtoBlock = false;
  797. this.mouseHandled = false;
  798. this.upButton = null;
  799. this.downButton = null;
  800. this.fadedUpButton = null;
  801. this.fadedDownButton = null;
  802. this.count = 0;
  803. this.makeMenu = function (createHeader) {
  804. var palette = this;
  805. function __processButtonIcon(palette, name, bitmap, args) {
  806. bitmap.scaleX = bitmap.scaleY = bitmap.scale = 0.8;
  807. palette.menuContainer.addChild(bitmap);
  808. palette.palettes.container.addChild(palette.menuContainer);
  809. };
  810. function __processCloseIcon(palette, name, bitmap, args) {
  811. bitmap.scaleX = bitmap.scaleY = bitmap.scale = 0.7;
  812. palette.menuContainer.addChild(bitmap);
  813. bitmap.x = paletteWidth - STANDARDBLOCKHEIGHT;
  814. bitmap.y = 0;
  815. var hitArea = new createjs.Shape();
  816. hitArea.graphics.beginFill('#FFF').drawEllipse(-paletteWidth / 2, -STANDARDBLOCKHEIGHT / 2, paletteWidth, STANDARDBLOCKHEIGHT);
  817. hitArea.x = paletteWidth / 2;
  818. hitArea.y = STANDARDBLOCKHEIGHT / 2;
  819. palette.menuContainer.hitArea = hitArea;
  820. palette.menuContainer.visible = false;
  821. if (!palette.mouseHandled) {
  822. palette._loadPaletteMenuHandler();
  823. palette.mouseHandled = true;
  824. }
  825. };
  826. function __processUpIcon(palette, name, bitmap, args) {
  827. bitmap.scaleX = bitmap.scaleY = bitmap.scale = 0.7;
  828. palette.palettes.stage.addChild(bitmap);
  829. bitmap.x = palette.menuContainer.x + paletteWidth;
  830. bitmap.y = palette.menuContainer.y + STANDARDBLOCKHEIGHT;
  831. __calculateHitArea(bitmap);
  832. var hitArea = new createjs.Shape();
  833. bitmap.visible = false;
  834. palette.upButton = bitmap;
  835. palette.upButton.on('click', function (event) {
  836. palette.scrollEvent(STANDARDBLOCKHEIGHT, 10);
  837. });
  838. };
  839. function __processDownIcon(palette, name, bitmap, args) {
  840. bitmap.scaleX = bitmap.scaleY = bitmap.scale = 0.7;
  841. palette.palettes.stage.addChild(bitmap);
  842. bitmap.x = palette.menuContainer.x + paletteWidth;
  843. bitmap.y = palette._getDownButtonY() - STANDARDBLOCKHEIGHT;
  844. __calculateHitArea(bitmap);
  845. palette.downButton = bitmap;
  846. palette.downButton.on('click', function (event) {
  847. palette.scrollEvent(-STANDARDBLOCKHEIGHT, 10);
  848. });
  849. };
  850. function __makeFadedDownIcon(palette, name, bitmap, args) {
  851. bitmap.scaleX = bitmap.scaleY = bitmap.scale = 0.7;
  852. palette.palettes.stage.addChild(bitmap);
  853. bitmap.x = palette.menuContainer.x + paletteWidth;
  854. bitmap.y = palette._getDownButtonY();
  855. __calculateHitArea(bitmap);
  856. palette.fadedDownButton = bitmap;
  857. };
  858. function __makeFadedUpIcon(palette, name, bitmap, args) {
  859. bitmap.scaleX = bitmap.scaleY = bitmap.scale = 0.7;
  860. palette.palettes.stage.addChild(bitmap);
  861. bitmap.x = palette.menuContainer.x + paletteWidth;
  862. bitmap.y = palette.menuContainer.y + STANDARDBLOCKHEIGHT;
  863. __calculateHitArea(bitmap);
  864. palette.fadedUpButton = bitmap;
  865. };
  866. function __calculateHitArea(bitmap) {
  867. var hitArea = new createjs.Shape();
  868. hitArea.graphics.beginFill('#FFF').drawRect(0, 0, STANDARDBLOCKHEIGHT, STANDARDBLOCKHEIGHT);
  869. hitArea.x = 0;
  870. hitArea.y = 0;
  871. bitmap.hitArea = hitArea;
  872. bitmap.visible = false;
  873. };
  874. function __processHeader(palette, name, bitmap, args) {
  875. palette.menuContainer.addChild(bitmap);
  876. makePaletteBitmap(palette, DOWNICON, name, __processDownIcon, null);
  877. makePaletteBitmap(palette, FADEDDOWNICON, name, __makeFadedDownIcon, null);
  878. makePaletteBitmap(palette, FADEDUPICON, name, __makeFadedUpIcon, null);
  879. makePaletteBitmap(palette, UPICON, name, __processUpIcon, null);
  880. makePaletteBitmap(palette, CLOSEICON, name, __processCloseIcon, null);
  881. makePaletteBitmap(palette, PALETTEICONS[name], name, __processButtonIcon, null);
  882. };
  883. if (this.menuContainer == null) {
  884. this.menuContainer = new createjs.Container();
  885. this.menuContainer.snapToPixelEnabled = true;
  886. }
  887. if (!createHeader) {
  888. return;
  889. }
  890. var paletteWidth = MENUWIDTH + this._getOverflowWidth();
  891. this.menuContainer.removeAllChildren();
  892. // Create the menu button
  893. makePaletteBitmap(this, PALETTEHEADER.replace('fill_color', '#282828').replace('palette_label', toTitleCase(_(this.name))).replace(/header_width/g, paletteWidth), this.name, __processHeader, null);
  894. };
  895. this._getDownButtonY = function () {
  896. var h = maxPaletteHeight(this.palettes.cellSize, this.palettes.scale);
  897. return h + STANDARDBLOCKHEIGHT / 2;
  898. };
  899. this._resizeEvent = function () {
  900. this.hide();
  901. this._updateBackground();
  902. this._updateBlockMasks();
  903. if (this.downButton !== null) {
  904. this.downButton.y = this._getDownButtonY();
  905. this.fadedDownButton.y = this.downButton.y;
  906. }
  907. };
  908. this._updateBlockMasks = function () {
  909. var h = Math.min(maxPaletteHeight(this.palettes.cellSize, this.palettes.scale), this.y);
  910. var w = MENUWIDTH + this._getOverflowWidth();
  911. for (var i in this.protoContainers) {
  912. var s = new createjs.Shape();
  913. s.graphics.r(0, 0, w, h);
  914. s.x = this.background.x;
  915. s.y = this.background.y;
  916. this.protoContainers[i].mask = s;
  917. }
  918. };
  919. this._getOverflowWidth = function() {
  920. var maxWidth = 0;
  921. for(var i in this.protoList) {
  922. maxWidth = Math.max(maxWidth, this.protoList[i].textWidth);
  923. }
  924. return (maxWidth > 100 ? maxWidth - 30 : 0);
  925. }
  926. this._updateBackground = function () {
  927. if (this.menuContainer == null) {
  928. return;
  929. }
  930. if (this.background !== null) {
  931. this.background.removeAllChildren();
  932. } else {
  933. this.background = new createjs.Container();
  934. this.background.snapToPixelEnabled = true;
  935. this.background.visible = false;
  936. this.palettes.stage.addChild(this.background);
  937. this._setupBackgroundEvents();
  938. }
  939. // Since we don't always add items at the end, the dependency
  940. // on this.y is unrelable. Easy workaround is just to always
  941. // extend the palette to the bottom.
  942. // var h = Math.min(maxPaletteHeight(this.palettes.cellSize, this.palettes.scale), this.y);
  943. var h = maxPaletteHeight(this.palettes.cellSize, this.palettes.scale);
  944. var shape = new createjs.Shape();
  945. shape.graphics.f('#949494').r(0, 0, MENUWIDTH + this._getOverflowWidth(), h).ef();
  946. shape.width = MENUWIDTH + this._getOverflowWidth();
  947. shape.height = h;
  948. this.background.addChild(shape);
  949. this.background.x = this.menuContainer.x;
  950. this.background.y = this.menuContainer.y + STANDARDBLOCKHEIGHT;
  951. };
  952. this._resetLayout = function () {
  953. // Account for menu toolbar
  954. if (this.menuContainer == null) {
  955. console.log('menuContainer is null');
  956. return;
  957. }
  958. for (var i in this.protoContainers) {
  959. this.protoContainers[i].y -= this.scrollDiff;
  960. }
  961. this.y = this.menuContainer.y + STANDARDBLOCKHEIGHT;
  962. var items = [];
  963. // Reverse order
  964. for (var i in this.protoContainers) {
  965. items.push(this.protoContainers[i]);
  966. }
  967. var n = items.length;
  968. for (var j = 0; j < n; j++) {
  969. var i = items.pop();
  970. i.x = this.menuContainer.x;
  971. i.y = this.y;
  972. var bounds = i.getBounds();
  973. if (bounds != null) {
  974. // Pack them in a bit tighter
  975. this.y += bounds.height - (STANDARDBLOCKHEIGHT * 0.1);
  976. } else {
  977. // If artwork isn't ready, assume it is of standard
  978. // size, e.g., and action block.
  979. this.y += STANDARDBLOCKHEIGHT * 0.9;
  980. }
  981. }
  982. for (var i in this.protoContainers) {
  983. this.protoContainers[i].y += this.scrollDiff;
  984. }
  985. };
  986. this._updateMenu = function (hide) {
  987. var palette = this;
  988. function __calculateBounds(palette, blk, modname, protoListBlk) {
  989. var bounds = palette.protoContainers[modname].getBounds();
  990. palette.protoContainers[modname].cache(bounds.x, bounds.y, Math.ceil(bounds.width), Math.ceil(bounds.height));
  991. var hitArea = new createjs.Shape();
  992. // Trim the hitArea height slightly to make it easier to
  993. // select single-height blocks below double-height blocks.
  994. hitArea.graphics.beginFill('#FFF').drawRect(0, 0, Math.ceil(bounds.width), Math.ceil(bounds.height * 0.75));
  995. palette.protoContainers[modname].hitArea = hitArea;
  996. palette._loadPaletteMenuItemHandler(protoListBlk, modname);
  997. palette.palettes.refreshCanvas();
  998. };
  999. function __processBitmap(palette, modname, bitmap, args) {
  1000. var b = args[0];
  1001. var blk = args[1];
  1002. var protoListBlk = args[2];
  1003. if (palette.protoContainers[modname] == undefined) {
  1004. console.log('no protoContainer for ' + modname);
  1005. return;
  1006. }
  1007. palette.protoContainers[modname].addChild(bitmap);
  1008. bitmap.x = PALETTELEFTMARGIN;
  1009. bitmap.y = 0;
  1010. bitmap.scaleX = PROTOBLOCKSCALE;
  1011. bitmap.scaleY = PROTOBLOCKSCALE;
  1012. bitmap.scale = PROTOBLOCKSCALE;
  1013. if (b.image) {
  1014. var image = new Image();
  1015. image.onload = function () {
  1016. var bitmap = new createjs.Bitmap(image);
  1017. if (image.width > image.height) {
  1018. bitmap.scaleX = bitmap.scaleY = bitmap.scale = MEDIASAFEAREA[2] / image.width * (b.scale / 2);
  1019. } else {
  1020. bitmap.scaleX = bitmap.scaleY = bitmap.scale = MEDIASAFEAREA[3] / image.height * (b.scale / 2);
  1021. }
  1022. palette.protoContainers[modname].addChild(bitmap);
  1023. bitmap.x = MEDIASAFEAREA[0] * (b.scale / 2);
  1024. bitmap.y = MEDIASAFEAREA[1] * (b.scale / 2);
  1025. __calculateBounds(palette, blk, modname, protoListBlk);
  1026. };
  1027. image.src = b.image;
  1028. } else {
  1029. __calculateBounds(palette, blk, modname, protoListBlk);
  1030. }
  1031. };
  1032. function __processFiller(palette, modname, bitmap, args) {
  1033. var b = args[0];
  1034. makePaletteBitmap(palette, b.artwork, b.modname, __processBitmap, args);
  1035. };
  1036. if (this.menuContainer == null) {
  1037. this.makeMenu(true);
  1038. } else {
  1039. // Hide the menu while we update.
  1040. if (hide) {
  1041. this.hide();
  1042. } else if (this.palettes.mobile) {
  1043. this.hide();
  1044. }
  1045. }
  1046. this.y = 0;
  1047. this.model.update();
  1048. var blocks = this.model.blocks;
  1049. if (BUILTINPALETTES.indexOf(name) == -1)
  1050. blocks.reverse();
  1051. for (var blk in blocks) {
  1052. var b = blocks[blk];
  1053. if (!this.protoContainers[b.modname]) {
  1054. // create graphics for the palette entry for this block
  1055. this.protoContainers[b.modname] = new createjs.Container();
  1056. this.protoContainers[b.modname].snapToPixelEnabled = true;
  1057. this.protoContainers[b.modname].x = this.menuContainer.x;
  1058. this.protoContainers[b.modname].y = this.menuContainer.y + this.y + this.scrollDiff + STANDARDBLOCKHEIGHT;
  1059. this.palettes.stage.addChild(this.protoContainers[b.modname]);
  1060. this.protoContainers[b.modname].visible = false;
  1061. this.size += Math.ceil(b.height * PROTOBLOCKSCALE);
  1062. this.y += Math.ceil(b.height * PROTOBLOCKSCALE);
  1063. this._updateBackground();
  1064. // Since the protoList might change while this block
  1065. // is being created, we cannot rely on blk to be the
  1066. // proper index, so pass the entry itself as an
  1067. // argument.
  1068. makePaletteBitmap(this, PALETTEFILLER.replace(/filler_height/g, b.height.toString()), b.modname, __processFiller, [b, blk, this.protoList[blk]]);
  1069. } else {
  1070. this.protoContainers[b.modname].x = this.menuContainer.x;
  1071. this.protoContainers[b.modname].y = this.menuContainer.y + this.y + this.scrollDiff + STANDARDBLOCKHEIGHT;
  1072. this.y += Math.ceil(b.height * PROTOBLOCKSCALE);
  1073. }
  1074. }
  1075. this.makeMenu(false);
  1076. if (this.palettes.mobile) {
  1077. this.hide();
  1078. }
  1079. };
  1080. this._moveMenu = function (x, y) {
  1081. // :sigh: race condition on iOS 7.1.2
  1082. if (this.menuContainer == null) return;
  1083. var dx = x - this.menuContainer.x;
  1084. var dy = y - this.menuContainer.y;
  1085. this.menuContainer.x = x;
  1086. this.menuContainer.y = y;
  1087. this._moveMenuItemsRelative(dx, dy);
  1088. };
  1089. this._moveMenuRelative = function (dx, dy) {
  1090. this.menuContainer.x += dx;
  1091. this.menuContainer.y += dy;
  1092. this._moveMenuItemsRelative(dx, dy);
  1093. };
  1094. this.hide = function () {
  1095. this.hideMenu();
  1096. };
  1097. this.show = function () {
  1098. if (this.palettes.mobile) {
  1099. this.hideMenu();
  1100. } else {
  1101. this.showMenu();
  1102. }
  1103. for (var i in this.protoContainers) {
  1104. this.protoContainers[i].visible = true;
  1105. }
  1106. this._updateBlockMasks();
  1107. if (this.background !== null) {
  1108. this.background.visible = true;
  1109. }
  1110. };
  1111. this.hideMenu = function () {
  1112. if (this.menuContainer != null) {
  1113. this.menuContainer.visible = false;
  1114. this._hideMenuItems(true);
  1115. }
  1116. this._moveMenu(this.palettes.cellSize, this.palettes.cellSize);
  1117. };
  1118. this.showMenu = function () {
  1119. if (this.palettes.mobile) {
  1120. this.menuContainer.visible = false;
  1121. } else {
  1122. this.menuContainer.visible = true;
  1123. }
  1124. };
  1125. this._hideMenuItems = function (init) {
  1126. for (var i in this.protoContainers) {
  1127. this.protoContainers[i].visible = false;
  1128. }
  1129. if (this.background !== null) {
  1130. this.background.visible = false;
  1131. }
  1132. if (this.fadedDownButton != null) {
  1133. this.upButton.visible = false;
  1134. this.downButton.visible = false;
  1135. this.fadedUpButton.visible = false;
  1136. this.fadedDownButton.visible = false;
  1137. }
  1138. this.visible = false;
  1139. };
  1140. this._showMenuItems = function (init) {
  1141. if (this.scrollDiff === 0) {
  1142. this.count = 0;
  1143. }
  1144. for (var i in this.protoContainers) {
  1145. this.protoContainers[i].visible = true;
  1146. }
  1147. this._updateBlockMasks();
  1148. if (this.background !== null) {
  1149. this.background.visible = true;
  1150. }
  1151. // Use scroll position to determine visibility
  1152. this.scrollEvent(0, 10);
  1153. this.visible = true;
  1154. };
  1155. this._moveMenuItems = function (x, y) {
  1156. for (var i in this.protoContainers) {
  1157. this.protoContainers[i].x = x;
  1158. this.protoContainers[i].y = y;
  1159. }
  1160. if (this.background !== null) {
  1161. this.background.x = x;
  1162. this.background.y = y;
  1163. }
  1164. };
  1165. this._moveMenuItemsRelative = function (dx, dy) {
  1166. for (var i in this.protoContainers) {
  1167. this.protoContainers[i].x += dx;
  1168. this.protoContainers[i].y += dy;
  1169. }
  1170. if (this.background !== null) {
  1171. this.background.x += dx;
  1172. this.background.y += dy;
  1173. }
  1174. if (this.fadedDownButton !== null) {
  1175. this.upButton.x += dx;
  1176. this.upButton.y += dy;
  1177. this.downButton.x += dx;
  1178. this.downButton.y += dy;
  1179. this.fadedUpButton.x += dx;
  1180. this.fadedUpButton.y += dy;
  1181. this.fadedDownButton.x += dx;
  1182. this.fadedDownButton.y += dy;
  1183. }
  1184. };
  1185. this.scrollEvent = function (direction, scrollSpeed) {
  1186. var diff = direction * scrollSpeed;
  1187. var h = Math.min(maxPaletteHeight(this.palettes.cellSize, this.palettes.scale), this.y);
  1188. if (this.y < maxPaletteHeight(this.palettes.cellSize, this.palettes.scale)) {
  1189. this.upButton.visible = false;
  1190. this.downButton.visible = false;
  1191. this.fadedUpButton.visible = false;
  1192. this.fadedDownButton.visible = false;
  1193. return;
  1194. }
  1195. if (this.scrollDiff + diff > 0 && direction > 0) {
  1196. var dy = -this.scrollDiff;
  1197. if (dy === 0) {
  1198. this.downButton.visible = true;
  1199. this.upButton.visible = false;
  1200. this.fadedUpButton.visible = true;
  1201. this.fadedDownButton.visible = false;
  1202. return;
  1203. }
  1204. this.scrollDiff += dy;
  1205. this.fadedDownButton.visible = false;
  1206. this.downButton.visible = true;
  1207. for (var i in this.protoContainers) {
  1208. this.protoContainers[i].y += dy;
  1209. this.protoContainers[i].visible = true;
  1210. if (this.scrollDiff === 0) {
  1211. this.downButton.visible = true;
  1212. this.upButton.visible = false;
  1213. this.fadedUpButton.visible = true;
  1214. this.fadedDownButton.visible = false;
  1215. }
  1216. }
  1217. } else if (this.y + this.scrollDiff + diff < h && direction < 0) {
  1218. var dy = -this.y + h - this.scrollDiff;
  1219. if (dy === 0) {
  1220. this.upButton.visible = true;
  1221. this.downButton.visible = false;
  1222. this.fadedDownButton.visible = true;
  1223. this.fadedUpButton.visible = false;
  1224. return;
  1225. }
  1226. this.scrollDiff += -this.y + h - this.scrollDiff;
  1227. this.fadedUpButton.visible = false;
  1228. this.upButton.visible = true;
  1229. for (var i in this.protoContainers) {
  1230. this.protoContainers[i].y += dy;
  1231. this.protoContainers[i].visible = true;
  1232. }
  1233. if(-this.y + h - this.scrollDiff === 0) {
  1234. this.upButton.visible = true;
  1235. this.downButton.visible = false;
  1236. this.fadedDownButton.visible = true;
  1237. this.fadedUpButton.visible = false;
  1238. }
  1239. } else if (this.count === 0) {
  1240. this.fadedUpButton.visible = true;
  1241. this.fadedDownButton.visible = false;
  1242. this.upButton.visible = false;
  1243. this.downButton.visible = true;
  1244. } else {
  1245. this.scrollDiff += diff;
  1246. this.fadedUpButton.visible = false;
  1247. this.fadedDownButton.visible = false;
  1248. this.upButton.visible = true;
  1249. this.downButton.visible = true;
  1250. for (var i in this.protoContainers) {
  1251. this.protoContainers[i].y += diff;
  1252. this.protoContainers[i].visible = true;
  1253. }
  1254. }
  1255. this._updateBlockMasks();
  1256. var stage = this.palettes.stage;
  1257. stage.setChildIndex(this.menuContainer, stage.getNumChildren() - 1);
  1258. this.palettes.refreshCanvas();
  1259. this.count += 1;
  1260. };
  1261. this.getInfo = function () {
  1262. var returnString = this.name + ' palette:';
  1263. for (var thisBlock in this.protoList) {
  1264. returnString += ' ' + this.protoList[thisBlock].name;
  1265. }
  1266. return returnString;
  1267. };
  1268. this.remove = function (protoblock, name) {
  1269. // Remove the protoblock and its associated artwork container.
  1270. // console.log('removing action ' + name);
  1271. var i = this.protoList.indexOf(protoblock);
  1272. if (i !== -1) {
  1273. this.protoList.splice(i, 1);
  1274. }
  1275. for (var i = 0; i < this.model.blocks.length; i++) {
  1276. if (['nameddo', 'nameddoArg', 'namedcalc', 'namedcalcArg'].indexOf(this.model.blocks[i].blkname) !== -1 && this.model.blocks[i].modname === name) {
  1277. this.model.blocks.splice(i, 1);
  1278. break;
  1279. }
  1280. }
  1281. this.palettes.stage.removeChild(this.protoContainers[name]);
  1282. delete this.protoContainers[name];
  1283. };
  1284. this.add = function (protoblock, top) {
  1285. // Add a new palette entry to the end of the list (default) or
  1286. // to the top.
  1287. if (this.protoList.indexOf(protoblock) === -1) {
  1288. if (top === undefined) {
  1289. this.protoList.push(protoblock);
  1290. } else {
  1291. this.protoList.splice(0, 0, protoblock);
  1292. }
  1293. }
  1294. return this;
  1295. };
  1296. this._setupBackgroundEvents = function () {
  1297. var palette = this;
  1298. var scrolling = false;
  1299. this.background.on('mouseover', function (event) {
  1300. palette.palettes.activePalette = palette;
  1301. });
  1302. this.background.on('mouseout', function (event) {
  1303. palette.palettes.activePalette = null;
  1304. });
  1305. this.background.on('mousedown', function (event) {
  1306. scrolling = true;
  1307. var lastY = event.stageY;
  1308. palette.background.on('pressmove', function (event) {
  1309. if (!scrolling) {
  1310. return;
  1311. }
  1312. var diff = event.stageY - lastY;
  1313. palette.scrollEvent(diff, 10);
  1314. lastY = event.stageY;
  1315. });
  1316. palette.background.on('pressup', function (event) {
  1317. palette.palettes.activePalette = null;
  1318. scrolling = false;
  1319. }, null, true); // once = true
  1320. });
  1321. };
  1322. // Palette Menu event handlers
  1323. this._loadPaletteMenuHandler =function () {
  1324. // The palette menu is the container for the protoblocks. One
  1325. // palette per palette button.
  1326. var palette = this;
  1327. var locked = false;
  1328. var trashcan = this.palettes.trashcan;
  1329. var paletteWidth = MENUWIDTH + this._getOverflowWidth();
  1330. this.menuContainer.on('click', function (event) {
  1331. if (Math.round(event.stageX / palette.palettes.scale) > palette.menuContainer.x + paletteWidth - STANDARDBLOCKHEIGHT) {
  1332. palette.hide();
  1333. palette.palettes.refreshCanvas();
  1334. return;
  1335. }
  1336. if (locked) {
  1337. return;
  1338. }
  1339. locked = true;
  1340. setTimeout(function () {
  1341. locked = false;
  1342. }, 500);
  1343. for (var p in palette.palettes.dict) {
  1344. if (palette.name != p) {
  1345. if (palette.palettes.dict[p].visible) {
  1346. palette.palettes.dict[p]._hideMenuItems(false);
  1347. }
  1348. }
  1349. }
  1350. if (palette.visible) {
  1351. palette._hideMenuItems(false);
  1352. } else {
  1353. palette._showMenuItems(false);
  1354. }
  1355. palette.palettes.refreshCanvas();
  1356. });
  1357. this.menuContainer.on('mousedown', function (event) {
  1358. trashcan.show();
  1359. // Move them all?
  1360. var offset = {
  1361. x: palette.menuContainer.x - Math.round(event.stageX / palette.palettes.scale),
  1362. y: palette.menuContainer.y - Math.round(event.stageY / palette.palettes.scale)
  1363. };
  1364. palette.menuContainer.on('pressup', function (event) {
  1365. if (trashcan.overTrashcan(event.stageX / palette.palettes.scale, event.stageY / palette.palettes.scale)) {
  1366. if (trashcan.isVisible) {
  1367. palette.hide();
  1368. palette.palettes.refreshCanvas();
  1369. // Only delete plugin palettes.
  1370. if (palette.name === 'myblocks') {
  1371. palette._promptMacrosDelete();
  1372. } else if (BUILTINPALETTES.indexOf(palette.name) === -1) {
  1373. palette._promptPaletteDelete();
  1374. }
  1375. }
  1376. }
  1377. trashcan.hide();
  1378. });
  1379. palette.menuContainer.on('mouseout', function (event) {
  1380. if (trashcan.overTrashcan(event.stageX / palette.palettes.scale, event.stageY / palette.palettes.scale)) {
  1381. if (trashcan.isVisible) {
  1382. palette.hide();
  1383. palette.palettes.refreshCanvas();
  1384. }
  1385. }
  1386. trashcan.hide();
  1387. });
  1388. palette.menuContainer.on('pressmove', function (event) {
  1389. var oldX = palette.menuContainer.x;
  1390. var oldY = palette.menuContainer.y;
  1391. palette.menuContainer.x = Math.round(event.stageX / palette.palettes.scale) + offset.x;
  1392. palette.menuContainer.y = Math.round(event.stageY / palette.palettes.scale) + offset.y;
  1393. palette.palettes.refreshCanvas();
  1394. var dx = palette.menuContainer.x - oldX;
  1395. var dy = palette.menuContainer.y - oldY;
  1396. palette.palettes.initial_x = palette.menuContainer.x;
  1397. palette.palettes.initial_y = palette.menuContainer.y;
  1398. // If we are over the trash, warn the user.
  1399. if (trashcan.overTrashcan(event.stageX / palette.palettes.scale, event.stageY / palette.palettes.scale)) {
  1400. trashcan.startHighlightAnimation();
  1401. } else {
  1402. trashcan.stopHighlightAnimation();
  1403. }
  1404. // Hide the menu items while drag.
  1405. palette._hideMenuItems(false);
  1406. palette._moveMenuItemsRelative(dx, dy);
  1407. });
  1408. });
  1409. };
  1410. // Menu Item event handlers
  1411. this._loadPaletteMenuItemHandler = function (protoblk, blkname) {
  1412. // A menu item is a protoblock that is used to create a new block.
  1413. var palette = this;
  1414. var pressupLock = false;
  1415. var pressed = false;
  1416. var moved = false;
  1417. var saveX = this.protoContainers[blkname].x;
  1418. var saveY = this.protoContainers[blkname].y;
  1419. var bgScrolling = false;
  1420. this.protoContainers[blkname].on('mouseover', function (event) {
  1421. palette.palettes.activePalette = palette;
  1422. });
  1423. this.protoContainers[blkname].on('mousedown', function (event) {
  1424. var stage = palette.palettes.stage;
  1425. stage.setChildIndex(palette.protoContainers[blkname], stage.getNumChildren() - 1);
  1426. var h = Math.min(maxPaletteHeight(palette.palettes.cellSize, palette.palettes.scale), palette.palettes.y);
  1427. var clickY = event.stageY/palette.palettes.scale;
  1428. var paletteEndY = palette.menuContainer.y + h + STANDARDBLOCKHEIGHT;
  1429. // if(clickY < paletteEndY)
  1430. palette.protoContainers[blkname].mask = null;
  1431. moved = false;
  1432. pressed = true;
  1433. saveX = palette.protoContainers[blkname].x;
  1434. saveY = palette.protoContainers[blkname].y - palette.scrollDiff;
  1435. var startX = event.stageX;
  1436. var startY = event.stageY;
  1437. var lastY = event.stageY;
  1438. if (palette.draggingProtoBlock) {
  1439. return;
  1440. }
  1441. var mode = window.hasMouse ? MODEDRAG : MODEUNSURE;
  1442. palette.protoContainers[blkname].on('pressmove', function (event) {
  1443. if (mode === MODEDRAG) {
  1444. // if(clickY < paletteEndY)
  1445. moved = true;
  1446. palette.draggingProtoBlock = true;
  1447. palette.protoContainers[blkname].x = Math.round(event.stageX / palette.palettes.scale) - PALETTELEFTMARGIN;
  1448. palette.protoContainers[blkname].y = Math.round(event.stageY / palette.palettes.scale);
  1449. palette.palettes.refreshCanvas();
  1450. return;
  1451. }
  1452. if (mode === MODESCROLL) {
  1453. var diff = event.stageY - lastY;
  1454. palette.scrollEvent(diff, 10);
  1455. lastY = event.stageY;
  1456. return;
  1457. }
  1458. var xd = Math.abs(event.stageX - startX);
  1459. var yd = Math.abs(event.stageY - startY);
  1460. var diff = Math.sqrt(xd * xd + yd * yd);
  1461. if (mode === MODEUNSURE && diff > DECIDEDISTANCE) {
  1462. mode = yd > xd ? MODESCROLL : MODEDRAG;
  1463. }
  1464. });
  1465. });
  1466. this.protoContainers[blkname].on('mouseout', function (event) {
  1467. // Catch case when pressup event is missed.
  1468. // Put the protoblock back on the palette...
  1469. palette.palettes.activePalette = null;
  1470. if (pressed && moved) {
  1471. palette._restoreProtoblock(blkname, saveX, saveY + palette.scrollDiff);
  1472. pressed = false;
  1473. moved = false;
  1474. }
  1475. });
  1476. this.protoContainers[blkname].on('pressup', function (event) {
  1477. palette.palettes.activePalette = null;
  1478. if (pressupLock) {
  1479. return;
  1480. } else {
  1481. pressupLock = true;
  1482. setTimeout(function () {
  1483. pressupLock = false;
  1484. }, 1000);
  1485. }
  1486. palette._makeBlockFromProtoblock(protoblk, moved, blkname, event, saveX, saveY);
  1487. });
  1488. };
  1489. this._restoreProtoblock = function (name, x, y) {
  1490. // Return protoblock we've been dragging back to the palette.
  1491. this.protoContainers[name].x = x;
  1492. this.protoContainers[name].y = y;
  1493. // console.log('restore ' + name);
  1494. this._resetLayout();
  1495. };
  1496. this._promptPaletteDelete = function () {
  1497. var msg = 'Do you want to remove all "%s" blocks from your project?'.replace('%s', this.name)
  1498. if (!confirm(msg)) {
  1499. return;
  1500. }
  1501. this.palettes.remove(this.name);
  1502. delete pluginObjs['PALETTEHIGHLIGHTCOLORS'][this.name];
  1503. delete pluginObjs['PALETTESTROKECOLORS'][this.name];
  1504. delete pluginObjs['PALETTEFILLCOLORS'][this.name];
  1505. delete pluginObjs['PALETTEPLUGINS'][this.name];
  1506. if ('GLOBALS' in pluginObjs) {
  1507. delete pluginObjs['GLOBALS'][this.name];
  1508. }
  1509. if ('IMAGES' in pluginObjs) {
  1510. delete pluginObjs['IMAGES'][this.name];
  1511. }
  1512. if ('ONLOAD' in pluginObjs) {
  1513. delete pluginObjs['ONLOAD'][this.name];
  1514. }
  1515. if ('ONSTART' in pluginObjs) {
  1516. delete pluginObjs['ONSTART'][this.name];
  1517. }
  1518. if ('ONSTOP' in pluginObjs) {
  1519. delete pluginObjs['ONSTOP'][this.name];
  1520. }
  1521. for (var i = 0; i < this.protoList.length; i++) {
  1522. var name = this.protoList[i].name;
  1523. delete pluginObjs['FLOWPLUGINS'][name];
  1524. delete pluginObjs['ARGPLUGINS'][name];
  1525. delete pluginObjs['BLOCKPLUGINS'][name];
  1526. }
  1527. storage.plugins = preparePluginExports({});
  1528. if (sugarizerCompatibility.isInsideSugarizer()) {
  1529. sugarizerCompatibility.saveLocally();
  1530. }
  1531. };
  1532. this._promptMacrosDelete = function () {
  1533. var msg = 'Do you want to remove all the stacks from your custom palette?';
  1534. if (!confirm(msg)) {
  1535. return;
  1536. }
  1537. for (var i = 0; i < this.protoList.length; i++) {
  1538. var name = this.protoList[i].name;
  1539. delete this.protoContainers[name];
  1540. this.protoList.splice(i, 1);
  1541. }
  1542. this.palettes.updatePalettes('myblocks');
  1543. storage.macros = prepareMacroExports(null, null, {});
  1544. if (sugarizerCompatibility.isInsideSugarizer()) {
  1545. sugarizerCompatibility.saveLocally();
  1546. }
  1547. };
  1548. this._makeBlockFromPalette = function (protoblk, blkname, callback) {
  1549. if (protoblk == null) {
  1550. console.log('null protoblk?');
  1551. return;
  1552. }
  1553. switch (protoblk.name) {
  1554. case 'do':
  1555. blkname = 'do ' + protoblk.defaults[0];
  1556. var newBlk = protoblk.name;
  1557. var arg = protoblk.defaults[0];
  1558. break;
  1559. case 'storein':
  1560. // Use the name of the box in the label
  1561. blkname = 'store in ' + protoblk.defaults[0];
  1562. var newBlk = protoblk.name;
  1563. var arg = protoblk.defaults[0];
  1564. break;
  1565. case 'box':
  1566. // Use the name of the box in the label
  1567. blkname = protoblk.defaults[0];
  1568. var newBlk = protoblk.name;
  1569. var arg = protoblk.defaults[0];
  1570. break;
  1571. case 'namedbox':
  1572. // Use the name of the box in the label
  1573. if (protoblk.defaults[0] === undefined) {
  1574. blkname = 'namedbox';
  1575. var arg = _('box');
  1576. } else {
  1577. blkname = protoblk.defaults[0];
  1578. var arg = protoblk.defaults[0];
  1579. }
  1580. var newBlk = protoblk.name;
  1581. break;
  1582. case 'namedarg':
  1583. // Use the name of the arg in the label
  1584. if (protoblk.defaults[0] === undefined) {
  1585. blkname = 'namedarg';
  1586. var arg = '1';
  1587. } else {
  1588. blkname = protoblk.defaults[0];
  1589. var arg = protoblk.defaults[0];
  1590. }
  1591. var newBlk = protoblk.name;
  1592. break;
  1593. case 'nameddo':
  1594. // Use the name of the action in the label
  1595. if (protoblk.defaults[0] === undefined) {
  1596. blkname = 'nameddo';
  1597. var arg = _('action');
  1598. } else {
  1599. blkname = protoblk.defaults[0];
  1600. var arg = protoblk.defaults[0];
  1601. }
  1602. var newBlk = protoblk.name;
  1603. break;
  1604. case 'nameddoArg':
  1605. // Use the name of the action in the label
  1606. if (protoblk.defaults[0] === undefined) {
  1607. blkname = 'nameddoArg';
  1608. var arg = _('action');
  1609. } else {
  1610. blkname = protoblk.defaults[0];
  1611. var arg = protoblk.defaults[0];
  1612. }
  1613. var newBlk = protoblk.name;
  1614. break;
  1615. case 'namedcalc':
  1616. // Use the name of the action in the label
  1617. if (protoblk.defaults[0] === undefined) {
  1618. blkname = 'namedcalc';
  1619. var arg = _('action');
  1620. } else {
  1621. blkname = protoblk.defaults[0];
  1622. var arg = protoblk.defaults[0];
  1623. }
  1624. var newBlk = protoblk.name;
  1625. break;
  1626. case 'namedcalcArg':
  1627. // Use the name of the action in the label
  1628. if (protoblk.defaults[0] === undefined) {
  1629. blkname = 'namedcalcArg';
  1630. var arg = _('action');
  1631. } else {
  1632. blkname = protoblk.defaults[0];
  1633. var arg = protoblk.defaults[0];
  1634. }
  1635. var newBlk = protoblk.name;
  1636. break;
  1637. default:
  1638. var newBlk = blkname;
  1639. var arg = '__NOARG__';
  1640. break;
  1641. }
  1642. if (protoblk.name !== 'namedbox' && blockIsMacro(blkname)) {
  1643. moved = true;
  1644. saveX = this.protoContainers[blkname].x;
  1645. saveY = this.protoContainers[blkname].y;
  1646. this._makeBlockFromProtoblock(protoblk, moved, blkname, null, saveX, saveY);
  1647. } else {
  1648. var newBlock = paletteBlockButtonPush(this.palettes.blocks, newBlk, arg);
  1649. callback(newBlock);
  1650. }
  1651. };
  1652. this.cleanup = function () {
  1653. this._resetLayout();
  1654. this._updateBlockMasks();
  1655. this.palettes.refreshCanvas();
  1656. };
  1657. this._makeBlockFromProtoblock = function (protoblk, moved, blkname, event, saveX, saveY) {
  1658. var that = this;
  1659. function __myCallback (newBlock) {
  1660. // Move the drag group under the cursor.
  1661. that.palettes.blocks.findDragGroup(newBlock);
  1662. for (var i in that.palettes.blocks.dragGroup) {
  1663. that.palettes.blocks.moveBlockRelative(that.palettes.blocks.dragGroup[i], Math.round(event.stageX / that.palettes.scale) - that.palettes.blocks.stage.x, Math.round(event.stageY / that.palettes.scale) - that.palettes.blocks.stage.y);
  1664. }
  1665. // Dock with other blocks if needed
  1666. that.palettes.blocks.blockMoved(newBlock);
  1667. that.palettes.blocks.checkBounds();
  1668. };
  1669. if (moved) {
  1670. moved = false;
  1671. this.draggingProtoBlock = false;
  1672. var macroExpansion = getMacroExpansion(blkname, this.protoContainers[blkname].x - this.palettes.blocks.stage.x, this.protoContainers[blkname].y - this.palettes.blocks.stage.y);
  1673. if (macroExpansion != null) {
  1674. this.palettes.blocks.loadNewBlocks(macroExpansion);
  1675. var thisBlock = this.palettes.blocks.blockList.length - 1;
  1676. var topBlk = this.palettes.blocks.findTopBlock(thisBlock);
  1677. } else if (this.name === 'myblocks') {
  1678. // If we are on the myblocks palette, it is a macro.
  1679. var macroName = blkname.replace('macro_', '');
  1680. // We need to copy the macro data so it is not overwritten.
  1681. var obj = [];
  1682. for (var b = 0; b < this.palettes.macroDict[macroName].length; b++) {
  1683. var valueEntry = this.palettes.macroDict[macroName][b][1];
  1684. var newValue = [];
  1685. if (typeof(valueEntry) === 'string') {
  1686. newValue = valueEntry;
  1687. } else if (typeof(valueEntry[1]) === 'string') {
  1688. if (valueEntry[0] === 'number') {
  1689. newValue = [valueEntry[0], Number(valueEntry[1])];
  1690. } else {
  1691. newValue = [valueEntry[0], valueEntry[1]];
  1692. }
  1693. } else if (typeof(valueEntry[1]) === 'number') {
  1694. if (valueEntry[0] === 'number') {
  1695. newValue = [valueEntry[0], valueEntry[1]];
  1696. } else {
  1697. newValue = [valueEntry[0], valueEntry[1].toString()];
  1698. }
  1699. } else {
  1700. if (valueEntry[0] === 'number') {
  1701. newValue = [valueEntry[0], Number(valueEntry[1]['value'])];
  1702. } else {
  1703. newValue = [valueEntry[0], {'value': valueEntry[1]['value']}];
  1704. }
  1705. }
  1706. var newBlock = [this.palettes.macroDict[macroName][b][0],
  1707. newValue,
  1708. this.palettes.macroDict[macroName][b][2],
  1709. this.palettes.macroDict[macroName][b][3],
  1710. this.palettes.macroDict[macroName][b][4]];
  1711. obj.push(newBlock);
  1712. }
  1713. // Set the position of the top block in the stack
  1714. // before loading.
  1715. obj[0][2] = this.protoContainers[blkname].x - this.palettes.blocks.stage.x;
  1716. obj[0][3] = this.protoContainers[blkname].y - this.palettes.blocks.stage.y;
  1717. this.palettes.blocks.loadNewBlocks(obj);
  1718. // Ensure collapse state of new stack is set properly.
  1719. var thisBlock = this.palettes.blocks.blockList.length - 1;
  1720. var topBlk = this.palettes.blocks.findTopBlock(thisBlock);
  1721. setTimeout(function () {
  1722. this.palettes.blocks.blockList[topBlk].collapseToggle();
  1723. }, 500);
  1724. } else {
  1725. var newBlock = this._makeBlockFromPalette(protoblk, blkname, __myCallback, newBlock);
  1726. }
  1727. // Put the protoblock back on the palette...
  1728. this.cleanup();
  1729. }
  1730. };
  1731. return this;
  1732. };
  1733. function initPalettes (palettes) {
  1734. // Instantiate the palettes object on first load.
  1735. for (var i = 0; i < BUILTINPALETTES.length; i++) {
  1736. palettes.add(BUILTINPALETTES[i]);
  1737. }
  1738. palettes.makePalettes(true);
  1739. // Give the palettes time to load.
  1740. // We are in no hurry since we are waiting on the splash screen.
  1741. setTimeout(function () {
  1742. palettes.show();
  1743. palettes.bringToTop();
  1744. }, 6000);
  1745. };
  1746. const MODEUNSURE = 0;
  1747. const MODEDRAG = 1;
  1748. const MODESCROLL = 2;
  1749. const DECIDEDISTANCE = 20;
  1750. function makePaletteBitmap(palette, data, name, callback, extras) {
  1751. // Async creation of bitmap from SVG data
  1752. // Works with Chrome, Safari, Firefox (untested on IE)
  1753. var img = new Image();
  1754. img.onload = function () {
  1755. var bitmap = new createjs.Bitmap(img);
  1756. callback(palette, name, bitmap, extras);
  1757. };
  1758. img.src = 'data:image/svg+xml;base64,' + window.btoa(unescape(encodeURIComponent(data)));
  1759. };