mirror of
https://github.com/google/blockly.git
synced 2026-01-07 00:50:27 +01:00
Previously it was always bumping the first block added even if you are putting the new block over the first block, so it bumped the new block as well.
429 lines
14 KiB
JavaScript
429 lines
14 KiB
JavaScript
/**
|
|
* @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');
|
|
|
|
|
|
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);
|
|
this.elseCount_ = parseInt(xmlElement.getAttribute('else'), 10);
|
|
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);
|
|
}
|
|
},
|
|
/**
|
|
* 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 = Blockly.Block.obtain(workspace, 'controls_if_if');
|
|
containerBlock.initSvg();
|
|
var connection = containerBlock.getInput('STACK').connection;
|
|
for (var i = 1; i <= this.elseifCount_; i++) {
|
|
var elseifBlock = Blockly.Block.obtain(workspace, 'controls_if_elseif');
|
|
elseifBlock.initSvg();
|
|
connection.connect(elseifBlock.previousConnection);
|
|
connection = elseifBlock.nextConnection;
|
|
}
|
|
if (this.elseCount_) {
|
|
var elseBlock = Blockly.Block.obtain(workspace, '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) {
|
|
// Disconnect the else input blocks and remove the inputs.
|
|
if (this.elseCount_) {
|
|
this.removeInput('ELSE');
|
|
}
|
|
this.elseCount_ = 0;
|
|
// Disconnect all the elseif input blocks and remove the inputs.
|
|
for (var i = this.elseifCount_; i > 0; i--) {
|
|
this.removeInput('IF' + i);
|
|
this.removeInput('DO' + i);
|
|
}
|
|
this.elseifCount_ = 0;
|
|
// Rebuild the block's optional inputs.
|
|
var clauseBlock = containerBlock.getInputTargetBlock('STACK');
|
|
while (clauseBlock) {
|
|
switch (clauseBlock.type) {
|
|
case 'controls_if_elseif':
|
|
this.elseifCount_++;
|
|
var ifInput = this.appendValueInput('IF' + this.elseifCount_)
|
|
.setCheck('Boolean')
|
|
.appendField(Blockly.Msg.CONTROLS_IF_MSG_ELSEIF);
|
|
var doInput = this.appendStatementInput('DO' + this.elseifCount_);
|
|
doInput.appendField(Blockly.Msg.CONTROLS_IF_MSG_THEN);
|
|
// Reconnect any child blocks.
|
|
if (clauseBlock.valueConnection_) {
|
|
ifInput.connection.connect(clauseBlock.valueConnection_);
|
|
}
|
|
if (clauseBlock.statementConnection_) {
|
|
doInput.connection.connect(clauseBlock.statementConnection_);
|
|
}
|
|
break;
|
|
case 'controls_if_else':
|
|
this.elseCount_++;
|
|
var elseInput = this.appendStatementInput('ELSE');
|
|
elseInput.appendField(Blockly.Msg.CONTROLS_IF_MSG_ELSE);
|
|
// Reconnect any child blocks.
|
|
if (clauseBlock.statementConnection_) {
|
|
elseInput.connection.connect(clauseBlock.statementConnection_);
|
|
}
|
|
break;
|
|
default:
|
|
throw 'Unknown block type.';
|
|
}
|
|
clauseBlock = clauseBlock.nextConnection &&
|
|
clauseBlock.nextConnection.targetBlock();
|
|
}
|
|
},
|
|
/**
|
|
* Store pointers to any connected child blocks.
|
|
* @param {!Blockly.Block} containerBlock Root block in mutator.
|
|
* @this Blockly.Block
|
|
*/
|
|
saveConnections: function(containerBlock) {
|
|
var clauseBlock = containerBlock.getInputTargetBlock('STACK');
|
|
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();
|
|
}
|
|
}
|
|
};
|
|
|
|
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.appendStatementInput('STACK');
|
|
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 OPERATORS = Blockly.RTL ? [
|
|
['=', 'EQ'],
|
|
['\u2260', 'NEQ'],
|
|
['>', 'LT'],
|
|
['\u2265', 'LTE'],
|
|
['<', 'GT'],
|
|
['\u2264', 'GTE']
|
|
] : [
|
|
['=', 'EQ'],
|
|
['\u2260', 'NEQ'],
|
|
['<', 'LT'],
|
|
['\u2264', 'LTE'],
|
|
['>', 'GT'],
|
|
['\u2265', 'GTE']
|
|
];
|
|
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];
|
|
});
|
|
},
|
|
/**
|
|
* Called whenever anything on the workspace changes.
|
|
* Prevent mismatched types from being compared.
|
|
* @this Blockly.Block
|
|
*/
|
|
onchange: function() {
|
|
if (!this.workspace) {
|
|
// Block has been deleted.
|
|
return;
|
|
}
|
|
var blockA = this.getInputTargetBlock('A');
|
|
var blockB = this.getInputTargetBlock('B');
|
|
// Kick blocks that existed prior to this change if they don't match
|
|
if (this.blocks_ && blockA && blockB &&
|
|
!blockA.outputConnection.checkType_(blockB.outputConnection)) {
|
|
// Mismatch between two inputs. Disconnect previous and bump it away.
|
|
goog.array.forEach(this.blocks_, function(e) {
|
|
if (e === blockA || e === blockB) {
|
|
e.setParent(null);
|
|
e.bumpNeighbours_();
|
|
}
|
|
});
|
|
}
|
|
this.blocks_ = [blockA, 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.setHelpUrl(Blockly.Msg.LOGIC_NEGATE_HELPURL);
|
|
this.setColour(Blockly.Blocks.logic.HUE);
|
|
this.setOutput(true, 'Boolean');
|
|
this.interpolateMsg(Blockly.Msg.LOGIC_NEGATE_TITLE,
|
|
['BOOL', 'Boolean', Blockly.ALIGN_RIGHT],
|
|
Blockly.ALIGN_RIGHT);
|
|
this.setTooltip(Blockly.Msg.LOGIC_NEGATE_TOOLTIP);
|
|
}
|
|
};
|
|
|
|
Blockly.Blocks['logic_boolean'] = {
|
|
/**
|
|
* Block for boolean data type: true and false.
|
|
* @this Blockly.Block
|
|
*/
|
|
init: function() {
|
|
var BOOLEANS =
|
|
[[Blockly.Msg.LOGIC_BOOLEAN_TRUE, 'TRUE'],
|
|
[Blockly.Msg.LOGIC_BOOLEAN_FALSE, 'FALSE']];
|
|
this.setHelpUrl(Blockly.Msg.LOGIC_BOOLEAN_HELPURL);
|
|
this.setColour(Blockly.Blocks.logic.HUE);
|
|
this.setOutput(true, 'Boolean');
|
|
this.appendDummyInput()
|
|
.appendField(new Blockly.FieldDropdown(BOOLEANS), 'BOOL');
|
|
this.setTooltip(Blockly.Msg.LOGIC_BOOLEAN_TOOLTIP);
|
|
}
|
|
};
|
|
|
|
Blockly.Blocks['logic_null'] = {
|
|
/**
|
|
* Block for null data type.
|
|
* @this Blockly.Block
|
|
*/
|
|
init: function() {
|
|
this.setHelpUrl(Blockly.Msg.LOGIC_NULL_HELPURL);
|
|
this.setColour(Blockly.Blocks.logic.HUE);
|
|
this.setOutput(true);
|
|
this.appendDummyInput()
|
|
.appendField(Blockly.Msg.LOGIC_NULL);
|
|
this.setTooltip(Blockly.Msg.LOGIC_NULL_TOOLTIP);
|
|
}
|
|
};
|
|
|
|
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);
|
|
}
|
|
};
|