diff --git a/core/interfaces/i_movable.ts b/core/interfaces/i_movable.ts index cc2a2b727c3..00c7759ddf0 100644 --- a/core/interfaces/i_movable.ts +++ b/core/interfaces/i_movable.ts @@ -4,6 +4,8 @@ * SPDX-License-Identifier: Apache-2.0 */ +import {Coordinate} from '../utils/coordinate.js'; + // Former goog.module ID: Blockly.IMovable /** @@ -16,4 +18,12 @@ export interface IMovable { * @returns True if movable. */ isMovable(): boolean; + + /** + * Return the coordinates of the top-left corner of this movable object + * relative to the drawing surface's origin (0,0), in workspace units. + * + * @returns Object with .x and .y properties. + */ + getRelativeToSurfaceXY(): Coordinate; } diff --git a/core/shortcut_items.ts b/core/shortcut_items.ts index 57b90a498a7..e8886c9ef7b 100644 --- a/core/shortcut_items.ts +++ b/core/shortcut_items.ts @@ -12,6 +12,7 @@ import * as common from './common.js'; import {Gesture} from './gesture.js'; import {ICopyData, isCopyable} from './interfaces/i_copyable.js'; import {KeyboardShortcut, ShortcutRegistry} from './shortcut_registry.js'; +import {Coordinate} from './utils/coordinate.js'; import {KeyCodes} from './utils/keycodes.js'; import type {WorkspaceSvg} from './workspace_svg.js'; @@ -82,6 +83,7 @@ export function registerDelete() { let copyData: ICopyData | null = null; let copyWorkspace: WorkspaceSvg | null = null; +let copyCoords: Coordinate | null = null; /** * Keyboard shortcut to copy a block on ctrl+c, cmd+c, or alt+c. @@ -118,6 +120,7 @@ export function registerCopy() { const selected = common.getSelected(); if (!selected || !isCopyable(selected)) return false; copyData = selected.toCopyData(); + copyCoords = selected.getRelativeToSurfaceXY(); copyWorkspace = workspace; return !!copyData; }, @@ -158,6 +161,7 @@ export function registerCut() { const selected = common.getSelected(); if (!selected || !isCopyable(selected)) return false; copyData = selected.toCopyData(); + copyCoords = selected.getRelativeToSurfaceXY(); copyWorkspace = workspace; (selected as BlockSvg).checkAndDelete(); return true; @@ -188,8 +192,33 @@ export function registerPaste() { return !workspace.options.readOnly && !Gesture.inProgress(); }, callback() { - if (!copyData || !copyWorkspace) return false; - return !!clipboard.paste(copyData, copyWorkspace); + if (!copyData || !copyCoords || !copyWorkspace) return false; + const { + left: viewportLeft, + top: viewportTop, + width: viewportWidth, + height: viewportHeight, + } = copyWorkspace.getMetricsManager().getViewMetrics(true); + const selected = common.getSelected() as BlockSvg; + + // Pass the default copy coordinates when + // default location is inside viewport. + if ( + copyCoords.x >= viewportLeft && + copyCoords.x + selected.width <= viewportLeft + viewportWidth && + copyCoords.y >= viewportTop && + copyCoords.y + selected.height <= viewportTop + viewportHeight + ) { + return !!clipboard.paste(copyData, copyWorkspace, copyCoords); + } else { + // Pass the center of the new viewport + // to paste the copied block + const centerCoords = new Coordinate( + viewportLeft + Math.trunc(viewportWidth / 2 - selected.width / 2), + viewportTop + Math.trunc(viewportHeight / 2 - selected.height / 2), + ); + return !!clipboard.paste(copyData, copyWorkspace, centerCoords); + } }, keyCodes: [ctrlV, altV, metaV], };