mirror of
https://github.com/scratchfoundation/scratch-blocks.git
synced 2025-08-28 22:10:31 -04:00
New dragging! Merge from google (#891)
Port of a major refactor from Blockly. Dragging logic now lives in block_dragger.js, gesture.js, workspace_dragger.js, dragged_connection_manager.js (unused by scratch-blocks), and insertion_marker_manager.js (used only by scratch-blocks).
This commit is contained in:
parent
73630818c1
commit
6275e1137c
54 changed files with 3973 additions and 1473 deletions
|
@ -142,6 +142,8 @@ Blockly.Block = function(workspace, prototypeName, opt_id) {
|
|||
this.category_ = null;
|
||||
|
||||
/**
|
||||
* The block's position in workspace units. (0, 0) is at the workspace's
|
||||
* origin; scale does not change this value.
|
||||
* @type {!goog.math.Coordinate}
|
||||
* @private
|
||||
*/
|
||||
|
@ -339,7 +341,7 @@ Blockly.Block.prototype.getConnections_ = function() {
|
|||
/**
|
||||
* Walks down a stack of blocks and finds the last next connection on the stack.
|
||||
* @return {Blockly.Connection} The last next connection on the stack, or null.
|
||||
* @private
|
||||
* @package
|
||||
*/
|
||||
Blockly.Block.prototype.lastConnectionInStack = function() {
|
||||
var nextConnection = this.nextConnection;
|
||||
|
@ -360,7 +362,6 @@ Blockly.Block.prototype.lastConnectionInStack = function() {
|
|||
* connected should not coincidentally line up on screen.
|
||||
* @private
|
||||
*/
|
||||
// TODO: Refactor to return early in headless mode.
|
||||
Blockly.Block.prototype.bumpNeighbours_ = function() {
|
||||
console.warn('Not expected to reach this bumpNeighbours_ function. The ' +
|
||||
'BlockSvg function for bumpNeighbours_ was expected to be called instead.');
|
||||
|
@ -1239,10 +1240,14 @@ Blockly.Block.prototype.interpolate_ = function(message, args, lastDummyAlign) {
|
|||
for (var i = 0; i < tokens.length; i++) {
|
||||
var token = tokens[i];
|
||||
if (typeof token == 'number') {
|
||||
goog.asserts.assert(token > 0 && token <= args.length,
|
||||
'Message index %%s out of range.', token);
|
||||
goog.asserts.assert(!indexDup[token],
|
||||
'Message index %%s duplicated.', token);
|
||||
if (token <= 0 || token > args.length) {
|
||||
throw new Error('Block \"' + this.type + '\": ' +
|
||||
'Message index %' + token + ' out of range.');
|
||||
}
|
||||
if (indexDup[token]) {
|
||||
throw new Error('Block \"' + this.type + '\": ' +
|
||||
'Message index %' + token + ' duplicated.');
|
||||
}
|
||||
indexDup[token] = true;
|
||||
indexCount++;
|
||||
elements.push(args[token - 1]);
|
||||
|
@ -1253,8 +1258,10 @@ Blockly.Block.prototype.interpolate_ = function(message, args, lastDummyAlign) {
|
|||
}
|
||||
}
|
||||
}
|
||||
goog.asserts.assert(indexCount == args.length,
|
||||
'block "%s": Message does not reference all %s arg(s).', this.type, args.length);
|
||||
if(indexCount != args.length) {
|
||||
throw new Error('Block \"' + this.type + '\": ' +
|
||||
'Message does not reference all ' + args.length + ' arg(s).');
|
||||
}
|
||||
// Add last dummy input if needed.
|
||||
if (elements.length && (typeof elements[elements.length - 1] == 'string' ||
|
||||
goog.string.startsWith(elements[elements.length - 1]['type'],
|
||||
|
|
|
@ -80,6 +80,15 @@ Blockly.BlockDragSurfaceSvg.prototype.container_ = null;
|
|||
*/
|
||||
Blockly.BlockDragSurfaceSvg.prototype.scale_ = 1;
|
||||
|
||||
/**
|
||||
* Cached value for the translation of the drag surface.
|
||||
* This translation is in pixel units, because the scale is applied to the
|
||||
* drag group rather than the top-level SVG.
|
||||
* @type {goog.math.Coordinate}
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockDragSurfaceSvg.prototype.surfaceXY_ = null;
|
||||
|
||||
/**
|
||||
* Create the drag surface and inject it into the container.
|
||||
*/
|
||||
|
@ -109,6 +118,7 @@ Blockly.BlockDragSurfaceSvg.prototype.setBlocksAndShow = function(blocks) {
|
|||
// appendChild removes the blocks from the previous parent
|
||||
this.dragGroup_.appendChild(blocks);
|
||||
this.SVG_.style.display = 'block';
|
||||
this.surfaceXY_ = new goog.math.Coordinate(0, 0);
|
||||
// This allows blocks to be dragged outside of the blockly svg space.
|
||||
// This should be reset to hidden at the end of the block drag.
|
||||
// Note that this behavior is different from blockly where block disappear
|
||||
|
@ -118,10 +128,10 @@ Blockly.BlockDragSurfaceSvg.prototype.setBlocksAndShow = function(blocks) {
|
|||
};
|
||||
|
||||
/**
|
||||
* Translate and scale the entire drag surface group to keep in sync with the
|
||||
* workspace.
|
||||
* @param {number} x X translation
|
||||
* @param {number} y Y translation
|
||||
* Translate and scale the entire drag surface group to the given position, to
|
||||
* keep in sync with the workspace.
|
||||
* @param {number} x X translation in workspace coordinates.
|
||||
* @param {number} y Y translation in workspace coordinates.
|
||||
* @param {number} scale Scale of the group.
|
||||
*/
|
||||
Blockly.BlockDragSurfaceSvg.prototype.translateAndScaleGroup = function(x, y, scale) {
|
||||
|
@ -134,6 +144,23 @@ Blockly.BlockDragSurfaceSvg.prototype.translateAndScaleGroup = function(x, y, sc
|
|||
' scale(' + scale + ')');
|
||||
};
|
||||
|
||||
/**
|
||||
* Translate the drag surface's SVG based on its internal state.
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockDragSurfaceSvg.prototype.translateSurfaceInternal_ = function() {
|
||||
var x = this.surfaceXY_.x;
|
||||
var y = this.surfaceXY_.y;
|
||||
// This is a work-around to prevent a the blocks from rendering
|
||||
// fuzzy while they are being dragged on the drag surface.
|
||||
x = x.toFixed(0);
|
||||
y = y.toFixed(0);
|
||||
this.SVG_.style.display = 'block';
|
||||
|
||||
Blockly.utils.setCssTransform(this.SVG_,
|
||||
'translate3d(' + x + 'px, ' + y + 'px, 0px)');
|
||||
};
|
||||
|
||||
/**
|
||||
* Translate the entire drag surface during a drag.
|
||||
* We translate the drag surface instead of the blocks inside the surface
|
||||
|
@ -143,15 +170,8 @@ Blockly.BlockDragSurfaceSvg.prototype.translateAndScaleGroup = function(x, y, sc
|
|||
* @param {number} y Y translation for the entire surface.
|
||||
*/
|
||||
Blockly.BlockDragSurfaceSvg.prototype.translateSurface = function(x, y) {
|
||||
x *= this.scale_;
|
||||
y *= this.scale_;
|
||||
// This is a work-around to prevent a the blocks from rendering
|
||||
// fuzzy while they are being dragged on the drag surface.
|
||||
x = x.toFixed(0);
|
||||
y = y.toFixed(0);
|
||||
this.SVG_.style.display = 'block';
|
||||
Blockly.utils.setCssTransform(this.SVG_,
|
||||
'translate3d(' + x + 'px, ' + y + 'px, 0px)');
|
||||
this.surfaceXY_ = new goog.math.Coordinate(x * this.scale_, y * this.scale_);
|
||||
this.translateSurfaceInternal_();
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -186,19 +206,28 @@ Blockly.BlockDragSurfaceSvg.prototype.getCurrentBlock = function() {
|
|||
/**
|
||||
* Clear the group and hide the surface; move the blocks off onto the provided
|
||||
* element.
|
||||
* @param {!Element} newSurface Surface the dragging blocks should be moved to.
|
||||
* If the block is being deleted it doesn't need to go back to the original
|
||||
* surface, since it would be removed immediately during dispose.
|
||||
* @param {Element} opt_newSurface Surface the dragging blocks should be moved
|
||||
* to, or null if the blocks should be removed from this surface without
|
||||
* being moved to a different surface.
|
||||
*/
|
||||
Blockly.BlockDragSurfaceSvg.prototype.clearAndHide = function(newSurface) {
|
||||
// appendChild removes the node from this.dragGroup_
|
||||
newSurface.appendChild(this.getCurrentBlock());
|
||||
Blockly.BlockDragSurfaceSvg.prototype.clearAndHide = function(opt_newSurface) {
|
||||
if (opt_newSurface) {
|
||||
// appendChild removes the node from this.dragGroup_
|
||||
opt_newSurface.appendChild(this.getCurrentBlock());
|
||||
} else {
|
||||
this.dragGroup_.removeChild(this.getCurrentBlock());
|
||||
}
|
||||
this.SVG_.style.display = 'none';
|
||||
goog.asserts.assert(this.dragGroup_.childNodes.length == 0,
|
||||
'Drag group was not cleared.');
|
||||
this.surfaceXY_ = null;
|
||||
|
||||
// Reset the overflow property back to hidden so that nothing appears outside
|
||||
// of the blockly area.
|
||||
// Note that this behavior is different from blockly. See note in
|
||||
// setBlocksAndShow.
|
||||
var injectionDiv = document.getElementsByClassName('injectionDiv')[0];
|
||||
injectionDiv.style.overflow = 'hidden';
|
||||
|
||||
goog.asserts.assert(this.dragGroup_.childNodes.length == 0,
|
||||
'Drag group was not cleared.');
|
||||
};
|
||||
|
|
324
core/block_dragger.js
Normal file
324
core/block_dragger.js
Normal file
|
@ -0,0 +1,324 @@
|
|||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2017 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 Methods for dragging a block visually.
|
||||
* @author fenichel@google.com (Rachel Fenichel)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.BlockDragger');
|
||||
|
||||
goog.require('Blockly.InsertionMarkerManager');
|
||||
|
||||
goog.require('goog.math.Coordinate');
|
||||
goog.require('goog.asserts');
|
||||
|
||||
|
||||
/**
|
||||
* Class for a block dragger. It moves blocks around the workspace when they
|
||||
* are being dragged by a mouse or touch.
|
||||
* @param {!Blockly.Block} block The block to drag.
|
||||
* @param {!Blockly.WorkspaceSvg} workspace The workspace to drag on.
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.BlockDragger = function(block, workspace) {
|
||||
/**
|
||||
* The top block in the stack that is being dragged.
|
||||
* @type {!Blockly.BlockSvg}
|
||||
* @private
|
||||
*/
|
||||
this.draggingBlock_ = block;
|
||||
|
||||
/**
|
||||
* The workspace on which the block is being dragged.
|
||||
* @type {!Blockly.WorkspaceSvg}
|
||||
* @private
|
||||
*/
|
||||
this.workspace_ = workspace;
|
||||
|
||||
/**
|
||||
* Object that keeps track of connections on dragged blocks.
|
||||
* @type {!Blockly.InsertionMarkerManager}
|
||||
* @private
|
||||
*/
|
||||
this.draggedConnectionManager_ = new Blockly.InsertionMarkerManager(
|
||||
this.draggingBlock_);
|
||||
|
||||
/**
|
||||
* Which delete area the mouse pointer is over, if any.
|
||||
* One of {@link Blockly.DELETE_AREA_TRASH},
|
||||
* {@link Blockly.DELETE_AREA_TOOLBOX}, or {@link Blockly.DELETE_AREA_NONE}.
|
||||
* @type {?number}
|
||||
* @private
|
||||
*/
|
||||
this.deleteArea_ = null;
|
||||
|
||||
/**
|
||||
* Whether the block would be deleted if dropped immediately.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.wouldDeleteBlock_ = false;
|
||||
|
||||
/**
|
||||
* The location of the top left corner of the dragging block at the beginning
|
||||
* of the drag in workspace coordinates.
|
||||
* @type {!goog.math.Coordinate}
|
||||
* @private
|
||||
*/
|
||||
this.startXY_ = this.draggingBlock_.getRelativeToSurfaceXY();
|
||||
|
||||
/**
|
||||
* A list of all of the icons (comment, warning, and mutator) that are
|
||||
* on this block and its descendants. Moving an icon moves the bubble that
|
||||
* extends from it if that bubble is open.
|
||||
* @type {Array.<!Object>}
|
||||
* @private
|
||||
*/
|
||||
this.dragIconData_ = Blockly.BlockDragger.initIconData_(block);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sever all links from this object.
|
||||
* @package
|
||||
*/
|
||||
Blockly.BlockDragger.prototype.dispose = function() {
|
||||
this.draggingBlock_ = null;
|
||||
this.workspace_ = null;
|
||||
this.startWorkspace_ = null;
|
||||
this.dragIconData_.length = 0;
|
||||
|
||||
if (this.draggedConnectionManager_) {
|
||||
this.draggedConnectionManager_.dispose();
|
||||
this.draggedConnectionManager_ = null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Make a list of all of the icons (comment, warning, and mutator) that are
|
||||
* on this block and its descendants. Moving an icon moves the bubble that
|
||||
* extends from it if that bubble is open.
|
||||
* @param {!Blockly.BlockSvg} block The root block that is being dragged.
|
||||
* @return {!Array.<!Object>} The list of all icons and their locations.
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockDragger.initIconData_ = function(block) {
|
||||
// Build a list of icons that need to be moved and where they started.
|
||||
var dragIconData = [];
|
||||
var descendants = block.getDescendants();
|
||||
for (var i = 0, descendant; descendant = descendants[i]; i++) {
|
||||
var icons = descendant.getIcons();
|
||||
for (var j = 0; j < icons.length; j++) {
|
||||
var data = {
|
||||
// goog.math.Coordinate with x and y properties (workspace coordinates).
|
||||
location: icons[j].getIconLocation(),
|
||||
// Blockly.Icon
|
||||
icon: icons[j]
|
||||
};
|
||||
dragIconData.push(data);
|
||||
}
|
||||
}
|
||||
return dragIconData;
|
||||
};
|
||||
|
||||
/**
|
||||
* Start dragging a block. This includes moving it to the drag surface.
|
||||
* @param {!goog.math.Coordinate} currentDragDeltaXY How far the pointer has
|
||||
* moved from the position at mouse down, in pixel units.
|
||||
* @package
|
||||
*/
|
||||
Blockly.BlockDragger.prototype.startBlockDrag = function(currentDragDeltaXY) {
|
||||
if (!Blockly.Events.getGroup()) {
|
||||
Blockly.Events.setGroup(true);
|
||||
}
|
||||
|
||||
this.workspace_.setResizesEnabled(false);
|
||||
Blockly.BlockSvg.disconnectUiStop_();
|
||||
|
||||
if (this.draggingBlock_.getParent()) {
|
||||
this.draggingBlock_.unplug();
|
||||
var delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY);
|
||||
var newLoc = goog.math.Coordinate.sum(this.startXY_, delta);
|
||||
|
||||
this.draggingBlock_.translate(newLoc.x, newLoc.y);
|
||||
this.draggingBlock_.disconnectUiEffect();
|
||||
}
|
||||
this.draggingBlock_.setDragging(true);
|
||||
// For future consideration: we may be able to put moveToDragSurface inside
|
||||
// the block dragger, which would also let the block not track the block drag
|
||||
// surface.
|
||||
this.draggingBlock_.moveToDragSurface_();
|
||||
|
||||
if (this.workspace_.toolbox_) {
|
||||
this.workspace_.toolbox_.addDeleteStyle();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Execute a step of block dragging, based on the given event. Update the
|
||||
* display accordingly.
|
||||
* @param {!Event} e The most recent move event.
|
||||
* @param {!goog.math.Coordinate} currentDragDeltaXY How far the pointer has
|
||||
* moved from the position at the start of the drag, in pixel units.
|
||||
* @package
|
||||
*/
|
||||
Blockly.BlockDragger.prototype.dragBlock = function(e, currentDragDeltaXY) {
|
||||
var delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY);
|
||||
var newLoc = goog.math.Coordinate.sum(this.startXY_, delta);
|
||||
|
||||
this.draggingBlock_.moveDuringDrag(newLoc);
|
||||
this.dragIcons_(delta);
|
||||
|
||||
this.deleteArea_ = this.workspace_.isDeleteArea(e);
|
||||
this.draggedConnectionManager_.update(delta, this.deleteArea_);
|
||||
|
||||
this.updateCursorDuringBlockDrag_();
|
||||
};
|
||||
|
||||
/**
|
||||
* Finish a block drag and put the block back on the workspace.
|
||||
* @param {!Event} e The mouseup/touchend event.
|
||||
* @param {!goog.math.Coordinate} currentDragDeltaXY How far the pointer has
|
||||
* moved from the position at the start of the drag, in pixel units.
|
||||
* @package
|
||||
*/
|
||||
Blockly.BlockDragger.prototype.endBlockDrag = function(e, currentDragDeltaXY) {
|
||||
// Make sure internal state is fresh.
|
||||
this.dragBlock(e, currentDragDeltaXY);
|
||||
this.dragIconData_ = [];
|
||||
|
||||
Blockly.BlockSvg.disconnectUiStop_();
|
||||
|
||||
var delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY);
|
||||
var newLoc = goog.math.Coordinate.sum(this.startXY_, delta);
|
||||
this.draggingBlock_.moveOffDragSurface_(newLoc);
|
||||
|
||||
var deleted = this.maybeDeleteBlock_();
|
||||
if (!deleted) {
|
||||
// These are expensive and don't need to be done if we're deleting.
|
||||
this.draggingBlock_.moveConnections_(delta.x, delta.y);
|
||||
this.draggingBlock_.setDragging(false);
|
||||
this.draggedConnectionManager_.applyConnections();
|
||||
this.draggingBlock_.render();
|
||||
this.fireMoveEvent_();
|
||||
this.draggingBlock_.scheduleSnapAndBump();
|
||||
}
|
||||
this.workspace_.setResizesEnabled(true);
|
||||
|
||||
if (this.workspace_.toolbox_) {
|
||||
this.workspace_.toolbox_.removeDeleteStyle();
|
||||
}
|
||||
Blockly.Events.setGroup(false);
|
||||
};
|
||||
|
||||
/**
|
||||
* Fire a move event at the end of a block drag.
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockDragger.prototype.fireMoveEvent_ = function() {
|
||||
var event = new Blockly.Events.Move(this.draggingBlock_);
|
||||
event.oldCoordinate = this.startXY_;
|
||||
event.recordNew();
|
||||
Blockly.Events.fire(event);
|
||||
};
|
||||
|
||||
/**
|
||||
* Shut the trash can and, if necessary, delete the dragging block.
|
||||
* Should be called at the end of a block drag.
|
||||
* @return {boolean} whether the block was deleted.
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockDragger.prototype.maybeDeleteBlock_ = function() {
|
||||
var trashcan = this.workspace_.trashcan;
|
||||
|
||||
if (this.wouldDeleteBlock_) {
|
||||
if (trashcan) {
|
||||
goog.Timer.callOnce(trashcan.close, 100, trashcan);
|
||||
}
|
||||
// Fire a move event, so we know where to go back to for an undo.
|
||||
this.fireMoveEvent_();
|
||||
this.draggingBlock_.dispose(false, true);
|
||||
} else if (trashcan) {
|
||||
// Make sure the trash can is closed.
|
||||
trashcan.close();
|
||||
}
|
||||
return this.wouldDeleteBlock_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the cursor (and possibly the trash can lid) to reflect whether the
|
||||
* dragging block would be deleted if released immediately.
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockDragger.prototype.updateCursorDuringBlockDrag_ = function() {
|
||||
this.wouldDeleteBlock_ = this.draggedConnectionManager_.wouldDeleteBlock();
|
||||
var trashcan = this.workspace_.trashcan;
|
||||
if (this.wouldDeleteBlock_) {
|
||||
this.draggingBlock_.setDeleteStyle(true);
|
||||
if (this.deleteArea_ == Blockly.DELETE_AREA_TRASH && trashcan) {
|
||||
trashcan.setOpen_(true);
|
||||
}
|
||||
} else {
|
||||
this.draggingBlock_.setDeleteStyle(false);
|
||||
if (trashcan) {
|
||||
trashcan.setOpen_(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert a coordinate object from pixels to workspace units, including a
|
||||
* correction for mutator workspaces.
|
||||
* This function does not consider differing origins. It simply scales the
|
||||
* input's x and y values.
|
||||
* @param {!goog.math.Coordinate} pixelCoord A coordinate with x and y values
|
||||
* in css pixel units.
|
||||
* @return {!goog.math.Coordinate} The input coordinate divided by the workspace
|
||||
* scale.
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockDragger.prototype.pixelsToWorkspaceUnits_ = function(pixelCoord) {
|
||||
var result = new goog.math.Coordinate(pixelCoord.x / this.workspace_.scale,
|
||||
pixelCoord.y / this.workspace_.scale);
|
||||
if (this.workspace_.isMutator) {
|
||||
// If we're in a mutator, its scale is always 1, purely because of some
|
||||
// oddities in our rendering optimizations. The actual scale is the same as
|
||||
// the scale on the parent workspace.
|
||||
// Fix that for dragging.
|
||||
var mainScale = this.workspace_.options.parentWorkspace.scale;
|
||||
result = result.scale(1 / mainScale);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Move all of the icons connected to this drag.
|
||||
* @param {!goog.math.Coordinate} dxy How far to move the icons from their
|
||||
* original positions, in workspace units.
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockDragger.prototype.dragIcons_ = function(dxy) {
|
||||
// Moving icons moves their associated bubbles.
|
||||
for (var i = 0; i < this.dragIconData_.length; i++) {
|
||||
var data = this.dragIconData_[i];
|
||||
data.icon.setIconLocation(goog.math.Coordinate.sum(data.location, dxy));
|
||||
}
|
||||
};
|
|
@ -158,9 +158,6 @@ Blockly.BlockSvg.prototype.initSvg = function() {
|
|||
if (!this.workspace.options.readOnly && !this.eventsInit_) {
|
||||
Blockly.bindEventWithChecks_(this.getSvgRoot(), 'mousedown', this,
|
||||
this.onMouseDown_);
|
||||
var thisBlock = this;
|
||||
Blockly.bindEvent_(this.getSvgRoot(), 'touchstart', null,
|
||||
function(e) {Blockly.longStart_(e, thisBlock);});
|
||||
}
|
||||
this.eventsInit_ = true;
|
||||
|
||||
|
@ -294,79 +291,6 @@ Blockly.BlockSvg.prototype.getIcons = function() {
|
|||
return icons;
|
||||
};
|
||||
|
||||
/**
|
||||
* Wrapper function called when a mouseUp occurs during a drag operation.
|
||||
* @type {Array.<!Array>}
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockSvg.onMouseUpWrapper_ = null;
|
||||
|
||||
/**
|
||||
* Wrapper function called when a mouseMove occurs during a drag operation.
|
||||
* @type {Array.<!Array>}
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockSvg.onMouseMoveWrapper_ = null;
|
||||
|
||||
/**
|
||||
* Stop binding to the global mouseup and mousemove events.
|
||||
* @package
|
||||
*/
|
||||
Blockly.BlockSvg.terminateDrag = function() {
|
||||
if (Blockly.BlockSvg.onMouseUpWrapper_) {
|
||||
Blockly.unbindEvent_(Blockly.BlockSvg.onMouseUpWrapper_);
|
||||
Blockly.BlockSvg.onMouseUpWrapper_ = null;
|
||||
}
|
||||
if (Blockly.BlockSvg.onMouseMoveWrapper_) {
|
||||
Blockly.unbindEvent_(Blockly.BlockSvg.onMouseMoveWrapper_);
|
||||
Blockly.BlockSvg.onMouseMoveWrapper_ = null;
|
||||
}
|
||||
var selected = Blockly.selected;
|
||||
if (Blockly.dragMode_ == Blockly.DRAG_FREE) {
|
||||
// Terminate a drag operation.
|
||||
if (selected) {
|
||||
if (Blockly.replacementMarker_) {
|
||||
Blockly.BlockSvg.removeReplacementMarker();
|
||||
} else if (Blockly.insertionMarker_) {
|
||||
Blockly.Events.disable();
|
||||
if (Blockly.insertionMarkerConnection_) {
|
||||
Blockly.BlockSvg.disconnectInsertionMarker();
|
||||
}
|
||||
Blockly.insertionMarker_.dispose();
|
||||
Blockly.insertionMarker_ = null;
|
||||
Blockly.Events.enable();
|
||||
}
|
||||
// Update the connection locations.
|
||||
var xy = selected.getRelativeToSurfaceXY();
|
||||
var dxy = goog.math.Coordinate.difference(xy, selected.dragStartXY_);
|
||||
var event = new Blockly.Events.Move(selected);
|
||||
event.oldCoordinate = selected.dragStartXY_;
|
||||
event.recordNew();
|
||||
Blockly.Events.fire(event);
|
||||
selected.moveConnections_(dxy.x, dxy.y);
|
||||
delete selected.draggedBubbles_;
|
||||
selected.setDragging_(false);
|
||||
selected.moveOffDragSurface_();
|
||||
selected.render();
|
||||
// Re-enable workspace resizing.
|
||||
selected.workspace.setResizesEnabled(true);
|
||||
// Ensure that any snap and bump are part of this move's event group.
|
||||
var group = Blockly.Events.getGroup();
|
||||
setTimeout(function() {
|
||||
Blockly.Events.setGroup(group);
|
||||
selected.snapToGrid();
|
||||
Blockly.Events.setGroup(false);
|
||||
}, Blockly.BUMP_DELAY / 2);
|
||||
setTimeout(function() {
|
||||
Blockly.Events.setGroup(group);
|
||||
selected.bumpNeighbours_();
|
||||
Blockly.Events.setGroup(false);
|
||||
}, Blockly.BUMP_DELAY);
|
||||
}
|
||||
}
|
||||
Blockly.dragMode_ = Blockly.DRAG_NONE;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set parent of this block to be a new block or null.
|
||||
* @param {Blockly.BlockSvg} newParent New parent block.
|
||||
|
@ -408,6 +332,8 @@ Blockly.BlockSvg.prototype.setParent = function(newParent) {
|
|||
/**
|
||||
* Return the coordinates of the top-left corner of this block relative to the
|
||||
* drawing surface's origin (0,0), in workspace units.
|
||||
* If the block is on the workspace, (0, 0) is the origin of the workspace
|
||||
* coordinate system.
|
||||
* This does not change with workspace scale.
|
||||
* @return {!goog.math.Coordinate} Object with .x and .y properties in
|
||||
* workspace coordinates.
|
||||
|
@ -488,6 +414,7 @@ Blockly.BlockSvg.prototype.moveToDragSurface_ = function() {
|
|||
// The translation for drag surface blocks,
|
||||
// is equal to the current relative-to-surface position,
|
||||
// to keep the position in sync as it move on/off the surface.
|
||||
// This is in workspace coordinates.
|
||||
var xy = this.getRelativeToSurfaceXY();
|
||||
this.clearTransformAttributes_();
|
||||
this.workspace.blockDragSurface_.translateSurface(xy.x, xy.y);
|
||||
|
@ -499,19 +426,37 @@ Blockly.BlockSvg.prototype.moveToDragSurface_ = function() {
|
|||
* Move this block back to the workspace block canvas.
|
||||
* Generally should be called at the same time as setDragging_(false).
|
||||
* Does nothing if useDragSurface_ is false.
|
||||
* @param {!goog.math.Coordinate} newXY The position the block should take on
|
||||
* on the workspace canvas, in workspace coordinates.
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.moveOffDragSurface_ = function() {
|
||||
Blockly.BlockSvg.prototype.moveOffDragSurface_ = function(newXY) {
|
||||
if (!this.useDragSurface_) {
|
||||
return;
|
||||
}
|
||||
// Translate to current position, turning off 3d.
|
||||
var xy = this.getRelativeToSurfaceXY();
|
||||
this.clearTransformAttributes_();
|
||||
this.translate(xy.x, xy.y);
|
||||
this.translate(newXY.x, newXY.y);
|
||||
this.workspace.blockDragSurface_.clearAndHide(this.workspace.getCanvas());
|
||||
};
|
||||
|
||||
/**
|
||||
* Move this block during a drag, taking into account whether we are using a
|
||||
* drag surface to translate blocks.
|
||||
* This block must be a top-level block.
|
||||
* @param {!goog.math.Coordinate} newLoc The location to translate to, in
|
||||
* workspace coordinates.
|
||||
* @package
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.moveDuringDrag = function(newLoc) {
|
||||
if (this.useDragSurface_) {
|
||||
this.workspace.blockDragSurface_.translateSurface(newLoc.x, newLoc.y);
|
||||
} else {
|
||||
this.svgGroup_.translate_ = 'translate(' + newLoc.x + ',' + newLoc.y + ')';
|
||||
this.svgGroup_.setAttribute('transform',
|
||||
this.svgGroup_.translate_ + this.svgGroup_.skew_);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear the block of transform="..." attributes.
|
||||
* Used when the block is switching from 3d to 2d transform or vice versa.
|
||||
|
@ -528,7 +473,7 @@ Blockly.BlockSvg.prototype.snapToGrid = function() {
|
|||
if (!this.workspace) {
|
||||
return; // Deleted block.
|
||||
}
|
||||
if (Blockly.dragMode_ != Blockly.DRAG_NONE) {
|
||||
if (this.workspace.isDragging()) {
|
||||
return; // Don't bump blocks during a drag.
|
||||
}
|
||||
if (this.getParent()) {
|
||||
|
@ -556,6 +501,7 @@ Blockly.BlockSvg.prototype.snapToGrid = function() {
|
|||
/**
|
||||
* Returns the coordinates of a bounding box describing the dimensions of this
|
||||
* block and any blocks stacked below it.
|
||||
* Coordinate system: workspace coordinates.
|
||||
* @return {!{topLeft: goog.math.Coordinate, bottomRight: goog.math.Coordinate}}
|
||||
* Object with top left and bottom right coordinates of the bounding box.
|
||||
*/
|
||||
|
@ -689,139 +635,9 @@ Blockly.BlockSvg.prototype.tab = function(start, forward) {
|
|||
* @private
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.onMouseDown_ = function(e) {
|
||||
if (this.workspace.options.readOnly) {
|
||||
return;
|
||||
}
|
||||
if (this.isInFlyout) {
|
||||
// longStart's simulation of right-clicks for longpresses on touch devices
|
||||
// calls the onMouseDown_ function defined on the prototype of the object
|
||||
// the was longpressed (in this case, a Blockly.BlockSvg). In this case
|
||||
// that behaviour is wrong, because Blockly.Flyout.prototype.blockMouseDown
|
||||
// should be called for a mousedown on a block in the flyout, which blocks
|
||||
// execution of the block's onMouseDown_ function.
|
||||
if (e.type == 'touchstart' && Blockly.utils.isRightButton(e)) {
|
||||
Blockly.Flyout.blockRightClick_(e, this);
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}
|
||||
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();
|
||||
Blockly.hideChaff();
|
||||
Blockly.DropDownDiv.hideWithoutAnimation();
|
||||
if (Blockly.utils.isRightButton(e)) {
|
||||
// Right-click.
|
||||
this.showContextMenu_(e);
|
||||
// Click, not drag, so stop waiting for other touches from this identifier.
|
||||
Blockly.Touch.clearTouchIdentifier();
|
||||
} else if (!this.isMovable()) {
|
||||
// Allow immovable blocks to be selected and context menued, but not
|
||||
// dragged. Let this event bubble up to document, so the workspace may be
|
||||
// dragged instead.
|
||||
return;
|
||||
} else {
|
||||
if (!Blockly.Events.getGroup()) {
|
||||
Blockly.Events.setGroup(true);
|
||||
}
|
||||
// Left-click (or middle click)
|
||||
this.dragStartXY_ = this.getRelativeToSurfaceXY();
|
||||
this.workspace.startDrag(e, this.dragStartXY_);
|
||||
|
||||
Blockly.dragMode_ = Blockly.DRAG_STICKY;
|
||||
Blockly.BlockSvg.onMouseUpWrapper_ = Blockly.bindEventWithChecks_(document,
|
||||
'mouseup', this, this.onMouseUp_);
|
||||
Blockly.BlockSvg.onMouseMoveWrapper_ = Blockly.bindEventWithChecks_(
|
||||
document, 'mousemove', this, this.onMouseMove_);
|
||||
// Build a list of bubbles that need to be moved and where they started.
|
||||
this.draggedBubbles_ = [];
|
||||
var descendants = this.getDescendants();
|
||||
for (var i = 0, descendant; descendant = descendants[i]; i++) {
|
||||
var icons = descendant.getIcons();
|
||||
for (var j = 0; j < icons.length; j++) {
|
||||
var data = icons[j].getIconLocation();
|
||||
data.bubble = icons[j];
|
||||
this.draggedBubbles_.push(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
// This event has been handled. No need to bubble up to the document.
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle a mouse-up anywhere in the SVG pane. Is only registered when a
|
||||
* block is clicked. We can't use mouseUp on the block since a fast-moving
|
||||
* cursor can briefly escape the block before it catches up.
|
||||
* @param {!Event} e Mouse up event.
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.onMouseUp_ = function(e) {
|
||||
// A field is being edited if either the WidgetDiv or DropDownDiv is currently open.
|
||||
// If a field is being edited, don't fire any click events.
|
||||
var fieldEditing = Blockly.WidgetDiv.isVisible() || Blockly.DropDownDiv.isVisible();
|
||||
Blockly.Touch.clearTouchIdentifier();
|
||||
if (Blockly.dragMode_ != Blockly.DRAG_FREE && !fieldEditing) {
|
||||
// Move the block in front of the others. Do this at the end of a click
|
||||
// instead of rearranging the dom on mousedown. This helps with
|
||||
// performance and makes it easier to use psuedo element :active
|
||||
// to set the cursor.
|
||||
this.bringToFront_();
|
||||
Blockly.Events.fire(
|
||||
new Blockly.Events.Ui(this, 'click', undefined, undefined));
|
||||
// Scratch-specific: also fire a "stack click" event for this stack.
|
||||
// This is used to toggle the stack when any block in the stack is clicked.
|
||||
var rootBlock = this.workspace.getBlockById(this.id).getRootBlock();
|
||||
Blockly.Events.fire(
|
||||
new Blockly.Events.Ui(rootBlock, 'stackclick', undefined, undefined));
|
||||
}
|
||||
Blockly.terminateDrag_();
|
||||
|
||||
var deleteArea = this.workspace.isDeleteArea(e);
|
||||
|
||||
// Connect to a nearby block, but not if it's over the toolbox.
|
||||
if (Blockly.selected && Blockly.highlightedConnection_ &&
|
||||
deleteArea != Blockly.DELETE_AREA_TOOLBOX) {
|
||||
// Connect two blocks together.
|
||||
Blockly.localConnection_.connect(Blockly.highlightedConnection_);
|
||||
if (this.rendered) {
|
||||
// Trigger a connection animation.
|
||||
// Determine which connection is inferior (lower in the source stack).
|
||||
var inferiorConnection = Blockly.localConnection_.isSuperior() ?
|
||||
Blockly.highlightedConnection_ : Blockly.localConnection_;
|
||||
inferiorConnection.getSourceBlock().connectionUiEffect();
|
||||
}
|
||||
if (this.workspace.trashcan) {
|
||||
// Don't throw an object in the trash can if it just got connected.
|
||||
this.workspace.trashcan.close();
|
||||
}
|
||||
} else if (deleteArea && !this.getParent() && Blockly.selected.isDeletable()) {
|
||||
// We didn't connect the block, and it was over the trash can or the
|
||||
// toolbox. Delete it.
|
||||
var trashcan = this.workspace.trashcan;
|
||||
if (trashcan) {
|
||||
goog.Timer.callOnce(trashcan.close, 100, trashcan);
|
||||
}
|
||||
if (this.workspace.toolbox_) {
|
||||
this.workspace.toolbox_.removeDeleteStyle();
|
||||
}
|
||||
|
||||
Blockly.selected.dispose(false, true);
|
||||
}
|
||||
if (Blockly.highlightedConnection_) {
|
||||
Blockly.highlightedConnection_ = null;
|
||||
}
|
||||
if (!Blockly.WidgetDiv.isVisible()) {
|
||||
Blockly.Events.setGroup(false);
|
||||
var gesture = this.workspace.getGesture(e);
|
||||
if (gesture) {
|
||||
gesture.handleBlockStart(e, this);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -953,9 +769,9 @@ Blockly.BlockSvg.prototype.moveConnections_ = function(dx, dy) {
|
|||
/**
|
||||
* Recursively adds or removes the dragging class to this node and its children.
|
||||
* @param {boolean} adding True if adding, false if removing.
|
||||
* @private
|
||||
* @package
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.setDragging_ = function(adding) {
|
||||
Blockly.BlockSvg.prototype.setDragging = function(adding) {
|
||||
if (adding) {
|
||||
var group = this.getSvgRoot();
|
||||
group.translate_ = '';
|
||||
|
@ -971,379 +787,7 @@ Blockly.BlockSvg.prototype.setDragging_ = function(adding) {
|
|||
}
|
||||
// Recurse through all blocks attached under this one.
|
||||
for (var i = 0; i < this.childBlocks_.length; i++) {
|
||||
this.childBlocks_[i].setDragging_(adding);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Drag this block to follow the mouse.
|
||||
* @param {!Event} e Mouse move event.
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.onMouseMove_ = function(e) {
|
||||
if (e.type == 'mousemove' && e.clientX <= 1 && e.clientY == 0 &&
|
||||
e.button == 0) {
|
||||
/* HACK:
|
||||
Safari Mobile 6.0 and Chrome for Android 18.0 fire rogue mousemove
|
||||
events on certain touch actions. Ignore events with these signatures.
|
||||
This may result in a one-pixel blind spot in other browsers,
|
||||
but this shouldn't be noticeable. */
|
||||
e.stopPropagation();
|
||||
return;
|
||||
}
|
||||
|
||||
var oldXY = this.getRelativeToSurfaceXY();
|
||||
var newXY = this.workspace.moveDrag(e);
|
||||
|
||||
if (Blockly.dragMode_ == Blockly.DRAG_STICKY) {
|
||||
// Still dragging within the sticky DRAG_RADIUS.
|
||||
var dr = goog.math.Coordinate.distance(oldXY, newXY) * this.workspace.scale;
|
||||
if (dr > Blockly.DRAG_RADIUS) {
|
||||
// Switch to unrestricted dragging.
|
||||
Blockly.dragMode_ = Blockly.DRAG_FREE;
|
||||
Blockly.longStop_();
|
||||
|
||||
// Disable workspace resizing as an optimization.
|
||||
this.workspace.setResizesEnabled(false);
|
||||
// Clear WidgetDiv/DropDownDiv without animating, in case blocks are moved
|
||||
// around
|
||||
Blockly.WidgetDiv.hide(true);
|
||||
Blockly.DropDownDiv.hideWithoutAnimation();
|
||||
if (this.parentBlock_) {
|
||||
// Push this block to the very top of the stack.
|
||||
this.unplug();
|
||||
}
|
||||
this.setDragging_(true);
|
||||
this.moveToDragSurface_();
|
||||
}
|
||||
}
|
||||
if (Blockly.dragMode_ == Blockly.DRAG_FREE) {
|
||||
this.handleDragFree_(oldXY, newXY, e);
|
||||
}
|
||||
// This event has been handled. No need to bubble up to the document.
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle a mouse movement when a block is already freely dragging.
|
||||
* @param {!goog.math.Coordinate} oldXY The position of the block on screen
|
||||
* before the most recent mouse movement.
|
||||
* @param {!goog.math.Coordinate} newXY The new location after applying the
|
||||
* mouse movement.
|
||||
* @param {!Event} e Mouse move event.
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.handleDragFree_ = function(oldXY, newXY, e) {
|
||||
var dxy = goog.math.Coordinate.difference(oldXY, this.dragStartXY_);
|
||||
var group = this.getSvgRoot();
|
||||
if (this.useDragSurface_) {
|
||||
this.workspace.blockDragSurface_.translateSurface(newXY.x, newXY.y);
|
||||
} else {
|
||||
group.translate_ = 'translate(' + newXY.x + ',' + newXY.y + ')';
|
||||
group.setAttribute('transform', group.translate_ + group.skew_);
|
||||
}
|
||||
// Drag all the nested bubbles.
|
||||
for (var i = 0; i < this.draggedBubbles_.length; i++) {
|
||||
var commentData = this.draggedBubbles_[i];
|
||||
commentData.bubble.setIconLocation(
|
||||
goog.math.Coordinate.sum(commentData, dxy));
|
||||
}
|
||||
|
||||
// Check to see if any of this block's connections are within range of
|
||||
// another block's connection.
|
||||
var myConnections = this.getConnections_(false);
|
||||
// Also check the last connection on this stack
|
||||
var lastOnStack = this.lastConnectionInStack();
|
||||
if (lastOnStack && lastOnStack != this.nextConnection) {
|
||||
myConnections.push(lastOnStack);
|
||||
}
|
||||
var closestConnection = null;
|
||||
var localConnection = null;
|
||||
var radiusConnection = Blockly.SNAP_RADIUS;
|
||||
// If there is already a connection highlighted,
|
||||
// increase the radius we check for making new connections.
|
||||
// Why? When a connection is highlighted, blocks move around when the insertion
|
||||
// marker is created, which could cause the connection became out of range.
|
||||
// By increasing radiusConnection when a connection already exists,
|
||||
// we never "lose" the connection from the offset.
|
||||
if (Blockly.localConnection_ && Blockly.highlightedConnection_) {
|
||||
radiusConnection = Blockly.CONNECTING_SNAP_RADIUS;
|
||||
}
|
||||
for (i = 0; i < myConnections.length; i++) {
|
||||
var myConnection = myConnections[i];
|
||||
var neighbour = myConnection.closest(radiusConnection, dxy);
|
||||
if (neighbour.connection) {
|
||||
closestConnection = neighbour.connection;
|
||||
localConnection = myConnection;
|
||||
radiusConnection = neighbour.radius;
|
||||
}
|
||||
}
|
||||
|
||||
var updatePreviews = true;
|
||||
if (localConnection && localConnection.type == Blockly.OUTPUT_VALUE) {
|
||||
updatePreviews = true; // Always update previews for output connections.
|
||||
} else if (Blockly.localConnection_ && Blockly.highlightedConnection_) {
|
||||
var xDiff = Blockly.localConnection_.x_ + dxy.x -
|
||||
Blockly.highlightedConnection_.x_;
|
||||
var yDiff = Blockly.localConnection_.y_ + dxy.y -
|
||||
Blockly.highlightedConnection_.y_;
|
||||
var curDistance = Math.sqrt(xDiff * xDiff + yDiff * yDiff);
|
||||
|
||||
// Slightly prefer the existing preview over a new preview.
|
||||
if (closestConnection && radiusConnection > curDistance -
|
||||
Blockly.CURRENT_CONNECTION_PREFERENCE) {
|
||||
updatePreviews = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (updatePreviews) {
|
||||
var candidateIsLast = (localConnection == lastOnStack);
|
||||
this.updatePreviews(closestConnection, localConnection, radiusConnection,
|
||||
e, newXY.x - this.dragStartXY_.x, newXY.y - this.dragStartXY_.y,
|
||||
candidateIsLast);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Preview the results of the drag if the mouse is released immediately.
|
||||
* @param {Blockly.Connection} closestConnection The closest connection found
|
||||
* during the search
|
||||
* @param {Blockly.Connection} localConnection The connection on the moving
|
||||
* block.
|
||||
* @param {number} radiusConnection The distance between closestConnection and
|
||||
* localConnection.
|
||||
* @param {!Event} e Mouse move event.
|
||||
* @param {number} dx The x distance the block has moved onscreen up to this
|
||||
* point in the drag.
|
||||
* @param {number} dy The y distance the block has moved onscreen up to this
|
||||
* point in the drag.
|
||||
* @param {boolean} candidateIsLast True if the dragging stack is more than one
|
||||
* block long and localConnection is the last connection on the stack.
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.updatePreviews = function(closestConnection,
|
||||
localConnection, radiusConnection, e, dx, dy, candidateIsLast) {
|
||||
// Don't fire events for insertion marker creation or movement.
|
||||
Blockly.Events.disable();
|
||||
// Remove an insertion marker if needed. For Scratch-Blockly we are using
|
||||
// grayed-out blocks instead of highlighting the connection; for compatibility
|
||||
// with Web Blockly the name "highlightedConnection" will still be used.
|
||||
if (Blockly.highlightedConnection_ &&
|
||||
Blockly.highlightedConnection_ != closestConnection) {
|
||||
if (Blockly.replacementMarker_) {
|
||||
Blockly.BlockSvg.removeReplacementMarker();
|
||||
} else if (Blockly.insertionMarker_ && Blockly.insertionMarkerConnection_) {
|
||||
Blockly.BlockSvg.disconnectInsertionMarker();
|
||||
}
|
||||
// If there's already an insertion marker but it's representing the wrong
|
||||
// block, delete it so we can create the correct one.
|
||||
if (Blockly.insertionMarker_ &&
|
||||
((candidateIsLast && Blockly.localConnection_.sourceBlock_ == this) ||
|
||||
(!candidateIsLast && Blockly.localConnection_.sourceBlock_ != this))) {
|
||||
Blockly.insertionMarker_.dispose();
|
||||
Blockly.insertionMarker_ = null;
|
||||
}
|
||||
Blockly.highlightedConnection_ = null;
|
||||
Blockly.localConnection_ = null;
|
||||
}
|
||||
|
||||
var wouldDeleteBlock = this.updateCursor_(e, closestConnection);
|
||||
|
||||
// Add an insertion marker or replacement marker if needed.
|
||||
if (!wouldDeleteBlock && closestConnection &&
|
||||
closestConnection != Blockly.highlightedConnection_ &&
|
||||
!closestConnection.sourceBlock_.isInsertionMarker()) {
|
||||
Blockly.highlightedConnection_ = closestConnection;
|
||||
Blockly.localConnection_ = localConnection;
|
||||
|
||||
// Dragging a block over a nexisting block in an input should replace the
|
||||
// existing block and bump it out. Similarly, dragging a terminal block
|
||||
// over another (connected) terminal block will replace, not insert.
|
||||
var shouldReplace = (localConnection.type == Blockly.OUTPUT_VALUE ||
|
||||
(localConnection.type == Blockly.PREVIOUS_STATEMENT &&
|
||||
closestConnection.isConnected() &&
|
||||
!this.nextConnection));
|
||||
|
||||
if (shouldReplace) {
|
||||
this.addReplacementMarker_(localConnection, closestConnection);
|
||||
} else { // Should insert
|
||||
this.connectInsertionMarker_(localConnection, closestConnection);
|
||||
}
|
||||
}
|
||||
// Reenable events.
|
||||
Blockly.Events.enable();
|
||||
|
||||
// Provide visual indication of whether the block will be deleted if
|
||||
// dropped here.
|
||||
if (this.isDeletable()) {
|
||||
this.workspace.isDeleteArea(e);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Add highlighting showing which block will be replaced.
|
||||
* @param {Blockly.Connection} localConnection The connection on the dragging
|
||||
* block.
|
||||
* @param {Blockly.Connection} closestConnection The connnection to pretend to
|
||||
* connect to.
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.addReplacementMarker_ = function(localConnection,
|
||||
closestConnection) {
|
||||
if (closestConnection.targetBlock()) {
|
||||
Blockly.replacementMarker_ = closestConnection.targetBlock();
|
||||
Blockly.replacementMarker_.highlightForReplacement(true);
|
||||
} else if(localConnection.type == Blockly.OUTPUT_VALUE) {
|
||||
Blockly.replacementMarker_ = closestConnection.sourceBlock_;
|
||||
Blockly.replacementMarker_.highlightShapeForInput(closestConnection,
|
||||
true);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get rid of the highlighting marking the block that will be replaced.
|
||||
*/
|
||||
Blockly.BlockSvg.removeReplacementMarker = function() {
|
||||
// If there's no block in place, but we're still connecting to a value input,
|
||||
// then we must be highlighting an input shape.
|
||||
if (Blockly.highlightedConnection_.type == Blockly.INPUT_VALUE &&
|
||||
!Blockly.highlightedConnection_.isConnected()) {
|
||||
Blockly.replacementMarker_.highlightShapeForInput(
|
||||
Blockly.highlightedConnection_, false);
|
||||
} else {
|
||||
Blockly.replacementMarker_.highlightForReplacement(false);
|
||||
}
|
||||
Blockly.replacementMarker_ = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Place and render an insertion marker to indicate what would happen if you
|
||||
* release the drag right now.
|
||||
* @param {Blockly.Connection} localConnection The connection on the dragging
|
||||
* block.
|
||||
* @param {Blockly.Connection} closestConnection The connnection to connect the
|
||||
* insertion marker to.
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.connectInsertionMarker_ = function(localConnection,
|
||||
closestConnection) {
|
||||
var insertingBlock = Blockly.localConnection_.sourceBlock_;
|
||||
if (!Blockly.insertionMarker_) {
|
||||
Blockly.insertionMarker_ =
|
||||
this.workspace.newBlock(insertingBlock.type);
|
||||
if (insertingBlock.mutationToDom) {
|
||||
var oldMutationDom = insertingBlock.mutationToDom();
|
||||
Blockly.insertionMarker_.domToMutation(oldMutationDom);
|
||||
}
|
||||
Blockly.insertionMarker_.setInsertionMarker(true, insertingBlock.width);
|
||||
Blockly.insertionMarker_.initSvg();
|
||||
}
|
||||
|
||||
var insertionMarker = Blockly.insertionMarker_;
|
||||
var insertionMarkerConnection = insertionMarker.getMatchingConnection(
|
||||
localConnection.sourceBlock_, localConnection);
|
||||
if (insertionMarkerConnection != Blockly.insertionMarkerConnection_) {
|
||||
insertionMarker.rendered = true;
|
||||
// Render disconnected from everything else so that we have a valid
|
||||
// connection location.
|
||||
insertionMarker.render();
|
||||
insertionMarker.getSvgRoot().setAttribute('visibility', 'visible');
|
||||
|
||||
this.positionNewBlock(insertionMarker,
|
||||
insertionMarkerConnection, closestConnection);
|
||||
|
||||
if (insertionMarkerConnection.type == Blockly.PREVIOUS_STATEMENT &&
|
||||
!insertionMarker.nextConnection) {
|
||||
Blockly.bumpedConnection_ = closestConnection.targetConnection;
|
||||
}
|
||||
// Renders insertion marker.
|
||||
insertionMarkerConnection.connect(closestConnection);
|
||||
Blockly.insertionMarkerConnection_ = insertionMarkerConnection;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Disconnect the current insertion marker from the stack, and heal the stack to
|
||||
* its previous state.
|
||||
*/
|
||||
Blockly.BlockSvg.disconnectInsertionMarker = function() {
|
||||
// The insertion marker is the first block in a stack, either because it
|
||||
// doesn't have a previous connection or because the previous connection is
|
||||
// not connected. Unplug won't do anything in that case. Instead, unplug the
|
||||
// following block.
|
||||
if (Blockly.insertionMarkerConnection_ ==
|
||||
Blockly.insertionMarker_.nextConnection &&
|
||||
(!Blockly.insertionMarker_.previousConnection ||
|
||||
!Blockly.insertionMarker_.previousConnection.targetConnection)) {
|
||||
Blockly.insertionMarkerConnection_.targetBlock().unplug(false);
|
||||
}
|
||||
// Inside of a C-block, first statement connection.
|
||||
else if (Blockly.insertionMarkerConnection_.type == Blockly.NEXT_STATEMENT &&
|
||||
Blockly.insertionMarkerConnection_ !=
|
||||
Blockly.insertionMarker_.nextConnection) {
|
||||
var innerConnection = Blockly.insertionMarkerConnection_.targetConnection;
|
||||
innerConnection.sourceBlock_.unplug(false);
|
||||
var previousBlockNextConnection =
|
||||
Blockly.insertionMarker_.previousConnection ?
|
||||
Blockly.insertionMarker_.previousConnection.targetConnection : null;
|
||||
Blockly.insertionMarker_.unplug(true);
|
||||
if (previousBlockNextConnection) {
|
||||
previousBlockNextConnection.connect(innerConnection);
|
||||
}
|
||||
}
|
||||
else {
|
||||
Blockly.insertionMarker_.unplug(true /* healStack */);
|
||||
}
|
||||
|
||||
if (Blockly.insertionMarkerConnection_.targetConnection) {
|
||||
throw 'insertionMarkerConnection still connected at the end of disconnectInsertionMarker';
|
||||
}
|
||||
Blockly.insertionMarkerConnection_ = null;
|
||||
Blockly.insertionMarker_.getSvgRoot().setAttribute('visibility', 'hidden');
|
||||
};
|
||||
|
||||
/**
|
||||
* Provide visual indication of whether the block will be deleted if
|
||||
* dropped here.
|
||||
* Prefer connecting over dropping into the trash can, but prefer dragging to
|
||||
* the toolbox over connecting to other blocks.
|
||||
* @param {!Event} e Mouse move event.
|
||||
* @param {Blockly.Connection} closestConnection The connection this block would
|
||||
* potentially connect to if dropped here, or null.
|
||||
* @return {boolean} True if the block would be deleted if dropped here,
|
||||
* otherwise false.
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.updateCursor_ = function(e, closestConnection) {
|
||||
var deleteArea = this.workspace.isDeleteArea(e);
|
||||
var wouldConnect = Blockly.selected && closestConnection &&
|
||||
deleteArea != Blockly.DELETE_AREA_TOOLBOX;
|
||||
var wouldDelete = deleteArea && !this.getParent() &&
|
||||
Blockly.selected.isDeletable();
|
||||
var showDeleteCursor = wouldDelete && !wouldConnect;
|
||||
|
||||
if (showDeleteCursor) {
|
||||
if (deleteArea == Blockly.DELETE_AREA_TRASH && this.workspace.trashcan) {
|
||||
this.workspace.trashcan.setOpen_(true);
|
||||
}
|
||||
Blockly.utils.addClass(/** @type {!Element} */ (this.svgGroup_),
|
||||
'blocklyDraggingDelete');
|
||||
|
||||
if (this.workspace.toolbox_) {
|
||||
// Change the cursor to a hand with an 'x'
|
||||
this.workspace.toolbox_.addDeleteStyle();
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
|
||||
if (this.workspace.trashcan) {
|
||||
this.workspace.trashcan.setOpen_(false);
|
||||
}
|
||||
Blockly.utils.removeClass(/** @type {!Element} */ (this.svgGroup_),
|
||||
'blocklyDraggingDelete');
|
||||
if (this.workspace.toolbox_) {
|
||||
// Change the cursor on the toolbox
|
||||
this.workspace.toolbox_.removeDeleteStyle();
|
||||
}
|
||||
return false;
|
||||
this.childBlocks_[i].setDragging(adding);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1429,7 +873,7 @@ Blockly.BlockSvg.prototype.dispose = function(healStack, animate) {
|
|||
// If this block is being dragged, unlink the mouse events.
|
||||
if (Blockly.selected == this) {
|
||||
this.unselect();
|
||||
Blockly.terminateDrag_();
|
||||
this.workspace.cancelCurrentGesture();
|
||||
}
|
||||
// If this block has a context menu open, close it.
|
||||
if (Blockly.ContextMenu.currentBlock == this) {
|
||||
|
@ -1518,6 +962,22 @@ Blockly.BlockSvg.disposeUiStep_ = function(clone, rtl, start, workspaceScale) {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Play some UI effects (sound, animation) when disconnecting a block.
|
||||
* No-op in scratch-blocks, which has no disconnect animation.
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.disconnectUiEffect = function() {
|
||||
};
|
||||
|
||||
/**
|
||||
* Stop the disconnect UI animation immediately.
|
||||
* No-op in scratch-blocks, which has no disconnect animation.
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockSvg.disconnectUiStop_ = function() {
|
||||
};
|
||||
|
||||
/**
|
||||
* Enable or disable a block.
|
||||
*/
|
||||
|
@ -1587,7 +1047,7 @@ Blockly.BlockSvg.prototype.setWarningText = function(text, opt_id) {
|
|||
clearTimeout(this.setWarningText.pid_[id]);
|
||||
delete this.setWarningText.pid_[id];
|
||||
}
|
||||
if (Blockly.dragMode_ == Blockly.DRAG_FREE) {
|
||||
if (this.workspace.isDragging()) {
|
||||
// Don't change the warning text during a drag.
|
||||
// Wait until the drag finishes.
|
||||
var thisBlock = this;
|
||||
|
@ -1663,6 +1123,22 @@ Blockly.BlockSvg.prototype.removeSelect = function() {
|
|||
'blocklySelected');
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the cursor over this block by adding or removing a class.
|
||||
* @param {boolean} enable True if the delete cursor should be shown, false
|
||||
* otherwise.
|
||||
* @package
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.setDeleteStyle = function(enable) {
|
||||
if (enable) {
|
||||
Blockly.utils.addClass(/** @type {!Element} */ (this.svgGroup_),
|
||||
'blocklyDraggingDelete');
|
||||
} else {
|
||||
Blockly.utils.removeClass(/** @type {!Element} */ (this.svgGroup_),
|
||||
'blocklyDraggingDelete');
|
||||
}
|
||||
};
|
||||
|
||||
// Overrides of functions on Blockly.Block that take into account whether the
|
||||
// block has been rendered.
|
||||
|
||||
|
@ -1684,14 +1160,14 @@ Blockly.BlockSvg.prototype.setColour = function(colour, colourSecondary,
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Move this block to the front of the visible workspace.
|
||||
* <g> tags do not respect z-index so svg renders them in the
|
||||
* order that they are in the dom. By placing this block first within the
|
||||
* block group's <g>, it will render on top of any other blocks.
|
||||
* @package
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.bringToFront_ = function() {
|
||||
Blockly.BlockSvg.prototype.bringToFront = function() {
|
||||
var block = this;
|
||||
do {
|
||||
var root = block.getSvgRoot();
|
||||
|
@ -1699,6 +1175,7 @@ Blockly.BlockSvg.prototype.bringToFront_ = function() {
|
|||
block = block.getParent();
|
||||
} while (block);
|
||||
};
|
||||
|
||||
/**
|
||||
* Set whether this block can chain onto the bottom of another block.
|
||||
* @param {boolean} newBoolean True if there can be a previous statement.
|
||||
|
@ -1822,7 +1299,7 @@ Blockly.BlockSvg.prototype.appendInput_ = function(type, name) {
|
|||
* Otherwise, for a non-rendered block return an empty list, and for a
|
||||
* collapsed block don't return inputs connections.
|
||||
* @return {!Array.<!Blockly.Connection>} Array of connections.
|
||||
* @private
|
||||
* @package
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.getConnections_ = function(all) {
|
||||
var myConnections = [];
|
||||
|
@ -1902,3 +1379,26 @@ Blockly.BlockSvg.prototype.bumpNeighbours_ = function() {
|
|||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Schedule snapping to grid and bumping neighbours to occur after a brief
|
||||
* delay.
|
||||
* @package
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.scheduleSnapAndBump = function() {
|
||||
var block = this;
|
||||
// Ensure that any snap and bump are part of this move's event group.
|
||||
var group = Blockly.Events.getGroup();
|
||||
|
||||
setTimeout(function() {
|
||||
Blockly.Events.setGroup(group);
|
||||
block.snapToGrid();
|
||||
Blockly.Events.setGroup(false);
|
||||
}, Blockly.BUMP_DELAY / 2);
|
||||
|
||||
setTimeout(function() {
|
||||
Blockly.Events.setGroup(group);
|
||||
block.bumpNeighbours_();
|
||||
Blockly.Events.setGroup(false);
|
||||
}, Blockly.BUMP_DELAY);
|
||||
};
|
||||
|
|
|
@ -79,20 +79,6 @@ Blockly.mainWorkspace = null;
|
|||
*/
|
||||
Blockly.selected = null;
|
||||
|
||||
/**
|
||||
* Currently highlighted connection (during a drag).
|
||||
* @type {Blockly.Connection}
|
||||
* @private
|
||||
*/
|
||||
Blockly.highlightedConnection_ = null;
|
||||
|
||||
/**
|
||||
* Connection on dragged block that matches the highlighted connection.
|
||||
* @type {Blockly.Connection}
|
||||
* @private
|
||||
*/
|
||||
Blockly.localConnection_ = null;
|
||||
|
||||
/**
|
||||
* All of the connections on blocks that are currently being dragged.
|
||||
* @type {!Array.<!Blockly.Connection>}
|
||||
|
@ -100,38 +86,6 @@ Blockly.localConnection_ = null;
|
|||
*/
|
||||
Blockly.draggingConnections_ = [];
|
||||
|
||||
/**
|
||||
* Connection on the insertion marker block that matches
|
||||
* Blockly.localConnection_ on the dragged block.
|
||||
* @type {Blockly.Connection}
|
||||
* @private
|
||||
*/
|
||||
Blockly.insertionMarkerConnection_ = null;
|
||||
|
||||
/**
|
||||
* Grayed-out block that indicates to the user what will happen if they release
|
||||
* a drag immediately.
|
||||
* @type {Blockly.Block}
|
||||
* @private
|
||||
*/
|
||||
Blockly.insertionMarker_ = null;
|
||||
|
||||
/**
|
||||
* The block that will be replaced if the drag is released immediately. Should
|
||||
* be visually highlighted to indicate this to the user.
|
||||
* @type {Blockly.Block}
|
||||
* @private
|
||||
*/
|
||||
Blockly.replacementMarker_ = null;
|
||||
|
||||
/**
|
||||
* Connection that was bumped out of the way by an insertion marker, and may
|
||||
* need to be put back as the drag continues.
|
||||
* @type {Blockly.Connection}
|
||||
* @private
|
||||
*/
|
||||
Blockly.bumpedConnection_ = null;
|
||||
|
||||
/**
|
||||
* Contents of the local clipboard.
|
||||
* @type {Element}
|
||||
|
@ -146,15 +100,6 @@ Blockly.clipboardXml_ = null;
|
|||
*/
|
||||
Blockly.clipboardSource_ = null;
|
||||
|
||||
/**
|
||||
* Is the mouse dragging a block?
|
||||
* DRAG_NONE - No drag operation.
|
||||
* DRAG_STICKY - Still inside the sticky DRAG_RADIUS.
|
||||
* DRAG_FREE - Freely draggable.
|
||||
* @private
|
||||
*/
|
||||
Blockly.dragMode_ = Blockly.DRAG_NONE;
|
||||
|
||||
/**
|
||||
* Cached value for whether 3D is supported.
|
||||
* @type {!boolean}
|
||||
|
@ -242,7 +187,15 @@ Blockly.onKeyDown_ = function(e) {
|
|||
// Delete or backspace.
|
||||
// Stop the browser from going back to the previous page.
|
||||
e.preventDefault();
|
||||
// Don't delete while dragging. Jeez.
|
||||
if (Blockly.mainWorkspace.isDragging()) {
|
||||
return;
|
||||
}
|
||||
} else if (e.altKey || e.ctrlKey || e.metaKey) {
|
||||
// Don't use meta keys during drags.
|
||||
if (Blockly.mainWorkspace.isDragging()) {
|
||||
return;
|
||||
}
|
||||
if (Blockly.selected &&
|
||||
Blockly.selected.isDeletable() && Blockly.selected.isMovable()) {
|
||||
if (e.keyCode == 67) {
|
||||
|
@ -253,12 +206,7 @@ Blockly.onKeyDown_ = function(e) {
|
|||
// 'x' for cut.
|
||||
Blockly.copy_(Blockly.selected);
|
||||
Blockly.hideChaff();
|
||||
var heal = Blockly.dragMode_ != Blockly.DRAG_FREE;
|
||||
Blockly.selected.dispose(heal, true);
|
||||
if (Blockly.highlightedConnection_) {
|
||||
Blockly.highlightedConnection_.unhighlight();
|
||||
Blockly.highlightedConnection_ = null;
|
||||
}
|
||||
Blockly.selected.dispose(/* heal */ true, true);
|
||||
}
|
||||
}
|
||||
if (e.keyCode == 86) {
|
||||
|
@ -276,15 +224,6 @@ Blockly.onKeyDown_ = function(e) {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Stop binding to the global mouseup and mousemove events.
|
||||
* @private
|
||||
*/
|
||||
Blockly.terminateDrag_ = function() {
|
||||
Blockly.BlockSvg.terminateDrag();
|
||||
Blockly.Flyout.terminateDrag_();
|
||||
};
|
||||
|
||||
/**
|
||||
* Copy a block onto the local clipboard.
|
||||
* @param {!Blockly.Block} block Block to be copied.
|
||||
|
@ -337,7 +276,8 @@ Blockly.onContextMenu_ = function(e) {
|
|||
*/
|
||||
Blockly.hideChaff = function(opt_allowToolbox) {
|
||||
Blockly.Tooltip.hide();
|
||||
Blockly.WidgetDiv.hide();
|
||||
Blockly.WidgetDiv.hide(true);
|
||||
Blockly.DropDownDiv.hideWithoutAnimation();
|
||||
if (!opt_allowToolbox) {
|
||||
var workspace = Blockly.getMainWorkspace();
|
||||
if (workspace.toolbox_ &&
|
||||
|
|
|
@ -240,12 +240,6 @@ Blockly.Connection.prototype.dispose = function() {
|
|||
if (this.inDB_) {
|
||||
this.db_.removeConnection_(this);
|
||||
}
|
||||
if (Blockly.highlightedConnection_ == this) {
|
||||
Blockly.highlightedConnection_ = null;
|
||||
}
|
||||
if (Blockly.localConnection_ == this) {
|
||||
Blockly.localConnection_ = null;
|
||||
}
|
||||
this.db_ = null;
|
||||
this.dbOpposite_ = null;
|
||||
};
|
||||
|
@ -379,8 +373,7 @@ Blockly.Connection.prototype.isConnectionAllowed = function(candidate) {
|
|||
// If the other side of this connection is the active insertion marker
|
||||
// connection, we've obviously already decided that this is a good
|
||||
// connection.
|
||||
if (candidate.targetConnection ==
|
||||
Blockly.insertionMarkerConnection_) {
|
||||
if (candidate.targetBlock().isInsertionMarker()) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
|
|
|
@ -32,6 +32,13 @@ goog.provide('Blockly.constants');
|
|||
*/
|
||||
Blockly.DRAG_RADIUS = 3;
|
||||
|
||||
/**
|
||||
* Number of pixels the mouse must move before a drag/scroll starts from the
|
||||
* flyout. Because the drag-intention is determined when this is reached, it is
|
||||
* larger than Blockly.DRAG_RADIUS so that the drag-direction is clearer.
|
||||
*/
|
||||
Blockly.FLYOUT_DRAG_RADIUS = 10;
|
||||
|
||||
/**
|
||||
* Maximum misalignment between connections for them to snap together.
|
||||
*/
|
||||
|
@ -268,6 +275,13 @@ Blockly.Categories = {
|
|||
"more": "more"
|
||||
};
|
||||
|
||||
/**
|
||||
* ENUM representing that an event is not in any delete areas.
|
||||
* Null for backwards compatibility reasons.
|
||||
* @const
|
||||
*/
|
||||
Blockly.DELETE_AREA_NONE = null;
|
||||
|
||||
/**
|
||||
* ENUM representing that an event is in the delete area of the trash can.
|
||||
* @const
|
||||
|
|
|
@ -43,6 +43,12 @@ goog.require('goog.ui.MenuItem');
|
|||
*/
|
||||
Blockly.ContextMenu.currentBlock = null;
|
||||
|
||||
/**
|
||||
* @type {Array.<!Array>} Opaque data that can be passed to unbindEvent_.
|
||||
* @private
|
||||
*/
|
||||
Blockly.ContextMenu.eventWrapper_ = null;
|
||||
|
||||
/**
|
||||
* Construct the menu based on the list of options and show the menu.
|
||||
* @param {!Event} e Mouse event.
|
||||
|
@ -55,12 +61,33 @@ Blockly.ContextMenu.show = function(e, options, rtl) {
|
|||
Blockly.ContextMenu.hide();
|
||||
return;
|
||||
}
|
||||
var menu = Blockly.ContextMenu.populate_(options, rtl);
|
||||
|
||||
goog.events.listen(menu, goog.ui.Component.EventType.ACTION,
|
||||
Blockly.ContextMenu.hide);
|
||||
|
||||
Blockly.ContextMenu.position_(menu, e, rtl);
|
||||
// 1ms delay is required for focusing on context menus because some other
|
||||
// mouse event is still waiting in the queue and clears focus.
|
||||
setTimeout(function() {menu.getElement().focus();}, 1);
|
||||
Blockly.ContextMenu.currentBlock = null; // May be set by Blockly.Block.
|
||||
};
|
||||
|
||||
/**
|
||||
* Create the context menu object and populate it with the given options.
|
||||
* @param {!Array.<!Object>} options Array of menu options.
|
||||
* @param {boolean} rtl True if RTL, false if LTR.
|
||||
* @return {!goog.ui.Menu} The menu that will be shown on right click.
|
||||
* @private
|
||||
*/
|
||||
Blockly.ContextMenu.populate_ = function(options, rtl) {
|
||||
/* Here's what one option object looks like:
|
||||
{text: 'Make It So',
|
||||
enabled: true,
|
||||
callback: Blockly.MakeItSo}
|
||||
*/
|
||||
var menu = new goog.ui.Menu();
|
||||
menu.setAllowAutoFocus(true);
|
||||
menu.setRightToLeft(rtl);
|
||||
for (var i = 0, option; option = options[i]; i++) {
|
||||
var menuItem = new goog.ui.MenuItem(option.text);
|
||||
|
@ -76,9 +103,19 @@ Blockly.ContextMenu.show = function(e, options, rtl) {
|
|||
};
|
||||
}
|
||||
}
|
||||
goog.events.listen(menu, goog.ui.Component.EventType.ACTION,
|
||||
Blockly.ContextMenu.hide);
|
||||
// Record windowSize and scrollOffset before adding menu.
|
||||
return menu;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add the menu to the page and position it correctly.
|
||||
* @param {!goog.ui.Menu} menu The menu to add and position.
|
||||
* @param {!Event} e Mouse event for the right click that is making the context
|
||||
* menu appear.
|
||||
* @param {boolean} rtl True if RTL, false if LTR.
|
||||
* @private
|
||||
*/
|
||||
Blockly.ContextMenu.position_ = function(menu, e, rtl) {
|
||||
// Record windowSize and scrollOffset before adding menu.
|
||||
var windowSize = goog.dom.getViewportSize();
|
||||
var scrollOffset = goog.style.getViewportPageOffset(document);
|
||||
var div = Blockly.WidgetDiv.DIV;
|
||||
|
@ -109,12 +146,6 @@ Blockly.ContextMenu.show = function(e, options, rtl) {
|
|||
}
|
||||
}
|
||||
Blockly.WidgetDiv.position(x, y, windowSize, scrollOffset, rtl);
|
||||
|
||||
menu.setAllowAutoFocus(true);
|
||||
// 1ms delay is required for focusing on context menus because some other
|
||||
// mouse event is still waiting in the queue and clears focus.
|
||||
setTimeout(function() {menuDom.focus();}, 1);
|
||||
Blockly.ContextMenu.currentBlock = null; // May be set by Blockly.Block.
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -123,6 +154,9 @@ Blockly.ContextMenu.show = function(e, options, rtl) {
|
|||
Blockly.ContextMenu.hide = function() {
|
||||
Blockly.WidgetDiv.hideIfOwner(Blockly.ContextMenu);
|
||||
Blockly.ContextMenu.currentBlock = null;
|
||||
if (Blockly.ContextMenu.eventWrapper_) {
|
||||
Blockly.unbindEvent_(Blockly.ContextMenu.eventWrapper_);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -152,6 +152,7 @@ Blockly.Css.CONTENT = [
|
|||
'height: 100%;',
|
||||
'position: relative;',
|
||||
'overflow: hidden;', /* So blocks in drag surface disappear at edges */
|
||||
'touch-action: none',
|
||||
'}',
|
||||
|
||||
'.blocklyNonSelectable {',
|
||||
|
|
237
core/dragged_connection_manager.js
Normal file
237
core/dragged_connection_manager.js
Normal file
|
@ -0,0 +1,237 @@
|
|||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2017 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 that controls updates to connections during drags.
|
||||
* @author fenichel@google.com (Rachel Fenichel)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.DraggedConnectionManager');
|
||||
|
||||
goog.require('Blockly.RenderedConnection');
|
||||
|
||||
goog.require('goog.math.Coordinate');
|
||||
|
||||
|
||||
/**
|
||||
* Class that controls updates to connections during drags. It is primarily
|
||||
* responsible for finding the closest eligible connection and highlighting or
|
||||
* unhiglighting it as needed during a drag.
|
||||
* @param {!Blockly.BlockSvg} block The top block in the stack being dragged.
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.DraggedConnectionManager = function(block) {
|
||||
Blockly.selected = block;
|
||||
|
||||
/**
|
||||
* The top block in the stack being dragged.
|
||||
* Does not change during a drag.
|
||||
* @type {!Blockly.Block}
|
||||
* @private
|
||||
*/
|
||||
this.topBlock_ = block;
|
||||
|
||||
/**
|
||||
* The workspace on which these connections are being dragged.
|
||||
* Does not change during a drag.
|
||||
* @type {!Blockly.WorkspaceSvg}
|
||||
* @private
|
||||
*/
|
||||
this.workspace_ = block.workspace;
|
||||
|
||||
/**
|
||||
* The connections on the dragging blocks that are available to connect to
|
||||
* other blocks. This includes all open connections on the top block, as well
|
||||
* as the last connection on the block stack.
|
||||
* Does not change during a drag.
|
||||
* @type {!Array.<!Blockly.RenderedConnection>}
|
||||
* @private
|
||||
*/
|
||||
this.availableConnections_ = this.initAvailableConnections_();
|
||||
|
||||
/**
|
||||
* The connection that this block would connect to if released immediately.
|
||||
* Updated on every mouse move.
|
||||
* @type {Blockly.RenderedConnection}
|
||||
* @private
|
||||
*/
|
||||
this.closestConnection_ = null;
|
||||
|
||||
/**
|
||||
* The connection that would connect to this.closestConnection_ if this block
|
||||
* were released immediately.
|
||||
* Updated on every mouse move.
|
||||
* @type {Blockly.RenderedConnection}
|
||||
* @private
|
||||
*/
|
||||
this.localConnection_ = null;
|
||||
|
||||
/**
|
||||
* The distance between this.closestConnection_ and this.localConnection_,
|
||||
* in workspace units.
|
||||
* Updated on every mouse move.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.radiusConnection_ = 0;
|
||||
|
||||
/**
|
||||
* Whether the block would be deleted if it were dropped immediately.
|
||||
* Updated on every mouse move.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.wouldDeleteBlock_ = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sever all links from this object.
|
||||
* @package
|
||||
*/
|
||||
Blockly.DraggedConnectionManager.prototype.dispose = function() {
|
||||
this.topBlock_ = null;
|
||||
this.workspace_ = null;
|
||||
this.availableConnections_.length = 0;
|
||||
this.closestConnection_ = null;
|
||||
this.localConnection_ = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return whether the block would be deleted if dropped immediately, based on
|
||||
* information from the most recent move event.
|
||||
* @return {boolean} true if the block would be deleted if dropped immediately.
|
||||
* @package
|
||||
*/
|
||||
Blockly.DraggedConnectionManager.prototype.wouldDeleteBlock = function() {
|
||||
return this.wouldDeleteBlock_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Connect to the closest connection and render the results.
|
||||
* This should be called at the end of a drag.
|
||||
* @package
|
||||
*/
|
||||
Blockly.DraggedConnectionManager.prototype.applyConnections = function() {
|
||||
if (this.closestConnection_) {
|
||||
// Connect two blocks together.
|
||||
this.localConnection_.connect(this.closestConnection_);
|
||||
if (this.rendered) {
|
||||
// Trigger a connection animation.
|
||||
// Determine which connection is inferior (lower in the source stack).
|
||||
var inferiorConnection = this.localConnection_.isSuperior() ?
|
||||
this.closestConnection_ : this.localConnection_;
|
||||
inferiorConnection.getSourceBlock().connectionUiEffect();
|
||||
}
|
||||
this.removeHighlighting_();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update highlighted connections based on the most recent move location.
|
||||
* @param {!goog.math.Coordinate} dxy Position relative to drag start,
|
||||
* in workspace units.
|
||||
* @param {?number} deleteArea One of {@link Blockly.DELETE_AREA_TRASH},
|
||||
* {@link Blockly.DELETE_AREA_TOOLBOX}, or {@link Blockly.DELETE_AREA_NONE}.
|
||||
* @package
|
||||
*/
|
||||
Blockly.DraggedConnectionManager.prototype.update = function(dxy, deleteArea) {
|
||||
var oldClosestConnection = this.closestConnection_;
|
||||
var closestConnectionChanged = this.updateClosest_(dxy);
|
||||
|
||||
if (closestConnectionChanged && oldClosestConnection) {
|
||||
oldClosestConnection.unhighlight();
|
||||
}
|
||||
|
||||
// Prefer connecting over dropping into the trash can, but prefer dragging to
|
||||
// the toolbox over connecting to other blocks.
|
||||
var wouldConnect = !!this.closestConnection_ &&
|
||||
deleteArea != Blockly.DELETE_AREA_TOOLBOX;
|
||||
var wouldDelete = !!deleteArea && !this.topBlock_.getParent() &&
|
||||
this.topBlock_.isDeletable();
|
||||
this.wouldDeleteBlock_ = wouldDelete && !wouldConnect;
|
||||
|
||||
if (!this.wouldDeleteBlock_ && closestConnectionChanged &&
|
||||
this.closestConnection_) {
|
||||
this.addHighlighting_();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove highlighting from the currently highlighted connection, if it exists.
|
||||
* @private
|
||||
*/
|
||||
Blockly.DraggedConnectionManager.prototype.removeHighlighting_ = function() {
|
||||
if (this.closestConnection_) {
|
||||
this.closestConnection_.unhighlight();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Add highlighting to the closest connection, if it exists.
|
||||
* @private
|
||||
*/
|
||||
Blockly.DraggedConnectionManager.prototype.addHighlighting_ = function() {
|
||||
if (this.closestConnection_) {
|
||||
this.closestConnection_.highlight();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Populate the list of available connections on this block stack. This should
|
||||
* only be called once, at the beginning of a drag.
|
||||
* @return {!Array.<!Blockly.RenderedConnection>} a list of available
|
||||
* connections.
|
||||
* @private
|
||||
*/
|
||||
Blockly.DraggedConnectionManager.prototype.initAvailableConnections_ = function() {
|
||||
var available = this.topBlock_.getConnections_(false);
|
||||
// Also check the last connection on this stack
|
||||
var lastOnStack = this.topBlock_.lastConnectionInStack();
|
||||
if (lastOnStack && lastOnStack != this.topBlock_.nextConnection) {
|
||||
available.push(lastOnStack);
|
||||
}
|
||||
return available;
|
||||
};
|
||||
|
||||
/**
|
||||
* Find the new closest connection, and update internal state in response.
|
||||
* @param {!goog.math.Coordinate} dxy Position relative to the drag start,
|
||||
* in workspace units.
|
||||
* @return {boolean} Whether the closest connection has changed.
|
||||
* @private
|
||||
*/
|
||||
Blockly.DraggedConnectionManager.prototype.updateClosest_ = function(dxy) {
|
||||
var oldClosestConnection = this.closestConnection_;
|
||||
|
||||
this.closestConnection_ = null;
|
||||
this.localConnection_ = null;
|
||||
this.radiusConnection_ = Blockly.SNAP_RADIUS;
|
||||
for (var i = 0; i < this.availableConnections_.length; i++) {
|
||||
var myConnection = this.availableConnections_[i];
|
||||
var neighbour = myConnection.closest(this.radiusConnection_, dxy);
|
||||
if (neighbour.connection) {
|
||||
this.closestConnection_ = neighbour.connection;
|
||||
this.localConnection_ = myConnection;
|
||||
this.radiusConnection_ = neighbour.radius;
|
||||
}
|
||||
}
|
||||
return oldClosestConnection != this.closestConnection_;
|
||||
};
|
|
@ -822,7 +822,7 @@ Blockly.Events.disableOrphans = function(event) {
|
|||
child.setDisabled(false);
|
||||
}
|
||||
} else if ((block.outputConnection || block.previousConnection) &&
|
||||
Blockly.dragMode_ == Blockly.DRAG_NONE) {
|
||||
!workspace.isDragging()) {
|
||||
do {
|
||||
block.setDisabled(true);
|
||||
block = block.getNextBlock();
|
||||
|
|
|
@ -28,6 +28,8 @@
|
|||
|
||||
goog.provide('Blockly.Field');
|
||||
|
||||
goog.require('Blockly.Gesture');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.math.Size');
|
||||
|
@ -173,12 +175,12 @@ Blockly.Field.prototype.init = function() {
|
|||
|
||||
this.updateEditable();
|
||||
this.sourceBlock_.getSvgRoot().appendChild(this.fieldGroup_);
|
||||
this.mouseUpWrapper_ =
|
||||
Blockly.bindEventWithChecks_(this.getClickTarget_(), 'mouseup', this,
|
||||
this.onMouseUp_);
|
||||
// Force a render.
|
||||
this.render_();
|
||||
this.size_.width = 0;
|
||||
this.mouseDownWrapper_ =
|
||||
Blockly.bindEventWithChecks_(this.fieldGroup_, 'mousedown', this,
|
||||
this.onMouseDown_);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -192,9 +194,9 @@ Blockly.Field.prototype.initModel = function() {
|
|||
* Dispose of all DOM objects belonging to this editable field.
|
||||
*/
|
||||
Blockly.Field.prototype.dispose = function() {
|
||||
if (this.mouseUpWrapper_) {
|
||||
Blockly.unbindEvent_(this.mouseUpWrapper_);
|
||||
this.mouseUpWrapper_ = null;
|
||||
if (this.mouseDownWrapper_) {
|
||||
Blockly.unbindEvent_(this.mouseDownWrapper_);
|
||||
this.mouseDownWrapper_ = null;
|
||||
}
|
||||
this.sourceBlock_ = null;
|
||||
goog.dom.removeNode(this.fieldGroup_);
|
||||
|
@ -628,32 +630,21 @@ Blockly.Field.prototype.setValue = function(newValue) {
|
|||
};
|
||||
|
||||
/**
|
||||
* Handle a mouse up event on an editable field.
|
||||
* @param {!Event} e Mouse up event.
|
||||
* Handle a mouse down event on a field.
|
||||
* @param {!Event} e Mouse down event.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Field.prototype.onMouseUp_ = function(e) {
|
||||
if ((goog.userAgent.IPHONE || goog.userAgent.IPAD) &&
|
||||
!goog.userAgent.isVersionOrHigher('537.51.2') &&
|
||||
e.layerX !== 0 && e.layerY !== 0) {
|
||||
// Old iOS spawns a bogus event on the next touch after a 'prompt()' edit.
|
||||
// Unlike the real events, these have a layerX and layerY set.
|
||||
Blockly.Field.prototype.onMouseDown_ = function(e) {
|
||||
if (!this.sourceBlock_ || !this.sourceBlock_.workspace) {
|
||||
return;
|
||||
} else if (Blockly.utils.isRightButton(e)) {
|
||||
// Right-click.
|
||||
return;
|
||||
} else if (this.sourceBlock_.workspace.isDragging()) {
|
||||
// Drag operation is concluding. Don't open the editor.
|
||||
return;
|
||||
} else if (this.sourceBlock_.isEditable()) {
|
||||
// Non-abstract sub-classes must define a showEditor_ method.
|
||||
this.showEditor_();
|
||||
// The field is handling the touch, but we also want the blockSvg onMouseUp
|
||||
// handler to fire, so we will leave the touch identifier as it is.
|
||||
// The next onMouseUp is responsible for nulling it out.
|
||||
}
|
||||
var gesture = this.sourceBlock_.workspace.getGesture(e);
|
||||
if (gesture) {
|
||||
gesture.setStartField(this);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Change the tooltip text for this field.
|
||||
* @param {string|!Element} newTip Text for tooltip or a parent element to
|
||||
|
|
|
@ -149,11 +149,11 @@ Blockly.FieldAngle.prototype.showEditor_ = function() {
|
|||
}, svg);
|
||||
this.gauge_ = Blockly.utils.createSvgElement('path',
|
||||
{'class': 'blocklyAngleGauge'}, svg);
|
||||
this.line_ = Blockly.utils.createSvgElement('line',
|
||||
{'x1': Blockly.FieldAngle.HALF,
|
||||
'y1': Blockly.FieldAngle.HALF,
|
||||
'class': 'blocklyAngleLine'
|
||||
}, svg);
|
||||
this.line_ = Blockly.utils.createSvgElement('line',{
|
||||
'x1': Blockly.FieldAngle.HALF,
|
||||
'y1': Blockly.FieldAngle.HALF,
|
||||
'class': 'blocklyAngleLine'
|
||||
}, svg);
|
||||
// Draw markers around the edge.
|
||||
for (var angle = 0; angle < 360; angle += 15) {
|
||||
Blockly.utils.createSvgElement('line', {
|
||||
|
|
|
@ -100,28 +100,6 @@ Blockly.FieldDropdown.prototype.imageElement_ = null;
|
|||
*/
|
||||
Blockly.FieldDropdown.prototype.imageJson_ = null;
|
||||
|
||||
/**
|
||||
* Language-neutral currently selected string or image object.
|
||||
* @type {string|!Object}
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldDropdown.prototype.value_ = '';
|
||||
|
||||
/**
|
||||
* SVG image element if currently selected option is an image, or null.
|
||||
* @type {SVGElement}
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldDropdown.prototype.imageElement_ = null;
|
||||
|
||||
/**
|
||||
* Object with src, height, width, and alt attributes if currently selected
|
||||
* option is an image, or null.
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldDropdown.prototype.imageJson_ = null;
|
||||
|
||||
/**
|
||||
* Install this dropdown on a block.
|
||||
*/
|
||||
|
|
|
@ -490,7 +490,7 @@ Blockly.FieldTextInput.prototype.widgetDispose_ = function() {
|
|||
}
|
||||
thisField.setText(text);
|
||||
// Rerender the field now that the text has changed.
|
||||
thisField.sourceBlock_.rendered && thisField.render_();
|
||||
thisField.sourceBlock_.rendered && thisField.sourceBlock_.render();
|
||||
Blockly.unbindEvent_(htmlInput.onKeyDownWrapper_);
|
||||
Blockly.unbindEvent_(htmlInput.onKeyUpWrapper_);
|
||||
Blockly.unbindEvent_(htmlInput.onKeyPressWrapper_);
|
||||
|
|
|
@ -135,30 +135,36 @@ Blockly.FieldVariable.prototype.setValue = function(newValue) {
|
|||
* @this {Blockly.FieldVariable}
|
||||
*/
|
||||
Blockly.FieldVariable.dropdownCreate = function() {
|
||||
var variableNameList = [];
|
||||
if (this.sourceBlock_ && 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 = [];
|
||||
|
||||
var variableModelList = this.sourceBlock_.workspace.getVariablesOfType('');
|
||||
for (var i = 0; i < variableModelList.length; i++) {
|
||||
variableNameList.push(variableModelList[i].name);
|
||||
}
|
||||
}
|
||||
// Ensure that the currently selected variable is an option.
|
||||
var name = this.getText();
|
||||
if (name && variableList.indexOf(name) == -1) {
|
||||
variableList.push(name);
|
||||
if (name && variableNameList.indexOf(name) == -1) {
|
||||
variableNameList.push(name);
|
||||
}
|
||||
variableList.sort(goog.string.caseInsensitiveCompare);
|
||||
variableNameList.sort(goog.string.caseInsensitiveCompare);
|
||||
|
||||
this.renameVarItemIndex_ = variableList.length;
|
||||
variableList.push(Blockly.Msg.RENAME_VARIABLE);
|
||||
this.renameVarItemIndex_ = variableNameList.length;
|
||||
variableNameList.push(Blockly.Msg.RENAME_VARIABLE);
|
||||
|
||||
this.deleteVarItemIndex_ = variableList.length;
|
||||
variableList.push(Blockly.Msg.DELETE_VARIABLE.replace('%1', name));
|
||||
this.deleteVarItemIndex_ = variableNameList.length;
|
||||
variableNameList.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 = [];
|
||||
for (var i = 0; i < variableList.length; i++) {
|
||||
options[i] = [variableList[i], variableList[i]];
|
||||
for (var i = 0; i < variableNameList.length; i++) {
|
||||
// TODO(marisaleung): Set options[i] to [name, uuid]. This requires
|
||||
// changes where the variable gets set since the initialized value would be
|
||||
// id.
|
||||
options[i] = [variableNameList[i], variableNameList[i]];
|
||||
}
|
||||
return options;
|
||||
};
|
||||
|
|
|
@ -32,6 +32,7 @@ goog.require('Blockly.Block');
|
|||
goog.require('Blockly.Comment');
|
||||
goog.require('Blockly.Events');
|
||||
goog.require('Blockly.FlyoutButton');
|
||||
goog.require('Blockly.Gesture');
|
||||
goog.require('Blockly.Touch');
|
||||
goog.require('Blockly.WorkspaceSvg');
|
||||
goog.require('goog.dom');
|
||||
|
@ -112,20 +113,6 @@ Blockly.Flyout = function(workspaceOptions) {
|
|||
*/
|
||||
this.permanentlyDisabled_ = [];
|
||||
|
||||
/**
|
||||
* y coordinate of mousedown - used to calculate scroll distances.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.startDragMouseY_ = 0;
|
||||
|
||||
/**
|
||||
* x coordinate of mousedown - used to calculate scroll distances.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.startDragMouseX_ = 0;
|
||||
|
||||
/**
|
||||
* The toolbox that this flyout belongs to, or none if tihs is a simple
|
||||
* workspace.
|
||||
|
@ -135,51 +122,6 @@ Blockly.Flyout = function(workspaceOptions) {
|
|||
this.parentToolbox_ = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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_.
|
||||
* @type {Blockly.Flyout}
|
||||
* @private
|
||||
*/
|
||||
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.
|
||||
* @type {Event}
|
||||
* @private
|
||||
*/
|
||||
Blockly.Flyout.startDownEvent_ = null;
|
||||
|
||||
/**
|
||||
* Flyout block where the drag/click was initiated. Used to fire click events or
|
||||
* create a new block.
|
||||
* @type {Blockly.Block}
|
||||
* @private
|
||||
*/
|
||||
Blockly.Flyout.startBlock_ = null;
|
||||
|
||||
/**
|
||||
* Wrapper function called when a mouseup occurs during a background or block
|
||||
* drag operation.
|
||||
* @type {function}
|
||||
* private
|
||||
*/
|
||||
Blockly.Flyout.onMouseUpWrapper_ = null;
|
||||
|
||||
/**
|
||||
* Wrapper function called when a mousemove occurs during a background drag.
|
||||
* @type {function}
|
||||
* @private
|
||||
*/
|
||||
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}
|
||||
|
@ -207,13 +149,6 @@ Blockly.Flyout.prototype.containerVisible_ = true;
|
|||
*/
|
||||
Blockly.Flyout.prototype.CORNER_RADIUS = 0;
|
||||
|
||||
/**
|
||||
* Number of pixels the mouse must move before a drag/scroll starts. Because the
|
||||
* drag-intention is determined when this is reached, it is larger than
|
||||
* Blockly.DRAG_RADIUS so that the drag-direction is clearer.
|
||||
*/
|
||||
Blockly.Flyout.prototype.DRAG_RADIUS = 10;
|
||||
|
||||
/**
|
||||
* Margin around the edges of the blocks in the flyout.
|
||||
* @type {number}
|
||||
|
@ -295,16 +230,6 @@ Blockly.Flyout.prototype.verticalOffset_ = 0;
|
|||
*/
|
||||
Blockly.Flyout.prototype.dragAngleRange_ = 70;
|
||||
|
||||
/**
|
||||
* Is the flyout dragging (scrolling)?
|
||||
* 0 - DRAG_NONE - no drag is ongoing or state is undetermined
|
||||
* 1 - DRAG_STICKY - still within the sticky drag radius
|
||||
* 2 - DRAG_FREE - in scroll mode (never create a new block)
|
||||
* @private
|
||||
*/
|
||||
Blockly.Flyout.prototype.dragMode_ = Blockly.DRAG_NONE;
|
||||
|
||||
|
||||
/**
|
||||
* Creates the flyout's DOM. Only needs to be called once. The flyout can
|
||||
* either exist as its own svg element or be a g element nested inside a
|
||||
|
@ -345,10 +270,14 @@ Blockly.Flyout.prototype.init = function(targetWorkspace) {
|
|||
this.position();
|
||||
|
||||
Array.prototype.push.apply(this.eventWrappers_,
|
||||
Blockly.bindEvent_(this.svgGroup_, 'wheel', this, this.wheel_));
|
||||
Blockly.bindEventWithChecks_(this.svgGroup_, 'wheel', this, this.wheel_));
|
||||
// Dragging the flyout up and down (or left and right).
|
||||
Array.prototype.push.apply(this.eventWrappers_,
|
||||
Blockly.bindEvent_(this.svgGroup_, 'mousedown', this, this.onMouseDown_));
|
||||
Blockly.bindEventWithChecks_(this.svgGroup_, 'mousedown', this, this.onMouseDown_));
|
||||
|
||||
// A flyout connected to a workspace doesn't have its own current gesture.
|
||||
this.workspace_.getGesture =
|
||||
this.targetWorkspace_.getGesture.bind(this.targetWorkspace_);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -403,7 +332,8 @@ Blockly.Flyout.prototype.getHeight = function() {
|
|||
|
||||
/**
|
||||
* Get the flyout's workspace.
|
||||
* @return {!Blockly.Workspace} Workspace on which this flyout's blocks are placed.
|
||||
* @return {!Blockly.WorkspaceSvg} The workspace inside the flyout.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Flyout.prototype.getWorkspace = function() {
|
||||
return this.workspace_;
|
||||
|
@ -627,22 +557,6 @@ Blockly.Flyout.prototype.addBlockListeners_ = function(root, block, rect) {
|
|||
block.removeSelect));
|
||||
};
|
||||
|
||||
/**
|
||||
* Actions to take when a block in the flyout is right-clicked.
|
||||
* @param {!Event} e Event that triggered the right-click. Could originate from
|
||||
* a long-press in a touch environment.
|
||||
* @param {Blockly.BlockSvg} block The block that was clicked.
|
||||
*/
|
||||
Blockly.Flyout.blockRightClick_ = function(e, block) {
|
||||
Blockly.terminateDrag_();
|
||||
Blockly.WidgetDiv.hide(true);
|
||||
Blockly.DropDownDiv.hideWithoutAnimation();
|
||||
Blockly.hideChaff(true);
|
||||
block.showContextMenu_(e);
|
||||
// This was a right-click, so end the gesture immediately.
|
||||
Blockly.Touch.clearTouchIdentifier();
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle a mouse-down on an SVG block in a non-closing flyout.
|
||||
* @param {!Blockly.Block} block The flyout block to copy.
|
||||
|
@ -652,29 +566,11 @@ Blockly.Flyout.blockRightClick_ = function(e, block) {
|
|||
Blockly.Flyout.prototype.blockMouseDown_ = function(block) {
|
||||
var flyout = this;
|
||||
return function(e) {
|
||||
if (Blockly.utils.isRightButton(e)) {
|
||||
Blockly.Flyout.blockRightClick_(e, block);
|
||||
} else {
|
||||
flyout.dragMode_ = Blockly.DRAG_NONE;
|
||||
Blockly.terminateDrag_();
|
||||
Blockly.WidgetDiv.hide(true);
|
||||
Blockly.DropDownDiv.hideWithoutAnimation();
|
||||
Blockly.hideChaff();
|
||||
// Left-click (or middle click)
|
||||
// Record the current mouse position.
|
||||
flyout.startDragMouseY_ = e.clientY;
|
||||
flyout.startDragMouseX_ = e.clientX;
|
||||
Blockly.Flyout.startDownEvent_ = e;
|
||||
Blockly.Flyout.startBlock_ = block;
|
||||
Blockly.Flyout.startFlyout_ = flyout;
|
||||
Blockly.Flyout.onMouseUpWrapper_ = Blockly.bindEvent_(document,
|
||||
'mouseup', flyout, flyout.onMouseUp_);
|
||||
Blockly.Flyout.onMouseMoveBlockWrapper_ = Blockly.bindEvent_(document,
|
||||
'mousemove', flyout, flyout.onMouseMoveBlock_);
|
||||
var gesture = flyout.targetWorkspace_.getGesture(e);
|
||||
if (gesture) {
|
||||
gesture.setStartBlock(block);
|
||||
gesture.handleFlyoutStart(e, flyout);
|
||||
}
|
||||
// This event has been handled. No need to bubble up to the document.
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -684,220 +580,47 @@ Blockly.Flyout.prototype.blockMouseDown_ = function(block) {
|
|||
* @private
|
||||
*/
|
||||
Blockly.Flyout.prototype.onMouseDown_ = function(e) {
|
||||
this.dragMode_ = Blockly.DRAG_FREE;
|
||||
if (Blockly.utils.isRightButton(e)) {
|
||||
// Don't start drags with right clicks.
|
||||
Blockly.Touch.clearTouchIdentifier();
|
||||
return;
|
||||
}
|
||||
Blockly.WidgetDiv.hide(true);
|
||||
Blockly.DropDownDiv.hideWithoutAnimation();
|
||||
Blockly.hideChaff(true);
|
||||
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',
|
||||
this, Blockly.Flyout.terminateDrag_);
|
||||
// This event has been handled. No need to bubble up to the document.
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle a mouse-up anywhere in the SVG pane. Is only registered when a
|
||||
* block is clicked. We can't use mouseUp on the block since a fast-moving
|
||||
* cursor can briefly escape the block before it catches up.
|
||||
* @param {!Event} e Mouse up event.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Flyout.prototype.onMouseUp_ = function(/*e*/) {
|
||||
if (!this.workspace_.isDragging()) {
|
||||
// This was a click, not a drag. End the gesture.
|
||||
Blockly.Touch.clearTouchIdentifier();
|
||||
// A field is being edited if either the WidgetDiv or DropDownDiv is currently open.
|
||||
// If a field is being edited, don't fire any click events.
|
||||
var fieldEditing = Blockly.WidgetDiv.isVisible() || Blockly.DropDownDiv.isVisible();
|
||||
if (this.autoClose) {
|
||||
this.createBlockFunc_(Blockly.Flyout.startBlock_)(
|
||||
Blockly.Flyout.startDownEvent_);
|
||||
} else if (!fieldEditing) {
|
||||
Blockly.Events.fire(
|
||||
new Blockly.Events.Ui(Blockly.Flyout.startBlock_, 'click',
|
||||
undefined, undefined));
|
||||
Blockly.Events.fire(
|
||||
new Blockly.Events.Ui(Blockly.Flyout.startBlock_, 'stackclick',
|
||||
undefined, undefined));
|
||||
}
|
||||
}
|
||||
Blockly.terminateDrag_();
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle a mouse-move to vertically drag the flyout.
|
||||
* @param {!Event} e Mouse move event.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Flyout.prototype.onMouseMove_ = function(e) {
|
||||
var metrics = this.getMetrics_();
|
||||
if (this.horizontalLayout_) {
|
||||
if (metrics.contentWidth - metrics.viewWidth < 0) {
|
||||
return;
|
||||
}
|
||||
var dx = e.clientX - this.startDragMouseX_;
|
||||
this.startDragMouseX_ = e.clientX;
|
||||
var x = metrics.viewLeft - dx;
|
||||
x = goog.math.clamp(x, 0, metrics.contentWidth - metrics.viewWidth);
|
||||
this.scrollbar_.set(x);
|
||||
} else {
|
||||
if (metrics.contentHeight - metrics.viewHeight < 0) {
|
||||
return;
|
||||
}
|
||||
var dy = e.clientY - this.startDragMouseY_;
|
||||
this.startDragMouseY_ = e.clientY;
|
||||
var y = metrics.viewTop - dy;
|
||||
y = goog.math.clamp(y, 0, metrics.contentHeight - metrics.viewHeight);
|
||||
this.scrollbar_.set(y);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Mouse button is down on a block in a non-closing flyout. Create the block
|
||||
* if the mouse moves beyond a small radius. This allows one to play with
|
||||
* fields without instantiating blocks that instantly self-destruct.
|
||||
* @param {!Event} e Mouse move event.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Flyout.prototype.onMouseMoveBlock_ = function(e) {
|
||||
if (e.type == 'mousemove' && e.clientX <= 1 && e.clientY == 0 &&
|
||||
e.button == 0) {
|
||||
/* HACK:
|
||||
Safari Mobile 6.0 and Chrome for Android 18.0 fire rogue mousemove events
|
||||
on certain touch actions. Ignore events with these signatures.
|
||||
This may result in a one-pixel blind spot in other browsers,
|
||||
but this shouldn't be noticeable. */
|
||||
e.stopPropagation();
|
||||
return;
|
||||
}
|
||||
var dx = e.clientX - Blockly.Flyout.startDownEvent_.clientX;
|
||||
var dy = e.clientY - Blockly.Flyout.startDownEvent_.clientY;
|
||||
var createBlock = this.determineDragIntention_(dx, dy);
|
||||
Blockly.longStop_();
|
||||
if (createBlock) {
|
||||
this.createBlockFunc_(Blockly.Flyout.startBlock_)(
|
||||
Blockly.Flyout.startDownEvent_);
|
||||
} else if (this.dragMode_ == Blockly.DRAG_FREE) {
|
||||
// Do a scroll.
|
||||
this.onMouseMove_(e);
|
||||
}
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
/**
|
||||
* Determine the intention of a drag.
|
||||
* Updates dragMode_ based on a drag delta and the current mode,
|
||||
* and returns true if we should create a new block.
|
||||
* @param {number} dx X delta of the drag.
|
||||
* @param {number} dy Y delta of the drag.
|
||||
* @return {boolean} True if a new block should be created.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Flyout.prototype.determineDragIntention_ = function(dx, dy) {
|
||||
if (this.dragMode_ == Blockly.DRAG_FREE) {
|
||||
// Once in free mode, always stay in free mode and never create a block.
|
||||
return false;
|
||||
}
|
||||
var dragDistance = Math.sqrt(dx * dx + dy * dy);
|
||||
if (dragDistance < this.DRAG_RADIUS) {
|
||||
// Still within the sticky drag radius.
|
||||
this.dragMode_ = Blockly.DRAG_STICKY;
|
||||
return false;
|
||||
} else {
|
||||
if (this.isDragTowardWorkspace_(dx, dy) || !this.scrollbar_.isVisible()) {
|
||||
// Immediately create a block.
|
||||
return true;
|
||||
} else {
|
||||
// Immediately move to free mode - the drag is away from the workspace.
|
||||
this.dragMode_ = Blockly.DRAG_FREE;
|
||||
return false;
|
||||
}
|
||||
var gesture = this.targetWorkspace_.getGesture(e);
|
||||
if (gesture) {
|
||||
gesture.handleFlyoutStart(e, this);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a copy of this block on the workspace.
|
||||
* @param {!Blockly.Block} originBlock The flyout block to copy.
|
||||
* @return {!Function} Function to call when block is clicked.
|
||||
* @private
|
||||
* @param {!Blockly.BlockSvg} originalBlock The block to copy from the flyout.
|
||||
* @return {Blockly.BlockSvg} The newly created block, or null if something
|
||||
* went wrong with deserialization.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Flyout.prototype.createBlockFunc_ = function(originBlock) {
|
||||
var flyout = this;
|
||||
return function(e) {
|
||||
// Hide drop-downs and animating WidgetDiv immediately
|
||||
Blockly.WidgetDiv.hide(true);
|
||||
Blockly.DropDownDiv.hideWithoutAnimation();
|
||||
if (Blockly.utils.isRightButton(e)) {
|
||||
// Right-click. Don't create a block, let the context menu show.
|
||||
return;
|
||||
Blockly.Flyout.prototype.createBlock = function(originalBlock) {
|
||||
var newBlock = null;
|
||||
Blockly.Events.disable();
|
||||
this.targetWorkspace_.setResizesEnabled(false);
|
||||
try {
|
||||
newBlock = this.placeNewBlock_(originalBlock);
|
||||
//Force a render on IE and Edge to get around the issue described in
|
||||
//Blockly.Field.getCachedWidth
|
||||
if (goog.userAgent.IE || goog.userAgent.EDGE) {
|
||||
var blocks = newBlock.getDescendants();
|
||||
for (var i = blocks.length - 1; i >= 0; i--) {
|
||||
blocks[i].render(false);
|
||||
}
|
||||
}
|
||||
if (originBlock.disabled) {
|
||||
// Beyond capacity.
|
||||
return;
|
||||
}
|
||||
Blockly.Events.disable();
|
||||
// Disable workspace resizing. Reenable at the end of the drag. This avoids
|
||||
// a spurious resize between creating the new block and placing it in the
|
||||
// workspace.
|
||||
flyout.targetWorkspace_.setResizesEnabled(false);
|
||||
try {
|
||||
var block = flyout.placeNewBlock_(originBlock);
|
||||
} finally {
|
||||
Blockly.Events.enable();
|
||||
}
|
||||
if (Blockly.Events.isEnabled()) {
|
||||
Blockly.Events.setGroup(true);
|
||||
Blockly.Events.fire(new Blockly.Events.Create(block));
|
||||
}
|
||||
if (flyout.autoClose) {
|
||||
flyout.hide();
|
||||
}
|
||||
// Start a dragging operation on the new block.
|
||||
block.onMouseDown_(e);
|
||||
Blockly.dragMode_ = Blockly.DRAG_FREE;
|
||||
block.setDragging_(true);
|
||||
block.moveToDragSurface_();
|
||||
};
|
||||
};
|
||||
// Close the flyout.
|
||||
Blockly.hideChaff();
|
||||
} finally {
|
||||
Blockly.Events.enable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop binding to the global mouseup and mousemove events.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Flyout.terminateDrag_ = function() {
|
||||
if (Blockly.Flyout.startFlyout_) {
|
||||
// User was dragging the flyout background, and has stopped.
|
||||
if (Blockly.Flyout.startFlyout_.dragMode_ == Blockly.DRAG_FREE) {
|
||||
Blockly.Touch.clearTouchIdentifier();
|
||||
}
|
||||
Blockly.Flyout.startFlyout_.dragMode_ = Blockly.DRAG_NONE;
|
||||
Blockly.Flyout.startFlyout_ = null;
|
||||
if (Blockly.Events.isEnabled()) {
|
||||
Blockly.Events.setGroup(true);
|
||||
Blockly.Events.fire(new Blockly.Events.Create(newBlock));
|
||||
}
|
||||
if (Blockly.Flyout.onMouseUpWrapper_) {
|
||||
Blockly.unbindEvent_(Blockly.Flyout.onMouseUpWrapper_);
|
||||
Blockly.Flyout.onMouseUpWrapper_ = null;
|
||||
if (this.autoClose) {
|
||||
this.hide();
|
||||
}
|
||||
if (Blockly.Flyout.onMouseMoveBlockWrapper_) {
|
||||
Blockly.unbindEvent_(Blockly.Flyout.onMouseMoveBlockWrapper_);
|
||||
Blockly.Flyout.onMouseMoveBlockWrapper_ = null;
|
||||
}
|
||||
if (Blockly.Flyout.onMouseMoveWrapper_) {
|
||||
Blockly.unbindEvent_(Blockly.Flyout.onMouseMoveWrapper_);
|
||||
Blockly.Flyout.onMouseMoveWrapper_ = null;
|
||||
}
|
||||
Blockly.Flyout.startDownEvent_ = null;
|
||||
Blockly.Flyout.startBlock_ = null;
|
||||
return newBlock;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -914,3 +637,12 @@ Blockly.Flyout.prototype.reflow = function() {
|
|||
this.workspace_.addChangeListener(this.reflowWrapper_);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @return {boolean} True if this flyout may be scrolled with a scrollbar or by
|
||||
* dragging.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Flyout.prototype.isScrollable = function() {
|
||||
return this.scrollbar_ ? this.scrollbar_.isVisible() : false;
|
||||
};
|
||||
|
|
|
@ -115,6 +115,13 @@ Blockly.FlyoutButton.prototype.width = 0;
|
|||
*/
|
||||
Blockly.FlyoutButton.prototype.height = 40; // Can't be computed like the width
|
||||
|
||||
/**
|
||||
* Opaque data that can be passed to Blockly.unbindEvent_.
|
||||
* @type {Array.<!Array>}
|
||||
* @private
|
||||
*/
|
||||
Blockly.FlyoutButton.prototype.onMouseUpWrapper_ = null;
|
||||
|
||||
/**
|
||||
* Create the button elements.
|
||||
* @return {!Element} The button's SVG group.
|
||||
|
@ -164,6 +171,9 @@ Blockly.FlyoutButton.prototype.createDom = function() {
|
|||
svgText.setAttribute('y', this.height / 2);
|
||||
|
||||
this.updateTransform_();
|
||||
|
||||
this.mouseUpWrapper_ = Blockly.bindEventWithChecks_(this.svgGroup_, 'mouseup',
|
||||
this, this.onMouseUp_);
|
||||
return this.svgGroup_;
|
||||
};
|
||||
|
||||
|
@ -208,6 +218,9 @@ Blockly.FlyoutButton.prototype.getTargetWorkspace = function() {
|
|||
* Dispose of this button.
|
||||
*/
|
||||
Blockly.FlyoutButton.prototype.dispose = function() {
|
||||
if (this.onMouseUpWrapper_) {
|
||||
Blockly.unbindEvent_(this.onMouseUpWrapper_);
|
||||
}
|
||||
if (this.svgGroup_) {
|
||||
goog.dom.removeNode(this.svgGroup_);
|
||||
this.svgGroup_ = null;
|
||||
|
@ -219,15 +232,13 @@ Blockly.FlyoutButton.prototype.dispose = function() {
|
|||
/**
|
||||
* Do something when the button is clicked.
|
||||
* @param {!Event} e Mouse up event.
|
||||
* @private
|
||||
*/
|
||||
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.FlyoutButton.prototype.onMouseUp_ = function(e) {
|
||||
var gesture = this.targetWorkspace_.getGesture(e);
|
||||
if (gesture) {
|
||||
gesture.cancel();
|
||||
}
|
||||
|
||||
// Call the callback registered to this button.
|
||||
if (this.callback_) {
|
||||
|
|
83
core/flyout_dragger.js
Normal file
83
core/flyout_dragger.js
Normal file
|
@ -0,0 +1,83 @@
|
|||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2017 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 Methods for dragging a flyout visually.
|
||||
* @author fenichel@google.com (Rachel Fenichel)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.FlyoutDragger');
|
||||
|
||||
goog.require('Blockly.WorkspaceDragger');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.math.Coordinate');
|
||||
|
||||
|
||||
/**
|
||||
* Class for a flyout dragger. It moves a flyout workspace around when it is
|
||||
* being dragged by a mouse or touch.
|
||||
* Note that the workspace itself manages whether or not it has a drag surface
|
||||
* and how to do translations based on that. This simply passes the right
|
||||
* commands based on events.
|
||||
* @param {!Blockly.Flyout} flyout The flyout to drag.
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.FlyoutDragger = function(flyout) {
|
||||
Blockly.FlyoutDragger.superClass_.constructor.call(this,
|
||||
flyout.getWorkspace());
|
||||
|
||||
/**
|
||||
* The scrollbar to update to move the flyout.
|
||||
* Unlike the main workspace, the flyout has only one scrollbar, in either the
|
||||
* horizontal or the vertical direction.
|
||||
* @type {!Blockly.Scrollbar}
|
||||
* @private
|
||||
*/
|
||||
this.scrollbar_ = flyout.scrollbar_;
|
||||
|
||||
/**
|
||||
* Whether the flyout scrolls horizontally. If false, the flyout scrolls
|
||||
* vertically.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.horizontalLayout_ = flyout.horizontalLayout_;
|
||||
};
|
||||
goog.inherits(Blockly.FlyoutDragger, Blockly.WorkspaceDragger);
|
||||
|
||||
/**
|
||||
* Move the appropriate scrollbar to drag the flyout.
|
||||
* Since flyouts only scroll in one direction at a time, this will discard one
|
||||
* of the calculated values.
|
||||
* x and y are in pixels.
|
||||
* @param {number} x The new x position to move the scrollbar to.
|
||||
* @param {number} y The new y position to move the scrollbar to.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FlyoutDragger.prototype.updateScroll_ = function(x, y) {
|
||||
// Move the scrollbar and the flyout will scroll automatically.
|
||||
if (this.horizontalLayout_) {
|
||||
this.scrollbar_.set(x);
|
||||
} else {
|
||||
this.scrollbar_.set(y);
|
||||
}
|
||||
};
|
|
@ -326,7 +326,11 @@ Blockly.HorizontalFlyout.prototype.layout_ = function(contents, gaps) {
|
|||
var buttonSvg = button.createDom();
|
||||
button.moveTo(cursorX, cursorY);
|
||||
button.show();
|
||||
Blockly.bindEvent_(buttonSvg, 'mouseup', button, button.onMouseUp);
|
||||
// Clicking on a flyout button or label is a lot like clicking on the
|
||||
// flyout background.
|
||||
this.listeners_.push(Blockly.bindEventWithChecks_(buttonSvg, 'mousedown',
|
||||
this, this.onMouseDown_));
|
||||
|
||||
|
||||
this.buttons_.push(button);
|
||||
cursorX += (button.width + gaps[i]);
|
||||
|
@ -334,33 +338,18 @@ Blockly.HorizontalFlyout.prototype.layout_ = function(contents, gaps) {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle a mouse-move to drag the flyout.
|
||||
* @param {!Event} e Mouse move event.
|
||||
* @private
|
||||
*/
|
||||
Blockly.HorizontalFlyout.prototype.onMouseMove_ = function(e) {
|
||||
var metrics = this.getMetrics_();
|
||||
if (metrics.contentWidth - metrics.viewWidth < 0) {
|
||||
return;
|
||||
}
|
||||
var dx = e.clientX - this.startDragMouseX_;
|
||||
this.startDragMouseX_ = e.clientX;
|
||||
var x = metrics.viewLeft - dx;
|
||||
x = goog.math.clamp(x, 0, metrics.contentWidth - metrics.viewWidth);
|
||||
this.scrollbar_.set(x);
|
||||
};
|
||||
|
||||
/**
|
||||
* Determine if a drag delta is toward the workspace, based on the position
|
||||
* and orientation of the flyout. This is used in determineDragIntention_ to
|
||||
* determine if a new block should be created or if the flyout should scroll.
|
||||
* @param {number} dx X delta of the drag.
|
||||
* @param {number} dy Y delta of the drag.
|
||||
* and orientation of the flyout. This to decide if a new block should be
|
||||
* created or if the flyout should scroll.
|
||||
* @param {!goog.math.Coordinate} currentDragDeltaXY How far the pointer has
|
||||
* moved from the position at mouse down, in pixel units.
|
||||
* @return {boolean} true if the drag is toward the workspace.
|
||||
* @private
|
||||
* @package
|
||||
*/
|
||||
Blockly.HorizontalFlyout.prototype.isDragTowardWorkspace_ = function(dx, dy) {
|
||||
Blockly.HorizontalFlyout.prototype.isDragTowardWorkspace = function(currentDragDeltaXY) {
|
||||
var dx = currentDragDeltaXY.x;
|
||||
var dy = currentDragDeltaXY.y;
|
||||
// Direction goes from -180 to 180, with 0 toward the right and 90 on top.
|
||||
var dragDirection = Math.atan2(dy, dx) / Math.PI * 180;
|
||||
|
||||
|
|
|
@ -447,7 +447,10 @@ Blockly.VerticalFlyout.prototype.layout_ = function(contents, gaps) {
|
|||
var buttonSvg = button.createDom();
|
||||
button.moveTo(cursorX, cursorY);
|
||||
button.show();
|
||||
Blockly.bindEvent_(buttonSvg, 'mouseup', button, button.onMouseUp);
|
||||
// Clicking on a flyout button or label is a lot like clicking on the
|
||||
// flyout background.
|
||||
this.listeners_.push(Blockly.bindEventWithChecks_(buttonSvg, 'mousedown',
|
||||
this, this.onMouseDown_));
|
||||
|
||||
this.buttons_.push(button);
|
||||
cursorY += button.height + gaps[i];
|
||||
|
@ -557,33 +560,18 @@ Blockly.VerticalFlyout.prototype.checkboxClicked_ = function(checkboxObj) {
|
|||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle a mouse-move to vertically drag the flyout.
|
||||
* @param {!Event} e Mouse move event.
|
||||
* @private
|
||||
*/
|
||||
Blockly.VerticalFlyout.prototype.onMouseMove_ = function(e) {
|
||||
var metrics = this.getMetrics_();
|
||||
if (metrics.contentHeight - metrics.viewHeight < 0) {
|
||||
return;
|
||||
}
|
||||
var dy = e.clientY - this.startDragMouseY_;
|
||||
this.startDragMouseY_ = e.clientY;
|
||||
var y = -this.workspace_.scrollY - dy;
|
||||
y = goog.math.clamp(y, 0, metrics.contentHeight - metrics.viewHeight);
|
||||
this.scrollbar_.set(y);
|
||||
};
|
||||
|
||||
/**
|
||||
* Determine if a drag delta is toward the workspace, based on the position
|
||||
* and orientation of the flyout. This is used in determineDragIntention_ to
|
||||
* determine if a new block should be created or if the flyout should scroll.
|
||||
* @param {number} dx X delta of the drag.
|
||||
* @param {number} dy Y delta of the drag.
|
||||
* and orientation of the flyout. This to decide if a new block should be
|
||||
* created or if the flyout should scroll.
|
||||
* @param {!goog.math.Coordinate} currentDragDeltaXY How far the pointer has
|
||||
* moved from the position at mouse down, in pixel units.
|
||||
* @return {boolean} true if the drag is toward the workspace.
|
||||
* @private
|
||||
* @package
|
||||
*/
|
||||
Blockly.VerticalFlyout.prototype.isDragTowardWorkspace_ = function(dx, dy) {
|
||||
Blockly.VerticalFlyout.prototype.isDragTowardWorkspace = function(currentDragDeltaXY) {
|
||||
var dx = currentDragDeltaXY.x;
|
||||
var dy = currentDragDeltaXY.y;
|
||||
// Direction goes from -180 to 180, with 0 toward the right and 90 on top.
|
||||
var dragDirection = Math.atan2(dy, dx) / Math.PI * 180;
|
||||
|
||||
|
|
763
core/gesture.js
Normal file
763
core/gesture.js
Normal file
|
@ -0,0 +1,763 @@
|
|||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2017 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 The class representing an in-progress gesture, usually a drag
|
||||
* or a tap.
|
||||
* @author fenichel@google.com (Rachel Fenichel)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.Gesture');
|
||||
|
||||
goog.require('Blockly.BlockDragger');
|
||||
goog.require('Blockly.constants');
|
||||
goog.require('Blockly.FlyoutDragger');
|
||||
goog.require('Blockly.Tooltip');
|
||||
goog.require('Blockly.Touch');
|
||||
goog.require('Blockly.WorkspaceDragger');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.math.Coordinate');
|
||||
|
||||
|
||||
/**
|
||||
* NB: In this file "start" refers to touchstart, mousedown, and pointerstart
|
||||
* events. "End" refers to touchend, mouseup, and pointerend events.
|
||||
* TODO: Consider touchcancel/pointercancel.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class for one gesture.
|
||||
* @param {!Event} e The event that kicked off this gesture.
|
||||
* @param {!Blockly.WorkspaceSvg} creatorWorkspace The workspace that created
|
||||
* this gesture and has a reference to it.
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.Gesture = function(e, creatorWorkspace) {
|
||||
|
||||
/**
|
||||
* The position of the mouse when the gesture started. Units are css pixels,
|
||||
* with (0, 0) at the top left of the browser window (mouseEvent clientX/Y).
|
||||
* @type {goog.math.Coordinate}
|
||||
*/
|
||||
this.mouseDownXY_ = null;
|
||||
|
||||
/**
|
||||
* How far the mouse has moved during this drag, in pixel units.
|
||||
* (0, 0) is at this.mouseDownXY_.
|
||||
* @type {goog.math.Coordinate}
|
||||
* private
|
||||
*/
|
||||
this.currentDragDeltaXY_ = 0;
|
||||
|
||||
/**
|
||||
* The field that the gesture started on, or null if it did not start on a
|
||||
* field.
|
||||
* @type {Blockly.Field}
|
||||
* @private
|
||||
*/
|
||||
this.startField_ = null;
|
||||
|
||||
/**
|
||||
* The block that the gesture started on, or null if it did not block on a
|
||||
* field.
|
||||
* @type {Blockly.BlockSvg}
|
||||
* @private
|
||||
*/
|
||||
this.startBlock_ = null;
|
||||
|
||||
/**
|
||||
* The workspace that the gesture started on. There may be multiple
|
||||
* workspaces on a page; this is more accurate than using
|
||||
* Blockly.getMainWorkspace().
|
||||
* @type {Blockly.WorkspaceSvg}
|
||||
* @private
|
||||
*/
|
||||
this.startWorkspace_ = null;
|
||||
|
||||
/**
|
||||
* The workspace that created this gesture. This workspace keeps a reference
|
||||
* to the gesture, which will need to be cleared at deletion.
|
||||
* This may be different from the start workspace. For instance, a flyout is
|
||||
* a workspace, but its parent workspace manages gestures for it.
|
||||
* @type {Blockly.WorkspaceSvg}
|
||||
* @private
|
||||
*/
|
||||
this.creatorWorkspace_ = creatorWorkspace;
|
||||
|
||||
/**
|
||||
* Whether the pointer has at any point moved out of the drag radius.
|
||||
* A gesture that exceeds the drag radius is a drag even if it ends exactly at
|
||||
* its start point.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.hasExceededDragRadius_ = false;
|
||||
|
||||
/**
|
||||
* Whether the workspace is currently being dragged.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.isDraggingWorkspace_ = false;
|
||||
|
||||
/**
|
||||
* Whether the block is currently being dragged.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.isDraggingBlock_ = false;
|
||||
|
||||
/**
|
||||
* The event that most recently updated this gesture.
|
||||
* @type {!Event}
|
||||
* @private
|
||||
*/
|
||||
this.mostRecentEvent_ = e;
|
||||
|
||||
/**
|
||||
* A handle to use to unbind a mouse move listener at the end of a drag.
|
||||
* Opaque data returned from Blockly.bindEventWithChecks_.
|
||||
* @type {Array.<!Array>}
|
||||
* @private
|
||||
*/
|
||||
this.onMoveWrapper_ = null;
|
||||
|
||||
/**
|
||||
* A handle to use to unbind a mouse up listener at the end of a drag.
|
||||
* Opaque data returned from Blockly.bindEventWithChecks_.
|
||||
* @type {Array.<!Array>}
|
||||
* @private
|
||||
*/
|
||||
this.onUpWrapper_ = null;
|
||||
|
||||
/**
|
||||
* The object tracking a block drag, or null if none is in progress.
|
||||
* @type {Blockly.BlockDragger}
|
||||
* @private
|
||||
*/
|
||||
this.blockDragger_ = null;
|
||||
|
||||
/**
|
||||
* The object tracking a workspace or flyout workspace drag, or null if none
|
||||
* is in progress.
|
||||
* @type {Blockly.WorkspaceDragger}
|
||||
* @private
|
||||
*/
|
||||
this.workspaceDragger_ = null;
|
||||
|
||||
/**
|
||||
* The flyout a gesture started in, if any.
|
||||
* @type {Blockly.Flyout}
|
||||
* @private
|
||||
*/
|
||||
this.flyout_ = null;
|
||||
|
||||
/**
|
||||
* Boolean for sanity-checking that some code is only called once.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.calledUpdateIsDragging_ = false;
|
||||
|
||||
/**
|
||||
* Boolean for sanity-checking that some code is only called once.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.hasStarted_ = false;
|
||||
|
||||
/**
|
||||
* Boolean used internally to break a cycle in disposal.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.isEnding_ = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sever all links from this object.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Gesture.prototype.dispose = function() {
|
||||
Blockly.Touch.clearTouchIdentifier();
|
||||
Blockly.Tooltip.unblock();
|
||||
// Clear the owner's reference to this gesture.
|
||||
this.creatorWorkspace_.clearGesture();
|
||||
|
||||
if (this.onMoveWrapper_) {
|
||||
Blockly.unbindEvent_(this.onMoveWrapper_);
|
||||
}
|
||||
if (this.onUpWrapper_) {
|
||||
Blockly.unbindEvent_(this.onUpWrapper_);
|
||||
}
|
||||
|
||||
|
||||
this.startField_ = null;
|
||||
this.startBlock_ = null;
|
||||
this.startWorkspace_ = null;
|
||||
this.flyout_ = null;
|
||||
|
||||
if (this.blockDragger_) {
|
||||
this.blockDragger_.dispose();
|
||||
this.blockDragger_ = null;
|
||||
}
|
||||
if (this.workspaceDragger_) {
|
||||
this.workspaceDragger_.dispose();
|
||||
this.workspaceDragger_ = null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update internal state based on an event.
|
||||
* @param {!Event} e The most recent mouse or touch event.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Gesture.prototype.updateFromEvent_ = function(e) {
|
||||
var currentXY = new goog.math.Coordinate(e.clientX, e.clientY);
|
||||
var changed = this.updateDragDelta_(currentXY);
|
||||
// Exceeded the drag radius for the first time.
|
||||
if (changed){
|
||||
this.updateIsDragging_();
|
||||
Blockly.longStop_();
|
||||
}
|
||||
this.mostRecentEvent_ = e;
|
||||
};
|
||||
|
||||
/**
|
||||
* DO MATH to set currentDragDeltaXY_ based on the most recent mouse position.
|
||||
* @param {!goog.math.Coordinate} currentXY The most recent mouse/pointer
|
||||
* position, in pixel units, with (0, 0) at the window's top left corner.
|
||||
* @return {boolean} True if the drag just exceeded the drag radius for the
|
||||
* first time.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Gesture.prototype.updateDragDelta_ = function(currentXY) {
|
||||
this.currentDragDeltaXY_ = goog.math.Coordinate.difference(currentXY,
|
||||
this.mouseDownXY_);
|
||||
|
||||
if (!this.hasExceededDragRadius_) {
|
||||
var currentDragDelta = goog.math.Coordinate.magnitude(
|
||||
this.currentDragDeltaXY_);
|
||||
|
||||
// The flyout has a different drag radius from the rest of Blockly.
|
||||
var limitRadius = this.flyout_ ? Blockly.FLYOUT_DRAG_RADIUS :
|
||||
Blockly.DRAG_RADIUS;
|
||||
|
||||
this.hasExceededDragRadius_ = currentDragDelta > limitRadius;
|
||||
return this.hasExceededDragRadius_;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Update this gesture to record whether a block is being dragged from the
|
||||
* flyout.
|
||||
* This function should be called on a mouse/touch move event the first time the
|
||||
* drag radius is exceeded. It should be called no more than once per gesture.
|
||||
* If a block should be dragged from the flyout this function creates the new
|
||||
* block on the main workspace and updates startBlock_ and startWorkspace_.
|
||||
* @return {boolean} True if a block is being dragged from the flyout.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Gesture.prototype.updateIsDraggingFromFlyout_ = function() {
|
||||
// Disabled blocks may not be dragged from the flyout.
|
||||
if (this.startBlock_.disabled) {
|
||||
return false;
|
||||
}
|
||||
if (!this.flyout_.isScrollable() ||
|
||||
this.flyout_.isDragTowardWorkspace(this.currentDragDeltaXY_)) {
|
||||
this.startWorkspace_ = this.flyout_.targetWorkspace_;
|
||||
this.startWorkspace_.updateScreenCalculationsIfScrolled();
|
||||
// Start the event group now, so that the same event group is used for block
|
||||
// creation and block dragging.
|
||||
if (!Blockly.Events.getGroup()) {
|
||||
Blockly.Events.setGroup(true);
|
||||
}
|
||||
this.startBlock_ = this.flyout_.createBlock(this.startBlock_);
|
||||
this.startBlock_.select();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Update this gesture to record whether a block is being dragged.
|
||||
* This function should be called on a mouse/touch move event the first time the
|
||||
* drag radius is exceeded. It should be called no more than once per gesture.
|
||||
* If a block should be dragged, either from the flyout or in the workspace,
|
||||
* this function creates the necessary BlockDragger and starts the drag.
|
||||
* @return {boolean} true if a block is being dragged.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Gesture.prototype.updateIsDraggingBlock_ = function() {
|
||||
if (!this.startBlock_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.flyout_) {
|
||||
this.isDraggingBlock_ = this.updateIsDraggingFromFlyout_();
|
||||
} else if (this.startBlock_.isMovable()){
|
||||
this.isDraggingBlock_ = true;
|
||||
}
|
||||
|
||||
if (this.isDraggingBlock_) {
|
||||
this.startDraggingBlock_();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Update this gesture to record whether a workspace is being dragged.
|
||||
* This function should be called on a mouse/touch move event the first time the
|
||||
* drag radius is exceeded. It should be called no more than once per gesture.
|
||||
* If a workspace is being dragged this function creates the necessary
|
||||
* WorkspaceDragger or FlyoutDragger and starts the drag.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Gesture.prototype.updateIsDraggingWorkspace_ = function() {
|
||||
var wsMovable = this.flyout_ ? this.flyout_.isScrollable() :
|
||||
this.startWorkspace_ && this.startWorkspace_.isDraggable();
|
||||
|
||||
if (!wsMovable) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.flyout_) {
|
||||
this.workspaceDragger_ = new Blockly.FlyoutDragger(this.flyout_);
|
||||
} else {
|
||||
this.workspaceDragger_ = new Blockly.WorkspaceDragger(this.startWorkspace_);
|
||||
}
|
||||
|
||||
this.isDraggingWorkspace_ = true;
|
||||
this.workspaceDragger_.startDrag();
|
||||
};
|
||||
|
||||
/**
|
||||
* Update this gesture to record whether anything is being dragged.
|
||||
* This function should be called on a mouse/touch move event the first time the
|
||||
* drag radius is exceeded. It should be called no more than once per gesture.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Gesture.prototype.updateIsDragging_ = function() {
|
||||
// Sanity check.
|
||||
goog.asserts.assert(!this.calledUpdateIsDragging_,
|
||||
'updateIsDragging_ should only be called once per gesture.');
|
||||
this.calledUpdateIsDragging_ = true;
|
||||
|
||||
// First check if it was a block drag.
|
||||
if (this.updateIsDraggingBlock_()) {
|
||||
return;
|
||||
}
|
||||
// Then check if it's a workspace drag.
|
||||
this.updateIsDraggingWorkspace_();
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a block dragger and start dragging the selected block.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Gesture.prototype.startDraggingBlock_ = function() {
|
||||
this.blockDragger_ = new Blockly.BlockDragger(this.startBlock_,
|
||||
this.startWorkspace_);
|
||||
this.blockDragger_.startBlockDrag(this.currentDragDeltaXY_);
|
||||
this.blockDragger_.dragBlock(this.mostRecentEvent_,
|
||||
this.currentDragDeltaXY_);
|
||||
};
|
||||
|
||||
/**
|
||||
* Start a gesture: update the workspace to indicate that a gesture is in
|
||||
* progress and bind mousemove and mouseup handlers.
|
||||
* @param {!Event} e A mouse down or touch start event.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Gesture.prototype.doStart = function(e) {
|
||||
if (Blockly.utils.isTargetInput(e)) {
|
||||
this.cancel();
|
||||
return;
|
||||
}
|
||||
this.hasStarted_ = true;
|
||||
|
||||
Blockly.BlockSvg.disconnectUiStop_();
|
||||
this.startWorkspace_.updateScreenCalculationsIfScrolled();
|
||||
if (this.startWorkspace_.isMutator) {
|
||||
// Mutator's coordinate system could be out of date because the bubble was
|
||||
// dragged, the block was moved, the parent workspace zoomed, etc.
|
||||
this.startWorkspace_.resize();
|
||||
}
|
||||
this.startWorkspace_.markFocused();
|
||||
this.mostRecentEvent_ = e;
|
||||
|
||||
// Hide chaff also hides the flyout by default.
|
||||
Blockly.hideChaff(!!this.flyout_);
|
||||
Blockly.Tooltip.block();
|
||||
|
||||
if (this.startBlock_) {
|
||||
this.startBlock_.select();
|
||||
}
|
||||
|
||||
if (Blockly.utils.isRightButton(e)) {
|
||||
this.handleRightClick(e);
|
||||
return;
|
||||
}
|
||||
|
||||
if (goog.string.caseInsensitiveEquals(e.type, 'touchstart')) {
|
||||
Blockly.longStart_(e, this);
|
||||
}
|
||||
|
||||
this.mouseDownXY_ = new goog.math.Coordinate(e.clientX, e.clientY);
|
||||
|
||||
this.onMoveWrapper_ = Blockly.bindEventWithChecks_(
|
||||
document, 'mousemove', null, this.handleMove.bind(this));
|
||||
this.onUpWrapper_ = Blockly.bindEventWithChecks_(
|
||||
document, 'mouseup', null, this.handleUp.bind(this));
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle a mouse move or touch move event.
|
||||
* @param {!Event} e A mouse move or touch move event.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Gesture.prototype.handleMove = function(e) {
|
||||
this.updateFromEvent_(e);
|
||||
if (this.isDraggingWorkspace_) {
|
||||
this.workspaceDragger_.drag(this.currentDragDeltaXY_);
|
||||
} else if (this.isDraggingBlock_) {
|
||||
this.blockDragger_.dragBlock(this.mostRecentEvent_,
|
||||
this.currentDragDeltaXY_);
|
||||
}
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle a mouse up or touch end event.
|
||||
* @param {!Event} e A mouse up or touch end event.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Gesture.prototype.handleUp = function(e) {
|
||||
this.updateFromEvent_(e);
|
||||
Blockly.longStop_();
|
||||
|
||||
if (this.isEnding_) {
|
||||
console.log('Trying to end a gesture recursively.');
|
||||
return;
|
||||
}
|
||||
this.isEnding_ = true;
|
||||
// The ordering of these checks is important: drags have higher priority than
|
||||
// clicks. Fields have higher priority than blocks; blocks have higher
|
||||
// priority than workspaces.
|
||||
if (this.isDraggingBlock_) {
|
||||
this.blockDragger_.endBlockDrag(e, this.currentDragDeltaXY_);
|
||||
} else if (this.isDraggingWorkspace_) {
|
||||
this.workspaceDragger_.endDrag(this.currentDragDeltaXY_);
|
||||
} else if (this.isFieldClick_()) {
|
||||
this.doFieldClick_();
|
||||
} else if (this.isBlockClick_()) {
|
||||
this.doBlockClick_();
|
||||
} else if (this.isWorkspaceClick_()) {
|
||||
this.doWorkspaceClick_();
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
this.dispose();
|
||||
};
|
||||
|
||||
/**
|
||||
* Cancel an in-progress gesture. If a workspace or block drag is in progress,
|
||||
* end the drag at the most recent location.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Gesture.prototype.cancel = function() {
|
||||
// Disposing of a block cancels in-progress drags, but dragging to a delete
|
||||
// area disposes of a block and leads to recursive disposal. Break that cycle.
|
||||
if (this.isEnding_) {
|
||||
return;
|
||||
}
|
||||
Blockly.longStop_();
|
||||
if (this.isDraggingBlock_) {
|
||||
this.blockDragger_.endBlockDrag(this.mostRecentEvent_,
|
||||
this.currentDragDeltaXY_);
|
||||
} else if (this.isDraggingWorkspace_) {
|
||||
this.workspaceDragger_.endDrag(this.currentDragDeltaXY_);
|
||||
}
|
||||
this.dispose();
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle a real or faked right-click event by showing a context menu.
|
||||
* @param {!Event} e A mouse move or touch move event.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Gesture.prototype.handleRightClick = function(e) {
|
||||
if (this.startBlock_) {
|
||||
this.bringBlockToFront_();
|
||||
Blockly.hideChaff(this.flyout_);
|
||||
this.startBlock_.showContextMenu_(e);
|
||||
} else if (this.startWorkspace_ && !this.flyout_) {
|
||||
Blockly.hideChaff();
|
||||
this.startWorkspace_.showContextMenu_(e);
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
this.dispose();
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle a mousedown/touchstart event on a workspace.
|
||||
* @param {!Event} e A mouse down or touch start event.
|
||||
* @param {!Blockly.Workspace} ws The workspace the event hit.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Gesture.prototype.handleWsStart = function(e, ws) {
|
||||
goog.asserts.assert(!this.hasStarted_,
|
||||
'Tried to call gesture.handleWsStart, but the gesture had already been ' +
|
||||
'started.');
|
||||
this.setStartWorkspace_(ws);
|
||||
this.mostRecentEvent_ = e;
|
||||
this.doStart(e);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle a mousedown/touchstart event on a flyout.
|
||||
* @param {!Event} e A mouse down or touch start event.
|
||||
* @param {!Blockly.Flyout} flyout The flyout the event hit.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Gesture.prototype.handleFlyoutStart = function(e, flyout) {
|
||||
goog.asserts.assert(!this.hasStarted_,
|
||||
'Tried to call gesture.handleFlyoutStart, but the gesture had already been ' +
|
||||
'started.');
|
||||
this.setStartFlyout_(flyout);
|
||||
this.handleWsStart(e, flyout.getWorkspace());
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle a mousedown/touchstart event on a block.
|
||||
* @param {!Event} e A mouse down or touch start event.
|
||||
* @param {!Blockly.BlockSvg} block The block the event hit.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Gesture.prototype.handleBlockStart = function(e, block) {
|
||||
goog.asserts.assert(!this.hasStarted_,
|
||||
'Tried to call gesture.handleBlockStart, but the gesture had already been ' +
|
||||
'started.');
|
||||
this.setStartBlock(block);
|
||||
this.mostRecentEvent_ = e;
|
||||
};
|
||||
|
||||
/* Begin functions defining what actions to take to execute clicks on each type
|
||||
* of target. Any developer wanting to add behaviour on clicks should modify
|
||||
* only this code. */
|
||||
|
||||
/**
|
||||
* Execute a field click.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Gesture.prototype.doFieldClick_ = function() {
|
||||
this.startField_.showEditor_();
|
||||
this.bringBlockToFront_();
|
||||
};
|
||||
|
||||
/**
|
||||
* Execute a block click.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Gesture.prototype.doBlockClick_ = function() {
|
||||
// Block click in an autoclosing flyout.
|
||||
if (this.flyout_ && this.flyout_.autoClose) {
|
||||
if (!Blockly.Events.getGroup()) {
|
||||
Blockly.Events.setGroup(true);
|
||||
}
|
||||
var newBlock = this.flyout_.createBlock(this.startBlock_);
|
||||
newBlock.scheduleSnapAndBump();
|
||||
} else {
|
||||
// A field is being edited if either the WidgetDiv or DropDownDiv is currently open.
|
||||
// If a field is being edited, don't fire any click events.
|
||||
var fieldEditing = Blockly.WidgetDiv.isVisible() || Blockly.DropDownDiv.isVisible();
|
||||
if (!fieldEditing) {
|
||||
Blockly.Events.fire(
|
||||
new Blockly.Events.Ui(this.startBlock_, 'click', undefined, undefined));
|
||||
// Scratch-specific: also fire a "stack click" event for this stack.
|
||||
// This is used to toggle the stack when any block in the stack is clicked.
|
||||
var rootBlock = this.startBlock_.getRootBlock();
|
||||
Blockly.Events.fire(
|
||||
new Blockly.Events.Ui(rootBlock, 'stackclick', undefined, undefined));
|
||||
}
|
||||
}
|
||||
this.bringBlockToFront_();
|
||||
Blockly.Events.setGroup(false);
|
||||
};
|
||||
|
||||
/**
|
||||
* Execute a workspace click.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Gesture.prototype.doWorkspaceClick_ = function() {
|
||||
if (Blockly.selected) {
|
||||
Blockly.selected.unselect();
|
||||
}
|
||||
};
|
||||
|
||||
/* End functions defining what actions to take to execute clicks on each type
|
||||
* of target. */
|
||||
|
||||
/**
|
||||
* Move the dragged/clicked block to the front of the workspace so that it is
|
||||
* not occluded by other blocks.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Gesture.prototype.bringBlockToFront_ = function() {
|
||||
// Blocks in the flyout don't overlap, so skip the work.
|
||||
if (this.startBlock_ && !this.flyout_) {
|
||||
this.startBlock_.bringToFront();
|
||||
}
|
||||
};
|
||||
|
||||
/* Begin functions for populating a gesture at mouse down. */
|
||||
|
||||
/**
|
||||
* Record the field that a gesture started on.
|
||||
* @param {Blockly.Field} field The field the gesture started on.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Gesture.prototype.setStartField = function(field) {
|
||||
goog.asserts.assert(!this.hasStarted_,
|
||||
'Tried to call gesture.setStartField, but the gesture had already been ' +
|
||||
'started.');
|
||||
if (!this.startField_) {
|
||||
this.startField_ = field;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Record the block that a gesture started on.
|
||||
* If the block is a shadow, record the parent. If the block is in the flyout,
|
||||
* use the root block from the block group.
|
||||
* @param {Blockly.BlockSvg} block The block the gesture started on.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Gesture.prototype.setStartBlock = function(block) {
|
||||
if (!this.startBlock_) {
|
||||
if (block.isShadow()) {
|
||||
this.setStartBlock(block.getParent());
|
||||
} else if (block.isInFlyout && block != block.getRootBlock()) {
|
||||
this.setStartBlock(block.getRootBlock());
|
||||
} else {
|
||||
this.startBlock_ = block;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Record the workspace that a gesture started on.
|
||||
* @param {Blockly.WorkspaceSvg} ws The workspace the gesture started on.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Gesture.prototype.setStartWorkspace_ = function(ws) {
|
||||
if (!this.startWorkspace_) {
|
||||
this.startWorkspace_ = ws;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Record the flyout that a gesture started on.
|
||||
* @param {Blockly.Flyout} flyout The flyout the gesture started on.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Gesture.prototype.setStartFlyout_ = function(flyout) {
|
||||
if (!this.flyout_) {
|
||||
this.flyout_ = flyout;
|
||||
}
|
||||
};
|
||||
|
||||
/* End functions for populating a gesture at mouse down. */
|
||||
|
||||
/* Begin helper functions defining types of clicks. Any developer wanting
|
||||
* to change the definition of a click should modify only this code. */
|
||||
|
||||
/**
|
||||
* Whether this gesture is a click on a block. This should only be called when
|
||||
* ending a gesture (mouse up, touch end).
|
||||
* @return {boolean} whether this gesture was a click on a block.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Gesture.prototype.isBlockClick_ = function() {
|
||||
// A block click starts on a block, never escapes the drag radius, and is not
|
||||
// a field click.
|
||||
var hasStartBlock = !!this.startBlock_;
|
||||
return hasStartBlock && !this.hasExceededDragRadius_ && !this.isFieldClick_();
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether this gesture is a click on a field. This should only be called when
|
||||
* ending a gesture (mouse up, touch end).
|
||||
* @return {boolean} whether this gesture was a click on a field.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Gesture.prototype.isFieldClick_ = function() {
|
||||
var fieldEditable = this.startField_ ?
|
||||
this.startField_.isCurrentlyEditable() : false;
|
||||
return fieldEditable && !this.hasExceededDragRadius_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether this gesture is a click on a workspace. This should only be called
|
||||
* when ending a gesture (mouse up, touch end).
|
||||
* @return {boolean} whether this gesture was a click on a workspace.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Gesture.prototype.isWorkspaceClick_ = function() {
|
||||
var onlyTouchedWorkspace = !this.startBlock_ && !this.startField_;
|
||||
return onlyTouchedWorkspace && !this.hasExceededDragRadius_;
|
||||
};
|
||||
|
||||
/* End helper functions defining types of clicks. */
|
||||
|
||||
/**
|
||||
* Whether this gesture is a drag of either a workspace or block.
|
||||
* This function is called externally to block actions that cannot be taken
|
||||
* mid-drag (e.g. using the keyboard to delete the selected blocks).
|
||||
* @return {boolean} true if this gesture is a drag of a workspace or block.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Gesture.prototype.isDragging = function() {
|
||||
return this.isDraggingWorkspace_ || this.isDraggingBlock_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether this gesture has already been started. In theory every mouse down
|
||||
* has a corresponding mouse up, but in reality it is possible to lose a
|
||||
* mouse up, leaving an in-process gesture hanging.
|
||||
* @return {boolean} whether this gesture was a click on a workspace.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Gesture.prototype.hasStarted = function() {
|
||||
return this.hasStarted_;
|
||||
};
|
|
@ -170,7 +170,7 @@ Blockly.Icon.prototype.renderIcon = function(cursorX) {
|
|||
|
||||
/**
|
||||
* Notification that the icon has moved. Update the arrow accordingly.
|
||||
* @param {!goog.math.Coordinate} xy Absolute location.
|
||||
* @param {!goog.math.Coordinate} xy Absolute location in workspace coordinates.
|
||||
*/
|
||||
Blockly.Icon.prototype.setIconLocation = function(xy) {
|
||||
this.iconXY_ = xy;
|
||||
|
@ -197,7 +197,8 @@ Blockly.Icon.prototype.computeIconLocation = function() {
|
|||
|
||||
/**
|
||||
* Returns the center of the block's icon relative to the surface.
|
||||
* @return {!goog.math.Coordinate} Object with x and y properties.
|
||||
* @return {!goog.math.Coordinate} Object with x and y properties in workspace
|
||||
* coordinates.
|
||||
*/
|
||||
Blockly.Icon.prototype.getIconLocation = function() {
|
||||
return this.iconXY_;
|
||||
|
|
|
@ -166,7 +166,6 @@ Blockly.createDom_ = function(container, options) {
|
|||
'operator': 'in', 'result': 'outGlow'}, replacementGlowFilter);
|
||||
Blockly.utils.createSvgElement('feComposite',
|
||||
{'in': 'SourceGraphic', 'in2': 'outGlow', 'operator': 'over'}, replacementGlowFilter);
|
||||
|
||||
/*
|
||||
<pattern id="blocklyDisabledPattern837493" patternUnits="userSpaceOnUse"
|
||||
width="10" height="10">
|
||||
|
@ -237,7 +236,7 @@ Blockly.createMainWorkspace_ = function(svg, options, blockDragSurface, workspac
|
|||
|
||||
if (!options.readOnly && !options.hasScrollbars) {
|
||||
var workspaceChanged = function() {
|
||||
if (Blockly.dragMode_ == Blockly.DRAG_NONE) {
|
||||
if (!mainWorkspace.isDragging()) {
|
||||
var metrics = mainWorkspace.getMetrics();
|
||||
var edgeLeft = metrics.viewLeft + metrics.absoluteLeft;
|
||||
var edgeTop = metrics.viewTop + metrics.absoluteTop;
|
||||
|
@ -371,10 +370,6 @@ Blockly.inject.bindDocumentEvents_ = function() {
|
|||
// should run regardless of what other touch event handlers have run.
|
||||
Blockly.bindEvent_(document, 'touchend', null, Blockly.longStop_);
|
||||
Blockly.bindEvent_(document, 'touchcancel', null, Blockly.longStop_);
|
||||
// Don't use bindEvent_ for document's mouseup since that would create a
|
||||
// corresponding touch handler that would squelch the ability to interact
|
||||
// with non-Blockly elements.
|
||||
document.addEventListener('mouseup', Blockly.onMouseUp_, false);
|
||||
// Some iPad versions don't fire resize after portrait to landscape change.
|
||||
if (goog.userAgent.IPAD) {
|
||||
Blockly.bindEventWithChecks_(window, 'orientationchange', document,
|
||||
|
|
645
core/insertion_marker_manager.js
Normal file
645
core/insertion_marker_manager.js
Normal file
|
@ -0,0 +1,645 @@
|
|||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2017 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 that controls updates to connections during drags.
|
||||
* @author fenichel@google.com (Rachel Fenichel)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.InsertionMarkerManager');
|
||||
|
||||
goog.require('Blockly.RenderedConnection');
|
||||
|
||||
goog.require('goog.math.Coordinate');
|
||||
|
||||
|
||||
/**
|
||||
* Class that controls updates to connections during drags. It is primarily
|
||||
* responsible for finding the closest eligible connection and highlighting or
|
||||
* unhiglighting it as needed during a drag.
|
||||
* @param {!Blockly.BlockSvg} block The top block in the stack being dragged.
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.InsertionMarkerManager = function(block) {
|
||||
Blockly.selected = block;
|
||||
|
||||
/**
|
||||
* The top block in the stack being dragged.
|
||||
* Does not change during a drag.
|
||||
* @type {!Blockly.Block}
|
||||
* @private
|
||||
*/
|
||||
this.topBlock_ = block;
|
||||
|
||||
/**
|
||||
* The workspace on which these connections are being dragged.
|
||||
* Does not change during a drag.
|
||||
* @type {!Blockly.WorkspaceSvg}
|
||||
* @private
|
||||
*/
|
||||
this.workspace_ = block.workspace;
|
||||
|
||||
/**
|
||||
* The last connection on the stack, if it's not the last connection on the
|
||||
* first block.
|
||||
* Set in initAvailableConnections, if at all.
|
||||
* @type {Blockly.RenderedConnection}
|
||||
* @private
|
||||
*/
|
||||
this.lastOnStack_ = null;
|
||||
|
||||
/**
|
||||
* The insertion marker corresponding to the last block in the stack, if
|
||||
* that's not the same as the first block in the stack.
|
||||
* Set in initAvailableConnections, if at all
|
||||
* @type {Blockly.BlockSvg}
|
||||
* @private
|
||||
*/
|
||||
this.lastMarker_ = null;
|
||||
|
||||
/**
|
||||
* The insertion marker that shows up between blocks to show where a block
|
||||
* would go if dropped immediately.
|
||||
* This is the scratch-blocks equivalent of connection highlighting.
|
||||
* @type {Blockly.BlockSvg}
|
||||
* @private
|
||||
*/
|
||||
this.firstMarker_ = this.createMarkerBlock_(this.topBlock_);
|
||||
|
||||
/**
|
||||
* The connection that this block would connect to if released immediately.
|
||||
* Updated on every mouse move.
|
||||
* This is not on any of the blocks that are being dragged.
|
||||
* @type {Blockly.RenderedConnection}
|
||||
* @private
|
||||
*/
|
||||
this.closestConnection_ = null;
|
||||
|
||||
/**
|
||||
* The connection that would connect to this.closestConnection_ if this block
|
||||
* were released immediately.
|
||||
* Updated on every mouse move.
|
||||
* This is on the top block that is being dragged or the last block in the
|
||||
* dragging stack.
|
||||
* @type {Blockly.RenderedConnection}
|
||||
* @private
|
||||
*/
|
||||
this.localConnection_ = null;
|
||||
|
||||
/**
|
||||
* Whether the block would be deleted if it were dropped immediately.
|
||||
* Updated on every mouse move.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.wouldDeleteBlock_ = false;
|
||||
|
||||
/**
|
||||
* Connection on the insertion marker block that corresponds to
|
||||
* this.localConnection_ on the currently dragged block.
|
||||
* This is part of the scratch-blocks equivalent of connection highlighting.
|
||||
* @type {Blockly.RenderedConnection}
|
||||
* @private
|
||||
*/
|
||||
this.markerConnection_ = null;
|
||||
|
||||
/**
|
||||
* Whether we are currently highlighting the block (shadow or real) that would
|
||||
* be replaced if the drag were released immediately.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.highlightingBlock_ = false;
|
||||
|
||||
/**
|
||||
* The block that is being highlighted for replacement, or null.
|
||||
* @type {Blockly.BlockSvg}
|
||||
* @private
|
||||
*/
|
||||
this.highlightedBlock_ = null;
|
||||
|
||||
/**
|
||||
* The connections on the dragging blocks that are available to connect to
|
||||
* other blocks. This includes all open connections on the top block, as well
|
||||
* as the last connection on the block stack.
|
||||
* Does not change during a drag.
|
||||
* @type {!Array.<!Blockly.RenderedConnection>}
|
||||
* @private
|
||||
*/
|
||||
this.availableConnections_ = this.initAvailableConnections_();
|
||||
};
|
||||
|
||||
/**
|
||||
* Sever all links from this object.
|
||||
* @package
|
||||
*/
|
||||
Blockly.InsertionMarkerManager.prototype.dispose = function() {
|
||||
this.topBlock_ = null;
|
||||
this.workspace_ = null;
|
||||
this.availableConnections_.length = 0;
|
||||
this.closestConnection_ = null;
|
||||
this.localConnection_ = null;
|
||||
if (this.firstMarker_) {
|
||||
this.firstMarker_.dispose();
|
||||
this.firstMarker_ = null;
|
||||
}
|
||||
if (this.lastMarker_) {
|
||||
this.lastMarker_.dispose();
|
||||
this.lastMarker_ = null;
|
||||
}
|
||||
this.highlightedBlock_ = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return whether the block would be deleted if dropped immediately, based on
|
||||
* information from the most recent move event.
|
||||
* @return {boolean} true if the block would be deleted if dropped immediately.
|
||||
* @package
|
||||
*/
|
||||
Blockly.InsertionMarkerManager.prototype.wouldDeleteBlock = function() {
|
||||
return this.wouldDeleteBlock_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Connect to the closest connection and render the results.
|
||||
* This should be called at the end of a drag.
|
||||
* @package
|
||||
*/
|
||||
Blockly.InsertionMarkerManager.prototype.applyConnections = function() {
|
||||
if (this.closestConnection_) {
|
||||
// Don't fire events for insertion markers.
|
||||
Blockly.Events.disable();
|
||||
this.hidePreview_();
|
||||
Blockly.Events.enable();
|
||||
// Connect two blocks together.
|
||||
this.localConnection_.connect(this.closestConnection_);
|
||||
if (this.rendered) {
|
||||
// Trigger a connection animation.
|
||||
// Determine which connection is inferior (lower in the source stack).
|
||||
var inferiorConnection = this.localConnection_.isSuperior() ?
|
||||
this.closestConnection_ : this.localConnection_;
|
||||
inferiorConnection.getSourceBlock().connectionUiEffect();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update highlighted connections based on the most recent move location.
|
||||
* @param {!goog.math.Coordinate} dxy Position relative to drag start,
|
||||
* in workspace units.
|
||||
* @param {?number} deleteArea One of {@link Blockly.DELETE_AREA_TRASH},
|
||||
* {@link Blockly.DELETE_AREA_TOOLBOX}, or {@link Blockly.DELETE_AREA_NONE}.
|
||||
* @package
|
||||
*/
|
||||
Blockly.InsertionMarkerManager.prototype.update = function(dxy, deleteArea) {
|
||||
var candidate = this.getCandidate_(dxy);
|
||||
|
||||
this.wouldDeleteBlock_ = this.shouldDelete_(candidate, deleteArea);
|
||||
var shouldUpdate = this.wouldDeleteBlock_ ||
|
||||
this.shouldUpdatePreviews_(candidate, dxy);
|
||||
|
||||
if (shouldUpdate) {
|
||||
// Don't fire events for insertion marker creation or movement.
|
||||
Blockly.Events.disable();
|
||||
this.maybeHidePreview_(candidate);
|
||||
this.maybeShowPreview_(candidate);
|
||||
Blockly.Events.enable();
|
||||
}
|
||||
};
|
||||
|
||||
/**** Begin initialization functions ****/
|
||||
|
||||
/**
|
||||
* Create an insertion marker that represents the given block.
|
||||
* @param {!Blockly.BlockSvg} sourceBlock The block that the insertion marker
|
||||
* will represent.
|
||||
* @return {!Blockly.BlockSvg} The insertion marker that represents the given
|
||||
* block.
|
||||
* @private
|
||||
*/
|
||||
Blockly.InsertionMarkerManager.prototype.createMarkerBlock_ = function(sourceBlock) {
|
||||
var imType = sourceBlock.type;
|
||||
var result = this.workspace_.newBlock(imType);
|
||||
if (sourceBlock.mutationToDom) {
|
||||
var oldMutationDom = sourceBlock.mutationToDom();
|
||||
result.domToMutation(oldMutationDom);
|
||||
}
|
||||
result.setInsertionMarker(true, sourceBlock.width);
|
||||
result.initSvg();
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Populate the list of available connections on this block stack. This should
|
||||
* only be called once, at the beginning of a drag.
|
||||
* If the stack has more than one block, this function will populate
|
||||
* lastOnStack_ and create the corresponding insertion marker.
|
||||
* @return {!Array.<!Blockly.RenderedConnection>} a list of available
|
||||
* connections.
|
||||
* @private
|
||||
*/
|
||||
Blockly.InsertionMarkerManager.prototype.initAvailableConnections_ = function() {
|
||||
var available = this.topBlock_.getConnections_(false);
|
||||
// Also check the last connection on this stack
|
||||
var lastOnStack = this.topBlock_.lastConnectionInStack();
|
||||
if (lastOnStack && lastOnStack != this.topBlock_.nextConnection) {
|
||||
available.push(lastOnStack);
|
||||
this.lastOnStack_ = lastOnStack;
|
||||
this.lastMarker_ = this.createMarkerBlock_(lastOnStack.sourceBlock_);
|
||||
}
|
||||
return available;
|
||||
};
|
||||
|
||||
/**** End initialization functions ****/
|
||||
|
||||
|
||||
/**
|
||||
* Whether the previews (insertion marker and replacement marker) should be
|
||||
* updated based on the closest candidate and the current drag distance.
|
||||
* @param {!Object} candidate An object containing a local connection, a closest
|
||||
* connection, and a radius. Returned by getCandidate_.
|
||||
* @param {!goog.math.Coordinate} dxy Position relative to drag start,
|
||||
* in workspace units.
|
||||
* @return {boolean} whether the preview should be updated.
|
||||
* @private
|
||||
*/
|
||||
Blockly.InsertionMarkerManager.prototype.shouldUpdatePreviews_ = function(
|
||||
candidate, dxy) {
|
||||
var candidateLocal = candidate.local;
|
||||
var candidateClosest = candidate.closest;
|
||||
var radius = candidate.radius;
|
||||
|
||||
// Found a connection!
|
||||
if (candidateLocal && candidateClosest) {
|
||||
if (candidateLocal.type == Blockly.OUTPUT_VALUE) {
|
||||
// Always update previews for output connections.
|
||||
return true;
|
||||
}
|
||||
// We're already showing an insertion marker.
|
||||
// Decide whether the new connection has higher priority.
|
||||
if (this.localConnection_ && this.closestConnection_) {
|
||||
// The connection was the same as the current connection.
|
||||
if (this.closestConnection_ == candidateClosest) {
|
||||
return false;
|
||||
}
|
||||
var xDiff = this.localConnection_.x_ + dxy.x - this.closestConnection_.x_;
|
||||
var yDiff = this.localConnection_.y_ + dxy.y - this.closestConnection_.y_;
|
||||
var curDistance = Math.sqrt(xDiff * xDiff + yDiff * yDiff);
|
||||
// Slightly prefer the existing preview over a new preview.
|
||||
return !(candidateClosest && radius > curDistance -
|
||||
Blockly.CURRENT_CONNECTION_PREFERENCE);
|
||||
} else if (!this.localConnection_ && !this.closestConnection_) {
|
||||
// We weren't showing a preview before, but we should now.
|
||||
return true;
|
||||
} else {
|
||||
console.error('Only one of localConnection_ and closestConnection_ was set.');
|
||||
}
|
||||
} else { // No connection found.
|
||||
// Only need to update if we were showing a preview before.
|
||||
return !!(this.localConnection_ && this.closestConnection_);
|
||||
}
|
||||
|
||||
console.error('Returning true from shouldUpdatePreviews, but it\'s not clear why.');
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Find the nearest valid connection, which may be the same as the current
|
||||
* closest connection.
|
||||
* @param {!goog.math.Coordinate} dxy Position relative to drag start,
|
||||
* in workspace units.
|
||||
* @return {!Object} candidate An object containing a local connection, a closest
|
||||
* connection, and a radius.
|
||||
*/
|
||||
Blockly.InsertionMarkerManager.prototype.getCandidate_ = function(dxy) {
|
||||
var radius = this.getStartRadius_();
|
||||
var candidateClosest = null;
|
||||
var candidateLocal = null;
|
||||
|
||||
for (var i = 0; i < this.availableConnections_.length; i++) {
|
||||
var myConnection = this.availableConnections_[i];
|
||||
var neighbour = myConnection.closest(radius, dxy);
|
||||
if (neighbour.connection) {
|
||||
candidateClosest = neighbour.connection;
|
||||
candidateLocal = myConnection;
|
||||
radius = neighbour.radius;
|
||||
}
|
||||
}
|
||||
return {
|
||||
closest: candidateClosest,
|
||||
local: candidateLocal,
|
||||
radius: radius
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Decide the radius at which to start searching for the closest connection.
|
||||
* @return {number} The radius at which to start the search for the closest
|
||||
* connection.
|
||||
* @private
|
||||
*/
|
||||
Blockly.InsertionMarkerManager.prototype.getStartRadius_ = function() {
|
||||
// If there is already a connection highlighted,
|
||||
// increase the radius we check for making new connections.
|
||||
// Why? When a connection is highlighted, blocks move around when the insertion
|
||||
// marker is created, which could cause the connection became out of range.
|
||||
// By increasing radiusConnection when a connection already exists,
|
||||
// we never "lose" the connection from the offset.
|
||||
if (this.closestConnection_ && this.localConnection_) {
|
||||
return Blockly.CONNECTING_SNAP_RADIUS;
|
||||
}
|
||||
return Blockly.SNAP_RADIUS;
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether ending the drag would replace a block or insert a block.
|
||||
* @return {boolean} True if dropping the block immediately would replace
|
||||
* another block. False if dropping the block immediately would result in
|
||||
* the block being inserted in a block stack.
|
||||
* @private
|
||||
*/
|
||||
Blockly.InsertionMarkerManager.prototype.shouldReplace_ = function() {
|
||||
var closest = this.closestConnection_;
|
||||
var local = this.localConnection_;
|
||||
|
||||
// Dragging a block over an existing block in an input should replace the
|
||||
// existing block and bump it out.
|
||||
if (local.type == Blockly.OUTPUT_VALUE) {
|
||||
return true; // Replace.
|
||||
}
|
||||
|
||||
// Connecting to a statement input of c-block is an insertion, even if that
|
||||
// c-block is terminal (e.g. forever).
|
||||
if (local == local.sourceBlock_.getFirstStatementConnection()) {
|
||||
return false; // Insert.
|
||||
}
|
||||
|
||||
// Dragging a terminal block over another (connected) terminal block will
|
||||
// replace, not insert.
|
||||
var isTerminalBlock = !this.topBlock_.nextConnection;
|
||||
var isConnectedTerminal = isTerminalBlock &&
|
||||
local.type == Blockly.PREVIOUS_STATEMENT && closest.isConnected();
|
||||
if (isConnectedTerminal) {
|
||||
return true; // Replace.
|
||||
}
|
||||
|
||||
// Otherwise it's an insertion.
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether ending the drag would delete the block.
|
||||
* @param {!Object} candidate An object containing a local connection, a closest
|
||||
* connection, and a radius.
|
||||
* @param {?number} deleteArea One of {@link Blockly.DELETE_AREA_TRASH},
|
||||
* {@link Blockly.DELETE_AREA_TOOLBOX}, or {@link Blockly.DELETE_AREA_NONE}.
|
||||
* @return {boolean} True if dropping the block immediately would replace
|
||||
* delete the block. False otherwise.
|
||||
* @private
|
||||
*/
|
||||
Blockly.InsertionMarkerManager.prototype.shouldDelete_ = function(candidate,
|
||||
deleteArea) {
|
||||
// Prefer connecting over dropping into the trash can, but prefer dragging to
|
||||
// the toolbox over connecting to other blocks.
|
||||
var wouldConnect = candidate && !!candidate.closest &&
|
||||
deleteArea != Blockly.DELETE_AREA_TOOLBOX;
|
||||
var wouldDelete = !!deleteArea && !this.topBlock_.getParent() &&
|
||||
this.topBlock_.isDeletable();
|
||||
|
||||
return wouldDelete && !wouldConnect;
|
||||
};
|
||||
|
||||
/**** Begin preview visibility functions ****/
|
||||
|
||||
/**
|
||||
* Show an insertion marker or replacement highlighting during a drag, if
|
||||
* needed.
|
||||
* At the beginning of this function, this.localConnection_ and
|
||||
* this.closestConnection_ should both be null.
|
||||
* @param {!Object} candidate An object containing a local connection, a closest
|
||||
* connection, and a radius.
|
||||
* @private
|
||||
*/
|
||||
Blockly.InsertionMarkerManager.prototype.maybeShowPreview_ = function(candidate) {
|
||||
// Nope, don't add a marker.
|
||||
if (this.wouldDeleteBlock_) {
|
||||
return;
|
||||
}
|
||||
var closest = candidate.closest;
|
||||
var local = candidate.local;
|
||||
|
||||
// Nothing to connect to.
|
||||
if (!closest) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Something went wrong and we're trying to connect to an invalid connection.
|
||||
if (closest == this.closestConnection_ ||
|
||||
closest.sourceBlock_.isInsertionMarker()) {
|
||||
return;
|
||||
}
|
||||
// Add an insertion marker or replacement marker.
|
||||
this.closestConnection_ = closest;
|
||||
this.localConnection_ = local;
|
||||
this.showPreview_();
|
||||
};
|
||||
|
||||
/**
|
||||
* A preview should be shown. This function figures out if it should be a block
|
||||
* highlight or an insertion marker, and shows the appropriate one.
|
||||
* @private
|
||||
*/
|
||||
Blockly.InsertionMarkerManager.prototype.showPreview_ = function() {
|
||||
if (this.shouldReplace_()) {
|
||||
this.highlightBlock_();
|
||||
} else { // Should insert
|
||||
this.connectMarker_();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Show an insertion marker or replacement highlighting during a drag, if
|
||||
* needed.
|
||||
* At the end of this function, this.localConnection_ and
|
||||
* this.closestConnection_ should both be null.
|
||||
* @param {!Object} candidate An object containing a local connection, a closest
|
||||
* connection, and a radius.
|
||||
* @private
|
||||
*/
|
||||
Blockly.InsertionMarkerManager.prototype.maybeHidePreview_ = function(candidate) {
|
||||
// If there's no new preview, remove the old one but don't bother deleting it.
|
||||
// We might need it later, and this saves disposing of it and recreating it.
|
||||
if (!candidate.closest) {
|
||||
this.hidePreview_();
|
||||
}
|
||||
// If there's a new preview and there was an preview before, and either
|
||||
// connection has changed, remove the old preview.
|
||||
var hadPreview = this.closestConnection_ && this.localConnection_;
|
||||
var closestChanged = this.closestConnection_ != candidate.closest;
|
||||
var localChanged = this.localConnection_ != candidate.local;
|
||||
|
||||
// Also hide if we had a preview before but now we're going to delete instead.
|
||||
if (hadPreview && (closestChanged || localChanged || this.wouldDeleteBlock_)) {
|
||||
this.hidePreview_();
|
||||
}
|
||||
|
||||
// Either way, clear out old state.
|
||||
this.markerConnection_ = null;
|
||||
this.closestConnection_ = null;
|
||||
this.localConnection_ = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* A preview should be hidden. This function figures out if it is a block
|
||||
* highlight or an insertion marker, and hides the appropriate one.
|
||||
* @private
|
||||
*/
|
||||
Blockly.InsertionMarkerManager.prototype.hidePreview_ = function() {
|
||||
if (this.highlightingBlock_) {
|
||||
this.unhighlightBlock_();
|
||||
} else if (this.markerConnection_) {
|
||||
this.disconnectMarker_();
|
||||
}
|
||||
};
|
||||
|
||||
/**** End preview visibility functions ****/
|
||||
|
||||
/**** Begin block highlighting functions ****/
|
||||
|
||||
/**
|
||||
* Add highlighting showing which block will be replaced.
|
||||
* Scratch-specific code, where "highlighting" applies to a block rather than
|
||||
* a connection.
|
||||
*/
|
||||
Blockly.InsertionMarkerManager.prototype.highlightBlock_ = function() {
|
||||
var closest = this.closestConnection_;
|
||||
var local = this.localConnection_;
|
||||
if (closest.targetBlock()) {
|
||||
this.highlightedBlock_ = closest.targetBlock();
|
||||
closest.targetBlock().highlightForReplacement(true);
|
||||
} else if(local.type == Blockly.OUTPUT_VALUE) {
|
||||
this.highlightedBlock_ = closest.sourceBlock_;
|
||||
closest.sourceBlock_.highlightShapeForInput(closest, true);
|
||||
}
|
||||
this.highlightingBlock_ = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get rid of the highlighting marking the block that will be replaced.
|
||||
* Scratch-specific code, where "highlighting" applies to a block rather than
|
||||
* a connection.
|
||||
*/
|
||||
Blockly.InsertionMarkerManager.prototype.unhighlightBlock_ = function() {
|
||||
var closest = this.closestConnection_;
|
||||
// If there's no block in place, but we're still connecting to a value input,
|
||||
// then we must have been highlighting an input shape.
|
||||
if (closest.type == Blockly.INPUT_VALUE && !closest.isConnected()) {
|
||||
this.highlightedBlock_.highlightShapeForInput(closest, false);
|
||||
} else {
|
||||
this.highlightedBlock_.highlightForReplacement(false);
|
||||
}
|
||||
this.highlightedBlock_ = null;
|
||||
this.highlightingBlock_ = false;
|
||||
};
|
||||
|
||||
/**** End block highlighting functions ****/
|
||||
|
||||
/**** Begin insertion marker display functions ****/
|
||||
|
||||
/**
|
||||
* Disconnect the insertion marker block in a manner that returns the stack to
|
||||
* original state.
|
||||
* @private
|
||||
*/
|
||||
Blockly.InsertionMarkerManager.prototype.disconnectMarker_ = function() {
|
||||
if (!this.markerConnection_) {
|
||||
console.log('No insertion marker connection to disconnect');
|
||||
return;
|
||||
}
|
||||
|
||||
var imConn = this.markerConnection_;
|
||||
var imBlock = imConn.sourceBlock_;
|
||||
var markerNext = imBlock.nextConnection;
|
||||
var markerPrev = imBlock.previousConnection;
|
||||
|
||||
|
||||
// The insertion marker is the first block in a stack, either because it
|
||||
// doesn't have a previous connection or because the previous connection is
|
||||
// not connected. Unplug won't do anything in that case. Instead, unplug the
|
||||
// following block.
|
||||
if (imConn == markerNext && !(markerPrev && markerPrev.targetConnection)) {
|
||||
imConn.targetBlock().unplug(false);
|
||||
}
|
||||
// Inside of a C-block, first statement connection.
|
||||
else if (imConn.type == Blockly.NEXT_STATEMENT && imConn != markerNext) {
|
||||
var innerConnection = imConn.targetConnection;
|
||||
innerConnection.sourceBlock_.unplug(false);
|
||||
|
||||
var previousBlockNextConnection =
|
||||
markerPrev ? markerPrev.targetConnection : null;
|
||||
|
||||
imBlock.unplug(true);
|
||||
if (previousBlockNextConnection) {
|
||||
previousBlockNextConnection.connect(innerConnection);
|
||||
}
|
||||
} else {
|
||||
imBlock.unplug(true /* healStack */);
|
||||
}
|
||||
|
||||
if (imConn.targetConnection) {
|
||||
throw 'markerConnection_ still connected at the end of disconnectInsertionMarker';
|
||||
}
|
||||
|
||||
this.markerConnection_ = null;
|
||||
imBlock.getSvgRoot().setAttribute('visibility', 'hidden');
|
||||
};
|
||||
|
||||
/**
|
||||
* Add an insertion marker connected to the appropriate blocks.
|
||||
* @private
|
||||
*/
|
||||
Blockly.InsertionMarkerManager.prototype.connectMarker_ = function() {
|
||||
var local = this.localConnection_;
|
||||
var closest = this.closestConnection_;
|
||||
|
||||
var isLastInStack = this.lastOnStack_ && local == this.lastOnStack_;
|
||||
var imBlock = isLastInStack ? this.lastMarker_ : this.firstMarker_;
|
||||
var imConn = imBlock.getMatchingConnection(local.sourceBlock_, local);
|
||||
|
||||
goog.asserts.assert(imConn != this.markerConnection_,
|
||||
'Made it to connectMarker_ even though the marker isn\'t changing');
|
||||
|
||||
// Render disconnected from everything else so that we have a valid
|
||||
// connection location.
|
||||
imBlock.render();
|
||||
imBlock.rendered = true;
|
||||
imBlock.getSvgRoot().setAttribute('visibility', 'visible');
|
||||
|
||||
// TODO: positionNewBlock should be on Blockly.BlockSvg, not prototype,
|
||||
// because it doesn't rely on anything in the block it's called on.
|
||||
imBlock.positionNewBlock(imBlock, imConn, closest);
|
||||
|
||||
// Connect() also renders the insertion marker.
|
||||
imConn.connect(closest);
|
||||
this.markerConnection_ = imConn;
|
||||
};
|
||||
|
||||
/**** End insertion marker display functions ****/
|
|
@ -294,7 +294,7 @@ Blockly.Mutator.prototype.setVisible = function(visible) {
|
|||
* @private
|
||||
*/
|
||||
Blockly.Mutator.prototype.workspaceChanged_ = function() {
|
||||
if (Blockly.dragMode_ == Blockly.DRAG_NONE) {
|
||||
if (!this.workspace_.isDragging()) {
|
||||
var blocks = this.workspace_.getTopBlocks(false);
|
||||
var MARGIN = 20;
|
||||
for (var b = 0, block; block = blocks[b]; b++) {
|
||||
|
@ -338,7 +338,11 @@ Blockly.Mutator.prototype.workspaceChanged_ = function() {
|
|||
if (block.rendered) {
|
||||
block.render();
|
||||
}
|
||||
this.resizeBubble_();
|
||||
// Don't update the bubble until the drag has ended, to avoid moving blocks
|
||||
// under the cursor.
|
||||
if (!this.workspace_.isDragging()) {
|
||||
this.resizeBubble_();
|
||||
}
|
||||
Blockly.Events.setGroup(false);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -69,7 +69,7 @@ Blockly.RenderedConnection.prototype.distanceFrom = function(otherConnection) {
|
|||
* @private
|
||||
*/
|
||||
Blockly.RenderedConnection.prototype.bumpAwayFrom_ = function(staticConnection) {
|
||||
if (Blockly.dragMode_ != Blockly.DRAG_NONE) {
|
||||
if (this.sourceBlock_.workspace.isDragging()) {
|
||||
// Don't move blocks around while the user is doing the same.
|
||||
return;
|
||||
}
|
||||
|
@ -168,6 +168,7 @@ Blockly.RenderedConnection.prototype.tighten_ = function() {
|
|||
if (!svgRoot) {
|
||||
throw 'block is not rendered.';
|
||||
}
|
||||
// Workspace coordinates.
|
||||
var xy = Blockly.utils.getRelativeXY(svgRoot);
|
||||
block.getSvgRoot().setAttribute('transform',
|
||||
'translate(' + (xy.x - dx) + ',' + (xy.y - dy) + ')');
|
||||
|
@ -177,7 +178,7 @@ Blockly.RenderedConnection.prototype.tighten_ = function() {
|
|||
|
||||
/**
|
||||
* Find the closest compatible connection to this connection.
|
||||
* All parameters are in workspace units
|
||||
* All parameters are in workspace units.
|
||||
* @param {number} maxLimit The maximum radius to another connection.
|
||||
* @param {!goog.math.Coordinate} dxy Offset between this connection's location
|
||||
* in the database and the current location (as a result of dragging).
|
||||
|
|
|
@ -248,6 +248,7 @@ Blockly.Scrollbar.prototype.originHasChanged_ = true;
|
|||
|
||||
/**
|
||||
* The size of the area within which the scrollbar handle can move.
|
||||
* Coordinate system: pixel coordinates.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
|
@ -255,6 +256,7 @@ Blockly.Scrollbar.prototype.scrollViewSize_ = 0;
|
|||
|
||||
/**
|
||||
* The length of the scrollbar handle.
|
||||
* Coordinate system: pixel coordinates.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
|
@ -262,6 +264,7 @@ Blockly.Scrollbar.prototype.handleLength_ = 0;
|
|||
|
||||
/**
|
||||
* The offset of the start of the handle from the start of the scrollbar range.
|
||||
* Coordinate system: pixel coordinates.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
|
@ -284,7 +287,6 @@ Blockly.Scrollbar.prototype.containerVisible_ = true;
|
|||
/**
|
||||
* Width of vertical scrollbar or height of horizontal scrollbar.
|
||||
* Increase the size of scrollbars on touch devices.
|
||||
* Don't define if there is no document object (e.g. node.js).
|
||||
*/
|
||||
Blockly.Scrollbar.scrollbarThickness = 11;
|
||||
if (goog.events.BrowserFeature.TOUCH_ENABLED) {
|
||||
|
|
|
@ -257,6 +257,24 @@ Blockly.Toolbox.prototype.removeDeleteStyle = function() {
|
|||
'blocklyToolboxDelete');
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds styles on the toolbox indicating blocks will be deleted.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Toolbox.prototype.addDeleteStyle = function() {
|
||||
Blockly.utils.addClass(/** @type {!Element} */ (this.HtmlDiv),
|
||||
'blocklyToolboxDelete');
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove styles from the toolbox that indicate blocks will be deleted.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Toolbox.prototype.removeDeleteStyle = function() {
|
||||
Blockly.utils.removeClass(/** @type {!Element} */ (this.HtmlDiv),
|
||||
'blocklyToolboxDelete');
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the deletion rectangle for this toolbox.
|
||||
* @return {goog.math.Rect} Rectangle in which to delete.
|
||||
|
|
|
@ -44,6 +44,13 @@ goog.require('goog.dom.TagName');
|
|||
*/
|
||||
Blockly.Tooltip.visible = false;
|
||||
|
||||
/**
|
||||
* Is someone else blocking the tooltip from being shown?
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
Blockly.Tooltip.blocked_ = false;
|
||||
|
||||
/**
|
||||
* Maximum width (in characters) of a tooltip.
|
||||
*/
|
||||
|
@ -153,6 +160,10 @@ Blockly.Tooltip.bindMouseEvents = function(element) {
|
|||
* @private
|
||||
*/
|
||||
Blockly.Tooltip.onMouseOver_ = function(e) {
|
||||
if (Blockly.Tooltip.blocked_) {
|
||||
// Someone doesn't want us to show tooltips.
|
||||
return;
|
||||
}
|
||||
// If the tooltip is an object, treat it as a pointer to the next object in
|
||||
// the chain to look at. Terminate when a string or function is found.
|
||||
var element = e.target;
|
||||
|
@ -174,6 +185,10 @@ Blockly.Tooltip.onMouseOver_ = function(e) {
|
|||
* @private
|
||||
*/
|
||||
Blockly.Tooltip.onMouseOut_ = function(/*e*/) {
|
||||
if (Blockly.Tooltip.blocked_) {
|
||||
// Someone doesn't want us to show tooltips.
|
||||
return;
|
||||
}
|
||||
// Moving from one element to another (overlapping or with no gap) generates
|
||||
// a mouseOut followed instantly by a mouseOver. Fork off the mouseOut
|
||||
// event and kill it if a mouseOver is received immediately.
|
||||
|
@ -196,12 +211,13 @@ Blockly.Tooltip.onMouseMove_ = function(e) {
|
|||
if (!Blockly.Tooltip.element_ || !Blockly.Tooltip.element_.tooltip) {
|
||||
// No tooltip here to show.
|
||||
return;
|
||||
} else if (Blockly.dragMode_ != Blockly.DRAG_NONE) {
|
||||
// Don't display a tooltip during a drag.
|
||||
return;
|
||||
} else if (Blockly.WidgetDiv.isVisible()) {
|
||||
// Don't display a tooltip if a widget is open (tooltip would be under it).
|
||||
return;
|
||||
} else if (Blockly.Tooltip.blocked_) {
|
||||
// Someone doesn't want us to show tooltips. We are probably handling a
|
||||
// user gesture, such as a click or drag.
|
||||
return;
|
||||
}
|
||||
if (Blockly.Tooltip.visible) {
|
||||
// Compute the distance between the mouse position when the tooltip was
|
||||
|
@ -235,11 +251,34 @@ Blockly.Tooltip.hide = function() {
|
|||
clearTimeout(Blockly.Tooltip.showPid_);
|
||||
};
|
||||
|
||||
/**
|
||||
* Hide any in-progress tooltips and block showing new tooltips until the next
|
||||
* call to unblock().
|
||||
* @package
|
||||
*/
|
||||
Blockly.Tooltip.block = function() {
|
||||
Blockly.Tooltip.hide();
|
||||
Blockly.Tooltip.blocked_ = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Unblock tooltips: allow them to be scheduled and shown according to their own
|
||||
* logic.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Tooltip.unblock = function() {
|
||||
Blockly.Tooltip.blocked_ = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create the tooltip and show it.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Tooltip.show_ = function() {
|
||||
if (Blockly.Tooltip.blocked_) {
|
||||
// Someone doesn't want us to show tooltips.
|
||||
return;
|
||||
}
|
||||
Blockly.Tooltip.poisonedElement_ = Blockly.Tooltip.element_;
|
||||
if (!Blockly.Tooltip.DIV) {
|
||||
return;
|
||||
|
|
|
@ -41,13 +41,6 @@ goog.require('goog.string');
|
|||
*/
|
||||
Blockly.Touch.touchIdentifier_ = null;
|
||||
|
||||
/**
|
||||
* Wrapper function called when a touch mouseUp occurs during a drag operation.
|
||||
* @type {Array.<!Array>}
|
||||
* @private
|
||||
*/
|
||||
Blockly.Touch.onTouchUpWrapper_ = null;
|
||||
|
||||
/**
|
||||
* The TOUCH_MAP lookup dictionary specifies additional touch events to fire,
|
||||
* in conjunction with mouse events.
|
||||
|
@ -75,11 +68,10 @@ Blockly.longPid_ = 0;
|
|||
* which after about a second opens the context menu. The tasks is killed
|
||||
* if the touch event terminates early.
|
||||
* @param {!Event} e Touch start event.
|
||||
* @param {!Blockly.Block|!Blockly.WorkspaceSvg} uiObject The block or workspace
|
||||
* under the touchstart event.
|
||||
* @param {Blockly.Gesture} gesture The gesture that triggered this longStart.
|
||||
* @private
|
||||
*/
|
||||
Blockly.longStart_ = function(e, uiObject) {
|
||||
Blockly.longStart_ = function(e, gesture) {
|
||||
Blockly.longStop_();
|
||||
// Punt on multitouch events.
|
||||
if (e.changedTouches.length != 1) {
|
||||
|
@ -90,7 +82,11 @@ Blockly.longStart_ = function(e, uiObject) {
|
|||
// e was a touch event. It needs to pretend to be a mouse event.
|
||||
e.clientX = e.changedTouches[0].clientX;
|
||||
e.clientY = e.changedTouches[0].clientY;
|
||||
uiObject.onMouseDown_(e);
|
||||
|
||||
// Let the gesture route the right-click correctly.
|
||||
if (gesture) {
|
||||
gesture.handleRightClick(e);
|
||||
}
|
||||
}, Blockly.LONGPRESS);
|
||||
};
|
||||
|
||||
|
@ -106,56 +102,6 @@ Blockly.longStop_ = function() {
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handle a mouse-up anywhere on the page.
|
||||
* @param {!Event} e Mouse up event.
|
||||
* @private
|
||||
*/
|
||||
Blockly.onMouseUp_ = function(/* e */) {
|
||||
var workspace = Blockly.getMainWorkspace();
|
||||
if (workspace.dragMode_ == Blockly.DRAG_NONE) {
|
||||
return;
|
||||
}
|
||||
Blockly.Touch.clearTouchIdentifier();
|
||||
|
||||
// TODO(#781): Check whether this needs to be called for all drag modes.
|
||||
workspace.resetDragSurface();
|
||||
workspace.dragMode_ = Blockly.DRAG_NONE;
|
||||
// Unbind the touch event if it exists.
|
||||
if (Blockly.Touch.onTouchUpWrapper_) {
|
||||
Blockly.unbindEvent_(Blockly.Touch.onTouchUpWrapper_);
|
||||
Blockly.Touch.onTouchUpWrapper_ = null;
|
||||
}
|
||||
if (Blockly.onMouseMoveWrapper_) {
|
||||
Blockly.unbindEvent_(Blockly.onMouseMoveWrapper_);
|
||||
Blockly.onMouseMoveWrapper_ = null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle a mouse-move on SVG drawing surface.
|
||||
* @param {!Event} e Mouse move event.
|
||||
* @private
|
||||
*/
|
||||
Blockly.onMouseMove_ = function(e) {
|
||||
var workspace = Blockly.getMainWorkspace();
|
||||
if (workspace.dragMode_ != Blockly.DRAG_NONE) {
|
||||
var dx = e.clientX - workspace.startDragMouseX;
|
||||
var dy = e.clientY - workspace.startDragMouseY;
|
||||
var x = workspace.startScrollX + dx;
|
||||
var y = workspace.startScrollY + dy;
|
||||
workspace.scroll(x, y);
|
||||
// Cancel the long-press if the drag has moved too far.
|
||||
if (Math.sqrt(dx * dx + dy * dy) > Blockly.DRAG_RADIUS) {
|
||||
Blockly.longStop_();
|
||||
workspace.dragMode_ = Blockly.DRAG_FREE;
|
||||
}
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear the touch identifier that tracks which touch stream to pay attention
|
||||
* to. This ends the current drag/gesture and allows other pointers to be
|
||||
|
|
|
@ -281,7 +281,7 @@ Blockly.utils.getRelativeXY.XY_2D_REGEX_ =
|
|||
* @param {Element} parent Optional parent on which to append the element.
|
||||
* @return {!SVGElement} Newly created SVG element.
|
||||
*/
|
||||
Blockly.utils.createSvgElement = function(name, attrs, parent) {
|
||||
Blockly.utils.createSvgElement = function(name, attrs, parent /*, opt_workspace */) {
|
||||
var e = /** @type {!SVGElement} */ (
|
||||
document.createElementNS(Blockly.SVG_NS, name));
|
||||
for (var key in attrs) {
|
||||
|
|
226
core/variable_map.js
Normal file
226
core/variable_map.js
Normal file
|
@ -0,0 +1,226 @@
|
|||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2017 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 Object representing a map of variables and their types.
|
||||
* @author marisaleung@google.com (Marisa Leung)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.VariableMap');
|
||||
|
||||
goog.require('Blockly.VariableModel');
|
||||
|
||||
|
||||
/**
|
||||
* Class for a variable map. This contains a dictionary data structure with
|
||||
* variable types as keys and lists of variables as values. The list of
|
||||
* variables are the type indicated by the key.
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.VariableMap = function() {
|
||||
/**
|
||||
* @type {!Object<string, !Array.<Blockly.VariableModel>>}
|
||||
* A map from variable type to list of variable names. The lists contain all
|
||||
* of the named variables in the workspace, including variables
|
||||
* that are not currently in use.
|
||||
* @private
|
||||
*/
|
||||
this.variableMap_ = {};
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear the variable map.
|
||||
*/
|
||||
Blockly.VariableMap.prototype.clear = function() {
|
||||
this.variableMap_ = new Object(null);
|
||||
};
|
||||
|
||||
/**
|
||||
* Rename the given variable by updating its name in the variable map.
|
||||
* TODO: #468
|
||||
* @param {?Blockly.VariableModel} variable Variable to rename.
|
||||
* @param {string} newName New variable name.
|
||||
*/
|
||||
Blockly.VariableMap.prototype.renameVariable = function(variable, newName) {
|
||||
var newVariable = this.getVariable(newName);
|
||||
var variableIndex = -1;
|
||||
var newVariableIndex = -1;
|
||||
var type = '';
|
||||
if (variable || newVariable) {
|
||||
type = (variable || newVariable).type;
|
||||
}
|
||||
|
||||
var variableList = this.getVariablesOfType(type);
|
||||
if (variable) {
|
||||
variableIndex = variableList.indexOf(variable);
|
||||
}
|
||||
if (newVariable){ // see if I can get rid of newVariable dependency
|
||||
newVariableIndex = variableList.indexOf(newVariable);
|
||||
}
|
||||
|
||||
if (variableIndex == -1 && newVariableIndex == -1) {
|
||||
this.createVariable(newName, '');
|
||||
console.log('Tried to rename an non-existent variable.');
|
||||
} else if (variableIndex == newVariableIndex ||
|
||||
variableIndex != -1 && newVariableIndex == -1) {
|
||||
// Only changing case, or renaming to a completely novel name.
|
||||
this.variableMap_[type][variableIndex].name = newName;
|
||||
} else if (variableIndex != -1 && newVariableIndex != -1) {
|
||||
// Renaming one existing variable to another existing variable.
|
||||
// The case might have changed, so we update the destination ID.
|
||||
this.variableMap_[type][newVariableIndex].name = newName;
|
||||
this.variableMap_[type].splice(variableIndex, 1);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a variable with a given name, optional type, and optional id.
|
||||
* @param {!string} name The name of the variable. This must be unique across
|
||||
* variables and procedures.
|
||||
* @param {?string} opt_type The type of the variable like 'int' or 'string'.
|
||||
* Does not need to be unique. Field_variable can filter variables based on
|
||||
* their type. This will default to '' which is a specific type.
|
||||
* @param {?string} opt_id The unique id of the variable. This will default to
|
||||
* a UUID.
|
||||
* @return {?Blockly.VariableModel} The newly created variable.
|
||||
*/
|
||||
Blockly.VariableMap.prototype.createVariable = function(name, opt_type, opt_id) {
|
||||
var variable = this.getVariable(name);
|
||||
if (variable) {
|
||||
if (opt_type && variable.type != opt_type) {
|
||||
throw Error('Variable "' + name + '" is already in use and its type is "'
|
||||
+ variable.type + '" which conflicts with the passed in ' +
|
||||
'type, "' + opt_type + '".');
|
||||
}
|
||||
if (opt_id && variable.getId() != opt_id) {
|
||||
throw Error('Variable "' + name + '" is already in use and its id is "'
|
||||
+ variable.getId() + '" which conflicts with the passed in ' +
|
||||
'id, "' + opt_id + '".');
|
||||
}
|
||||
// The variable already exists and has the same id and type.
|
||||
return variable;
|
||||
}
|
||||
if (opt_id && this.getVariableById(opt_id)) {
|
||||
throw Error('Variable id, "' + opt_id + '", is already in use.');
|
||||
}
|
||||
opt_id = opt_id || Blockly.utils.genUid();
|
||||
opt_type = opt_type || '';
|
||||
|
||||
variable = new Blockly.VariableModel(name, opt_type, opt_id);
|
||||
// If opt_type is not a key, create a new list.
|
||||
if (!this.variableMap_[opt_type]) {
|
||||
this.variableMap_[opt_type] = [variable];
|
||||
} else {
|
||||
// Else append the variable to the preexisting list.
|
||||
this.variableMap_[opt_type].push(variable);
|
||||
}
|
||||
return variable;
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete a variable.
|
||||
* @param {Blockly.VariableModel} variable Variable to delete.
|
||||
*/
|
||||
Blockly.VariableMap.prototype.deleteVariable = function(variable) {
|
||||
var variableList = this.variableMap_[variable.type];
|
||||
for (var i = 0, tempVar; tempVar = variableList[i]; i++) {
|
||||
if (tempVar.getId() == variable.getId()) {
|
||||
variableList.splice(i, 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Find the variable by the given name and return it. Return null if it is not
|
||||
* found.
|
||||
* @param {!string} name The name to check for.
|
||||
* @return {?Blockly.VariableModel} The variable with the given name, or null if
|
||||
* it was not found.
|
||||
*/
|
||||
Blockly.VariableMap.prototype.getVariable = function(name) {
|
||||
var keys = Object.keys(this.variableMap_);
|
||||
for (var i = 0; i < keys.length; i++ ) {
|
||||
var key = keys[i];
|
||||
for (var j = 0, variable; variable = this.variableMap_[key][j]; j++) {
|
||||
if (Blockly.Names.equals(variable.name, name)) {
|
||||
return variable;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Find the variable by the given id and return it. Return null if it is not
|
||||
* found.
|
||||
* @param {!string} id The id to check for.
|
||||
* @return {?Blockly.VariableModel} The variable with the given id.
|
||||
*/
|
||||
Blockly.VariableMap.prototype.getVariableById = function(id) {
|
||||
var keys = Object.keys(this.variableMap_);
|
||||
for (var i = 0; i < keys.length; i++ ) {
|
||||
var key = keys[i];
|
||||
for (var j = 0, variable; variable = this.variableMap_[key][j]; j++) {
|
||||
if (variable.getId() == id) {
|
||||
return variable;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a list containing all of the variables of a specified type. If type is
|
||||
* null, return list of variables with empty string type.
|
||||
* @param {?string} type Type of the variables to find.
|
||||
* @return {Array.<Blockly.VariableModel>} The sought after variables of the
|
||||
* passed in type. An empty array if none are found.
|
||||
*/
|
||||
Blockly.VariableMap.prototype.getVariablesOfType = function(type) {
|
||||
type = type || '';
|
||||
var variable_list = this.variableMap_[type];
|
||||
if (variable_list) {
|
||||
return variable_list;
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Return all variable types.
|
||||
* @return {!Array.<string>} List of variable types.
|
||||
*/
|
||||
Blockly.VariableMap.prototype.getVariableTypes = function() {
|
||||
return Object.keys(this.variableMap_);
|
||||
};
|
||||
|
||||
/**
|
||||
* Return all variables of all types.
|
||||
* @return {!Array.<Blockly.VariableModel>} List of variable models.
|
||||
*/
|
||||
Blockly.VariableMap.prototype.getAllVariables = function() {
|
||||
var all_variables = [];
|
||||
var keys = Object.keys(this.variableMap_);
|
||||
for (var i = 0; i < keys.length; i++ ) {
|
||||
all_variables = all_variables.concat(this.variableMap_[keys[i]]);
|
||||
}
|
||||
return all_variables;
|
||||
};
|
|
@ -90,7 +90,7 @@ Blockly.Variables.allUsedVariables = function(root) {
|
|||
* 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.
|
||||
* @return {!Array.<Blockly.VariableModel>} Array of variable models.
|
||||
*/
|
||||
Blockly.Variables.allVariables = function(root) {
|
||||
if (root instanceof Blockly.Block) {
|
||||
|
@ -98,8 +98,9 @@ Blockly.Variables.allVariables = function(root) {
|
|||
console.warn('Deprecated call to Blockly.Variables.allVariables ' +
|
||||
'with a block instead of a workspace. You may want ' +
|
||||
'Blockly.Variables.allUsedVariables');
|
||||
return {};
|
||||
}
|
||||
return root.variableList;
|
||||
return root.getAllVariables();
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -108,8 +109,12 @@ Blockly.Variables.allVariables = function(root) {
|
|||
* @return {!Array.<!Element>} Array of XML block elements.
|
||||
*/
|
||||
Blockly.Variables.flyoutCategory = function(workspace) {
|
||||
var variableList = workspace.variableList;
|
||||
variableList.sort(goog.string.caseInsensitiveCompare);
|
||||
var variableNameList = [];
|
||||
var variableModelList = workspace.getVariablesOfType('');
|
||||
for (var i = 0; i < variableModelList.length; i++) {
|
||||
variableNameList.push(variableModelList[i].name);
|
||||
}
|
||||
variableNameList.sort(goog.string.caseInsensitiveCompare);
|
||||
|
||||
var xmlList = [];
|
||||
var button = goog.dom.createDom('button');
|
||||
|
@ -122,8 +127,7 @@ Blockly.Variables.flyoutCategory = function(workspace) {
|
|||
|
||||
xmlList.push(button);
|
||||
|
||||
|
||||
for (var i = 0; i < variableList.length; i++) {
|
||||
for (var i = 0; i < variableNameList.length; i++) {
|
||||
if (Blockly.Blocks['data_variable']) {
|
||||
// <block type="data_variable">
|
||||
// <field name="VARIABLE">variablename</field>
|
||||
|
@ -132,7 +136,7 @@ Blockly.Variables.flyoutCategory = function(workspace) {
|
|||
block.setAttribute('type', 'data_variable');
|
||||
block.setAttribute('gap', 8);
|
||||
|
||||
var field = goog.dom.createDom('field', null, variableList[i]);
|
||||
var field = goog.dom.createDom('field', null, variableNameList[i]);
|
||||
field.setAttribute('name', 'VARIABLE');
|
||||
block.appendChild(field);
|
||||
|
||||
|
@ -157,7 +161,7 @@ Blockly.Variables.flyoutCategory = function(workspace) {
|
|||
var block = goog.dom.createDom('block');
|
||||
block.setAttribute('type', 'data_setvariableto');
|
||||
block.setAttribute('gap', 8);
|
||||
block.appendChild(Blockly.Variables.createVariableDom_(variableList[0]));
|
||||
block.appendChild(Blockly.Variables.createVariableDom_(variableNameList[0]));
|
||||
block.appendChild(Blockly.Variables.createTextDom_());
|
||||
xmlList.push(block);
|
||||
}
|
||||
|
@ -175,7 +179,7 @@ Blockly.Variables.flyoutCategory = function(workspace) {
|
|||
var block = goog.dom.createDom('block');
|
||||
block.setAttribute('type', 'data_changevariableby');
|
||||
block.setAttribute('gap', 8);
|
||||
block.appendChild(Blockly.Variables.createVariableDom_(variableList[0]));
|
||||
block.appendChild(Blockly.Variables.createVariableDom_(variableNameList[0]));
|
||||
block.appendChild(Blockly.Variables.createMathNumberDom_());
|
||||
xmlList.push(block);
|
||||
}
|
||||
|
@ -188,7 +192,7 @@ Blockly.Variables.flyoutCategory = function(workspace) {
|
|||
var block = goog.dom.createDom('block');
|
||||
block.setAttribute('type', 'data_showvariable');
|
||||
block.setAttribute('gap', 8);
|
||||
block.appendChild(Blockly.Variables.createVariableDom_(variableList[0]));
|
||||
block.appendChild(Blockly.Variables.createVariableDom_(variableNameList[0]));
|
||||
xmlList.push(block);
|
||||
}
|
||||
if (Blockly.Blocks['data_hidevariable']) {
|
||||
|
@ -199,7 +203,7 @@ Blockly.Variables.flyoutCategory = function(workspace) {
|
|||
// </block>
|
||||
var block = goog.dom.createDom('block');
|
||||
block.setAttribute('type', 'data_hidevariable');
|
||||
block.appendChild(Blockly.Variables.createVariableDom_(variableList[0]));
|
||||
block.appendChild(Blockly.Variables.createVariableDom_(variableNameList[0]));
|
||||
xmlList.push(block);
|
||||
}
|
||||
}
|
||||
|
@ -307,7 +311,7 @@ Blockly.Variables.noVariableText = function() {
|
|||
* @return {string} New variable name.
|
||||
*/
|
||||
Blockly.Variables.generateUniqueName = function(workspace) {
|
||||
var variableList = workspace.variableList;
|
||||
var variableList = workspace.getAllVariables();
|
||||
var newName = '';
|
||||
if (variableList.length) {
|
||||
var nameSuffix = 1;
|
||||
|
@ -317,7 +321,7 @@ Blockly.Variables.generateUniqueName = function(workspace) {
|
|||
while (!newName) {
|
||||
var inUse = false;
|
||||
for (var i = 0; i < variableList.length; i++) {
|
||||
if (variableList[i].toLowerCase() == potName) {
|
||||
if (variableList[i].name.toLowerCase() == potName) {
|
||||
// This potential name is already used.
|
||||
inUse = true;
|
||||
break;
|
||||
|
@ -360,13 +364,21 @@ Blockly.Variables.createVariable = function(workspace, opt_callback) {
|
|||
Blockly.Variables.promptName(Blockly.Msg.NEW_VARIABLE_TITLE, defaultName,
|
||||
function(text) {
|
||||
if (text) {
|
||||
if (workspace.variableIndexOf(text) != -1) {
|
||||
if (workspace.getVariable(text)) {
|
||||
Blockly.alert(Blockly.Msg.VARIABLE_ALREADY_EXISTS.replace('%1',
|
||||
text.toLowerCase()),
|
||||
function() {
|
||||
promptAndCheckWithAlert(text); // Recurse
|
||||
});
|
||||
} else {
|
||||
}
|
||||
else if (!Blockly.Procedures.isLegalName_(text, workspace)) {
|
||||
Blockly.alert(Blockly.Msg.PROCEDURE_ALREADY_EXISTS.replace('%1',
|
||||
text.toLowerCase()),
|
||||
function() {
|
||||
promptAndCheckWithAlert(text); // Recurse
|
||||
});
|
||||
}
|
||||
else {
|
||||
workspace.createVariable(text);
|
||||
if (opt_callback) {
|
||||
opt_callback(text);
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
|
||||
goog.provide('Blockly.Workspace');
|
||||
|
||||
goog.require('Blockly.VariableMap');
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.math');
|
||||
|
||||
|
@ -80,12 +81,15 @@ 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
|
||||
|
||||
/**
|
||||
* @type {!Blockly.VariableMap}
|
||||
* A map from variable type to list of variable names. The lists contain all
|
||||
* of the named variables in the workspace, including variables
|
||||
* that are not currently in use.
|
||||
* @private
|
||||
*/
|
||||
this.variableList = [];
|
||||
this.variableMap_ = new Blockly.VariableMap();
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -121,19 +125,20 @@ Blockly.Workspace.SCAN_ANGLE = 3;
|
|||
|
||||
/**
|
||||
* Add a block to the list of top blocks.
|
||||
* @param {!Blockly.Block} block Block to remove.
|
||||
* @param {!Blockly.Block} block Block to add.
|
||||
*/
|
||||
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]);
|
||||
}
|
||||
if (!this.isFlyout) {
|
||||
return;
|
||||
}
|
||||
// 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 variableNames = Blockly.Variables.allUsedVariables(block);
|
||||
for (var i = 0, name; name = variableNames[i]; i++) {
|
||||
if (!this.getVariable(name)) {
|
||||
this.createVariable(name);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -197,7 +202,7 @@ Blockly.Workspace.prototype.clear = function() {
|
|||
if (!existingGroup) {
|
||||
Blockly.Events.setGroup(false);
|
||||
}
|
||||
this.variableList.length = 0;
|
||||
this.variableMap_.clear();
|
||||
// Any block with a drop-down or WidgetDiv was disposed.
|
||||
if (Blockly.DropDownDiv) {
|
||||
Blockly.DropDownDiv.hideWithoutAnimation();
|
||||
|
@ -208,83 +213,119 @@ Blockly.Workspace.prototype.clear = function() {
|
|||
};
|
||||
|
||||
/**
|
||||
* Walk the workspace and update the list of variables to include all variables
|
||||
* in use on the workspace. Use when loading new workspaces from disk.
|
||||
* @param {boolean} clearList True if the old variable list should be cleared.
|
||||
* Walk the workspace and update the map of variables to only contain ones in
|
||||
* use on the workspace. Use when loading new workspaces from disk.
|
||||
* @param {boolean} clear True if the old variable map should be cleared.
|
||||
*/
|
||||
Blockly.Workspace.prototype.updateVariableList = function(clearList) {
|
||||
Blockly.Workspace.prototype.updateVariableStore = function(clear) {
|
||||
// TODO: Sort
|
||||
if (!this.isFlyout) {
|
||||
// Update the list in place so that the flyout's references stay correct.
|
||||
if (clearList) {
|
||||
this.variableList.length = 0;
|
||||
if (this.isFlyout) {
|
||||
return;
|
||||
}
|
||||
var variableNames = Blockly.Variables.allUsedVariables(this);
|
||||
var varList = [];
|
||||
for (var i = 0, name; name = variableNames[i]; i++) {
|
||||
// Get variable model with the used variable name.
|
||||
var tempVar = this.getVariable(name);
|
||||
if (tempVar) {
|
||||
varList.push({'name': tempVar.name, 'type': tempVar.type,
|
||||
'id': tempVar.getId()});
|
||||
}
|
||||
var allVariables = Blockly.Variables.allUsedVariables(this);
|
||||
for (var i = 0; i < allVariables.length; i++) {
|
||||
this.createVariable(allVariables[i]);
|
||||
else {
|
||||
varList.push({'name': name, 'type': null, 'id': null});
|
||||
// TODO(marisaleung): Use variable.type and variable.getId() once variable
|
||||
// instances are storing more than just name.
|
||||
}
|
||||
}
|
||||
if (clear) {
|
||||
this.variableMap_.clear();
|
||||
}
|
||||
// Update the list in place so that the flyout's references stay correct.
|
||||
for (var i = 0, varDict; varDict = varList[i]; i++) {
|
||||
if (!this.getVariable(varDict.name)) {
|
||||
this.createVariable(varDict.name, varDict.type, varDict.id);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Rename a variable by updating its name in the variable list.
|
||||
* Rename a variable by updating its name in the variable map. Identify the
|
||||
* variable to rename with the given variable.
|
||||
* TODO: #468
|
||||
* @param {string} oldName Variable to rename.
|
||||
* @param {?Blockly.VariableModel} variable 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);
|
||||
Blockly.Workspace.prototype.renameVariableInternal_ = function(variable, newName) {
|
||||
var newVariable = this.getVariable(newName);
|
||||
var oldCase;
|
||||
|
||||
// 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];
|
||||
// If they are different types, throw an error.
|
||||
if (variable && newVariable && variable.type != newVariable.type) {
|
||||
throw Error('Variable "' + variable.name + '" is type "' + variable.type +
|
||||
'" and variable "' + newName + '" is type "' + newVariable.type +
|
||||
'". Both must be the same type.');
|
||||
}
|
||||
|
||||
// Find if newVariable case is different.
|
||||
if (newVariable && newVariable.name != newName) {
|
||||
oldCase = newVariable.name;
|
||||
}
|
||||
|
||||
Blockly.Events.setGroup(true);
|
||||
var blocks = this.getAllBlocks();
|
||||
// Iterate through every block.
|
||||
// Iterate through every block and update name.
|
||||
for (var i = 0; i < blocks.length; i++) {
|
||||
blocks[i].renameVar(oldName, newName);
|
||||
blocks[i].renameVar(variable.name, newName);
|
||||
if (oldCase) {
|
||||
blocks[i].renameVar(oldCase, newName);
|
||||
}
|
||||
}
|
||||
Blockly.Events.setGroup(false);
|
||||
|
||||
this.variableMap_.renameVariable(variable, newName);
|
||||
};
|
||||
|
||||
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.
|
||||
// The case might have changed, so we update the destination ID.
|
||||
this.variableList[newVariableIndex] = newName;
|
||||
this.variableList.splice(variableIndex, 1);
|
||||
} else {
|
||||
this.variableList.push(newName);
|
||||
console.log('Tried to rename an non-existent variable.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Rename a variable by updating its name in the variable map. Identify the
|
||||
* variable to rename with the given name.
|
||||
* TODO: #468
|
||||
* @param {string} oldName Variable to rename.
|
||||
* @param {string} newName New variable name.
|
||||
*/
|
||||
Blockly.Workspace.prototype.renameVariable = function(oldName, newName) {
|
||||
// Warning: Prefer to use renameVariableById.
|
||||
var variable = this.getVariable(oldName);
|
||||
this.renameVariableInternal_(variable, newName);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a variable with the given name.
|
||||
* TODO: #468
|
||||
* @param {string} name The new variable's name.
|
||||
* Rename a variable by updating its name in the variable map. Identify the
|
||||
* variable to rename with the given id.
|
||||
* @param {string} id Id of the variable to rename.
|
||||
* @param {string} newName New variable name.
|
||||
*/
|
||||
Blockly.Workspace.prototype.createVariable = function(name) {
|
||||
Blockly.Workspace.prototype.renameVariableById = function(id, newName) {
|
||||
var variable = this.getVariableById(id);
|
||||
this.renameVariableInternal_(variable, newName);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a variable with a given name, optional type, and optional id.
|
||||
* @param {!string} name The name of the variable. This must be unique across
|
||||
* variables and procedures.
|
||||
* @param {?string} opt_type The type of the variable like 'int' or 'string'.
|
||||
* Does not need to be unique. Field_variable can filter variables based on
|
||||
* their type. This will default to '' which is a specific type.
|
||||
* @param {?string} opt_id The unique id of the variable. This will default to
|
||||
* a UUID.
|
||||
* @return {?Blockly.VariableModel} The newly created variable.
|
||||
*/
|
||||
Blockly.Workspace.prototype.createVariable = function(name, opt_type, opt_id) {
|
||||
if (name.toLowerCase() == Blockly.Variables.noVariableText()) {
|
||||
return;
|
||||
}
|
||||
var index = this.variableIndexOf(name);
|
||||
if (index == -1) {
|
||||
this.variableList.push(name);
|
||||
}
|
||||
return this.variableMap_.createVariable(name, opt_type, opt_id);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -312,15 +353,11 @@ Blockly.Workspace.prototype.getVariableUses = function(name) {
|
|||
};
|
||||
|
||||
/**
|
||||
* Delete a variables and all of its uses from this workspace. May prompt the
|
||||
* user for confirmation.
|
||||
* Delete a variable by the passed in name and all of its uses from this
|
||||
* workspace. May prompt the user for confirmation.
|
||||
* @param {string} name Name of variable to delete.
|
||||
*/
|
||||
Blockly.Workspace.prototype.deleteVariable = function(name) {
|
||||
var variableIndex = this.variableIndexOf(name);
|
||||
if (variableIndex == -1) {
|
||||
return;
|
||||
}
|
||||
// Check whether this variable is a function parameter before deleting.
|
||||
var uses = this.getVariableUses(name);
|
||||
for (var i = 0, block; block = uses[i]; i++) {
|
||||
|
@ -336,6 +373,7 @@ Blockly.Workspace.prototype.deleteVariable = function(name) {
|
|||
}
|
||||
|
||||
var workspace = this;
|
||||
var variable = workspace.getVariable(name);
|
||||
if (uses.length > 1) {
|
||||
// Confirm before deleting multiple blocks.
|
||||
Blockly.confirm(
|
||||
|
@ -343,30 +381,41 @@ Blockly.Workspace.prototype.deleteVariable = function(name) {
|
|||
replace('%2', name),
|
||||
function(ok) {
|
||||
if (ok) {
|
||||
workspace.deleteVariableInternal_(name);
|
||||
workspace.deleteVariableInternal_(variable);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// No confirmation necessary for a single block.
|
||||
this.deleteVariableInternal_(name);
|
||||
this.deleteVariableInternal_(variable);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete a variables by the passed in id and all of its uses from this
|
||||
* workspace. May prompt the user for confirmation.
|
||||
* @param {string} id Id of variable to delete.
|
||||
*/
|
||||
Blockly.Workspace.prototype.deleteVariableById = function(id) {
|
||||
var variable = this.getVariableById(id);
|
||||
if (variable) {
|
||||
this.deleteVariableInternal_(variable);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Deletes a variable and all of its uses from this workspace without asking the
|
||||
* user for confirmation.
|
||||
* @param {string} name The name of the variable to delete
|
||||
* @param {Blockly.VariableModel} variable Variable to delete.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Workspace.prototype.deleteVariableInternal_ = function(name) {
|
||||
var uses = this.getVariableUses(name);
|
||||
var variableIndex = this.variableIndexOf(name);
|
||||
Blockly.Workspace.prototype.deleteVariableInternal_ = function(variable) {
|
||||
var uses = this.getVariableUses(variable.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);
|
||||
this.variableMap_.deleteVariable(variable);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -375,16 +424,34 @@ Blockly.Workspace.prototype.deleteVariableInternal_ = function(name) {
|
|||
* @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.
|
||||
* @deprecated April 2017
|
||||
*/
|
||||
Blockly.Workspace.prototype.variableIndexOf = function(name) {
|
||||
for (var i = 0, varname; varname = this.variableList[i]; i++) {
|
||||
if (Blockly.Names.equals(varname, name)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
Blockly.Workspace.prototype.variableIndexOf = function(/* name */) {
|
||||
console.warn(
|
||||
'Deprecated call to Blockly.Workspace.prototype.variableIndexOf');
|
||||
return -1;
|
||||
};
|
||||
|
||||
/**
|
||||
* Find the variable by the given name and return it. Return null if it is not
|
||||
* found.
|
||||
* @param {!string} name The name to check for.
|
||||
* @return {?Blockly.VariableModel} the variable with the given name.
|
||||
*/
|
||||
Blockly.Workspace.prototype.getVariable = function(name) {
|
||||
return this.variableMap_.getVariable(name);
|
||||
};
|
||||
|
||||
/**
|
||||
* Find the variable by the given id and return it. Return null if it is not
|
||||
* found.
|
||||
* @param {!string} id The id to check for.
|
||||
* @return {?Blockly.VariableModel} The variable with the given id.
|
||||
*/
|
||||
Blockly.Workspace.prototype.getVariableById = function(id) {
|
||||
return this.variableMap_.getVariableById(id);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the horizontal offset of the workspace.
|
||||
* Intended for LTR/RTL compatibility in XML.
|
||||
|
@ -523,6 +590,50 @@ Blockly.Workspace.prototype.getFlyout = function() {
|
|||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks whether all value and statement inputs in the workspace are filled
|
||||
* with blocks.
|
||||
* @param {boolean=} opt_shadowBlocksAreFilled An optional argument controlling
|
||||
* whether shadow blocks are counted as filled. Defaults to true.
|
||||
* @return {boolean} True if all inputs are filled, false otherwise.
|
||||
*/
|
||||
Blockly.Workspace.prototype.allInputsFilled = function(opt_shadowBlocksAreFilled) {
|
||||
var blocks = this.getTopBlocks(false);
|
||||
for (var i = 0, block; block = blocks[i]; i++) {
|
||||
if (!block.allInputsFilled(opt_shadowBlocksAreFilled)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Find the variable with the specified type. If type is null, return list of
|
||||
* variables with empty string type.
|
||||
* @param {?string} type Type of the variables to find.
|
||||
* @return {Array.<Blockly.VariableModel>} The sought after variables of the
|
||||
* passed in type. An empty array if none are found.
|
||||
*/
|
||||
Blockly.Workspace.prototype.getVariablesOfType = function(type) {
|
||||
return this.variableMap_.getVariablesOfType(type);
|
||||
};
|
||||
|
||||
/**
|
||||
* Return all variable types.
|
||||
* @return {!Array.<string>} List of variable types.
|
||||
*/
|
||||
Blockly.Workspace.prototype.getVariableTypes = function() {
|
||||
return this.variableMap_.getVariableTypes();
|
||||
};
|
||||
|
||||
/**
|
||||
* Return all variables of all types.
|
||||
* @return {!Array.<Blockly.VariableModel>} List of variable models.
|
||||
*/
|
||||
Blockly.Workspace.prototype.getAllVariables = function() {
|
||||
return this.variableMap_.getAllVariables();
|
||||
};
|
||||
|
||||
/**
|
||||
* Database of all workspaces.
|
||||
* @private
|
||||
|
|
132
core/workspace_dragger.js
Normal file
132
core/workspace_dragger.js
Normal file
|
@ -0,0 +1,132 @@
|
|||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2017 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 Methods for dragging a workspace visually.
|
||||
* @author fenichel@google.com (Rachel Fenichel)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.WorkspaceDragger');
|
||||
|
||||
goog.require('goog.math.Coordinate');
|
||||
goog.require('goog.asserts');
|
||||
|
||||
|
||||
/**
|
||||
* Class for a workspace dragger. It moves the workspace around when it is
|
||||
* being dragged by a mouse or touch.
|
||||
* Note that the workspace itself manages whether or not it has a drag surface
|
||||
* and how to do translations based on that. This simply passes the right
|
||||
* commands based on events.
|
||||
* @param {!Blockly.WorkspaceSvg} workspace The workspace to drag.
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.WorkspaceDragger = function(workspace) {
|
||||
/**
|
||||
* @type {!Blockly.WorkspaceSvg}
|
||||
* @private
|
||||
*/
|
||||
this.workspace_ = workspace;
|
||||
|
||||
/**
|
||||
* The workspace's metrics object at the beginning of the drag. Contains size
|
||||
* and position metrics of a workspace.
|
||||
* Coordinate system: pixel coordinates.
|
||||
* @type {!Object}
|
||||
* @private
|
||||
*/
|
||||
this.startDragMetrics_ = workspace.getMetrics();
|
||||
|
||||
/**
|
||||
* The scroll position of the workspace at the beginning of the drag.
|
||||
* Coordinate system: pixel coordinates.
|
||||
* @type {!goog.math.Coordinate}
|
||||
* @private
|
||||
*/
|
||||
this.startScrollXY_ = new goog.math.Coordinate(workspace.scrollX,
|
||||
workspace.scrollY);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sever all links from this object.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceDragger.prototype.dispose = function() {
|
||||
this.workspace_ = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Start dragging the workspace.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceDragger.prototype.startDrag = function() {
|
||||
if (Blockly.selected) {
|
||||
Blockly.selected.unselect();
|
||||
}
|
||||
this.workspace_.setupDragSurface();
|
||||
};
|
||||
|
||||
/**
|
||||
* Finish dragging the workspace and put everything back where it belongs.
|
||||
* @param {!goog.math.Coordinate} currentDragDeltaXY How far the pointer has
|
||||
* moved from the position at the start of the drag, in pixel coordinates.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceDragger.prototype.endDrag = function(currentDragDeltaXY) {
|
||||
// Make sure everything is up to date.
|
||||
this.drag(currentDragDeltaXY);
|
||||
this.workspace_.resetDragSurface();
|
||||
};
|
||||
|
||||
/**
|
||||
* Move the workspace based on the most recent mouse movements.
|
||||
* @param {!goog.math.Coordinate} currentDragDeltaXY How far the pointer has
|
||||
* moved from the position at the start of the drag, in pixel coordinates.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceDragger.prototype.drag = function(currentDragDeltaXY) {
|
||||
var metrics = this.startDragMetrics_;
|
||||
var newXY = goog.math.Coordinate.sum(this.startScrollXY_, currentDragDeltaXY);
|
||||
|
||||
// Bound the new XY based on workspace bounds.
|
||||
var x = Math.min(newXY.x, -metrics.contentLeft);
|
||||
var y = Math.min(newXY.y, -metrics.contentTop);
|
||||
x = Math.max(x, metrics.viewWidth - metrics.contentLeft -
|
||||
metrics.contentWidth);
|
||||
y = Math.max(y, metrics.viewHeight - metrics.contentTop -
|
||||
metrics.contentHeight);
|
||||
|
||||
x = -x - metrics.contentLeft;
|
||||
y = -y - metrics.contentTop;
|
||||
|
||||
this.updateScroll_(x, y);
|
||||
};
|
||||
|
||||
/**
|
||||
* Move the scrollbars to drag the workspace.
|
||||
* x and y are in pixels.
|
||||
* @param {number} x The new x position to move the scrollbar to.
|
||||
* @param {number} y The new y position to move the scrollbar to.
|
||||
* @private
|
||||
*/
|
||||
Blockly.WorkspaceDragger.prototype.updateScroll_ = function(x, y) {
|
||||
this.workspace_.scrollbar.set(x, y);
|
||||
};
|
|
@ -34,6 +34,7 @@ goog.require('Blockly.constants');
|
|||
goog.require('Blockly.DropDownDiv');
|
||||
goog.require('Blockly.Events');
|
||||
//goog.require('Blockly.HorizontalFlyout');
|
||||
goog.require('Blockly.Gesture');
|
||||
goog.require('Blockly.Options');
|
||||
goog.require('Blockly.ScrollbarPair');
|
||||
goog.require('Blockly.Touch');
|
||||
|
@ -69,27 +70,6 @@ Blockly.WorkspaceSvg = function(options, opt_blockDragSurface, opt_wsDragSurface
|
|||
options.setMetrics || Blockly.WorkspaceSvg.setTopLevelWorkspaceMetrics_;
|
||||
|
||||
Blockly.ConnectionDB.init(this);
|
||||
if (opt_blockDragSurface) {
|
||||
this.blockDragSurface_ = opt_blockDragSurface;
|
||||
}
|
||||
|
||||
if (opt_wsDragSurface) {
|
||||
this.workspaceDragSurface_ = opt_wsDragSurface;
|
||||
}
|
||||
|
||||
this.useWorkspaceDragSurface_ =
|
||||
this.workspaceDragSurface_ && Blockly.utils.is3dSupported();
|
||||
|
||||
if (opt_blockDragSurface) {
|
||||
this.blockDragSurface_ = opt_blockDragSurface;
|
||||
}
|
||||
|
||||
if (opt_wsDragSurface) {
|
||||
this.workspaceDragSurface_ = opt_wsDragSurface;
|
||||
}
|
||||
|
||||
this.useWorkspaceDragSurface_ =
|
||||
this.workspaceDragSurface_ && Blockly.utils.is3dSupported();
|
||||
|
||||
if (opt_blockDragSurface) {
|
||||
this.blockDragSurface_ = opt_blockDragSurface;
|
||||
|
@ -151,15 +131,6 @@ Blockly.WorkspaceSvg.prototype.isFlyout = false;
|
|||
*/
|
||||
Blockly.WorkspaceSvg.prototype.isMutator = false;
|
||||
|
||||
/**
|
||||
* Is this workspace currently being dragged around?
|
||||
* DRAG_NONE - No drag operation.
|
||||
* DRAG_BEGIN - Still inside the initial DRAG_RADIUS.
|
||||
* DRAG_FREE - Workspace has been dragged further than DRAG_RADIUS.
|
||||
* @private
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.dragMode_ = Blockly.DRAG_NONE;
|
||||
|
||||
/**
|
||||
* Whether this workspace has resizes enabled.
|
||||
* Disable during batch operations for a performance improvement.
|
||||
|
@ -169,25 +140,25 @@ Blockly.WorkspaceSvg.prototype.dragMode_ = Blockly.DRAG_NONE;
|
|||
Blockly.WorkspaceSvg.prototype.resizesEnabled_ = true;
|
||||
|
||||
/**
|
||||
* Current horizontal scrolling offset.
|
||||
* Current horizontal scrolling offset in pixel units.
|
||||
* @type {number}
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.scrollX = 0;
|
||||
|
||||
/**
|
||||
* Current vertical scrolling offset.
|
||||
* Current vertical scrolling offset in pixel units.
|
||||
* @type {number}
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.scrollY = 0;
|
||||
|
||||
/**
|
||||
* Horizontal scroll value when scrolling started.
|
||||
* Horizontal scroll value when scrolling started in pixel units.
|
||||
* @type {number}
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.startScrollX = 0;
|
||||
|
||||
/**
|
||||
* Vertical scroll value when scrolling started.
|
||||
* Vertical scroll value when scrolling started in pixel units.
|
||||
* @type {number}
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.startScrollY = 0;
|
||||
|
@ -217,6 +188,13 @@ Blockly.WorkspaceSvg.prototype.trashcan = null;
|
|||
*/
|
||||
Blockly.WorkspaceSvg.prototype.scrollbar = null;
|
||||
|
||||
/**
|
||||
* The current gesture in progress on this workspace, if any.
|
||||
* @type {Blockly.Gesture}
|
||||
* @private
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.currentGesture_ = null;
|
||||
|
||||
/**
|
||||
* This workspace's surface for dragging blocks, if it exists.
|
||||
* @type {Blockly.BlockDragSurfaceSvg}
|
||||
|
@ -387,11 +365,16 @@ Blockly.WorkspaceSvg.prototype.createDom = function(opt_backgroundClass) {
|
|||
*/
|
||||
this.svgGroup_ = Blockly.utils.createSvgElement('g',
|
||||
{'class': 'blocklyWorkspace'}, null);
|
||||
|
||||
// Note that a <g> alone does not receive mouse events--it must have a
|
||||
// valid target inside it. If no background class is specified, as in the
|
||||
// flyout, the workspace will not receive mouse events.
|
||||
if (opt_backgroundClass) {
|
||||
/** @type {SVGElement} */
|
||||
this.svgBackground_ = Blockly.utils.createSvgElement('rect',
|
||||
{'height': '100%', 'width': '100%', 'class': opt_backgroundClass},
|
||||
this.svgGroup_);
|
||||
|
||||
if (opt_backgroundClass == 'blocklyMainBackground') {
|
||||
this.svgBackground_.style.fill =
|
||||
'url(#' + this.options.gridPattern.id + ')';
|
||||
|
@ -414,9 +397,6 @@ Blockly.WorkspaceSvg.prototype.createDom = function(opt_backgroundClass) {
|
|||
if (!this.isFlyout) {
|
||||
Blockly.bindEventWithChecks_(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.bindEventWithChecks_(this.svgGroup_, 'wheel', this,
|
||||
|
@ -446,6 +426,9 @@ Blockly.WorkspaceSvg.prototype.createDom = function(opt_backgroundClass) {
|
|||
Blockly.WorkspaceSvg.prototype.dispose = function() {
|
||||
// Stop rerendering.
|
||||
this.rendered = false;
|
||||
if (this.currentGesture_) {
|
||||
this.currentGesture_.cancel();
|
||||
}
|
||||
Blockly.WorkspaceSvg.superClass_.dispose.call(this);
|
||||
if (this.svgGroup_) {
|
||||
goog.dom.removeNode(this.svgGroup_);
|
||||
|
@ -921,10 +904,20 @@ Blockly.WorkspaceSvg.prototype.paste = function(xmlBlock) {
|
|||
if (!this.rendered) {
|
||||
return;
|
||||
}
|
||||
Blockly.terminateDrag_(); // Dragging while pasting? No.
|
||||
if (this.currentGesture_) {
|
||||
this.currentGesture_.cancel(); // Dragging while pasting? No.
|
||||
}
|
||||
Blockly.Events.disable();
|
||||
try {
|
||||
var block = Blockly.Xml.domToBlock(xmlBlock, this);
|
||||
// Rerender to get around problem with IE and Edge not measuring text
|
||||
// correctly when it is hidden.
|
||||
if (goog.userAgent.IE || goog.userAgent.EDGE) {
|
||||
var blocks = block.getDescendants();
|
||||
for (var i = blocks.length - 1; i >= 0; i--) {
|
||||
blocks[i].render(false);
|
||||
}
|
||||
}
|
||||
// Move the duplicate to original position.
|
||||
var blockX = parseInt(xmlBlock.getAttribute('x'), 10);
|
||||
var blockY = parseInt(xmlBlock.getAttribute('y'), 10);
|
||||
|
@ -1000,7 +993,7 @@ Blockly.WorkspaceSvg.prototype.renameVariable = function(oldName, newName) {
|
|||
Blockly.WorkspaceSvg.prototype.createVariable = function(name) {
|
||||
Blockly.WorkspaceSvg.superClass_.createVariable.call(this, name);
|
||||
// Don't refresh the toolbox if there's a drag in progress.
|
||||
if (this.toolbox_ && this.toolbox_.flyout_ && !Blockly.Flyout.startFlyout_) {
|
||||
if (this.toolbox_ && this.toolbox_.flyout_ && !this.currentGesture_) {
|
||||
this.toolbox_.refreshSelection();
|
||||
}
|
||||
};
|
||||
|
@ -1025,7 +1018,6 @@ Blockly.WorkspaceSvg.prototype.recordDeleteAreas = function() {
|
|||
|
||||
/**
|
||||
* Is the mouse event over a delete area (toolbox or non-closing flyout)?
|
||||
* Opens or closes the trashcan and sets the cursor as a side effect.
|
||||
* @param {!Event} e Mouse move event.
|
||||
* @return {?number} Null if not over a delete area, or an enum representing
|
||||
* which delete area the event is over.
|
||||
|
@ -1038,7 +1030,7 @@ Blockly.WorkspaceSvg.prototype.isDeleteArea = function(e) {
|
|||
if (this.deleteAreaToolbox_ && this.deleteAreaToolbox_.contains(xy)) {
|
||||
return Blockly.DELETE_AREA_TOOLBOX;
|
||||
}
|
||||
return null;
|
||||
return Blockly.DELETE_AREA_NONE;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -1047,61 +1039,13 @@ Blockly.WorkspaceSvg.prototype.isDeleteArea = function(e) {
|
|||
* @private
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.onMouseDown_ = function(e) {
|
||||
this.markFocused();
|
||||
if (Blockly.utils.isTargetInput(e)) {
|
||||
Blockly.Touch.clearTouchIdentifier();
|
||||
return;
|
||||
}
|
||||
Blockly.terminateDrag_(); // In case mouse-up event was lost.
|
||||
Blockly.hideChaff();
|
||||
// TODO (fenichel): Move this to gesture.
|
||||
Blockly.DropDownDiv.hide();
|
||||
var isTargetWorkspace = e.target && e.target.nodeName &&
|
||||
(e.target.nodeName.toLowerCase() == 'svg' ||
|
||||
e.target == this.svgBackground_);
|
||||
if (isTargetWorkspace && Blockly.selected && !this.options.readOnly) {
|
||||
// Clicking on the document clears the selection.
|
||||
Blockly.selected.unselect();
|
||||
}
|
||||
if (Blockly.utils.isRightButton(e)) {
|
||||
// Right-click.
|
||||
this.showContextMenu_(e);
|
||||
// This is to handle the case where the event is pretending to be a right
|
||||
// click event but it was really a long press. In that case, we want to make
|
||||
// sure any in progress drags are stopped.
|
||||
Blockly.onMouseUp_(e);
|
||||
// Since this was a click, not a drag, end the gesture immediately.
|
||||
Blockly.Touch.clearTouchIdentifier();
|
||||
} else if (this.scrollbar) {
|
||||
this.dragMode_ = Blockly.DRAG_BEGIN;
|
||||
// Record the current mouse position.
|
||||
this.startDragMouseX = e.clientX;
|
||||
this.startDragMouseY = e.clientY;
|
||||
this.startDragMetrics = this.getMetrics();
|
||||
this.startScrollX = this.scrollX;
|
||||
this.startScrollY = this.scrollY;
|
||||
|
||||
this.setupDragSurface();
|
||||
// If this is a touch event then bind to the mouseup so workspace drag mode
|
||||
// is turned off and double move events are not performed on a block.
|
||||
// See comment in inject.js Blockly.init_ as to why mouseup events are
|
||||
// bound to the document instead of the SVG's surface.
|
||||
if ('mouseup' in Blockly.Touch.TOUCH_MAP) {
|
||||
Blockly.Touch.onTouchUpWrapper_ = Blockly.Touch.onTouchUpWrapper_ || [];
|
||||
Blockly.Touch.onTouchUpWrapper_ = Blockly.Touch.onTouchUpWrapper_.concat(
|
||||
Blockly.bindEventWithChecks_(document, 'mouseup', null,
|
||||
Blockly.onMouseUp_));
|
||||
}
|
||||
Blockly.onMouseMoveWrapper_ = Blockly.onMouseMoveWrapper_ || [];
|
||||
Blockly.onMouseMoveWrapper_ = Blockly.onMouseMoveWrapper_.concat(
|
||||
Blockly.bindEventWithChecks_(document, 'mousemove', null,
|
||||
Blockly.onMouseMove_));
|
||||
} else {
|
||||
// It was a click, but the workspace isn't draggable.
|
||||
Blockly.Touch.clearTouchIdentifier();
|
||||
var gesture = this.getGesture(e);
|
||||
if (gesture) {
|
||||
gesture.handleWsStart(e, this);
|
||||
}
|
||||
// This event has been handled. No need to bubble up to the document.
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -1138,10 +1082,7 @@ Blockly.WorkspaceSvg.prototype.moveDrag = function(e) {
|
|||
* @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;
|
||||
return this.currentGesture_ && this.currentGesture_.isDragging();
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -1158,9 +1099,12 @@ Blockly.WorkspaceSvg.prototype.isDraggable = function() {
|
|||
* @private
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.onMouseWheel_ = function(e) {
|
||||
// TODO: Remove terminateDrag and compensate for coordinate skew during zoom.
|
||||
// TODO: Remove gesture cancellation and compensate for coordinate skew during
|
||||
// zoom.
|
||||
if (this.currentGesture_) {
|
||||
this.currentGesture_.cancel();
|
||||
}
|
||||
if (e.ctrlKey) {
|
||||
Blockly.terminateDrag_();
|
||||
// The vertical scroll distance that corresponds to a click of a zoom button.
|
||||
var PIXELS_PER_ZOOM_STEP = 50;
|
||||
var delta = -e.deltaY / PIXELS_PER_ZOOM_STEP;
|
||||
|
@ -1183,6 +1127,7 @@ Blockly.WorkspaceSvg.prototype.onMouseWheel_ = function(e) {
|
|||
|
||||
/**
|
||||
* Calculate the bounding box for the blocks on the workspace.
|
||||
* Coordinate system: workspace coordinates.
|
||||
*
|
||||
* @return {Object} Contains the position and size of the bounding box
|
||||
* containing the blocks on the workspace.
|
||||
|
@ -1252,6 +1197,7 @@ Blockly.WorkspaceSvg.prototype.showContextMenu_ = function(e) {
|
|||
var menuOptions = [];
|
||||
var topBlocks = this.getTopBlocks(true);
|
||||
var eventGroup = Blockly.utils.genUid();
|
||||
var ws = this;
|
||||
|
||||
// Options to undo/redo previous action.
|
||||
var undoOption = {};
|
||||
|
@ -1367,6 +1313,9 @@ Blockly.WorkspaceSvg.prototype.showContextMenu_ = function(e) {
|
|||
Blockly.Msg.DELETE_X_BLOCKS.replace('%1', String(deleteCount)),
|
||||
enabled: deleteCount > 0,
|
||||
callback: function() {
|
||||
if (ws.currentGesture_) {
|
||||
ws.currentGesture_.cancel();
|
||||
}
|
||||
if (deleteList.length < 2 ) {
|
||||
deleteNext();
|
||||
} else {
|
||||
|
@ -1757,6 +1706,7 @@ Blockly.WorkspaceSvg.prototype.updateStackGlowScale_ = function() {
|
|||
/**
|
||||
* Return an object with all the metrics required to size scrollbars for a
|
||||
* top level workspace. The following properties are computed:
|
||||
* Coordinate system: pixel coordinates.
|
||||
* .viewHeight: Height of the visible rectangle,
|
||||
* .viewWidth: Width of the visible rectangle,
|
||||
* .contentHeight: Height of the contents,
|
||||
|
@ -1793,6 +1743,7 @@ Blockly.WorkspaceSvg.getTopLevelWorkspaceMetrics_ = function() {
|
|||
var MARGIN = Blockly.Flyout.prototype.CORNER_RADIUS - 1;
|
||||
var viewWidth = svgSize.width - MARGIN;
|
||||
var viewHeight = svgSize.height - MARGIN;
|
||||
|
||||
var blockBox = this.getBlocksBoundingBox();
|
||||
|
||||
// Fix scale.
|
||||
|
@ -1976,6 +1927,57 @@ Blockly.WorkspaceSvg.prototype.removeToolboxCategoryCallback = function(key) {
|
|||
this.toolboxCategoryCallbacks_[key] = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Look up the gesture that is tracking this touch stream on this workspace.
|
||||
* May create a new gesture.
|
||||
* @param {!Event} e Mouse event or touch event
|
||||
* @return {Blockly.Gesture} The gesture that is tracking this touch stream,
|
||||
* or null if no valid gesture exists.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.getGesture = function(e) {
|
||||
var isStart = (e.type == 'mousedown' || e.type == 'touchstart');
|
||||
|
||||
var gesture = this.currentGesture_;
|
||||
if (gesture) {
|
||||
if (isStart && gesture.hasStarted()) {
|
||||
console.warn('tried to start the same gesture twice');
|
||||
// That's funny. We must have missed a mouse up.
|
||||
// Cancel it, rather than try to retrieve all of the state we need.
|
||||
gesture.cancel();
|
||||
return null;
|
||||
}
|
||||
return gesture;
|
||||
}
|
||||
|
||||
// No gesture existed on this workspace, but this looks like the start of a
|
||||
// new gesture.
|
||||
if (isStart) {
|
||||
this.currentGesture_ = new Blockly.Gesture(e, this);
|
||||
return this.currentGesture_;
|
||||
}
|
||||
// No gesture existed and this event couldn't be the start of a new gesture.
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear the reference to the current gesture.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.clearGesture = function() {
|
||||
this.currentGesture_ = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Cancel the current gesture, if one exists.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.cancelCurrentGesture = function() {
|
||||
if (this.currentGesture_) {
|
||||
this.currentGesture_.cancel();
|
||||
}
|
||||
};
|
||||
|
||||
// Export symbols that would otherwise be renamed by Closure compiler.
|
||||
Blockly.WorkspaceSvg.prototype['setVisible'] =
|
||||
Blockly.WorkspaceSvg.prototype.setVisible;
|
||||
|
|
|
@ -332,7 +332,7 @@ Blockly.Xml.domToWorkspace = function(xml, workspace) {
|
|||
}
|
||||
Blockly.Field.stopCache();
|
||||
|
||||
workspace.updateVariableList(false);
|
||||
workspace.updateVariableStore(false);
|
||||
// Re-enable workspace resizing.
|
||||
if (workspace.setResizesEnabled) {
|
||||
workspace.setResizesEnabled(true);
|
||||
|
@ -629,6 +629,9 @@ Blockly.Xml.domToBlockHeadless_ = function(xmlBlock, workspace) {
|
|||
goog.asserts.assert(child.isShadow(),
|
||||
'Shadow block not allowed non-shadow child.');
|
||||
}
|
||||
// Ensure this block doesn't have any variable inputs.
|
||||
goog.asserts.assert(block.getVars().length == 0,
|
||||
'Shadow blocks cannot have variable fields.');
|
||||
block.setShadow(true);
|
||||
}
|
||||
return block;
|
||||
|
|
|
@ -196,6 +196,14 @@ Blockly.ZoomControls.prototype.createDom = function() {
|
|||
);
|
||||
|
||||
// Attach event listeners.
|
||||
Blockly.bindEventWithChecks_(zoomresetSvg, 'mousedown', null, function(e) {
|
||||
workspace.markFocused();
|
||||
workspace.setScale(workspace.options.zoomOptions.startScale);
|
||||
workspace.scrollCenter();
|
||||
Blockly.Touch.clearTouchIdentifier(); // Don't block future drags.
|
||||
e.stopPropagation(); // Don't start a workspace scroll.
|
||||
e.preventDefault(); // Stop double-clicking from selecting text.
|
||||
});
|
||||
Blockly.bindEventWithChecks_(zoominSvg, 'mousedown', null, function(e) {
|
||||
workspace.markFocused();
|
||||
workspace.zoomCenter(1);
|
||||
|
@ -210,14 +218,6 @@ Blockly.ZoomControls.prototype.createDom = function() {
|
|||
e.stopPropagation(); // Don't start a workspace scroll.
|
||||
e.preventDefault(); // Stop double-clicking from selecting text.
|
||||
});
|
||||
Blockly.bindEventWithChecks_(zoomresetSvg, 'mousedown', null, function(e) {
|
||||
workspace.markFocused();
|
||||
workspace.setScale(workspace.options.zoomOptions.startScale);
|
||||
workspace.scrollCenter();
|
||||
Blockly.Touch.clearTouchIdentifier(); // Don't block future drags.
|
||||
e.stopPropagation(); // Don't start a workspace scroll.
|
||||
e.preventDefault(); // Stop double-clicking from selecting text.
|
||||
});
|
||||
|
||||
return this.svgGroup_;
|
||||
};
|
||||
|
|
|
@ -103,7 +103,7 @@ Blockly.Dart.init = function(workspace) {
|
|||
}
|
||||
|
||||
var defvars = [];
|
||||
var variables = workspace.variableList;
|
||||
var variables = workspace.getAllVariables();
|
||||
if (variables.length) {
|
||||
for (var i = 0; i < variables.length; i++) {
|
||||
defvars[i] = Blockly.Dart.variableDB_.getName(variables[i],
|
||||
|
|
|
@ -153,7 +153,7 @@ Blockly.JavaScript.init = function(workspace) {
|
|||
}
|
||||
|
||||
var defvars = [];
|
||||
var variables = workspace.variableList;
|
||||
var variables = workspace.getAllVariables();
|
||||
if (variables.length) {
|
||||
for (var i = 0; i < variables.length; i++) {
|
||||
defvars[i] = Blockly.JavaScript.variableDB_.getName(variables[i],
|
||||
|
|
|
@ -161,7 +161,7 @@ Blockly.Python.init = function(workspace) {
|
|||
}
|
||||
|
||||
var defvars = [];
|
||||
var variables = workspace.variableList;
|
||||
var variables = workspace.getAllVariables();
|
||||
for (var i = 0; i < variables.length; i++) {
|
||||
defvars[i] = Blockly.Python.variableDB_.getName(variables[i],
|
||||
Blockly.Variables.NAME_TYPE) + ' = None';
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
{"MATH_HUE": "230", "LOOPS_HUE": "120", "LISTS_HUE": "260", "LOGIC_HUE": "210", "VARIABLES_HUE": "330", "TEXTS_HUE": "160", "PROCEDURES_HUE": "290", "COLOUR_HUE": "20"}
|
|
@ -125,6 +125,8 @@ Blockly.Msg.NEW_VARIABLE = 'Create variable...';
|
|||
Blockly.Msg.NEW_VARIABLE_TITLE = 'New variable name:';
|
||||
/// alert - Tells the user that the name they entered is already in use.
|
||||
Blockly.Msg.VARIABLE_ALREADY_EXISTS = 'A variable named "%1" already exists.'
|
||||
/// alert - Tells the user that the name they entered is already in use for a procedure.
|
||||
Blockly.Msg.PROCEDURE_ALREADY_EXISTS = 'A procedure named "%1" already exists.'
|
||||
|
||||
// Variable deletion.
|
||||
/// confirm - Ask the user to confirm their deletion of multiple uses of a variable.
|
||||
|
|
|
@ -5,12 +5,11 @@ if [ ! -d $chromedriver_dir ]; then
|
|||
mkdir $chromedriver_dir
|
||||
fi
|
||||
|
||||
if [[ $os_name == 'Linux' ]]; then
|
||||
if [[ $os_name == 'Linux' && ! -f $chromedriver_dir/chromedriver ]]; then
|
||||
cd chromedriver && curl -L https://chromedriver.storage.googleapis.com/2.29/chromedriver_linux64.zip > tmp.zip && unzip -o tmp.zip && rm tmp.zip
|
||||
# wait until download finish
|
||||
sleep 5
|
||||
elif [[ $os_name == 'Darwin' ]]; then
|
||||
elif [[ $os_name == 'Darwin' && ! -f $chromedriver_dir/chromedriver ]]; then
|
||||
cd chromedriver && curl -L https://chromedriver.storage.googleapis.com/2.29/chromedriver_mac64.zip | tar xz
|
||||
# wait until download finish
|
||||
sleep 5
|
||||
fi
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
#!/bin/bash
|
||||
os_name=`uname`
|
||||
|
||||
if [ -f geckodriver ]; then
|
||||
exit 0
|
||||
fi
|
||||
echo "downloading gechdriver"
|
||||
|
||||
if [[ $os_name == 'Linux' ]]; then
|
||||
cd ../ && curl -L https://github.com/mozilla/geckodriver/releases/download/v0.11.1/geckodriver-v0.11.1-linux64.tar.gz | tar xz
|
||||
sleep 5
|
||||
|
|
|
@ -6,10 +6,9 @@ if [ ! -d $DIR ]; then
|
|||
mkdir $DIR
|
||||
fi
|
||||
|
||||
echo "downloading selenium jar"
|
||||
|
||||
if [ ! -f $DIR/$FILE ]; then
|
||||
cd $DIR && curl -O http://selenium-release.storage.googleapis.com/3.0/selenium-server-standalone-3.0.1.jar
|
||||
sleep 5
|
||||
fi
|
||||
|
||||
|
||||
|
||||
|
|
8
scripts/setup_linux_env.sh
Executable file
8
scripts/setup_linux_env.sh
Executable file
|
@ -0,0 +1,8 @@
|
|||
#!/bin/bash
|
||||
|
||||
if [ "${TRAVIS_OS_NAME}" == "linux" ]
|
||||
then
|
||||
export CHROME_BIN="/usr/bin/google-chrome"
|
||||
export DISPLAY=:99.0
|
||||
sh -e /etc/init.d/xvfb start &
|
||||
fi
|
8
scripts/setup_osx_env.sh
Executable file
8
scripts/setup_osx_env.sh
Executable file
|
@ -0,0 +1,8 @@
|
|||
#!/bin/bash
|
||||
|
||||
if [ "${TRAVIS_OS_NAME}" == "osx" ]
|
||||
then
|
||||
brew cask install google-chrome
|
||||
sudo Xvfb :99 -ac -screen 0 1024x768x8 &
|
||||
export CHROME_BIN="/Applications/Google Chrome.app"
|
||||
fi
|
|
@ -13,10 +13,11 @@ function check_command {
|
|||
|
||||
check_command scripts/get_geckdriver.sh
|
||||
sleep 5
|
||||
check_command scripts/get_selenium.sh
|
||||
check_command scripts/get_selenium.sh
|
||||
sleep 5
|
||||
check_command scripts/get_chromedriver.sh
|
||||
sleep 5
|
||||
check_command scripts/selenium_connect.sh
|
||||
sleep 3
|
||||
check_command scripts/get_chromedriver.sh
|
||||
sleep 10
|
||||
check_command scripts/selenium_connect.sh
|
||||
sleep 10
|
||||
|
||||
exit $EXIT_STATUS
|
||||
|
|
|
@ -18,5 +18,6 @@
|
|||
<script src="xml_test.js"></script>
|
||||
<script src="json_test.js"></script>
|
||||
<script src="variable_model_test.js"></script>
|
||||
<script src="variable_map_test.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -10,7 +10,7 @@ var path = process.cwd();
|
|||
var browser = webdriverio
|
||||
.remote(options)
|
||||
.init()
|
||||
.url("file://" + path + "/tests/jsunit/index.html").pause(3000);
|
||||
.url("file://" + path + "/tests/jsunit/index.html").pause(5000);
|
||||
|
||||
|
||||
browser
|
||||
|
|
282
tests/jsunit/variable_map_test.js
Normal file
282
tests/jsunit/variable_map_test.js
Normal file
|
@ -0,0 +1,282 @@
|
|||
/**
|
||||
* @license
|
||||
* Blockly Tests
|
||||
*
|
||||
* Copyright 2017 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.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.require('goog.testing');
|
||||
goog.require('goog.testing.MockControl');
|
||||
|
||||
var variable_map;
|
||||
var mockControl_;
|
||||
|
||||
function variableMapTest_setUp() {
|
||||
variable_map = new Blockly.VariableMap();
|
||||
mockControl_ = new goog.testing.MockControl();
|
||||
}
|
||||
|
||||
function variableMapTest_tearDown() {
|
||||
mockControl_.$tearDown();
|
||||
variable_map = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a variable with the given values exists.
|
||||
* @param {!string} name The expected name of the variable.
|
||||
* @param {!string} type The expected type of the variable.
|
||||
* @param {!string} id The expected id of the variable.
|
||||
*/
|
||||
function variableMapTest_checkVariableValues(name, type, id) {
|
||||
var variable = variable_map.getVariable(name);
|
||||
assertNotUndefined(variable);
|
||||
assertEquals(name, variable.name);
|
||||
assertEquals(type, variable.type);
|
||||
assertEquals(id, variable.getId());
|
||||
}
|
||||
|
||||
function test_getVariable_Trivial() {
|
||||
variableMapTest_setUp();
|
||||
var var_1 = variable_map.createVariable('name1', 'type1', 'id1');
|
||||
var var_2 = variable_map.createVariable('name2', 'type1', 'id2');
|
||||
var var_3 = variable_map.createVariable('name3', 'type2', 'id3');
|
||||
var result_1 = variable_map.getVariable('name1');
|
||||
var result_2 = variable_map.getVariable('name2');
|
||||
var result_3 = variable_map.getVariable('name3');
|
||||
|
||||
assertEquals(var_1, result_1);
|
||||
assertEquals(var_2, result_2);
|
||||
assertEquals(var_3, result_3);
|
||||
variableMapTest_tearDown();
|
||||
}
|
||||
|
||||
function test_getVariable_NotFound() {
|
||||
variableMapTest_setUp();
|
||||
var result = variable_map.getVariable('name1');
|
||||
assertNull(result);
|
||||
variableMapTest_tearDown();
|
||||
}
|
||||
|
||||
function test_getVariableById_Trivial() {
|
||||
variableMapTest_setUp();
|
||||
var var_1 = variable_map.createVariable('name1', 'type1', 'id1');
|
||||
var var_2 = variable_map.createVariable('name2', 'type1', 'id2');
|
||||
var var_3 = variable_map.createVariable('name3', 'type2', 'id3');
|
||||
var result_1 = variable_map.getVariableById('id1');
|
||||
var result_2 = variable_map.getVariableById('id2');
|
||||
var result_3 = variable_map.getVariableById('id3');
|
||||
|
||||
assertEquals(var_1, result_1);
|
||||
assertEquals(var_2, result_2);
|
||||
assertEquals(var_3, result_3);
|
||||
variableMapTest_tearDown();
|
||||
}
|
||||
|
||||
function test_getVariableById_NotFound() {
|
||||
variableMapTest_setUp();
|
||||
var result = variable_map.getVariableById('id1');
|
||||
assertNull(result);
|
||||
variableMapTest_tearDown();
|
||||
}
|
||||
|
||||
function test_createVariableTrivial() {
|
||||
variableMapTest_setUp();
|
||||
variable_map.createVariable('name1', 'type1', 'id1');
|
||||
variableMapTest_checkVariableValues('name1', 'type1', 'id1')
|
||||
variableMapTest_tearDown();
|
||||
}
|
||||
|
||||
function test_createVariableAlreadyExists() {
|
||||
// Expect that when the variable already exists, the variableMap_ is unchanged.
|
||||
variableMapTest_setUp();
|
||||
var var_1 = variable_map.createVariable('name1', 'type1', 'id1');
|
||||
|
||||
// Assert there is only one variable in the variable_map.
|
||||
var keys = Object.keys(variable_map.variableMap_);
|
||||
assertEquals(1, keys.length);
|
||||
var varMapLength = variable_map.variableMap_[keys[0]].length;
|
||||
assertEquals(1, varMapLength);
|
||||
|
||||
variable_map.createVariable('name1');
|
||||
variableMapTest_checkVariableValues('name1', 'type1', 'id1');
|
||||
// Check that the size of the variableMap_ did not change.
|
||||
keys = Object.keys(variable_map.variableMap_);
|
||||
assertEquals(1, keys.length);
|
||||
varMapLength = variable_map.variableMap_[keys[0]].length;
|
||||
assertEquals(1, varMapLength);
|
||||
variableMapTest_tearDown();
|
||||
}
|
||||
|
||||
function test_createVariableNullAndUndefinedType() {
|
||||
variableMapTest_setUp();
|
||||
variable_map.createVariable('name1', null, 'id1');
|
||||
variable_map.createVariable('name2', undefined, 'id2');
|
||||
|
||||
variableMapTest_checkVariableValues('name1', '', 'id1');
|
||||
variableMapTest_checkVariableValues('name2', '', 'id2');
|
||||
variableMapTest_tearDown();
|
||||
}
|
||||
|
||||
function test_createVariableNullId() {
|
||||
variableMapTest_setUp();
|
||||
var mockGenUid = setUpMockMethod(Blockly.utils, 'genUid', null, '1');
|
||||
try {
|
||||
variable_map.createVariable('name1', 'type1', null);
|
||||
mockGenUid.$verify();
|
||||
variableMapTest_checkVariableValues('name1', 'type1', '1');
|
||||
}
|
||||
finally {
|
||||
variableMapTest_tearDown();
|
||||
}
|
||||
}
|
||||
|
||||
function test_createVariableUndefinedId() {
|
||||
variableMapTest_setUp();
|
||||
var mockGenUid = setUpMockMethod(Blockly.utils, 'genUid', null, '1');
|
||||
try {
|
||||
variable_map.createVariable('name1', 'type1', undefined);
|
||||
mockGenUid.$verify();
|
||||
variableMapTest_checkVariableValues('name1', 'type1', '1');
|
||||
}
|
||||
finally {
|
||||
variableMapTest_tearDown();
|
||||
}
|
||||
}
|
||||
|
||||
function test_createVariableIdAlreadyExists() {
|
||||
variableMapTest_setUp();
|
||||
variable_map.createVariable('name1', 'type1', 'id1');
|
||||
try {
|
||||
variable_map.createVariable('name2', 'type2', 'id1');
|
||||
fail();
|
||||
} catch (e) {
|
||||
// expected
|
||||
}
|
||||
variableMapTest_tearDown();
|
||||
}
|
||||
|
||||
function test_createVariableMismatchedIdAndType() {
|
||||
variableMapTest_setUp();
|
||||
variable_map.createVariable('name1', 'type1', 'id1');
|
||||
try {
|
||||
variable_map.createVariable('name1', 'type2', 'id1');
|
||||
fail();
|
||||
} catch (e) {
|
||||
// expected
|
||||
}
|
||||
try {
|
||||
variable_map.createVariable('name1', 'type1', 'id2');
|
||||
fail();
|
||||
} catch (e) {
|
||||
// expected
|
||||
}
|
||||
variableMapTest_tearDown();
|
||||
}
|
||||
|
||||
function test_createVariableTwoSameTypes() {
|
||||
variableMapTest_setUp();
|
||||
variable_map.createVariable('name1', 'type1', 'id1');
|
||||
variable_map.createVariable('name2', 'type1', 'id2');
|
||||
|
||||
variableMapTest_checkVariableValues('name1', 'type1', 'id1');
|
||||
variableMapTest_checkVariableValues('name2', 'type1', 'id2');
|
||||
variableMapTest_tearDown();
|
||||
}
|
||||
|
||||
function test_getVariablesOfType_Trivial() {
|
||||
variableMapTest_setUp();
|
||||
var var_1 = variable_map.createVariable('name1', 'type1', 'id1');
|
||||
var var_2 = variable_map.createVariable('name2', 'type1', 'id2');
|
||||
variable_map.createVariable('name3', 'type2', 'id3');
|
||||
variable_map.createVariable('name4', 'type3', 'id4');
|
||||
var result_array_1 = variable_map.getVariablesOfType('type1');
|
||||
var result_array_2 = variable_map.getVariablesOfType('type5');
|
||||
this.isEqualArrays([var_1, var_2], result_array_1);
|
||||
this.isEqualArrays([], result_array_2);
|
||||
variableMapTest_tearDown();
|
||||
}
|
||||
|
||||
function test_getVariablesOfType_Null() {
|
||||
variableMapTest_setUp();
|
||||
var var_1 = variable_map.createVariable('name1', '', 'id1');
|
||||
var var_2 = variable_map.createVariable('name2', '', 'id2');
|
||||
var var_3 = variable_map.createVariable('name3', '', 'id3');
|
||||
variable_map.createVariable('name4', 'type1', 'id4');
|
||||
var result_array = variable_map.getVariablesOfType(null);
|
||||
this.isEqualArrays([var_1, var_2, var_3], result_array);
|
||||
variableMapTest_tearDown();
|
||||
}
|
||||
|
||||
function test_getVariablesOfType_EmptyString() {
|
||||
variableMapTest_setUp();
|
||||
var var_1 = variable_map.createVariable('name1', null, 'id1');
|
||||
var var_2 = variable_map.createVariable('name2', null, 'id2');
|
||||
var result_array = variable_map.getVariablesOfType('');
|
||||
this.isEqualArrays([var_1, var_2], result_array);
|
||||
variableMapTest_tearDown();
|
||||
}
|
||||
|
||||
function test_getVariablesOfType_Deleted() {
|
||||
variableMapTest_setUp();
|
||||
var variable = variable_map.createVariable('name1', null, 'id1');
|
||||
variable_map.deleteVariable(variable);
|
||||
var result_array = variable_map.getVariablesOfType('');
|
||||
this.isEqualArrays([], result_array);
|
||||
variableMapTest_tearDown();
|
||||
}
|
||||
|
||||
function test_getVariablesOfType_DoesNotExist() {
|
||||
variableMapTest_setUp();
|
||||
var result_array = variable_map.getVariablesOfType('type1');
|
||||
this.isEqualArrays([], result_array);
|
||||
variableMapTest_tearDown();
|
||||
}
|
||||
|
||||
function test_getVariableTypes_Trivial() {
|
||||
variableMapTest_setUp();
|
||||
variable_map.createVariable('name1', 'type1', 'id1');
|
||||
variable_map.createVariable('name2', 'type1', 'id2');
|
||||
variable_map.createVariable('name3', 'type2', 'id3');
|
||||
variable_map.createVariable('name4', 'type3', 'id4');
|
||||
var result_array = variable_map.getVariableTypes();
|
||||
this.isEqualArrays(['type1', 'type2', 'type3'], result_array);
|
||||
variableMapTest_tearDown();
|
||||
}
|
||||
|
||||
function test_getVariableTypes_None() {
|
||||
variableMapTest_setUp();
|
||||
var result_array = variable_map.getVariableTypes();
|
||||
this.isEqualArrays([], result_array);
|
||||
variableMapTest_tearDown();
|
||||
}
|
||||
|
||||
function test_getAllVariables_Trivial() {
|
||||
variableMapTest_setUp();
|
||||
var var_1 = variable_map.createVariable('name1', 'type1', 'id1');
|
||||
var var_2 = variable_map.createVariable('name2', 'type1', 'id2');
|
||||
var var_3 = variable_map.createVariable('name3', 'type2', 'id3');
|
||||
var result_array = variable_map.getAllVariables();
|
||||
this.isEqualArrays([var_1, var_2, var_3], result_array);
|
||||
variableMapTest_tearDown();
|
||||
}
|
||||
|
||||
function test_getAllVariables_None() {
|
||||
variableMapTest_setUp();
|
||||
var result_array = variable_map.getAllVariables();
|
||||
this.isEqualArrays([], result_array);
|
||||
variableMapTest_tearDown();
|
||||
}
|
|
@ -19,8 +19,107 @@
|
|||
*/
|
||||
'use strict';
|
||||
|
||||
goog.require('goog.testing');
|
||||
goog.require('goog.testing.MockControl');
|
||||
|
||||
var workspace;
|
||||
var mockControl_;
|
||||
var saved_msg = Blockly.Msg.DELETE_VARIABLE;
|
||||
Blockly.defineBlocksWithJsonArray([{
|
||||
"type": "get_var_block",
|
||||
"message0": "%1",
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_variable",
|
||||
"name": "VAR",
|
||||
}
|
||||
]
|
||||
}]);
|
||||
|
||||
function workspaceTest_setUp() {
|
||||
workspace = new Blockly.Workspace();
|
||||
mockControl_ = new goog.testing.MockControl();
|
||||
}
|
||||
|
||||
function workspaceTest_setUpWithMockBlocks() {
|
||||
workspaceTest_setUp();
|
||||
// Need to define this because field_variable's dropdownCreate() calls replace
|
||||
// on undefined value, Blockly.Msg.DELETE_VARIABLE. To fix this, define
|
||||
// Blockly.Msg.DELETE_VARIABLE as %1 so the replace function finds the %1 it
|
||||
// expects.
|
||||
Blockly.Msg.DELETE_VARIABLE = '%1';
|
||||
}
|
||||
|
||||
function workspaceTest_tearDown() {
|
||||
mockControl_.$tearDown();
|
||||
workspace.dispose();
|
||||
}
|
||||
|
||||
function workspaceTest_tearDownWithMockBlocks() {
|
||||
workspaceTest_tearDown();
|
||||
Blockly.Msg.DELETE_VARIABLE = saved_msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a test get_var_block.
|
||||
* @param {?string} variable The string to put into the variable field.
|
||||
* @return {!Blockly.Block} The created block.
|
||||
*/
|
||||
function createMockBlock(variable) {
|
||||
var block = new Blockly.Block(workspace, 'get_var_block');
|
||||
block.inputList[0].fieldRow[0].setValue(variable);
|
||||
return block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that two arrays have the same content.
|
||||
* @param {!Array.<string>} array1 The first array.
|
||||
* @param {!Array.<string>} array2 The second array.
|
||||
*/
|
||||
function isEqualArrays(array1, array2) {
|
||||
assertEquals(array1.length, array2.length);
|
||||
for (var i = 0; i < array1.length; i++) {
|
||||
assertEquals(array1[i], array2[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a variable with the given values exists.
|
||||
* @param {!string} name The expected name of the variable.
|
||||
* @param {!string} type The expected type of the variable.
|
||||
* @param {!string} id The expected id of the variable.
|
||||
*/
|
||||
function workspaceTest_checkVariableValues(name, type, id) {
|
||||
var variable = workspace.getVariable(name);
|
||||
assertNotUndefined(variable);
|
||||
assertEquals(name, variable.name);
|
||||
assertEquals(type, variable.type);
|
||||
assertEquals(id, variable.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a controlled MethodMock. Set the expected return values. Set the
|
||||
* method to replay.
|
||||
* @param {!Object} scope The scope of the method to be mocked out.
|
||||
* @param {!string} funcName The name of the function we're going to mock.
|
||||
* @param {Object} parameters The parameters to call the mock with.
|
||||
* @param {!Object} return_value The value to return when called.
|
||||
* @return {!goog.testing.MockInterface} The mocked method.
|
||||
*/
|
||||
function setUpMockMethod(scope, funcName, parameters, return_value) {
|
||||
var mockMethod = mockControl_.createMethodMock(scope, funcName);
|
||||
if (parameters) {
|
||||
mockMethod(parameters).$returns(return_value);
|
||||
}
|
||||
else {
|
||||
mockMethod().$returns(return_value);
|
||||
}
|
||||
mockMethod.$replay();
|
||||
return mockMethod;
|
||||
}
|
||||
|
||||
function test_emptyWorkspace() {
|
||||
var workspace = new Blockly.Workspace();
|
||||
workspaceTest_setUp();
|
||||
try {
|
||||
assertEquals('Empty workspace (1).', 0, workspace.getTopBlocks(true).length);
|
||||
assertEquals('Empty workspace (2).', 0, workspace.getTopBlocks(false).length);
|
||||
|
@ -29,20 +128,20 @@ function test_emptyWorkspace() {
|
|||
assertEquals('Empty workspace (4).', 0, workspace.getTopBlocks(true).length);
|
||||
assertEquals('Empty workspace (5).', 0, workspace.getTopBlocks(false).length);
|
||||
assertEquals('Empty workspace (6).', 0, workspace.getAllBlocks().length);
|
||||
} finally {
|
||||
workspace.dispose();
|
||||
}
|
||||
finally {
|
||||
workspaceTest_tearDown();
|
||||
}
|
||||
}
|
||||
|
||||
function test_flatWorkspace() {
|
||||
var workspace = new Blockly.Workspace();
|
||||
var blockA, blockB;
|
||||
workspaceTest_setUp();
|
||||
try {
|
||||
blockA = workspace.newBlock('');
|
||||
var blockA = workspace.newBlock('');
|
||||
assertEquals('One block workspace (1).', 1, workspace.getTopBlocks(true).length);
|
||||
assertEquals('One block workspace (2).', 1, workspace.getTopBlocks(false).length);
|
||||
assertEquals('One block workspace (3).', 1, workspace.getAllBlocks().length);
|
||||
blockB = workspace.newBlock('');
|
||||
var blockB = workspace.newBlock('');
|
||||
assertEquals('Two block workspace (1).', 2, workspace.getTopBlocks(true).length);
|
||||
assertEquals('Two block workspace (2).', 2, workspace.getTopBlocks(false).length);
|
||||
assertEquals('Two block workspace (3).', 2, workspace.getAllBlocks().length);
|
||||
|
@ -55,17 +154,15 @@ function test_flatWorkspace() {
|
|||
assertEquals('Cleared workspace (2).', 0, workspace.getTopBlocks(false).length);
|
||||
assertEquals('Cleared workspace (3).', 0, workspace.getAllBlocks().length);
|
||||
} finally {
|
||||
blockB && blockB.dispose();
|
||||
blockA && blockA.dispose();
|
||||
workspace.dispose();
|
||||
workspaceTest_tearDown();
|
||||
}
|
||||
}
|
||||
|
||||
function test_maxBlocksWorkspace() {
|
||||
var workspace = new Blockly.Workspace();
|
||||
var blockA = workspace.newBlock('');
|
||||
var blockB = workspace.newBlock('');
|
||||
workspaceTest_setUp();
|
||||
try {
|
||||
var blockA = workspace.newBlock('');
|
||||
var blockB = workspace.newBlock('');
|
||||
assertEquals('Infinite capacity.', Infinity, workspace.remainingCapacity());
|
||||
workspace.options.maxBlocks = 3;
|
||||
assertEquals('Three capacity.', 1, workspace.remainingCapacity());
|
||||
|
@ -78,9 +175,7 @@ function test_maxBlocksWorkspace() {
|
|||
workspace.clear();
|
||||
assertEquals('Cleared capacity.', 0, workspace.remainingCapacity());
|
||||
} finally {
|
||||
blockB.dispose();
|
||||
blockA.dispose();
|
||||
workspace.dispose();
|
||||
workspaceTest_tearDown();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -106,10 +201,10 @@ function test_getWorkspaceById() {
|
|||
}
|
||||
|
||||
function test_getBlockById() {
|
||||
var workspace = new Blockly.Workspace();
|
||||
var blockA = workspace.newBlock('');
|
||||
var blockB = workspace.newBlock('');
|
||||
workspaceTest_setUp();
|
||||
try {
|
||||
var blockA = workspace.newBlock('');
|
||||
var blockB = workspace.newBlock('');
|
||||
assertEquals('Find blockA.', blockA, workspace.getBlockById(blockA.id));
|
||||
assertEquals('Find blockB.', blockB, workspace.getBlockById(blockB.id));
|
||||
assertEquals('No block found.', null,
|
||||
|
@ -120,8 +215,351 @@ function test_getBlockById() {
|
|||
workspace.clear();
|
||||
assertEquals('Can\'t find blockB.', null, workspace.getBlockById(blockB.id));
|
||||
} finally {
|
||||
blockB.dispose();
|
||||
blockA.dispose();
|
||||
workspace.dispose();
|
||||
workspaceTest_tearDown();
|
||||
}
|
||||
}
|
||||
|
||||
function test_deleteVariable_InternalTrivial() {
|
||||
workspaceTest_setUpWithMockBlocks()
|
||||
var var_1 = workspace.createVariable('name1', 'type1', 'id1');
|
||||
workspace.createVariable('name2', 'type2', 'id2');
|
||||
createMockBlock('name1');
|
||||
createMockBlock('name1');
|
||||
createMockBlock('name2');
|
||||
|
||||
workspace.deleteVariableInternal_(var_1);
|
||||
var variable = workspace.getVariable('name1');
|
||||
var block_var_name = workspace.topBlocks_[0].getVars()[0];
|
||||
assertNull(variable);
|
||||
workspaceTest_checkVariableValues('name2', 'type2', 'id2');
|
||||
assertEquals('name2', block_var_name);
|
||||
workspaceTest_tearDownWithMockBlocks();
|
||||
}
|
||||
|
||||
// TODO(marisaleung): Test the alert for deleting a variable that is a procedure.
|
||||
|
||||
function test_updateVariableStore_TrivialNoClear() {
|
||||
workspaceTest_setUp();
|
||||
workspace.createVariable('name1', 'type1', 'id1');
|
||||
workspace.createVariable('name2', 'type2', 'id2');
|
||||
var mockAllUsedVariables = setUpMockMethod(Blockly.Variables,
|
||||
'allUsedVariables', workspace, ['name1', 'name2']);
|
||||
|
||||
try {
|
||||
workspace.updateVariableStore();
|
||||
mockAllUsedVariables.$verify();
|
||||
workspaceTest_checkVariableValues('name1', 'type1', 'id1');
|
||||
workspaceTest_checkVariableValues('name2', 'type2', 'id2');
|
||||
}
|
||||
finally {
|
||||
workspaceTest_tearDown();
|
||||
}
|
||||
}
|
||||
|
||||
function test_updateVariableStore_NameNotInvariableMap_NoClear() {
|
||||
workspaceTest_setUp();
|
||||
setUpMockMethod(Blockly.Variables, 'allUsedVariables', workspace, ['name1']);
|
||||
setUpMockMethod(Blockly.utils, 'genUid', null, '1');
|
||||
|
||||
try {
|
||||
workspace.updateVariableStore();
|
||||
mockControl_.$verifyAll();
|
||||
workspaceTest_checkVariableValues('name1', '', '1');
|
||||
}
|
||||
finally {
|
||||
workspaceTest_tearDown();
|
||||
}
|
||||
}
|
||||
|
||||
function test_updateVariableStore_ClearAndAllInUse() {
|
||||
workspaceTest_setUp();
|
||||
workspace.createVariable('name1', 'type1', 'id1');
|
||||
workspace.createVariable('name2', 'type2', 'id2');
|
||||
var mockAllUsedVariables = setUpMockMethod(Blockly.Variables,
|
||||
'allUsedVariables', workspace, ['name1', 'name2']);
|
||||
|
||||
try {
|
||||
workspace.updateVariableStore(true);
|
||||
mockAllUsedVariables.$verify();
|
||||
workspaceTest_checkVariableValues('name1', 'type1', 'id1');
|
||||
workspaceTest_checkVariableValues('name2', 'type2', 'id2');
|
||||
}
|
||||
finally {
|
||||
workspaceTest_tearDown();
|
||||
}
|
||||
}
|
||||
|
||||
function test_updateVariableStore_ClearAndOneInUse() {
|
||||
workspaceTest_setUp();
|
||||
workspace.createVariable('name1', 'type1', 'id1');
|
||||
workspace.createVariable('name2', 'type2', 'id2');
|
||||
var mockAllUsedVariables = setUpMockMethod(Blockly.Variables,
|
||||
'allUsedVariables', workspace, ['name1']);
|
||||
|
||||
try {
|
||||
workspace.updateVariableStore(true);
|
||||
mockAllUsedVariables.$verify();
|
||||
workspaceTest_checkVariableValues('name1', 'type1', 'id1');
|
||||
var variabe = workspace.getVariable('name2');
|
||||
assertNull(variable);
|
||||
}
|
||||
finally {
|
||||
workspaceTest_tearDown();
|
||||
}
|
||||
}
|
||||
|
||||
function test_addTopBlock_TrivialFlyoutIsTrue() {
|
||||
workspaceTest_setUpWithMockBlocks()
|
||||
workspace.isFlyout = true;
|
||||
var block = createMockBlock();
|
||||
workspace.removeTopBlock(block);
|
||||
setUpMockMethod(Blockly.Variables, 'allUsedVariables', block, ['name1']);
|
||||
setUpMockMethod(Blockly.utils, 'genUid', null, '1');
|
||||
|
||||
try {
|
||||
workspace.addTopBlock(block);
|
||||
mockControl_.$verifyAll();
|
||||
workspaceTest_checkVariableValues('name1', '', '1');
|
||||
}
|
||||
finally {
|
||||
workspaceTest_tearDownWithMockBlocks();
|
||||
}
|
||||
}
|
||||
|
||||
function test_clear_Trivial() {
|
||||
workspaceTest_setUp();
|
||||
workspace.createVariable('name1', 'type1', 'id1');
|
||||
workspace.createVariable('name2', 'type2', 'id2');
|
||||
var mockSetGroup = mockControl_.createMethodMock(Blockly.Events, 'setGroup');
|
||||
mockSetGroup(true);
|
||||
mockSetGroup(false);
|
||||
mockSetGroup.$replay();
|
||||
|
||||
try {
|
||||
workspace.clear();
|
||||
mockControl_.$verifyAll();
|
||||
var topBlocks_length = workspace.topBlocks_.length;
|
||||
var varMapLength = Object.keys(workspace.variableMap_.variableMap_).length;
|
||||
assertEquals(0, topBlocks_length);
|
||||
assertEquals(0, varMapLength);
|
||||
}
|
||||
finally {
|
||||
workspaceTest_tearDown();
|
||||
}
|
||||
}
|
||||
|
||||
function test_clear_NoVariables() {
|
||||
workspaceTest_setUp();
|
||||
var mockSetGroup = mockControl_.createMethodMock(Blockly.Events, 'setGroup');
|
||||
mockSetGroup(true);
|
||||
mockSetGroup(false);
|
||||
mockSetGroup.$replay();
|
||||
|
||||
try {
|
||||
workspace.clear();
|
||||
mockSetGroup.$verify();
|
||||
var topBlocks_length = workspace.topBlocks_.length;
|
||||
var varMapLength = Object.keys(workspace.variableMap_.variableMap_).length;
|
||||
assertEquals(0, topBlocks_length);
|
||||
assertEquals(0, varMapLength);
|
||||
}
|
||||
finally {
|
||||
workspaceTest_tearDown();
|
||||
}
|
||||
}
|
||||
|
||||
function test_renameVariable_NoBlocks() {
|
||||
// Expect 'renameVariable' to create new variable with newName.
|
||||
workspaceTest_setUp();
|
||||
var oldName = 'name1';
|
||||
var newName = 'name2';
|
||||
var mockSetGroup = mockControl_.createMethodMock(Blockly.Events, 'setGroup');
|
||||
var mockGenUid = mockControl_.createMethodMock(Blockly.utils, 'genUid');
|
||||
// Mocked setGroup to ensure only one call to the mocked genUid.
|
||||
mockSetGroup(true);
|
||||
mockSetGroup(false);
|
||||
mockGenUid().$returns('1');
|
||||
mockControl_.$replayAll();
|
||||
|
||||
try {
|
||||
workspace.renameVariable(oldName, newName);
|
||||
mockControl_.$verifyAll();
|
||||
workspaceTest_checkVariableValues('name2', '', '1');
|
||||
var variable = workspace.getVariable(oldName);
|
||||
assertNull(variable);
|
||||
}
|
||||
finally {
|
||||
workspaceTest_tearDown();
|
||||
}
|
||||
}
|
||||
|
||||
function test_renameVariable_SameNameNoBlocks() {
|
||||
// Expect 'renameVariable' to create new variable with newName.
|
||||
workspaceTest_setUpWithMockBlocks()
|
||||
var name = 'name1';
|
||||
workspace.createVariable(name, 'type1', 'id1');
|
||||
|
||||
workspace.renameVariable(name, name);
|
||||
workspaceTest_checkVariableValues(name, 'type1', 'id1');
|
||||
workspaceTest_tearDownWithMockBlocks();
|
||||
}
|
||||
|
||||
function test_renameVariable_OnlyOldNameBlockExists() {
|
||||
// Expect 'renameVariable' to change oldName variable name to newName.
|
||||
workspaceTest_setUpWithMockBlocks()
|
||||
var oldName = 'name1';
|
||||
var newName = 'name2';
|
||||
workspace.createVariable(oldName, 'type1', 'id1');
|
||||
createMockBlock(oldName);
|
||||
|
||||
workspace.renameVariable(oldName, newName);
|
||||
workspaceTest_checkVariableValues(newName, 'type1', 'id1');
|
||||
var variable = workspace.getVariable(oldName);
|
||||
var block_var_name = workspace.topBlocks_[0].getVars()[0];
|
||||
assertNull(variable);
|
||||
assertEquals(newName, block_var_name);
|
||||
workspaceTest_tearDownWithMockBlocks();
|
||||
}
|
||||
|
||||
function test_renameVariable_TwoVariablesSameType() {
|
||||
// Expect 'renameVariable' to change oldName variable name to newName.
|
||||
// Expect oldName block name to change to newName
|
||||
workspaceTest_setUpWithMockBlocks()
|
||||
var oldName = 'name1';
|
||||
var newName = 'name2';
|
||||
workspace.createVariable(oldName, 'type1', 'id1');
|
||||
workspace.createVariable(newName, 'type1', 'id2');
|
||||
createMockBlock(oldName);
|
||||
createMockBlock(newName);
|
||||
|
||||
workspace.renameVariable(oldName, newName);
|
||||
workspaceTest_checkVariableValues(newName, 'type1', 'id2');
|
||||
var variable = workspace.getVariable(oldName);
|
||||
var block_var_name_1 = workspace.topBlocks_[0].getVars()[0];
|
||||
var block_var_name_2 = workspace.topBlocks_[1].getVars()[0];
|
||||
assertNull(variable);
|
||||
assertEquals(newName, block_var_name_1);
|
||||
assertEquals(newName, block_var_name_2);
|
||||
workspaceTest_tearDownWithMockBlocks();
|
||||
}
|
||||
|
||||
function test_renameVariable_TwoVariablesDifferentType() {
|
||||
// Expect triggered error because of different types
|
||||
workspaceTest_setUpWithMockBlocks()
|
||||
var oldName = 'name1';
|
||||
var newName = 'name2';
|
||||
workspace.createVariable(oldName, 'type1', 'id1');
|
||||
workspace.createVariable(newName, 'type2', 'id2');
|
||||
createMockBlock(oldName);
|
||||
createMockBlock(newName);
|
||||
|
||||
try {
|
||||
workspace.renameVariable(oldName, newName);
|
||||
fail();
|
||||
} catch (e) {
|
||||
// expected
|
||||
}
|
||||
workspaceTest_checkVariableValues(oldName, 'type1', 'id1');
|
||||
workspaceTest_checkVariableValues(newName, 'type2', 'id2');
|
||||
var block_var_name_1 = workspace.topBlocks_[0].getVars()[0];
|
||||
var block_var_name_2 = workspace.topBlocks_[1].getVars()[0];
|
||||
assertEquals(oldName, block_var_name_1);
|
||||
assertEquals(newName, block_var_name_2);
|
||||
workspaceTest_tearDownWithMockBlocks();
|
||||
}
|
||||
|
||||
function test_renameVariable_OldCase() {
|
||||
// Expect triggered error because of different types
|
||||
workspaceTest_setUpWithMockBlocks();
|
||||
var oldCase = 'Name1';
|
||||
var newName = 'name1';
|
||||
workspace.createVariable(oldCase, 'type1', 'id1');
|
||||
createMockBlock(oldCase);
|
||||
|
||||
workspace.renameVariable(oldCase, newName);
|
||||
workspaceTest_checkVariableValues(newName, 'type1', 'id1');
|
||||
var result_oldCase = workspace.getVariable(oldCase).name
|
||||
assertNotEquals(oldCase, result_oldCase);
|
||||
workspaceTest_tearDownWithMockBlocks();
|
||||
}
|
||||
|
||||
function test_renameVariable_TwoVariablesAndOldCase() {
|
||||
// Expect triggered error because of different types
|
||||
workspaceTest_setUpWithMockBlocks()
|
||||
var oldName = 'name1';
|
||||
var oldCase = 'Name2';
|
||||
var newName = 'name2';
|
||||
workspace.createVariable(oldName, 'type1', 'id1');
|
||||
workspace.createVariable(oldCase, 'type1', 'id2');
|
||||
createMockBlock(oldName);
|
||||
createMockBlock(oldCase);
|
||||
|
||||
workspace.renameVariable(oldName, newName);
|
||||
|
||||
workspaceTest_checkVariableValues(newName, 'type1', 'id2');
|
||||
var variable = workspace.getVariable(oldName);
|
||||
var result_oldCase = workspace.getVariable(oldCase).name;
|
||||
var block_var_name_1 = workspace.topBlocks_[0].getVars()[0];
|
||||
var block_var_name_2 = workspace.topBlocks_[1].getVars()[0];
|
||||
assertNull(variable);
|
||||
assertNotEquals(oldCase, result_oldCase);
|
||||
assertEquals(newName, block_var_name_1);
|
||||
assertEquals(newName, block_var_name_2);
|
||||
workspaceTest_tearDownWithMockBlocks();
|
||||
}
|
||||
|
||||
// Extra testing not required for renameVariableById. It calls renameVariable
|
||||
// and that has extensive testing.
|
||||
function test_renameVariableById_TwoVariablesSameType() {
|
||||
// Expect 'renameVariableById' to change oldName variable name to newName.
|
||||
// Expect oldName block name to change to newName
|
||||
workspaceTest_setUpWithMockBlocks()
|
||||
var oldName = 'name1';
|
||||
var newName = 'name2';
|
||||
workspace.createVariable(oldName, 'type1', 'id1');
|
||||
workspace.createVariable(newName, 'type1', 'id2');
|
||||
createMockBlock(oldName);
|
||||
createMockBlock(newName);
|
||||
|
||||
workspace.renameVariableById('id1', newName);
|
||||
workspaceTest_checkVariableValues(newName, 'type1', 'id2');
|
||||
var variable = workspace.getVariable(oldName)
|
||||
var block_var_name_1 = workspace.topBlocks_[0].getVars()[0];
|
||||
var block_var_name_2 = workspace.topBlocks_[1].getVars()[0];
|
||||
assertNull(variable);
|
||||
assertEquals(newName, block_var_name_1);
|
||||
assertEquals(newName, block_var_name_2);
|
||||
workspaceTest_tearDownWithMockBlocks();
|
||||
}
|
||||
|
||||
function test_deleteVariable_Trivial() {
|
||||
workspaceTest_setUpWithMockBlocks()
|
||||
workspace.createVariable('name1', 'type1', 'id1');
|
||||
workspace.createVariable('name2', 'type1', 'id2');
|
||||
createMockBlock('name1');
|
||||
createMockBlock('name2');
|
||||
|
||||
workspace.deleteVariable('name1');
|
||||
workspaceTest_checkVariableValues('name2', 'type1', 'id2');
|
||||
var variable = workspace.getVariable('name1');
|
||||
var block_var_name = workspace.topBlocks_[0].getVars()[0];
|
||||
assertNull(variable);
|
||||
assertEquals('name2', block_var_name);
|
||||
workspaceTest_tearDownWithMockBlocks();
|
||||
}
|
||||
|
||||
function test_deleteVariableById_Trivial() {
|
||||
workspaceTest_setUpWithMockBlocks()
|
||||
workspace.createVariable('name1', 'type1', 'id1');
|
||||
workspace.createVariable('name2', 'type1', 'id2');
|
||||
createMockBlock('name1');
|
||||
createMockBlock('name2');
|
||||
|
||||
workspace.deleteVariableById('id1');
|
||||
workspaceTest_checkVariableValues('name2', 'type1', 'id2');
|
||||
var variable = workspace.getVariable('name1');
|
||||
var block_var_name = workspace.topBlocks_[0].getVars()[0];
|
||||
assertNull(variable);
|
||||
assertEquals('name2', block_var_name);
|
||||
workspaceTest_tearDownWithMockBlocks();
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue