mirror of
https://github.com/google/blockly.git
synced 2026-01-11 10:57:07 +01:00
Merge branch 'develop' into feature/simple_multitouch
This commit is contained in:
@@ -127,6 +127,9 @@ Blockly.Block = function(workspace, prototypeName, opt_id) {
|
||||
this.workspace = workspace;
|
||||
/** @type {boolean} */
|
||||
this.isInFlyout = workspace.isFlyout;
|
||||
/** @type {boolean} */
|
||||
this.isInMutator = workspace.isMutator;
|
||||
|
||||
/** @type {boolean} */
|
||||
this.RTL = workspace.RTL;
|
||||
|
||||
@@ -194,6 +197,10 @@ Blockly.Block.prototype.colour_ = '#000000';
|
||||
* all children of this block.
|
||||
*/
|
||||
Blockly.Block.prototype.dispose = function(healStack) {
|
||||
if (!this.workspace) {
|
||||
// Already deleted.
|
||||
return;
|
||||
}
|
||||
// Terminate onchange event calls.
|
||||
if (this.onchangeWrapper_) {
|
||||
this.workspace.removeChangeListener(this.onchangeWrapper_);
|
||||
@@ -908,10 +915,13 @@ Blockly.Block.prototype.setCollapsed = function(collapsed) {
|
||||
/**
|
||||
* Create a human-readable text representation of this block and any children.
|
||||
* @param {number=} opt_maxLength Truncate the string to this length.
|
||||
* @param {string=} opt_emptyToken The placeholder string used to denote an
|
||||
* empty field. If not specified, '?' is used.
|
||||
* @return {string} Text of block.
|
||||
*/
|
||||
Blockly.Block.prototype.toString = function(opt_maxLength) {
|
||||
Blockly.Block.prototype.toString = function(opt_maxLength, opt_emptyToken) {
|
||||
var text = [];
|
||||
var emptyFieldPlaceholder = opt_emptyToken || '?';
|
||||
if (this.collapsed_) {
|
||||
text.push(this.getInput('_TEMP_COLLAPSED_INPUT').fieldRow[0].text_);
|
||||
} else {
|
||||
@@ -922,9 +932,9 @@ Blockly.Block.prototype.toString = function(opt_maxLength) {
|
||||
if (input.connection) {
|
||||
var child = input.connection.targetBlock();
|
||||
if (child) {
|
||||
text.push(child.toString());
|
||||
text.push(child.toString(undefined, opt_emptyToken));
|
||||
} else {
|
||||
text.push('?');
|
||||
text.push(emptyFieldPlaceholder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,7 +288,7 @@ Blockly.BlockSvg.prototype.render = function(opt_bubble) {
|
||||
parentBlock.render(true);
|
||||
} else {
|
||||
// Top-most block. Fire an event to allow scrollbars to resize.
|
||||
Blockly.resizeSvgContents(this.workspace);
|
||||
this.workspace.resizeContents();
|
||||
}
|
||||
}
|
||||
Blockly.Field.stopCache();
|
||||
|
||||
@@ -276,7 +276,7 @@ Blockly.BlockSvg.terminateDrag = function() {
|
||||
Blockly.Events.setGroup(false);
|
||||
}, Blockly.BUMP_DELAY);
|
||||
// Fire an event to allow scrollbars to resize.
|
||||
Blockly.resizeSvgContents(selected.workspace);
|
||||
selected.workspace.resizeContents();
|
||||
}
|
||||
}
|
||||
Blockly.dragMode_ = Blockly.DRAG_NONE;
|
||||
@@ -346,7 +346,7 @@ Blockly.BlockSvg.prototype.moveBy = function(dx, dy) {
|
||||
'translate(' + (xy.x + dx) + ',' + (xy.y + dy) + ')');
|
||||
this.moveConnections_(dx, dy);
|
||||
event.recordNew();
|
||||
Blockly.resizeSvgContents(this.workspace);
|
||||
this.workspace.resizeContents();
|
||||
Blockly.Events.fire(event);
|
||||
};
|
||||
|
||||
@@ -533,6 +533,13 @@ Blockly.BlockSvg.prototype.onMouseDown_ = function(e) {
|
||||
if (this.isInFlyout) {
|
||||
return;
|
||||
}
|
||||
if (this.isInMutator) {
|
||||
// Mutator's coordinate system could be out of date because the bubble was
|
||||
// dragged, the block was moved, the parent workspace zoomed, etc.
|
||||
this.workspace.resize();
|
||||
}
|
||||
|
||||
this.workspace.updateScreenCalculationsIfScrolled();
|
||||
this.workspace.markFocused();
|
||||
Blockly.terminateDrag_();
|
||||
this.select();
|
||||
@@ -946,11 +953,9 @@ Blockly.BlockSvg.prototype.setMovable = function(movable) {
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.setEditable = function(editable) {
|
||||
Blockly.BlockSvg.superClass_.setEditable.call(this, editable);
|
||||
if (this.rendered) {
|
||||
var icons = this.getIcons();
|
||||
for (var i = 0; i < icons.length; i++) {
|
||||
icons[i].updateEditable();
|
||||
}
|
||||
var icons = this.getIcons();
|
||||
for (var i = 0; i < icons.length; i++) {
|
||||
icons[i].updateEditable();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -979,6 +984,10 @@ Blockly.BlockSvg.prototype.getSvgRoot = function() {
|
||||
* @param {boolean} animate If true, show a disposal animation and sound.
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.dispose = function(healStack, animate) {
|
||||
if (!this.workspace) {
|
||||
// The block has already been deleted.
|
||||
return;
|
||||
}
|
||||
Blockly.Tooltip.hide();
|
||||
Blockly.Field.startCache();
|
||||
// Save the block's workspace temporarily so we can resize the
|
||||
@@ -1013,7 +1022,7 @@ Blockly.BlockSvg.prototype.dispose = function(healStack, animate) {
|
||||
Blockly.BlockSvg.superClass_.dispose.call(this, healStack);
|
||||
|
||||
goog.dom.removeNode(this.svgGroup_);
|
||||
Blockly.resizeSvgContents(blockWorkspace);
|
||||
blockWorkspace.resizeContents();
|
||||
// Sever JavaScript to DOM connections.
|
||||
this.svgGroup_ = null;
|
||||
this.svgPath_ = null;
|
||||
|
||||
122
core/blockly.js
122
core/blockly.js
@@ -453,128 +453,6 @@ Blockly.hideChaff = function(opt_allowToolbox) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Return an object with all the metrics required to size scrollbars for the
|
||||
* main workspace. The following properties are computed:
|
||||
* .viewHeight: Height of the visible rectangle,
|
||||
* .viewWidth: Width of the visible rectangle,
|
||||
* .contentHeight: Height of the contents,
|
||||
* .contentWidth: Width of the content,
|
||||
* .viewTop: Offset of top edge of visible rectangle from parent,
|
||||
* .viewLeft: Offset of left edge of visible rectangle from parent,
|
||||
* .contentTop: Offset of the top-most content from the y=0 coordinate,
|
||||
* .contentLeft: Offset of the left-most content from the x=0 coordinate.
|
||||
* .absoluteTop: Top-edge of view.
|
||||
* .absoluteLeft: Left-edge of view.
|
||||
* .toolboxWidth: Width of toolbox, if it exists. Otherwise zero.
|
||||
* .toolboxHeight: Height of toolbox, if it exists. Otherwise zero.
|
||||
* .flyoutWidth: Width of the flyout if it is always open. Otherwise zero.
|
||||
* .flyoutHeight: Height of flyout if it is always open. Otherwise zero.
|
||||
* .toolboxPosition: Top, bottom, left or right.
|
||||
* @return {Object} Contains size and position metrics of main workspace.
|
||||
* @private
|
||||
* @this Blockly.WorkspaceSvg
|
||||
*/
|
||||
Blockly.getMainWorkspaceMetrics_ = function() {
|
||||
var svgSize = Blockly.svgSize(this.getParentSvg());
|
||||
if (this.toolbox_) {
|
||||
if (this.toolboxPosition == Blockly.TOOLBOX_AT_TOP ||
|
||||
this.toolboxPosition == Blockly.TOOLBOX_AT_BOTTOM) {
|
||||
svgSize.height -= this.toolbox_.getHeight();
|
||||
} else if (this.toolboxPosition == Blockly.TOOLBOX_AT_LEFT ||
|
||||
this.toolboxPosition == Blockly.TOOLBOX_AT_RIGHT) {
|
||||
svgSize.width -= this.toolbox_.getWidth();
|
||||
}
|
||||
}
|
||||
// Set the margin to match the flyout's margin so that the workspace does
|
||||
// not jump as blocks are added.
|
||||
var MARGIN = Blockly.Flyout.prototype.CORNER_RADIUS - 1;
|
||||
var viewWidth = svgSize.width - MARGIN;
|
||||
var viewHeight = svgSize.height - MARGIN;
|
||||
var blockBox = this.getBlocksBoundingBox();
|
||||
|
||||
// Fix scale.
|
||||
var contentWidth = blockBox.width * this.scale;
|
||||
var contentHeight = blockBox.height * this.scale;
|
||||
var contentX = blockBox.x * this.scale;
|
||||
var contentY = blockBox.y * this.scale;
|
||||
if (this.scrollbar) {
|
||||
// Add a border around the content that is at least half a screenful wide.
|
||||
// Ensure border is wide enough that blocks can scroll over entire screen.
|
||||
var leftEdge = Math.min(contentX - viewWidth / 2,
|
||||
contentX + contentWidth - viewWidth);
|
||||
var rightEdge = Math.max(contentX + contentWidth + viewWidth / 2,
|
||||
contentX + viewWidth);
|
||||
var topEdge = Math.min(contentY - viewHeight / 2,
|
||||
contentY + contentHeight - viewHeight);
|
||||
var bottomEdge = Math.max(contentY + contentHeight + viewHeight / 2,
|
||||
contentY + viewHeight);
|
||||
} else {
|
||||
var leftEdge = blockBox.x;
|
||||
var rightEdge = leftEdge + blockBox.width;
|
||||
var topEdge = blockBox.y;
|
||||
var bottomEdge = topEdge + blockBox.height;
|
||||
}
|
||||
var absoluteLeft = 0;
|
||||
if (this.toolbox_ && this.toolboxPosition == Blockly.TOOLBOX_AT_LEFT) {
|
||||
absoluteLeft = this.toolbox_.getWidth();
|
||||
}
|
||||
var absoluteTop = 0;
|
||||
if (this.toolbox_ && this.toolboxPosition == Blockly.TOOLBOX_AT_TOP) {
|
||||
absoluteTop = this.toolbox_.getHeight();
|
||||
}
|
||||
|
||||
var metrics = {
|
||||
viewHeight: svgSize.height,
|
||||
viewWidth: svgSize.width,
|
||||
contentHeight: bottomEdge - topEdge,
|
||||
contentWidth: rightEdge - leftEdge,
|
||||
viewTop: -this.scrollY,
|
||||
viewLeft: -this.scrollX,
|
||||
contentTop: topEdge,
|
||||
contentLeft: leftEdge,
|
||||
absoluteTop: absoluteTop,
|
||||
absoluteLeft: absoluteLeft,
|
||||
toolboxWidth: this.toolbox_ ? this.toolbox_.getWidth() : 0,
|
||||
toolboxHeight: this.toolbox_ ? this.toolbox_.getHeight() : 0,
|
||||
flyoutWidth: this.flyout_ ? this.flyout_.getWidth() : 0,
|
||||
flyoutHeight: this.flyout_ ? this.flyout_.getHeight() : 0,
|
||||
toolboxPosition: this.toolboxPosition
|
||||
};
|
||||
return metrics;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the X/Y translations of the main workspace to match the scrollbars.
|
||||
* @param {!Object} xyRatio Contains an x and/or y property which is a float
|
||||
* between 0 and 1 specifying the degree of scrolling.
|
||||
* @private
|
||||
* @this Blockly.WorkspaceSvg
|
||||
*/
|
||||
Blockly.setMainWorkspaceMetrics_ = function(xyRatio) {
|
||||
if (!this.scrollbar) {
|
||||
throw 'Attempt to set main workspace scroll without scrollbars.';
|
||||
}
|
||||
var metrics = this.getMetrics();
|
||||
if (goog.isNumber(xyRatio.x)) {
|
||||
this.scrollX = -metrics.contentWidth * xyRatio.x - metrics.contentLeft;
|
||||
}
|
||||
if (goog.isNumber(xyRatio.y)) {
|
||||
this.scrollY = -metrics.contentHeight * xyRatio.y - metrics.contentTop;
|
||||
}
|
||||
var x = this.scrollX + metrics.absoluteLeft;
|
||||
var y = this.scrollY + metrics.absoluteTop;
|
||||
this.translate(x, y);
|
||||
if (this.options.gridPattern) {
|
||||
this.options.gridPattern.setAttribute('x', x);
|
||||
this.options.gridPattern.setAttribute('y', y);
|
||||
if (goog.userAgent.IE) {
|
||||
// IE doesn't notice that the x/y offsets have changed. Force an update.
|
||||
this.updateGridPattern_();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* When something in Blockly's workspace changes, call a function.
|
||||
* @param {!Function} func Function to call.
|
||||
|
||||
26
core/css.js
26
core/css.js
@@ -135,12 +135,25 @@ Blockly.Css.CONTENT = [
|
||||
'background-color: #fff;',
|
||||
'outline: none;',
|
||||
'overflow: hidden;', /* IE overflows by default. */
|
||||
'display: block;',
|
||||
'}',
|
||||
|
||||
'.blocklyWidgetDiv {',
|
||||
'display: none;',
|
||||
'position: absolute;',
|
||||
'z-index: 999;',
|
||||
'z-index: 99999;', /* big value for bootstrap3 compatibility */
|
||||
'}',
|
||||
|
||||
'.injectionDiv {',
|
||||
'height: 100%;',
|
||||
'position: relative;',
|
||||
'}',
|
||||
|
||||
'.blocklyNonSelectable {',
|
||||
'user-select: none;',
|
||||
'-moz-user-select: none;',
|
||||
'-webkit-user-select: none;',
|
||||
'-ms-user-select: none;',
|
||||
'}',
|
||||
|
||||
'.blocklyTooltipDiv {',
|
||||
@@ -154,7 +167,7 @@ Blockly.Css.CONTENT = [
|
||||
'opacity: 0.9;',
|
||||
'padding: 2px;',
|
||||
'position: absolute;',
|
||||
'z-index: 1000;',
|
||||
'z-index: 100000;', /* big value for bootstrap3 compatibility */
|
||||
'}',
|
||||
|
||||
'.blocklyResizeSE {',
|
||||
@@ -244,6 +257,15 @@ Blockly.Css.CONTENT = [
|
||||
'fill: #000;',
|
||||
'}',
|
||||
|
||||
'.blocklyFlyoutButton {',
|
||||
'fill: #888;',
|
||||
'cursor: default',
|
||||
'}',
|
||||
|
||||
'.blocklyFlyoutButton:hover {',
|
||||
'fill: #ccc;',
|
||||
'}',
|
||||
|
||||
/*
|
||||
Don't allow users to select text. It gets annoying when trying to
|
||||
drag a block and selected text moves instead.
|
||||
|
||||
@@ -388,7 +388,7 @@ Blockly.Events.Create.prototype.fromJson = function(json) {
|
||||
Blockly.Events.Create.prototype.run = function(forward) {
|
||||
var workspace = Blockly.Workspace.getById(this.workspaceId);
|
||||
if (forward) {
|
||||
var xml = goog.dom.createUntypedDom('xml');
|
||||
var xml = goog.dom.createDom('xml');
|
||||
xml.appendChild(this.xml);
|
||||
Blockly.Xml.domToWorkspace(xml, workspace);
|
||||
} else {
|
||||
@@ -465,7 +465,7 @@ Blockly.Events.Delete.prototype.run = function(forward) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var xml = goog.dom.createUntypedDom('xml');
|
||||
var xml = goog.dom.createDom('xml');
|
||||
xml.appendChild(this.oldXml);
|
||||
Blockly.Xml.domToWorkspace(xml, workspace);
|
||||
}
|
||||
|
||||
@@ -178,20 +178,17 @@ Blockly.Field.prototype.dispose = function() {
|
||||
* Add or remove the UI indicating if this field is editable or not.
|
||||
*/
|
||||
Blockly.Field.prototype.updateEditable = function() {
|
||||
if (!this.EDITABLE || !this.fieldGroup_) {
|
||||
var group = this.fieldGroup_;
|
||||
if (!this.EDITABLE || !group) {
|
||||
return;
|
||||
}
|
||||
if (this.sourceBlock_.isEditable()) {
|
||||
Blockly.addClass_(/** @type {!Element} */ (this.fieldGroup_),
|
||||
'blocklyEditableText');
|
||||
Blockly.removeClass_(/** @type {!Element} */ (this.fieldGroup_),
|
||||
'blocklyNoNEditableText');
|
||||
Blockly.addClass_(group, 'blocklyEditableText');
|
||||
Blockly.removeClass_(group, 'blocklyNonEditableText');
|
||||
this.fieldGroup_.style.cursor = this.CURSOR;
|
||||
} else {
|
||||
Blockly.addClass_(/** @type {!Element} */ (this.fieldGroup_),
|
||||
'blocklyNonEditableText');
|
||||
Blockly.removeClass_(/** @type {!Element} */ (this.fieldGroup_),
|
||||
'blocklyEditableText');
|
||||
Blockly.addClass_(group, 'blocklyNonEditableText');
|
||||
Blockly.removeClass_(group, 'blocklyEditableText');
|
||||
this.fieldGroup_.style.cursor = '';
|
||||
}
|
||||
};
|
||||
|
||||
@@ -92,7 +92,7 @@ Blockly.FieldNumber.prototype.classValidator = function(text) {
|
||||
return null;
|
||||
}
|
||||
// Round to nearest multiple of precision.
|
||||
if (this.precision_ && Number.isFinite(n)) {
|
||||
if (this.precision_ && isFinite(n)) {
|
||||
n = Math.round(n / this.precision_) * this.precision_;
|
||||
}
|
||||
// Get the value in range.
|
||||
|
||||
@@ -271,6 +271,9 @@ Blockly.FieldTextInput.prototype.widgetDispose_ = function() {
|
||||
} else {
|
||||
// Validation function has changed the text.
|
||||
text = text1;
|
||||
if (thisField.onFinishEditing_) {
|
||||
thisField.onFinishEditing_(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
thisField.setValue(text);
|
||||
|
||||
@@ -65,6 +65,12 @@ Blockly.FieldVariable.prototype.init = function() {
|
||||
this.sourceBlock_.workspace;
|
||||
this.setValue(Blockly.Variables.generateUniqueName(workspace));
|
||||
}
|
||||
// If the selected variable doesn't exist yet, create it.
|
||||
// For instance, some blocks in the toolbox have variable dropdowns filled
|
||||
// in by default.
|
||||
if (!this.sourceBlock_.isInFlyout) {
|
||||
this.sourceBlock_.workspace.createVariable(this.getValue());
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -97,8 +103,9 @@ Blockly.FieldVariable.prototype.setValue = function(newValue) {
|
||||
*/
|
||||
Blockly.FieldVariable.dropdownCreate = function() {
|
||||
if (this.sourceBlock_ && this.sourceBlock_.workspace) {
|
||||
var variableList =
|
||||
Blockly.Variables.allVariables(this.sourceBlock_.workspace);
|
||||
// Get a copy of the list, so that adding rename and new variable options
|
||||
// doesn't modify the workspace's list.
|
||||
var variableList = this.sourceBlock_.workspace.variableList.slice(0);
|
||||
} else {
|
||||
var variableList = [];
|
||||
}
|
||||
@@ -109,7 +116,7 @@ Blockly.FieldVariable.dropdownCreate = function() {
|
||||
}
|
||||
variableList.sort(goog.string.caseInsensitiveCompare);
|
||||
variableList.push(Blockly.Msg.RENAME_VARIABLE);
|
||||
variableList.push(Blockly.Msg.NEW_VARIABLE);
|
||||
variableList.push(Blockly.Msg.DELETE_VARIABLE.replace('%1', name));
|
||||
// Variables are not language-specific, use the name as both the user-facing
|
||||
// text and the internal representation.
|
||||
var options = [];
|
||||
@@ -121,46 +128,27 @@ Blockly.FieldVariable.dropdownCreate = function() {
|
||||
|
||||
/**
|
||||
* Event handler for a change in variable name.
|
||||
* Special case the 'New variable...' and 'Rename variable...' options.
|
||||
* In both of these special cases, prompt the user for a new name.
|
||||
* Special case the 'Rename variable...' and 'Delete variable...' options.
|
||||
* In the rename case, prompt the user for a new name.
|
||||
* @param {string} text The selected dropdown menu option.
|
||||
* @return {null|undefined|string} An acceptable new variable name, or null if
|
||||
* change is to be either aborted (cancel button) or has been already
|
||||
* handled (rename), or undefined if an existing variable was chosen.
|
||||
*/
|
||||
Blockly.FieldVariable.prototype.classValidator = function(text) {
|
||||
function promptName(promptText, defaultText) {
|
||||
Blockly.hideChaff();
|
||||
var newVar = window.prompt(promptText, defaultText);
|
||||
// Merge runs of whitespace. Strip leading and trailing whitespace.
|
||||
// Beyond this, all names are legal.
|
||||
if (newVar) {
|
||||
newVar = newVar.replace(/[\s\xa0]+/g, ' ').replace(/^ | $/g, '');
|
||||
if (newVar == Blockly.Msg.RENAME_VARIABLE ||
|
||||
newVar == Blockly.Msg.NEW_VARIABLE) {
|
||||
// Ok, not ALL names are legal...
|
||||
newVar = null;
|
||||
}
|
||||
}
|
||||
return newVar;
|
||||
}
|
||||
var workspace = this.sourceBlock_.workspace;
|
||||
if (text == Blockly.Msg.RENAME_VARIABLE) {
|
||||
var oldVar = this.getText();
|
||||
text = promptName(Blockly.Msg.RENAME_VARIABLE_TITLE.replace('%1', oldVar),
|
||||
oldVar);
|
||||
Blockly.hideChaff();
|
||||
text = Blockly.Variables.promptName(
|
||||
Blockly.Msg.RENAME_VARIABLE_TITLE.replace('%1', oldVar), oldVar);
|
||||
if (text) {
|
||||
Blockly.Variables.renameVariable(oldVar, text, workspace);
|
||||
workspace.renameVariable(oldVar, text);
|
||||
}
|
||||
return null;
|
||||
} else if (text == Blockly.Msg.NEW_VARIABLE) {
|
||||
text = promptName(Blockly.Msg.NEW_VARIABLE_TITLE, '');
|
||||
// Since variables are case-insensitive, ensure that if the new variable
|
||||
// matches with an existing variable, the new case prevails throughout.
|
||||
if (text) {
|
||||
Blockly.Variables.renameVariable(text, text, workspace);
|
||||
return text;
|
||||
}
|
||||
} else if (text == Blockly.Msg.DELETE_VARIABLE.replace('%1',
|
||||
this.getText())) {
|
||||
workspace.deleteVariable(this.getText());
|
||||
return null;
|
||||
}
|
||||
return undefined;
|
||||
|
||||
229
core/flyout.js
229
core/flyout.js
@@ -29,6 +29,7 @@ goog.provide('Blockly.Flyout');
|
||||
goog.require('Blockly.Block');
|
||||
goog.require('Blockly.Comment');
|
||||
goog.require('Blockly.Events');
|
||||
goog.require('Blockly.FlyoutButton');
|
||||
goog.require('Blockly.WorkspaceSvg');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.events');
|
||||
@@ -84,6 +85,13 @@ Blockly.Flyout = function(workspaceOptions) {
|
||||
* @type {!Array.<!Element>}
|
||||
* @private
|
||||
*/
|
||||
this.backgroundButtons_ = [];
|
||||
|
||||
/**
|
||||
* List of visible buttons.
|
||||
* @type {!Array.<!Blockly.FlyoutButton>}
|
||||
* @private
|
||||
*/
|
||||
this.buttons_ = [];
|
||||
|
||||
/**
|
||||
@@ -99,8 +107,60 @@ Blockly.Flyout = function(workspaceOptions) {
|
||||
* @private
|
||||
*/
|
||||
this.permanentlyDisabled_ = [];
|
||||
|
||||
/**
|
||||
* y coordinate of mousedown - used to calculate scroll distances.
|
||||
* @private {number}
|
||||
*/
|
||||
this.startDragMouseY_ = 0;
|
||||
|
||||
/**
|
||||
* x coordinate of mousedown - used to calculate scroll distances.
|
||||
* @private {number}
|
||||
*/
|
||||
this.startDragMouseX_ = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* When a flyout drag is in progress, this is a reference to the flyout being
|
||||
* dragged. This is used by Flyout.terminateDrag_ to reset dragMode_.
|
||||
* @private {Blockly.Flyout}
|
||||
*/
|
||||
Blockly.Flyout.startFlyout_ = null;
|
||||
|
||||
/**
|
||||
* Event that started a drag. Used to determine the drag distance/direction and
|
||||
* also passed to BlockSvg.onMouseDown_() after creating a new block.
|
||||
* @private {Event}
|
||||
*/
|
||||
Blockly.Flyout.startDownEvent_ = null;
|
||||
|
||||
/**
|
||||
* Flyout block where the drag/click was initiated. Used to fire click events or
|
||||
* create a new block.
|
||||
* @private {Event}
|
||||
*/
|
||||
Blockly.Flyout.startBlock_ = null;
|
||||
|
||||
/**
|
||||
* Wrapper function called when a mouseup occurs during a background or block
|
||||
* drag operation.
|
||||
* @private {Array.<!Array>}
|
||||
*/
|
||||
Blockly.Flyout.onMouseUpWrapper_ = null;
|
||||
|
||||
/**
|
||||
* Wrapper function called when a mousemove occurs during a background drag.
|
||||
* @private {Array.<!Array>}
|
||||
*/
|
||||
Blockly.Flyout.onMouseMoveWrapper_ = null;
|
||||
|
||||
/**
|
||||
* Wrapper function called when a mousemove occurs during a block drag.
|
||||
* @private {Array.<!Array>}
|
||||
*/
|
||||
Blockly.Flyout.onMouseMoveBlockWrapper_ = null;
|
||||
|
||||
/**
|
||||
* Does the flyout automatically close when a block is created?
|
||||
* @type {boolean}
|
||||
@@ -128,6 +188,20 @@ Blockly.Flyout.prototype.DRAG_RADIUS = 10;
|
||||
*/
|
||||
Blockly.Flyout.prototype.MARGIN = Blockly.Flyout.prototype.CORNER_RADIUS;
|
||||
|
||||
/**
|
||||
* Gap between items in horizontal flyouts. Can be overridden with the "sep"
|
||||
* element.
|
||||
* @const {number}
|
||||
*/
|
||||
Blockly.Flyout.prototype.GAP_X = Blockly.Flyout.prototype.MARGIN * 3;
|
||||
|
||||
/**
|
||||
* Gap between items in vertical flyouts. Can be overridden with the "sep"
|
||||
* element.
|
||||
* @const {number}
|
||||
*/
|
||||
Blockly.Flyout.prototype.GAP_Y = Blockly.Flyout.prototype.MARGIN * 3;
|
||||
|
||||
/**
|
||||
* Top/bottom padding between scrollbar and edge of flyout background.
|
||||
* @type {number}
|
||||
@@ -583,24 +657,48 @@ Blockly.Flyout.prototype.show = function(xmlList) {
|
||||
|
||||
this.svgGroup_.style.display = 'block';
|
||||
// Create the blocks to be shown in this flyout.
|
||||
var blocks = [];
|
||||
var contents = [];
|
||||
var gaps = [];
|
||||
this.permanentlyDisabled_.length = 0;
|
||||
for (var i = 0, xml; xml = xmlList[i]; i++) {
|
||||
if (xml.tagName && xml.tagName.toUpperCase() == 'BLOCK') {
|
||||
var curBlock = Blockly.Xml.domToBlock(xml, this.workspace_);
|
||||
if (curBlock.disabled) {
|
||||
// Record blocks that were initially disabled.
|
||||
// Do not enable these blocks as a result of capacity filtering.
|
||||
this.permanentlyDisabled_.push(curBlock);
|
||||
if (xml.tagName) {
|
||||
var tagName = xml.tagName.toUpperCase();
|
||||
var default_gap = this.horizontalLayout_ ? this.GAP_X : this.GAP_Y;
|
||||
if (tagName == 'BLOCK') {
|
||||
var curBlock = Blockly.Xml.domToBlock(xml, this.workspace_);
|
||||
if (curBlock.disabled) {
|
||||
// Record blocks that were initially disabled.
|
||||
// Do not enable these blocks as a result of capacity filtering.
|
||||
this.permanentlyDisabled_.push(curBlock);
|
||||
}
|
||||
contents.push({type: 'block', block: curBlock});
|
||||
var gap = parseInt(xml.getAttribute('gap'), 10);
|
||||
gaps.push(isNaN(gap) ? default_gap : gap);
|
||||
} else if (xml.tagName.toUpperCase() == 'SEP') {
|
||||
// Change the gap between two blocks.
|
||||
// <sep gap="36"></sep>
|
||||
// The default gap is 24, can be set larger or smaller.
|
||||
// This overwrites the gap attribute on the previous block.
|
||||
// Note that a deprecated method is to add a gap to a block.
|
||||
// <block type="math_arithmetic" gap="8"></block>
|
||||
var newGap = parseInt(xml.getAttribute('gap'), 10);
|
||||
// Ignore gaps before the first block.
|
||||
if (!isNaN(newGap) && gaps.length > 0) {
|
||||
gaps[gaps.length - 1] = newGap;
|
||||
} else {
|
||||
gaps.push(default_gap);
|
||||
}
|
||||
} else if (tagName == 'BUTTON') {
|
||||
var label = xml.getAttribute('text');
|
||||
var curButton = new Blockly.FlyoutButton(this.workspace_,
|
||||
this.targetWorkspace_, label);
|
||||
contents.push({type: 'button', button: curButton});
|
||||
gaps.push(default_gap);
|
||||
}
|
||||
blocks.push(curBlock);
|
||||
var gap = parseInt(xml.getAttribute('gap'), 10);
|
||||
gaps.push(isNaN(gap) ? this.MARGIN * 3 : gap);
|
||||
}
|
||||
}
|
||||
|
||||
this.layoutBlocks_(blocks, gaps);
|
||||
this.layout_(contents, gaps);
|
||||
|
||||
// IE 11 is an incompetent browser that fails to fire mouseout events.
|
||||
// When the mouse is over the background, deselect all blocks.
|
||||
@@ -632,56 +730,70 @@ Blockly.Flyout.prototype.show = function(xmlList) {
|
||||
|
||||
/**
|
||||
* Lay out the blocks in the flyout.
|
||||
* @param {!Array.<!Blockly.BlockSvg>} blocks The blocks to lay out.
|
||||
* @param {!Array.<!Object>} contents The blocks and buttons to lay out.
|
||||
* @param {!Array.<number>} gaps The visible gaps between blocks.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Flyout.prototype.layoutBlocks_ = function(blocks, gaps) {
|
||||
Blockly.Flyout.prototype.layout_ = function(contents, gaps) {
|
||||
this.workspace_.scale = this.targetWorkspace_.scale;
|
||||
var margin = this.MARGIN;
|
||||
var cursorX = this.RTL ? margin : margin + Blockly.BlockSvg.TAB_WIDTH;
|
||||
var cursorY = margin;
|
||||
if (this.horizontalLayout_ && this.RTL) {
|
||||
blocks = blocks.reverse();
|
||||
contents = contents.reverse();
|
||||
}
|
||||
for (var i = 0, block; block = blocks[i]; i++) {
|
||||
var allBlocks = block.getDescendants();
|
||||
for (var j = 0, child; child = allBlocks[j]; j++) {
|
||||
// Mark blocks as being inside a flyout. This is used to detect and
|
||||
// prevent the closure of the flyout if the user right-clicks on such a
|
||||
// block.
|
||||
child.isInFlyout = true;
|
||||
}
|
||||
block.render();
|
||||
var root = block.getSvgRoot();
|
||||
var blockHW = block.getHeightWidth();
|
||||
var tab = block.outputConnection ? Blockly.BlockSvg.TAB_WIDTH : 0;
|
||||
if (this.horizontalLayout_) {
|
||||
cursorX += tab;
|
||||
}
|
||||
|
||||
if (this.horizontalLayout_ && this.RTL) {
|
||||
block.moveBy(cursorX + blockHW.width - tab, cursorY);
|
||||
} else {
|
||||
block.moveBy(cursorX, cursorY);
|
||||
for (var i = 0, item; item = contents[i]; i++) {
|
||||
if (item.type == 'block') {
|
||||
var block = item.block;
|
||||
var allBlocks = block.getDescendants();
|
||||
for (var j = 0, child; child = allBlocks[j]; j++) {
|
||||
// Mark blocks as being inside a flyout. This is used to detect and
|
||||
// prevent the closure of the flyout if the user right-clicks on such a
|
||||
// block.
|
||||
child.isInFlyout = true;
|
||||
}
|
||||
block.render();
|
||||
var root = block.getSvgRoot();
|
||||
var blockHW = block.getHeightWidth();
|
||||
var tab = block.outputConnection ? Blockly.BlockSvg.TAB_WIDTH : 0;
|
||||
if (this.horizontalLayout_) {
|
||||
cursorX += tab;
|
||||
}
|
||||
block.moveBy((this.horizontalLayout_ && this.RTL) ?
|
||||
cursorX + blockHW.width - tab : cursorX,
|
||||
cursorY);
|
||||
if (this.horizontalLayout_) {
|
||||
cursorX += (blockHW.width + gaps[i] - tab);
|
||||
} else {
|
||||
cursorY += blockHW.height + gaps[i];
|
||||
}
|
||||
|
||||
// Create an invisible rectangle under the block to act as a button. Just
|
||||
// using the block as a button is poor, since blocks have holes in them.
|
||||
var rect = Blockly.createSvgElement('rect', {'fill-opacity': 0}, null);
|
||||
rect.tooltip = block;
|
||||
Blockly.Tooltip.bindMouseEvents(rect);
|
||||
// Add the rectangles under the blocks, so that the blocks' tooltips work.
|
||||
this.workspace_.getCanvas().insertBefore(rect, block.getSvgRoot());
|
||||
block.flyoutRect_ = rect;
|
||||
this.backgroundButtons_[i] = rect;
|
||||
|
||||
this.addBlockListeners_(root, block, rect);
|
||||
} else if (item.type == 'button') {
|
||||
var button = item.button;
|
||||
var buttonSvg = button.createDom();
|
||||
button.moveTo(cursorX, cursorY);
|
||||
button.show();
|
||||
Blockly.bindEvent_(buttonSvg, 'mouseup', button, button.onMouseUp);
|
||||
|
||||
this.buttons_.push(button);
|
||||
if (this.horizontalLayout_) {
|
||||
cursorX += (button.width + gaps[i]);
|
||||
} else {
|
||||
cursorY += button.height + gaps[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (this.horizontalLayout_) {
|
||||
cursorX += (blockHW.width + gaps[i] - tab);
|
||||
} else {
|
||||
cursorY += blockHW.height + gaps[i];
|
||||
}
|
||||
|
||||
// Create an invisible rectangle under the block to act as a button. Just
|
||||
// using the block as a button is poor, since blocks have holes in them.
|
||||
var rect = Blockly.createSvgElement('rect', {'fill-opacity': 0}, null);
|
||||
rect.tooltip = block;
|
||||
Blockly.Tooltip.bindMouseEvents(rect);
|
||||
// Add the rectangles under the blocks, so that the blocks' tooltips work.
|
||||
this.workspace_.getCanvas().insertBefore(rect, block.getSvgRoot());
|
||||
block.flyoutRect_ = rect;
|
||||
this.buttons_[i] = rect;
|
||||
|
||||
this.addBlockListeners_(root, block, rect);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -698,9 +810,14 @@ Blockly.Flyout.prototype.clearOldBlocks_ = function() {
|
||||
}
|
||||
}
|
||||
// Delete any background buttons from a previous showing.
|
||||
for (var j = 0, rect; rect = this.buttons_[j]; j++) {
|
||||
for (var j = 0, rect; rect = this.backgroundButtons_[j]; j++) {
|
||||
goog.dom.removeNode(rect);
|
||||
}
|
||||
this.backgroundButtons_.length = 0;
|
||||
|
||||
for (var i = 0, button; button = this.buttons_[i]; i++) {
|
||||
button.dispose();
|
||||
}
|
||||
this.buttons_.length = 0;
|
||||
};
|
||||
|
||||
@@ -774,6 +891,7 @@ Blockly.Flyout.prototype.onMouseDown_ = function(e) {
|
||||
this.dragMode_ = Blockly.DRAG_FREE;
|
||||
this.startDragMouseY_ = e.clientY;
|
||||
this.startDragMouseX_ = e.clientX;
|
||||
Blockly.Flyout.startFlyout_ = this;
|
||||
Blockly.Flyout.onMouseMoveWrapper_ = Blockly.bindEvent_(document, 'mousemove',
|
||||
this, this.onMouseMove_);
|
||||
Blockly.Flyout.onMouseUpWrapper_ = Blockly.bindEvent_(document, 'mouseup',
|
||||
@@ -1122,10 +1240,6 @@ Blockly.Flyout.terminateDrag_ = function() {
|
||||
Blockly.unbindEvent_(Blockly.Flyout.onMouseMoveWrapper_);
|
||||
Blockly.Flyout.onMouseMoveWrapper_ = null;
|
||||
}
|
||||
if (Blockly.Flyout.onMouseUpWrapper_) {
|
||||
Blockly.unbindEvent_(Blockly.Flyout.onMouseUpWrapper_);
|
||||
Blockly.Flyout.onMouseUpWrapper_ = null;
|
||||
}
|
||||
Blockly.Flyout.startDownEvent_ = null;
|
||||
Blockly.Flyout.startBlock_ = null;
|
||||
Blockly.Flyout.startFlyout_ = null;
|
||||
@@ -1190,6 +1304,9 @@ Blockly.Flyout.prototype.reflowVertical = function(blocks) {
|
||||
}
|
||||
flyoutWidth = Math.max(flyoutWidth, width);
|
||||
}
|
||||
for (var i = 0, button; button = this.buttons_[i]; i++) {
|
||||
flyoutWidth = Math.max(flyoutWidth, button.width);
|
||||
}
|
||||
flyoutWidth += this.MARGIN * 1.5 + Blockly.BlockSvg.TAB_WIDTH;
|
||||
flyoutWidth *= this.workspace_.scale;
|
||||
flyoutWidth += Blockly.Scrollbar.scrollbarThickness;
|
||||
|
||||
169
core/flyout_button.js
Normal file
169
core/flyout_button.js
Normal file
@@ -0,0 +1,169 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2016 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 Class for a button in the flyout.
|
||||
* @author fenichel@google.com (Rachel Fenichel)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.FlyoutButton');
|
||||
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.math.Coordinate');
|
||||
|
||||
|
||||
/**
|
||||
* Class for a button in the flyout.
|
||||
* @param {!Blockly.Workspace} workspace The workspace in which to place this
|
||||
* button.
|
||||
* @param {!Blockly.Workspace} targetWorkspace The flyout's target workspace.
|
||||
* @param {string} text The text to display on the button.
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.FlyoutButton = function(workspace, targetWorkspace, text) {
|
||||
/**
|
||||
* @type {!Blockly.Workspace}
|
||||
* @private
|
||||
*/
|
||||
this.workspace_ = workspace;
|
||||
|
||||
/**
|
||||
* @type {!Blockly.Workspace}
|
||||
* @private
|
||||
*/
|
||||
this.targetWorkspace_ = targetWorkspace;
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
this.text_ = text;
|
||||
|
||||
/**
|
||||
* @type {goog.math.Coordinate}
|
||||
* @private
|
||||
*/
|
||||
this.position_ = new goog.math.Coordinate(0, 0);
|
||||
};
|
||||
|
||||
/**
|
||||
* The margin around the text in the button.
|
||||
*/
|
||||
Blockly.FlyoutButton.MARGIN = 5;
|
||||
|
||||
/**
|
||||
* The width of the button's rect.
|
||||
* @type {number}
|
||||
*/
|
||||
Blockly.FlyoutButton.prototype.width = 0;
|
||||
|
||||
/**
|
||||
* The height of the button's rect.
|
||||
* @type {number}
|
||||
*/
|
||||
Blockly.FlyoutButton.prototype.height = 0;
|
||||
|
||||
/**
|
||||
* Create the button elements.
|
||||
* @return {!Element} The button's SVG group.
|
||||
*/
|
||||
Blockly.FlyoutButton.prototype.createDom = function() {
|
||||
this.svgGroup_ = Blockly.createSvgElement('g',
|
||||
{'class': 'blocklyFlyoutButton'}, this.workspace_.getCanvas());
|
||||
|
||||
// Rect with rounded corners.
|
||||
var rect = Blockly.createSvgElement('rect',
|
||||
{'rx': 4, 'ry': 4,
|
||||
'height': 0, 'width': 0},
|
||||
this.svgGroup_);
|
||||
|
||||
var svgText = Blockly.createSvgElement('text',
|
||||
{'class': 'blocklyText', 'x': 0, 'y': 0,
|
||||
'text-anchor': 'middle'}, this.svgGroup_);
|
||||
svgText.textContent = this.text_;
|
||||
|
||||
this.width = svgText.getComputedTextLength() +
|
||||
2 * Blockly.FlyoutButton.MARGIN;
|
||||
this.height = 20; // Can't compute it :(
|
||||
|
||||
rect.setAttribute('width', this.width);
|
||||
rect.setAttribute('height', this.height);
|
||||
|
||||
svgText.setAttribute('x', this.width / 2);
|
||||
svgText.setAttribute('y', this.height - Blockly.FlyoutButton.MARGIN);
|
||||
|
||||
this.updateTransform_();
|
||||
return this.svgGroup_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Correctly position the flyout button and make it visible.
|
||||
*/
|
||||
Blockly.FlyoutButton.prototype.show = function() {
|
||||
this.updateTransform_();
|
||||
this.svgGroup_.setAttribute('display', 'block');
|
||||
};
|
||||
|
||||
/**
|
||||
* Update svg attributes to match internal state.
|
||||
*/
|
||||
Blockly.FlyoutButton.prototype.updateTransform_ = function() {
|
||||
this.svgGroup_.setAttribute('transform', 'translate(' + this.position_.x +
|
||||
',' + this.position_.y + ')');
|
||||
};
|
||||
|
||||
/**
|
||||
* Move the button to the given x, y coordinates.
|
||||
* @param {number} x The new x coordinate.
|
||||
* @param {number} y The new y coordinate.
|
||||
*/
|
||||
Blockly.FlyoutButton.prototype.moveTo = function(x, y) {
|
||||
this.position_.x = x;
|
||||
this.position_.y = y;
|
||||
this.updateTransform_();
|
||||
};
|
||||
|
||||
/**
|
||||
* Dispose of this button.
|
||||
*/
|
||||
Blockly.FlyoutButton.prototype.dispose = function() {
|
||||
if (this.svgGroup_) {
|
||||
goog.dom.removeNode(this.svgGroup_);
|
||||
this.svgGroup_ = null;
|
||||
}
|
||||
this.workspace_ = null;
|
||||
this.targetWorkspace_ = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Do something when the button is clicked.
|
||||
* @param {!Event} e Mouse up event.
|
||||
*/
|
||||
Blockly.FlyoutButton.prototype.onMouseUp = function(e) {
|
||||
// Don't scroll the page.
|
||||
e.preventDefault();
|
||||
// Don't propagate mousewheel event (zooming).
|
||||
e.stopPropagation();
|
||||
// Stop binding to mouseup and mousemove events--flyout mouseup would normally
|
||||
// do this, but we're skipping that.
|
||||
Blockly.Flyout.terminateDrag_();
|
||||
Blockly.Variables.createVariable(this.targetWorkspace_);
|
||||
};
|
||||
11
core/icon.js
11
core/icon.js
@@ -78,6 +78,10 @@ Blockly.Icon.prototype.createIcon = function() {
|
||||
*/
|
||||
this.iconGroup_ = Blockly.createSvgElement('g',
|
||||
{'class': 'blocklyIconGroup'}, null);
|
||||
if (this.block_.isInFlyout) {
|
||||
Blockly.addClass_(/** @type {!Element} */ (this.iconGroup_),
|
||||
'blocklyIconGroupReadonly');
|
||||
}
|
||||
this.drawIcon_(this.iconGroup_);
|
||||
|
||||
this.block_.getSvgRoot().appendChild(this.iconGroup_);
|
||||
@@ -101,13 +105,6 @@ Blockly.Icon.prototype.dispose = function() {
|
||||
* Add or remove the UI indicating if this icon may be clicked or not.
|
||||
*/
|
||||
Blockly.Icon.prototype.updateEditable = function() {
|
||||
if (this.block_.isInFlyout || !this.block_.isEditable()) {
|
||||
Blockly.addClass_(/** @type {!Element} */ (this.iconGroup_),
|
||||
'blocklyIconGroupReadonly');
|
||||
} else {
|
||||
Blockly.removeClass_(/** @type {!Element} */ (this.iconGroup_),
|
||||
'blocklyIconGroupReadonly');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -51,7 +51,9 @@ Blockly.inject = function(container, opt_options) {
|
||||
throw 'Error: container is not in current document.';
|
||||
}
|
||||
var options = new Blockly.Options(opt_options || {});
|
||||
var svg = Blockly.createDom_(container, options);
|
||||
var subContainer = goog.dom.createDom('div', 'injectionDiv');
|
||||
container.appendChild(subContainer);
|
||||
var svg = Blockly.createDom_(subContainer, options);
|
||||
var workspace = Blockly.createMainWorkspace_(svg, options);
|
||||
Blockly.init_(workspace);
|
||||
workspace.markFocused();
|
||||
@@ -183,8 +185,6 @@ Blockly.createDom_ = function(container, options) {
|
||||
*/
|
||||
Blockly.createMainWorkspace_ = function(svg, options) {
|
||||
options.parentWorkspace = null;
|
||||
options.getMetrics = Blockly.getMainWorkspaceMetrics_;
|
||||
options.setMetrics = Blockly.setMainWorkspaceMetrics_;
|
||||
var mainWorkspace = new Blockly.WorkspaceSvg(options);
|
||||
mainWorkspace.scale = options.zoomOptions.startScale;
|
||||
svg.appendChild(mainWorkspace.createDom('blocklyMainBackground'));
|
||||
|
||||
@@ -110,10 +110,9 @@ Blockly.Mutator.prototype.createEditor_ = function() {
|
||||
null);
|
||||
// Convert the list of names into a list of XML objects for the flyout.
|
||||
if (this.quarkNames_.length) {
|
||||
var quarkXml = goog.dom.createUntypedDom('xml');
|
||||
var quarkXml = goog.dom.createDom('xml');
|
||||
for (var i = 0, quarkName; quarkName = this.quarkNames_[i]; i++) {
|
||||
quarkXml.appendChild(
|
||||
goog.dom.createUntypedDom('block', {'type': quarkName}));
|
||||
quarkXml.appendChild(goog.dom.createDom('block', {'type': quarkName}));
|
||||
}
|
||||
} else {
|
||||
var quarkXml = null;
|
||||
@@ -130,6 +129,7 @@ Blockly.Mutator.prototype.createEditor_ = function() {
|
||||
setMetrics: null
|
||||
};
|
||||
this.workspace_ = new Blockly.WorkspaceSvg(workspaceOptions);
|
||||
this.workspace_.isMutator = true;
|
||||
this.svgDialog_.appendChild(
|
||||
this.workspace_.createDom('blocklyMutatorBackground'));
|
||||
return this.svgDialog_;
|
||||
@@ -139,17 +139,23 @@ Blockly.Mutator.prototype.createEditor_ = function() {
|
||||
* Add or remove the UI indicating if this icon may be clicked or not.
|
||||
*/
|
||||
Blockly.Mutator.prototype.updateEditable = function() {
|
||||
if (this.block_.isEditable()) {
|
||||
// Default behaviour for an icon.
|
||||
Blockly.Icon.prototype.updateEditable.call(this);
|
||||
} else {
|
||||
// Close any mutator bubble. Icon is not clickable.
|
||||
this.setVisible(false);
|
||||
if (this.iconGroup_) {
|
||||
Blockly.addClass_(/** @type {!Element} */ (this.iconGroup_),
|
||||
'blocklyIconGroupReadonly');
|
||||
if (!this.block_.isInFlyout) {
|
||||
if (this.block_.isEditable()) {
|
||||
if (this.iconGroup_) {
|
||||
Blockly.removeClass_(/** @type {!Element} */ (this.iconGroup_),
|
||||
'blocklyIconGroupReadonly');
|
||||
}
|
||||
} else {
|
||||
// Close any mutator bubble. Icon is not clickable.
|
||||
this.setVisible(false);
|
||||
if (this.iconGroup_) {
|
||||
Blockly.addClass_(/** @type {!Element} */ (this.iconGroup_),
|
||||
'blocklyIconGroupReadonly');
|
||||
}
|
||||
}
|
||||
}
|
||||
// Default behaviour for an icon.
|
||||
Blockly.Icon.prototype.updateEditable.call(this);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -132,16 +132,14 @@ Blockly.Options.prototype.parentWorkspace = null;
|
||||
|
||||
/**
|
||||
* If set, sets the translation of the workspace to match the scrollbars.
|
||||
* No-op if unset.
|
||||
*/
|
||||
Blockly.Options.prototype.setMetrics = function() { return; };
|
||||
Blockly.Options.prototype.setMetrics = null;
|
||||
|
||||
/**
|
||||
* Return an object with the metrics required to size the workspace, or null
|
||||
* if unset.
|
||||
* @return {Object} Contains size an position metrics, or null.
|
||||
* Return an object with the metrics required to size the workspace.
|
||||
* @return {Object} Contains size and position metrics, or null.
|
||||
*/
|
||||
Blockly.Options.prototype.getMetrics = function() { return null; };
|
||||
Blockly.Options.prototype.getMetrics = null;
|
||||
|
||||
/**
|
||||
* Parse the user-specified zoom options, using reasonable defaults where
|
||||
|
||||
@@ -162,21 +162,21 @@ Blockly.Procedures.flyoutCategory = function(workspace) {
|
||||
var xmlList = [];
|
||||
if (Blockly.Blocks['procedures_defnoreturn']) {
|
||||
// <block type="procedures_defnoreturn" gap="16"></block>
|
||||
var block = goog.dom.createUntypedDom('block');
|
||||
var block = goog.dom.createDom('block');
|
||||
block.setAttribute('type', 'procedures_defnoreturn');
|
||||
block.setAttribute('gap', 16);
|
||||
xmlList.push(block);
|
||||
}
|
||||
if (Blockly.Blocks['procedures_defreturn']) {
|
||||
// <block type="procedures_defreturn" gap="16"></block>
|
||||
var block = goog.dom.createUntypedDom('block');
|
||||
var block = goog.dom.createDom('block');
|
||||
block.setAttribute('type', 'procedures_defreturn');
|
||||
block.setAttribute('gap', 16);
|
||||
xmlList.push(block);
|
||||
}
|
||||
if (Blockly.Blocks['procedures_ifreturn']) {
|
||||
// <block type="procedures_ifreturn" gap="16"></block>
|
||||
var block = goog.dom.createUntypedDom('block');
|
||||
var block = goog.dom.createDom('block');
|
||||
block.setAttribute('type', 'procedures_ifreturn');
|
||||
block.setAttribute('gap', 16);
|
||||
xmlList.push(block);
|
||||
@@ -195,14 +195,14 @@ Blockly.Procedures.flyoutCategory = function(workspace) {
|
||||
// <arg name="x"></arg>
|
||||
// </mutation>
|
||||
// </block>
|
||||
var block = goog.dom.createUntypedDom('block');
|
||||
var block = goog.dom.createDom('block');
|
||||
block.setAttribute('type', templateName);
|
||||
block.setAttribute('gap', 16);
|
||||
var mutation = goog.dom.createUntypedDom('mutation');
|
||||
var mutation = goog.dom.createDom('mutation');
|
||||
mutation.setAttribute('name', name);
|
||||
block.appendChild(mutation);
|
||||
for (var j = 0; j < args.length; j++) {
|
||||
var arg = goog.dom.createUntypedDom('arg');
|
||||
var arg = goog.dom.createDom('arg');
|
||||
arg.setAttribute('name', args[j]);
|
||||
mutation.appendChild(arg);
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ Blockly.RenderedConnection.prototype.bumpAwayFrom_ = function(staticConnection)
|
||||
}
|
||||
// Raise it to the top for extra visibility.
|
||||
var selected = Blockly.selected == rootBlock;
|
||||
selected || rootBlock.select();
|
||||
selected || rootBlock.addSelect();
|
||||
var dx = (staticConnection.x_ + Blockly.SNAP_RADIUS) - this.x_;
|
||||
var dy = (staticConnection.y_ + Blockly.SNAP_RADIUS) - this.y_;
|
||||
if (reverse) {
|
||||
@@ -97,7 +97,7 @@ Blockly.RenderedConnection.prototype.bumpAwayFrom_ = function(staticConnection)
|
||||
dx = -dx;
|
||||
}
|
||||
rootBlock.moveBy(dx, dy);
|
||||
selected || rootBlock.unselect();
|
||||
selected || rootBlock.removeSelect();
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -145,12 +145,13 @@ Blockly.Toolbox.prototype.lastCategory_ = null;
|
||||
*/
|
||||
Blockly.Toolbox.prototype.init = function() {
|
||||
var workspace = this.workspace_;
|
||||
var svg = this.workspace_.getParentSvg();
|
||||
|
||||
// Create an HTML container for the Toolbox menu.
|
||||
this.HtmlDiv =
|
||||
goog.dom.createDom(goog.dom.TagName.DIV, 'blocklyToolboxDiv');
|
||||
this.HtmlDiv.setAttribute('dir', workspace.RTL ? 'RTL' : 'LTR');
|
||||
document.body.appendChild(this.HtmlDiv);
|
||||
svg.parentNode.insertBefore(this.HtmlDiv, svg);
|
||||
|
||||
// Clicking on toolbox closes popups.
|
||||
Blockly.bindEvent_(this.HtmlDiv, 'mousedown', this,
|
||||
@@ -187,8 +188,11 @@ Blockly.Toolbox.prototype.init = function() {
|
||||
tree.setShowLines(false);
|
||||
tree.setShowExpandIcons(false);
|
||||
tree.setSelectedItem(null);
|
||||
this.populate_(workspace.options.languageTree);
|
||||
var openNode = this.populate_(workspace.options.languageTree);
|
||||
tree.render(this.HtmlDiv);
|
||||
if (openNode) {
|
||||
tree.setSelectedItem(openNode);
|
||||
}
|
||||
this.addColour_();
|
||||
this.position();
|
||||
};
|
||||
@@ -233,61 +237,59 @@ Blockly.Toolbox.prototype.position = function() {
|
||||
var svgPosition = goog.style.getPageOffset(svg);
|
||||
var svgSize = Blockly.svgSize(svg);
|
||||
if (this.horizontalLayout_) {
|
||||
treeDiv.style.left = svgPosition.x + 'px';
|
||||
treeDiv.style.left = '0';
|
||||
treeDiv.style.height = 'auto';
|
||||
treeDiv.style.width = svgSize.width + 'px';
|
||||
this.height = treeDiv.offsetHeight;
|
||||
if (this.toolboxPosition == Blockly.TOOLBOX_AT_TOP) { // Top
|
||||
treeDiv.style.top = svgPosition.y + 'px';
|
||||
treeDiv.style.top = '0';
|
||||
} else { // Bottom
|
||||
var topOfToolbox = svgPosition.y + svgSize.height - treeDiv.offsetHeight;
|
||||
treeDiv.style.top = topOfToolbox + 'px';
|
||||
treeDiv.style.bottom = '0';
|
||||
}
|
||||
} else {
|
||||
if (this.toolboxPosition == Blockly.TOOLBOX_AT_RIGHT) { // Right
|
||||
treeDiv.style.left =
|
||||
(svgPosition.x + svgSize.width - treeDiv.offsetWidth) + 'px';
|
||||
treeDiv.style.right = '0';
|
||||
} else { // Left
|
||||
treeDiv.style.left = svgPosition.x + 'px';
|
||||
treeDiv.style.left = '0';
|
||||
}
|
||||
treeDiv.style.height = svgSize.height + 'px';
|
||||
treeDiv.style.top = svgPosition.y + 'px';
|
||||
this.width = treeDiv.offsetWidth;
|
||||
if (this.toolboxPosition == Blockly.TOOLBOX_AT_LEFT) {
|
||||
// For some reason the LTR toolbox now reports as 1px too wide.
|
||||
this.width -= 1;
|
||||
}
|
||||
}
|
||||
this.flyout_.position();
|
||||
};
|
||||
|
||||
/**
|
||||
* Fill the toolbox with categories and blocks.
|
||||
* @param {Node} newTree DOM tree of blocks, or null.
|
||||
* @param {!Node} newTree DOM tree of blocks.
|
||||
* @return {Node} Tree node to open at startup (or null).
|
||||
* @private
|
||||
*/
|
||||
Blockly.Toolbox.prototype.populate_ = function(newTree) {
|
||||
this.tree_.removeChildren(); // Delete any existing content.
|
||||
this.tree_.blocks = [];
|
||||
this.hasColours_ = false;
|
||||
this.syncTrees_(newTree, this.tree_, this.workspace_.options.pathToMedia);
|
||||
var openNode =
|
||||
this.syncTrees_(newTree, this.tree_, this.workspace_.options.pathToMedia);
|
||||
|
||||
if (this.tree_.blocks.length) {
|
||||
throw 'Toolbox cannot have both blocks and categories in the root level.';
|
||||
}
|
||||
|
||||
// Fire a resize event since the toolbox may have changed width and height.
|
||||
Blockly.resizeSvgContents(this.workspace_);
|
||||
this.workspace_.resizeContents();
|
||||
return openNode;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sync trees of the toolbox.
|
||||
* @param {Node} treeIn DOM tree of blocks, or null.
|
||||
* @param {Blockly.Toolbox.TreeControl} treeOut
|
||||
* @param {!Node} treeIn DOM tree of blocks.
|
||||
* @param {!Blockly.Toolbox.TreeControl} treeOut
|
||||
* @param {string} pathToMedia
|
||||
* @return {Node} Tree node to open at startup (or null).
|
||||
* @private
|
||||
*/
|
||||
Blockly.Toolbox.prototype.syncTrees_ = function(treeIn, treeOut, pathToMedia) {
|
||||
var openNode = null;
|
||||
var lastElement = null;
|
||||
for (var i = 0, childIn; childIn = treeIn.childNodes[i]; i++) {
|
||||
if (!childIn.tagName) {
|
||||
@@ -304,7 +306,10 @@ Blockly.Toolbox.prototype.syncTrees_ = function(treeIn, treeOut, pathToMedia) {
|
||||
// Variables and procedures are special dynamic categories.
|
||||
childOut.blocks = custom;
|
||||
} else {
|
||||
this.syncTrees_(childIn, childOut, pathToMedia);
|
||||
var newOpenNode = this.syncTrees_(childIn, childOut, pathToMedia);
|
||||
if (newOpenNode) {
|
||||
openNode = newOpenNode;
|
||||
}
|
||||
}
|
||||
var colour = childIn.getAttribute('colour');
|
||||
if (goog.isString(colour)) {
|
||||
@@ -319,7 +324,9 @@ Blockly.Toolbox.prototype.syncTrees_ = function(treeIn, treeOut, pathToMedia) {
|
||||
}
|
||||
if (childIn.getAttribute('expanded') == 'true') {
|
||||
if (childOut.blocks.length) {
|
||||
this.tree_.setSelectedItem(childOut);
|
||||
// This is a category that directly contians blocks.
|
||||
// After the tree is rendered, open this category and show flyout.
|
||||
openNode = childOut;
|
||||
}
|
||||
childOut.setExpanded(true);
|
||||
} else {
|
||||
@@ -341,10 +348,8 @@ Blockly.Toolbox.prototype.syncTrees_ = function(treeIn, treeOut, pathToMedia) {
|
||||
// Note that a deprecated method is to add a gap to a block.
|
||||
// <block type="math_arithmetic" gap="8"></block>
|
||||
var newGap = parseFloat(childIn.getAttribute('gap'));
|
||||
if (!isNaN(newGap)) {
|
||||
var oldGap = parseFloat(lastElement.getAttribute('gap'));
|
||||
var gap = isNaN(oldGap) ? newGap : oldGap + newGap;
|
||||
lastElement.setAttribute('gap', gap);
|
||||
if (!isNaN(newGap) && lastElement) {
|
||||
lastElement.setAttribute('gap', newGap);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -356,6 +361,7 @@ Blockly.Toolbox.prototype.syncTrees_ = function(treeIn, treeOut, pathToMedia) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return openNode;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -427,6 +433,18 @@ Blockly.Toolbox.prototype.getClientRect = function() {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the flyout's contents without closing it. Should be used in response
|
||||
* to a change in one of the dynamic categories, such as variables or
|
||||
* procedures.
|
||||
*/
|
||||
Blockly.Toolbox.prototype.refreshSelection = function() {
|
||||
var selectedItem = this.tree_.getSelectedItem();
|
||||
if (selectedItem && selectedItem.blocks) {
|
||||
this.flyout_.show(selectedItem.blocks);
|
||||
}
|
||||
};
|
||||
|
||||
// Extending Closure's Tree UI.
|
||||
|
||||
/**
|
||||
@@ -544,6 +562,7 @@ Blockly.Toolbox.TreeControl.prototype.setSelectedItem = function(node) {
|
||||
Blockly.Toolbox.TreeNode = function(toolbox, html, opt_config, opt_domHelper) {
|
||||
goog.ui.tree.TreeNode.call(this, html, opt_config, opt_domHelper);
|
||||
if (toolbox) {
|
||||
this.horizontalLayout_ = toolbox.horizontalLayout_;
|
||||
var resize = function() {
|
||||
// Even though the div hasn't changed size, the visible workspace
|
||||
// surface of the workspace has, so we may need to reposition everything.
|
||||
@@ -595,6 +614,28 @@ Blockly.Toolbox.TreeNode.prototype.onDoubleClick_ = function(e) {
|
||||
// NOP.
|
||||
};
|
||||
|
||||
/**
|
||||
* Remap event.keyCode in horizontalLayout so that arrow
|
||||
* keys work properly and call original onKeyDown handler.
|
||||
* @param {!goog.events.BrowserEvent} e The browser event.
|
||||
* @return {boolean} The handled value.
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
Blockly.Toolbox.TreeNode.prototype.onKeyDown = function(e) {
|
||||
if (this.horizontalLayout_) {
|
||||
var map = {};
|
||||
map[goog.events.KeyCodes.RIGHT] = goog.events.KeyCodes.DOWN;
|
||||
map[goog.events.KeyCodes.LEFT] = goog.events.KeyCodes.UP;
|
||||
map[goog.events.KeyCodes.UP] = goog.events.KeyCodes.LEFT;
|
||||
map[goog.events.KeyCodes.DOWN] = goog.events.KeyCodes.RIGHT;
|
||||
|
||||
var newKeyCode = map[e.keyCode];
|
||||
e.keyCode = newKeyCode || e.keyCode;
|
||||
}
|
||||
return Blockly.Toolbox.TreeNode.superClass_.onKeyDown.call(this, e);
|
||||
};
|
||||
|
||||
/**
|
||||
* A blank separator node in the tree.
|
||||
* @param {Object=} config The configuration for the tree. See
|
||||
|
||||
@@ -37,13 +37,14 @@ goog.require('goog.string');
|
||||
Blockly.Variables.NAME_TYPE = 'VARIABLE';
|
||||
|
||||
/**
|
||||
* Find all user-created variables.
|
||||
* Find all user-created variables that are in use in the workspace.
|
||||
* For use by generators.
|
||||
* @param {!Blockly.Block|!Blockly.Workspace} root Root block or workspace.
|
||||
* @return {!Array.<string>} Array of variable names.
|
||||
*/
|
||||
Blockly.Variables.allVariables = function(root) {
|
||||
Blockly.Variables.allUsedVariables = function(root) {
|
||||
var blocks;
|
||||
if (root.getDescendants) {
|
||||
if (root instanceof Blockly.Block) {
|
||||
// Root is Block.
|
||||
blocks = root.getDescendants();
|
||||
} else if (root.getAllBlocks) {
|
||||
@@ -54,13 +55,15 @@ Blockly.Variables.allVariables = function(root) {
|
||||
}
|
||||
var variableHash = Object.create(null);
|
||||
// Iterate through every block and add each variable to the hash.
|
||||
for (var i = 0; i < blocks.length; i++) {
|
||||
var blockVariables = blocks[i].getVars();
|
||||
for (var j = 0; j < blockVariables.length; j++) {
|
||||
var varName = blockVariables[j];
|
||||
// Variable name may be null if the block is only half-built.
|
||||
if (varName) {
|
||||
variableHash[varName.toLowerCase()] = varName;
|
||||
for (var x = 0; x < blocks.length; x++) {
|
||||
var blockVariables = blocks[x].getVars();
|
||||
if (blockVariables) {
|
||||
for (var y = 0; y < blockVariables.length; y++) {
|
||||
var varName = blockVariables[y];
|
||||
// Variable name may be null if the block is only half-built.
|
||||
if (varName) {
|
||||
variableHash[varName.toLowerCase()] = varName;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,19 +76,19 @@ Blockly.Variables.allVariables = function(root) {
|
||||
};
|
||||
|
||||
/**
|
||||
* Find all instances of the specified variable and rename them.
|
||||
* @param {string} oldName Variable to rename.
|
||||
* @param {string} newName New variable name.
|
||||
* @param {!Blockly.Workspace} workspace Workspace rename variables in.
|
||||
* Find all variables that the user has created through the workspace or
|
||||
* toolbox. For use by generators.
|
||||
* @param {!Blockly.Workspace} root The workspace to inspect.
|
||||
* @return {!Array.<string>} Array of variable names.
|
||||
*/
|
||||
Blockly.Variables.renameVariable = function(oldName, newName, workspace) {
|
||||
Blockly.Events.setGroup(true);
|
||||
var blocks = workspace.getAllBlocks();
|
||||
// Iterate through every block.
|
||||
for (var i = 0; i < blocks.length; i++) {
|
||||
blocks[i].renameVar(oldName, newName);
|
||||
Blockly.Variables.allVariables = function(root) {
|
||||
if (root instanceof Blockly.Block) {
|
||||
// Root is Block.
|
||||
console.warn('Deprecated call to Blockly.Variables.allVariables ' +
|
||||
'with a block instead of a workspace. You may want ' +
|
||||
'Blockly.Variables.allUsedVariables');
|
||||
}
|
||||
Blockly.Events.setGroup(false);
|
||||
return root.variableList;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -94,44 +97,79 @@ Blockly.Variables.renameVariable = function(oldName, newName, workspace) {
|
||||
* @return {!Array.<!Element>} Array of XML block elements.
|
||||
*/
|
||||
Blockly.Variables.flyoutCategory = function(workspace) {
|
||||
var variableList = Blockly.Variables.allVariables(workspace);
|
||||
var variableList = workspace.variableList;
|
||||
variableList.sort(goog.string.caseInsensitiveCompare);
|
||||
// In addition to the user's variables, we also want to display the default
|
||||
// variable name at the top. We also don't want this duplicated if the
|
||||
// user has created a variable of the same name.
|
||||
goog.array.remove(variableList, Blockly.Msg.VARIABLES_DEFAULT_NAME);
|
||||
variableList.unshift(Blockly.Msg.VARIABLES_DEFAULT_NAME);
|
||||
|
||||
var xmlList = [];
|
||||
for (var i = 0; i < variableList.length; i++) {
|
||||
var button = goog.dom.createDom('button');
|
||||
button.setAttribute('text', Blockly.Msg.NEW_VARIABLE);
|
||||
xmlList.push(button);
|
||||
|
||||
if (variableList.length > 0) {
|
||||
if (Blockly.Blocks['variables_set']) {
|
||||
// <block type="variables_set" gap="8">
|
||||
// <block type="variables_set" gap="20">
|
||||
// <field name="VAR">item</field>
|
||||
// </block>
|
||||
var block = goog.dom.createUntypedDom('block');
|
||||
var block = goog.dom.createDom('block');
|
||||
block.setAttribute('type', 'variables_set');
|
||||
if (Blockly.Blocks['variables_get']) {
|
||||
if (Blockly.Blocks['math_change']) {
|
||||
block.setAttribute('gap', 8);
|
||||
} else {
|
||||
block.setAttribute('gap', 24);
|
||||
}
|
||||
var field = goog.dom.createUntypedDom('field', null, variableList[i]);
|
||||
var field = goog.dom.createDom('field', null, variableList[0]);
|
||||
field.setAttribute('name', 'VAR');
|
||||
block.appendChild(field);
|
||||
xmlList.push(block);
|
||||
}
|
||||
if (Blockly.Blocks['variables_get']) {
|
||||
// <block type="variables_get" gap="24">
|
||||
// <field name="VAR">item</field>
|
||||
if (Blockly.Blocks['math_change']) {
|
||||
// <block type="math_change">
|
||||
// <value name="DELTA">
|
||||
// <shadow type="math_number">
|
||||
// <field name="NUM">1</field>
|
||||
// </shadow>
|
||||
// </value>
|
||||
// </block>
|
||||
var block = goog.dom.createUntypedDom('block');
|
||||
block.setAttribute('type', 'variables_get');
|
||||
if (Blockly.Blocks['variables_set']) {
|
||||
block.setAttribute('gap', 24);
|
||||
var block = goog.dom.createDom('block');
|
||||
block.setAttribute('type', 'math_change');
|
||||
if (Blockly.Blocks['variables_get']) {
|
||||
block.setAttribute('gap', 20);
|
||||
}
|
||||
var field = goog.dom.createUntypedDom('field', null, variableList[i]);
|
||||
var value = goog.dom.createDom('value');
|
||||
value.setAttribute('name', 'DELTA');
|
||||
block.appendChild(value);
|
||||
|
||||
var field = goog.dom.createDom('field', null, variableList[0]);
|
||||
field.setAttribute('name', 'VAR');
|
||||
block.appendChild(field);
|
||||
|
||||
var shadowBlock = goog.dom.createDom('shadow');
|
||||
shadowBlock.setAttribute('type', 'math_number');
|
||||
value.appendChild(shadowBlock);
|
||||
|
||||
var numberField = goog.dom.createDom('field', null, '1');
|
||||
numberField.setAttribute('name', 'NUM');
|
||||
shadowBlock.appendChild(numberField);
|
||||
|
||||
xmlList.push(block);
|
||||
}
|
||||
|
||||
for (var i = 0; i < variableList.length; i++) {
|
||||
if (Blockly.Blocks['variables_get']) {
|
||||
// <block type="variables_get" gap="8">
|
||||
// <field name="VAR">item</field>
|
||||
// </block>
|
||||
var block = goog.dom.createDom('block');
|
||||
block.setAttribute('type', 'variables_get');
|
||||
if (Blockly.Blocks['variables_set']) {
|
||||
block.setAttribute('gap', 8);
|
||||
}
|
||||
var field = goog.dom.createDom('field', null, variableList[i]);
|
||||
field.setAttribute('name', 'VAR');
|
||||
block.appendChild(field);
|
||||
xmlList.push(block);
|
||||
}
|
||||
}
|
||||
}
|
||||
return xmlList;
|
||||
};
|
||||
@@ -145,7 +183,7 @@ Blockly.Variables.flyoutCategory = function(workspace) {
|
||||
* @return {string} New variable name.
|
||||
*/
|
||||
Blockly.Variables.generateUniqueName = function(workspace) {
|
||||
var variableList = Blockly.Variables.allVariables(workspace);
|
||||
var variableList = workspace.variableList;
|
||||
var newName = '';
|
||||
if (variableList.length) {
|
||||
var nameSuffix = 1;
|
||||
@@ -184,3 +222,52 @@ Blockly.Variables.generateUniqueName = function(workspace) {
|
||||
}
|
||||
return newName;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new variable on the given workspace.
|
||||
* @param {!Blockly.Workspace} workspace The workspace on which to create the
|
||||
* variable.
|
||||
* @return {null|undefined|string} An acceptable new variable name, or null if
|
||||
* change is to be aborted (cancel button), or undefined if an existing
|
||||
* variable was chosen.
|
||||
*/
|
||||
Blockly.Variables.createVariable = function(workspace) {
|
||||
while (true) {
|
||||
var text = Blockly.Variables.promptName(Blockly.Msg.NEW_VARIABLE_TITLE, '');
|
||||
if (text) {
|
||||
if (workspace.variableIndexOf(text) != -1) {
|
||||
window.alert(Blockly.Msg.VARIABLE_ALREADY_EXISTS.replace('%1',
|
||||
text.toLowerCase()));
|
||||
} else {
|
||||
workspace.createVariable(text);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
text = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return text;
|
||||
};
|
||||
|
||||
/**
|
||||
* Prompt the user for a new variable name.
|
||||
* @param {string} promptText The string of the prompt.
|
||||
* @param {string} defaultText The default value to show in the prompt's field.
|
||||
* @return {?string} The new variable name, or null if the user picked
|
||||
* something illegal.
|
||||
*/
|
||||
Blockly.Variables.promptName = function(promptText, defaultText) {
|
||||
var newVar = window.prompt(promptText, defaultText);
|
||||
// Merge runs of whitespace. Strip leading and trailing whitespace.
|
||||
// Beyond this, all names are legal.
|
||||
if (newVar) {
|
||||
newVar = newVar.replace(/[\s\xa0]+/g, ' ').replace(/^ | $/g, '');
|
||||
if (newVar == Blockly.Msg.RENAME_VARIABLE ||
|
||||
newVar == Blockly.Msg.NEW_VARIABLE) {
|
||||
// Ok, not ALL names are legal...
|
||||
newVar = null;
|
||||
}
|
||||
}
|
||||
return newVar;
|
||||
};
|
||||
|
||||
@@ -95,7 +95,6 @@ Blockly.WidgetDiv.hide = function() {
|
||||
Blockly.WidgetDiv.DIV.style.display = 'none';
|
||||
Blockly.WidgetDiv.DIV.style.left = '';
|
||||
Blockly.WidgetDiv.DIV.style.top = '';
|
||||
Blockly.WidgetDiv.DIV.style.height = '';
|
||||
Blockly.WidgetDiv.dispose_ && Blockly.WidgetDiv.dispose_();
|
||||
Blockly.WidgetDiv.dispose_ = null;
|
||||
goog.dom.removeChildren(Blockly.WidgetDiv.DIV);
|
||||
@@ -149,6 +148,5 @@ Blockly.WidgetDiv.position = function(anchorX, anchorY, windowSize,
|
||||
}
|
||||
Blockly.WidgetDiv.DIV.style.left = anchorX + 'px';
|
||||
Blockly.WidgetDiv.DIV.style.top = anchorY + 'px';
|
||||
Blockly.WidgetDiv.DIV.style.height =
|
||||
(windowSize.height - anchorY + scrollOffset.y) + 'px';
|
||||
Blockly.WidgetDiv.DIV.style.height = windowSize.height + 'px';
|
||||
};
|
||||
|
||||
@@ -73,6 +73,12 @@ Blockly.Workspace = function(opt_options) {
|
||||
* @private
|
||||
*/
|
||||
this.blockDB_ = Object.create(null);
|
||||
/*
|
||||
* @type {!Array.<!string>}
|
||||
* A list of all of the named variables in the workspace, including variables
|
||||
* that are not currently in use.
|
||||
*/
|
||||
this.variableList = [];
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -112,6 +118,17 @@ Blockly.Workspace.SCAN_ANGLE = 3;
|
||||
*/
|
||||
Blockly.Workspace.prototype.addTopBlock = function(block) {
|
||||
this.topBlocks_.push(block);
|
||||
if (this.isFlyout) {
|
||||
// This is for the (unlikely) case where you have a variable in a block in
|
||||
// an always-open flyout. It needs to be possible to edit the block in the
|
||||
// flyout, so the contents of the dropdown need to be correct.
|
||||
var variables = Blockly.Variables.allUsedVariables(block);
|
||||
for (var i = 0; i < variables.length; i++) {
|
||||
if (this.variableList.indexOf(variables[i]) == -1) {
|
||||
this.variableList.push(variables[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -181,6 +198,158 @@ Blockly.Workspace.prototype.clear = function() {
|
||||
if (!existingGroup) {
|
||||
Blockly.Events.setGroup(false);
|
||||
}
|
||||
|
||||
this.variableList.length = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Walk the workspace and update the list of variables to only contain ones in
|
||||
* use on the workspace. Use when loading new workspaces from disk.
|
||||
* @param {boolean} clearList True if the old variable list should be cleared.
|
||||
*/
|
||||
Blockly.Workspace.prototype.updateVariableList = function(clearList) {
|
||||
// TODO: Sort
|
||||
if (!this.isFlyout) {
|
||||
// Update the list in place so that the flyout's references stay correct.
|
||||
if (clearList) {
|
||||
this.variableList.length = 0;
|
||||
}
|
||||
var allVariables = Blockly.Variables.allUsedVariables(this);
|
||||
for (var i = 0; i < allVariables.length; i++) {
|
||||
this.createVariable(allVariables[i]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Rename a variable by updating its name in the variable list.
|
||||
* TODO: #468
|
||||
* @param {string} oldName Variable to rename.
|
||||
* @param {string} newName New variable name.
|
||||
*/
|
||||
Blockly.Workspace.prototype.renameVariable = function(oldName, newName) {
|
||||
// Find the old name in the list.
|
||||
var variableIndex = this.variableIndexOf(oldName);
|
||||
var newVariableIndex = this.variableIndexOf(newName);
|
||||
|
||||
// We might be renaming to an existing name but with different case. If so,
|
||||
// we will also update all of the blocks using the new name to have the
|
||||
// correct case.
|
||||
if (newVariableIndex != -1 &&
|
||||
this.variableList[newVariableIndex] != newName) {
|
||||
var oldCase = this.variableList[newVariableIndex];
|
||||
}
|
||||
|
||||
Blockly.Events.setGroup(true);
|
||||
var blocks = this.getAllBlocks();
|
||||
// Iterate through every block.
|
||||
for (var i = 0; i < blocks.length; i++) {
|
||||
blocks[i].renameVar(oldName, newName);
|
||||
if (oldCase) {
|
||||
blocks[i].renameVar(oldCase, newName);
|
||||
}
|
||||
}
|
||||
Blockly.Events.setGroup(false);
|
||||
|
||||
|
||||
if (variableIndex == newVariableIndex ||
|
||||
variableIndex != -1 && newVariableIndex == -1) {
|
||||
// Only changing case, or renaming to a completely novel name.
|
||||
this.variableList[variableIndex] = newName;
|
||||
} else if (variableIndex != -1 && newVariableIndex != -1) {
|
||||
// Renaming one existing variable to another existing variable.
|
||||
this.variableList.splice(variableIndex, 1);
|
||||
// The case might have changed.
|
||||
this.variableList[newVariableIndex] = newName;
|
||||
} else {
|
||||
this.variableList.push(newName);
|
||||
console.log('Tried to rename an non-existent variable.');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a variable with the given name.
|
||||
* TODO: #468
|
||||
* @param {string} name The new variable's name.
|
||||
*/
|
||||
Blockly.Workspace.prototype.createVariable = function(name) {
|
||||
var index = this.variableIndexOf(name);
|
||||
if (index == -1) {
|
||||
this.variableList.push(name);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Find all the uses of a named variable.
|
||||
* @param {string} name Name of variable.
|
||||
* @return {!Array.<!Blockly.Block>} Array of block usages.
|
||||
*/
|
||||
Blockly.Workspace.prototype.getVariableUses = function(name) {
|
||||
var uses = [];
|
||||
var blocks = this.getAllBlocks();
|
||||
// Iterate through every block and check the name.
|
||||
for (var i = 0; i < blocks.length; i++) {
|
||||
var blockVariables = blocks[i].getVars();
|
||||
if (blockVariables) {
|
||||
for (var j = 0; j < blockVariables.length; j++) {
|
||||
var varName = blockVariables[j];
|
||||
// Variable name may be null if the block is only half-built.
|
||||
if (varName && Blockly.Names.equals(varName, name)) {
|
||||
uses.push(blocks[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return uses;
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete a variables and all of its uses from this workspace.
|
||||
* @param {string} name Name of variable to delete.
|
||||
*/
|
||||
Blockly.Workspace.prototype.deleteVariable = function(name) {
|
||||
var variableIndex = this.variableIndexOf(name);
|
||||
if (variableIndex != -1) {
|
||||
var uses = this.getVariableUses(name);
|
||||
if (uses.length > 1) {
|
||||
for (var i = 0, block; block = uses[i]; i++) {
|
||||
if (block.type == 'procedures_defnoreturn' ||
|
||||
block.type == 'procedures_defreturn') {
|
||||
var procedureName = block.getFieldValue('NAME');
|
||||
window.alert(
|
||||
Blockly.Msg.CANNOT_DELETE_VARIABLE_PROCEDURE.replace('%1', name).
|
||||
replace('%2', procedureName));
|
||||
return;
|
||||
}
|
||||
}
|
||||
window.confirm(
|
||||
Blockly.Msg.DELETE_VARIABLE_CONFIRMATION.replace('%1', uses.length).
|
||||
replace('%2', name));
|
||||
}
|
||||
|
||||
Blockly.Events.setGroup(true);
|
||||
for (var i = 0; i < uses.length; i++) {
|
||||
uses[i].dispose(true, false);
|
||||
}
|
||||
Blockly.Events.setGroup(false);
|
||||
this.variableList.splice(variableIndex, 1);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check whether a variable exists with the given name. The check is
|
||||
* case-insensitive.
|
||||
* @param {string} name The name to check for.
|
||||
* @return {number} The index of the name in the variable list, or -1 if it is
|
||||
* not present.
|
||||
*/
|
||||
Blockly.Workspace.prototype.variableIndexOf = function(name) {
|
||||
for (var i = 0, varname; varname = this.variableList[i]; i++) {
|
||||
if (Blockly.Names.equals(varname, name)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -50,8 +50,10 @@ goog.require('goog.userAgent');
|
||||
*/
|
||||
Blockly.WorkspaceSvg = function(options) {
|
||||
Blockly.WorkspaceSvg.superClass_.constructor.call(this, options);
|
||||
this.getMetrics = options.getMetrics;
|
||||
this.setMetrics = options.setMetrics;
|
||||
this.getMetrics =
|
||||
options.getMetrics || Blockly.WorkspaceSvg.getTopLevelWorkspaceMetrics_;
|
||||
this.setMetrics =
|
||||
options.setMetrics || Blockly.WorkspaceSvg.setTopLevelWorkspaceMetrics_;
|
||||
|
||||
Blockly.ConnectionDB.init(this);
|
||||
|
||||
@@ -82,6 +84,13 @@ Blockly.WorkspaceSvg.prototype.rendered = true;
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.isFlyout = false;
|
||||
|
||||
/**
|
||||
* Is this workspace the surface for a mutator?
|
||||
* @type {boolean}
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.isMutator = false;
|
||||
|
||||
/**
|
||||
* Is this workspace currently being dragged around?
|
||||
* DRAG_NONE - No drag operation.
|
||||
@@ -147,6 +156,15 @@ Blockly.WorkspaceSvg.prototype.scrollbar = null;
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.lastSound_ = null;
|
||||
|
||||
/**
|
||||
* Last known position of the page scroll.
|
||||
* This is used to determine whether we have recalculated screen coordinate
|
||||
* stuff since the page scrolled.
|
||||
* @type {!goog.math.Coordinate}
|
||||
* @private
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.lastRecordedPageScroll_ = null;
|
||||
|
||||
/**
|
||||
* Inverted screen CTM, for use in mouseToSvg.
|
||||
* @type {SVGMatrix}
|
||||
@@ -219,13 +237,16 @@ Blockly.WorkspaceSvg.prototype.createDom = function(opt_backgroundClass) {
|
||||
if (this.options.zoomOptions && this.options.zoomOptions.controls) {
|
||||
bottom = this.addZoomControls_(bottom);
|
||||
}
|
||||
Blockly.bindEvent_(this.svgGroup_, 'mousedown', this, this.onMouseDown_);
|
||||
var thisWorkspace = this;
|
||||
Blockly.bindEvent_(this.svgGroup_, 'touchstart', null,
|
||||
function(e) {Blockly.longStart_(e, thisWorkspace);});
|
||||
if (this.options.zoomOptions && this.options.zoomOptions.wheel) {
|
||||
// Mouse-wheel.
|
||||
Blockly.bindEvent_(this.svgGroup_, 'wheel', this, this.onMouseWheel_);
|
||||
|
||||
if (!this.isFlyout) {
|
||||
Blockly.bindEvent_(this.svgGroup_, 'mousedown', this, this.onMouseDown_);
|
||||
var thisWorkspace = this;
|
||||
Blockly.bindEvent_(this.svgGroup_, 'touchstart', null,
|
||||
function(e) {Blockly.longStart_(e, thisWorkspace);});
|
||||
if (this.options.zoomOptions && this.options.zoomOptions.wheel) {
|
||||
// Mouse-wheel.
|
||||
Blockly.bindEvent_(this.svgGroup_, 'wheel', this, this.onMouseWheel_);
|
||||
}
|
||||
}
|
||||
|
||||
// Determine if there needs to be a category tree, or a simple list of
|
||||
@@ -275,8 +296,9 @@ Blockly.WorkspaceSvg.prototype.dispose = function() {
|
||||
this.zoomControls_ = null;
|
||||
}
|
||||
if (!this.options.parentWorkspace) {
|
||||
// Top-most workspace. Dispose of the SVG too.
|
||||
goog.dom.removeNode(this.getParentSvg());
|
||||
// Top-most workspace. Dispose of the div that the
|
||||
// svg is injected into (i.e. injectionDiv).
|
||||
goog.dom.removeNode(this.getParentSvg().parentNode);
|
||||
}
|
||||
if (this.resizeHandlerWrapper_) {
|
||||
Blockly.unbindEvent_(this.resizeHandlerWrapper_);
|
||||
@@ -343,6 +365,16 @@ Blockly.WorkspaceSvg.prototype.addFlyout_ = function() {
|
||||
this.svgGroup_.insertBefore(svgFlyout, this.svgBlockCanvas_);
|
||||
};
|
||||
|
||||
/**
|
||||
* Update items that use screen coordinate calculations
|
||||
* because something has changed (e.g. scroll position, window size).
|
||||
* @private
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.updateScreenCalculations_ = function() {
|
||||
this.updateInverseScreenCTM();
|
||||
this.recordDeleteAreas();
|
||||
};
|
||||
|
||||
/**
|
||||
* Resize the parts of the workspace that change when the workspace
|
||||
* contents (e.g. block positions) change. This will also scroll the
|
||||
@@ -382,11 +414,25 @@ Blockly.WorkspaceSvg.prototype.resize = function() {
|
||||
if (this.scrollbar) {
|
||||
this.scrollbar.resize();
|
||||
}
|
||||
|
||||
this.updateInverseScreenCTM();
|
||||
this.recordDeleteAreas();
|
||||
this.updateScreenCalculations_();
|
||||
};
|
||||
|
||||
/**
|
||||
* Resizes and repositions workspace chrome if the page has a new
|
||||
* scroll position.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.updateScreenCalculationsIfScrolled
|
||||
= function() {
|
||||
/* eslint-disable indent */
|
||||
var currScroll = goog.dom.getDocumentScroll();
|
||||
if (!goog.math.Coordinate.equals(this.lastRecordedPageScroll_,
|
||||
currScroll)) {
|
||||
this.lastRecordedPageScroll_ = currScroll;
|
||||
this.updateScreenCalculations_();
|
||||
}
|
||||
}; /* eslint-enable indent */
|
||||
|
||||
/**
|
||||
* Get the SVG element that forms the drawing surface.
|
||||
* @return {!Element} SVG element.
|
||||
@@ -592,6 +638,19 @@ Blockly.WorkspaceSvg.prototype.paste = function(xmlBlock) {
|
||||
block.select();
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new variable with the given name. Update the flyout to show the new
|
||||
* variable immediately.
|
||||
* TODO: #468
|
||||
* @param {string} name The new variable's name.
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.createVariable = function(name) {
|
||||
Blockly.WorkspaceSvg.superClass_.createVariable.call(this, name);
|
||||
if (this.toolbox_ && this.toolbox_.flyout_) {
|
||||
this.toolbox_.refreshSelection();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Make a list of all the delete areas for this workspace.
|
||||
*/
|
||||
@@ -715,11 +774,13 @@ Blockly.WorkspaceSvg.prototype.moveDrag = function(e) {
|
||||
};
|
||||
|
||||
/**
|
||||
* Is the user currently dragging a block or scrolling the workspace?
|
||||
* Is the user currently dragging a block or scrolling the flyout/workspace?
|
||||
* @return {boolean} True if currently dragging or scrolling.
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.isDragging = function() {
|
||||
return Blockly.dragMode_ == Blockly.DRAG_FREE ||
|
||||
(Blockly.Flyout.startFlyout_ &&
|
||||
Blockly.Flyout.startFlyout_.dragMode_ == Blockly.DRAG_FREE) ||
|
||||
this.dragMode_ == Blockly.DRAG_FREE;
|
||||
};
|
||||
|
||||
@@ -780,9 +841,8 @@ Blockly.WorkspaceSvg.prototype.getBlocksBoundingBox = function() {
|
||||
|
||||
/**
|
||||
* Clean up the workspace by ordering all the blocks in a column.
|
||||
* @private
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.cleanUp_ = function() {
|
||||
Blockly.WorkspaceSvg.prototype.cleanUp = function() {
|
||||
Blockly.Events.setGroup(true);
|
||||
var topBlocks = this.getTopBlocks(true);
|
||||
var cursorY = 0;
|
||||
@@ -795,7 +855,7 @@ Blockly.WorkspaceSvg.prototype.cleanUp_ = function() {
|
||||
}
|
||||
Blockly.Events.setGroup(false);
|
||||
// Fire an event to allow scrollbars to resize.
|
||||
Blockly.resizeSvgContents(this);
|
||||
this.resizeContents();
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -828,7 +888,7 @@ Blockly.WorkspaceSvg.prototype.showContextMenu_ = function(e) {
|
||||
var cleanOption = {};
|
||||
cleanOption.text = Blockly.Msg.CLEAN_UP;
|
||||
cleanOption.enabled = topBlocks.length > 1;
|
||||
cleanOption.callback = this.cleanUp_.bind(this);
|
||||
cleanOption.callback = this.cleanUp.bind(this);
|
||||
menuOptions.push(cleanOption);
|
||||
}
|
||||
|
||||
@@ -1215,6 +1275,126 @@ Blockly.WorkspaceSvg.prototype.updateGridPattern_ = function() {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Return an object with all the metrics required to size scrollbars for a
|
||||
* top level workspace. The following properties are computed:
|
||||
* .viewHeight: Height of the visible rectangle,
|
||||
* .viewWidth: Width of the visible rectangle,
|
||||
* .contentHeight: Height of the contents,
|
||||
* .contentWidth: Width of the content,
|
||||
* .viewTop: Offset of top edge of visible rectangle from parent,
|
||||
* .viewLeft: Offset of left edge of visible rectangle from parent,
|
||||
* .contentTop: Offset of the top-most content from the y=0 coordinate,
|
||||
* .contentLeft: Offset of the left-most content from the x=0 coordinate.
|
||||
* .absoluteTop: Top-edge of view.
|
||||
* .absoluteLeft: Left-edge of view.
|
||||
* .toolboxWidth: Width of toolbox, if it exists. Otherwise zero.
|
||||
* .toolboxHeight: Height of toolbox, if it exists. Otherwise zero.
|
||||
* .flyoutWidth: Width of the flyout if it is always open. Otherwise zero.
|
||||
* .flyoutHeight: Height of flyout if it is always open. Otherwise zero.
|
||||
* .toolboxPosition: Top, bottom, left or right.
|
||||
* @return {Object} Contains size and position metrics of a top level workspace.
|
||||
* @private
|
||||
*/
|
||||
Blockly.WorkspaceSvg.getTopLevelWorkspaceMetrics_ = function() {
|
||||
var svgSize = Blockly.svgSize(this.getParentSvg());
|
||||
if (this.toolbox_) {
|
||||
if (this.toolboxPosition == Blockly.TOOLBOX_AT_TOP ||
|
||||
this.toolboxPosition == Blockly.TOOLBOX_AT_BOTTOM) {
|
||||
svgSize.height -= this.toolbox_.getHeight();
|
||||
} else if (this.toolboxPosition == Blockly.TOOLBOX_AT_LEFT ||
|
||||
this.toolboxPosition == Blockly.TOOLBOX_AT_RIGHT) {
|
||||
svgSize.width -= this.toolbox_.getWidth();
|
||||
}
|
||||
}
|
||||
// Set the margin to match the flyout's margin so that the workspace does
|
||||
// not jump as blocks are added.
|
||||
var MARGIN = Blockly.Flyout.prototype.CORNER_RADIUS - 1;
|
||||
var viewWidth = svgSize.width - MARGIN;
|
||||
var viewHeight = svgSize.height - MARGIN;
|
||||
var blockBox = this.getBlocksBoundingBox();
|
||||
|
||||
// Fix scale.
|
||||
var contentWidth = blockBox.width * this.scale;
|
||||
var contentHeight = blockBox.height * this.scale;
|
||||
var contentX = blockBox.x * this.scale;
|
||||
var contentY = blockBox.y * this.scale;
|
||||
if (this.scrollbar) {
|
||||
// Add a border around the content that is at least half a screenful wide.
|
||||
// Ensure border is wide enough that blocks can scroll over entire screen.
|
||||
var leftEdge = Math.min(contentX - viewWidth / 2,
|
||||
contentX + contentWidth - viewWidth);
|
||||
var rightEdge = Math.max(contentX + contentWidth + viewWidth / 2,
|
||||
contentX + viewWidth);
|
||||
var topEdge = Math.min(contentY - viewHeight / 2,
|
||||
contentY + contentHeight - viewHeight);
|
||||
var bottomEdge = Math.max(contentY + contentHeight + viewHeight / 2,
|
||||
contentY + viewHeight);
|
||||
} else {
|
||||
var leftEdge = blockBox.x;
|
||||
var rightEdge = leftEdge + blockBox.width;
|
||||
var topEdge = blockBox.y;
|
||||
var bottomEdge = topEdge + blockBox.height;
|
||||
}
|
||||
var absoluteLeft = 0;
|
||||
if (this.toolbox_ && this.toolboxPosition == Blockly.TOOLBOX_AT_LEFT) {
|
||||
absoluteLeft = this.toolbox_.getWidth();
|
||||
}
|
||||
var absoluteTop = 0;
|
||||
if (this.toolbox_ && this.toolboxPosition == Blockly.TOOLBOX_AT_TOP) {
|
||||
absoluteTop = this.toolbox_.getHeight();
|
||||
}
|
||||
|
||||
var metrics = {
|
||||
viewHeight: svgSize.height,
|
||||
viewWidth: svgSize.width,
|
||||
contentHeight: bottomEdge - topEdge,
|
||||
contentWidth: rightEdge - leftEdge,
|
||||
viewTop: -this.scrollY,
|
||||
viewLeft: -this.scrollX,
|
||||
contentTop: topEdge,
|
||||
contentLeft: leftEdge,
|
||||
absoluteTop: absoluteTop,
|
||||
absoluteLeft: absoluteLeft,
|
||||
toolboxWidth: this.toolbox_ ? this.toolbox_.getWidth() : 0,
|
||||
toolboxHeight: this.toolbox_ ? this.toolbox_.getHeight() : 0,
|
||||
flyoutWidth: this.flyout_ ? this.flyout_.getWidth() : 0,
|
||||
flyoutHeight: this.flyout_ ? this.flyout_.getHeight() : 0,
|
||||
toolboxPosition: this.toolboxPosition
|
||||
};
|
||||
return metrics;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the X/Y translations of a top level workspace to match the scrollbars.
|
||||
* @param {!Object} xyRatio Contains an x and/or y property which is a float
|
||||
* between 0 and 1 specifying the degree of scrolling.
|
||||
* @private
|
||||
*/
|
||||
Blockly.WorkspaceSvg.setTopLevelWorkspaceMetrics_ = function(xyRatio) {
|
||||
if (!this.scrollbar) {
|
||||
throw 'Attempt to set top level workspace scroll without scrollbars.';
|
||||
}
|
||||
var metrics = this.getMetrics();
|
||||
if (goog.isNumber(xyRatio.x)) {
|
||||
this.scrollX = -metrics.contentWidth * xyRatio.x - metrics.contentLeft;
|
||||
}
|
||||
if (goog.isNumber(xyRatio.y)) {
|
||||
this.scrollY = -metrics.contentHeight * xyRatio.y - metrics.contentTop;
|
||||
}
|
||||
var x = this.scrollX + metrics.absoluteLeft;
|
||||
var y = this.scrollY + metrics.absoluteTop;
|
||||
this.translate(x, y);
|
||||
if (this.options.gridPattern) {
|
||||
this.options.gridPattern.setAttribute('x', x);
|
||||
this.options.gridPattern.setAttribute('y', y);
|
||||
if (goog.userAgent.IE) {
|
||||
// IE doesn't notice that the x/y offsets have changed. Force an update.
|
||||
this.updateGridPattern_();
|
||||
}
|
||||
}
|
||||
};
|
||||
// Export symbols that would otherwise be renamed by Closure compiler.
|
||||
Blockly.WorkspaceSvg.prototype['setVisible'] =
|
||||
Blockly.WorkspaceSvg.prototype.setVisible;
|
||||
|
||||
25
core/xml.js
25
core/xml.js
@@ -36,7 +36,7 @@ goog.require('goog.dom');
|
||||
* @return {!Element} XML document.
|
||||
*/
|
||||
Blockly.Xml.workspaceToDom = function(workspace) {
|
||||
var xml = goog.dom.createUntypedDom('xml');
|
||||
var xml = goog.dom.createDom('xml');
|
||||
var blocks = workspace.getTopBlocks(true);
|
||||
for (var i = 0, block; block = blocks[i]; i++) {
|
||||
xml.appendChild(Blockly.Xml.blockToDomWithXY(block));
|
||||
@@ -68,8 +68,7 @@ Blockly.Xml.blockToDomWithXY = function(block) {
|
||||
* @return {!Element} Tree of XML elements.
|
||||
*/
|
||||
Blockly.Xml.blockToDom = function(block) {
|
||||
var element = goog.dom.createUntypedDom(
|
||||
block.isShadow() ? 'shadow' : 'block');
|
||||
var element = goog.dom.createDom(block.isShadow() ? 'shadow' : 'block');
|
||||
element.setAttribute('type', block.type);
|
||||
element.setAttribute('id', block.id);
|
||||
if (block.mutationToDom) {
|
||||
@@ -81,8 +80,7 @@ Blockly.Xml.blockToDom = function(block) {
|
||||
}
|
||||
function fieldToDom(field) {
|
||||
if (field.name && field.EDITABLE) {
|
||||
var container =
|
||||
goog.dom.createUntypedDom('field', null, field.getValue());
|
||||
var container = goog.dom.createDom('field', null, field.getValue());
|
||||
container.setAttribute('name', field.name);
|
||||
element.appendChild(container);
|
||||
}
|
||||
@@ -95,8 +93,7 @@ Blockly.Xml.blockToDom = function(block) {
|
||||
|
||||
var commentText = block.getCommentText();
|
||||
if (commentText) {
|
||||
var commentElement =
|
||||
goog.dom.createUntypedDom('comment', null, commentText);
|
||||
var commentElement = goog.dom.createDom('comment', null, commentText);
|
||||
if (typeof block.comment == 'object') {
|
||||
commentElement.setAttribute('pinned', block.comment.isVisible());
|
||||
var hw = block.comment.getBubbleSize();
|
||||
@@ -107,7 +104,7 @@ Blockly.Xml.blockToDom = function(block) {
|
||||
}
|
||||
|
||||
if (block.data) {
|
||||
var dataElement = goog.dom.createUntypedDom('data', null, block.data);
|
||||
var dataElement = goog.dom.createDom('data', null, block.data);
|
||||
element.appendChild(dataElement);
|
||||
}
|
||||
|
||||
@@ -119,9 +116,9 @@ Blockly.Xml.blockToDom = function(block) {
|
||||
} else {
|
||||
var childBlock = input.connection.targetBlock();
|
||||
if (input.type == Blockly.INPUT_VALUE) {
|
||||
container = goog.dom.createUntypedDom('value');
|
||||
container = goog.dom.createDom('value');
|
||||
} else if (input.type == Blockly.NEXT_STATEMENT) {
|
||||
container = goog.dom.createUntypedDom('statement');
|
||||
container = goog.dom.createDom('statement');
|
||||
}
|
||||
var shadow = input.connection.getShadowDom();
|
||||
if (shadow && (!childBlock || !childBlock.isShadow())) {
|
||||
@@ -158,8 +155,8 @@ Blockly.Xml.blockToDom = function(block) {
|
||||
|
||||
var nextBlock = block.getNextBlock();
|
||||
if (nextBlock) {
|
||||
var container = goog.dom.createUntypedDom(
|
||||
'next', null, Blockly.Xml.blockToDom(nextBlock));
|
||||
var container = goog.dom.createDom('next', null,
|
||||
Blockly.Xml.blockToDom(nextBlock));
|
||||
element.appendChild(container);
|
||||
}
|
||||
var shadow = block.nextConnection && block.nextConnection.getShadowDom();
|
||||
@@ -316,6 +313,8 @@ Blockly.Xml.domToWorkspace = function(xml, workspace) {
|
||||
Blockly.Events.setGroup(false);
|
||||
}
|
||||
Blockly.Field.stopCache();
|
||||
|
||||
workspace.updateVariableList(false);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -359,7 +358,7 @@ Blockly.Xml.domToBlock = function(xmlBlock, workspace) {
|
||||
topBlock.updateDisabled();
|
||||
// Allow the scrollbars to resize and move based on the new contents.
|
||||
// TODO(@picklesrus): #387. Remove when domToBlock avoids resizing.
|
||||
Blockly.resizeSvgContents(workspace);
|
||||
workspace.resizeContents();
|
||||
}
|
||||
} finally {
|
||||
Blockly.Events.enable();
|
||||
|
||||
Reference in New Issue
Block a user