mirror of
https://github.com/google/blockly.git
synced 2026-01-08 17:40:09 +01:00
feat: add basic render queueing (#6851)
* feat: add basic render queueing * feat: change connecting and disconnecting to queue renders * feat: delay bringToFront * chore: format * chore: fix build * fix: stop updating connections when setting the parent. This was causing erroneous block bumps because the connection locations were changed before the blocks were actually rerendered. * fix: connection highlight positioning
This commit is contained in:
@@ -230,7 +230,7 @@ export class BlockDragger implements IBlockDragger {
|
||||
// These are expensive and don't need to be done if we're deleting.
|
||||
this.draggingBlock_.setDragging(false);
|
||||
if (delta) { // !preventMove
|
||||
this.updateBlockAfterMove_(delta);
|
||||
this.updateBlockAfterMove_();
|
||||
} else {
|
||||
// Blocks dragged directly from a flyout may need to be bumped into
|
||||
// bounds.
|
||||
@@ -283,18 +283,14 @@ export class BlockDragger implements IBlockDragger {
|
||||
|
||||
/**
|
||||
* Updates the necessary information to place a block at a certain location.
|
||||
*
|
||||
* @param delta The change in location from where the block started the drag
|
||||
* to where it ended the drag.
|
||||
*/
|
||||
protected updateBlockAfterMove_(delta: Coordinate) {
|
||||
this.draggingBlock_.moveConnections(delta.x, delta.y);
|
||||
protected updateBlockAfterMove_() {
|
||||
this.fireMoveEvent_();
|
||||
if (this.draggedConnectionManager_.wouldConnectBlock()) {
|
||||
// Applying connections also rerenders the relevant blocks.
|
||||
this.draggedConnectionManager_.applyConnections();
|
||||
} else {
|
||||
this.draggingBlock_.render();
|
||||
this.draggingBlock_.queueRender();
|
||||
}
|
||||
this.draggingBlock_.scheduleSnapAndBump();
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@ import * as svgMath from './utils/svg_math.js';
|
||||
import {Warning} from './warning.js';
|
||||
import type {Workspace} from './workspace.js';
|
||||
import type {WorkspaceSvg} from './workspace_svg.js';
|
||||
import {queueRender} from './render_management.js';
|
||||
|
||||
|
||||
/**
|
||||
@@ -311,9 +312,6 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
|
||||
const oldXY = this.getRelativeToSurfaceXY();
|
||||
if (newParent) {
|
||||
(newParent as BlockSvg).getSvgRoot().appendChild(svgRoot);
|
||||
const newXY = this.getRelativeToSurfaceXY();
|
||||
// Move the connections to match the child's new position.
|
||||
this.moveConnections(newXY.x - oldXY.x, newXY.y - oldXY.y);
|
||||
} else if (oldParent) {
|
||||
// If we are losing a parent, we want to move our DOM element to the
|
||||
// root of the workspace.
|
||||
@@ -1541,7 +1539,17 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
|
||||
}
|
||||
|
||||
/**
|
||||
* Lays out and reflows a block based on its contents and settings.
|
||||
* Triggers a rerender after a delay to allow for batching.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
queueRender() {
|
||||
queueRender(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Immediately lays out and reflows a block based on its contents and
|
||||
* settings.
|
||||
*
|
||||
* @param opt_bubble If false, just render this block.
|
||||
* If true, also render block's parent, grandparent, etc. Defaults to true.
|
||||
@@ -1559,7 +1567,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
|
||||
this.updateCollapsed_();
|
||||
}
|
||||
this.workspace.getRenderer().render(this);
|
||||
this.updateConnectionLocations_();
|
||||
this.updateConnectionLocations();
|
||||
|
||||
if (opt_bubble !== false) {
|
||||
const parentBlock = this.getParent();
|
||||
@@ -1593,8 +1601,10 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
|
||||
* Update all of the connections on this block with the new locations
|
||||
* calculated during rendering. Also move all of the connected blocks based
|
||||
* on the new connection locations.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
private updateConnectionLocations_() {
|
||||
updateConnectionLocations() {
|
||||
const blockTL = this.getRelativeToSurfaceXY();
|
||||
// Don't tighten previous or output connections because they are inferior
|
||||
// connections.
|
||||
|
||||
@@ -199,7 +199,9 @@ export class InsertionMarkerManager {
|
||||
blockAnimations.connectionUiEffect(inferiorConnection.getSourceBlock());
|
||||
// Bring the just-edited stack to the front.
|
||||
const rootBlock = this.topBlock.getRootBlock();
|
||||
rootBlock.bringToFront();
|
||||
setTimeout(() => {
|
||||
rootBlock.bringToFront();
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
71
core/render_management.ts
Normal file
71
core/render_management.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2023 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {BlockSvg} from './block_svg';
|
||||
|
||||
|
||||
const rootBlocks = new Set<BlockSvg>();
|
||||
const dirtyBlocks = new WeakSet<BlockSvg>();
|
||||
let pid = 0;
|
||||
|
||||
/**
|
||||
* Registers that the given block and all of its parents need to be rerendered,
|
||||
* and registers a callback to do so after a delay, to allowf or batching.
|
||||
*
|
||||
* @param block The block to rerender.
|
||||
* @internal
|
||||
*/
|
||||
export function queueRender(block: BlockSvg) {
|
||||
queueBlock(block);
|
||||
if (!pid) pid = window.requestAnimationFrame(doRenders);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given block and its parents to the render queue. Adds the root block
|
||||
* to the list of root blocks.
|
||||
*
|
||||
* @param block The block to queue.
|
||||
*/
|
||||
function queueBlock(block: BlockSvg) {
|
||||
dirtyBlocks.add(block);
|
||||
const parent = block.getParent();
|
||||
if (parent) {
|
||||
queueBlock(parent);
|
||||
} else {
|
||||
rootBlocks.add(block);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rerenders all of the blocks in the queue.
|
||||
*/
|
||||
function doRenders() {
|
||||
for (const block of rootBlocks) {
|
||||
if (block.isDisposed()) continue;
|
||||
renderBlock(block);
|
||||
}
|
||||
|
||||
pid = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively renders all of the children of the given block, and then renders
|
||||
* the block.
|
||||
*
|
||||
* @param block The block to rerender.
|
||||
*/
|
||||
function renderBlock(block: BlockSvg) {
|
||||
for (const child of block.getChildren(false)) {
|
||||
renderBlock(child);
|
||||
}
|
||||
if (dirtyBlocks.has(block)) {
|
||||
dirtyBlocks.delete(block);
|
||||
rootBlocks.delete(block);
|
||||
block.render(false);
|
||||
} else {
|
||||
block.updateConnectionLocations();
|
||||
}
|
||||
}
|
||||
@@ -185,13 +185,19 @@ export class RenderedConnection extends Connection {
|
||||
* @param y New absolute y coordinate, in workspace coordinates.
|
||||
*/
|
||||
moveTo(x: number, y: number) {
|
||||
const dx = this.x - x;
|
||||
const dy = this.y - y;
|
||||
|
||||
if (this.trackedState_ === RenderedConnection.TrackedState.WILL_TRACK) {
|
||||
this.db_.addConnection(this, y);
|
||||
this.trackedState_ = RenderedConnection.TrackedState.TRACKED;
|
||||
} else if (this.trackedState_ === RenderedConnection.TrackedState.TRACKED) {
|
||||
} else if (
|
||||
this.trackedState_ === RenderedConnection.TrackedState.TRACKED &&
|
||||
(dx !== 0 || dy !== 0)) {
|
||||
this.db_.removeConnection(this, this.y);
|
||||
this.db_.addConnection(this, y);
|
||||
}
|
||||
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
@@ -302,14 +308,12 @@ export class RenderedConnection extends Connection {
|
||||
(shape as unknown as PathLeftShape).pathLeft +
|
||||
svgPaths.lineOnAxis('h', xLen);
|
||||
}
|
||||
const xy = this.sourceBlock_.getRelativeToSurfaceXY();
|
||||
const x = this.x - xy.x;
|
||||
const y = this.y - xy.y;
|
||||
const offset = this.offsetInBlock_;
|
||||
this.highlightPath = dom.createSvgElement(
|
||||
Svg.PATH, {
|
||||
'class': 'blocklyHighlightedConnectionPath',
|
||||
'd': steps,
|
||||
'transform': 'translate(' + x + ',' + y + ')' +
|
||||
'transform': `translate(${offset.x}, ${offset.y})` +
|
||||
(this.sourceBlock_.RTL ? ' scale(-1 1)' : ''),
|
||||
},
|
||||
this.sourceBlock_.getSvgRoot());
|
||||
@@ -459,11 +463,11 @@ export class RenderedConnection extends Connection {
|
||||
const renderedChild = childBlock as BlockSvg;
|
||||
// Rerender the parent so that it may reflow.
|
||||
if (renderedParent.rendered) {
|
||||
renderedParent.render();
|
||||
renderedParent.queueRender();
|
||||
}
|
||||
if (renderedChild.rendered) {
|
||||
renderedChild.updateDisabled();
|
||||
renderedChild.render();
|
||||
renderedChild.queueRender();
|
||||
// Reset visibility, since the child is now a top block.
|
||||
renderedChild.getSvgRoot().style.display = 'block';
|
||||
}
|
||||
@@ -484,7 +488,7 @@ export class RenderedConnection extends Connection {
|
||||
|
||||
const parentBlock = this.getSourceBlock();
|
||||
if (parentBlock.rendered) {
|
||||
parentBlock.render();
|
||||
parentBlock.queueRender();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -528,11 +532,11 @@ export class RenderedConnection extends Connection {
|
||||
this.type === ConnectionType.PREVIOUS_STATEMENT) {
|
||||
// Child block may need to square off its corners if it is in a stack.
|
||||
// Rendering a child will render its parent.
|
||||
childBlock.render();
|
||||
childBlock.queueRender();
|
||||
} else {
|
||||
// Child block does not change shape. Rendering the parent node will
|
||||
// move its connected children into position.
|
||||
parentBlock.render();
|
||||
parentBlock.queueRender();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user