Field Text transformation methods (#2930)
* Add field text transform methods for converting from text to value and vice versa.
@@ -134,6 +134,7 @@ Blockly.FieldAngle.prototype.initView = function() {
|
|||||||
/**
|
/**
|
||||||
* Updates the graph when the field rerenders.
|
* Updates the graph when the field rerenders.
|
||||||
* @private
|
* @private
|
||||||
|
* @override
|
||||||
*/
|
*/
|
||||||
Blockly.FieldAngle.prototype.render_ = function() {
|
Blockly.FieldAngle.prototype.render_ = function() {
|
||||||
Blockly.FieldAngle.superClass_.render_.call(this);
|
Blockly.FieldAngle.superClass_.render_.call(this);
|
||||||
|
|||||||
@@ -56,12 +56,6 @@ Blockly.FieldTextInput = function(opt_value, opt_validator) {
|
|||||||
}
|
}
|
||||||
Blockly.FieldTextInput.superClass_.constructor.call(this, opt_value,
|
Blockly.FieldTextInput.superClass_.constructor.call(this, opt_value,
|
||||||
opt_validator);
|
opt_validator);
|
||||||
/**
|
|
||||||
* A cache of the last value in the html input.
|
|
||||||
* @type {*}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
this.htmlInputValue_ = null;
|
|
||||||
};
|
};
|
||||||
goog.inherits(Blockly.FieldTextInput, Blockly.Field);
|
goog.inherits(Blockly.FieldTextInput, Blockly.Field);
|
||||||
|
|
||||||
@@ -237,10 +231,10 @@ Blockly.FieldTextInput.prototype.showPromptEditor_ = function() {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
Blockly.FieldTextInput.prototype.showInlineEditor_ = function(quietInput) {
|
Blockly.FieldTextInput.prototype.showInlineEditor_ = function(quietInput) {
|
||||||
this.isBeingEdited_ = true;
|
|
||||||
Blockly.WidgetDiv.show(
|
Blockly.WidgetDiv.show(
|
||||||
this, this.sourceBlock_.RTL, this.widgetDispose_.bind(this));
|
this, this.sourceBlock_.RTL, this.widgetDispose_.bind(this));
|
||||||
this.htmlInput_ = this.widgetCreate_();
|
this.htmlInput_ = this.widgetCreate_();
|
||||||
|
this.isBeingEdited_ = true;
|
||||||
|
|
||||||
if (!quietInput) {
|
if (!quietInput) {
|
||||||
this.htmlInput_.focus();
|
this.htmlInput_.focus();
|
||||||
@@ -268,7 +262,7 @@ Blockly.FieldTextInput.prototype.widgetCreate_ = function() {
|
|||||||
htmlInput.style.borderRadius = borderRadius;
|
htmlInput.style.borderRadius = borderRadius;
|
||||||
div.appendChild(htmlInput);
|
div.appendChild(htmlInput);
|
||||||
|
|
||||||
htmlInput.value = htmlInput.defaultValue = String(this.value_);
|
htmlInput.value = htmlInput.defaultValue = this.getEditorText_(this.value_);
|
||||||
htmlInput.untypedDefaultValue_ = this.value_;
|
htmlInput.untypedDefaultValue_ = this.value_;
|
||||||
htmlInput.oldValue_ = null;
|
htmlInput.oldValue_ = null;
|
||||||
if (Blockly.utils.userAgent.GECKO) {
|
if (Blockly.utils.userAgent.GECKO) {
|
||||||
@@ -292,19 +286,11 @@ Blockly.FieldTextInput.prototype.widgetDispose_ = function() {
|
|||||||
// Finalize value.
|
// Finalize value.
|
||||||
this.isBeingEdited_ = false;
|
this.isBeingEdited_ = false;
|
||||||
this.isTextValid_ = true;
|
this.isTextValid_ = true;
|
||||||
// No need to call setValue because if the widget is being closed the
|
|
||||||
// latest input text has already been validated.
|
// Always re-render when the we close the editor as value
|
||||||
if (this.value_ != this.htmlInputValue_) {
|
// set on the field's node may be inconsistent with the field's
|
||||||
// At the end of an edit the text should be the same as the value. It
|
// internal value.
|
||||||
// may not be if the input text is different than the validated text.
|
this.forceRerender();
|
||||||
// There are two scenarios where that is the case:
|
|
||||||
// - The text in the input was invalid.
|
|
||||||
// - The text in the input is different to that returned by a validator.
|
|
||||||
// Re-render to fix that.
|
|
||||||
this.htmlInputValue_ = null;
|
|
||||||
this.forceRerender();
|
|
||||||
}
|
|
||||||
// Otherwise don't rerender.
|
|
||||||
|
|
||||||
// Call onFinishEditing
|
// Call onFinishEditing
|
||||||
// TODO: Get rid of this or make it less of a hack.
|
// TODO: Get rid of this or make it less of a hack.
|
||||||
@@ -384,7 +370,9 @@ Blockly.FieldTextInput.prototype.onHtmlInputChange_ = function(_e) {
|
|||||||
// moved up to the Field setValue method. This would create a
|
// moved up to the Field setValue method. This would create a
|
||||||
// broader fix for all field types.
|
// broader fix for all field types.
|
||||||
Blockly.Events.setGroup(true);
|
Blockly.Events.setGroup(true);
|
||||||
this.setEditorValue_(text);
|
var value = this.getValueFromEditorText_(text);
|
||||||
|
this.setValue(value);
|
||||||
|
this.forceRerender();
|
||||||
Blockly.Events.setGroup(false);
|
Blockly.Events.setGroup(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -397,14 +385,15 @@ Blockly.FieldTextInput.prototype.onHtmlInputChange_ = function(_e) {
|
|||||||
* @protected
|
* @protected
|
||||||
*/
|
*/
|
||||||
Blockly.FieldTextInput.prototype.setEditorValue_ = function(newValue) {
|
Blockly.FieldTextInput.prototype.setEditorValue_ = function(newValue) {
|
||||||
this.setValue(newValue);
|
this.isDirty_ = true;
|
||||||
if (this.isBeingEdited_) {
|
if (this.isBeingEdited_) {
|
||||||
this.htmlInput_.value = newValue;
|
// In the case this method is passed an invalid value, we still
|
||||||
// This cache is stored in order to determine if we must re-render when
|
// pass it through the transformation method `getEditorText` to deal
|
||||||
// disposing of the widget div.
|
// with. Otherwise, the internal field's state will be inconsistent
|
||||||
this.htmlInputValue_ = newValue;
|
// with what's shown to the user.
|
||||||
this.forceRerender();
|
this.htmlInput_.value = this.getEditorText_(newValue);
|
||||||
}
|
}
|
||||||
|
this.setValue(newValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -460,7 +449,7 @@ Blockly.FieldTextInput.numberValidator = function(text) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensure that only a nonnegative integer may be entered.
|
* Ensure that only a non-negative integer may be entered.
|
||||||
* @param {string} text The user's text.
|
* @param {string} text The user's text.
|
||||||
* @return {?string} A string representing a valid int, or null if invalid.
|
* @return {?string} A string representing a valid int, or null if invalid.
|
||||||
* @deprecated
|
* @deprecated
|
||||||
@@ -474,7 +463,7 @@ Blockly.FieldTextInput.nonnegativeIntegerValidator = function(text) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use the `getText_` developer hook to override the field's text represenation.
|
* Use the `getText_` developer hook to override the field's text representation.
|
||||||
* When we're currently editing, return the current html value instead.
|
* When we're currently editing, return the current html value instead.
|
||||||
* Otherwise, return null which tells the field to use the default behaviour
|
* Otherwise, return null which tells the field to use the default behaviour
|
||||||
* (which is a string cast of the field's value).
|
* (which is a string cast of the field's value).
|
||||||
@@ -490,4 +479,31 @@ Blockly.FieldTextInput.prototype.getText_ = function() {
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform the provided value into a text to show in the html input.
|
||||||
|
* Override this method if the field's html input representation is different
|
||||||
|
* than the field's value. This should be coupled with an override of
|
||||||
|
* `getValueFromEditorText_`.
|
||||||
|
* @param {*} value The value stored in this field.
|
||||||
|
* @returns {string} The text to show on the html input.
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
Blockly.FieldTextInput.prototype.getEditorText_ = function(value) {
|
||||||
|
return String(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform the text received from the html input into a value to store
|
||||||
|
* in this field.
|
||||||
|
* Override this method if the field's html input representation is different
|
||||||
|
* than the field's value. This should be coupled with an override of
|
||||||
|
* `getEditorText_`.
|
||||||
|
* @param {string} text Text received from the html input.
|
||||||
|
* @returns {*} The value to store.
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
Blockly.FieldTextInput.prototype.getValueFromEditorText_ = function(text) {
|
||||||
|
return text;
|
||||||
|
};
|
||||||
|
|
||||||
Blockly.fieldRegistry.register('field_input', Blockly.FieldTextInput);
|
Blockly.fieldRegistry.register('field_input', Blockly.FieldTextInput);
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 0 B |
@@ -1,153 +1,54 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="utf-8">
|
||||||
<title>Blockly Demo: Custom Fields</title>
|
<title>Blockly Demos</title>
|
||||||
<script src="../../blockly_compressed.js"></script>
|
<style>
|
||||||
<script src="blocks.js"></script>
|
body {
|
||||||
<script src="field_turtle.js"></script>
|
margin: 0 10%;
|
||||||
<script src="../../msg/js/en.js"></script>
|
background-color: #fff;
|
||||||
<link rel="stylesheet" type="text/css" href="custom-fields.css">
|
font-family: sans-serif;
|
||||||
</head>
|
}
|
||||||
<body onload="start()">
|
h1 {
|
||||||
<h1><a href="https://developers.google.com/blockly/">Blockly</a> >
|
font-weight: normal;
|
||||||
<a href="../index.html">Demos</a> > Custom Fields</h1>
|
font-size: 140%;
|
||||||
|
}
|
||||||
|
td {
|
||||||
|
padding: 1ex;
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1><a href="https://developers.google.com/blockly/">Blockly</a> >
|
||||||
|
<a href="../index.html">Demos</a> > Custom Fields</h1>
|
||||||
|
|
||||||
|
<p>These demos are intended for developers who want creating custom block fields.</p>
|
||||||
|
|
||||||
<p>This is a demo of creating custom block fields. In this case the field
|
<table>
|
||||||
is used to define a turtle.
|
<tr>
|
||||||
</p>
|
<td>
|
||||||
|
<a href="turtle/index.html">
|
||||||
|
<img src="turtle/icon.png" height=80 width=100>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div><a href="turtle/index.html">Turtle Field</a></div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
<p>All of the custom field implementation is in
|
<tr>
|
||||||
demos/custom-fields/field_turtle.js, including comments on each required
|
<td>
|
||||||
function.
|
<a href="pitch/index.html">
|
||||||
</p>
|
<img src="pitch/media/notes.png" height=40 width=100>
|
||||||
|
</a>
|
||||||
<p>Click on the blocks' comment icons to learn what they are demonstrating.
|
</td>
|
||||||
Use the buttons below to see how the fields react to changes.
|
<td>
|
||||||
</p>
|
<div><a href="pitch/index.html">Pitch Field</a></div>
|
||||||
|
</td>
|
||||||
<p>
|
</tr>
|
||||||
<input type="button" value="set random style" onclick="setRandomStyle()">
|
</table>
|
||||||
<input type="button" value="toggle shadow" onclick="toggleShadow()">
|
</body>
|
||||||
<input type="button" value="toggle enabled" onclick="toggleEnabled()">
|
|
||||||
<input type="button" value="toggle editable" onclick="toggleEditable()">
|
|
||||||
<input type="button" value="toggle collapsed" onclick="toggleCollapsed()">
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<input type="button" value="Export to XML" onclick="toXml()">
|
|
||||||
<input type="button" value="Import from XML" onclick="fromXml()">
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<textarea id="importExport"
|
|
||||||
style="width: 200px; height: 480px;"
|
|
||||||
onchange="textAreaChange();"
|
|
||||||
onkeyup="textAreaChange()"></textarea>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div id="blocklyDiv" style="width: 600px; height: 480px;"></div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
function toXml() {
|
|
||||||
var output = document.getElementById('importExport');
|
|
||||||
var xml = Blockly.Xml.workspaceToDom(workspace);
|
|
||||||
output.value = Blockly.Xml.domToPrettyText(xml);
|
|
||||||
output.focus();
|
|
||||||
output.select();
|
|
||||||
}
|
|
||||||
|
|
||||||
function fromXml() {
|
|
||||||
var input = document.getElementById('importExport');
|
|
||||||
var xml = Blockly.Xml.textToDom(input.value);
|
|
||||||
Blockly.Xml.domToWorkspace(xml, workspace);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setRandomStyle() {
|
|
||||||
var blocks = workspace.getAllBlocks();
|
|
||||||
var styles = Object.keys(Blockly.getTheme().getAllBlockStyles());
|
|
||||||
styles.splice(styles.indexOf(blocks[0].getStyleName()), 1);
|
|
||||||
var style = styles[Math.floor(Math.random() * styles.length)];
|
|
||||||
for(var i = 0, block; block = blocks[i]; i++) {
|
|
||||||
block.setStyle(style);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleShadow() {
|
|
||||||
var blocks = workspace.getAllBlocks();
|
|
||||||
for(var i = 0, block; block = blocks[i]; i++) {
|
|
||||||
block.setShadow(!block.isShadow());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleEnabled() {
|
|
||||||
var blocks = workspace.getAllBlocks();
|
|
||||||
for(var i = 0, block; block = blocks[i]; i++) {
|
|
||||||
block.setEnabled(!block.isEnabled());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleEditable() {
|
|
||||||
var blocks = workspace.getAllBlocks();
|
|
||||||
for(var i = 0, block; block = blocks[i]; i++) {
|
|
||||||
block.setEditable(!block.isEditable());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleCollapsed() {
|
|
||||||
var blocks = workspace.getAllBlocks();
|
|
||||||
for(var i = 0, block; block = blocks[i]; i++) {
|
|
||||||
block.setCollapsed(!block.isCollapsed());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function appendDom() {
|
|
||||||
var blocks = document.getElementById('workspace-blocks');
|
|
||||||
if (blocks.firstElementChild) {
|
|
||||||
Blockly.Xml.appendDomToWorkspace(blocks, workspace);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function start() {
|
|
||||||
workspace = Blockly.inject('blocklyDiv', options);
|
|
||||||
appendDom();
|
|
||||||
workspace.scrollCenter();
|
|
||||||
}
|
|
||||||
|
|
||||||
var options = {
|
|
||||||
media: '../../media/',
|
|
||||||
grid: {
|
|
||||||
spacing: 25,
|
|
||||||
length: 3,
|
|
||||||
colour: '#ccc'
|
|
||||||
},
|
|
||||||
move: {
|
|
||||||
scrollbars: true,
|
|
||||||
drag: true,
|
|
||||||
wheel: true,
|
|
||||||
},
|
|
||||||
zoom: {
|
|
||||||
controls: true,
|
|
||||||
startScale: 1.0,
|
|
||||||
maxScale: 4,
|
|
||||||
minScale: 0.25,
|
|
||||||
scaleSpeed: 1.1
|
|
||||||
}
|
|
||||||
/*toolbox: document.getElementById('toolbox')*/
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<xml xmlns="https://developers.google.com/blockly/xml" id="workspace-blocks" style="display: none">
|
|
||||||
<block type="turtle_basic"></block>
|
|
||||||
<block type="turtle_nullifier" y="120"></block>
|
|
||||||
<block type="turtle_changer" y="230"></block>
|
|
||||||
</xml>
|
|
||||||
</body>
|
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
33
demos/custom-fields/pitch/blocks.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Visual Blocks Editor
|
||||||
|
*
|
||||||
|
* Copyright 2019 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 Pitch field demo blocks.
|
||||||
|
* @author samelh@gmail.com (Sam El-Husseini)
|
||||||
|
*/
|
||||||
|
|
||||||
|
Blockly.Blocks['test_pitch_field'] = {
|
||||||
|
init: function() {
|
||||||
|
this.appendDummyInput()
|
||||||
|
.appendField('pitch')
|
||||||
|
.appendField(new Blockly.FieldPitch('7'), 'PITCH');
|
||||||
|
this.setStyle('loop_blocks');
|
||||||
|
}
|
||||||
|
};
|
||||||
231
demos/custom-fields/pitch/field_pitch.js
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
/**
|
||||||
|
* Visual Blocks Editor
|
||||||
|
*
|
||||||
|
* Copyright 2016 Google Inc.
|
||||||
|
* https://github.com/google/blockly-games
|
||||||
|
*
|
||||||
|
* 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 Music pitch input field. Borrowed from Blockly Games.
|
||||||
|
* @author fraser@google.com (Neil Fraser)
|
||||||
|
* @author samelh@google.com (Sam El-Husseini)
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
goog.provide('Blockly.FieldPitch');
|
||||||
|
|
||||||
|
goog.require('Blockly.FieldTextInput');
|
||||||
|
goog.require('Blockly.utils.math');
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class for an editable pitch field.
|
||||||
|
* @param {string} text The initial content of the field.
|
||||||
|
* @extends {Blockly.FieldTextInput}
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
Blockly.FieldPitch = function(text) {
|
||||||
|
Blockly.FieldPitch.superClass_.constructor.call(this, text);
|
||||||
|
};
|
||||||
|
goog.inherits(Blockly.FieldPitch, Blockly.FieldTextInput);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a FieldPitch from a JSON arg object.
|
||||||
|
* @param {!Object} options A JSON object with options (pitch).
|
||||||
|
* @return {!Blockly.FieldPitch} The new field instance.
|
||||||
|
* @package
|
||||||
|
* @nocollapse
|
||||||
|
*/
|
||||||
|
Blockly.FieldPitch.fromJson = function(options) {
|
||||||
|
return new Blockly.FieldPitch(options['pitch']);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All notes available for the picker.
|
||||||
|
*/
|
||||||
|
Blockly.FieldPitch.NOTES = 'C3 D3 E3 F3 G3 A3 B3 C4 D4 E4 F4 G4 A4'.split(/ /);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the inline free-text editor on top of the text and the note picker.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
Blockly.FieldPitch.prototype.showEditor_ = function() {
|
||||||
|
Blockly.FieldPitch.superClass_.showEditor_.call(this);
|
||||||
|
|
||||||
|
var div = Blockly.WidgetDiv.DIV;
|
||||||
|
if (!div.firstChild) {
|
||||||
|
// Mobile interface uses Blockly.prompt.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Build the DOM.
|
||||||
|
var editor = this.dropdownCreate_();
|
||||||
|
Blockly.DropDownDiv.getContentDiv().appendChild(editor);
|
||||||
|
|
||||||
|
var border = this.sourceBlock_.getColourBorder();
|
||||||
|
border = border.colourBorder || border.colourLight;
|
||||||
|
Blockly.DropDownDiv.setColour(this.sourceBlock_.getColour(), border);
|
||||||
|
|
||||||
|
Blockly.DropDownDiv.showPositionedByField(
|
||||||
|
this, this.dropdownDispose_.bind(this));
|
||||||
|
|
||||||
|
// The note picker is different from other fields in that it updates on
|
||||||
|
// mousemove even if it's not in the middle of a drag. In future we may
|
||||||
|
// change this behaviour. For now, using bindEvent_ instead of
|
||||||
|
// bindEventWithChecks_ allows it to work without a mousedown/touchstart.
|
||||||
|
this.clickWrapper_ =
|
||||||
|
Blockly.bindEvent_(this.imageElement_, 'click', this,
|
||||||
|
this.hide_);
|
||||||
|
this.moveWrapper_ =
|
||||||
|
Blockly.bindEvent_(this.imageElement_, 'mousemove', this,
|
||||||
|
this.onMouseMove);
|
||||||
|
|
||||||
|
this.updateGraph_();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the pitch editor.
|
||||||
|
* @return {!Element} The newly created pitch picker.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
Blockly.FieldPitch.prototype.dropdownCreate_ = function() {
|
||||||
|
this.imageElement_ = document.createElement('div');
|
||||||
|
this.imageElement_.id = 'notePicker';
|
||||||
|
|
||||||
|
return this.imageElement_;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispose of events belonging to the pitch editor.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
Blockly.FieldPitch.prototype.dropdownDispose_ = function() {
|
||||||
|
Blockly.unbindEvent_(this.clickWrapper_);
|
||||||
|
Blockly.unbindEvent_(this.moveWrapper_);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide the editor.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
Blockly.FieldPitch.prototype.hide_ = function() {
|
||||||
|
Blockly.WidgetDiv.hide();
|
||||||
|
Blockly.DropDownDiv.hideWithoutAnimation();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the note to match the mouse's position.
|
||||||
|
* @param {!Event} e Mouse move event.
|
||||||
|
*/
|
||||||
|
Blockly.FieldPitch.prototype.onMouseMove = function(e) {
|
||||||
|
var bBox = this.imageElement_.getBoundingClientRect();
|
||||||
|
var dy = e.clientY - bBox.top;
|
||||||
|
var note = Blockly.utils.math.clamp(Math.round(13.5 - dy / 7.5), 0, 12);
|
||||||
|
this.imageElement_.style.backgroundPosition = (-note * 37) + 'px 0';
|
||||||
|
this.setEditorValue_(note);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert the machine-readable value (0-12) to human-readable
|
||||||
|
* text (C3-A4).
|
||||||
|
* @param {number|string} value The provided value.
|
||||||
|
* @return {string} The respective note.
|
||||||
|
*/
|
||||||
|
Blockly.FieldPitch.prototype.valueToNote = function(value) {
|
||||||
|
var note = Blockly.FieldPitch.NOTES[Number(value)];
|
||||||
|
return note;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert the human-readable text (C3-A4) to machine-readable
|
||||||
|
* value (0-12).
|
||||||
|
* @param {string} text The provided note.
|
||||||
|
* @return {number} The respective value.
|
||||||
|
*/
|
||||||
|
Blockly.FieldPitch.prototype.noteToValue = function(text) {
|
||||||
|
var i = Blockly.FieldPitch.NOTES.indexOf(text);
|
||||||
|
return i;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the text to be displayed on the field node.
|
||||||
|
* @return {?string} The HTML value if we're editing, otherwise null. Null means
|
||||||
|
* the super class will handle it, likely a string cast of value.
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
Blockly.FieldPitch.prototype.getText_ = function() {
|
||||||
|
if (this.isBeingEdited_) {
|
||||||
|
return Blockly.FieldPitch.superClass_.getText_.call(this);
|
||||||
|
}
|
||||||
|
return this.valueToNote(this.getValue()) || null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform the provided value into a text to show in the HTML input.
|
||||||
|
* @param {*} value The value stored in this field.
|
||||||
|
* @returns {string} The text to show on the HTML input.
|
||||||
|
*/
|
||||||
|
Blockly.FieldPitch.prototype.getEditorText_ = function(value) {
|
||||||
|
return this.valueToNote(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform the text received from the HTML input (note) into a value
|
||||||
|
* to store in this field.
|
||||||
|
* @param {string} text Text received from the HTML input.
|
||||||
|
* @returns {*} The value to store.
|
||||||
|
*/
|
||||||
|
Blockly.FieldPitch.prototype.getValueFromEditorText_ = function(text) {
|
||||||
|
return this.noteToValue(text);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the graph when the field rerenders.
|
||||||
|
* @private
|
||||||
|
* @override
|
||||||
|
*/
|
||||||
|
Blockly.FieldPitch.prototype.render_ = function() {
|
||||||
|
Blockly.FieldPitch.superClass_.render_.call(this);
|
||||||
|
this.updateGraph_();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redraw the note picker with the current note.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
Blockly.FieldPitch.prototype.updateGraph_ = function() {
|
||||||
|
if (!this.imageElement_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var i = this.getValue();
|
||||||
|
this.imageElement_.style.backgroundPosition = (-i * 37) + 'px 0';
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure that only a valid value may be entered.
|
||||||
|
* @param {*} opt_newValue The input value.
|
||||||
|
* @return {*} A valid value, or null if invalid.
|
||||||
|
*/
|
||||||
|
Blockly.FieldPitch.prototype.doClassValidation_ = function(opt_newValue) {
|
||||||
|
if (opt_newValue === null || opt_newValue === undefined) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var note = this.valueToNote(opt_newValue);
|
||||||
|
if (note) {
|
||||||
|
return opt_newValue;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
Blockly.fieldRegistry.register('field_pitch', Blockly.FieldPitch);
|
||||||
103
demos/custom-fields/pitch/index.html
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Blockly Demo: Custom Pitch Field</title>
|
||||||
|
<script src="../../../blockly_uncompressed.js"></script>
|
||||||
|
<script src="blocks.js"></script>
|
||||||
|
<script src="field_pitch.js"></script>
|
||||||
|
<script src="../../../msg/js/en.js"></script>
|
||||||
|
<link rel="stylesheet" type="text/css" href="pitch.css">
|
||||||
|
</head>
|
||||||
|
<body onload="start()">
|
||||||
|
<h1>
|
||||||
|
<a href="https://developers.google.com/blockly/">Blockly</a> >
|
||||||
|
<a href="../../index.html">Demos</a> >
|
||||||
|
<a href="../index.html">Custom Fields</a> > Pitch Field</h1>
|
||||||
|
|
||||||
|
|
||||||
|
<p>This is a demo of creating custom block fields. In this case the field
|
||||||
|
is used to select a note pitch.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>All of the custom field implementation is in
|
||||||
|
demos/custom-fields/pitch/field_pitch.js, including comments on each required
|
||||||
|
function.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<input type="button" value="Export to XML" onclick="toXml()">
|
||||||
|
<input type="button" value="Import from XML" onclick="fromXml()">
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<textarea id="importExport"
|
||||||
|
style="width: 200px; height: 480px;"
|
||||||
|
onchange="textAreaChange();"
|
||||||
|
onkeyup="textAreaChange()"></textarea>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div id="blocklyDiv" style="width: 600px; height: 480px;"></div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function toXml() {
|
||||||
|
var output = document.getElementById('importExport');
|
||||||
|
var xml = Blockly.Xml.workspaceToDom(workspace);
|
||||||
|
output.value = Blockly.Xml.domToPrettyText(xml);
|
||||||
|
output.focus();
|
||||||
|
output.select();
|
||||||
|
}
|
||||||
|
|
||||||
|
function fromXml() {
|
||||||
|
var input = document.getElementById('importExport');
|
||||||
|
var xml = Blockly.Xml.textToDom(input.value);
|
||||||
|
Blockly.Xml.domToWorkspace(xml, workspace);
|
||||||
|
}
|
||||||
|
|
||||||
|
function appendDom() {
|
||||||
|
var blocks = document.getElementById('workspace-blocks');
|
||||||
|
if (blocks.firstElementChild) {
|
||||||
|
Blockly.Xml.appendDomToWorkspace(blocks, workspace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function start() {
|
||||||
|
workspace = Blockly.inject('blocklyDiv', options);
|
||||||
|
appendDom();
|
||||||
|
workspace.scrollCenter();
|
||||||
|
}
|
||||||
|
|
||||||
|
var options = {
|
||||||
|
media: '../../../media/',
|
||||||
|
grid: {
|
||||||
|
spacing: 25,
|
||||||
|
length: 3,
|
||||||
|
colour: '#ccc'
|
||||||
|
},
|
||||||
|
move: {
|
||||||
|
scrollbars: true,
|
||||||
|
drag: true,
|
||||||
|
wheel: true,
|
||||||
|
},
|
||||||
|
zoom: {
|
||||||
|
controls: true,
|
||||||
|
startScale: 1.0,
|
||||||
|
maxScale: 4,
|
||||||
|
minScale: 0.25,
|
||||||
|
scaleSpeed: 1.1
|
||||||
|
}
|
||||||
|
/*toolbox: document.getElementById('toolbox')*/
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<xml xmlns="https://developers.google.com/blockly/xml" id="workspace-blocks" style="display: none">
|
||||||
|
<block type="test_pitch_field"></block>
|
||||||
|
</xml>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
BIN
demos/custom-fields/pitch/media/notes.png
Normal file
|
After Width: | Height: | Size: 894 B |
27
demos/custom-fields/pitch/pitch.css
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Visual Blocks Editor
|
||||||
|
*
|
||||||
|
* Copyright 2019 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#notePicker {
|
||||||
|
background-image: url(media/notes.png);
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
height: 109px;
|
||||||
|
width: 46px;
|
||||||
|
}
|
||||||
BIN
demos/custom-fields/turtle/icon.png
Normal file
|
After Width: | Height: | Size: 8.5 KiB |
155
demos/custom-fields/turtle/index.html
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Blockly Demo: Custom Turtle Field</title>
|
||||||
|
<script src="../../../blockly_compressed.js"></script>
|
||||||
|
<script src="blocks.js"></script>
|
||||||
|
<script src="field_turtle.js"></script>
|
||||||
|
<script src="../../../msg/js/en.js"></script>
|
||||||
|
<link rel="stylesheet" type="text/css" href="turtle.css">
|
||||||
|
</head>
|
||||||
|
<body onload="start()">
|
||||||
|
<h1>
|
||||||
|
<a href="https://developers.google.com/blockly/">Blockly</a> >
|
||||||
|
<a href="../../index.html">Demos</a> >
|
||||||
|
<a href="../index.html">Custom Fields</a> > Turtle Field</h1>
|
||||||
|
|
||||||
|
|
||||||
|
<p>This is a demo of creating custom block fields. In this case the field
|
||||||
|
is used to define a turtle.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>All of the custom field implementation is in
|
||||||
|
demos/custom-fields/turtle/field_turtle.js, including comments on each required
|
||||||
|
function.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>Click on the blocks' comment icons to learn what they are demonstrating.
|
||||||
|
Use the buttons below to see how the fields react to changes.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<input type="button" value="set random style" onclick="setRandomStyle()">
|
||||||
|
<input type="button" value="toggle shadow" onclick="toggleShadow()">
|
||||||
|
<input type="button" value="toggle enabled" onclick="toggleEnabled()">
|
||||||
|
<input type="button" value="toggle editable" onclick="toggleEditable()">
|
||||||
|
<input type="button" value="toggle collapsed" onclick="toggleCollapsed()">
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<input type="button" value="Export to XML" onclick="toXml()">
|
||||||
|
<input type="button" value="Import from XML" onclick="fromXml()">
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<textarea id="importExport"
|
||||||
|
style="width: 200px; height: 480px;"
|
||||||
|
onchange="textAreaChange();"
|
||||||
|
onkeyup="textAreaChange()"></textarea>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div id="blocklyDiv" style="width: 600px; height: 480px;"></div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function toXml() {
|
||||||
|
var output = document.getElementById('importExport');
|
||||||
|
var xml = Blockly.Xml.workspaceToDom(workspace);
|
||||||
|
output.value = Blockly.Xml.domToPrettyText(xml);
|
||||||
|
output.focus();
|
||||||
|
output.select();
|
||||||
|
}
|
||||||
|
|
||||||
|
function fromXml() {
|
||||||
|
var input = document.getElementById('importExport');
|
||||||
|
var xml = Blockly.Xml.textToDom(input.value);
|
||||||
|
Blockly.Xml.domToWorkspace(xml, workspace);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setRandomStyle() {
|
||||||
|
var blocks = workspace.getAllBlocks();
|
||||||
|
var styles = Object.keys(Blockly.getTheme().getAllBlockStyles());
|
||||||
|
styles.splice(styles.indexOf(blocks[0].getStyleName()), 1);
|
||||||
|
var style = styles[Math.floor(Math.random() * styles.length)];
|
||||||
|
for(var i = 0, block; block = blocks[i]; i++) {
|
||||||
|
block.setStyle(style);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleShadow() {
|
||||||
|
var blocks = workspace.getAllBlocks();
|
||||||
|
for(var i = 0, block; block = blocks[i]; i++) {
|
||||||
|
block.setShadow(!block.isShadow());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleEnabled() {
|
||||||
|
var blocks = workspace.getAllBlocks();
|
||||||
|
for(var i = 0, block; block = blocks[i]; i++) {
|
||||||
|
block.setEnabled(!block.isEnabled());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleEditable() {
|
||||||
|
var blocks = workspace.getAllBlocks();
|
||||||
|
for(var i = 0, block; block = blocks[i]; i++) {
|
||||||
|
block.setEditable(!block.isEditable());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleCollapsed() {
|
||||||
|
var blocks = workspace.getAllBlocks();
|
||||||
|
for(var i = 0, block; block = blocks[i]; i++) {
|
||||||
|
block.setCollapsed(!block.isCollapsed());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function appendDom() {
|
||||||
|
var blocks = document.getElementById('workspace-blocks');
|
||||||
|
if (blocks.firstElementChild) {
|
||||||
|
Blockly.Xml.appendDomToWorkspace(blocks, workspace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function start() {
|
||||||
|
workspace = Blockly.inject('blocklyDiv', options);
|
||||||
|
appendDom();
|
||||||
|
workspace.scrollCenter();
|
||||||
|
}
|
||||||
|
|
||||||
|
var options = {
|
||||||
|
media: '../../../media/',
|
||||||
|
grid: {
|
||||||
|
spacing: 25,
|
||||||
|
length: 3,
|
||||||
|
colour: '#ccc'
|
||||||
|
},
|
||||||
|
move: {
|
||||||
|
scrollbars: true,
|
||||||
|
drag: true,
|
||||||
|
wheel: true,
|
||||||
|
},
|
||||||
|
zoom: {
|
||||||
|
controls: true,
|
||||||
|
startScale: 1.0,
|
||||||
|
maxScale: 4,
|
||||||
|
minScale: 0.25,
|
||||||
|
scaleSpeed: 1.1
|
||||||
|
}
|
||||||
|
/*toolbox: document.getElementById('toolbox')*/
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<xml xmlns="https://developers.google.com/blockly/xml" id="workspace-blocks" style="display: none">
|
||||||
|
<block type="turtle_basic"></block>
|
||||||
|
<block type="turtle_nullifier" y="120"></block>
|
||||||
|
<block type="turtle_changer" y="230"></block>
|
||||||
|
</xml>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
Before Width: | Height: | Size: 687 B After Width: | Height: | Size: 687 B |
|
Before Width: | Height: | Size: 581 B After Width: | Height: | Size: 581 B |
|
Before Width: | Height: | Size: 602 B After Width: | Height: | Size: 602 B |
|
Before Width: | Height: | Size: 871 B After Width: | Height: | Size: 871 B |
|
Before Width: | Height: | Size: 541 B After Width: | Height: | Size: 541 B |
|
Before Width: | Height: | Size: 363 B After Width: | Height: | Size: 363 B |
@@ -19,44 +19,44 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
.customFieldsTurtleWidget {
|
.customFieldsTurtleWidget {
|
||||||
width: 150px;
|
width: 150px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.customFieldsTurtleWidget button {
|
.customFieldsTurtleWidget button {
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
border: none;
|
border: none;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
opacity: .6;
|
opacity: .6;
|
||||||
color: #000;
|
color: #000;
|
||||||
}
|
}
|
||||||
|
|
||||||
.customFieldsTurtleWidget .table {
|
.customFieldsTurtleWidget .table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.customFieldsTurtleWidget .row {
|
.customFieldsTurtleWidget .row {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.customFieldsTurtleWidget .arrow {
|
.customFieldsTurtleWidget .arrow {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
flex-grow: 0;
|
flex-grow: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.customFieldsTurtleWidget .text {
|
.customFieldsTurtleWidget .text {
|
||||||
height: 20px;
|
height: 20px;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.customFieldsTurtleWidget .randomize {
|
.customFieldsTurtleWidget .randomize {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.blocklyNonEditableText text,
|
.blocklyNonEditableText text,
|
||||||
.blocklyEditableText text {
|
.blocklyEditableText text {
|
||||||
fill: #000;
|
fill: #000;
|
||||||
}
|
}
|
||||||
@@ -152,7 +152,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a href="custom-fields/index.html">
|
<a href="custom-fields/index.html">
|
||||||
<img src="custom-fields/icon.png" height=80 width=100>
|
<img src="custom-fields/turtle/icon.png" height=80 width=100>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
|||||||