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:
Beka Westberg
2023-03-01 10:29:01 -08:00
committed by GitHub
parent ae2e6b23c5
commit f2b75fb877
5 changed files with 107 additions and 24 deletions

View File

@@ -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();
}

View File

@@ -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.

View File

@@ -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
View 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();
}
}

View File

@@ -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();
}
}