Merge branch 'develop' into feature/simple_multitouch

This commit is contained in:
Rachel Fenichel
2016-08-25 12:25:48 -07:00
176 changed files with 12045 additions and 1159 deletions

View File

@@ -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);
}
}
}

View File

@@ -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();

View File

@@ -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;

View File

@@ -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.

View File

@@ -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.

View File

@@ -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);
}

View File

@@ -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 = '';
}
};

View File

@@ -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.

View File

@@ -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);

View File

@@ -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;

View File

@@ -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
View 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_);
};

View File

@@ -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');
}
};
/**

View File

@@ -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'));

View File

@@ -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);
};
/**

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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();
};
/**

View File

@@ -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

View File

@@ -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;
};

View File

@@ -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';
};

View File

@@ -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;
};
/**

View File

@@ -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;

View File

@@ -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();