diff --git a/core/block_svg.js b/core/block_svg.js index ee70a93d1..036de42f6 100644 --- a/core/block_svg.js +++ b/core/block_svg.js @@ -528,7 +528,6 @@ Blockly.BlockSvg.prototype.onMouseDown_ = function(e) { return; } if (this.isInFlyout) { - e.stopPropagation(); return; } this.workspace.markFocused(); diff --git a/core/flyout.js b/core/flyout.js index c65276fea..c93d26ee6 100644 --- a/core/flyout.js +++ b/core/flyout.js @@ -114,6 +114,13 @@ Blockly.Flyout.prototype.autoClose = true; */ Blockly.Flyout.prototype.CORNER_RADIUS = 8; +/** + * Number of pixels the mouse must move before a drag/scroll starts. Because the + * drag-intention is determined when this is reached, it is larger than + * Blockly.DRAG_RADIUS so that the drag-direction is clearer. + */ +Blockly.Flyout.prototype.DRAG_RADIUS = 10; + /** * Margin around the edges of the blocks in the flyout. * @type {number} @@ -142,6 +149,33 @@ Blockly.Flyout.prototype.width_ = 0; */ Blockly.Flyout.prototype.height_ = 0; +/** + * Is the flyout dragging (scrolling)? + * 0 - DRAG_NONE - no drag is ongoing or state is undetermined. + * 1 - DRAG_STICKY - still within the sticky drag radius. + * 2 - DRAG_FREE - in scroll mode (never create a new block). + * @private + */ +Blockly.Flyout.prototype.dragMode_ = Blockly.DRAG_NONE; + +/** + * Range of a drag angle from a flyout considered "dragging toward workspace". + * Drags that are within the bounds of this many degrees from the orthogonal + * line to the flyout edge are considered to be "drags toward the workspace". + * Example: + * Flyout Edge Workspace + * [block] / <-within this angle, drags "toward workspace" | + * [block] ---- orthogonal to flyout boundary ---- | + * [block] \ | + * The angle is given in degrees from the orthogonal. + * + * This is used to know when to create a new block and when to scroll the + * flyout. Setting it to 360 means that all drags create a new block. + * @type {number} + * @private +*/ +Blockly.Flyout.prototype.dragAngleRange_ = 70; + /** * Creates the flyout's DOM. Only needs to be called once. * @return {!Element} The flyout's SVG group. @@ -679,17 +713,10 @@ Blockly.Flyout.prototype.clearOldBlocks_ = function() { * @private */ Blockly.Flyout.prototype.addBlockListeners_ = function(root, block, rect) { - if (this.autoClose) { - this.listeners_.push(Blockly.bindEvent_(root, 'mousedown', null, - this.createBlockFunc_(block))); - this.listeners_.push(Blockly.bindEvent_(rect, 'mousedown', null, - this.createBlockFunc_(block))); - } else { - this.listeners_.push(Blockly.bindEvent_(root, 'mousedown', null, - this.blockMouseDown_(block))); - this.listeners_.push(Blockly.bindEvent_(rect, 'mousedown', null, - this.blockMouseDown_(block))); - } + this.listeners_.push(Blockly.bindEvent_(root, 'mousedown', null, + this.blockMouseDown_(block))); + this.listeners_.push(Blockly.bindEvent_(rect, 'mousedown', null, + this.blockMouseDown_(block))); this.listeners_.push(Blockly.bindEvent_(root, 'mouseover', block, block.addSelect)); this.listeners_.push(Blockly.bindEvent_(root, 'mouseout', block, @@ -710,7 +737,7 @@ Blockly.Flyout.prototype.blockMouseDown_ = function(block) { var flyout = this; return function(e) { Blockly.terminateDrag_(); - Blockly.hideChaff(); + Blockly.hideChaff(true); if (Blockly.isRightButton(e)) { // Right-click. block.showContextMenu_(e); @@ -718,16 +745,19 @@ Blockly.Flyout.prototype.blockMouseDown_ = function(block) { // Left-click (or middle click) Blockly.Css.setCursor(Blockly.Css.Cursor.CLOSED); // Record the current mouse position. + flyout.startDragMouseY_ = e.clientY; + flyout.startDragMouseX_ = e.clientX; Blockly.Flyout.startDownEvent_ = e; Blockly.Flyout.startBlock_ = block; Blockly.Flyout.startFlyout_ = flyout; Blockly.Flyout.onMouseUpWrapper_ = Blockly.bindEvent_(document, - 'mouseup', this, flyout.onMouseUp_); + 'mouseup', flyout, flyout.onMouseUp_); Blockly.Flyout.onMouseMoveBlockWrapper_ = Blockly.bindEvent_(document, - 'mousemove', this, flyout.onMouseMoveBlock_); + 'mousemove', flyout, flyout.onMouseMoveBlock_); } // This event has been handled. No need to bubble up to the document. e.stopPropagation(); + e.preventDefault(); }; }; @@ -741,7 +771,7 @@ Blockly.Flyout.prototype.onMouseDown_ = function(e) { return; } Blockly.hideChaff(true); - Blockly.Flyout.terminateDrag_(); + this.dragMode_ = Blockly.DRAG_FREE; this.startDragMouseY_ = e.clientY; this.startDragMouseX_ = e.clientX; Blockly.Flyout.onMouseMoveWrapper_ = Blockly.bindEvent_(document, 'mousemove', @@ -761,10 +791,15 @@ Blockly.Flyout.prototype.onMouseDown_ = function(e) { * @private */ Blockly.Flyout.prototype.onMouseUp_ = function(e) { - if (!this.workspace_.isDragging() && !Blockly.WidgetDiv.isVisible()) { - Blockly.Events.fire( - new Blockly.Events.Ui(Blockly.Flyout.startBlock_, 'click', - undefined, undefined)); + if (!this.workspace_.isDragging()) { + if (this.autoClose) { + this.createBlockFunc_(Blockly.Flyout.startBlock_)( + Blockly.Flyout.startDownEvent_); + } else if (!Blockly.WidgetDiv.isVisible()) { + Blockly.Events.fire( + new Blockly.Events.Ui(Blockly.Flyout.startBlock_, 'click', + undefined, undefined)); + } } Blockly.terminateDrag_(); }; @@ -817,12 +852,90 @@ Blockly.Flyout.prototype.onMouseMoveBlock_ = function(e) { } var dx = e.clientX - Blockly.Flyout.startDownEvent_.clientX; var dy = e.clientY - Blockly.Flyout.startDownEvent_.clientY; - // Still dragging within the sticky DRAG_RADIUS. - if (Math.sqrt(dx * dx + dy * dy) > Blockly.DRAG_RADIUS) { - // Create the block. - Blockly.Flyout.startFlyout_.createBlockFunc_(Blockly.Flyout.startBlock_)( + + var createBlock = this.determineDragIntention_(dx, dy); + if (createBlock) { + this.createBlockFunc_(Blockly.Flyout.startBlock_)( Blockly.Flyout.startDownEvent_); + } else if (this.dragMode_ == Blockly.DRAG_FREE) { + // Do a scroll. + this.onMouseMove_(e); } + e.stopPropagation(); +}; + +/** + * Determine the intention of a drag. + * Updates dragMode_ based on a drag delta and the current mode, + * and returns true if we should create a new block. + * @param {number} dx X delta of the drag. + * @param {number} dy Y delta of the drag. + * @return {boolean} True if a new block should be created. + * @private + */ +Blockly.Flyout.prototype.determineDragIntention_ = function(dx, dy) { + if (this.dragMode_ == Blockly.DRAG_FREE) { + // Once in free mode, always stay in free mode and never create a block. + return false; + } + var dragDistance = Math.sqrt(dx * dx + dy * dy); + if (dragDistance < this.DRAG_RADIUS) { + // Still within the sticky drag radius. + this.dragMode_ = Blockly.DRAG_STICKY; + return false; + } else { + if (this.isDragTowardWorkspace_(dx, dy) || !this.scrollbar_.isVisible()) { + // Immediately create a block. + return true; + } else { + // Immediately move to free mode - the drag is away from the workspace. + this.dragMode_ = Blockly.DRAG_FREE; + return false; + } + } +}; + +/** + * Determine if a drag delta is toward the workspace, based on the position + * and orientation of the flyout. This is used in determineDragIntention_ to + * determine if a new block should be created or if the flyout should scroll. + * @param {number} dx X delta of the drag. + * @param {number} dy Y delta of the drag. + * @return {boolean} true if the drag is toward the workspace. + * @private + */ +Blockly.Flyout.prototype.isDragTowardWorkspace_ = function(dx, dy) { + // Direction goes from -180 to 180, with 0 toward the right and 90 on top. + var dragDirection = Math.atan2(dy, dx) / Math.PI * 180; + + var draggingTowardWorkspace = false; + var range = this.dragAngleRange_; + if (this.horizontalLayout_) { + if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_TOP) { + // Horizontal at top. + if (dragDirection < 90 + range && dragDirection > 90 - range) { + draggingTowardWorkspace = true; + } + } else { + // Horizontal at bottom. + if (dragDirection > -90 - range && dragDirection < -90 + range) { + draggingTowardWorkspace = true; + } + } + } else { + if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_LEFT) { + // Vertical at left. + if (dragDirection < range && dragDirection > -range) { + draggingTowardWorkspace = true; + } + } else { + // Vertical at right. + if (dragDirection < -180 + range || dragDirection > 180 - range) { + draggingTowardWorkspace = true; + } + } + } + return draggingTowardWorkspace; }; /** @@ -990,6 +1103,9 @@ Blockly.Flyout.prototype.getClientRect = function() { * @private */ Blockly.Flyout.terminateDrag_ = function() { + if (Blockly.Flyout.startFlyout_) { + Blockly.Flyout.startFlyout_.dragMode_ = Blockly.DRAG_NONE; + } if (Blockly.Flyout.onMouseUpWrapper_) { Blockly.unbindEvent_(Blockly.Flyout.onMouseUpWrapper_); Blockly.Flyout.onMouseUpWrapper_ = null;