mirror of
https://github.com/google/blockly.git
synced 2026-01-07 17:10:11 +01:00
Merge pull request #1987 from rachel-fenichel/feature/insertion_markers
Insertion markers in Blockly!
This commit is contained in:
@@ -37,7 +37,7 @@ this.BLOCKLY_BOOT = function(root) {
|
||||
goog.addDependency("../../../" + dir + "/core/block.js", ['Blockly.Block'], ['Blockly.Blocks', 'Blockly.Comment', 'Blockly.Connection', 'Blockly.Events.BlockChange', 'Blockly.Events.BlockCreate', 'Blockly.Events.BlockDelete', 'Blockly.Events.BlockMove', 'Blockly.Extensions', 'Blockly.Input', 'Blockly.Mutator', 'Blockly.utils', 'Blockly.Warning', 'Blockly.Workspace', 'goog.math.Coordinate']);
|
||||
goog.addDependency("../../../" + dir + "/core/block_animations.js", ['Blockly.BlockAnimations'], ['Blockly.utils']);
|
||||
goog.addDependency("../../../" + dir + "/core/block_drag_surface.js", ['Blockly.BlockDragSurfaceSvg'], ['Blockly.utils', 'goog.math.Coordinate']);
|
||||
goog.addDependency("../../../" + dir + "/core/block_dragger.js", ['Blockly.BlockDragger'], ['Blockly.BlockAnimations', 'Blockly.DraggedConnectionManager', 'Blockly.Events.BlockMove', 'goog.math.Coordinate']);
|
||||
goog.addDependency("../../../" + dir + "/core/block_dragger.js", ['Blockly.BlockDragger'], ['Blockly.BlockAnimations', 'Blockly.InsertionMarkerManager', 'Blockly.Events.BlockMove', 'goog.math.Coordinate']);
|
||||
goog.addDependency("../../../" + dir + "/core/block_events.js", ['Blockly.Events.BlockBase', 'Blockly.Events.BlockChange', 'Blockly.Events.BlockCreate', 'Blockly.Events.BlockDelete', 'Blockly.Events.BlockMove', 'Blockly.Events.Change', 'Blockly.Events.Create', 'Blockly.Events.Delete', 'Blockly.Events.Move'], ['Blockly.Events', 'Blockly.Events.Abstract', 'Blockly.Xml.utils', 'goog.math.Coordinate']);
|
||||
goog.addDependency("../../../" + dir + "/core/block_render_svg.js", ['Blockly.BlockSvg.render'], ['Blockly.BlockSvg']);
|
||||
goog.addDependency("../../../" + dir + "/core/block_svg.js", ['Blockly.BlockSvg'], ['Blockly.Block', 'Blockly.BlockAnimations', 'Blockly.ContextMenu', 'Blockly.Events.Ui', 'Blockly.Events.BlockMove', 'Blockly.Grid', 'Blockly.RenderedConnection', 'Blockly.Tooltip', 'Blockly.Touch', 'Blockly.utils', 'goog.color', 'goog.math.Coordinate']);
|
||||
@@ -77,6 +77,7 @@ goog.addDependency("../../../" + dir + "/core/grid.js", ['Blockly.Grid'], ['Bloc
|
||||
goog.addDependency("../../../" + dir + "/core/icon.js", ['Blockly.Icon'], ['Blockly.utils', 'goog.math.Coordinate']);
|
||||
goog.addDependency("../../../" + dir + "/core/inject.js", ['Blockly.inject'], ['Blockly.BlockDragSurfaceSvg', 'Blockly.Css', 'Blockly.Grid', 'Blockly.Options', 'Blockly.utils', 'Blockly.WorkspaceSvg', 'Blockly.WorkspaceDragSurfaceSvg', 'goog.ui.Component', 'goog.userAgent']);
|
||||
goog.addDependency("../../../" + dir + "/core/input.js", ['Blockly.Input'], ['Blockly.Connection', 'Blockly.FieldLabel']);
|
||||
goog.addDependency("../../../" + dir + "/core/insertion_marker_manager.js", ['Blockly.InsertionMarkerManager'], ['Blockly.BlockAnimations', 'Blockly.Events.BlockMove', 'Blockly.RenderedConnection', 'goog.math.Coordinate']);
|
||||
goog.addDependency("../../../" + dir + "/core/msg.js", ['Blockly.Msg'], []);
|
||||
goog.addDependency("../../../" + dir + "/core/mutator.js", ['Blockly.Mutator'], ['Blockly.Bubble', 'Blockly.Events.BlockChange', 'Blockly.Events.Ui', 'Blockly.Icon', 'Blockly.utils', 'Blockly.WorkspaceSvg', 'Blockly.Xml', 'Blockly.Xml.utils']);
|
||||
goog.addDependency("../../../" + dir + "/core/names.js", ['Blockly.Names'], []);
|
||||
@@ -1786,6 +1787,7 @@ goog.require('Blockly.Grid');
|
||||
goog.require('Blockly.HorizontalFlyout');
|
||||
goog.require('Blockly.Icon');
|
||||
goog.require('Blockly.Input');
|
||||
goog.require('Blockly.InsertionMarkerManager');
|
||||
goog.require('Blockly.Msg');
|
||||
goog.require('Blockly.Mutator');
|
||||
goog.require('Blockly.Names');
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
goog.provide('Blockly.BlockDragger');
|
||||
|
||||
goog.require('Blockly.BlockAnimations');
|
||||
goog.require('Blockly.DraggedConnectionManager');
|
||||
goog.require('Blockly.InsertionMarkerManager');
|
||||
goog.require('Blockly.Events.BlockMove');
|
||||
|
||||
goog.require('goog.math.Coordinate');
|
||||
@@ -57,10 +57,10 @@ Blockly.BlockDragger = function(block, workspace) {
|
||||
|
||||
/**
|
||||
* Object that keeps track of connections on dragged blocks.
|
||||
* @type {!Blockly.DraggedConnectionManager}
|
||||
* @type {!Blockly.InsertionMarkerManager}
|
||||
* @private
|
||||
*/
|
||||
this.draggedConnectionManager_ = new Blockly.DraggedConnectionManager(
|
||||
this.draggedConnectionManager_ = new Blockly.InsertionMarkerManager(
|
||||
this.draggingBlock_);
|
||||
|
||||
/**
|
||||
@@ -162,6 +162,9 @@ Blockly.BlockDragger.prototype.startBlockDrag = function(currentDragDeltaXY,
|
||||
this.draggingBlock_.bringToFront();
|
||||
}
|
||||
|
||||
// During a drag there may be a lot of rerenders, but not field changes.
|
||||
// Turn the cache on so we don't do spurious remeasures during the drag.
|
||||
Blockly.Field.startCache();
|
||||
this.workspace_.setResizesEnabled(false);
|
||||
Blockly.BlockAnimations.disconnectUiStop();
|
||||
|
||||
@@ -222,6 +225,8 @@ Blockly.BlockDragger.prototype.endBlockDrag = function(e, currentDragDeltaXY) {
|
||||
this.dragBlock(e, currentDragDeltaXY);
|
||||
this.dragIconData_ = [];
|
||||
|
||||
Blockly.Field.stopCache();
|
||||
|
||||
Blockly.BlockAnimations.disconnectUiStop();
|
||||
|
||||
var delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY);
|
||||
|
||||
@@ -385,6 +385,11 @@ Blockly.BlockSvg.prototype.renderFields_ = function(fieldList,
|
||||
Blockly.BlockSvg.SEP_SPACE_X;
|
||||
}
|
||||
}
|
||||
// Fields are invisible on insertion marker. They still have to be rendered
|
||||
// so that the block can be sized correctly.
|
||||
if (this.isInsertionMarker()) {
|
||||
root.setAttribute('display', 'none');
|
||||
}
|
||||
}
|
||||
return this.RTL ? -cursorX : cursorX;
|
||||
};
|
||||
@@ -1144,3 +1149,43 @@ Blockly.BlockSvg.prototype.renderStatementInput_ = function(pathObject, row,
|
||||
cursor.y += Blockly.BlockSvg.SEP_SPACE_Y;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Position an new block correctly, so that it doesn't move the existing block
|
||||
* when connected to it.
|
||||
* @param {!Blockly.Block} newBlock The block to position - either the first
|
||||
* block in a dragged stack or an insertion marker.
|
||||
* @param {!Blockly.Connection} newConnection The connection on the new block's
|
||||
* stack - either a connection on newBlock, or the last NEXT_STATEMENT
|
||||
* connection on the stack if the stack's being dropped before another
|
||||
* block.
|
||||
* @param {!Blockly.Connection} existingConnection The connection on the
|
||||
* existing block, which newBlock should line up with.
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.positionNewBlock = function(newBlock, newConnection,
|
||||
existingConnection) {
|
||||
// We only need to position the new block if it's before the existing one,
|
||||
// otherwise its position is set by the previous block.
|
||||
if (newConnection.type == Blockly.NEXT_STATEMENT ||
|
||||
newConnection.type == Blockly.INPUT_VALUE) {
|
||||
var dx = existingConnection.x_ - newConnection.x_;
|
||||
var dy = existingConnection.y_ - newConnection.y_;
|
||||
|
||||
newBlock.moveBy(dx, dy);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Visual effect to show that if the dragging block is dropped, this block will
|
||||
* be replaced. If a shadow block, it will disappear. Otherwise it will bump.
|
||||
* @param {boolean} add True if highlighting should be added.
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.highlightForReplacement = function(add) {
|
||||
if (add) {
|
||||
Blockly.utils.addClass(/** @type {!Element} */ (this.svgGroup_),
|
||||
'blocklyReplaceable');
|
||||
} else {
|
||||
Blockly.utils.removeClass(/** @type {!Element} */ (this.svgGroup_),
|
||||
'blocklyReplaceable');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -334,12 +334,67 @@ Blockly.Connection.prototype.checkConnection_ = function(target) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the two connections can be dragged to connect to each other.
|
||||
* This is used by the connection database when searching for the closest
|
||||
* connection.
|
||||
* @param {!Blockly.Connection} candidate A nearby connection to check, which
|
||||
* must be a previous connection.
|
||||
* @return {boolean} True if the connection is allowed, false otherwise.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Connection.prototype.canConnectToPrevious_ = function(candidate) {
|
||||
if (this.targetConnection) {
|
||||
// This connection is already occupied.
|
||||
// A next connection will never disconnect itself mid-drag.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't let blocks try to connect to themselves or ones they nest.
|
||||
if (Blockly.draggingConnections_.indexOf(candidate) != -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var firstStatementConnection =
|
||||
this.sourceBlock_.getFirstStatementConnection();
|
||||
var isFirstStatementConnection = this == firstStatementConnection;
|
||||
var isNextConnection = this == this.sourceBlock_.nextConnection;
|
||||
|
||||
// Complex blocks with no previous connection will not be allowed to connect
|
||||
// mid-stack.
|
||||
var sourceHasPreviousConn = this.sourceBlock_.previousConnection != null;
|
||||
|
||||
if (isNextConnection ||
|
||||
(isFirstStatementConnection && !sourceHasPreviousConn)) {
|
||||
// If the candidate is the first connection in a stack, we can connect.
|
||||
if (!candidate.targetConnection) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var targetBlock = candidate.targetBlock();
|
||||
// If it is connected to a real block, game over.
|
||||
if (!targetBlock.isInsertionMarker()) {
|
||||
return false;
|
||||
}
|
||||
// If it's connected to an insertion marker but that insertion marker
|
||||
// is the first block in a stack, it's still fine. If that insertion
|
||||
// marker is in the middle of a stack, it won't work.
|
||||
return !targetBlock.getPreviousBlock();
|
||||
}
|
||||
console.warn('Returning false by default from canConnectToPrevious_.');
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the two connections can be dragged to connect to each other.
|
||||
* @param {!Blockly.Connection} candidate A nearby connection to check.
|
||||
* @return {boolean} True if the connection is allowed, false otherwise.
|
||||
*/
|
||||
Blockly.Connection.prototype.isConnectionAllowed = function(candidate) {
|
||||
// Don't consider insertion markers.
|
||||
if (candidate.sourceBlock_.isInsertionMarker()) {
|
||||
return false;
|
||||
}
|
||||
// Type checking.
|
||||
var canConnect = this.canConnectWithReason_(candidate);
|
||||
if (canConnect != Blockly.Connection.CAN_CONNECT) {
|
||||
@@ -348,30 +403,27 @@ Blockly.Connection.prototype.isConnectionAllowed = function(candidate) {
|
||||
|
||||
switch (candidate.type) {
|
||||
case Blockly.PREVIOUS_STATEMENT:
|
||||
// Don't offer to connect the bottom of a statement block to one that's
|
||||
// already connected.
|
||||
if (candidate.isConnected() || this.isConnected()) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case Blockly.OUTPUT_VALUE:
|
||||
return this.canConnectToPrevious_(candidate);
|
||||
case Blockly.OUTPUT_VALUE: {
|
||||
// Don't offer to connect an already connected left (male) value plug to
|
||||
// an available right (female) value plug.
|
||||
if (candidate.isConnected() || this.isConnected()) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case Blockly.INPUT_VALUE:
|
||||
}
|
||||
case Blockly.INPUT_VALUE: {
|
||||
// Offering to connect the left (male) of a value block to an already
|
||||
// connected value pair is ok, we'll splice it in.
|
||||
// However, don't offer to splice into an unmovable block.
|
||||
if (candidate.targetConnection &&
|
||||
// However, don't offer to splice into an immovable block.
|
||||
if (candidate.isConnected() &&
|
||||
!candidate.targetBlock().isMovable() &&
|
||||
!candidate.targetBlock().isShadow()) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case Blockly.NEXT_STATEMENT:
|
||||
}
|
||||
case Blockly.NEXT_STATEMENT: {
|
||||
// Don't let a block with no next connection bump other blocks out of the
|
||||
// stack. But covering up a shadow block or stack of shadow blocks is
|
||||
// fine. Similarly, replacing a terminal statement with another terminal
|
||||
@@ -383,6 +435,7 @@ Blockly.Connection.prototype.isConnectionAllowed = function(candidate) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw Error('Unknown connection type in isConnectionAllowed');
|
||||
}
|
||||
|
||||
@@ -42,7 +42,26 @@ Blockly.FLYOUT_DRAG_RADIUS = 10;
|
||||
/**
|
||||
* Maximum misalignment between connections for them to snap together.
|
||||
*/
|
||||
Blockly.SNAP_RADIUS = 20;
|
||||
Blockly.SNAP_RADIUS = 36;
|
||||
|
||||
/**
|
||||
* Maximum misalignment between connections for them to snap together,
|
||||
* when a connection is already highlighted.
|
||||
*/
|
||||
Blockly.CONNECTING_SNAP_RADIUS = 48;
|
||||
|
||||
/**
|
||||
* How much to prefer staying connected to the current connection over moving to
|
||||
* a new connection. The current previewed connection is considered to be this
|
||||
* much closer to the matching connection on the block than it actually is.
|
||||
*/
|
||||
Blockly.CURRENT_CONNECTION_PREFERENCE = 0;
|
||||
|
||||
/**
|
||||
* The main colour of insertion markers, in hex. The block is rendered a
|
||||
* transparent grey by changing the fill opacity in CSS.
|
||||
*/
|
||||
Blockly.INSERTION_MARKER_COLOUR = '#000000';
|
||||
|
||||
/**
|
||||
* Delay in ms between trigger and bumping unconnected block out of alignment.
|
||||
|
||||
16
core/css.js
16
core/css.js
@@ -284,6 +284,22 @@ Blockly.Css.CONTENT = [
|
||||
'display: none;',
|
||||
'}',
|
||||
|
||||
'.blocklyInsertionMarker>.blocklyPath,',
|
||||
'.blocklyInsertionMarker>.blocklyPathLight,',
|
||||
'.blocklyInsertionMarker>.blocklyPathDark {',
|
||||
'fill-opacity: .2;',
|
||||
'stroke: none',
|
||||
'}',
|
||||
|
||||
'.blocklyReplaceable .blocklyPath {',
|
||||
'fill-opacity: 0.5;',
|
||||
'}',
|
||||
|
||||
'.blocklyReplaceable .blocklyPathLight,',
|
||||
'.blocklyReplaceable .blocklyPathDark {',
|
||||
'display: none;',
|
||||
'}',
|
||||
|
||||
'.blocklyText {',
|
||||
'cursor: default;',
|
||||
'fill: #fff;',
|
||||
|
||||
@@ -36,6 +36,7 @@ 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.
|
||||
* @deprecated July 2018. Use InsertionMarkerManager.
|
||||
* @param {!Blockly.BlockSvg} block The top block in the stack being dragged.
|
||||
* @constructor
|
||||
*/
|
||||
|
||||
@@ -147,7 +147,8 @@ Blockly.Icon.prototype.updateColour = function() {
|
||||
* @return {number} Horizontal offset for next item to draw.
|
||||
*/
|
||||
Blockly.Icon.prototype.renderIcon = function(cursorX) {
|
||||
if (this.collapseHidden && this.block_.isCollapsed()) {
|
||||
if ((this.collapseHidden && this.block_.isCollapsed()) ||
|
||||
this.block_.isInsertionMarker()) {
|
||||
this.iconGroup_.setAttribute('display', 'none');
|
||||
return cursorX;
|
||||
}
|
||||
|
||||
697
core/insertion_marker_manager.js
Normal file
697
core/insertion_marker_manager.js
Normal file
@@ -0,0 +1,697 @@
|
||||
/**
|
||||
* @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.BlockAnimations');
|
||||
goog.require('Blockly.Events.BlockMove');
|
||||
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.
|
||||
* @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.
|
||||
* @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;
|
||||
|
||||
Blockly.Events.disable();
|
||||
try {
|
||||
if (this.firstMarker_) {
|
||||
this.firstMarker_.dispose();
|
||||
this.firstMarker_ = null;
|
||||
}
|
||||
if (this.lastMarker_) {
|
||||
this.lastMarker_.dispose();
|
||||
this.lastMarker_ = null;
|
||||
}
|
||||
} finally {
|
||||
Blockly.Events.enable();
|
||||
}
|
||||
|
||||
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_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return whether the block would be connected if dropped immediately, based on
|
||||
* information from the most recent move event.
|
||||
* @return {boolean} true if the block would be connected if dropped immediately.
|
||||
* @package
|
||||
*/
|
||||
Blockly.InsertionMarkerManager.prototype.wouldConnectBlock = function() {
|
||||
return !!this.closestConnection_;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.topBlock_.rendered) {
|
||||
// Trigger a connection animation.
|
||||
// Determine which connection is inferior (lower in the source stack).
|
||||
var inferiorConnection = this.localConnection_.isSuperior() ?
|
||||
this.closestConnection_ : this.localConnection_;
|
||||
Blockly.BlockAnimations.connectionUiEffect(
|
||||
inferiorConnection.getSourceBlock());
|
||||
// Bring the just-edited stack to the front.
|
||||
var rootBlock = this.topBlock_.getRootBlock();
|
||||
rootBlock.bringToFront();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
Blockly.Events.disable();
|
||||
try {
|
||||
var result = this.workspace_.newBlock(imType);
|
||||
result.setInsertionMarker(true, sourceBlock.width);
|
||||
if (sourceBlock.mutationToDom) {
|
||||
var oldMutationDom = sourceBlock.mutationToDom();
|
||||
if (oldMutationDom) {
|
||||
result.domToMutation(oldMutationDom);
|
||||
}
|
||||
}
|
||||
// Copy field values from the other block. These values may impact the
|
||||
// rendered size of the insertion marker. Note that we do not care about
|
||||
// child blocks here.
|
||||
for (var i = 0; i < sourceBlock.inputList.length; i++) {
|
||||
var input = sourceBlock.inputList[i];
|
||||
for (var j = 0; j < input.fieldRow.length; j++) {
|
||||
var field = input.fieldRow[j];
|
||||
result.setFieldValue(field.getValue(), field.name);
|
||||
}
|
||||
}
|
||||
|
||||
result.initSvg();
|
||||
result.getSvgRoot().setAttribute('visibility', 'hidden');
|
||||
} finally {
|
||||
Blockly.Events.enable();
|
||||
}
|
||||
|
||||
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) {
|
||||
// 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.
|
||||
if (local.type == Blockly.OUTPUT_VALUE) {
|
||||
// Insert the dragged block into the stack if possible.
|
||||
if (!closest.isConnected() ||
|
||||
Blockly.Connection.lastConnectionInRow_(this.topBlock_,
|
||||
closest.targetConnection.getSourceBlock())) {
|
||||
return false; // Insert.
|
||||
}
|
||||
// Otherwise replace the existing block and bump it out.
|
||||
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()) {
|
||||
console.log("trying to connect to an insertion marker");
|
||||
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_();
|
||||
}
|
||||
// Also highlight the actual connection, as a nod to previous behaviour.
|
||||
if (this.closestConnection_) {
|
||||
this.closestConnection_.highlight();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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_();
|
||||
} else {
|
||||
// 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.closestConnection_) {
|
||||
this.closestConnection_.unhighlight();
|
||||
}
|
||||
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.
|
||||
*/
|
||||
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_;
|
||||
// TODO: remove?
|
||||
closest.sourceBlock_.highlightShapeForInput(closest, true);
|
||||
}
|
||||
this.highlightingBlock_ = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get rid of the highlighting marking the block that will be replaced.
|
||||
*/
|
||||
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;
|
||||
var markerOutput = imBlock.outputConnection;
|
||||
|
||||
var isFirstInStatementStack =
|
||||
(imConn == markerNext && !(markerPrev && markerPrev.targetConnection));
|
||||
|
||||
var isFirstInOutputStack = imConn.type == Blockly.INPUT_VALUE &&
|
||||
!(markerOutput && markerOutput.targetConnection);
|
||||
// The insertion marker is the first block in a stack. Unplug won't do
|
||||
// anything in that case. Instead, unplug the following block.
|
||||
if (isFirstInStatementStack || isFirstInOutputStack) {
|
||||
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');
|
||||
|
||||
// Position based on the calculated connection locations.
|
||||
imBlock.positionNewBlock(imBlock, imConn, closest);
|
||||
|
||||
// Connect() also renders the insertion marker.
|
||||
imConn.connect(closest);
|
||||
this.markerConnection_ = imConn;
|
||||
};
|
||||
|
||||
/**** End insertion marker display functions ****/
|
||||
@@ -279,6 +279,7 @@ function helper_searchDB(db, x, y, radius, shared_workspace) {
|
||||
var tempConn = helper_createConnection(x, y,
|
||||
Blockly.NEXT_STATEMENT, shared_workspace, true);
|
||||
tempConn.sourceBlock_ = helper_makeSourceBlock(shared_workspace);
|
||||
tempConn.sourceBlock_.nextConnection = tempConn;
|
||||
var closest = db.searchForClosest(tempConn, radius, {x: 0, y: 0});
|
||||
return closest.connection;
|
||||
}
|
||||
@@ -289,7 +290,9 @@ function helper_makeSourceBlock(sharedWorkspace) {
|
||||
getParent: function() { return null; },
|
||||
movable_: true,
|
||||
isMovable: function() { return true; },
|
||||
isShadow: function() { return false; }
|
||||
isShadow: function() { return false; },
|
||||
isInsertionMarker: function() { return false; },
|
||||
getFirstStatementConnection: function() { return null; }
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -313,6 +313,112 @@ function test_isConnectionAllowed_NoNext() {
|
||||
assertTrue(two.isConnectionAllowed(one));
|
||||
}
|
||||
|
||||
function test_canConnectToPrevious_alreadyConnected() {
|
||||
var sharedWorkspace = {};
|
||||
var one = helper_createConnection(0, 0, Blockly.NEXT_STATEMENT);
|
||||
one.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace);
|
||||
one.sourceBlock_.nextConnection = one;
|
||||
|
||||
var two = helper_createConnection(0, 0, Blockly.PREVIOUS_STATEMENT);
|
||||
two.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace);
|
||||
two.sourceBlock_.previousConnection = two;
|
||||
|
||||
Blockly.Connection.connectReciprocally_(one, two);
|
||||
|
||||
var three = helper_createConnection(0, 0, Blockly.PREVIOUS_STATEMENT);
|
||||
three.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace);
|
||||
three.sourceBlock_.previousConnection = three;
|
||||
|
||||
// The next connection is already occupied and cannot disconnect itself
|
||||
// mid-drag.
|
||||
assertFalse(one.canConnectToPrevious_(three));
|
||||
}
|
||||
|
||||
function test_canConnect_dragging() {
|
||||
var sharedWorkspace = {};
|
||||
var one = helper_createConnection(0, 0, Blockly.NEXT_STATEMENT);
|
||||
one.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace);
|
||||
one.sourceBlock_.nextConnection = one;
|
||||
|
||||
var two = helper_createConnection(0, 0, Blockly.PREVIOUS_STATEMENT);
|
||||
two.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace);
|
||||
two.sourceBlock_.previousConnection = two;
|
||||
|
||||
Blockly.Connection.connectReciprocally_(one, two);
|
||||
|
||||
Blockly.draggingConnections_.push(one);
|
||||
Blockly.draggingConnections_.push(two);
|
||||
|
||||
assertFalse(two.isConnectionAllowed(one));
|
||||
assertFalse(one.isConnectionAllowed(two));
|
||||
}
|
||||
|
||||
function test_canConnect_stackStart() {
|
||||
var sharedWorkspace = {};
|
||||
var block1Next = helper_createConnection(0, 0, Blockly.NEXT_STATEMENT);
|
||||
var block1 = helper_makeSourceBlock(sharedWorkspace);
|
||||
block1Next.sourceBlock_ = block1
|
||||
block1.nextConnection = block1Next;
|
||||
|
||||
var block1Prev = helper_createConnection(0, 0, Blockly.PREVIOUS_STATEMENT);
|
||||
block1Prev.sourceBlock_ = block1;
|
||||
block1.previousConnection = block1Prev
|
||||
|
||||
var block2Prev = helper_createConnection(0, 0, Blockly.PREVIOUS_STATEMENT);
|
||||
block2Prev.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace);
|
||||
block2Prev.sourceBlock_.previousConnection = block2Prev;
|
||||
|
||||
Blockly.Connection.connectReciprocally_(block1Next, block2Prev);
|
||||
|
||||
var three = helper_createConnection(0, 0, Blockly.NEXT_STATEMENT);
|
||||
three.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace);
|
||||
three.sourceBlock_.nextConnection = three;
|
||||
|
||||
// Can connect at the beginning of the stack.
|
||||
assertTrue(three.canConnectToPrevious_(block1Prev));
|
||||
// But not in the middle of the stack.
|
||||
assertFalse(three.canConnectToPrevious_(block2Prev));
|
||||
}
|
||||
|
||||
function test_canConnect_stackStart_insertionMarker() {
|
||||
var sharedWorkspace = {};
|
||||
var block1Next = helper_createConnection(0, 0, Blockly.NEXT_STATEMENT);
|
||||
var block1 = helper_makeSourceBlock(sharedWorkspace);
|
||||
block1.isInsertionMarker = function() {
|
||||
return true;
|
||||
}
|
||||
block1.getPreviousBlock = function() {
|
||||
return false;
|
||||
}
|
||||
block1Next.sourceBlock_ = block1
|
||||
block1.nextConnection = block1Next;
|
||||
|
||||
var block1Prev = helper_createConnection(0, 0, Blockly.PREVIOUS_STATEMENT);
|
||||
block1Prev.sourceBlock_ = block1;
|
||||
block1.previousConnection = block1Prev
|
||||
|
||||
var block2Prev = helper_createConnection(0, 0, Blockly.PREVIOUS_STATEMENT);
|
||||
var block2 = helper_makeSourceBlock(sharedWorkspace);
|
||||
block2Prev.sourceBlock_ = block2;
|
||||
block2.previousConnection = block2Prev;
|
||||
block2.getPreviousBlock = function() {
|
||||
return block1;
|
||||
}
|
||||
|
||||
|
||||
Blockly.Connection.connectReciprocally_(block1Next, block2Prev);
|
||||
|
||||
var three = helper_createConnection(0, 0, Blockly.NEXT_STATEMENT);
|
||||
three.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace);
|
||||
three.sourceBlock_.nextConnection = three;
|
||||
|
||||
// Can't connect to the previous connection of an insertion marker.
|
||||
assertFalse(three.isConnectionAllowed(block1Prev));
|
||||
// But can connect to a previous connection that is already connected to an
|
||||
// insertion marker.
|
||||
assertTrue(three.isConnectionAllowed(block2Prev));
|
||||
}
|
||||
|
||||
function testCheckConnection_Okay() {
|
||||
connectionTest_setUp();
|
||||
previous.checkConnection_(next);
|
||||
|
||||
Reference in New Issue
Block a user