Skip to content

Commit 6465aa2

Browse files
feat: position new top-level blocks to avoid overlap (#378)
Initial attempt at #95 Similar to `WorkspaceSvg.cleanUp()` but does not constrain itself to not affecting code ordering in order to use horizontal space.
1 parent c38e11d commit 6465aa2

File tree

1 file changed

+96
-1
lines changed

1 file changed

+96
-1
lines changed

src/actions/enter.ts

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import {
88
ASTNode,
9+
Events,
910
ShortcutRegistry,
1011
utils as BlocklyUtils,
1112
dialog,
@@ -126,6 +127,9 @@ export class EnterAction {
126127
* the block will be placed on.
127128
*/
128129
private insertFromFlyout(workspace: WorkspaceSvg) {
130+
workspace.setResizesEnabled(false);
131+
Events.setGroup(true);
132+
129133
const stationaryNode = this.navigation.getStationaryNode(workspace);
130134
const newBlock = this.createNewBlock(workspace);
131135
if (!newBlock) return;
@@ -137,8 +141,99 @@ export class EnterAction {
137141
}
138142
}
139143

144+
if (workspace.getTopBlocks().includes(newBlock)) {
145+
this.positionNewTopLevelBlock(workspace, newBlock);
146+
}
147+
148+
Events.setGroup(false);
149+
workspace.setResizesEnabled(true);
150+
140151
this.navigation.focusWorkspace(workspace);
141-
workspace.getCursor()?.setCurNode(ASTNode.createBlockNode(newBlock));
152+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
153+
workspace.getCursor()?.setCurNode(ASTNode.createBlockNode(newBlock)!);
154+
}
155+
156+
/**
157+
* Position a new top-level block to avoid overlap at the top left.
158+
*
159+
* Similar to `WorkspaceSvg.cleanUp()` but does not constrain itself to not
160+
* affecting code ordering in order to use horizontal space.
161+
*
162+
* @param workspace The workspace.
163+
* @param newBlock The top-level block to move to free space.
164+
*/
165+
private positionNewTopLevelBlock(
166+
workspace: WorkspaceSvg,
167+
newBlock: BlockSvg,
168+
) {
169+
const initialY = 10;
170+
const initialX = 10;
171+
const xSpacing = 80;
172+
173+
const filteredTopBlocks = workspace
174+
.getTopBlocks(true)
175+
.filter((block) => block.id !== newBlock.id);
176+
const allBlockBounds = filteredTopBlocks.map((block) =>
177+
block.getBoundingRectangle(),
178+
);
179+
180+
const toolboxWidth = workspace.getToolbox()?.getWidth();
181+
const workspaceWidth =
182+
workspace.getParentSvg().clientWidth - (toolboxWidth ?? 0);
183+
const workspaceHeight = workspace.getParentSvg().clientHeight;
184+
const {height: newBlockHeight, width: newBlockWidth} =
185+
newBlock.getHeightWidth();
186+
187+
const getNextIntersectingBlock = function (
188+
newBlockRect: BlocklyUtils.Rect,
189+
): BlocklyUtils.Rect | null {
190+
for (const rect of allBlockBounds) {
191+
if (newBlockRect.intersects(rect)) {
192+
return rect;
193+
}
194+
}
195+
return null;
196+
};
197+
198+
let cursorY = initialY;
199+
let cursorX = initialX;
200+
const minBlockHeight = workspace
201+
.getRenderer()
202+
.getConstants().MIN_BLOCK_HEIGHT;
203+
// Make the initial movement of shifting the block to its best possible position.
204+
let boundingRect = newBlock.getBoundingRectangle();
205+
newBlock.moveBy(cursorX - boundingRect.left, cursorY - boundingRect.top, [
206+
'cleanup',
207+
]);
208+
newBlock.snapToGrid();
209+
210+
boundingRect = newBlock.getBoundingRectangle();
211+
let conflictingRect = getNextIntersectingBlock(boundingRect);
212+
while (conflictingRect != null) {
213+
const newCursorX =
214+
conflictingRect.left + conflictingRect.getWidth() + xSpacing;
215+
const newCursorY =
216+
conflictingRect.top + conflictingRect.getHeight() + minBlockHeight;
217+
if (newCursorX + newBlockWidth <= workspaceWidth) {
218+
cursorX = newCursorX;
219+
} else if (newCursorY + newBlockHeight <= workspaceHeight) {
220+
cursorY = newCursorY;
221+
cursorX = initialX;
222+
} else {
223+
// Off screen, but new blocks will be selected which will scroll them
224+
// into view.
225+
cursorY = newCursorY;
226+
cursorX = initialX;
227+
}
228+
newBlock.moveBy(cursorX - boundingRect.left, cursorY - boundingRect.top, [
229+
'cleanup',
230+
]);
231+
newBlock.snapToGrid();
232+
boundingRect = newBlock.getBoundingRectangle();
233+
conflictingRect = getNextIntersectingBlock(boundingRect);
234+
}
235+
236+
newBlock.bringToFront();
142237
}
143238

144239
/**

0 commit comments

Comments
 (0)