diff --git a/blockly_uncompressed.js b/blockly_uncompressed.js index 48244f0f049..0a3a3e3363b 100644 --- a/blockly_uncompressed.js +++ b/blockly_uncompressed.js @@ -42,6 +42,8 @@ goog.addDependency('../../core/contextmenu.js', ['Blockly.ContextMenu'], ['Block goog.addDependency('../../core/contextmenu_items.js', ['Blockly.ContextMenuItems'], ['Blockly.ContextMenuRegistry', 'Blockly.Events', 'Blockly.constants', 'Blockly.inputTypes'], {'lang': 'es5'}); goog.addDependency('../../core/contextmenu_registry.js', ['Blockly.ContextMenuRegistry'], [], {'lang': 'es5'}); goog.addDependency('../../core/css.js', ['Blockly.Css'], [], {'lang': 'es5'}); +goog.addDependency('../../core/delete_area.js', ['Blockly.DeleteArea'], ['Blockly.DragTarget', 'Blockly.IDeleteArea']); +goog.addDependency('../../core/drag_target.js', ['Blockly.DragTarget'], ['Blockly.IDragTarget']); goog.addDependency('../../core/dropdowndiv.js', ['Blockly.DropDownDiv'], ['Blockly.utils.Rect', 'Blockly.utils.dom', 'Blockly.utils.math', 'Blockly.utils.style']); goog.addDependency('../../core/events/block_events.js', ['Blockly.Events.BlockBase', 'Blockly.Events.BlockChange', 'Blockly.Events.BlockCreate', 'Blockly.Events.BlockDelete', 'Blockly.Events.BlockMove', 'Blockly.Events.Change', 'Blockly.Events.Create', 'Blockly.Events.Delete', 'Blockly.Events.Move'], ['Blockly.Events', 'Blockly.Events.Abstract', 'Blockly.Xml', 'Blockly.connectionTypes', 'Blockly.registry', 'Blockly.utils.Coordinate', 'Blockly.utils.object', 'Blockly.utils.xml']); goog.addDependency('../../core/events/events.js', ['Blockly.Events'], ['Blockly.registry', 'Blockly.utils']); @@ -73,11 +75,11 @@ goog.addDependency('../../core/field_number.js', ['Blockly.FieldNumber'], ['Bloc goog.addDependency('../../core/field_registry.js', ['Blockly.fieldRegistry'], ['Blockly.registry']); goog.addDependency('../../core/field_textinput.js', ['Blockly.FieldTextInput'], ['Blockly.DropDownDiv', 'Blockly.Events', 'Blockly.Events.BlockChange', 'Blockly.Field', 'Blockly.Msg', 'Blockly.WidgetDiv', 'Blockly.browserEvents', 'Blockly.fieldRegistry', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.KeyCodes', 'Blockly.utils.aria', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.userAgent']); goog.addDependency('../../core/field_variable.js', ['Blockly.FieldVariable'], ['Blockly.Events.BlockChange', 'Blockly.FieldDropdown', 'Blockly.Msg', 'Blockly.VariableModel', 'Blockly.Variables', 'Blockly.Xml', 'Blockly.constants', 'Blockly.fieldRegistry', 'Blockly.utils', 'Blockly.utils.Size', 'Blockly.utils.object']); -goog.addDependency('../../core/flyout_base.js', ['Blockly.Flyout'], ['Blockly.Block', 'Blockly.Events', 'Blockly.Events.BlockCreate', 'Blockly.Events.VarCreate', 'Blockly.FlyoutMetricsManager', 'Blockly.Gesture', 'Blockly.IDeleteArea', 'Blockly.IFlyout', 'Blockly.ScrollbarPair', 'Blockly.Tooltip', 'Blockly.Touch', 'Blockly.WorkspaceSvg', 'Blockly.Xml', 'Blockly.blockRendering', 'Blockly.browserEvents', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.toolbox', 'Blockly.utils.xml']); +goog.addDependency('../../core/flyout_base.js', ['Blockly.Flyout'], ['Blockly.Block', 'Blockly.DeleteArea', 'Blockly.Events', 'Blockly.Events.BlockCreate', 'Blockly.Events.VarCreate', 'Blockly.FlyoutMetricsManager', 'Blockly.Gesture', 'Blockly.IFlyout', 'Blockly.ScrollbarPair', 'Blockly.Tooltip', 'Blockly.Touch', 'Blockly.WorkspaceSvg', 'Blockly.Xml', 'Blockly.blockRendering', 'Blockly.browserEvents', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.toolbox', 'Blockly.utils.xml']); goog.addDependency('../../core/flyout_button.js', ['Blockly.FlyoutButton'], ['Blockly.Css', 'Blockly.browserEvents', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.style'], {'lang': 'es5'}); goog.addDependency('../../core/flyout_horizontal.js', ['Blockly.HorizontalFlyout'], ['Blockly.Block', 'Blockly.DropDownDiv', 'Blockly.Flyout', 'Blockly.Scrollbar', 'Blockly.WidgetDiv', 'Blockly.constants', 'Blockly.registry', 'Blockly.utils', 'Blockly.utils.Rect', 'Blockly.utils.object', 'Blockly.utils.toolbox']); goog.addDependency('../../core/flyout_vertical.js', ['Blockly.VerticalFlyout'], ['Blockly.Block', 'Blockly.DropDownDiv', 'Blockly.Flyout', 'Blockly.Scrollbar', 'Blockly.WidgetDiv', 'Blockly.constants', 'Blockly.registry', 'Blockly.utils', 'Blockly.utils.Rect', 'Blockly.utils.object', 'Blockly.utils.toolbox']); -goog.addDependency('../../core/generator.js', ['Blockly.Generator'], ['Blockly.Block', 'Blockly.constants']); +goog.addDependency('../../core/generator.js', ['Blockly.Generator'], ['Blockly.Block', 'Blockly.constants', 'Blockly.utils.deprecation']); goog.addDependency('../../core/gesture.js', ['Blockly.Gesture'], ['Blockly.BlockDragger', 'Blockly.BubbleDragger', 'Blockly.Events', 'Blockly.Events.Click', 'Blockly.Tooltip', 'Blockly.Touch', 'Blockly.Workspace', 'Blockly.WorkspaceDragger', 'Blockly.blockAnimations', 'Blockly.browserEvents', 'Blockly.constants', 'Blockly.utils', 'Blockly.utils.Coordinate']); goog.addDependency('../../core/grid.js', ['Blockly.Grid'], ['Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.userAgent']); goog.addDependency('../../core/icon.js', ['Blockly.Icon'], ['Blockly.browserEvents', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.Size', 'Blockly.utils.Svg', 'Blockly.utils.dom']); @@ -94,7 +96,8 @@ goog.addDependency('../../core/interfaces/i_connection_checker.js', ['Blockly.IC goog.addDependency('../../core/interfaces/i_contextmenu.js', ['Blockly.IContextMenu'], []); goog.addDependency('../../core/interfaces/i_copyable.js', ['Blockly.ICopyable'], []); goog.addDependency('../../core/interfaces/i_deletable.js', ['Blockly.IDeletable'], []); -goog.addDependency('../../core/interfaces/i_deletearea.js', ['Blockly.IDeleteArea'], []); +goog.addDependency('../../core/interfaces/i_delete_area.js', ['Blockly.IDeleteArea'], ['Blockly.IDragTarget']); +goog.addDependency('../../core/interfaces/i_drag_target.js', ['Blockly.IDragTarget'], ['Blockly.IComponent']); goog.addDependency('../../core/interfaces/i_flyout.js', ['Blockly.IFlyout'], []); goog.addDependency('../../core/interfaces/i_metrics_manager.js', ['Blockly.IMetricsManager'], []); goog.addDependency('../../core/interfaces/i_movable.js', ['Blockly.IMovable'], []); @@ -174,12 +177,12 @@ goog.addDependency('../../core/theme_manager.js', ['Blockly.ThemeManager'], ['Bl goog.addDependency('../../core/toolbox/category.js', ['Blockly.ToolboxCategory'], ['Blockly.ISelectableToolboxItem', 'Blockly.ToolboxItem', 'Blockly.registry', 'Blockly.utils', 'Blockly.utils.aria', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.toolbox'], {'lang': 'es5'}); goog.addDependency('../../core/toolbox/collapsible_category.js', ['Blockly.CollapsibleToolboxCategory'], ['Blockly.ICollapsibleToolboxItem', 'Blockly.ToolboxCategory', 'Blockly.ToolboxItem', 'Blockly.ToolboxSeparator', 'Blockly.registry', 'Blockly.utils.aria', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.toolbox']); goog.addDependency('../../core/toolbox/separator.js', ['Blockly.ToolboxSeparator'], ['Blockly.IToolboxItem', 'Blockly.ToolboxItem', 'Blockly.registry', 'Blockly.utils.dom'], {'lang': 'es5'}); -goog.addDependency('../../core/toolbox/toolbox.js', ['Blockly.Toolbox'], ['Blockly.CollapsibleToolboxCategory', 'Blockly.Css', 'Blockly.Events', 'Blockly.Events.ToolboxItemSelect', 'Blockly.IAutoHideable', 'Blockly.IDeleteArea', 'Blockly.IKeyboardAccessible', 'Blockly.IStyleable', 'Blockly.IToolbox', 'Blockly.Options', 'Blockly.Touch', 'Blockly.browserEvents', 'Blockly.constants', 'Blockly.registry', 'Blockly.utils', 'Blockly.utils.Rect', 'Blockly.utils.aria', 'Blockly.utils.dom', 'Blockly.utils.toolbox'], {'lang': 'es5'}); +goog.addDependency('../../core/toolbox/toolbox.js', ['Blockly.Toolbox'], ['Blockly.CollapsibleToolboxCategory', 'Blockly.Css', 'Blockly.DeleteArea', 'Blockly.Events', 'Blockly.Events.ToolboxItemSelect', 'Blockly.IAutoHideable', 'Blockly.IKeyboardAccessible', 'Blockly.IStyleable', 'Blockly.IToolbox', 'Blockly.Options', 'Blockly.Touch', 'Blockly.browserEvents', 'Blockly.constants', 'Blockly.registry', 'Blockly.utils', 'Blockly.utils.Rect', 'Blockly.utils.aria', 'Blockly.utils.dom', 'Blockly.utils.toolbox'], {'lang': 'es5'}); goog.addDependency('../../core/toolbox/toolbox_item.js', ['Blockly.ToolboxItem'], ['Blockly.IToolboxItem']); goog.addDependency('../../core/tooltip.js', ['Blockly.Tooltip'], ['Blockly.browserEvents', 'Blockly.utils.string']); goog.addDependency('../../core/touch.js', ['Blockly.Touch'], ['Blockly.constants', 'Blockly.utils', 'Blockly.utils.global', 'Blockly.utils.string']); goog.addDependency('../../core/touch_gesture.js', ['Blockly.TouchGesture'], ['Blockly.Gesture', 'Blockly.browserEvents', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.object']); -goog.addDependency('../../core/trashcan.js', ['Blockly.Trashcan'], ['Blockly.Events', 'Blockly.Events.TrashcanOpen', 'Blockly.IAutoHideable', 'Blockly.IDeleteArea', 'Blockly.IPositionable', 'Blockly.Options', 'Blockly.Xml', 'Blockly.browserEvents', 'Blockly.constants', 'Blockly.registry', 'Blockly.uiPosition', 'Blockly.utils.Rect', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.toolbox'], {'lang': 'es5'}); +goog.addDependency('../../core/trashcan.js', ['Blockly.Trashcan'], ['Blockly.DeleteArea', 'Blockly.Events', 'Blockly.Events.TrashcanOpen', 'Blockly.IAutoHideable', 'Blockly.IPositionable', 'Blockly.Options', 'Blockly.Xml', 'Blockly.browserEvents', 'Blockly.constants', 'Blockly.registry', 'Blockly.uiPosition', 'Blockly.utils.Rect', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.toolbox'], {'lang': 'es5'}); goog.addDependency('../../core/utils.js', ['Blockly.utils'], ['Blockly.Msg', 'Blockly.constants', 'Blockly.utils.Coordinate', 'Blockly.utils.Rect', 'Blockly.utils.colour', 'Blockly.utils.global', 'Blockly.utils.string', 'Blockly.utils.style', 'Blockly.utils.userAgent']); goog.addDependency('../../core/utils/aria.js', ['Blockly.utils.aria'], []); goog.addDependency('../../core/utils/colour.js', ['Blockly.utils.colour'], []); diff --git a/core/block_dragger.js b/core/block_dragger.js index 3fb9dc7ed46..c9c4774309f 100644 --- a/core/block_dragger.js +++ b/core/block_dragger.js @@ -25,6 +25,7 @@ goog.require('Blockly.utils.Coordinate'); goog.require('Blockly.utils.dom'); goog.requireType('Blockly.BlockSvg'); +goog.requireType('Blockly.IDragTarget'); goog.requireType('Blockly.WorkspaceSvg'); @@ -59,13 +60,11 @@ Blockly.BlockDragger = function(block, workspace) { this.draggingBlock_); /** - * 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} + * Which drag area the mouse pointer is over, if any. + * @type {?Blockly.IDragTarget} * @private */ - this.deleteArea_ = null; + this.dragTarget_ = null; /** * Whether the block would be deleted if dropped immediately. @@ -210,8 +209,15 @@ Blockly.BlockDragger.prototype.dragBlock = function(e, currentDragDeltaXY) { this.draggingBlock_.moveDuringDrag(newLoc); this.dragIcons_(delta); - this.deleteArea_ = this.workspace_.isDeleteArea(e); - this.draggedConnectionManager_.update(delta, this.deleteArea_); + var oldDragTarget = this.dragTarget_; + this.dragTarget_ = this.workspace_.getDragTarget(e); + if (this.dragTarget_ !== oldDragTarget) { + oldDragTarget && oldDragTarget.onDragExit(); + this.dragTarget_ && this.dragTarget_.onDragEnter(); + } + + this.draggedConnectionManager_.update(delta, this.dragTarget_); + this.wouldDeleteBlock_ = this.draggedConnectionManager_.wouldDeleteBlock(); this.updateCursorDuringBlockDrag_(); }; @@ -237,8 +243,16 @@ Blockly.BlockDragger.prototype.endBlockDrag = function(e, currentDragDeltaXY) { var newLoc = Blockly.utils.Coordinate.sum(this.startXY_, delta); this.draggingBlock_.moveOffDragSurface(newLoc); - var deleted = this.maybeDeleteBlock_(); - if (!deleted) { + if (this.dragTarget_) { + this.dragTarget_.onBlockDrop(this.draggingBlock_); + } + + if (this.wouldDeleteBlock_) { + // Fire a move event, so we know where to go back to for an undo. + this.fireMoveEvent_(); + this.draggingBlock_.dispose(false, true); + Blockly.draggingConnections = []; + } else { // These are expensive and don't need to be done if we're deleting. this.draggingBlock_.moveConnections(delta.x, delta.y); this.draggingBlock_.setDragging(false); @@ -284,49 +298,13 @@ Blockly.BlockDragger.prototype.fireMoveEvent_ = function() { 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.BlockDragger.prototype.maybeDeleteBlock_ = function() { - var trashcan = this.workspace_.trashcan; - - if (this.wouldDeleteBlock_) { - if (trashcan) { - setTimeout(trashcan.closeLid.bind(trashcan), 100); - } - // Fire a move event, so we know where to go back to for an undo. - this.fireMoveEvent_(); - this.draggingBlock_.dispose(false, true); - Blockly.draggingConnections = []; - } else if (trashcan) { - // Make sure the trash can lid is closed. - trashcan.closeLid(); - } - 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.BlockDragger.prototype.updateCursorDuringBlockDrag_ = function() { - this.wouldDeleteBlock_ = this.draggedConnectionManager_.wouldDeleteBlock(); - var trashcan = this.workspace_.trashcan; - if (this.wouldDeleteBlock_) { - this.draggingBlock_.setDeleteStyle(true); - if (this.deleteArea_ == Blockly.DELETE_AREA_TRASH && trashcan) { - trashcan.setLidOpen(true); - } - } else { - this.draggingBlock_.setDeleteStyle(false); - if (trashcan) { - trashcan.setLidOpen(false); - } - } + this.draggingBlock_.setDeleteStyle(this.wouldDeleteBlock_); }; /** diff --git a/core/bubble_dragger.js b/core/bubble_dragger.js index 2f75276dd00..38c33915805 100644 --- a/core/bubble_dragger.js +++ b/core/bubble_dragger.js @@ -51,13 +51,11 @@ Blockly.BubbleDragger = function(bubble, workspace) { this.workspace_ = workspace; /** - * 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} + * Which drag target the mouse pointer is over, if any. + * @type {?Blockly.IDragTarget} * @private */ - this.deleteArea_ = null; + this.dragTarget_ = null; /** * Whether the bubble would be deleted if dropped immediately. @@ -136,33 +134,41 @@ Blockly.BubbleDragger.prototype.dragBubble = function(e, currentDragDeltaXY) { this.draggingBubble_.moveDuringDrag(this.dragSurface_, newLoc); - if (this.draggingBubble_.isDeletable()) { - this.deleteArea_ = this.workspace_.isDeleteArea(e); - this.updateCursorDuringBubbleDrag_(); + var oldDragTarget = this.dragTarget_; + this.dragTarget_ = this.workspace_.getDragTarget(e); + if (this.dragTarget_ !== oldDragTarget) { + oldDragTarget && oldDragTarget.onDragExit(); + this.dragTarget_ && this.dragTarget_.onDragEnter(); } + this.wouldDeleteBubble_ = this.shouldDelete_(this.dragTarget_); + + this.updateCursorDuringBubbleDrag_(); }; /** - * Shut the trash can and, if necessary, delete the dragging bubble. - * Should be called at the end of a bubble drag. - * @return {boolean} Whether the bubble was deleted. + * Whether ending the drag would delete the bubble. + * @param {?Blockly.IDragTarget} dragTarget The drag target that the bubblee is + * currently over. + * @return {boolean} Whether dropping the bubble immediately would delete the + * block. * @private */ -Blockly.BubbleDragger.prototype.maybeDeleteBubble_ = function() { - var trashcan = this.workspace_.trashcan; +Blockly.BubbleDragger.prototype.shouldDelete_ = function(dragTarget) { + var couldDeleteBubble = this.draggingBubble_.isDeletable(); - if (this.wouldDeleteBubble_) { - if (trashcan) { - setTimeout(trashcan.closeLid.bind(trashcan), 100); + if (couldDeleteBubble && dragTarget) { + // TODO(#4881) use hasCapability instead of getComponents + var deleteAreas = this.workspace_.getComponentManager().getComponents( + Blockly.ComponentManager.Capability.DELETE_AREA, false); + var isDeleteArea = deleteAreas.some(function(deleteArea) { + return dragTarget === deleteArea; + }); + if (isDeleteArea) { + return (/** @type {!Blockly.IDeleteArea} */ (dragTarget)) + .wouldDeleteBubble(this.draggingBubble_); } - // 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 lid is closed. - trashcan.closeLid(); } - return this.wouldDeleteBubble_; + return false; }; /** @@ -171,18 +177,10 @@ Blockly.BubbleDragger.prototype.maybeDeleteBubble_ = function() { * @private */ Blockly.BubbleDragger.prototype.updateCursorDuringBubbleDrag_ = function() { - this.wouldDeleteBubble_ = this.deleteArea_ != Blockly.DELETE_AREA_NONE; - var trashcan = this.workspace_.trashcan; if (this.wouldDeleteBubble_) { this.draggingBubble_.setDeleteStyle(true); - if (this.deleteArea_ == Blockly.DELETE_AREA_TRASH && trashcan) { - trashcan.setLidOpen(true); - } } else { this.draggingBubble_.setDeleteStyle(false); - if (trashcan) { - trashcan.setLidOpen(false); - } } }; @@ -200,12 +198,18 @@ Blockly.BubbleDragger.prototype.endBubbleDrag = function( var delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY); var newLoc = Blockly.utils.Coordinate.sum(this.startXY_, delta); - // Move the bubble to its final location. this.draggingBubble_.moveTo(newLoc.x, newLoc.y); - var deleted = this.maybeDeleteBubble_(); - if (!deleted) { + if (this.dragTarget_) { + this.dragTarget_.onBubbleDrop(this.draggingBubble_); + } + + if (this.wouldDeleteBubble_) { + // Fire a move event, so we know where to go back to for an undo. + this.fireMoveEvent_(); + this.draggingBubble_.dispose(false, true); + } else { // Put everything back onto the bubble canvas. if (this.dragSurface_) { this.dragSurface_.clearAndHide(this.workspace_.getBubbleCanvas()); diff --git a/core/component_manager.js b/core/component_manager.js index ddb2091b524..e5b7c41924b 100644 --- a/core/component_manager.js +++ b/core/component_manager.js @@ -15,6 +15,8 @@ goog.provide('Blockly.ComponentManager'); goog.requireType('Blockly.IAutoHideable'); goog.requireType('Blockly.IComponent'); +goog.requireType('Blockly.IDeleteArea'); +goog.requireType('Blockly.IDragTarget'); goog.requireType('Blockly.IPositionable'); @@ -206,6 +208,15 @@ Blockly.ComponentManager.Capability.prototype.toString = function() { /** @type {!Blockly.ComponentManager.Capability} */ Blockly.ComponentManager.Capability.POSITIONABLE = new Blockly.ComponentManager.Capability('positionable'); + +/** @type {!Blockly.ComponentManager.Capability} */ +Blockly.ComponentManager.Capability.DRAG_TARGET = + new Blockly.ComponentManager.Capability('drag_target'); + +/** @type {!Blockly.ComponentManager.Capability} */ +Blockly.ComponentManager.Capability.DELETE_AREA = + new Blockly.ComponentManager.Capability('delete_area'); + /** @type {!Blockly.ComponentManager.Capability} */ Blockly.ComponentManager.Capability.AUTOHIDEABLE = new Blockly.ComponentManager.Capability('autohideable'); diff --git a/core/constants.js b/core/constants.js index 522544abf41..1d19fb8d5d0 100644 --- a/core/constants.js +++ b/core/constants.js @@ -161,26 +161,6 @@ Blockly.OPPOSITE_TYPE[Blockly.connectionTypes.NEXT_STATEMENT] = Blockly.OPPOSITE_TYPE[Blockly.connectionTypes.PREVIOUS_STATEMENT] = Blockly.connectionTypes.NEXT_STATEMENT; -/** - * ENUM representing that an event is not in any delete areas. - * Null for backwards compatibility reasons. - * @const - */ -Blockly.DELETE_AREA_NONE = null; - -/** - * ENUM representing that an event is in the delete area of the trash can. - * @const - */ -Blockly.DELETE_AREA_TRASH = 1; - -/** - * ENUM representing that an event is in the delete area of the toolbox or - * flyout. - * @const - */ -Blockly.DELETE_AREA_TOOLBOX = 2; - /** * String for use in the "custom" attribute of a category in toolbox XML. * This string indicates that the category should be dynamically populated with diff --git a/core/delete_area.js b/core/delete_area.js new file mode 100644 index 00000000000..92edf36f561 --- /dev/null +++ b/core/delete_area.js @@ -0,0 +1,53 @@ +/** + * @license + * Copyright 2021 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @fileoverview The abstract class for a component that can delete a block or + * bubble that is dropped on top of it. + * @author kozbial@google.com (Monica Kozbial) + */ + +'use strict'; + +goog.provide('Blockly.DeleteArea'); + +goog.require('Blockly.DragTarget'); +goog.require('Blockly.IDeleteArea'); + + +/** + * Abstract class for a component that can delete a block or bubble that is + * dropped on top of it. + * @extends {Blockly.DragTarget} + * @implements {Blockly.IDeleteArea} + * @constructor + */ +Blockly.DeleteArea = function() { + Blockly.DeleteArea.superClass_.constructor.call(this); +}; +Blockly.utils.object.inherits(Blockly.DeleteArea, Blockly.DragTarget); + +/** + * Returns whether the provided block would be deleted if dropped on this area. + * @param {!Blockly.BlockSvg} _block The block. + * @param {boolean} couldConnect Whether the block could could connect to + * another. + * @return {boolean} Whether the block provided would be deleted if dropped on + * this area. + */ +Blockly.DeleteArea.prototype.wouldDeleteBlock = function(_block, couldConnect) { + return !couldConnect; +}; + +/** + * Returns whether the provided bubble would be deleted if dropped on this area. + * @param {!Blockly.IBubble} _bubble The bubble. + * @return {boolean} Whether the bubble provided would be deleted if dropped on + * this area. + */ +Blockly.DeleteArea.prototype.wouldDeleteBubble = function(_bubble) { + return true; +}; diff --git a/core/drag_target.js b/core/drag_target.js new file mode 100644 index 00000000000..7a905fa0d59 --- /dev/null +++ b/core/drag_target.js @@ -0,0 +1,70 @@ +/** + * @license + * Copyright 2021 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @fileoverview The abstract class for a component with custom behaviour when a + * block or bubble is dragged over or dropped on top of it. + * @author kozbial@google.com (Monica Kozbial) + */ + +'use strict'; + +goog.provide('Blockly.DragTarget'); + +goog.require('Blockly.IDragTarget'); + +goog.requireType('Blockly.BlockSvg'); +goog.requireType('Blockly.IBubble'); +goog.requireType('Blockly.utils.Rect'); + + +/** + * Abstract class for a component with custom behaviour when a block or bubble + * is dragged over or dropped on top of it. + * @implements {Blockly.IDragTarget} + * @constructor + */ +Blockly.DragTarget = function() {}; + +/** + * Returns the bounding rectangle of the drag target area in pixel units + * relative to the Blockly injection div. + * @return {?Blockly.utils.Rect} The component's bounding box. Null if drag + * target area should be ignored. + */ +Blockly.DragTarget.prototype.getClientRect; + +/** + * Handles when a cursor with a block or bubble enters this drag target. + */ +Blockly.DragTarget.prototype.onDragEnter = function() { + // no-op +}; + +/** + * Handles when a cursor with a block or bubble exits this drag target. + */ +Blockly.DragTarget.prototype.onDragExit = function() { + // no-op +}; + +/** + * Handles when a block is dropped on this component. Should not handle delete + * here. + * @param {!Blockly.BlockSvg} _block The block. + */ +Blockly.DragTarget.prototype.onBlockDrop = function(_block) { + // no-op +}; + +/** + * Handles when a bubble is dropped on this component. Should not handle delete + * here. + * @param {!Blockly.IBubble} _bubble The bubble. + */ +Blockly.DragTarget.prototype.onBubbleDrop = function(_bubble) { + // no-op +}; diff --git a/core/flyout_base.js b/core/flyout_base.js index cc699fc901a..06ceecbbb15 100644 --- a/core/flyout_base.js +++ b/core/flyout_base.js @@ -16,6 +16,7 @@ goog.require('Blockly.Block'); /** @suppress {extraRequire} */ goog.require('Blockly.blockRendering'); goog.require('Blockly.browserEvents'); +goog.require('Blockly.DeleteArea'); goog.require('Blockly.Events'); /** @suppress {extraRequire} */ goog.require('Blockly.Events.BlockCreate'); @@ -24,7 +25,6 @@ goog.require('Blockly.Events.VarCreate'); goog.require('Blockly.FlyoutMetricsManager'); /** @suppress {extraRequire} */ goog.require('Blockly.Gesture'); -goog.require('Blockly.IDeleteArea'); goog.require('Blockly.IFlyout'); goog.require('Blockly.ScrollbarPair'); goog.require('Blockly.Tooltip'); @@ -51,10 +51,11 @@ goog.requireType('Blockly.utils.Rect'); * workspace. * @constructor * @abstract - * @implements {Blockly.IDeleteArea} * @implements {Blockly.IFlyout} + * @extends {Blockly.DeleteArea} */ Blockly.Flyout = function(workspaceOptions) { + Blockly.Flyout.superClass_.constructor.call(this); workspaceOptions.setMetrics = this.setMetrics_.bind(this); /** @@ -140,6 +141,7 @@ Blockly.Flyout = function(workspaceOptions) { */ this.targetWorkspace = null; }; +Blockly.utils.object.inherits(Blockly.Flyout, Blockly.DeleteArea); /** * Does the flyout automatically close when a block is created? @@ -300,6 +302,16 @@ Blockly.Flyout.prototype.init = function(targetWorkspace) { this.workspace_.setVariableMap(this.targetWorkspace.getVariableMap()); this.workspace_.createPotentialVariableMap(); + + targetWorkspace.getComponentManager().addComponent({ + id: 'flyout' + this.workspace_.id, + component: this, + weight: 1, + capabilities: [ + Blockly.ComponentManager.Capability.DELETE_AREA, + Blockly.ComponentManager.Capability.DRAG_TARGET + ] + }); }; /** @@ -380,6 +392,11 @@ Blockly.Flyout.prototype.setVisible = function(visible) { this.isVisible_ = visible; if (visibilityChanged) { + if (!this.autoClose) { + // Auto-close flyouts are ignored as drag targets, so only non auto-close + // flyouts need to have their drag target updated. + this.workspace_.recordDragTargets(); + } this.updateDisplay_(); } }; @@ -1018,8 +1035,9 @@ Blockly.Flyout.prototype.placeNewBlock_ = function(oldBlock) { }; /** - * Return the deletion rectangle for this flyout in viewport coordinates. - * @return {?Blockly.utils.Rect} Rectangle in which to delete. + * Returns the bounding rectangle of the drag target area in pixel units + * relative to viewport. + * @return {Blockly.utils.Rect} The component's bounding box. */ Blockly.Flyout.prototype.getClientRect; diff --git a/core/flyout_horizontal.js b/core/flyout_horizontal.js index 249bed0217d..9ef63183456 100644 --- a/core/flyout_horizontal.js +++ b/core/flyout_horizontal.js @@ -304,11 +304,15 @@ Blockly.HorizontalFlyout.prototype.isDragTowardWorkspace = function( }; /** - * Return the deletion rectangle for this flyout in viewport coordinates. - * @return {?Blockly.utils.Rect} Rectangle in which to delete. + * Returns the bounding rectangle of the drag target area in pixel units + * relative to viewport. + * @return {?Blockly.utils.Rect} The component's bounding box. Null if drag + * target area should be ignored. */ Blockly.HorizontalFlyout.prototype.getClientRect = function() { - if (!this.svgGroup_) { + if (!this.svgGroup_ || this.autoClose || this.isVisible()) { + // The bounding rectangle won't compute correctly if the flyout is closed + // and auto-close flyouts aren't valid drag targets (or delete areas). return null; } diff --git a/core/flyout_vertical.js b/core/flyout_vertical.js index 364382b22a3..ca60d5718c2 100644 --- a/core/flyout_vertical.js +++ b/core/flyout_vertical.js @@ -287,11 +287,15 @@ Blockly.VerticalFlyout.prototype.isDragTowardWorkspace = function( }; /** - * Return the deletion rectangle for this flyout in viewport coordinates. - * @return {?Blockly.utils.Rect} Rectangle in which to delete. + * Returns the bounding rectangle of the drag target area in pixel units + * relative to viewport. + * @return {?Blockly.utils.Rect} The component's bounding box. Null if drag + * target area should be ignored. */ Blockly.VerticalFlyout.prototype.getClientRect = function() { - if (!this.svgGroup_) { + if (!this.svgGroup_ || this.autoClose || this.isVisible()) { + // The bounding rectangle won't compute correctly if the flyout is closed + // and auto-close flyouts aren't valid drag targets (or delete areas). return null; } diff --git a/core/insertion_marker_manager.js b/core/insertion_marker_manager.js index a6a427d1206..1a7988c2421 100644 --- a/core/insertion_marker_manager.js +++ b/core/insertion_marker_manager.js @@ -240,14 +240,15 @@ Blockly.InsertionMarkerManager.prototype.applyConnections = function() { * Update connections based on the most recent move location. * @param {!Blockly.utils.Coordinate} dxy Position relative to drag start, * in workspace units. - * @param {?number} deleteArea One of {@link Blockly.DELETE_AREA_TRASH}, - * {@link Blockly.DELETE_AREA_TOOLBOX}, or {@link Blockly.DELETE_AREA_NONE}. + * @param {?Blockly.IDragTarget} dragTarget The drag target that the block is + * currently over. * @package */ -Blockly.InsertionMarkerManager.prototype.update = function(dxy, deleteArea) { +Blockly.InsertionMarkerManager.prototype.update = function(dxy, dragTarget) { var candidate = this.getCandidate_(dxy); - this.wouldDeleteBlock_ = this.shouldDelete_(candidate, deleteArea); + this.wouldDeleteBlock_ = this.shouldDelete_(candidate, dragTarget); + var shouldUpdate = this.wouldDeleteBlock_ || this.shouldUpdatePreviews_(candidate, dxy); @@ -445,23 +446,32 @@ Blockly.InsertionMarkerManager.prototype.getStartRadius_ = function() { /** * Whether ending the drag would delete the block. * @param {!Object} candidate An object containing a local connection, a closest - * connection, and a radius. - * @param {?number} deleteArea One of {@link Blockly.DELETE_AREA_TRASH}, - * {@link Blockly.DELETE_AREA_TOOLBOX}, or {@link Blockly.DELETE_AREA_NONE}. - * @return {boolean} True if dropping the block immediately would replace - * delete the block. False otherwise. + * connection, and a radius. + * @param {?Blockly.IDragTarget} dragTarget The drag target that the block is + * currently over. + * @return {boolean} Whether dropping the block immediately would delete the + * block. * @private */ -Blockly.InsertionMarkerManager.prototype.shouldDelete_ = function(candidate, - deleteArea) { - // Prefer connecting over dropping into the trash can, but prefer dragging to - // the toolbox over connecting to other blocks. - var wouldConnect = candidate && !!candidate.closest && - deleteArea != Blockly.DELETE_AREA_TOOLBOX; - var wouldDelete = !!deleteArea && !this.topBlock_.getParent() && - this.topBlock_.isDeletable(); - - return wouldDelete && !wouldConnect; +Blockly.InsertionMarkerManager.prototype.shouldDelete_ = function( + candidate, dragTarget) { + var couldDeleteBlock = + !this.topBlock_.getParent() && this.topBlock_.isDeletable(); + + if (couldDeleteBlock && dragTarget) { + // TODO(#4881) use hasCapability instead of getComponents + var deleteAreas = this.workspace_.getComponentManager().getComponents( + Blockly.ComponentManager.Capability.DELETE_AREA, false); + var isDeleteArea = deleteAreas.some(function(deleteArea) { + return dragTarget === deleteArea; + }); + if (isDeleteArea) { + return ( + /** @type {!Blockly.IDeleteArea} */ (dragTarget)) + .wouldDeleteBlock(this.topBlock_, candidate && !!candidate.closest); + } + } + return false; }; /** diff --git a/core/interfaces/i_delete_area.js b/core/interfaces/i_delete_area.js new file mode 100644 index 00000000000..e3bbb3d7c85 --- /dev/null +++ b/core/interfaces/i_delete_area.js @@ -0,0 +1,44 @@ +/** + * @license + * Copyright 2021 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @fileoverview The interface for a component that can delete a block or bubble + * that is dropped on top of it. + * @author kozbial@google.com (Monica Kozbial) + */ + +'use strict'; + +goog.provide('Blockly.IDeleteArea'); + +goog.require('Blockly.IDragTarget'); + + +/** + * Interface for a component that can delete a block or bubble that is dropped + * on top of it. + * @extends {Blockly.IDragTarget} + * @interface + */ +Blockly.IDeleteArea = function() {}; + +/** + * Returns whether the provided block would be deleted if dropped on this area. + * @param {!Blockly.BlockSvg} block The block. + * @param {boolean} couldConnect Whether the block could could connect to + * another. + * @return {boolean} Whether the block provided would be deleted if dropped on + * this area. + */ +Blockly.IDeleteArea.prototype.wouldDeleteBlock; + +/** + * Returns whether the provided bubble would be deleted if dropped on this area. + * @param {!Blockly.IBubble} bubble The bubble. + * @return {boolean} Whether the bubble provided would be deleted if dropped on + * this area. + */ +Blockly.IDeleteArea.prototype.wouldDeleteBubble; diff --git a/core/interfaces/i_deletearea.js b/core/interfaces/i_deletearea.js deleted file mode 100644 index f406a0152e8..00000000000 --- a/core/interfaces/i_deletearea.js +++ /dev/null @@ -1,30 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * @fileoverview The interface for a component that can delete a block that is - * dropped on top of it. - * @author aschmiedt@google.com (Abby Schmiedt) - */ - -'use strict'; - -goog.provide('Blockly.IDeleteArea'); - -goog.requireType('Blockly.utils.Rect'); - - -/** - * Interface for a component that can delete a block that is dropped on top of it. - * @interface - */ -Blockly.IDeleteArea = function() {}; - -/** - * Return the deletion rectangle. - * @return {Blockly.utils.Rect} Rectangle in which to delete. - */ -Blockly.IDeleteArea.prototype.getClientRect; diff --git a/core/interfaces/i_drag_target.js b/core/interfaces/i_drag_target.js new file mode 100644 index 00000000000..fa7c2c2867c --- /dev/null +++ b/core/interfaces/i_drag_target.js @@ -0,0 +1,60 @@ +/** + * @license + * Copyright 2021 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @fileoverview The interface for a component that has a handler for when a + * block is dropped on top of it. + * @author kozbial@google.com (Monica Kozbial) + */ + +'use strict'; + +goog.provide('Blockly.IDragTarget'); + +goog.require('Blockly.IComponent'); + +goog.requireType('Blockly.BlockSvg'); +goog.requireType('Blockly.IBubble'); +goog.requireType('Blockly.utils.Rect'); + +/** + * Interface for a component with custom behaviour when a block or bubble is + * dragged over or dropped on top of it. + * @extends {Blockly.IComponent} + * @interface + */ +Blockly.IDragTarget = function() {}; + +/** + * Returns the bounding rectangle of the drag target area in pixel units + * relative to viewport. + * @return {Blockly.utils.Rect} The component's bounding box. + */ +Blockly.IDragTarget.prototype.getClientRect; + +/** + * Handles when a cursor with a block or bubble enters this drag target. + */ +Blockly.IDragTarget.prototype.onDragEnter; + +/** + * Handles when a cursor with a block or bubble exits this drag target. + */ +Blockly.IDragTarget.prototype.onDragExit; + +/** + * Handles when a block is dropped on this component. Should not handle delete + * here. + * @param {!Blockly.BlockSvg} block The block. + */ +Blockly.IDragTarget.prototype.onBlockDrop; + +/** + * Handles when a bubble is dropped on this component. Should not handle delete + * here. + * @param {!Blockly.IBubble} bubble The bubble. + */ +Blockly.IDragTarget.prototype.onBubbleDrop; diff --git a/core/mutator.js b/core/mutator.js index 52135bf834f..1bd6d098800 100644 --- a/core/mutator.js +++ b/core/mutator.js @@ -287,7 +287,7 @@ Blockly.Mutator.prototype.resizeBubble_ = function() { */ Blockly.Mutator.prototype.onBubbleMove_ = function() { if (this.workspace_) { - this.workspace_.recordDeleteAreas(); + this.workspace_.recordDragTargets(); } }; diff --git a/core/toolbox/toolbox.js b/core/toolbox/toolbox.js index b64d07b50b9..64a1a51dbc9 100644 --- a/core/toolbox/toolbox.js +++ b/core/toolbox/toolbox.js @@ -17,11 +17,11 @@ goog.require('Blockly.CollapsibleToolboxCategory'); /** @suppress {extraRequire} */ goog.require('Blockly.constants'); goog.require('Blockly.Css'); +goog.require('Blockly.DeleteArea'); goog.require('Blockly.Events'); /** @suppress {extraRequire} */ goog.require('Blockly.Events.ToolboxItemSelect'); goog.require('Blockly.IAutoHideable'); -goog.require('Blockly.IDeleteArea'); goog.require('Blockly.IKeyboardAccessible'); goog.require('Blockly.IStyleable'); goog.require('Blockly.IToolbox'); @@ -50,11 +50,12 @@ goog.requireType('Blockly.WorkspaceSvg'); * @constructor * @implements {Blockly.IAutoHideable} * @implements {Blockly.IKeyboardAccessible} - * @implements {Blockly.IDeleteArea} * @implements {Blockly.IStyleable} * @implements {Blockly.IToolbox} + * @extends {Blockly.DeleteArea} */ Blockly.Toolbox = function(workspace) { + Blockly.Toolbox.superClass_.constructor.call(this); /** * The workspace this toolbox is on. * @type {!Blockly.WorkspaceSvg} @@ -159,6 +160,7 @@ Blockly.Toolbox = function(workspace) { */ this.boundEvents_ = []; }; +Blockly.utils.object.inherits(Blockly.Toolbox, Blockly.DeleteArea); /** * Handles the given keyboard shortcut. @@ -189,13 +191,14 @@ Blockly.Toolbox.prototype.init = function() { themeManager.subscribe(this.HtmlDiv, 'toolboxBackgroundColour', 'background-color'); themeManager.subscribe(this.HtmlDiv, 'toolboxForegroundColour', 'color'); - this.workspace_.getComponentManager().addComponent({ id: 'toolbox', component: this, weight: 1, capabilities: [ - Blockly.ComponentManager.Capability.AUTOHIDEABLE + Blockly.ComponentManager.Capability.AUTOHIDEABLE, + Blockly.ComponentManager.Capability.DELETE_AREA, + Blockly.ComponentManager.Capability.DRAG_TARGET ] }); }; @@ -496,9 +499,10 @@ Blockly.Toolbox.prototype.removeStyle = function(style) { }; /** - * Return the deletion rectangle for this toolbox. - * @return {?Blockly.utils.Rect} Rectangle in which to delete. - * @public + * Returns the bounding rectangle of the drag target area in pixel units + * relative to viewport. + * @return {?Blockly.utils.Rect} The component's bounding box. Null if drag + * target area should be ignored. */ Blockly.Toolbox.prototype.getClientRect = function() { if (!this.HtmlDiv) { @@ -529,6 +533,19 @@ Blockly.Toolbox.prototype.getClientRect = function() { } }; +/** + * Returns whether the provided block would be deleted if dropped on this area. + * @param {!Blockly.BlockSvg} _block The block. + * @param {boolean} _couldConnect Whether the block could could connect to + * another. + * @return {boolean} Whether the block provided would be deleted if dropped on + * this area. + */ +Blockly.Toolbox.prototype.wouldDeleteBlock = function(_block, _couldConnect) { + // Prefer dragging to the toolbox over connecting to other blocks. + return true; +}; + /** * Gets the toolbox item with the given ID. * @param {string} id The ID of the toolbox item. diff --git a/core/trashcan.js b/core/trashcan.js index cbcf896832e..7781a79bd93 100644 --- a/core/trashcan.js +++ b/core/trashcan.js @@ -15,11 +15,11 @@ goog.provide('Blockly.Trashcan'); goog.require('Blockly.browserEvents'); /** @suppress {extraRequire} */ goog.require('Blockly.constants'); +goog.require('Blockly.DeleteArea'); goog.require('Blockly.Events'); /** @suppress {extraRequire} */ goog.require('Blockly.Events.TrashcanOpen'); goog.require('Blockly.IAutoHideable'); -goog.require('Blockly.IDeleteArea'); goog.require('Blockly.IPositionable'); goog.require('Blockly.Options'); goog.require('Blockly.registry'); @@ -40,10 +40,11 @@ goog.requireType('Blockly.WorkspaceSvg'); * @param {!Blockly.WorkspaceSvg} workspace The workspace to sit in. * @constructor * @implements {Blockly.IAutoHideable} - * @implements {Blockly.IDeleteArea} * @implements {Blockly.IPositionable} + * @extends {Blockly.DeleteArea} */ Blockly.Trashcan = function(workspace) { + Blockly.Trashcan.superClass_.constructor.call(this); /** * The workspace the trashcan sits in. * @type {!Blockly.WorkspaceSvg} @@ -102,6 +103,7 @@ Blockly.Trashcan = function(workspace) { } this.workspace_.addChangeListener(this.onDelete_.bind(this)); }; +Blockly.utils.object.inherits(Blockly.Trashcan, Blockly.DeleteArea); /** * Width of both the trash can and lid images. @@ -364,8 +366,10 @@ Blockly.Trashcan.prototype.init = function() { component: this, weight: 1, capabilities: [ - Blockly.ComponentManager.Capability.POSITIONABLE, - Blockly.ComponentManager.Capability.AUTOHIDEABLE + Blockly.ComponentManager.Capability.AUTOHIDEABLE, + Blockly.ComponentManager.Capability.DELETE_AREA, + Blockly.ComponentManager.Capability.DRAG_TARGET, + Blockly.ComponentManager.Capability.POSITIONABLE ] }); this.initialized_ = true; @@ -501,8 +505,10 @@ Blockly.Trashcan.prototype.getBoundingRectangle = function() { }; /** - * Return the deletion rectangle for this trash can. - * @return {?Blockly.utils.Rect} Rectangle in which to delete. + * Returns the bounding rectangle of the drag target area in pixel units + * relative to viewport. + * @return {?Blockly.utils.Rect} The component's bounding box. Null if drag + * target area should be ignored. */ Blockly.Trashcan.prototype.getClientRect = function() { if (!this.svgGroup_) { @@ -518,6 +524,51 @@ Blockly.Trashcan.prototype.getClientRect = function() { return new Blockly.utils.Rect(top, bottom, left, right); }; +/** + * Handles when a cursor with a block or bubble enters this drag target. + * @override + */ +Blockly.Trashcan.prototype.onDragEnter = function() { + this.setLidOpen(true); +}; + +/** + * Handles when a cursor with a block or bubble exits this drag target. + * @override + */ +Blockly.Trashcan.prototype.onDragExit = function() { + this.setLidOpen(false); +}; + + +/** + * Handles when a block is dropped on this component. Should not handle delete + * here. + * @param {!Blockly.BlockSvg} _block The block. + * @override + */ +Blockly.Trashcan.prototype.onBlockDrop = function(_block) { + this.onDrop_(); +}; + +/** + * Handles when a bubble is dropped on this component. Should not handle delete + * here. + * @param {!Blockly.IBubble} _bubble The bubble. + * @override + */ +Blockly.Trashcan.prototype.onBubbleDrop = function(_bubble) { + this.onDrop_(); +}; + +/** + * Handles when a block or bubble is dropped on this component. + * @private + */ +Blockly.Trashcan.prototype.onDrop_ = function() { + setTimeout(this.closeLid.bind(this), 100); +}; + /** * Flip the lid open or shut. * @param {boolean} state True if open. diff --git a/core/workspace_svg.js b/core/workspace_svg.js index 9365b8c5a0b..2ff70291899 100644 --- a/core/workspace_svg.js +++ b/core/workspace_svg.js @@ -61,6 +61,7 @@ goog.requireType('Blockly.blockRendering.Renderer'); goog.requireType('Blockly.Cursor'); goog.requireType('Blockly.FlyoutButton'); goog.requireType('Blockly.IBoundedElement'); +goog.requireType('Blockly.IDragTarget'); goog.requireType('Blockly.IFlyout'); goog.requireType('Blockly.IMetricsManager'); goog.requireType('Blockly.IToolbox'); @@ -232,6 +233,17 @@ Blockly.WorkspaceSvg = function( */ this.topBoundedElements_ = []; + /** + * The recorded drag targets. + * @type {!Array< + * { + * component: !Blockly.IDragTarget, + * clientRect: !Blockly.utils.Rect, + * }>} + * @private + */ + this.dragTargetAreas_ = []; + /** * The cached size of the parent svg element. * Used to compute svg metrics. @@ -879,7 +891,7 @@ Blockly.WorkspaceSvg.prototype.createDom = function(opt_backgroundClass) { if (this.grid_) { this.grid_.update(this.scale); } - this.recordDeleteAreas(); + this.recordDragTargets(); var CursorClass = Blockly.registry.getClassFromOptions( Blockly.registry.Type.CURSOR, this.options); @@ -1096,7 +1108,7 @@ Blockly.WorkspaceSvg.prototype.getToolbox = function() { */ Blockly.WorkspaceSvg.prototype.updateScreenCalculations_ = function() { this.updateInverseScreenCTM(); - this.recordDeleteAreas(); + this.recordDragTargets(); }; /** @@ -1617,38 +1629,50 @@ Blockly.WorkspaceSvg.prototype.createVariable = function(name, /** * Make a list of all the delete areas for this workspace. + * @deprecated Use workspace.recordDragTargets. (2021 June) */ Blockly.WorkspaceSvg.prototype.recordDeleteAreas = function() { - if (this.trashcan && this.svgGroup_.parentNode) { - this.deleteAreaTrash_ = this.trashcan.getClientRect(); - } else { - this.deleteAreaTrash_ = null; - } - if (this.flyout_) { - this.deleteAreaToolbox_ = this.flyout_.getClientRect(); - } else if (this.toolbox_ && typeof this.toolbox_.getClientRect == 'function') { - this.deleteAreaToolbox_ = this.toolbox_.getClientRect(); - } else { - this.deleteAreaToolbox_ = null; + Blockly.utils.deprecation.warn( + 'WorkspaceSvg.prototype.recordDeleteAreas', + 'June 2021', + 'June 2022', + 'WorkspaceSvg.prototype.recordDragTargets'); + this.recordDragTargets(); +}; + +/** + * Make a list of all the delete areas for this workspace. + */ +Blockly.WorkspaceSvg.prototype.recordDragTargets = function() { + var dragTargets = this.componentManager_.getComponents( + Blockly.ComponentManager.Capability.DRAG_TARGET, true); + + this.dragTargetAreas_ = []; + for (var i = 0, targetArea; (targetArea = dragTargets[i]); i++) { + var rect = targetArea.getClientRect(); + if (rect) { + this.dragTargetAreas_.push({ + component: targetArea, + clientRect: rect, + }); + } } }; + /** - * Is the mouse event over a delete area (toolbox or non-closing flyout)? + * Returns the drag target the mouse event is over. * @param {!Event} e Mouse move event. - * @return {?number} Null if not over a delete area, or an enum representing - * which delete area the event is over. + * @return {?Blockly.IDragTarget} Null if not over a drag target, or the drag + * target the event is over. */ -Blockly.WorkspaceSvg.prototype.isDeleteArea = function(e) { - if (this.deleteAreaTrash_ && - this.deleteAreaTrash_.contains(e.clientX, e.clientY)) { - return Blockly.DELETE_AREA_TRASH; - } - if (this.deleteAreaToolbox_ && - this.deleteAreaToolbox_.contains(e.clientX, e.clientY)) { - return Blockly.DELETE_AREA_TOOLBOX; +Blockly.WorkspaceSvg.prototype.getDragTarget = function(e) { + for (var i = 0, targetArea; (targetArea = this.dragTargetAreas_[i]); i++) { + if (targetArea.clientRect.contains(e.clientX, e.clientY)) { + return targetArea.component; + } } - return Blockly.DELETE_AREA_NONE; + return null; }; /** @@ -2200,7 +2224,7 @@ Blockly.WorkspaceSvg.prototype.setScale = function(newScale) { if (this.flyout_) { // No toolbox, resize flyout. this.flyout_.reflow(); - this.recordDeleteAreas(); + this.recordDragTargets(); } if (this.grid_) { this.grid_.update(this.scale);