mirror of
https://github.com/google/blockly.git
synced 2026-01-04 23:50:12 +01:00
* Revert "fix: dragging blocks by shadows to delete (#8138)"
This reverts commit 3fd749205f.
* fix: dragging shadows
164 lines
5.3 KiB
TypeScript
164 lines
5.3 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2024 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import {IDragTarget} from '../interfaces/i_drag_target.js';
|
|
import {IDeletable, isDeletable} from '../interfaces/i_deletable.js';
|
|
import {IDragger} from '../interfaces/i_dragger.js';
|
|
import {IDraggable} from '../interfaces/i_draggable.js';
|
|
import {Coordinate} from '../utils/coordinate.js';
|
|
import {WorkspaceSvg} from '../workspace_svg.js';
|
|
import {ComponentManager} from '../component_manager.js';
|
|
import {IDeleteArea} from '../interfaces/i_delete_area.js';
|
|
import * as registry from '../registry.js';
|
|
import * as eventUtils from '../events/utils.js';
|
|
import * as blockAnimations from '../block_animations.js';
|
|
import {BlockSvg} from '../block_svg.js';
|
|
|
|
export class Dragger implements IDragger {
|
|
protected startLoc: Coordinate;
|
|
|
|
protected dragTarget: IDragTarget | null = null;
|
|
|
|
constructor(
|
|
protected draggable: IDraggable,
|
|
protected workspace: WorkspaceSvg,
|
|
) {
|
|
this.startLoc = draggable.getRelativeToSurfaceXY();
|
|
}
|
|
|
|
/** Handles any drag startup. */
|
|
onDragStart(e: PointerEvent) {
|
|
this.draggable.startDrag(e);
|
|
}
|
|
|
|
/**
|
|
* Handles calculating where the element should actually be moved to.
|
|
*
|
|
* @param totalDelta The total amount in pixel coordinates the mouse has moved
|
|
* since the start of the drag.
|
|
*/
|
|
onDrag(e: PointerEvent, totalDelta: Coordinate) {
|
|
this.moveDraggable(e, totalDelta);
|
|
const root = this.getRoot(this.draggable);
|
|
|
|
// Must check `wouldDelete` before calling other hooks on drag targets
|
|
// since we have documented that we would do so.
|
|
if (isDeletable(root)) {
|
|
root.setDeleteStyle(this.wouldDeleteDraggable(e, root));
|
|
}
|
|
this.updateDragTarget(e);
|
|
}
|
|
|
|
/** Updates the drag target under the pointer (if there is one). */
|
|
protected updateDragTarget(e: PointerEvent) {
|
|
const newDragTarget = this.workspace.getDragTarget(e);
|
|
const root = this.getRoot(this.draggable);
|
|
if (this.dragTarget !== newDragTarget) {
|
|
this.dragTarget?.onDragExit(root);
|
|
newDragTarget?.onDragEnter(root);
|
|
}
|
|
newDragTarget?.onDragOver(root);
|
|
this.dragTarget = newDragTarget;
|
|
}
|
|
|
|
/**
|
|
* Calculates the correct workspace coordinate for the movable and tells
|
|
* the draggable to go to that location.
|
|
*/
|
|
private moveDraggable(e: PointerEvent, totalDelta: Coordinate) {
|
|
const delta = this.pixelsToWorkspaceUnits(totalDelta);
|
|
const newLoc = Coordinate.sum(this.startLoc, delta);
|
|
this.draggable.drag(newLoc, e);
|
|
}
|
|
|
|
/**
|
|
* Returns true if we would delete the draggable if it was dropped
|
|
* at the current location.
|
|
*/
|
|
protected wouldDeleteDraggable(
|
|
e: PointerEvent,
|
|
rootDraggable: IDraggable & IDeletable,
|
|
) {
|
|
const dragTarget = this.workspace.getDragTarget(e);
|
|
if (!dragTarget) return false;
|
|
|
|
const componentManager = this.workspace.getComponentManager();
|
|
const isDeleteArea = componentManager.hasCapability(
|
|
dragTarget.id,
|
|
ComponentManager.Capability.DELETE_AREA,
|
|
);
|
|
if (!isDeleteArea) return false;
|
|
|
|
return (dragTarget as IDeleteArea).wouldDelete(rootDraggable);
|
|
}
|
|
|
|
/** Handles any drag cleanup. */
|
|
onDragEnd(e: PointerEvent) {
|
|
const origGroup = eventUtils.getGroup();
|
|
const dragTarget = this.workspace.getDragTarget(e);
|
|
const root = this.getRoot(this.draggable);
|
|
|
|
if (dragTarget) {
|
|
this.dragTarget?.onDrop(root);
|
|
}
|
|
|
|
if (this.shouldReturnToStart(e, root)) {
|
|
this.draggable.revertDrag();
|
|
}
|
|
|
|
const wouldDelete = isDeletable(root) && this.wouldDeleteDraggable(e, root);
|
|
|
|
// TODO(#8148): use a generalized API instead of an instanceof check.
|
|
if (wouldDelete && this.draggable instanceof BlockSvg) {
|
|
blockAnimations.disposeUiEffect(this.draggable.getRootBlock());
|
|
}
|
|
|
|
this.draggable.endDrag(e);
|
|
|
|
if (wouldDelete && isDeletable(root)) {
|
|
// We want to make sure the delete gets grouped with any possible
|
|
// move event.
|
|
const newGroup = eventUtils.getGroup();
|
|
eventUtils.setGroup(origGroup);
|
|
root.dispose();
|
|
eventUtils.setGroup(newGroup);
|
|
}
|
|
}
|
|
|
|
// We need to special case blocks for now so that we look at the root block
|
|
// instead of the one actually being dragged in most cases.
|
|
private getRoot(draggable: IDraggable): IDraggable {
|
|
return draggable instanceof BlockSvg ? draggable.getRootBlock() : draggable;
|
|
}
|
|
|
|
/**
|
|
* Returns true if we should return the draggable to its original location
|
|
* at the end of the drag.
|
|
*/
|
|
protected shouldReturnToStart(e: PointerEvent, rootDraggable: IDraggable) {
|
|
const dragTarget = this.workspace.getDragTarget(e);
|
|
if (!dragTarget) return false;
|
|
return dragTarget.shouldPreventMove(rootDraggable);
|
|
}
|
|
|
|
protected 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;
|
|
}
|
|
}
|
|
|
|
registry.register(registry.Type.BLOCK_DRAGGER, registry.DEFAULT, Dragger);
|