mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-18 10:36:22 +08:00
be28038e94
### TL;DR For canvas elements, this PR adds placeholders during zooming operations to improve performance.  ### What changed? - Implemented placeholder rendering during zooming operations in the canvas renderer, but not only DOM. - Added a `forceFullRender` property to the `GfxCompatibleInterface` to allow elements to opt out of placeholder rendering - Set `forceFullRender = true` for connectors to ensure they always render properly, even during zooming - Connected the turbo renderer to the viewport's zooming state to automatically switch between full and placeholder rendering ### Why make this change? Rendering complex elements during zooming operations can cause performance issues and make the UI feel sluggish. Rendering connector label also leads to high cost DOM `set font` delays.  The turbo renderer improves performance by displaying simple placeholders for elements during zooming, while still rendering critical elements like connectors fully. This creates a smoother user experience while maintaining essential visual information. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Introduced a feature-flag-controlled "turbo" rendering mode that displays placeholder graphics during zooming for improved performance. - Added the ability to override placeholder rendering for specific elements, ensuring full rendering when required. - **Bug Fixes** - Enhanced rendering logic to ensure connectors always render fully, even during zoom operations. - **Documentation** - Updated API documentation to reflect new properties related to rendering behavior. - **Tests** - Improved tests to verify correct rendering behavior for connectors. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
174 lines
4.9 KiB
TypeScript
174 lines
4.9 KiB
TypeScript
import type {
|
|
Bound,
|
|
IBound,
|
|
IVec,
|
|
PointLocation,
|
|
SerializedXYWH,
|
|
XYWH,
|
|
} from '@blocksuite/global/gfx';
|
|
|
|
import type { EditorHost } from '../../view/element/lit-host.js';
|
|
import type { GfxGroupModel, GfxModel } from './model.js';
|
|
|
|
/**
|
|
* The methods that a graphic element should implement.
|
|
* It is already included in the `GfxCompatibleInterface` interface.
|
|
*/
|
|
export interface GfxElementGeometry {
|
|
containsBound(bound: Bound): boolean;
|
|
getNearestPoint(point: IVec): IVec;
|
|
getLineIntersections(start: IVec, end: IVec): PointLocation[] | null;
|
|
getRelativePointLocation(point: IVec): PointLocation;
|
|
includesPoint(
|
|
x: number,
|
|
y: number,
|
|
options: PointTestOptions,
|
|
host: EditorHost
|
|
): boolean;
|
|
intersectsBound(bound: Bound): boolean;
|
|
}
|
|
|
|
/**
|
|
* All the model that can be rendered in graphics mode should implement this interface.
|
|
*/
|
|
export interface GfxCompatibleInterface extends IBound, GfxElementGeometry {
|
|
xywh: SerializedXYWH;
|
|
index: string;
|
|
|
|
/**
|
|
* Defines the extension of the response area beyond the element's bounding box.
|
|
* This tuple specifies the horizontal and vertical margins to be added to the element's bound.
|
|
*
|
|
* The first value represents the horizontal extension (added to both left and right sides),
|
|
* and the second value represents the vertical extension (added to both top and bottom sides).
|
|
*
|
|
* The response area is computed as:
|
|
* `[x - horizontal, y - vertical, w + 2 * horizontal, h + 2 * vertical]`.
|
|
*
|
|
* Example:
|
|
* - xywh: `[0, 0, 100, 100]`, `responseExtension: [10, 20]`
|
|
* Resulting response area: `[-10, -20, 120, 140]`.
|
|
* - `responseExtension: [0, 0]` keeps the response area equal to the bounding box.
|
|
*/
|
|
responseExtension: [number, number];
|
|
|
|
readonly group: GfxGroupCompatibleInterface | null;
|
|
|
|
readonly groups: GfxGroupCompatibleInterface[];
|
|
|
|
readonly deserializedXYWH: XYWH;
|
|
|
|
/**
|
|
* The bound of the element without considering the response extension.
|
|
*/
|
|
readonly elementBound: Bound;
|
|
|
|
/**
|
|
* The bound of the element considering the response extension.
|
|
*/
|
|
readonly responseBound: Bound;
|
|
|
|
/**
|
|
* Indicates whether the current block is explicitly locked by self.
|
|
* For checking the lock status of the element, use `isLocked` instead.
|
|
* For (un)locking the element, use `(un)lock` instead.
|
|
*/
|
|
lockedBySelf?: boolean;
|
|
|
|
/**
|
|
* Check if the element is locked. It will check the lock status of the element and its ancestors.
|
|
*/
|
|
isLocked(): boolean;
|
|
isLockedBySelf(): boolean;
|
|
isLockedByAncestor(): boolean;
|
|
|
|
lock(): void;
|
|
unlock(): void;
|
|
|
|
/**
|
|
* Whether to disable fallback rendering for this element, e.g., during zooming.
|
|
* Defaults to false (fallback to placeholder rendering is enabled).
|
|
*/
|
|
forceFullRender?: boolean;
|
|
}
|
|
|
|
/**
|
|
* The symbol to mark a model as a container.
|
|
*/
|
|
export const gfxGroupCompatibleSymbol = Symbol('GfxGroupCompatible');
|
|
|
|
/**
|
|
* Check if the element is a container element.
|
|
*/
|
|
export const isGfxGroupCompatibleModel = (
|
|
elm: unknown
|
|
): elm is GfxGroupModel => {
|
|
if (typeof elm !== 'object' || elm === null) return false;
|
|
return (
|
|
gfxGroupCompatibleSymbol in elm && elm[gfxGroupCompatibleSymbol] === true
|
|
);
|
|
};
|
|
|
|
/**
|
|
* GfxGroupCompatibleElement is a model that can contain other models.
|
|
* It just like a group that in common graphic software.
|
|
*/
|
|
export interface GfxGroupCompatibleInterface extends GfxCompatibleInterface {
|
|
[gfxGroupCompatibleSymbol]: true;
|
|
|
|
/**
|
|
* All child ids of this container.
|
|
*/
|
|
childIds: string[];
|
|
|
|
/**
|
|
* All child element models of this container.
|
|
* Note that the `childElements` may not contains all the children in `childIds`,
|
|
* because some children may not be loaded.
|
|
*/
|
|
childElements: GfxModel[];
|
|
|
|
descendantElements: GfxModel[];
|
|
|
|
addChild(element: GfxCompatibleInterface): void;
|
|
removeChild(element: GfxCompatibleInterface): void;
|
|
hasChild(element: GfxCompatibleInterface): boolean;
|
|
|
|
hasDescendant(element: GfxCompatibleInterface): boolean;
|
|
}
|
|
|
|
/**
|
|
* The options for the hit testing of a point.
|
|
*/
|
|
export interface PointTestOptions {
|
|
/**
|
|
* The threshold of the hit test. The unit is pixel.
|
|
*/
|
|
hitThreshold?: number;
|
|
|
|
/**
|
|
* If true, the element bound will be used for the hit testing.
|
|
* By default, the response bound will be used.
|
|
*/
|
|
useElementBound?: boolean;
|
|
|
|
/**
|
|
* The padding of the response area for each element when do the hit testing. The unit is pixel.
|
|
* The first value is the padding for the x-axis, and the second value is the padding for the y-axis.
|
|
*/
|
|
responsePadding?: [number, number];
|
|
|
|
/**
|
|
* If true, the transparent area of the element will be ignored during the point inclusion test.
|
|
* Otherwise, the transparent area will be considered as filled area.
|
|
*
|
|
* Default is true.
|
|
*/
|
|
ignoreTransparent?: boolean;
|
|
|
|
/**
|
|
* The zoom level of current view when do the hit testing.
|
|
*/
|
|
zoom?: number;
|
|
}
|