From 2d2aca5d48fe0f8394a48df709632608c79c92a7 Mon Sep 17 00:00:00 2001 From: Rachel Fenichel Date: Fri, 4 Mar 2016 14:39:24 -0800 Subject: [PATCH 01/12] complicated ghosts --- core/block.js | 44 +++++++++++- core/block_render_svg_horizontal.js | 15 +++++ core/block_svg.js | 101 ++++++++++++++++++---------- 3 files changed, 123 insertions(+), 37 deletions(-) diff --git a/core/block.js b/core/block.js index 4fe6c9a67e..5fd43f82ca 100644 --- a/core/block.js +++ b/core/block.js @@ -623,6 +623,28 @@ Blockly.Block.prototype.setConnectionsHidden = function(hidden) { } }; +/** + * Find the connection on this block that corresponds to the given connection + * on the other block. + * Used to match connections between a block and its ghost. + * @param {!Blockly.Block} otherBlock The other block to match against. + * @param {!Blockly.Connection} conn The other connection to match. + * @return {Blockly.Connection} the matching connection on this block, or null. + */ +Blockly.Block.prototype.getMatchingConnection = function(otherBlock, conn) { + var connections = this.getConnections_(true); + var otherConnections = otherBlock.getConnections_(true); + if (connections.length != otherConnections.length) { + throw "Connection lists did not match in length."; + } + for (var i = 0; i < otherConnections.length; i++) { + if (otherConnections[i] == conn) { + return connections[i]; + } + } + return null; +}; + /** * Set the URL of this block's help page. * @param {string|Function} url URL string for block help, or function that @@ -694,12 +716,14 @@ Blockly.Block.prototype.setColour = function(colour, colourSecondary, colourTert if (colourSecondary !== undefined) { this.colourSecondary_ = this.makeColour_(colourSecondary); } else { - this.colourSecondary_ = goog.color.darken(colour, 0.1); + this.colourSecondary_ = goog.color.darken(goog.color.hexToRgb(this.colour_), + 0.1); } if (colourTertiary !== undefined) { this.colourTertiary_ = this.makeColour_(colourTertiary); } else { - this.colourTertiary_ = goog.color.darken(colour, 0.2); + this.colourTertiary_ = goog.color.darken(goog.color.hexToRgb(this.colour_), + 0.2); } if (this.rendered) { this.updateColour(); @@ -1419,6 +1443,22 @@ Blockly.Block.prototype.moveBy = function(dx, dy) { Blockly.Events.fire(event); }; +/** + * Move a block to an absolute location. + * @param {number} x Horizontal location. + * @param {number} y Vertical loaction. + * @param {boolean} suppress_event Whether to suppress the move event. + */ +Blockly.Block.prototype.moveTo = function(x, y, suppress_event) { + var event = new Blockly.Events.Move(this); + this.xy_.x = x; + this.xy_.y = y; + if (!suppress_event) { + event.recordNew(); + Blockly.Events.fire(event); + } +}; + /** * Database of all blocks. * @private diff --git a/core/block_render_svg_horizontal.js b/core/block_render_svg_horizontal.js index 99fd12dfd9..7833134d17 100644 --- a/core/block_render_svg_horizontal.js +++ b/core/block_render_svg_horizontal.js @@ -283,6 +283,17 @@ Blockly.BlockSvg.prototype.renderCompute_ = function() { metrics.startHat = true; } + var ghostBlock = this.ghostBlock_; + // Is there an insertion ghost? + if (ghostBlock) { + var ghostInfo = ghostBlock.ghostInfo_; + // Is it right in front of my nose? + if (ghostInfo.activeConnection == ghostBlock.nextConnection) { + ghostBlock.getSvgRoot().setAttribute('transform', + 'translate(' + + ",")) + } + } + // Does block have a statement? for (var i = 0, input; input = this.inputList[i]; i++) { if (input.type == Blockly.NEXT_STATEMENT) { @@ -358,6 +369,10 @@ Blockly.BlockSvg.prototype.renderDraw_ = function(metrics) { // Fetch the block's coordinates on the surface for use in anchoring // the connections. var connectionsXY = this.getRelativeToSurfaceXY(); + // if (this.isGhost_) { + // console.log(connectionsXY); + // console.log(metrics); + // } // Assemble the block's path. var steps = []; diff --git a/core/block_svg.js b/core/block_svg.js index ae3e9fa996..33da827856 100644 --- a/core/block_svg.js +++ b/core/block_svg.js @@ -812,45 +812,76 @@ Blockly.BlockSvg.prototype.onMouseMove_ = function(e) { } } - // Remove connection highlighting if needed. - if (Blockly.highlightedConnection_ && - Blockly.highlightedConnection_ != closestConnection) { - if (this.ghostBlock_) { - this.ghostBlock_.unplug(true /* healStack */); - this.ghostBlock_.dispose(); - this.ghostBlock_ = null; - } - Blockly.highlightedConnection_.unhighlight(); - Blockly.highlightedConnection_ = null; - Blockly.localConnection_ = null; + this.updatePreviews(closestConnection, localConnection, radiusConnection, + e, newXY.x - this.dragStartXY_.x, newXY.y - this.dragStartXY_.y, oldXY); + } + // This event has been handled. No need to bubble up to the document. + e.stopPropagation(); +}; + +/** + * 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. + */ +Blockly.BlockSvg.prototype.updatePreviews = function(closestConnection, + localConnection, radiusConnection, e, dx, dy, oldXY) { + // Remove connection highlighting if needed. + if (Blockly.highlightedConnection_ && + Blockly.highlightedConnection_ != closestConnection) { + Blockly.highlightedConnection_.unhighlight(); + + if (this.ghostBlock_) { + this.ghostBlock_.unplug(true /* healStack */); + this.ghostBlock_.dispose(); + this.ghostBlock_ = null; } - // Add connection highlighting if needed. - if (closestConnection && - closestConnection != Blockly.highlightedConnection_) { - closestConnection.highlight(); - Blockly.highlightedConnection_ = closestConnection; - Blockly.localConnection_ = localConnection; - if (!this.ghostBlock_){ - this.ghostBlock_ = this.workspace.newBlock(this.type); - this.ghostBlock_.setGhost(true); - this.ghostBlock_.moveConnections_(radiusConnection); - } - if (Blockly.localConnection_ == this.previousConnection) { - // Setting the block to rendered will actually change the connection - // behaviour :/ - this.ghostBlock_.rendered = true; - this.ghostBlock_.previousConnection.connect(closestConnection); - } - this.ghostBlock_.render(true); + Blockly.highlightedConnection_ = null; + Blockly.localConnection_ = null; + + } + // Add connection highlighting if needed. + if (closestConnection && + closestConnection != Blockly.highlightedConnection_ + && !closestConnection.sourceBlock_.isGhost()) { + closestConnection.highlight(); + Blockly.highlightedConnection_ = closestConnection; + Blockly.localConnection_ = localConnection; + if (!this.ghostBlock_){ + this.ghostBlock_ = this.workspace.newBlock(this.type); + this.ghostBlock_.setGhost(true); + this.ghostBlock_.initSvg(); + var ghostBlock = this.ghostBlock_; } - // Provide visual indication of whether the block will be deleted if - // dropped here. - if (this.isDeletable()) { - this.workspace.isDeleteArea(e); + + var localGhostConnection = ghostBlock.getMatchingConnection(this, + localConnection); + + if (localGhostConnection) { + ghostBlock.rendered = true; + if (localGhostConnection.type == Blockly.PREVIOUS_STATEMENT) { + } else if (localGhostConnection.type == Blockly.NEXT_STATEMENT) { + var relativeXy = this.getRelativeToSurfaceXY(); + var connectionOffsetX = (localConnection.x_ - (relativeXy.x - dx)); + var connectionOffsetY = (localConnection.y_ - (relativeXy.y - dy)); + var newX = closestConnection.x_ - connectionOffsetX; + var newY = closestConnection.y_ - connectionOffsetY; + ghostBlock.moveBy(newX, newY, true); + } + localGhostConnection.connect(closestConnection); } } - // This event has been handled. No need to bubble up to the document. - e.stopPropagation(); + + // Provide visual indication of whether the block will be deleted if + // dropped here. + if (this.isDeletable()) { + this.workspace.isDeleteArea(e); + } }; /** From 2103dd4ce27f13922a8c9f9af8ffbdc9497d50e8 Mon Sep 17 00:00:00 2001 From: Rachel Fenichel Date: Wed, 9 Mar 2016 16:58:18 -0800 Subject: [PATCH 02/12] Ghosts for pants block; conforms to blockly-style insertion rules. --- core/block_render_svg_horizontal.js | 21 +++---------- core/block_svg.js | 47 +++++++++++++++++++++-------- core/blockly.js | 10 +++++- 3 files changed, 47 insertions(+), 31 deletions(-) diff --git a/core/block_render_svg_horizontal.js b/core/block_render_svg_horizontal.js index 7833134d17..c05e09acbf 100644 --- a/core/block_render_svg_horizontal.js +++ b/core/block_render_svg_horizontal.js @@ -283,17 +283,6 @@ Blockly.BlockSvg.prototype.renderCompute_ = function() { metrics.startHat = true; } - var ghostBlock = this.ghostBlock_; - // Is there an insertion ghost? - if (ghostBlock) { - var ghostInfo = ghostBlock.ghostInfo_; - // Is it right in front of my nose? - if (ghostInfo.activeConnection == ghostBlock.nextConnection) { - ghostBlock.getSvgRoot().setAttribute('transform', - 'translate(' + + ",")) - } - } - // Does block have a statement? for (var i = 0, input; input = this.inputList[i]; i++) { if (input.type == Blockly.NEXT_STATEMENT) { @@ -369,11 +358,6 @@ Blockly.BlockSvg.prototype.renderDraw_ = function(metrics) { // Fetch the block's coordinates on the surface for use in anchoring // the connections. var connectionsXY = this.getRelativeToSurfaceXY(); - // if (this.isGhost_) { - // console.log(connectionsXY); - // console.log(metrics); - // } - // Assemble the block's path. var steps = []; @@ -392,7 +376,7 @@ Blockly.BlockSvg.prototype.renderDraw_ = function(metrics) { } // Position icon - if (!this.isGhost() && metrics.icon) { + if (metrics.icon) { var icon = metrics.icon.getSvgRoot(); var iconSize = metrics.icon.getSize(); // Icon's position is calculated relative to the "end" edge of the block. @@ -409,6 +393,9 @@ Blockly.BlockSvg.prototype.renderDraw_ = function(metrics) { iconX = -metrics.width + Blockly.BlockSvg.SEP_SPACE_X / 1.5; } } + if (this.isGhost()) { + icon.setAttribute('display', 'none'); + } icon.setAttribute('transform', 'translate(' + iconX + ',' + iconY + ') ' + iconScale); } diff --git a/core/block_svg.js b/core/block_svg.js index 33da827856..c0ade40cce 100644 --- a/core/block_svg.js +++ b/core/block_svg.js @@ -217,10 +217,8 @@ Blockly.BlockSvg.terminateDrag_ = function() { if (Blockly.dragMode_ == 2) { // Terminate a drag operation. if (selected) { - if (selected.ghostBlock_) { - selected.ghostBlock_.unplug(true /* healStack */); - selected.ghostBlock_.dispose(); - selected.ghostBlock_ = null; + if (selected.ghostBlock_ && Blockly.localGhostConnection_) { + selected.disconnectGhost(); } // Update the connection locations. var xy = selected.getRelativeToSurfaceXY(); @@ -836,10 +834,8 @@ Blockly.BlockSvg.prototype.updatePreviews = function(closestConnection, Blockly.highlightedConnection_ != closestConnection) { Blockly.highlightedConnection_.unhighlight(); - if (this.ghostBlock_) { - this.ghostBlock_.unplug(true /* healStack */); - this.ghostBlock_.dispose(); - this.ghostBlock_ = null; + if (this.ghostBlock_ && Blockly.localGhostConnection_) { + this.disconnectGhost(); } Blockly.highlightedConnection_ = null; Blockly.localConnection_ = null; @@ -856,16 +852,15 @@ Blockly.BlockSvg.prototype.updatePreviews = function(closestConnection, this.ghostBlock_ = this.workspace.newBlock(this.type); this.ghostBlock_.setGhost(true); this.ghostBlock_.initSvg(); - var ghostBlock = this.ghostBlock_; } + var ghostBlock = this.ghostBlock_; var localGhostConnection = ghostBlock.getMatchingConnection(this, localConnection); - - if (localGhostConnection) { + if (localGhostConnection != Blockly.localGhostConnection_) { ghostBlock.rendered = true; - if (localGhostConnection.type == Blockly.PREVIOUS_STATEMENT) { - } else if (localGhostConnection.type == Blockly.NEXT_STATEMENT) { + // Move the preview to the correct location before the existing block. + if (localGhostConnection.type == Blockly.NEXT_STATEMENT) { var relativeXy = this.getRelativeToSurfaceXY(); var connectionOffsetX = (localConnection.x_ - (relativeXy.x - dx)); var connectionOffsetY = (localConnection.y_ - (relativeXy.y - dy)); @@ -873,7 +868,11 @@ Blockly.BlockSvg.prototype.updatePreviews = function(closestConnection, var newY = closestConnection.y_ - connectionOffsetY; ghostBlock.moveBy(newX, newY, true); } + // Renders ghost. localGhostConnection.connect(closestConnection); + // Render dragging block so it appears on top. + this.workspace.getCanvas().appendChild(this.getSvgRoot()); + Blockly.localGhostConnection_ = localGhostConnection; } } @@ -884,6 +883,28 @@ Blockly.BlockSvg.prototype.updatePreviews = function(closestConnection, } }; +Blockly.BlockSvg.prototype.disconnectGhost = function() { + if ((Blockly.localGhostConnection_ == this.ghostBlock_.previousConnection && + this.ghostBlock_.nextConnection) || + Blockly.localGhostConnection_ == this.ghostBlock_.nextConnection) { + this.ghostBlock_.unplug(true /* healStack */); + } + if (Blockly.localGhostConnection_.type == Blockly.NEXT_STATEMENT && + Blockly.localGhostConnection_ != this.ghostBlock_.nextConnection) { + var innerConnection = Blockly.localGhostConnection_.targetConnection; + Blockly.localGhostConnection_.targetBlock().unplug(false); + var previousBlockNextConnection = + this.ghostBlock_.previousConnection.targetConnection; + this.ghostBlock_.unplug(true); + if (previousBlockNextConnection) { + previousBlockNextConnection.connect(innerConnection); + } + } + Blockly.localGhostConnection_ = null; + this.ghostBlock_.dispose(); + this.ghostBlock_ = null; +}; + /** * Add or remove the UI indicating if this block is movable or not. */ diff --git a/core/blockly.js b/core/blockly.js index d9e4e44a28..fe3e7d54c8 100644 --- a/core/blockly.js +++ b/core/blockly.js @@ -191,6 +191,14 @@ Blockly.highlightedConnection_ = null; */ Blockly.localConnection_ = null; +/** + * Connection on ghost block that matches Blockly.localConnecxtion_ on the + * dragged block. + * @type {Blockly.Connection} + * @private + */ +Blockly.localGhostConnection_ = null; + /** * Number of pixels the mouse must move before a drag starts. */ @@ -199,7 +207,7 @@ Blockly.DRAG_RADIUS = 5; /** * Maximum misalignment between connections for them to snap together. */ -Blockly.SNAP_RADIUS = 20; +Blockly.SNAP_RADIUS = 50; /** * Delay in ms between trigger and bumping unconnected block out of alignment. From 1d2f7124566b7c6e69a06d16f3879e47de82c703 Mon Sep 17 00:00:00 2001 From: Rachel Fenichel Date: Mon, 14 Mar 2016 17:14:00 -0700 Subject: [PATCH 03/12] WIP on ghosts for pants blocks --- core/block.js | 16 ---------------- core/block_svg.js | 14 ++++++++++++++ core/blockly.js | 8 ++++++++ 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/core/block.js b/core/block.js index 5fd43f82ca..0d4a484688 100644 --- a/core/block.js +++ b/core/block.js @@ -1443,22 +1443,6 @@ Blockly.Block.prototype.moveBy = function(dx, dy) { Blockly.Events.fire(event); }; -/** - * Move a block to an absolute location. - * @param {number} x Horizontal location. - * @param {number} y Vertical loaction. - * @param {boolean} suppress_event Whether to suppress the move event. - */ -Blockly.Block.prototype.moveTo = function(x, y, suppress_event) { - var event = new Blockly.Events.Move(this); - this.xy_.x = x; - this.xy_.y = y; - if (!suppress_event) { - event.recordNew(); - Blockly.Events.fire(event); - } -}; - /** * Database of all blocks. * @private diff --git a/core/block_svg.js b/core/block_svg.js index c0ade40cce..85d2e29bac 100644 --- a/core/block_svg.js +++ b/core/block_svg.js @@ -868,6 +868,10 @@ Blockly.BlockSvg.prototype.updatePreviews = function(closestConnection, var newY = closestConnection.y_ - connectionOffsetY; ghostBlock.moveBy(newX, newY, true); } + if (localGhostConnection.type == Blockly.PREVIOUS_STATEMENT && + !ghostBlock.nextConnection) { + Blockly.bumpedConnection_ = closestConnection.targetConnection; + } // Renders ghost. localGhostConnection.connect(closestConnection); // Render dragging block so it appears on top. @@ -889,6 +893,16 @@ Blockly.BlockSvg.prototype.disconnectGhost = function() { Blockly.localGhostConnection_ == this.ghostBlock_.nextConnection) { this.ghostBlock_.unplug(true /* healStack */); } + if ((Blockly.localGhostConnection_ == this.ghostBlock_.previousConnection && + !this.ghostBlock_.nextConnection)) { + var previousBlockNextConnection = + this.ghostBlock_.previousConnection.targetConnection; + this.ghostBlock_.unplug(true /* healStack */); + if (previousBlockNextConnection) { + previousBlockNextConnection.connect(Blockly.bumpedConnection_); + } + + } if (Blockly.localGhostConnection_.type == Blockly.NEXT_STATEMENT && Blockly.localGhostConnection_ != this.ghostBlock_.nextConnection) { var innerConnection = Blockly.localGhostConnection_.targetConnection; diff --git a/core/blockly.js b/core/blockly.js index fe3e7d54c8..d47775d889 100644 --- a/core/blockly.js +++ b/core/blockly.js @@ -199,6 +199,14 @@ Blockly.localConnection_ = null; */ Blockly.localGhostConnection_ = null; +/** + * Connection that was bumped out of the way by a ghost block, and may need + * to be put back as the drag continues. + * @type {Blockly.Connection} + * @private + */ +Blockly.bumpedConnection_ = null; + /** * Number of pixels the mouse must move before a drag starts. */ From 19bc2244b84d34749e0337df88024884f6b671a6 Mon Sep 17 00:00:00 2001 From: rachel-fenichel Date: Wed, 16 Mar 2016 12:01:43 -0700 Subject: [PATCH 04/12] Correctly handle ghosts at the beginning of a stack --- core/block_svg.js | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/core/block_svg.js b/core/block_svg.js index 85d2e29bac..d05d17ef7d 100644 --- a/core/block_svg.js +++ b/core/block_svg.js @@ -590,7 +590,6 @@ Blockly.BlockSvg.prototype.onMouseUp_ = function(e) { Blockly.fireUiEvent(window, 'resize'); } if (Blockly.highlightedConnection_) { - Blockly.highlightedConnection_.unhighlight(); Blockly.highlightedConnection_ = null; } Blockly.Css.setCursor(Blockly.Css.Cursor.OPEN); @@ -811,7 +810,7 @@ Blockly.BlockSvg.prototype.onMouseMove_ = function(e) { } this.updatePreviews(closestConnection, localConnection, radiusConnection, - e, newXY.x - this.dragStartXY_.x, newXY.y - this.dragStartXY_.y, oldXY); + e, newXY.x - this.dragStartXY_.x, newXY.y - this.dragStartXY_.y); } // This event has been handled. No need to bubble up to the document. e.stopPropagation(); @@ -826,14 +825,18 @@ Blockly.BlockSvg.prototype.onMouseMove_ = function(e) { * @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. */ Blockly.BlockSvg.prototype.updatePreviews = function(closestConnection, - localConnection, radiusConnection, e, dx, dy, oldXY) { - // Remove connection highlighting if needed. + localConnection, radiusConnection, e, dx, dy) { + // Remove ghosts if needed. For Scratch-Blockly we are using ghosts instead + // of highlighting the connection; for compatibility with Web Blockly the + // name "highlightedConnection" will still be used. if (Blockly.highlightedConnection_ && Blockly.highlightedConnection_ != closestConnection) { - Blockly.highlightedConnection_.unhighlight(); - if (this.ghostBlock_ && Blockly.localGhostConnection_) { this.disconnectGhost(); } @@ -845,7 +848,6 @@ Blockly.BlockSvg.prototype.updatePreviews = function(closestConnection, if (closestConnection && closestConnection != Blockly.highlightedConnection_ && !closestConnection.sourceBlock_.isGhost()) { - closestConnection.highlight(); Blockly.highlightedConnection_ = closestConnection; Blockly.localConnection_ = localConnection; if (!this.ghostBlock_){ @@ -887,11 +889,20 @@ Blockly.BlockSvg.prototype.updatePreviews = function(closestConnection, } }; +/** + * Disconnect the current ghost block from the stack, and heal the stack to its + * previous state. + */ Blockly.BlockSvg.prototype.disconnectGhost = function() { if ((Blockly.localGhostConnection_ == this.ghostBlock_.previousConnection && this.ghostBlock_.nextConnection) || - Blockly.localGhostConnection_ == this.ghostBlock_.nextConnection) { + (Blockly.localGhostConnection_ == this.ghostBlock_.nextConnection && + this.ghostBlock_.previousConnection)) { this.ghostBlock_.unplug(true /* healStack */); + } else if (Blockly.localGhostConnection_ == this.ghostBlock_.nextConnection) { + // The ghost block is thre first block in a stack. Unplug won't do anything + // in that case. Instead, unplug the following block. + Blockly.localGhostConnection_.targetBlock().unplug(false); } if ((Blockly.localGhostConnection_ == this.ghostBlock_.previousConnection && !this.ghostBlock_.nextConnection)) { From eb45586c48e131f2982b442460966b09f3ee8d6b Mon Sep 17 00:00:00 2001 From: rachel-fenichel Date: Wed, 16 Mar 2016 12:05:36 -0700 Subject: [PATCH 05/12] lint --- core/block_svg.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/block_svg.js b/core/block_svg.js index d05d17ef7d..a8025a7bca 100644 --- a/core/block_svg.js +++ b/core/block_svg.js @@ -832,7 +832,7 @@ Blockly.BlockSvg.prototype.onMouseMove_ = function(e) { */ Blockly.BlockSvg.prototype.updatePreviews = function(closestConnection, localConnection, radiusConnection, e, dx, dy) { - // Remove ghosts if needed. For Scratch-Blockly we are using ghosts instead + // Remove a ghost if needed. For Scratch-Blockly we are using ghosts instead // of highlighting the connection; for compatibility with Web Blockly the // name "highlightedConnection" will still be used. if (Blockly.highlightedConnection_ && @@ -842,9 +842,9 @@ Blockly.BlockSvg.prototype.updatePreviews = function(closestConnection, } Blockly.highlightedConnection_ = null; Blockly.localConnection_ = null; - } - // Add connection highlighting if needed. + + // Add a ghost if needed. if (closestConnection && closestConnection != Blockly.highlightedConnection_ && !closestConnection.sourceBlock_.isGhost()) { From 60677297cfddf6eba8eebd5979dff6f2af835eba Mon Sep 17 00:00:00 2001 From: rachel-fenichel Date: Wed, 16 Mar 2016 14:45:17 -0700 Subject: [PATCH 06/12] reuse ghost block instead of recreating --- core/block_svg.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/core/block_svg.js b/core/block_svg.js index a8025a7bca..c6a9a2b52f 100644 --- a/core/block_svg.js +++ b/core/block_svg.js @@ -219,6 +219,8 @@ Blockly.BlockSvg.terminateDrag_ = function() { if (selected) { if (selected.ghostBlock_ && Blockly.localGhostConnection_) { selected.disconnectGhost(); + selected.ghostBlock_.dispose(); + selected.ghostBlock_ = null; } // Update the connection locations. var xy = selected.getRelativeToSurfaceXY(); @@ -860,6 +862,7 @@ Blockly.BlockSvg.prototype.updatePreviews = function(closestConnection, var localGhostConnection = ghostBlock.getMatchingConnection(this, localConnection); if (localGhostConnection != Blockly.localGhostConnection_) { + ghostBlock.getSvgRoot().setAttribute('visibility', 'visible'); ghostBlock.rendered = true; // Move the preview to the correct location before the existing block. if (localGhostConnection.type == Blockly.NEXT_STATEMENT) { @@ -868,7 +871,8 @@ Blockly.BlockSvg.prototype.updatePreviews = function(closestConnection, var connectionOffsetY = (localConnection.y_ - (relativeXy.y - dy)); var newX = closestConnection.x_ - connectionOffsetX; var newY = closestConnection.y_ - connectionOffsetY; - ghostBlock.moveBy(newX, newY, true); + var ghostPosition = ghostBlock.getRelativeToSurfaceXY(); + ghostBlock.moveBy(newX - ghostPosition.x, newY - ghostPosition.y, true); } if (localGhostConnection.type == Blockly.PREVIOUS_STATEMENT && !ghostBlock.nextConnection) { @@ -926,8 +930,7 @@ Blockly.BlockSvg.prototype.disconnectGhost = function() { } } Blockly.localGhostConnection_ = null; - this.ghostBlock_.dispose(); - this.ghostBlock_ = null; + this.ghostBlock_.getSvgRoot().setAttribute('visibility', 'hidden'); }; /** From 1d5d89f9dfd35464125bf606871d844e3ffd71ec Mon Sep 17 00:00:00 2001 From: rachel-fenichel Date: Mon, 21 Mar 2016 16:57:39 -0400 Subject: [PATCH 07/12] Always disconnect the ghost. --- core/block_svg.js | 43 +++++++++++++++++++++---------------------- core/connection.js | 4 ++++ 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/core/block_svg.js b/core/block_svg.js index ec3b7d715e..685beab0a7 100644 --- a/core/block_svg.js +++ b/core/block_svg.js @@ -217,9 +217,11 @@ Blockly.BlockSvg.terminateDrag_ = function() { if (Blockly.dragMode_ == 2) { // Terminate a drag operation. if (selected) { - if (selected.ghostBlock_ && Blockly.localGhostConnection_) { + if (selected.ghostBlock_) { Blockly.Events.disable(); - selected.disconnectGhost(); + if (Blockly.localGhostConnection_) { + selected.disconnectGhost(); + } selected.ghostBlock_.dispose(); selected.ghostBlock_ = null; Blockly.Events.enable(); @@ -905,30 +907,20 @@ Blockly.BlockSvg.prototype.updatePreviews = function(closestConnection, * previous state. */ Blockly.BlockSvg.prototype.disconnectGhost = function() { - if ((Blockly.localGhostConnection_ == this.ghostBlock_.previousConnection && - this.ghostBlock_.nextConnection) || - (Blockly.localGhostConnection_ == this.ghostBlock_.nextConnection && - this.ghostBlock_.previousConnection)) { - this.ghostBlock_.unplug(true /* healStack */); - } else if (Blockly.localGhostConnection_ == this.ghostBlock_.nextConnection) { - // The ghost block is thre first block in a stack. Unplug won't do anything - // in that case. Instead, unplug the following block. + // The ghost block is the first block in a stack, either because it doesn't + // have a previous connection or because the previous connection is not + // connection. Unplug won't do anything in that case. Instead, unplug the + // following block. + if (Blockly.localGhostConnection_ == this.ghostBlock_.nextConnection && + (!this.ghostBlock_.previousConnection || + !this.ghostBlock_.previousConnection.targetConnection)) { Blockly.localGhostConnection_.targetBlock().unplug(false); } - if ((Blockly.localGhostConnection_ == this.ghostBlock_.previousConnection && - !this.ghostBlock_.nextConnection)) { - var previousBlockNextConnection = - this.ghostBlock_.previousConnection.targetConnection; - this.ghostBlock_.unplug(true /* healStack */); - if (previousBlockNextConnection) { - previousBlockNextConnection.connect(Blockly.bumpedConnection_); - } - - } - if (Blockly.localGhostConnection_.type == Blockly.NEXT_STATEMENT && + // Inside of a C-block, first statement connection. + else if (Blockly.localGhostConnection_.type == Blockly.NEXT_STATEMENT && Blockly.localGhostConnection_ != this.ghostBlock_.nextConnection) { var innerConnection = Blockly.localGhostConnection_.targetConnection; - Blockly.localGhostConnection_.targetBlock().unplug(false); + innerConnection.sourceBlock_.unplug(false); var previousBlockNextConnection = this.ghostBlock_.previousConnection.targetConnection; this.ghostBlock_.unplug(true); @@ -936,6 +928,13 @@ Blockly.BlockSvg.prototype.disconnectGhost = function() { previousBlockNextConnection.connect(innerConnection); } } + else { + this.ghostBlock_.unplug(true /* healStack */); + } + + if (Blockly.localGhostConnection_.targetConnection) { + throw 'LocalGhostConnection still connected at the end of disconnectGhost'; + } Blockly.localGhostConnection_ = null; this.ghostBlock_.getSvgRoot().setAttribute('visibility', 'hidden'); }; diff --git a/core/connection.js b/core/connection.js index d75ccfd2fd..e85bc34625 100644 --- a/core/connection.js +++ b/core/connection.js @@ -364,6 +364,10 @@ Blockly.Connection.prototype.isConnectionAllowed = function(candidate, return false; } + // Don't consider ghost blocks. + if (candidate.sourceBlock_.isGhost()) { + return false; + } // Type checking. var canConnect = this.canConnectWithReason_(candidate); if (canConnect != Blockly.Connection.CAN_CONNECT && From 4110635e1891a5e1ac9524bac77e298cfbfb4191 Mon Sep 17 00:00:00 2001 From: rachel-fenichel Date: Mon, 21 Mar 2016 17:13:49 -0400 Subject: [PATCH 08/12] Fix ghost block flashing --- core/connection.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/connection.js b/core/connection.js index e85bc34625..2ac078f504 100644 --- a/core/connection.js +++ b/core/connection.js @@ -380,7 +380,8 @@ Blockly.Connection.prototype.isConnectionAllowed = function(candidate, // 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 || this.targetConnection) { + if ((candidate.targetConnection && !candidate.targetConnection.sourceBlock_.isGhost()) || + this.targetConnection) { return false; } } @@ -398,7 +399,7 @@ Blockly.Connection.prototype.isConnectionAllowed = function(candidate, // Don't let a block with no next connection bump other blocks out of the // stack. if (this.type == Blockly.PREVIOUS_STATEMENT && - candidate.targetConnection && + (candidate.targetConnection && !candidate.targetConnection.sourceBlock_.isGhost()) && !this.sourceBlock_.nextConnection) { return false; } From 0178c4a2590ca30f58b96b07d5f8a51721b00f12 Mon Sep 17 00:00:00 2001 From: rachel-fenichel Date: Tue, 22 Mar 2016 13:03:47 -0400 Subject: [PATCH 09/12] revert bad logic --- core/connection.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/core/connection.js b/core/connection.js index 2ac078f504..6a8b213254 100644 --- a/core/connection.js +++ b/core/connection.js @@ -281,6 +281,14 @@ Blockly.Connection.prototype.dispose = function() { this.dbOpposite_ = null; }; +/** + * @return true if the connection is not connected or is connected to a ghost + * block, false otherwise. + */ +Blockly.Connection.prototype.isConnectedToNonGhost = function() { + return this.targetConnection && !this.targetConnection.sourceBlock_.isGhost(); +}; + /** * Does the connection belong to a superior block (higher in the source stack)? * @return {boolean} True if connection faces down or right. @@ -380,8 +388,7 @@ Blockly.Connection.prototype.isConnectionAllowed = function(candidate, // 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 && !candidate.targetConnection.sourceBlock_.isGhost()) || - this.targetConnection) { + if (candidate.targetConnection || this.targetConnection) { return false; } } @@ -399,8 +406,7 @@ Blockly.Connection.prototype.isConnectionAllowed = function(candidate, // Don't let a block with no next connection bump other blocks out of the // stack. if (this.type == Blockly.PREVIOUS_STATEMENT && - (candidate.targetConnection && !candidate.targetConnection.sourceBlock_.isGhost()) && - !this.sourceBlock_.nextConnection) { + candidate.targetConnection && !this.sourceBlock_.nextConnection) { return false; } From acae0e57ae41e13316509ff0e8a480147c0fbaa2 Mon Sep 17 00:00:00 2001 From: rachel-fenichel Date: Mon, 14 Mar 2016 17:10:15 -0700 Subject: [PATCH 10/12] C blocks consume other blocks. --- core/block.js | 14 +++++++++++ core/connection.js | 62 +++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 72 insertions(+), 4 deletions(-) diff --git a/core/block.js b/core/block.js index 0d4a484688..8cf2afc348 100644 --- a/core/block.js +++ b/core/block.js @@ -414,6 +414,20 @@ Blockly.Block.prototype.getNextBlock = function() { return this.nextConnection && this.nextConnection.targetBlock(); }; +/** + * Return the connection on the first statement input on this block, or null if + * there are none. + * @return {Blockly.Connection} The first statement connection or null. + */ +Blockly.Block.prototype.getFirstStatementConnection = function() { + for (var i = 0, input; input = this.inputList[i]; i++) { + if (input.connection && input.connection.type == Blockly.NEXT_STATEMENT) { + return input.connection; + } + } + return null; +}; + /** * Return the top-most block in this block's tree. * This will return itself if this block is at the top level. diff --git a/core/connection.js b/core/connection.js index 6a8b213254..d475b3007a 100644 --- a/core/connection.js +++ b/core/connection.js @@ -86,8 +86,18 @@ Blockly.Connection.REASON_DIFFERENT_WORKSPACES = 5; Blockly.Connection.connect_ = function(parentConnection, childConnection) { var parentBlock = parentConnection.sourceBlock_; var childBlock = childConnection.sourceBlock_; + var isSurroundingC = false; + if (parentConnection == parentBlock.getFirstStatementConnection()) { + isSurroundingC = true; + } // Disconnect any existing parent on the child connection. if (childConnection.targetConnection) { + // Scratch-specific behaviour: + // If we're using a c-shaped block to surround a stack, remember where the + // stack used to be connected. + if (isSurroundingC) { + var previousParentConnection = childConnection.targetConnection; + } childConnection.disconnect(); } if (parentConnection.targetConnection) { @@ -165,6 +175,10 @@ Blockly.Connection.connect_ = function(parentConnection, childConnection) { parentConnection.setShadowDom(shadowDom); } + if (isSurroundingC && previousParentConnection) { + previousParentConnection.connect(parentBlock.previousConnection); + } + var event; if (Blockly.Events.isEnabled()) { event = new Blockly.Events.Move(childBlock); @@ -384,15 +398,55 @@ Blockly.Connection.prototype.isConnectionAllowed = function(candidate, } // 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) { + // an available right (female) value plug. + if (candidate.type == Blockly.OUTPUT_VALUE) { if (candidate.targetConnection || this.targetConnection) { return false; } } + var firstStatementConnection = + this.sourceBlock_.getFirstStatementConnection(); + + if (candidate.type == Blockly.PREVIOUS_STATEMENT) { + // Scratch-specific behaviour: + // If this is a c-shaped block, statement blocks cannot be connected + // anywhere other than inside the first statement input. + if (firstStatementConnection) { + // Can't connect if there is alread a block inside the first statement + // input. + if (this == firstStatementConnection) { + if (this.targetConnection) { + return false; + } + } + // The only other eligible connection of this type is the next connection + // when the candidate is not already connection (connecting at the start + // of the stack). + else if (this == this.sourceBlock_.nextConnection && + candidate.targetConnection) { + return false; + } + } else { + // Otherwise, don't offer to connect the bottom of a statement block to + // the top of a block that's already connected. And don't connect the + // bottom of a statement block that's already connected. + if (this.targetConnection || candidate.targetConnection) { + return false; + } + } + } + + // Don't offer to connect the bottom of a statement block to one that's + // already connected. + // But the first statement input on c-block can connect to the start of a + // block in a stack. + if (candidate.type == Blockly.PREVIOUS_STATEMENT && + this != this.sourceBlock_.getFirstStatementConnection() && + (this.targetConnection || 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. From 8401d91071b37e3c3e5d9240555c35975408f6ce Mon Sep 17 00:00:00 2001 From: rachel-fenichel Date: Tue, 22 Mar 2016 17:12:00 -0400 Subject: [PATCH 11/12] Fix bugs with first in stack, again --- core/block_svg.js | 4 ++-- core/connection.js | 40 +++++++++++++++++++--------------------- 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/core/block_svg.js b/core/block_svg.js index 685beab0a7..fdf49be1b5 100644 --- a/core/block_svg.js +++ b/core/block_svg.js @@ -854,8 +854,8 @@ Blockly.BlockSvg.prototype.updatePreviews = function(closestConnection, // Add a ghost if needed. if (closestConnection && - closestConnection != Blockly.highlightedConnection_ - && !closestConnection.sourceBlock_.isGhost()) { + closestConnection != Blockly.highlightedConnection_ && + !closestConnection.sourceBlock_.isGhost()) { Blockly.highlightedConnection_ = closestConnection; Blockly.localConnection_ = localConnection; if (!this.ghostBlock_){ diff --git a/core/connection.js b/core/connection.js index d475b3007a..d9f393a93e 100644 --- a/core/connection.js +++ b/core/connection.js @@ -300,7 +300,7 @@ Blockly.Connection.prototype.dispose = function() { * block, false otherwise. */ Blockly.Connection.prototype.isConnectedToNonGhost = function() { - return this.targetConnection && !this.targetConnection.sourceBlock_.isGhost(); + return this.targetConnection && !this.targetBlock().isGhost(); }; /** @@ -409,6 +409,21 @@ Blockly.Connection.prototype.isConnectionAllowed = function(candidate, this.sourceBlock_.getFirstStatementConnection(); if (candidate.type == Blockly.PREVIOUS_STATEMENT) { + if (!firstStatementConnection || this != firstStatementConnection) { + if (this.targetConnection) { + return false; + } + if (candidate.targetConnection) { + // If the other side of this connection is the active ghost connection, + // we've obviously already decided that this is a good connection. + if (candidate.targetConnection == Blockly.localGhostConnection_) { + return true; + } else { + return false; + } + } + } + // Scratch-specific behaviour: // If this is a c-shaped block, statement blocks cannot be connected // anywhere other than inside the first statement input. @@ -421,32 +436,15 @@ Blockly.Connection.prototype.isConnectionAllowed = function(candidate, } } // The only other eligible connection of this type is the next connection - // when the candidate is not already connection (connecting at the start + // when the candidate is not already connected (connecting at the start // of the stack). else if (this == this.sourceBlock_.nextConnection && - candidate.targetConnection) { - return false; - } - } else { - // Otherwise, don't offer to connect the bottom of a statement block to - // the top of a block that's already connected. And don't connect the - // bottom of a statement block that's already connected. - if (this.targetConnection || candidate.targetConnection) { + candidate.isConnectedToNonGhost()) { return false; } } } - // Don't offer to connect the bottom of a statement block to one that's - // already connected. - // But the first statement input on c-block can connect to the start of a - // block in a stack. - if (candidate.type == Blockly.PREVIOUS_STATEMENT && - this != this.sourceBlock_.getFirstStatementConnection() && - (this.targetConnection || 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. @@ -460,7 +458,7 @@ Blockly.Connection.prototype.isConnectionAllowed = function(candidate, // Don't let a block with no next connection bump other blocks out of the // stack. if (this.type == Blockly.PREVIOUS_STATEMENT && - candidate.targetConnection && !this.sourceBlock_.nextConnection) { + candidate.isConnectedToNonGhost() && !this.sourceBlock_.nextConnection) { return false; } From 8c44d5f4fbcc3a073291e5a8dce58657617f8a9b Mon Sep 17 00:00:00 2001 From: rachel-fenichel Date: Mon, 28 Mar 2016 11:27:44 -0700 Subject: [PATCH 12/12] Make tests work. We really need to have mocks for testing at this point. --- tests/jsunit/connection_db_test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/jsunit/connection_db_test.js b/tests/jsunit/connection_db_test.js index 48851950c1..8f99558dcf 100644 --- a/tests/jsunit/connection_db_test.js +++ b/tests/jsunit/connection_db_test.js @@ -271,7 +271,8 @@ function helper_makeSourceBlock(sharedWorkspace) { movable_: true, isMovable: function() { return true; }, isShadow: function() { return false; }, - isGhost: function() { return false; } + isGhost: function() { return false; }, + getFirstStatementConnection: function() { return null; } }; }