logic.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433
  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 Logic blocks for Blockly.
  22. * @author q.neutron@gmail.com (Quynh Neutron)
  23. */
  24. 'use strict';
  25. goog.provide('Blockly.Blocks.logic');
  26. goog.require('Blockly.Blocks');
  27. Blockly.Blocks['controls_if'] = {
  28. /**
  29. * Block for if/elseif/else condition.
  30. * @this Blockly.Block
  31. */
  32. init: function() {
  33. this.setHelpUrl(Blockly.Msg.CONTROLS_IF_HELPURL);
  34. this.setColour(Blockscad.Toolbox.HEX_LOGIC);
  35. this.appendValueInput('IF0')
  36. .setCheck(['Boolean','Number'])
  37. .appendField(Blockly.Msg.CONTROLS_IF_MSG_IF);
  38. this.appendStatementInput('DO0')
  39. .appendField(Blockly.Msg.CONTROLS_IF_MSG_THEN)
  40. .setCheck(['CSG','CAG']);
  41. this.setPreviousStatement(true);
  42. //this.setNextStatement(true);
  43. this.setMutator(new Blockly.Mutator(['controls_if_elseif',
  44. 'controls_if_else']));
  45. // Assign 'this' to a variable for use in the tooltip closure below.
  46. var thisBlock = this;
  47. this.setTooltip(function() {
  48. if (!thisBlock.elseifCount_ && !thisBlock.elseCount_) {
  49. return Blockly.Msg.CONTROLS_IF_TOOLTIP_1;
  50. } else if (!thisBlock.elseifCount_ && thisBlock.elseCount_) {
  51. return Blockly.Msg.CONTROLS_IF_TOOLTIP_2;
  52. } else if (thisBlock.elseifCount_ && !thisBlock.elseCount_) {
  53. return Blockly.Msg.CONTROLS_IF_TOOLTIP_3;
  54. } else if (thisBlock.elseifCount_ && thisBlock.elseCount_) {
  55. return Blockly.Msg.CONTROLS_IF_TOOLTIP_4;
  56. }
  57. return '';
  58. });
  59. this.elseifCount_ = 0;
  60. this.elseCount_ = 0;
  61. },
  62. /**
  63. * Create XML to represent the number of else-if and else inputs.
  64. * @return {Element} XML storage element.
  65. * @this Blockly.Block
  66. */
  67. mutationToDom: function() {
  68. if (!this.elseifCount_ && !this.elseCount_) {
  69. return null;
  70. }
  71. var container = document.createElement('mutation');
  72. if (this.elseifCount_) {
  73. container.setAttribute('elseif', this.elseifCount_);
  74. }
  75. if (this.elseCount_) {
  76. container.setAttribute('else', 1);
  77. }
  78. return container;
  79. },
  80. /**
  81. * Parse XML to restore the else-if and else inputs.
  82. * @param {!Element} xmlElement XML storage element.
  83. * @this Blockly.Block
  84. */
  85. domToMutation: function(xmlElement) {
  86. this.elseifCount_ = parseInt(xmlElement.getAttribute('elseif'), 10) || 0;
  87. this.elseCount_ = parseInt(xmlElement.getAttribute('else'), 10) || 0;
  88. for (var i = 1; i <= this.elseifCount_; i++) {
  89. this.appendValueInput('IF' + i)
  90. .setCheck('Boolean')
  91. .appendField(Blockly.Msg.CONTROLS_IF_MSG_ELSEIF);
  92. this.appendStatementInput('DO' + i)
  93. .setCheck(['CSG','CAG'])
  94. .appendField(Blockly.Msg.CONTROLS_IF_MSG_THEN);
  95. }
  96. if (this.elseCount_) {
  97. this.appendStatementInput('ELSE')
  98. .setCheck(['CSG','CAG'])
  99. .appendField(Blockly.Msg.CONTROLS_IF_MSG_ELSE);
  100. }
  101. },
  102. /**
  103. * Populate the mutator's dialog with this block's components.
  104. * @param {!Blockly.Workspace} workspace Mutator's workspace.
  105. * @return {!Blockly.Block} Root block in mutator.
  106. * @this Blockly.Block
  107. */
  108. decompose: function(workspace) {
  109. var containerBlock = Blockly.Block.obtain(workspace, 'controls_if_if');
  110. containerBlock.initSvg();
  111. var connection = containerBlock.getInput('STACK').connection;
  112. for (var i = 1; i <= this.elseifCount_; i++) {
  113. var elseifBlock = Blockly.Block.obtain(workspace, 'controls_if_elseif');
  114. elseifBlock.initSvg();
  115. connection.connect(elseifBlock.previousConnection);
  116. connection = elseifBlock.nextConnection;
  117. }
  118. if (this.elseCount_) {
  119. var elseBlock = Blockly.Block.obtain(workspace, 'controls_if_else');
  120. elseBlock.initSvg();
  121. connection.connect(elseBlock.previousConnection);
  122. }
  123. return containerBlock;
  124. },
  125. /**
  126. * Reconfigure this block based on the mutator dialog's components.
  127. * @param {!Blockly.Block} containerBlock Root block in mutator.
  128. * @this Blockly.Block
  129. */
  130. compose: function(containerBlock) {
  131. // Disconnect the else input blocks and remove the inputs.
  132. if (this.elseCount_) {
  133. this.removeInput('ELSE');
  134. }
  135. this.elseCount_ = 0;
  136. // Disconnect all the elseif input blocks and remove the inputs.
  137. for (var i = this.elseifCount_; i > 0; i--) {
  138. this.removeInput('IF' + i);
  139. this.removeInput('DO' + i);
  140. }
  141. this.elseifCount_ = 0;
  142. // Rebuild the block's optional inputs.
  143. var clauseBlock = containerBlock.getInputTargetBlock('STACK');
  144. while (clauseBlock) {
  145. switch (clauseBlock.type) {
  146. case 'controls_if_elseif':
  147. this.elseifCount_++;
  148. var ifInput = this.appendValueInput('IF' + this.elseifCount_)
  149. .setCheck('Boolean')
  150. .appendField(Blockly.Msg.CONTROLS_IF_MSG_ELSEIF);
  151. var doInput = this.appendStatementInput('DO' + this.elseifCount_)
  152. .setCheck(['CSG','CAG']);
  153. doInput.appendField(Blockly.Msg.CONTROLS_IF_MSG_THEN);
  154. // Reconnect any child blocks.
  155. if (clauseBlock.valueConnection_) {
  156. ifInput.connection.connect(clauseBlock.valueConnection_);
  157. }
  158. if (clauseBlock.statementConnection_) {
  159. doInput.connection.connect(clauseBlock.statementConnection_);
  160. }
  161. break;
  162. case 'controls_if_else':
  163. this.elseCount_++;
  164. var elseInput = this.appendStatementInput('ELSE')
  165. .setCheck(['CSG','CAG']);
  166. elseInput.appendField(Blockly.Msg.CONTROLS_IF_MSG_ELSE);
  167. // Reconnect any child blocks.
  168. if (clauseBlock.statementConnection_) {
  169. elseInput.connection.connect(clauseBlock.statementConnection_);
  170. }
  171. break;
  172. default:
  173. throw 'Unknown block type.';
  174. }
  175. clauseBlock = clauseBlock.nextConnection &&
  176. clauseBlock.nextConnection.targetBlock();
  177. }
  178. },
  179. /**
  180. * Store pointers to any connected child blocks.
  181. * @param {!Blockly.Block} containerBlock Root block in mutator.
  182. * @this Blockly.Block
  183. */
  184. saveConnections: function(containerBlock) {
  185. var clauseBlock = containerBlock.getInputTargetBlock('STACK');
  186. var i = 1;
  187. while (clauseBlock) {
  188. switch (clauseBlock.type) {
  189. case 'controls_if_elseif':
  190. var inputIf = this.getInput('IF' + i);
  191. var inputDo = this.getInput('DO' + i);
  192. clauseBlock.valueConnection_ =
  193. inputIf && inputIf.connection.targetConnection;
  194. clauseBlock.statementConnection_ =
  195. inputDo && inputDo.connection.targetConnection;
  196. i++;
  197. break;
  198. case 'controls_if_else':
  199. var inputDo = this.getInput('ELSE');
  200. clauseBlock.statementConnection_ =
  201. inputDo && inputDo.connection.targetConnection;
  202. break;
  203. default:
  204. throw 'Unknown block type.';
  205. }
  206. clauseBlock = clauseBlock.nextConnection &&
  207. clauseBlock.nextConnection.targetBlock();
  208. }
  209. }
  210. };
  211. Blockly.Blocks['controls_if_if'] = {
  212. /**
  213. * Mutator block for if container.
  214. * @this Blockly.Block
  215. */
  216. init: function() {
  217. this.setColour(Blockscad.Toolbox.HEX_LOGIC);
  218. this.appendDummyInput()
  219. .appendField(Blockly.Msg.CONTROLS_IF_IF_TITLE_IF);
  220. this.appendStatementInput('STACK');
  221. this.setTooltip(Blockly.Msg.CONTROLS_IF_IF_TOOLTIP);
  222. this.contextMenu = false;
  223. }
  224. };
  225. Blockly.Blocks['controls_if_elseif'] = {
  226. /**
  227. * Mutator bolck for else-if condition.
  228. * @this Blockly.Block
  229. */
  230. init: function() {
  231. this.setColour(Blockscad.Toolbox.HEX_LOGIC);
  232. this.appendDummyInput()
  233. .appendField(Blockly.Msg.CONTROLS_IF_ELSEIF_TITLE_ELSEIF);
  234. this.setPreviousStatement(true);
  235. this.setNextStatement(true);
  236. this.setTooltip(Blockly.Msg.CONTROLS_IF_ELSEIF_TOOLTIP);
  237. this.contextMenu = false;
  238. }
  239. };
  240. Blockly.Blocks['controls_if_else'] = {
  241. /**
  242. * Mutator block for else condition.
  243. * @this Blockly.Block
  244. */
  245. init: function() {
  246. this.setColour(Blockscad.Toolbox.HEX_LOGIC);
  247. this.appendDummyInput()
  248. .appendField(Blockly.Msg.CONTROLS_IF_ELSE_TITLE_ELSE);
  249. this.setPreviousStatement(true);
  250. this.setTooltip(Blockly.Msg.CONTROLS_IF_ELSE_TOOLTIP);
  251. this.contextMenu = false;
  252. }
  253. };
  254. Blockly.Blocks['logic_compare'] = {
  255. /**
  256. * Block for comparison operator.
  257. * @this Blockly.Block
  258. */
  259. init: function() {
  260. var OPERATORS = this.RTL ? [
  261. ['=', 'EQ'],
  262. ['\u2260', 'NEQ'],
  263. ['>', 'LT'],
  264. ['\u2265', 'LTE'],
  265. ['<', 'GT'],
  266. ['\u2264', 'GTE']
  267. ] : [
  268. ['=', 'EQ'],
  269. ['\u2260', 'NEQ'],
  270. ['<', 'LT'],
  271. ['\u2264', 'LTE'],
  272. ['>', 'GT'],
  273. ['\u2265', 'GTE']
  274. ];
  275. this.setHelpUrl(Blockly.Msg.LOGIC_COMPARE_HELPURL);
  276. this.setColour(Blockscad.Toolbox.HEX_LOGIC);
  277. this.setOutput(true, 'Boolean');
  278. this.appendValueInput('A');
  279. this.appendValueInput('B')
  280. .appendField(new Blockly.FieldDropdown(OPERATORS), 'OP');
  281. this.setInputsInline(true);
  282. // Assign 'this' to a variable for use in the tooltip closure below.
  283. var thisBlock = this;
  284. this.setTooltip(function() {
  285. var op = thisBlock.getFieldValue('OP');
  286. var TOOLTIPS = {
  287. 'EQ': Blockly.Msg.LOGIC_COMPARE_TOOLTIP_EQ,
  288. 'NEQ': Blockly.Msg.LOGIC_COMPARE_TOOLTIP_NEQ,
  289. 'LT': Blockly.Msg.LOGIC_COMPARE_TOOLTIP_LT,
  290. 'LTE': Blockly.Msg.LOGIC_COMPARE_TOOLTIP_LTE,
  291. 'GT': Blockly.Msg.LOGIC_COMPARE_TOOLTIP_GT,
  292. 'GTE': Blockly.Msg.LOGIC_COMPARE_TOOLTIP_GTE
  293. };
  294. return TOOLTIPS[op];
  295. });
  296. this.prevBlocks_ = [null, null];
  297. },
  298. /**
  299. * Called whenever anything on the workspace changes.
  300. * Prevent mismatched types from being compared.
  301. * @this Blockly.Block
  302. */
  303. onchange: function() {
  304. var blockA = this.getInputTargetBlock('A');
  305. var blockB = this.getInputTargetBlock('B');
  306. // Disconnect blocks that existed prior to this change if they don't match.
  307. if (blockA && blockB &&
  308. !blockA.outputConnection.checkType_(blockB.outputConnection)) {
  309. // Mismatch between two inputs. Disconnect previous and bump it away.
  310. for (var i = 0; i < this.prevBlocks_.length; i++) {
  311. var block = this.prevBlocks_[i];
  312. if (block === blockA || block === blockB) {
  313. block.setParent(null);
  314. block.bumpNeighbours_();
  315. }
  316. }
  317. }
  318. this.prevBlocks_[0] = blockA;
  319. this.prevBlocks_[1] = blockB;
  320. }
  321. };
  322. Blockly.Blocks['logic_operation'] = {
  323. /**
  324. * Block for logical operations: 'and', 'or'.
  325. * @this Blockly.Block
  326. */
  327. init: function() {
  328. var OPERATORS =
  329. [[Blockly.Msg.LOGIC_OPERATION_AND, 'AND'],
  330. [Blockly.Msg.LOGIC_OPERATION_OR, 'OR']];
  331. this.setHelpUrl(Blockly.Msg.LOGIC_OPERATION_HELPURL);
  332. this.setColour(Blockscad.Toolbox.HEX_LOGIC);
  333. this.setOutput(true, 'Boolean');
  334. this.appendValueInput('A')
  335. .setCheck('Boolean');
  336. this.appendValueInput('B')
  337. .setCheck('Boolean')
  338. .appendField(new Blockly.FieldDropdown(OPERATORS), 'OP');
  339. this.setInputsInline(true);
  340. // Assign 'this' to a variable for use in the tooltip closure below.
  341. var thisBlock = this;
  342. this.setTooltip(function() {
  343. var op = thisBlock.getFieldValue('OP');
  344. var TOOLTIPS = {
  345. 'AND': Blockly.Msg.LOGIC_OPERATION_TOOLTIP_AND,
  346. 'OR': Blockly.Msg.LOGIC_OPERATION_TOOLTIP_OR
  347. };
  348. return TOOLTIPS[op];
  349. });
  350. }
  351. };
  352. Blockly.Blocks['logic_negate'] = {
  353. /**
  354. * Block for negation.
  355. * @this Blockly.Block
  356. */
  357. init: function() {
  358. this.setHelpUrl(Blockly.Msg.LOGIC_NEGATE_HELPURL);
  359. this.setColour(Blockscad.Toolbox.HEX_LOGIC);
  360. this.setOutput(true, 'Boolean');
  361. this.interpolateMsg(Blockly.Msg.LOGIC_NEGATE_TITLE,
  362. ['BOOL', 'Boolean', Blockly.ALIGN_RIGHT],
  363. Blockly.ALIGN_RIGHT);
  364. this.setTooltip(Blockly.Msg.LOGIC_NEGATE_TOOLTIP);
  365. }
  366. };
  367. Blockly.Blocks['logic_boolean'] = {
  368. /**
  369. * Block for boolean data type: true and false.
  370. * @this Blockly.Block
  371. */
  372. init: function() {
  373. var BOOLEANS =
  374. [[Blockly.Msg.LOGIC_BOOLEAN_TRUE, 'TRUE'],
  375. [Blockly.Msg.LOGIC_BOOLEAN_FALSE, 'FALSE']];
  376. this.setHelpUrl(Blockly.Msg.LOGIC_BOOLEAN_HELPURL);
  377. this.setColour(Blockscad.Toolbox.HEX_LOGIC);
  378. this.setOutput(true, 'Boolean');
  379. this.appendDummyInput()
  380. .appendField(new Blockly.FieldDropdown(BOOLEANS), 'BOOL');
  381. this.setTooltip(Blockly.Msg.LOGIC_BOOLEAN_TOOLTIP);
  382. }
  383. };
  384. Blockly.Blocks['logic_null'] = {
  385. /**
  386. * Block for null data type.
  387. * @this Blockly.Block
  388. */
  389. init: function() {
  390. this.setHelpUrl(Blockly.Msg.LOGIC_NULL_HELPURL);
  391. this.setColour(Blockscad.Toolbox.HEX_LOGIC);
  392. this.setOutput(true,'Boolean');
  393. this.appendDummyInput()
  394. .appendField(Blockly.Msg.LOGIC_NULL);
  395. this.setTooltip(Blockly.Msg.LOGIC_NULL_TOOLTIP);
  396. }
  397. };
  398. Blockly.Blocks['logic_ternary'] = {
  399. /**
  400. * Block for ternary operator.
  401. * @this Blockly.Block
  402. */
  403. init: function() {
  404. this.setHelpUrl(Blockly.Msg.LOGIC_TERNARY_HELPURL);
  405. this.setColour(Blockscad.Toolbox.HEX_LOGIC);
  406. this.appendValueInput('IF')
  407. .setCheck('Boolean')
  408. .appendField(Blockly.Msg.LOGIC_TERNARY_CONDITION);
  409. this.appendValueInput('THEN')
  410. // .setCheck('Number')
  411. .appendField(Blockly.Msg.LOGIC_TERNARY_IF_TRUE);
  412. this.appendValueInput('ELSE')
  413. // .setCheck('Number')
  414. .appendField(Blockly.Msg.LOGIC_TERNARY_IF_FALSE);
  415. this.setOutput(true,'Number');
  416. this.setTooltip(Blockly.Msg.LOGIC_TERNARY_TOOLTIP);
  417. }
  418. };