Wire up bubble dragging, with coordinate problems

This commit is contained in:
Rachel Fenichel
2018-02-05 16:57:44 -08:00
parent f497c70be0
commit 68914be67d
3 changed files with 457 additions and 26 deletions

View File

@@ -279,28 +279,33 @@ Blockly.Bubble.prototype.createDom_ = function(content, hasResize) {
* @private
*/
Blockly.Bubble.prototype.bubbleMouseDown_ = function(e) {
this.promote_();
Blockly.Bubble.unbindDragEvents_();
if (Blockly.utils.isRightButton(e)) {
// No right-click.
e.stopPropagation();
return;
} else if (Blockly.utils.isTargetInput(e)) {
// When focused on an HTML text input widget, don't trap any events.
return;
var gesture = this.workspace_.getGesture(e);
if (gesture) {
gesture.handleBubbleStart(e, this);
}
// Left-click (or middle click)
this.workspace_.startDrag(e, new goog.math.Coordinate(
this.workspace_.RTL ? -this.relativeLeft_ : this.relativeLeft_,
this.relativeTop_));
Blockly.Bubble.onMouseUpWrapper_ = Blockly.bindEventWithChecks_(document,
'mouseup', this, Blockly.Bubble.bubbleMouseUp_);
Blockly.Bubble.onMouseMoveWrapper_ = Blockly.bindEventWithChecks_(document,
'mousemove', this, this.bubbleMouseMove_);
Blockly.hideChaff();
// This event has been handled. No need to bubble up to the document.
e.stopPropagation();
// this.promote_();
// Blockly.Bubble.unbindDragEvents_();
// if (Blockly.utils.isRightButton(e)) {
// // No right-click.
// e.stopPropagation();
// return;
// } else if (Blockly.utils.isTargetInput(e)) {
// // When focused on an HTML text input widget, don't trap any events.
// return;
// }
// // Left-click (or middle click)
// this.workspace_.startDrag(e, new goog.math.Coordinate(
// this.workspace_.RTL ? -this.relativeLeft_ : this.relativeLeft_,
// this.relativeTop_));
// Blockly.Bubble.onMouseUpWrapper_ = Blockly.bindEventWithChecks_(document,
// 'mouseup', this, Blockly.Bubble.bubbleMouseUp_);
// Blockly.Bubble.onMouseMoveWrapper_ = Blockly.bindEventWithChecks_(document,
// 'mousemove', this, this.bubbleMouseMove_);
// Blockly.hideChaff();
// // This event has been handled. No need to bubble up to the document.
// e.stopPropagation();
};
/**
@@ -445,8 +450,11 @@ Blockly.Bubble.prototype.positionBubble_ = function() {
left += this.relativeLeft_;
}
var top = this.relativeTop_ + this.anchorXY_.y;
this.bubbleGroup_.setAttribute('transform',
'translate(' + left + ',' + top + ')');
this.moveTo_(left, top);
};
Blockly.Bubble.prototype.moveTo_ = function(x, y) {
this.bubbleGroup_.setAttribute('transform', 'translate(' + x + ',' + y + ')');
};
/**
@@ -595,3 +603,43 @@ Blockly.Bubble.prototype.dispose = function() {
this.content_ = null;
this.shape_ = null;
};
Blockly.Bubble.prototype.moveToDragSurface = function(dragSurface) {
//var bubbleXY = this.getRelativeToSurfaceXY();
//var anchorXY = this.getAnchorRelativeToSurfaceXY();
// TODO: check RTL.
var x = this.anchorXY_.x + this.relativeLeft_;
var y = this.anchorXY_.y + this.relativeTop_;
this.savedRelativeXY_ =
new goog.math.Coordinate(this.relativeLeft_, this.relativeTop_);
this.savedAnchorXY_ = this.anchorXY_;
this.moveTo_(0, 0);
dragSurface.translateSurface(x, y);
// Execute the move on the top-level SVG component.
dragSurface.setBlocksAndShow(this.bubbleGroup_);
};
Blockly.Bubble.prototype.moveDuringDrag = function(dragSurface, newLoc) {
console.log(newLoc);
dragSurface.translateSurface(newLoc.x, newLoc.y);
this.relativeLeft_ = this.savedRelativeXY_.x + newLoc.x;
this.relativeTop_ = this.savedRelativeXY_.y + newLoc.y;
this.renderArrow_();
};
Blockly.Bubble.prototype.moveOffDragSurface = function(dragSurface, newXY) {
//this.savedAnchorXY_ = this.anchorXY_;
this.anchorXY_ = this.savedAnchorXY_;
this.moveTo_(newXY.x, newXY.y);
dragSurface.clearAndHide(this.workspace_.getBubbleCanvas());
};
Blockly.Bubble.prototype.getRelativeToSurfaceXY = function() {
// This may not be quite right. It's probably the top-left of the bubble
// group.
return new goog.math.Coordinate(this.anchorXY_.x + this.relativeLeft_,
this.anchorXY_.y + this.relativeTop_);
};

282
core/bubble_dragger.js Normal file
View File

@@ -0,0 +1,282 @@
/**
* @license
* Visual Blocks Editor
*
* Copyright 2018 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 Methods for dragging a bubble visually.
* @author fenichel@google.com (Rachel Fenichel)
*/
'use strict';
goog.provide('Blockly.BubbleDragger');
goog.require('Blockly.DraggedConnectionManager');
goog.require('goog.math.Coordinate');
goog.require('goog.asserts');
/**
* Class for a block dragger. It moves blocks around the workspace when they
* are being dragged by a mouse or touch.
* @param {!Blockly.Bubble} bubble The bubble to drag.
* @param {!Blockly.WorkspaceSvg} workspace The workspace to drag on.
* @constructor
*/
Blockly.BubbleDragger = function(bubble, workspace) {
/**
* The top block in the stack that is being dragged.
* @type {!Blockly.BlockSvg}
* @private
*/
this.draggingBubble_ = bubble;
/**
* The workspace on which the bubble is being dragged.
* @type {!Blockly.WorkspaceSvg}
* @private
*/
this.workspace_ = workspace;
/**
* Object that keeps track of connections on dragged blocks.
* @type {!Blockly.DraggedConnectionManager}
* @private
*/
this.draggedConnectionManager_ = null;
// new Blockly.DraggedConnectionManager(
// this.draggingBubble_);
/**
* Which delete area the mouse pointer is over, if any.
* One of {@link Blockly.DELETE_AREA_TRASH},
* {@link Blockly.DELETE_AREA_TOOLBOX}, or {@link Blockly.DELETE_AREA_NONE}.
* @type {?number}
* @private
*/
this.deleteArea_ = null;
/**
* Whether the block would be deleted if dropped immediately.
* @type {boolean}
* @private
*/
this.wouldDeleteBlock_ = false;
/**
* The location of the top left corner of the dragging block at the beginning
* of the drag in workspace coordinates.
* @type {!goog.math.Coordinate}
* @private
*/
this.startXY_ = this.draggingBubble_.getRelativeToSurfaceXY();
// TODO: validate, getters, etc.
this.dragSurface_ = workspace.blockDragSurface_;
};
/**
* Sever all links from this object.
* @package
*/
Blockly.BubbleDragger.prototype.dispose = function() {
this.draggingBubble_ = null;
this.workspace_ = null;
this.startWorkspace_ = null;
this.dragIconData_.length = 0;
if (this.draggedConnectionManager_) {
this.draggedConnectionManager_.dispose();
this.draggedConnectionManager_ = null;
}
};
/**
* Start dragging a block. This includes moving it to the drag surface.
* @param {!goog.math.Coordinate} currentDragDeltaXY How far the pointer has
* moved from the position at mouse down, in pixel units.
* @package
*/
Blockly.BubbleDragger.prototype.startBubbleDrag = function(currentDragDeltaXY) {
console.log('starting bubble drag');
if (!Blockly.Events.getGroup()) {
Blockly.Events.setGroup(true);
}
this.workspace_.setResizesEnabled(false);
// if (this.draggingBubble_.getParent()) {
// this.draggingBubble_.unplug();
// var delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY);
// var newLoc = goog.math.Coordinate.sum(this.startXY_, delta);
// this.draggingBubble_.translate(newLoc.x, newLoc.y);
// this.draggingBubble_.disconnectUiEffect();
// }
//this.draggingBubble_.setDragging(true);
// For future consideration: we may be able to put moveToDragSurface inside
// the block dragger, which would also let the block not track the block drag
// surface.
this.draggingBubble_.moveToDragSurface(this.dragSurface_);
if (this.workspace_.toolbox_) {
var style = this.draggingBubble_.isDeletable() ? 'blocklyToolboxDelete' :
'blocklyToolboxGrab';
this.workspace_.toolbox_.addStyle(style);
}
};
/**
* Execute a step of block dragging, based on the given event. Update the
* display accordingly.
* @param {!Event} e The most recent move event.
* @param {!goog.math.Coordinate} currentDragDeltaXY How far the pointer has
* moved from the position at the start of the drag, in pixel units.
* @package
*/
Blockly.BubbleDragger.prototype.dragBubble = function(e, currentDragDeltaXY) {
var delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY);
var newLoc = goog.math.Coordinate.sum(this.startXY_, delta);
this.draggingBubble_.moveDuringDrag(this.dragSurface_, newLoc);
//this.dragIcons_(delta);
this.deleteArea_ = this.workspace_.isDeleteArea(e);
//this.draggedConnectionManager_.update(delta, this.deleteArea_);
//this.updateCursorDuringBubbleDrag_();
};
/**
* Finish a block drag and put the block back on the workspace.
* @param {!Event} e The mouseup/touchend event.
* @param {!goog.math.Coordinate} currentDragDeltaXY How far the pointer has
* moved from the position at the start of the drag, in pixel units.
* @package
*/
Blockly.BubbleDragger.prototype.endBubbleDrag = function(e, currentDragDeltaXY) {
// Make sure internal state is fresh.
this.dragBubble(e, currentDragDeltaXY);
this.dragIconData_ = [];
Blockly.BlockSvg.disconnectUiStop_();
var delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY);
var newLoc = goog.math.Coordinate.sum(this.startXY_, delta);
this.draggingBubble_.moveOffDragSurface(this.dragSurface_, newLoc);
var deleted = true;//this.maybeDeleteBlock_();
if (!deleted) {
// These are expensive and don't need to be done if we're deleting.
this.draggingBubble_.moveConnections_(delta.x, delta.y);
this.draggingBubble_.setDragging(false);
//this.draggedConnectionManager_.applyConnections();
this.draggingBubble_.render();
this.fireMoveEvent_();
this.draggingBubble_.scheduleSnapAndBump();
}
this.workspace_.setResizesEnabled(true);
if (this.workspace_.toolbox_) {
var style = this.draggingBubble_.isDeletable() ? 'blocklyToolboxDelete' :
'blocklyToolboxGrab';
this.workspace_.toolbox_.removeStyle(style);
}
Blockly.Events.setGroup(false);
};
/**
* Fire a move event at the end of a block drag.
* @private
*/
Blockly.BubbleDragger.prototype.fireMoveEvent_ = function() {
var event = new Blockly.Events.BlockMove(this.draggingBubble_);
event.oldCoordinate = this.startXY_;
event.recordNew();
Blockly.Events.fire(event);
};
/**
* Shut the trash can and, if necessary, delete the dragging block.
* Should be called at the end of a block drag.
* @return {boolean} whether the block was deleted.
* @private
*/
Blockly.BubbleDragger.prototype.maybeDeleteBubble_ = function() {
var trashcan = this.workspace_.trashcan;
if (this.wouldDeleteBlock_) {
if (trashcan) {
goog.Timer.callOnce(trashcan.close, 100, trashcan);
}
// Fire a move event, so we know where to go back to for an undo.
this.fireMoveEvent_();
this.draggingBubble_.dispose(false, true);
} else if (trashcan) {
// Make sure the trash can is closed.
trashcan.close();
}
return this.wouldDeleteBlock_;
};
/**
* Update the cursor (and possibly the trash can lid) to reflect whether the
* dragging block would be deleted if released immediately.
* @private
*/
Blockly.BubbleDragger.prototype.updateCursorDuringBubbleDrag_ = function() {
this.wouldDeleteBlock_ = false; //this.draggedConnectionManager_.wouldDeleteBlock();
var trashcan = this.workspace_.trashcan;
if (this.wouldDeleteBlock_) {
this.draggingBubble_.setDeleteStyle(true);
if (this.deleteArea_ == Blockly.DELETE_AREA_TRASH && trashcan) {
trashcan.setOpen_(true);
}
} else {
this.draggingBubble_.setDeleteStyle(false);
if (trashcan) {
trashcan.setOpen_(false);
}
}
};
/**
* Convert a coordinate object from pixels to workspace units, including a
* correction for mutator workspaces.
* This function does not consider differing origins. It simply scales the
* input's x and y values.
* @param {!goog.math.Coordinate} pixelCoord A coordinate with x and y values
* in css pixel units.
* @return {!goog.math.Coordinate} The input coordinate divided by the workspace
* scale.
* @private
*/
Blockly.BubbleDragger.prototype.pixelsToWorkspaceUnits_ = function(pixelCoord) {
var result = new goog.math.Coordinate(pixelCoord.x / this.workspace_.scale,
pixelCoord.y / this.workspace_.scale);
if (this.workspace_.isMutator) {
// If we're in a mutator, its scale is always 1, purely because of some
// oddities in our rendering optimizations. The actual scale is the same as
// the scale on the parent workspace.
// Fix that for dragging.
var mainScale = this.workspace_.options.parentWorkspace.scale;
result = result.scale(1 / mainScale);
}
return result;
};

View File

@@ -28,6 +28,7 @@
goog.provide('Blockly.Gesture');
goog.require('Blockly.BlockDragger');
goog.require('Blockly.BubbleDragger');
goog.require('Blockly.constants');
goog.require('Blockly.FlyoutDragger');
goog.require('Blockly.Tooltip');
@@ -68,6 +69,14 @@ Blockly.Gesture = function(e, creatorWorkspace) {
*/
this.currentDragDeltaXY_ = 0;
/**
* The bubble that the gesture started on, or null if it did not start on a
* bubble.
* @type {Blockly.Bubble}
* @private
*/
this.startBubble_ = null;
/**
* The field that the gesture started on, or null if it did not start on a
* field.
@@ -136,6 +145,13 @@ Blockly.Gesture = function(e, creatorWorkspace) {
*/
this.isDraggingBlock_ = false;
/**
* Whether the bubble is currently being dragged.
* @type {boolean}
* @private
*/
this.isDraggingBubble_ = false;
/**
* The event that most recently updated this gesture.
* @type {!Event}
@@ -159,6 +175,13 @@ Blockly.Gesture = function(e, creatorWorkspace) {
*/
this.onUpWrapper_ = null;
/**
* The object tracking a bubble drag, or null if none is in progress.
* @type {Blockly.BubbleDragger}
* @private
*/
this.bubbleDragger_ = null;
/**
* The object tracking a block drag, or null if none is in progress.
* @type {Blockly.BlockDragger}
@@ -235,6 +258,10 @@ Blockly.Gesture.prototype.dispose = function() {
this.workspaceDragger_.dispose();
this.workspaceDragger_ = null;
}
if (this.bubbleDragger_) {
this.bubbleDragger_.dispose();
this.bubbleDragger_ = null;
}
};
/**
@@ -312,6 +339,25 @@ Blockly.Gesture.prototype.updateIsDraggingFromFlyout_ = function() {
return false;
};
/**
* Update this gesture to record whether a bubble is being dragged.
* This function should be called on a mouse/touch move event the first time the
* drag radius is exceeded. It should be called no more than once per gesture.
* If a bubble should be dragged this function creates the necessary
* BubbleDragger and starts the drag.
* @return {boolean} true if a bubble is being dragged.
* @private
*/
Blockly.Gesture.prototype.updateIsDraggingBubble_ = function() {
if (!this.startBubble_) {
return false;
}
this.isDraggingBubble_ = true;
this.startDraggingBubble_();
return true;
};
/**
* Update this gesture to record whether a block is being dragged.
* This function should be called on a mouse/touch move event the first time the
@@ -377,7 +423,11 @@ Blockly.Gesture.prototype.updateIsDragging_ = function() {
'updateIsDragging_ should only be called once per gesture.');
this.calledUpdateIsDragging_ = true;
// First check if it was a block drag.
// First check if it was a bubble drag. Bubbles always sit on top of blocks.
if (this.updateIsDraggingBubble_()) {
return;
}
// Then check if it was a block drag.
if (this.updateIsDraggingBlock_()) {
return;
}
@@ -397,6 +447,19 @@ Blockly.Gesture.prototype.startDraggingBlock_ = function() {
this.currentDragDeltaXY_);
};
/**
* Create a bubble dragger and start dragging the selected bubble.
* TODO (fenichel): Possibly combine this and startDraggingBlock_.
* @private
*/
Blockly.Gesture.prototype.startDraggingBubble_ = function() {
this.bubbleDragger_ = new Blockly.BubbleDragger(this.startBubble_,
this.startWorkspace_);
this.bubbleDragger_.startBubbleDrag(this.currentDragDeltaXY_);
this.bubbleDragger_.dragBubble(this.mostRecentEvent_,
this.currentDragDeltaXY_);
};
/**
* Start a gesture: update the workspace to indicate that a gesture is in
* progress and bind mousemove and mouseup handlers.
@@ -470,6 +533,9 @@ Blockly.Gesture.prototype.handleMove = function(e) {
} else if (this.isDraggingBlock_) {
this.blockDragger_.dragBlock(this.mostRecentEvent_,
this.currentDragDeltaXY_);
} else if (this.isDraggingBubble_) {
this.bubbleDragger_.dragBubble(this.mostRecentEvent_,
this.currentDragDeltaXY_);
}
e.preventDefault();
e.stopPropagation();
@@ -492,7 +558,11 @@ Blockly.Gesture.prototype.handleUp = function(e) {
// The ordering of these checks is important: drags have higher priority than
// clicks. Fields have higher priority than blocks; blocks have higher
// priority than workspaces.
if (this.isDraggingBlock_) {
// The ordering within drags does not matter, because the three types of
// dragging are exclusive.
if (this.isDraggingBubble_) {
this.bubbleDragger_.endBubbleDrag(e, this.currentDragDeltaXY_);
} else if (this.isDraggingBlock_) {
this.blockDragger_.endBlockDrag(e, this.currentDragDeltaXY_);
} else if (this.isDraggingWorkspace_) {
this.workspaceDragger_.endDrag(this.currentDragDeltaXY_);
@@ -522,7 +592,10 @@ Blockly.Gesture.prototype.cancel = function() {
return;
}
Blockly.longStop_();
if (this.isDraggingBlock_) {
if (this.isDraggingBubble_) {
this.bubbleDragger_.endBubbleDrag(this.mostRecentEvent_,
this.currentDragDeltaXY_);
} else if (this.isDraggingBlock_) {
this.blockDragger_.endBlockDrag(this.mostRecentEvent_,
this.currentDragDeltaXY_);
} else if (this.isDraggingWorkspace_) {
@@ -546,6 +619,7 @@ Blockly.Gesture.prototype.handleRightClick = function(e) {
this.startWorkspace_.showContextMenu_(e);
}
// TODO: Handle right-click on a bubble.
e.preventDefault();
e.stopPropagation();
@@ -595,6 +669,20 @@ Blockly.Gesture.prototype.handleBlockStart = function(e, block) {
this.mostRecentEvent_ = e;
};
/**
* Handle a mousedown/touchstart event on a bubble.
* @param {!Event} e A mouse down or touch start event.
* @param {!Blockly.Bubble} bubble The bubble the event hit.
* @package
*/
Blockly.Gesture.prototype.handleBubbleStart = function(e, bubble) {
goog.asserts.assert(!this.hasStarted_,
'Tried to call gesture.handleBubbleStart, but the gesture had already ' +
'been started.');
this.setStartBubble(bubble);
this.mostRecentEvent_ = e;
};
/* Begin functions defining what actions to take to execute clicks on each type
* of target. Any developer wanting to add behaviour on clicks should modify
* only this code. */
@@ -644,6 +732,7 @@ Blockly.Gesture.prototype.doWorkspaceClick_ = function() {
/* End functions defining what actions to take to execute clicks on each type
* of target. */
// TODO (fenichel): Move bubbles to the front.
/**
* Move the dragged/clicked block to the front of the workspace so that it is
* not occluded by other blocks.
@@ -672,6 +761,17 @@ Blockly.Gesture.prototype.setStartField = function(field) {
}
};
/**
* Record the bubble that a gesture started on
* @param {Blockly.Bubble} bubble The bubble the gesture started on.
* @package
*/
Blockly.Gesture.prototype.setStartBubble = function(bubble) {
if (!this.startBubble_) {
this.startBubble_ = bubble;
}
};
/**
* Record the block that a gesture started on, and set the target block
* appropriately.
@@ -778,7 +878,8 @@ Blockly.Gesture.prototype.isWorkspaceClick_ = function() {
* @package
*/
Blockly.Gesture.prototype.isDragging = function() {
return this.isDraggingWorkspace_ || this.isDraggingBlock_;
return this.isDraggingWorkspace_ || this.isDraggingBlock_ ||
this.isDraggingBubble_;
};
/**