diff options
Diffstat (limited to 'blockly/blocks/logic.js')
-rw-r--r-- | blockly/blocks/logic.js | 488 |
1 files changed, 488 insertions, 0 deletions
diff --git a/blockly/blocks/logic.js b/blockly/blocks/logic.js new file mode 100644 index 0000000..57af601 --- /dev/null +++ b/blockly/blocks/logic.js @@ -0,0 +1,488 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2012 Google Inc. + * https://developers.google.com/blockly/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Logic blocks for Blockly. + * @author q.neutron@gmail.com (Quynh Neutron) + */ +'use strict'; + +goog.provide('Blockly.Blocks.logic'); + +goog.require('Blockly.Blocks'); + + +/** + * Common HSV hue for all blocks in this category. + */ +Blockly.Blocks.logic.HUE = 210; + +Blockly.Blocks['controls_if'] = { + /** + * Block for if/elseif/else condition. + * @this Blockly.Block + */ + init: function() { + this.setHelpUrl(Blockly.Msg.CONTROLS_IF_HELPURL); + this.setColour(Blockly.Blocks.logic.HUE); + this.appendValueInput('IF0') + .setCheck('Boolean') + .appendField(Blockly.Msg.CONTROLS_IF_MSG_IF); + this.appendStatementInput('DO0') + .appendField(Blockly.Msg.CONTROLS_IF_MSG_THEN); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setMutator(new Blockly.Mutator(['controls_if_elseif', + 'controls_if_else'])); + // Assign 'this' to a variable for use in the tooltip closure below. + var thisBlock = this; + this.setTooltip(function() { + if (!thisBlock.elseifCount_ && !thisBlock.elseCount_) { + return Blockly.Msg.CONTROLS_IF_TOOLTIP_1; + } else if (!thisBlock.elseifCount_ && thisBlock.elseCount_) { + return Blockly.Msg.CONTROLS_IF_TOOLTIP_2; + } else if (thisBlock.elseifCount_ && !thisBlock.elseCount_) { + return Blockly.Msg.CONTROLS_IF_TOOLTIP_3; + } else if (thisBlock.elseifCount_ && thisBlock.elseCount_) { + return Blockly.Msg.CONTROLS_IF_TOOLTIP_4; + } + return ''; + }); + this.elseifCount_ = 0; + this.elseCount_ = 0; + }, + /** + * Create XML to represent the number of else-if and else inputs. + * @return {Element} XML storage element. + * @this Blockly.Block + */ + mutationToDom: function() { + if (!this.elseifCount_ && !this.elseCount_) { + return null; + } + var container = document.createElement('mutation'); + if (this.elseifCount_) { + container.setAttribute('elseif', this.elseifCount_); + } + if (this.elseCount_) { + container.setAttribute('else', 1); + } + return container; + }, + /** + * Parse XML to restore the else-if and else inputs. + * @param {!Element} xmlElement XML storage element. + * @this Blockly.Block + */ + domToMutation: function(xmlElement) { + this.elseifCount_ = parseInt(xmlElement.getAttribute('elseif'), 10) || 0; + this.elseCount_ = parseInt(xmlElement.getAttribute('else'), 10) || 0; + this.updateShape_(); + }, + /** + * Populate the mutator's dialog with this block's components. + * @param {!Blockly.Workspace} workspace Mutator's workspace. + * @return {!Blockly.Block} Root block in mutator. + * @this Blockly.Block + */ + decompose: function(workspace) { + var containerBlock = workspace.newBlock('controls_if_if'); + containerBlock.initSvg(); + var connection = containerBlock.nextConnection; + for (var i = 1; i <= this.elseifCount_; i++) { + var elseifBlock = workspace.newBlock('controls_if_elseif'); + elseifBlock.initSvg(); + connection.connect(elseifBlock.previousConnection); + connection = elseifBlock.nextConnection; + } + if (this.elseCount_) { + var elseBlock = workspace.newBlock('controls_if_else'); + elseBlock.initSvg(); + connection.connect(elseBlock.previousConnection); + } + return containerBlock; + }, + /** + * Reconfigure this block based on the mutator dialog's components. + * @param {!Blockly.Block} containerBlock Root block in mutator. + * @this Blockly.Block + */ + compose: function(containerBlock) { + var clauseBlock = containerBlock.nextConnection.targetBlock(); + // Count number of inputs. + this.elseifCount_ = 0; + this.elseCount_ = 0; + var valueConnections = [null]; + var statementConnections = [null]; + var elseStatementConnection = null; + while (clauseBlock) { + switch (clauseBlock.type) { + case 'controls_if_elseif': + this.elseifCount_++; + valueConnections.push(clauseBlock.valueConnection_); + statementConnections.push(clauseBlock.statementConnection_); + break; + case 'controls_if_else': + this.elseCount_++; + elseStatementConnection = clauseBlock.statementConnection_; + break; + default: + throw 'Unknown block type.'; + } + clauseBlock = clauseBlock.nextConnection && + clauseBlock.nextConnection.targetBlock(); + } + this.updateShape_(); + // Reconnect any child blocks. + for (var i = 1; i <= this.elseifCount_; i++) { + Blockly.Mutator.reconnect(valueConnections[i], this, 'IF' + i); + Blockly.Mutator.reconnect(statementConnections[i], this, 'DO' + i); + } + Blockly.Mutator.reconnect(elseStatementConnection, this, 'ELSE'); + }, + /** + * Store pointers to any connected child blocks. + * @param {!Blockly.Block} containerBlock Root block in mutator. + * @this Blockly.Block + */ + saveConnections: function(containerBlock) { + var clauseBlock = containerBlock.nextConnection.targetBlock(); + var i = 1; + while (clauseBlock) { + switch (clauseBlock.type) { + case 'controls_if_elseif': + var inputIf = this.getInput('IF' + i); + var inputDo = this.getInput('DO' + i); + clauseBlock.valueConnection_ = + inputIf && inputIf.connection.targetConnection; + clauseBlock.statementConnection_ = + inputDo && inputDo.connection.targetConnection; + i++; + break; + case 'controls_if_else': + var inputDo = this.getInput('ELSE'); + clauseBlock.statementConnection_ = + inputDo && inputDo.connection.targetConnection; + break; + default: + throw 'Unknown block type.'; + } + clauseBlock = clauseBlock.nextConnection && + clauseBlock.nextConnection.targetBlock(); + } + }, + /** + * Modify this block to have the correct number of inputs. + * @private + * @this Blockly.Block + */ + updateShape_: function() { + // Delete everything. + if (this.getInput('ELSE')) { + this.removeInput('ELSE'); + } + var i = 1; + while (this.getInput('IF' + i)) { + this.removeInput('IF' + i); + this.removeInput('DO' + i); + i++; + } + // Rebuild block. + for (var i = 1; i <= this.elseifCount_; i++) { + this.appendValueInput('IF' + i) + .setCheck('Boolean') + .appendField(Blockly.Msg.CONTROLS_IF_MSG_ELSEIF); + this.appendStatementInput('DO' + i) + .appendField(Blockly.Msg.CONTROLS_IF_MSG_THEN); + } + if (this.elseCount_) { + this.appendStatementInput('ELSE') + .appendField(Blockly.Msg.CONTROLS_IF_MSG_ELSE); + } + } +}; + +Blockly.Blocks['controls_if_if'] = { + /** + * Mutator block for if container. + * @this Blockly.Block + */ + init: function() { + this.setColour(Blockly.Blocks.logic.HUE); + this.appendDummyInput() + .appendField(Blockly.Msg.CONTROLS_IF_IF_TITLE_IF); + this.setNextStatement(true); + this.setTooltip(Blockly.Msg.CONTROLS_IF_IF_TOOLTIP); + this.contextMenu = false; + } +}; + +Blockly.Blocks['controls_if_elseif'] = { + /** + * Mutator bolck for else-if condition. + * @this Blockly.Block + */ + init: function() { + this.setColour(Blockly.Blocks.logic.HUE); + this.appendDummyInput() + .appendField(Blockly.Msg.CONTROLS_IF_ELSEIF_TITLE_ELSEIF); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip(Blockly.Msg.CONTROLS_IF_ELSEIF_TOOLTIP); + this.contextMenu = false; + } +}; + +Blockly.Blocks['controls_if_else'] = { + /** + * Mutator block for else condition. + * @this Blockly.Block + */ + init: function() { + this.setColour(Blockly.Blocks.logic.HUE); + this.appendDummyInput() + .appendField(Blockly.Msg.CONTROLS_IF_ELSE_TITLE_ELSE); + this.setPreviousStatement(true); + this.setTooltip(Blockly.Msg.CONTROLS_IF_ELSE_TOOLTIP); + this.contextMenu = false; + } +}; + +Blockly.Blocks['logic_compare'] = { + /** + * Block for comparison operator. + * @this Blockly.Block + */ + init: function() { + var rtlOperators = [ + ['=', 'EQ'], + ['\u2260', 'NEQ'], + ['\u200F<\u200F', 'LT'], + ['\u200F\u2264\u200F', 'LTE'], + ['\u200F>\u200F', 'GT'], + ['\u200F\u2265\u200F', 'GTE'] + ]; + var ltrOperators = [ + ['=', 'EQ'], + ['\u2260', 'NEQ'], + ['<', 'LT'], + ['\u2264', 'LTE'], + ['>', 'GT'], + ['\u2265', 'GTE'] + ]; + var OPERATORS = this.RTL ? rtlOperators : ltrOperators; + this.setHelpUrl(Blockly.Msg.LOGIC_COMPARE_HELPURL); + this.setColour(Blockly.Blocks.logic.HUE); + this.setOutput(true, 'Boolean'); + this.appendValueInput('A'); + this.appendValueInput('B') + .appendField(new Blockly.FieldDropdown(OPERATORS), 'OP'); + this.setInputsInline(true); + // Assign 'this' to a variable for use in the tooltip closure below. + var thisBlock = this; + this.setTooltip(function() { + var op = thisBlock.getFieldValue('OP'); + var TOOLTIPS = { + 'EQ': Blockly.Msg.LOGIC_COMPARE_TOOLTIP_EQ, + 'NEQ': Blockly.Msg.LOGIC_COMPARE_TOOLTIP_NEQ, + 'LT': Blockly.Msg.LOGIC_COMPARE_TOOLTIP_LT, + 'LTE': Blockly.Msg.LOGIC_COMPARE_TOOLTIP_LTE, + 'GT': Blockly.Msg.LOGIC_COMPARE_TOOLTIP_GT, + 'GTE': Blockly.Msg.LOGIC_COMPARE_TOOLTIP_GTE + }; + return TOOLTIPS[op]; + }); + this.prevBlocks_ = [null, null]; + }, + /** + * Called whenever anything on the workspace changes. + * Prevent mismatched types from being compared. + * @param {!Blockly.Events.Abstract} e Change event. + * @this Blockly.Block + */ + onchange: function(e) { + var blockA = this.getInputTargetBlock('A'); + var blockB = this.getInputTargetBlock('B'); + // Disconnect blocks that existed prior to this change if they don't match. + if (blockA && blockB && + !blockA.outputConnection.checkType_(blockB.outputConnection)) { + // Mismatch between two inputs. Disconnect previous and bump it away. + // Ensure that any disconnections are grouped with the causing event. + Blockly.Events.setGroup(e.group); + for (var i = 0; i < this.prevBlocks_.length; i++) { + var block = this.prevBlocks_[i]; + if (block === blockA || block === blockB) { + block.unplug(); + block.bumpNeighbours_(); + } + } + Blockly.Events.setGroup(false); + } + this.prevBlocks_[0] = blockA; + this.prevBlocks_[1] = blockB; + } +}; + +Blockly.Blocks['logic_operation'] = { + /** + * Block for logical operations: 'and', 'or'. + * @this Blockly.Block + */ + init: function() { + var OPERATORS = + [[Blockly.Msg.LOGIC_OPERATION_AND, 'AND'], + [Blockly.Msg.LOGIC_OPERATION_OR, 'OR']]; + this.setHelpUrl(Blockly.Msg.LOGIC_OPERATION_HELPURL); + this.setColour(Blockly.Blocks.logic.HUE); + this.setOutput(true, 'Boolean'); + this.appendValueInput('A') + .setCheck('Boolean'); + this.appendValueInput('B') + .setCheck('Boolean') + .appendField(new Blockly.FieldDropdown(OPERATORS), 'OP'); + this.setInputsInline(true); + // Assign 'this' to a variable for use in the tooltip closure below. + var thisBlock = this; + this.setTooltip(function() { + var op = thisBlock.getFieldValue('OP'); + var TOOLTIPS = { + 'AND': Blockly.Msg.LOGIC_OPERATION_TOOLTIP_AND, + 'OR': Blockly.Msg.LOGIC_OPERATION_TOOLTIP_OR + }; + return TOOLTIPS[op]; + }); + } +}; + +Blockly.Blocks['logic_negate'] = { + /** + * Block for negation. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": Blockly.Msg.LOGIC_NEGATE_TITLE, + "args0": [ + { + "type": "input_value", + "name": "BOOL", + "check": "Boolean" + } + ], + "output": "Boolean", + "colour": Blockly.Blocks.logic.HUE, + "tooltip": Blockly.Msg.LOGIC_NEGATE_TOOLTIP, + "helpUrl": Blockly.Msg.LOGIC_NEGATE_HELPURL + }); + } +}; + +Blockly.Blocks['logic_boolean'] = { + /** + * Block for boolean data type: true and false. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": "%1", + "args0": [ + { + "type": "field_dropdown", + "name": "BOOL", + "options": [ + [Blockly.Msg.LOGIC_BOOLEAN_TRUE, "TRUE"], + [Blockly.Msg.LOGIC_BOOLEAN_FALSE, "FALSE"] + ] + } + ], + "output": "Boolean", + "colour": Blockly.Blocks.logic.HUE, + "tooltip": Blockly.Msg.LOGIC_BOOLEAN_TOOLTIP, + "helpUrl": Blockly.Msg.LOGIC_BOOLEAN_HELPURL + }); + } +}; + +Blockly.Blocks['logic_null'] = { + /** + * Block for null data type. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": Blockly.Msg.LOGIC_NULL, + "output": null, + "colour": Blockly.Blocks.logic.HUE, + "tooltip": Blockly.Msg.LOGIC_NULL_TOOLTIP, + "helpUrl": Blockly.Msg.LOGIC_NULL_HELPURL + }); + } +}; + +Blockly.Blocks['logic_ternary'] = { + /** + * Block for ternary operator. + * @this Blockly.Block + */ + init: function() { + this.setHelpUrl(Blockly.Msg.LOGIC_TERNARY_HELPURL); + this.setColour(Blockly.Blocks.logic.HUE); + this.appendValueInput('IF') + .setCheck('Boolean') + .appendField(Blockly.Msg.LOGIC_TERNARY_CONDITION); + this.appendValueInput('THEN') + .appendField(Blockly.Msg.LOGIC_TERNARY_IF_TRUE); + this.appendValueInput('ELSE') + .appendField(Blockly.Msg.LOGIC_TERNARY_IF_FALSE); + this.setOutput(true); + this.setTooltip(Blockly.Msg.LOGIC_TERNARY_TOOLTIP); + this.prevParentConnection_ = null; + }, + /** + * Called whenever anything on the workspace changes. + * Prevent mismatched types. + * @param {!Blockly.Events.Abstract} e Change event. + * @this Blockly.Block + */ + onchange: function(e) { + var blockA = this.getInputTargetBlock('THEN'); + var blockB = this.getInputTargetBlock('ELSE'); + var parentConnection = this.outputConnection.targetConnection; + // Disconnect blocks that existed prior to this change if they don't match. + if ((blockA || blockB) && parentConnection) { + for (var i = 0; i < 2; i++) { + var block = (i == 1) ? blockA : blockB; + if (block && !block.outputConnection.checkType_(parentConnection)) { + // Ensure that any disconnections are grouped with the causing event. + Blockly.Events.setGroup(e.group); + if (parentConnection === this.prevParentConnection_) { + this.unplug(); + parentConnection.getSourceBlock().bumpNeighbours_(); + } else { + block.unplug(); + block.bumpNeighbours_(); + } + Blockly.Events.setGroup(false); + } + } + } + this.prevParentConnection_ = parentConnection; + } +}; |