Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 86 additions & 13 deletions packages/blockly/core/dragging/block_drag_strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import * as blocks from '../serialization/blocks.js';
import {Coordinate} from '../utils.js';
import * as aria from '../utils/aria.js';
import * as dom from '../utils/dom.js';
import * as svgMath from '../utils/svg_math.js';
import {Rect} from '../utils/rect.js';
import type {WorkspaceSvg} from '../workspace_svg.js';

/** Represents a valid pair of connections between the dragging block and a block on the workspace. */
Expand Down Expand Up @@ -93,6 +93,11 @@ export class BlockDragStrategy implements IDragStrategy {

protected readonly BLOCK_CONNECTION_OFFSET = 10;

/**
* How far in from the edges of the workspace to position newly placed blocks.
*/
protected readonly WORKSPACE_MARGIN = 10;

constructor(private block: BlockSvg) {
this.workspace = block.workspace;
}
Expand All @@ -112,19 +117,87 @@ export class BlockDragStrategy implements IDragStrategy {
/**
* Positions a cloned block on its new workspace.
*
* @param oldBlock The flyout block that was cloned.
* @param newBlock The new block to position.
*/
private positionNewBlock(oldBlock: BlockSvg, newBlock: BlockSvg) {
const screenCoordinate = svgMath.wsToScreenCoordinates(
oldBlock.workspace,
oldBlock.getRelativeToSurfaceXY(),
);
const workspaceCoordinates = svgMath.screenToWsCoordinates(
newBlock.workspace,
screenCoordinate,
);
newBlock.moveDuringDrag(workspaceCoordinates);
private positionNewBlock(newBlock: BlockSvg) {
const workspace = newBlock.workspace;
const initialY = this.WORKSPACE_MARGIN;
const initialX = this.WORKSPACE_MARGIN;
// How far apart the new block should be placed horizontally from an
// existing one.
const xSpacing = 80;
Comment thread
gonfunko marked this conversation as resolved.
const blockPadding =
Math.max(config.connectingSnapRadius, config.snapRadius) + 2;

const filteredTopBlocks = workspace
.getTopBlocks(true)
.filter((block) => block.id !== newBlock.id);
const allBlockBounds = filteredTopBlocks.map((block) => {
const bounds = block.getBoundingRectangle();
// Expand the bounds to avoid the new block being placed within snapping
// distance.
return new Rect(
bounds.top - blockPadding,
bounds.bottom + blockPadding,
bounds.left - blockPadding,
bounds.right + blockPadding,
);
});

const toolboxWidth = workspace.getToolbox()?.getWidth();
const workspaceWidth =
workspace.getParentSvg().clientWidth - (toolboxWidth ?? 0);
const workspaceHeight = workspace.getParentSvg().clientHeight;
const {height: newBlockHeight, width: newBlockWidth} =
newBlock.getHeightWidth();

const getNextIntersectingBlock = function (
newBlockRect: Rect,
): Rect | null {
for (const rect of allBlockBounds) {
if (newBlockRect.intersects(rect)) {
return rect;
}
}
return null;
};

let cursorY = initialY;
let cursorX = initialX;
const minBlockHeight = workspace
.getRenderer()
.getConstants().MIN_BLOCK_HEIGHT;
// Make the initial movement of shifting the block to its best possible
// position.
let boundingRect = newBlock.getBoundingRectangle();
newBlock.moveBy(cursorX - boundingRect.left, cursorY - boundingRect.top, [
'cleanup',
]);

boundingRect = newBlock.getBoundingRectangle();
let conflictingRect = getNextIntersectingBlock(boundingRect);
while (conflictingRect != null) {
const newCursorX =
conflictingRect.left + conflictingRect.getWidth() + xSpacing;
const newCursorY =
conflictingRect.top + conflictingRect.getHeight() + minBlockHeight;
if (newCursorX + newBlockWidth <= workspaceWidth) {
cursorX = newCursorX;
} else if (newCursorY + newBlockHeight <= workspaceHeight) {
cursorY = newCursorY;
cursorX = initialX;
} else {
// Off screen, but new blocks will be selected which will scroll them
// into view.
cursorY = newCursorY;
cursorX = initialX;
}
newBlock.moveBy(cursorX - boundingRect.left, cursorY - boundingRect.top, [
'cleanup',
]);
boundingRect = newBlock.getBoundingRectangle();
conflictingRect = getNextIntersectingBlock(boundingRect);
}
}

/**
Expand All @@ -150,7 +223,7 @@ export class BlockDragStrategy implements IDragStrategy {
},
) as BlockSvg;
eventUtils.setRecordUndo(false);
this.positionNewBlock(this.block, newBlock);
this.positionNewBlock(newBlock);
eventUtils.setRecordUndo(true);

return newBlock;
Expand Down