Files
blockly/core/bubble_dragger.ts
Christopher Allen b0a7c004a9 refactor(build): Delete Closure Library (#7415)
* fix(build): Restore erroneously-deleted filter function

  This was deleted in PR #7406 as it was mainly being used to
  filter core/ vs. test/mocha/ deps into separate deps files -
  but it turns out also to be used for filtering error
  messages too.  Oops.

* refactor(tests): Migrate advanced compilation test to ES Modules

* refactor(build): Migrate main.js to TypeScript

  This turns out to be pretty straight forward, even if it would
  cause crashing if one actually tried to import this module
  instead of just feeding it to Closure Compiler.

* chore(build): Remove goog.declareModuleId calls

  Replace goog.declareModuleId calls with a comment recording the
  former module ID for posterity (or at least until we decide
  how to reformat the renamings file.

* chore(tests): Delete closure/goog/*

  For the moment we still need something to serve as base.js for
  the benefit of closure-make-deps, so we keep a vestigial
  base.js around, containing only the @provideGoog declaration.

* refactor(build): Remove vestigial base.js

  By changing slightly the command line arguments to
  closure-make-deps and closure-calculate-chunks the need to have
  any base.js is eliminated.

* chore: Typo fix for PR #7415
2023-08-31 00:24:47 +01:00

212 lines
6.8 KiB
TypeScript

/**
* @license
* Copyright 2018 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
/**
* Methods for dragging a bubble visually.
*
* @class
*/
// Former goog.module ID: Blockly.BubbleDragger
import {ComponentManager} from './component_manager.js';
import type {CommentMove} from './events/events_comment_move.js';
import * as eventUtils from './events/utils.js';
import type {IBubble} from './interfaces/i_bubble.js';
import type {IDeleteArea} from './interfaces/i_delete_area.js';
import type {IDragTarget} from './interfaces/i_drag_target.js';
import {Coordinate} from './utils/coordinate.js';
import {WorkspaceCommentSvg} from './workspace_comment_svg.js';
import type {WorkspaceSvg} from './workspace_svg.js';
/**
* Class for a bubble dragger. It moves things on the bubble canvas around the
* workspace when they are being dragged by a mouse or touch. These can be
* block comments, mutators, warnings, or workspace comments.
*/
export class BubbleDragger {
/** Which drag target the mouse pointer is over, if any. */
private dragTarget_: IDragTarget | null = null;
/** Whether the bubble would be deleted if dropped immediately. */
private wouldDeleteBubble_ = false;
private readonly startXY_: Coordinate;
/**
* @param bubble The item on the bubble canvas to drag.
* @param workspace The workspace to drag on.
*/
constructor(
private bubble: IBubble,
private workspace: WorkspaceSvg,
) {
/**
* The location of the top left corner of the dragging bubble's body at the
* beginning of the drag, in workspace coordinates.
*/
this.startXY_ = this.bubble.getRelativeToSurfaceXY();
}
/**
* Start dragging a bubble.
*
* @internal
*/
startBubbleDrag() {
if (!eventUtils.getGroup()) {
eventUtils.setGroup(true);
}
this.workspace.setResizesEnabled(false);
if ((this.bubble as AnyDuringMigration).setAutoLayout) {
(this.bubble as AnyDuringMigration).setAutoLayout(false);
}
this.bubble.setDragging && this.bubble.setDragging(true);
}
/**
* Execute a step of bubble dragging, based on the given event. Update the
* display accordingly.
*
* @param e The most recent move event.
* @param currentDragDeltaXY How far the pointer has moved from the position
* at the start of the drag, in pixel units.
* @internal
*/
dragBubble(e: PointerEvent, currentDragDeltaXY: Coordinate) {
const delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY);
const newLoc = Coordinate.sum(this.startXY_, delta);
this.bubble.moveDuringDrag(newLoc);
const oldDragTarget = this.dragTarget_;
this.dragTarget_ = this.workspace.getDragTarget(e);
const oldWouldDeleteBubble = this.wouldDeleteBubble_;
this.wouldDeleteBubble_ = this.shouldDelete_(this.dragTarget_);
if (oldWouldDeleteBubble !== this.wouldDeleteBubble_) {
// Prevent unnecessary add/remove class calls.
this.updateCursorDuringBubbleDrag_();
}
// Call drag enter/exit/over after wouldDeleteBlock is called in
// shouldDelete_
if (this.dragTarget_ !== oldDragTarget) {
oldDragTarget && oldDragTarget.onDragExit(this.bubble);
this.dragTarget_ && this.dragTarget_.onDragEnter(this.bubble);
}
this.dragTarget_ && this.dragTarget_.onDragOver(this.bubble);
}
/**
* Whether ending the drag would delete the bubble.
*
* @param dragTarget The drag target that the bubblee is currently over.
* @returns Whether dropping the bubble immediately would delete the block.
*/
private shouldDelete_(dragTarget: IDragTarget | null): boolean {
if (dragTarget) {
const componentManager = this.workspace.getComponentManager();
const isDeleteArea = componentManager.hasCapability(
dragTarget.id,
ComponentManager.Capability.DELETE_AREA,
);
if (isDeleteArea) {
return (dragTarget as IDeleteArea).wouldDelete(this.bubble, false);
}
}
return false;
}
/**
* Update the cursor (and possibly the trash can lid) to reflect whether the
* dragging bubble would be deleted if released immediately.
*/
private updateCursorDuringBubbleDrag_() {
this.bubble.setDeleteStyle(this.wouldDeleteBubble_);
}
/**
* Finish a bubble drag and put the bubble back on the workspace.
*
* @param e The pointerup event.
* @param currentDragDeltaXY How far the pointer has moved from the position
* at the start of the drag, in pixel units.
* @internal
*/
endBubbleDrag(e: PointerEvent, currentDragDeltaXY: Coordinate) {
// Make sure internal state is fresh.
this.dragBubble(e, currentDragDeltaXY);
const preventMove =
this.dragTarget_ && this.dragTarget_.shouldPreventMove(this.bubble);
let newLoc;
if (preventMove) {
newLoc = this.startXY_;
} else {
const delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY);
newLoc = Coordinate.sum(this.startXY_, delta);
}
// Move the bubble to its final location.
this.bubble.moveTo(newLoc.x, newLoc.y);
if (this.dragTarget_) {
this.dragTarget_.onDrop(this.bubble);
}
if (this.wouldDeleteBubble_) {
// Fire a move event, so we know where to go back to for an undo.
this.fireMoveEvent_();
this.bubble.dispose();
} else {
// Put everything back onto the bubble canvas.
if (this.bubble.setDragging) {
this.bubble.setDragging(false);
}
this.fireMoveEvent_();
}
this.workspace.setResizesEnabled(true);
eventUtils.setGroup(false);
}
/** Fire a move event at the end of a bubble drag. */
private fireMoveEvent_() {
if (this.bubble instanceof WorkspaceCommentSvg) {
const event = new (eventUtils.get(eventUtils.COMMENT_MOVE))(
this.bubble,
) as CommentMove;
event.setOldCoordinate(this.startXY_);
event.recordNew();
eventUtils.fire(event);
}
// TODO (fenichel): move events for comments.
return;
}
/**
* 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 pixelCoord A coordinate with x and y values in CSS pixel units.
* @returns The input coordinate divided by the workspace scale.
*/
private pixelsToWorkspaceUnits_(pixelCoord: Coordinate): Coordinate {
const result = new 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.
const mainScale = this.workspace.options.parentWorkspace!.scale;
result.scale(1 / mainScale);
}
return result;
}
}