From 482f7fa9a9367e3ab097c1ca8cd8a79302c911e0 Mon Sep 17 00:00:00 2001 From: Daniel Huber <30466471+daniel0611@users.noreply.github.com> Date: Sat, 21 Oct 2023 23:13:17 +0200 Subject: [PATCH] Paste elements at current cursor position --- src/features/copyPaste/keyListener.ts | 16 ++++++++-- src/features/copyPaste/pasteCommand.ts | 41 +++++++++++++++++++++++--- 2 files changed, 50 insertions(+), 7 deletions(-) diff --git a/src/features/copyPaste/keyListener.ts b/src/features/copyPaste/keyListener.ts index 0f1f1d1..b62e944 100644 --- a/src/features/copyPaste/keyListener.ts +++ b/src/features/copyPaste/keyListener.ts @@ -1,6 +1,13 @@ -import { injectable } from "inversify"; +import { inject, injectable } from "inversify"; import { PasteElementsAction } from "./pasteCommand"; -import { CommitModelAction, KeyListener, SModelElementImpl, SModelRootImpl, isSelected } from "sprotty"; +import { + CommitModelAction, + KeyListener, + MousePositionTracker, + SModelElementImpl, + SModelRootImpl, + isSelected, +} from "sprotty"; import { Action } from "sprotty-protocol"; import { matchesKeystroke } from "sprotty/lib/utils/keyboard"; @@ -14,6 +21,8 @@ import { matchesKeystroke } from "sprotty/lib/utils/keyboard"; export class CopyPasteKeyListener implements KeyListener { private copyElements: SModelElementImpl[] = []; + constructor(@inject(MousePositionTracker) private readonly mousePositionTracker: MousePositionTracker) {} + keyUp(_element: SModelElementImpl, _event: KeyboardEvent): Action[] { return []; } @@ -48,6 +57,7 @@ export class CopyPasteKeyListener implements KeyListener { * This is done inside a command, so that it can be undone/redone. */ private paste(): Action[] { - return [PasteElementsAction.create(this.copyElements), CommitModelAction.create()]; + const targetPosition = this.mousePositionTracker.lastPositionOnDiagram ?? { x: 0, y: 0 }; + return [PasteElementsAction.create(this.copyElements, targetPosition), CommitModelAction.create()]; } } diff --git a/src/features/copyPaste/pasteCommand.ts b/src/features/copyPaste/pasteCommand.ts index f66adb4..310d591 100644 --- a/src/features/copyPaste/pasteCommand.ts +++ b/src/features/copyPaste/pasteCommand.ts @@ -13,19 +13,21 @@ import { import { DynamicChildrenProcessor } from "../dfdElements/dynamicChildren"; import { generateRandomSprottyId } from "../../utils"; import { DfdNode, DfdNodeImpl } from "../dfdElements/nodes"; -import { Action, SPort } from "sprotty-protocol"; +import { Action, Point, SPort } from "sprotty-protocol"; import { ArrowEdge, ArrowEdgeImpl } from "../dfdElements/edges"; export interface PasteElementsAction extends Action { kind: typeof PasteElementsAction.KIND; copyElements: SModelElementImpl[]; + targetPosition: Point; } export namespace PasteElementsAction { export const KIND = "paste-clipboard-elements"; - export function create(copyElements: SModelElementImpl[]): PasteElementsAction { + export function create(copyElements: SModelElementImpl[], targetPosition: Point): PasteElementsAction { return { kind: KIND, copyElements, + targetPosition, }; } } @@ -51,7 +53,7 @@ export class PasteElementsCommand extends Command { } /** - * Selectes the newly created copy and deselects the copy source. + * Selects the newly created copy and deselects the copy source. */ private setSelection(context: CommandExecutionContext, selection: "old" | "new"): void { Object.entries(this.copyElementIdMapping).forEach(([oldId, newId]) => { @@ -67,8 +69,39 @@ export class PasteElementsCommand extends Command { }); } + /** + * Calculates the offset between the copy source elements and the set paste target position. + * Does this by finding the top left position of the copy source elements and subtracting it from the target position. + * + * @returns The offset between the top left position of the copy source elements and the target position. + */ + private computeElementOffset(): Point { + const sourcePosition = { x: Infinity, y: Infinity }; + + this.action.copyElements.forEach((element) => { + if (!(element instanceof SNodeImpl)) { + return; + } + + if (element.position.x < sourcePosition.x) { + sourcePosition.x = element.position.x; + } + if (element.position.y < sourcePosition.y) { + sourcePosition.y = element.position.y; + } + }); + + if (sourcePosition.x === Infinity || sourcePosition.y === Infinity) { + return { x: 0, y: 0 }; + } + + // Compute delta between top left position of copy source elements and the target position + return Point.subtract(this.action.targetPosition, sourcePosition); + } + execute(context: CommandExecutionContext): CommandReturn { // Step 1: copy nodes and their ports + const positionOffset = this.computeElementOffset(); this.action.copyElements.forEach((element) => { if (!(element instanceof SNodeImpl)) { return; @@ -77,7 +110,7 @@ export class PasteElementsCommand extends Command { const schema = { id: generateRandomSprottyId(), type: element.type, - position: { x: element.position.x + 30, y: element.position.y + 30 }, + position: Point.add(element.position, positionOffset), size: { height: -1, width: -1 }, text: "", labels: [],