Add isConnectionAllowed and tests for it

This commit is contained in:
Rachel Fenichel 2016-02-26 16:17:17 -08:00
parent 0a363803f0
commit 1b1d777bc5
3 changed files with 273 additions and 79 deletions

View file

@ -152,6 +152,17 @@ Blockly.Connection.prototype.isSuperior = function() {
this.type == Blockly.NEXT_STATEMENT;
};
/**
* Returns the distance between this connection and another connection.
* @param {Blockly.Connection} otherConnection The other connection to measure the distance to.
* @return {number} The distance between connections.
*/
Blockly.Connection.prototype.distanceFrom = function(otherConnection) {
var xDiff = this.x_ - otherConnection.x_;
var yDiff = this.y_ - otherConnection.y_;
return Math.sqrt(xDiff * xDiff + yDiff * yDiff);
};
/**
* Checks whether the current connection can connect with the target
* connection.
@ -546,94 +557,99 @@ Blockly.Connection.prototype.tighten_ = function() {
* another connection or null, and 'radius' which is the distance.
*/
Blockly.Connection.prototype.closest = function(maxLimit, dx, dy) {
if (this.targetConnection) {
// Don't offer to connect to a connection that's already connected.
return {connection: null, radius: maxLimit};
var closestConnection = this.dbOpposite_.searchForClosest(this, maxLimit, dx, dy);
if (closestConnection) {
return {connection: closestConnection, radius: this.distanceFrom(closestConnection)};
}
// Determine the opposite type of connection.
var db = this.dbOpposite_;
return {connection: null, radius: maxLimit};
// if (this.targetConnection) {
// // Don't offer to connect to a connection that's already connected.
// return {connection: null, radius: maxLimit};
// }
// // Determine the opposite type of connection.
// var db = this.dbOpposite_;
// Since this connection is probably being dragged, add the delta.
var currentX = this.x_ + dx;
var currentY = this.y_ + dy;
// // Since this connection is probably being dragged, add the delta.
// var currentX = this.x_ + dx;
// var currentY = this.y_ + dy;
// Find the closest y location.
var candidatePosition = db.findPositionForConnection_(this);
// // Find the closest y location.
// var candidatePosition = db.findPositionForConnection_(this);
// Walk forward and back on the y axis looking for the closest x,y point.
var pointerMin = candidatePosition;
var pointerMax = candidatePosition;
var closestConnection = null;
var sourceBlock = this.sourceBlock_;
var thisConnection = this;
if (db.length) {
while (pointerMin >= 0 && checkConnection_(pointerMin)) {
pointerMin--;
}
do {
pointerMax++;
} while (pointerMax < db.length && checkConnection_(pointerMax));
}
// // Walk forward and back on the y axis looking for the closest x,y point.
// var pointerMin = candidatePosition;
// var pointerMax = candidatePosition;
// var closestConnection = null;
// var sourceBlock = this.sourceBlock_;
// var thisConnection = this;
// if (db.length) {
// while (pointerMin >= 0 && checkConnection_(pointerMin)) {
// pointerMin--;
// }
// do {
// pointerMax++;
// } while (pointerMax < db.length && checkConnection_(pointerMax));
// }
/**
* Computes if the current connection is within the allowed radius of another
* connection.
* This function is a closure and has access to outside variables.
* @param {number} yIndex The other connection's index in the database.
* @return {boolean} True if the search needs to continue: either the current
* connection's vertical distance from the other connection is less than
* the allowed radius, or if the connection is not compatible.
* @private
*/
function checkConnection_(yIndex) {
var connection = db[yIndex];
if (connection.type == Blockly.OUTPUT_VALUE ||
connection.type == Blockly.PREVIOUS_STATEMENT) {
// Don't offer to connect an already connected left (male) value plug to
// an available right (female) value plug. Don't offer to connect the
// bottom of a statement block to one that's already connected.
if (connection.targetConnection) {
return true;
}
}
// Offering to connect the top of a statement block to an already connected
// connection is ok, we'll just insert it into the stack.
// /**
// * Computes if the current connection is within the allowed radius of another
// * connection.
// * This function is a closure and has access to outside variables.
// * @param {number} yIndex The other connection's index in the database.
// * @return {boolean} True if the search needs to continue: either the current
// * connection's vertical distance from the other connection is less than
// * the allowed radius, or if the connection is not compatible.
// * @private
// */
// function checkConnection_(yIndex) {
// var connection = db[yIndex];
// if (connection.type == Blockly.OUTPUT_VALUE ||
// connection.type == Blockly.PREVIOUS_STATEMENT) {
// // Don't offer to connect an already connected left (male) value plug to
// // an available right (female) value plug. Don't offer to connect the
// // bottom of a statement block to one that's already connected.
// if (connection.targetConnection) {
// return true;
// }
// }
// // Offering to connect the top of a statement block to an already connected
// // connection is ok, we'll just insert it into the stack.
// 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 (connection.type == Blockly.INPUT_VALUE &&
connection.targetConnection &&
!connection.targetBlock().isMovable() &&
!connection.targetBlock().isShadow()) {
return true;
}
// // 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 (connection.type == Blockly.INPUT_VALUE &&
// connection.targetConnection &&
// !connection.targetBlock().isMovable() &&
// !connection.targetBlock().isShadow()) {
// return true;
// }
// Do type checking.
if (!thisConnection.checkType_(connection)) {
return true;
}
// // Do type checking.
// if (!thisConnection.checkType_(connection)) {
// return true;
// }
// Don't let blocks try to connect to themselves or ones they nest.
var targetSourceBlock = connection.sourceBlock_;
do {
if (sourceBlock == targetSourceBlock) {
return true;
}
targetSourceBlock = targetSourceBlock.getParent();
} while (targetSourceBlock);
// // Don't let blocks try to connect to themselves or ones they nest.
// var targetSourceBlock = connection.sourceBlock_;
// do {
// if (sourceBlock == targetSourceBlock) {
// return true;
// }
// targetSourceBlock = targetSourceBlock.getParent();
// } while (targetSourceBlock);
// Only connections within the maxLimit radius.
var dx = currentX - connection.x_;
var dy = currentY - connection.y_;
var r = Math.sqrt(dx * dx + dy * dy);
if (r <= maxLimit) {
closestConnection = connection;
maxLimit = r;
}
return Math.abs(dy) < maxLimit;
}
return {connection: closestConnection, radius: maxLimit};
// // Only connections within the maxLimit radius.
// var dx = currentX - connection.x_;
// var dy = currentY - connection.y_;
// var r = Math.sqrt(dx * dx + dy * dy);
// if (r <= maxLimit) {
// closestConnection = connection;
// maxLimit = r;
// }
// return Math.abs(dy) < maxLimit;
// }
// return {connection: closestConnection, radius: maxLimit};
};
/**

View file

@ -204,6 +204,126 @@ Blockly.ConnectionDB.prototype.getNeighbours = function(connection, maxRadius) {
return neighbours;
};
Blockly.ConnectionDB.prototype.isInYRange_ = function(index, baseY, maxRadius) {
return (Math.abs(this[index].y_ - baseY) <= maxRadius);
}
/**
* Find the closest compatible connection to this connection.
* @param {Blockly.Connection} conn The connection searching for a compatible mate.
* @param {number} maxRadius The maximum radius to another connection.
* @param {number} dx Horizontal offset between this connection's location
* in the database and the current location (as a result of dragging).
* @param {number} dy Vertical offset between this connection's location
* in the database and the current location (as a result of dragging).
* @return {!{connection: ?Blockly.Connection, radius: number}} Contains two properties: 'connection' which is either
* another connection or null, and 'radius' which is the distance.
*/
Blockly.ConnectionDB.prototype.searchForClosest = function(conn, maxRadius, dx, dy) {
// Don't bother.
if (this.length == 0) {
return null;
}
// Stash the values of x and y from before the drag.
var baseY = conn.y_;
var baseX = conn.x_;
conn.x_ = baseX + dx;
conn.y_ = baseY + dy;
// findPositionForConnection finds an index for insertion, which is always after any
// block with the same y index. We want to search both forward and back, so search
// on both sides of the index.
var closestIndex = this.findPositionForConnection_(conn);
var bestConnection = null;
var bestRadius = maxRadius;
var temp;
// Walk forward and back on the y axis looking for the closest x,y point.
var pointerMin = closestIndex - 1;
while (pointerMin >= 0 && this.isInYRange_(pointerMin, conn.y_, maxRadius)) {
temp = this[pointerMin];
if (isConnectionAllowed(conn, temp, bestRadius)) {
bestConnection = temp;
bestRadius = temp.distanceFrom(conn);
}
pointerMin--;
}
var pointerMax = closestIndex;
while (pointerMax < this.length && this.isInYRange_(pointerMax, conn.y_, maxRadius)) {
temp = this[pointerMax];
if (isConnectionAllowed(conn, temp, bestRadius)) {
bestConnection = temp;
bestRadius = temp.distanceFrom(conn);
}
pointerMax++;
}
// Reset the values of x and y.
conn.x_ = baseX;
conn.y_ = baseY;
return bestConnection;
};
// TODO: fenichel: consider moving this to connection.js
/**
* Check if the two connections can be dragged to connect to each other.
* @param {Blockly.Connection} moving The connection being dragged.
* @param {Blockly.Connection} candidate A nearby connection to check.
* @param {number} maxRadius The maximum radius allowed for connections.
* @return {boolean} True if the connection is allowed, false otherwise.
*/
Blockly.ConnectionDB.prototype.isConnectionAllowed = function(moving, candidate, maxRadius) {
if (moving.distanceFrom(candidate) > maxRadius) {
return false;
}
// Type checking
var canConnect = moving.canConnectWithReason_(candidate);
if (canConnect != Blockly.Connection.CAN_CONNECT
&& canConnect != Blockly.Connection.REASON_MUST_DISCONNECT) {
return false;
}
// Don't offer to connect an already connected left (male) value plug to
// an available right (female) value plug. Don't offer to connect the
// bottom of a statement block to one that's already connected.
if (candidate.type == Blockly.OUTPUT_VALUE
|| candidate.type == Blockly.PREVIOUS_STATEMENT) {
if (candidate.targetConnection) {
return false;
}
}
// 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.type == Blockly.INPUT_VALUE &&
candidate.targetConnection &&
!candidate.targetBlock().isMovable() &&
!candidate.targetBlock().isShadow()) {
return true;
}
// Don't let blocks try to connect to themselves or ones they nest.
var targetSourceBlock = candidate.sourceBlock_;
var sourceBlock = moving.sourceBlock_;
if (targetSourceBlock && sourceBlock) {
do {
if (sourceBlock == targetSourceBlock) {
return true;
}
targetSourceBlock = targetSourceBlock.getParent();
} while (targetSourceBlock);
}
return true;
};
/**
* Initialize a set of connection DBs for a specified workspace.
* @param {!Blockly.Workspace} workspace The workspace this DB is for.

View file

@ -200,10 +200,68 @@ function test_DB_ordering() {
}
}
function test_DB_isConnectionAllowed() {
var db = new Blockly.ConnectionDB();
var sharedWorkspace = {};
// Two connections of opposite types near each other
var one = helper_createConnection(5 /* x */, 10 /* y */, Blockly.INPUT_VALUE);
one.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace);
var two = helper_createConnection(10 /* x */, 15 /* y */, Blockly.OUTPUT_VALUE);
two.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace);
assertTrue(db.isConnectionAllowed(one, two, 20.0));
// Move connections farther apart
two.x_ = 100;
two.y_ = 100;
assertFalse(db.isConnectionAllowed(one, two, 20.0));
// Don't offer to connect an already connected left (male) value plug to
// an available right (female) value plug.
var three = helper_createConnection(0, 0, Blockly.OUTPUT_VALUE);
three.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace);
assertTrue(db.isConnectionAllowed(one, three, 20.0));
var four = helper_createConnection(0, 0, Blockly.INPUT_VALUE);
four.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace);
Blockly.Connection.connectReciprocally(three, four);
assertFalse(db.isConnectionAllowed(one, three, 20.0));
// Don't connect two connections on the same block
two.sourceBlock_ = one.sourceBlock_;
assertFalse(db.isConnectionAllowed(one, two, 1000.0));
}
// function test_DB_isConnectionAllowedNext() {
// var db = new Blockly.ConnectionDB();
// var one = helper_createConnection(0, 0, Blockly.NEXT_STATEMENT);
// one.setInput(new Input.InputValue("test input", "" /* align */, null /* checks */));
// var two = helper_createConnection(0, 0, Blockly.NEXT_STATEMENT);
// two.setInput(new Input.InputValue("test input", "" /* align */, null /* checks */));
// // Don't offer to connect the bottom of a statement block to one that's already connected.
// varv three = helper_createConnection(0, 0, Blockly.PREVIOUS_STATEMENT);
// assertTrue(db.isConnectionAllowed(one, three, 20.0));
// three.connectReciprocally_(two);
// assertFalse(db.isConnectionAllowed(one, three, 20.0));
// }
function helper_getNeighbours(db, x, y, radius) {
return db.getNeighbours(helper_createConnection(x, y, Blockly.NEXT_STATEMENT), radius);
}
function helper_makeSourceBlock(sharedWorkspace) {
return {workspace: sharedWorkspace,
parentBlock_: null,
getParent: function() { return null; },
movable_: true,
isMovable: function() { return true; },
isShadow: function() { return false; }
};
}
function helper_createConnection(x, y, type) {
var conn = new Blockly.Connection({workspace: {}}, type);
conn.x_ = x;