summaryrefslogtreecommitdiff
path: root/blockly/blocks/logic.js
diff options
context:
space:
mode:
Diffstat (limited to 'blockly/blocks/logic.js')
-rw-r--r--blockly/blocks/logic.js488
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;
+ }
+};