diff --git a/core/dragging/dragger.ts b/core/dragging/dragger.ts index 2a65e5ac7ce..734adeeb9e3 100644 --- a/core/dragging/dragger.ts +++ b/core/dragging/dragger.ts @@ -4,65 +4,132 @@ * 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'; export class Dragger implements IDragger { - /** Starting location of the draggable, in workspace coordinates. */ private startLoc: Coordinate; + private dragTarget: IDragTarget | null = null; + constructor( private draggable: IDraggable, private workspace: WorkspaceSvg, ) { - this.startLoc = draggable.getLocation(); + this.startLoc = draggable.getRelativeToSurfaceXY(); } - /** - * Handles any drag startup. - * - * @param e PointerEvent that started the drag. - */ + /** Handles any drag startup. */ onDragStart(e: PointerEvent) { this.draggable.startDrag(e); } /** - * Handles dragging, including calculating where the element should - * actually be moved to. + * Handles calculating where the element should actually be moved to. * - * @param e PointerEvent that continued the drag. - * @param totalDelta The total distance, in pixels, that the mouse - * has moved since the start of the drag. + * @param totalDelta The total amount in pixel coordinates the mouse has moved + * since the start of the drag. */ onDrag(e: PointerEvent, totalDelta: Coordinate) { - this.draggable.drag(this.newLoc(totalDelta), e); + this.moveDraggable(e, totalDelta); + + // Must check `wouldDelete` before calling other hooks on drag targets + // since we have documented that we would do so. + if (isDeletable(this.draggable)) { + (this.draggable as AnyDuringMigration).setDeleteStyle( + this.wouldDeleteDraggable(e, this.draggable), + ); + } + this.updateDragTarget(e); + } + + /** Updates the drag target under the pointer (if there is one). */ + protected updateDragTarget(e: PointerEvent) { + const newDragTarget = this.workspace.getDragTarget(e); + if (this.dragTarget !== newDragTarget) { + this.dragTarget?.onDragExit(this.draggable as AnyDuringMigration); + newDragTarget?.onDragEnter(this.draggable as AnyDuringMigration); + } + newDragTarget?.onDragOver(this.draggable as AnyDuringMigration); + this.dragTarget = newDragTarget; } /** - * Handles any drag cleanup. - * - * @param e PointerEvent that finished the drag. - * @param totalDelta The total distance, in pixels, that the mouse - * has moved since the start of the drag. + * Calculates the correct workspace coordinate for the movable and tells + * the draggable to go to that location. */ - onDragEnd(e: PointerEvent, totalDelta: Coordinate) { - this.draggable.endDrag(this.newLoc(totalDelta), e); + private moveDraggable(e: PointerEvent, totalDelta: Coordinate) { + const delta = this.pixelsToWorkspaceUnits(totalDelta); + const newLoc = Coordinate.sum(this.startLoc, delta); + this.draggable.drag(newLoc, e); } /** - * Calculates where the IDraggable should actually be moved to. - * - * @param totalDelta The total distance, in pixels, that the mouse - * has moved since the start of the drag. - * @returns The new location, in workspace coordinates. + * Returns true if we would delete the draggable if it was dropped + * at the current location. + */ + protected wouldDeleteDraggable( + e: PointerEvent, + draggable: 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( + draggable, + false, + // !!this.getConnectionCandidate(draggable, delta), + ); + } + + /** Handles any drag cleanup. */ + onDragEnd(e: PointerEvent) { + const dragTarget = this.workspace.getDragTarget(e); + if (dragTarget) { + this.dragTarget?.onDrop(this.draggable as AnyDuringMigration); + } + + if (this.shouldReturnToStart(e, this.draggable)) { + this.draggable.revertDrag(); + } + + this.draggable.endDrag(e); + + if ( + isDeletable(this.draggable) && + this.wouldDeleteDraggable(e, this.draggable) + ) { + (this.draggable as AnyDuringMigration).dispose(); + } + } + + /** + * Returns true if we should return the draggable to its original location + * at the end of the drag. */ - protected newLoc(totalDelta: Coordinate): Coordinate { + protected shouldReturnToStart(e: PointerEvent, draggable: IDraggable) { + const dragTarget = this.workspace.getDragTarget(e); + if (!dragTarget) return false; + return dragTarget.shouldPreventMove(draggable as AnyDuringMigration); + } + + protected pixelsToWorkspaceUnits(pixelCoord: Coordinate): Coordinate { const result = new Coordinate( - totalDelta.x / this.workspace.scale, - totalDelta.y / this.workspace.scale, + 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 @@ -71,7 +138,6 @@ export class Dragger implements IDragger { const mainScale = this.workspace.options.parentWorkspace!.scale; result.scale(1 / mainScale); } - result.translate(this.startLoc.x, this.startLoc.y); return result; } } diff --git a/core/interfaces/i_deletable.ts b/core/interfaces/i_deletable.ts index 89cc086169a..518d71baaf2 100644 --- a/core/interfaces/i_deletable.ts +++ b/core/interfaces/i_deletable.ts @@ -17,3 +17,8 @@ export interface IDeletable { */ isDeletable(): boolean; } + +/** Returns whether the given object is an IDeletable. */ +export function isDeletable(obj: any): obj is IDeletable { + return obj['isDeletable'] !== undefined; +} diff --git a/core/interfaces/i_draggable.ts b/core/interfaces/i_draggable.ts index 572791e2862..6f98e53c5cc 100644 --- a/core/interfaces/i_draggable.ts +++ b/core/interfaces/i_draggable.ts @@ -5,33 +5,29 @@ */ import {Coordinate} from '../utils/coordinate'; -import {IDragTarget} from './i_drag_target'; /** * Represents an object that can be dragged. */ export interface IDraggable extends IDragStrategy { - /** Returns true iff the element is currently movable. */ - isMovable(): boolean; - /** * Returns the current location of the draggable in workspace * coordinates. * * @returns Coordinate of current location on workspace. */ - getLocation(): Coordinate; + getRelativeToSurfaceXY(): Coordinate; } -/** - * Represents an object that can be dragged. - */ export interface IDragStrategy { + /** Returns true iff the element is currently movable. */ + isMovable(): boolean; + /** * Handles any drag startup (e.g moving elements to the front of the * workspace). * - * @param e PointerEvent that started the drag; could be used to + * @param e PointerEvent that started the drag; can be used to * check modifier keys, etc. May be missing when dragging is * triggered programatically rather than by user. */ @@ -43,15 +39,10 @@ export interface IDragStrategy { * * @param newLoc Workspace coordinate to which the draggable has * been dragged. - * @param e PointerEvent that continued the drag. Should be used to - * look up any IDragTarget the pointer is over; could also be + * @param e PointerEvent that continued the drag. Can be * used to check modifier keys, etc. - * @param target The drag target the pointer is over, if any. Could - * be supplied as an alternative to providing a PointerEvent for - * programatic drags. */ drag(newLoc: Coordinate, e?: PointerEvent): void; - drag(newLoc: Coordinate, target: IDragTarget): void; /** * Handles any drag cleanup, including e.g. connecting or deleting @@ -59,13 +50,11 @@ export interface IDragStrategy { * * @param newLoc Workspace coordinate at which the drag finished. * been dragged. - * @param e PointerEvent that finished the drag. Should be used to - * look up any IDragTarget the pointer is over; could also be + * @param e PointerEvent that finished the drag. Can be * used to check modifier keys, etc. - * @param target The drag target the pointer is over, if any. Could - * be supplied as an alternative to providing a PointerEvent for - * programatic drags. */ - endDrag(newLoc: Coordinate, e?: PointerEvent): void; - endDrag(newLoc: Coordinate, target: IDragTarget): void; + endDrag(e?: PointerEvent): void; + + /** Moves the draggable back to where it was at the start of the drag. */ + revertDrag(): void; }