Insertion markers and replacement markers for value inputs and terminal blocks

This commit is contained in:
Rachel Fenichel 2016-06-14 23:12:33 -07:00
parent b9d931afd3
commit 1fcc7047ba
6 changed files with 169 additions and 36 deletions

View file

@ -389,6 +389,20 @@ Blockly.Block.prototype.getInputWithBlock = function(block) {
return null;
};
/**
* Return the input that contains the specified connection
* @param {!Blockly.Connection} conn A connection on this block.
* @return {Blockly.Input} The input that contains the specified connection.
*/
Blockly.Block.prototype.getInputWithConnection = function(conn) {
for (var i = 0, input; input = this.inputList[i]; i++) {
if (input.connection == conn) {
return input;
}
}
return null;
};
/**
* Return the parent block that surrounds the current block, or null if this
* block has no surrounding block. A parent block might just be the previous

View file

@ -298,6 +298,19 @@ Blockly.BlockSvg.prototype.updateColour = function() {
}
};
/**
* 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) {
this.svgPath_.setAttribute('fill', '#eeff01'); // TODO:#413
} else {
this.updateColour();
}
};
/**
* Returns a bounding box describing the dimensions of this block
* and any blocks stacked below it.

View file

@ -414,6 +414,39 @@ Blockly.BlockSvg.prototype.updateColour = function() {
}
};
/**
* 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) {
this.svgPath_.setAttribute('fill', '#eeff01'); // TODO:#413
} else {
this.updateColour();
}
};
/**
* Visual effect to show that if the dragging block is dropped it will connect
* to this input.
* @param {Blockly.Connection} conn The connection on the input to highlight.
* @param {boolean} add True if highlighting should be added.
*/
Blockly.BlockSvg.prototype.highlightShapeForInput = function(conn, add) {
var input = this.getInputWithConnection(conn);
if (!input) {
throw 'No input found for the connection';
}
var inputShape = this.inputShapes_[input.name];
if (add) {
inputShape.setAttribute('fill',
Math.random() < 0.5 ? '#ff00ff' : '#00ff00'); // TODO:#413
} else {
inputShape.setAttribute('fill', this.getColourTertiary());
}
};
/**
* Returns a bounding box describing the dimensions of this block
* and any blocks stacked below it.

View file

@ -296,7 +296,9 @@ Blockly.BlockSvg.terminateDrag_ = function() {
if (Blockly.dragMode_ == Blockly.DRAG_FREE) {
// Terminate a drag operation.
if (selected) {
if (Blockly.insertionMarker_) {
if (Blockly.replacementMarker_) {
Blockly.BlockSvg.removeReplacementMarker();
} else if (Blockly.insertionMarker_) {
Blockly.Events.disable();
if (Blockly.insertionMarkerConnection_) {
Blockly.BlockSvg.disconnectInsertionMarker();
@ -1070,7 +1072,9 @@ Blockly.BlockSvg.prototype.updatePreviews = function(closestConnection,
// with Web Blockly the name "highlightedConnection" will still be used.
if (Blockly.highlightedConnection_ &&
Blockly.highlightedConnection_ != closestConnection) {
if (Blockly.insertionMarker_ && Blockly.insertionMarkerConnection_) {
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
@ -1085,39 +1089,25 @@ Blockly.BlockSvg.prototype.updatePreviews = function(closestConnection,
Blockly.localConnection_ = null;
}
// Add an insertion marker if needed.
// Add an insertion marker or replacement marker if needed.
if (closestConnection &&
closestConnection != Blockly.highlightedConnection_ &&
!closestConnection.sourceBlock_.isInsertionMarker()) {
Blockly.highlightedConnection_ = closestConnection;
Blockly.localConnection_ = localConnection;
if (!Blockly.insertionMarker_) {
Blockly.insertionMarker_ =
this.workspace.newBlock(Blockly.localConnection_.sourceBlock_.type);
Blockly.insertionMarker_.setInsertionMarker(true);
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');
// 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));
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;
if (shouldReplace) {
this.addReplacementMarker_(localConnection, closestConnection);
} else { // Should insert
this.connectInsertionMarker_(localConnection, closestConnection);
}
}
// Reenable events.
@ -1130,6 +1120,81 @@ Blockly.BlockSvg.prototype.updatePreviews = function(closestConnection,
}
};
/**
* 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) {
if (!Blockly.insertionMarker_) {
Blockly.insertionMarker_ =
this.workspace.newBlock(Blockly.localConnection_.sourceBlock_.type);
Blockly.insertionMarker_.setInsertionMarker(true);
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.

View file

@ -109,6 +109,14 @@ Blockly.insertionMarkerConnection_ = null;
*/
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.

View file

@ -439,12 +439,8 @@ Blockly.Connection.prototype.isConnectionAllowed = function(candidate) {
break;
}
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.targetConnection || this.targetConnection) {
return false;
}
break;
// Can't drag an input to an output--you have to move the inferior block.
return false;
}
case Blockly.INPUT_VALUE: {
// Offering to connect the left (male) of a value block to an already
@ -469,9 +465,13 @@ Blockly.Connection.prototype.isConnectionAllowed = function(candidate) {
return false;
}
// Don't let a block with no next connection bump other blocks out of the
// stack.
// stack. But covering up a shadow block or stack of shadow blocks is
// fine. Similarly, replacing a terminal statement with another terminal
// statement is allowed.
if (candidate.isConnectedToNonInsertionMarker() &&
!this.sourceBlock_.nextConnection) {
!this.sourceBlock_.nextConnection &&
!candidate.targetBlock().isShadow() &&
candidate.targetBlock().nextConnection) {
return false;
}
break;