blockly/apps/blockfactory/blocks.js
/**
* Blockly Apps: Block Factory Blocks
*
* Copyright 2012 Google Inc.
* https://blockly.googlecode.com/
*
* 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 Blocks for Blockly's Block Factory application.
* @author fraser@google.com (Neil Fraser)
*/
'use strict';
Blockly.Blocks['factory_base'] = {
// Base of new block.
init: function() {
this.setColour(120);
this.appendDummyInput()
.appendField('name')
.appendField(new Blockly.FieldTextInput('math_foo'), 'NAME');
this.appendStatementInput('INPUTS')
.setCheck('Input')
.appendField('inputs');
var dropdown = new Blockly.FieldDropdown([
['external inputs', 'EXT'],
['inline inputs', 'INT']]);
this.appendDummyInput()
.appendField(dropdown, 'INLINE');
dropdown = new Blockly.FieldDropdown([
['no connections', 'NONE'],
['left output', 'LEFT'],
['top+bottom connections', 'BOTH'],
['top connection', 'TOP'],
['bottom connection', 'BOTTOM']],
function(option) {
var block = this.sourceBlock_;
var outputExists = block.getInput('OUTPUTTYPE');
var topExists = block.getInput('TOPTYPE');
var bottomExists = block.getInput('BOTTOMTYPE');
if (option == 'LEFT') {
if (!outputExists) {
block.appendValueInput('OUTPUTTYPE')
.setCheck('Type')
.appendField('output type');
block.moveInputBefore('OUTPUTTYPE', 'COLOUR');
}
} else if (outputExists) {
block.removeInput('OUTPUTTYPE');
}
if (option == 'TOP' || option == 'BOTH') {
if (!topExists) {
block.appendValueInput('TOPTYPE')
.setCheck('Type')
.appendField('top type');
block.moveInputBefore('TOPTYPE', 'COLOUR');
}
} else if (topExists) {
block.removeInput('TOPTYPE');
}
if (option == 'BOTTOM' || option == 'BOTH') {
if (!bottomExists) {
block.appendValueInput('BOTTOMTYPE')
.setCheck('Type')
.appendField('bottom type');
block.moveInputBefore('BOTTOMTYPE', 'COLOUR');
}
} else if (bottomExists) {
block.removeInput('BOTTOMTYPE');
}
});
this.appendDummyInput()
.appendField(dropdown, 'CONNECTIONS');
this.appendValueInput('COLOUR')
.setCheck('Colour')
.appendField('colour');
/*
this.appendValueInput('TOOLTIP')
.setCheck('String')
.appendField('tooltip');
this.appendValueInput('HELP')
.setCheck('String')
.appendField('help url');
*/
this.setTooltip('Build a custom block by plugging\n' +
'fields, inputs and other blocks here.');
}
};
var ALIGNMENT_OPTIONS =
[['left', 'LEFT'], ['right', 'RIGHT'], ['centre', 'CENTRE']];
Blockly.Blocks['input_value'] = {
// Value input.
init: function() {
this.setColour(210);
this.appendDummyInput()
.appendField('value input')
.appendField(new Blockly.FieldTextInput('NAME'), 'INPUTNAME');
this.appendStatementInput('FIELDS')
.setCheck('Field')
.appendField('fields')
.appendField(new Blockly.FieldDropdown(ALIGNMENT_OPTIONS), 'ALIGN');
this.appendValueInput('TYPE')
.setCheck('Type')
.setAlign(Blockly.ALIGN_RIGHT)
.appendField('type');
this.setPreviousStatement(true, 'Input');
this.setNextStatement(true, 'Input');
this.setTooltip('A value socket for horizontal connections.');
},
onchange: function() {
if (!this.workspace) {
// Block has been deleted.
return;
}
inputNameCheck(this);
}
};
Blockly.Blocks['input_statement'] = {
// Statement input.
init: function() {
this.setColour(210);
this.appendDummyInput()
.appendField('statement input')
.appendField(new Blockly.FieldTextInput('NAME'), 'INPUTNAME');
this.appendStatementInput('FIELDS')
.setCheck('Field')
.appendField('fields')
.appendField(new Blockly.FieldDropdown(ALIGNMENT_OPTIONS), 'ALIGN');
this.appendValueInput('TYPE')
.setCheck('Type')
.setAlign(Blockly.ALIGN_RIGHT)
.appendField('type');
this.setPreviousStatement(true, 'Input');
this.setNextStatement(true, 'Input');
this.setTooltip('A statement socket for enclosed vertical stacks.');
},
onchange: function() {
if (!this.workspace) {
// Block has been deleted.
return;
}
inputNameCheck(this);
}
};
Blockly.Blocks['input_dummy'] = {
// Dummy input.
init: function() {
this.setColour(210);
this.appendDummyInput()
.appendField('dummy input');
this.appendStatementInput('FIELDS')
.setCheck('Field')
.appendField('fields')
.appendField(new Blockly.FieldDropdown(ALIGNMENT_OPTIONS), 'ALIGN');
this.setPreviousStatement(true, 'Input');
this.setNextStatement(true, 'Input');
this.setTooltip('For adding fields on a separate\n' +
'row with no connections.\n' +
'Alignment options (left, right, centre)\n' +
'apply only to multi-line fields.');
}
};
Blockly.Blocks['field_static'] = {
// Text value.
init: function() {
this.setColour(160);
this.appendDummyInput()
.appendField('text')
.appendField(new Blockly.FieldTextInput(''), 'TEXT');
this.setPreviousStatement(true, 'Field');
this.setNextStatement(true, 'Field');
this.setTooltip('Static text that serves as a label.');
}
};
Blockly.Blocks['field_input'] = {
// Text input.
init: function() {
this.setColour(160);
this.appendDummyInput()
.appendField('text input')
.appendField(new Blockly.FieldTextInput('default'), 'TEXT')
.appendField(',')
.appendField(new Blockly.FieldTextInput('NAME'), 'FIELDNAME');
this.setPreviousStatement(true, 'Field');
this.setNextStatement(true, 'Field');
this.setTooltip('An input field for the user to enter text.');
},
onchange: function() {
if (!this.workspace) {
// Block has been deleted.
return;
}
fieldNameCheck(this);
}
};
Blockly.Blocks['field_angle'] = {
// Angle input.
init: function() {
this.setColour(160);
this.appendDummyInput()
.appendField('angle input')
.appendField(new Blockly.FieldAngle('90'), 'ANGLE')
.appendField(',')
.appendField(new Blockly.FieldTextInput('NAME'), 'FIELDNAME');
this.setPreviousStatement(true, 'Field');
this.setNextStatement(true, 'Field');
this.setTooltip('An input field for the user to enter an angle.');
},
onchange: function() {
if (!this.workspace) {
// Block has been deleted.
return;
}
fieldNameCheck(this);
}
};
Blockly.Blocks['field_dropdown'] = {
// Dropdown menu.
init: function() {
this.setColour(160);
this.appendDummyInput()
.appendField('dropdown')
.appendField(new Blockly.FieldTextInput('NAME'), 'FIELDNAME');
this.appendDummyInput('OPTION0')
.appendField(new Blockly.FieldTextInput('option'), 'USER0')
.appendField(',')
.appendField(new Blockly.FieldTextInput('OPTIONNAME'), 'CPU0');
this.appendDummyInput('OPTION1')
.appendField(new Blockly.FieldTextInput('option'), 'USER1')
.appendField(',')
.appendField(new Blockly.FieldTextInput('OPTIONNAME'), 'CPU1');
this.appendDummyInput('OPTION2')
.appendField(new Blockly.FieldTextInput('option'), 'USER2')
.appendField(',')
.appendField(new Blockly.FieldTextInput('OPTIONNAME'), 'CPU2');
this.setPreviousStatement(true, 'Field');
this.setNextStatement(true, 'Field');
this.setMutator(new Blockly.Mutator(['field_dropdown_option']));
this.setTooltip('Dropdown menu with a list of options.');
this.optionCount_ = 3;
},
mutationToDom: function(workspace) {
var container = document.createElement('mutation');
container.setAttribute('options', this.optionCount_);
return container;
},
domToMutation: function(container) {
for (var x = 0; x < this.optionCount_; x++) {
this.removeInput('OPTION' + x);
}
this.optionCount_ = parseInt(container.getAttribute('options'), 10);
for (var x = 0; x < this.optionCount_; x++) {
var input = this.appendDummyInput('OPTION' + x);
input.appendField(new Blockly.FieldTextInput('option'), 'USER' + x);
input.appendField(',');
input.appendField(new Blockly.FieldTextInput('OPTIONNAME'), 'CPU' + x);
}
},
decompose: function(workspace) {
var containerBlock =
Blockly.Block.obtain(workspace, 'field_dropdown_container');
containerBlock.initSvg();
var connection = containerBlock.getInput('STACK').connection;
for (var x = 0; x < this.optionCount_; x++) {
var optionBlock =
Blockly.Block.obtain(workspace, 'field_dropdown_option');
optionBlock.initSvg();
connection.connect(optionBlock.previousConnection);
connection = optionBlock.nextConnection;
}
return containerBlock;
},
compose: function(containerBlock) {
// Disconnect all input blocks and remove all inputs.
for (var x = this.optionCount_ - 1; x >= 0; x--) {
this.removeInput('OPTION' + x);
}
this.optionCount_ = 0;
// Rebuild the block's inputs.
var optionBlock = containerBlock.getInputTargetBlock('STACK');
while (optionBlock) {
this.appendDummyInput('OPTION' + this.optionCount_)
.appendField(new Blockly.FieldTextInput(
optionBlock.userData_ || 'option'), 'USER' + this.optionCount_)
.appendField(',')
.appendField(new Blockly.FieldTextInput(
optionBlock.cpuData_ || 'OPTIONNAME'), 'CPU' + this.optionCount_);
this.optionCount_++;
optionBlock = optionBlock.nextConnection &&
optionBlock.nextConnection.targetBlock();
}
},
saveConnections: function(containerBlock) {
// Store names and values for each option.
var optionBlock = containerBlock.getInputTargetBlock('STACK');
var x = 0;
while (optionBlock) {
optionBlock.userData_ = this.getFieldValue('USER' + x);
optionBlock.cpuData_ = this.getFieldValue('CPU' + x);
x++;
optionBlock = optionBlock.nextConnection &&
optionBlock.nextConnection.targetBlock();
}
},
onchange: function() {
if (!this.workspace) {
// Block has been deleted.
return;
}
if (this.optionCount_ < 1) {
this.setWarningText('Drop down menu must\nhave at least one option.');
} else {
fieldNameCheck(this);
}
}
};
Blockly.Blocks['field_dropdown_container'] = {
// Container.
init: function() {
this.setColour(160);
this.appendDummyInput()
.appendField('add options');
this.appendStatementInput('STACK');
this.setTooltip('Add, remove, or reorder options\n' +
'to reconfigure this dropdown menu.');
this.contextMenu = false;
}
};
Blockly.Blocks['field_dropdown_option'] = {
// Add option.
init: function() {
this.setColour(160);
this.appendDummyInput()
.appendField('option');
this.setPreviousStatement(true);
this.setNextStatement(true);
this.setTooltip('Add a new option to the dropdown menu.');
this.contextMenu = false;
}
};
Blockly.Blocks['field_checkbox'] = {
// Checkbox.
init: function() {
this.setColour(160);
this.appendDummyInput()
.appendField('checkbox')
.appendField(new Blockly.FieldCheckbox('TRUE'), 'CHECKED')
.appendField(',')
.appendField(new Blockly.FieldTextInput('NAME'), 'FIELDNAME');
this.setPreviousStatement(true, 'Field');
this.setNextStatement(true, 'Field');
this.setTooltip('Checkbox field.');
},
onchange: function() {
if (!this.workspace) {
// Block has been deleted.
return;
}
fieldNameCheck(this);
}
};
Blockly.Blocks['field_colour'] = {
// Colour input.
init: function() {
this.setColour(160);
this.appendDummyInput()
.appendField('colour')
.appendField(new Blockly.FieldColour('#ff0000'), 'COLOUR')
.appendField(',')
.appendField(new Blockly.FieldTextInput('NAME'), 'FIELDNAME');
this.setPreviousStatement(true, 'Field');
this.setNextStatement(true, 'Field');
this.setTooltip('Colour input field.');
},
onchange: function() {
if (!this.workspace) {
// Block has been deleted.
return;
}
fieldNameCheck(this);
}
};
Blockly.Blocks['field_variable'] = {
// Dropdown for variables.
init: function() {
this.setColour(160);
this.appendDummyInput()
.appendField('variable')
.appendField(new Blockly.FieldTextInput('item'), 'TEXT')
.appendField(',')
.appendField(new Blockly.FieldTextInput('NAME'), 'FIELDNAME');
this.setPreviousStatement(true, 'Field');
this.setNextStatement(true, 'Field');
this.setTooltip('Dropdown menu for variable names.');
},
onchange: function() {
if (!this.workspace) {
// Block has been deleted.
return;
}
fieldNameCheck(this);
}
};
Blockly.Blocks['field_image'] = {
// Image.
init: function() {
this.setColour(160);
var src = 'http://www.gstatic.com/codesite/ph/images/star_on.gif';
this.appendDummyInput()
.appendField('image')
.appendField(new Blockly.FieldTextInput(src), 'SRC');
this.appendDummyInput()
.appendField('width')
.appendField(new Blockly.FieldTextInput('15',
Blockly.FieldTextInput.numberValidator), 'WIDTH')
.appendField('height')
.appendField(new Blockly.FieldTextInput('15',
Blockly.FieldTextInput.numberValidator), 'HEIGHT')
.appendField('alt text')
.appendField(new Blockly.FieldTextInput('*'), 'ALT');
this.setPreviousStatement(true, 'Field');
this.setNextStatement(true, 'Field');
this.setTooltip('Static image (JPEG, PNG, GIF, SVG, BMP).\n' +
'Retains aspect ratio regardless of height and width.\n' +
'Alt text is for when collapsed.');
}
};
Blockly.Blocks['type_group'] = {
// Group of types.
init: function() {
this.setColour(230);
this.appendValueInput('TYPE0')
.setCheck('Type')
.appendField('any of');
this.appendValueInput('TYPE1')
.setCheck('Type');
this.setOutput(true, 'Type');
this.setMutator(new Blockly.Mutator(['type_group_item']));
this.setTooltip('Allows more than one type to be accepted.');
this.typeCount_ = 2;
},
mutationToDom: function(workspace) {
var container = document.createElement('mutation');
container.setAttribute('types', this.typeCount_);
return container;
},
domToMutation: function(container) {
for (var x = 0; x < this.typeCount_; x++) {
this.removeInput('TYPE' + x);
}
this.typeCount_ = parseInt(container.getAttribute('types'), 10);
for (var x = 0; x < this.typeCount_; x++) {
var input = this.appendValueInput('TYPE' + x)
.setCheck('Type');
if (x == 0) {
input.appendField('any of');
}
}
},
decompose: function(workspace) {
var containerBlock =
Blockly.Block.obtain(workspace, 'type_group_container');
containerBlock.initSvg();
var connection = containerBlock.getInput('STACK').connection;
for (var x = 0; x < this.typeCount_; x++) {
var typeBlock = Blockly.Block.obtain(workspace, 'type_group_item');
typeBlock.initSvg();
connection.connect(typeBlock.previousConnection);
connection = typeBlock.nextConnection;
}
return containerBlock;
},
compose: function(containerBlock) {
// Disconnect all input blocks and remove all inputs.
for (var x = this.typeCount_ - 1; x >= 0; x--) {
this.removeInput('TYPE' + x);
}
this.typeCount_ = 0;
// Rebuild the block's inputs.
var typeBlock = containerBlock.getInputTargetBlock('STACK');
while (typeBlock) {
var input = this.appendValueInput('TYPE' + this.typeCount_)
.setCheck('Type');
if (this.typeCount_ == 0) {
input.appendField('any of');
}
// Reconnect any child blocks.
if (typeBlock.valueConnection_) {
input.connection.connect(typeBlock.valueConnection_);
}
this.typeCount_++;
typeBlock = typeBlock.nextConnection &&
typeBlock.nextConnection.targetBlock();
}
},
saveConnections: function(containerBlock) {
// Store a pointer to any connected child blocks.
var typeBlock = containerBlock.getInputTargetBlock('STACK');
var x = 0;
while (typeBlock) {
var input = this.getInput('TYPE' + x);
typeBlock.valueConnection_ = input && input.connection.targetConnection;
x++;
typeBlock = typeBlock.nextConnection &&
typeBlock.nextConnection.targetBlock();
}
}
};
Blockly.Blocks['type_group_container'] = {
// Container.
init: function() {
this.setColour(230);
this.appendDummyInput()
.appendField('add types');
this.appendStatementInput('STACK');
this.setTooltip('Add, or remove allowed type.');
this.contextMenu = false;
}
};
Blockly.Blocks['type_group_item'] = {
// Add type.
init: function() {
this.setColour(230);
this.appendDummyInput()
.appendField('type');
this.setPreviousStatement(true);
this.setNextStatement(true);
this.setTooltip('Add a new allowed type.');
this.contextMenu = false;
}
};
Blockly.Blocks['type_null'] = {
// Null type.
valueType: 'null',
init: function() {
this.setColour(230);
this.appendDummyInput()
.appendField('any');
this.setOutput(true, 'Type');
this.setTooltip('Any type is allowed.');
}
};
Blockly.Blocks['type_boolean'] = {
// Boolean type.
valueType: 'Boolean',
init: function() {
this.setColour(230);
this.appendDummyInput()
.appendField('boolean');
this.setOutput(true, 'Type');
this.setTooltip('Booleans (true/false) are allowed.');
}
};
Blockly.Blocks['type_number'] = {
// Number type.
valueType: 'Number',
init: function() {
this.setColour(230);
this.appendDummyInput()
.appendField('number');
this.setOutput(true, 'Type');
this.setTooltip('Numbers (int/float) are allowed.');
}
};
Blockly.Blocks['type_string'] = {
// String type.
valueType: 'String',
init: function() {
this.setColour(230);
this.appendDummyInput()
.appendField('string');
this.setOutput(true, 'Type');
this.setTooltip('Strings (text) are allowed.');
}
};
Blockly.Blocks['type_list'] = {
// List type.
valueType: 'Array',
init: function() {
this.setColour(230);
this.appendDummyInput()
.appendField('list');
this.setOutput(true, 'Type');
this.setTooltip('Arrays (lists) are allowed.');
}
};
Blockly.Blocks['type_other'] = {
// Other type.
init: function() {
this.setColour(230);
this.appendDummyInput()
.appendField('other')
.appendField(new Blockly.FieldTextInput(''), 'TYPE');
this.setOutput(true, 'Type');
this.setTooltip('Custom type to allow.');
}
};
Blockly.Blocks['colour_hue'] = {
// Set the colour of the block.
init: function() {
this.appendDummyInput()
.appendField('hue:')
.appendField(new Blockly.FieldAngle('0', this.validator), 'HUE');
this.setOutput(true, 'Colour');
this.setTooltip('Paint the block with this colour.');
},
validator: function(text) {
// Update the current block's colour to match.
this.sourceBlock_.setColour(text);
}
};
/**
* Check to see if more than one field has this name.
* Highly inefficient (On^2), but n is small.
* @param {!Blockly.Block} referenceBlock Block to check.
*/
function fieldNameCheck(referenceBlock) {
var name = referenceBlock.getFieldValue('FIELDNAME').toLowerCase();
var count = 0;
var blocks = referenceBlock.workspace.getAllBlocks();
for (var x = 0, block; block = blocks[x]; x++) {
var otherName = block.getFieldValue('FIELDNAME');
if (!block.disabled && !block.getInheritedDisabled() &&
otherName && otherName.toLowerCase() == name) {
count++;
}
}
var msg = (count > 1) ?
'There are ' + count + ' field blocks\n with this name.' : null;
referenceBlock.setWarningText(msg);
}
/**
* Check to see if more than one input has this name.
* Highly inefficient (On^2), but n is small.
* @param {!Blockly.Block} referenceBlock Block to check.
*/
function inputNameCheck(referenceBlock) {
var name = referenceBlock.getFieldValue('INPUTNAME').toLowerCase();
var count = 0;
var blocks = referenceBlock.workspace.getAllBlocks();
for (var x = 0, block; block = blocks[x]; x++) {
var otherName = block.getFieldValue('INPUTNAME');
if (!block.disabled && !block.getInheritedDisabled() &&
otherName && otherName.toLowerCase() == name) {
count++;
}
}
var msg = (count > 1) ?
'There are ' + count + ' input blocks\n with this name.' : null;
referenceBlock.setWarningText(msg);
}