mirror of
https://github.com/scratchfoundation/scratch-blocks.git
synced 2025-08-28 22:10:31 -04:00
Insertion markers and replacement markers for value inputs and terminal blocks
This commit is contained in:
parent
b9d931afd3
commit
1fcc7047ba
6 changed files with 169 additions and 36 deletions
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue