aboutsummaryrefslogtreecommitdiffstats
path: root/packages/excalidraw/scene/Renderer.ts
diff options
context:
space:
mode:
authorkj_sh6042026-03-15 16:19:35 -0400
committerkj_sh6042026-03-15 16:19:35 -0400
commit6ec259a0e71174651bae95d4628138bf6fd68742 (patch)
tree5e33c6a5ec091ecabfcb257fdc7b6a88ed8754ac /packages/excalidraw/scene/Renderer.ts
parent16c8578b15c727f22921f8a80a56ee4d4e7f2272 (diff)
refactor: packages/
Diffstat (limited to 'packages/excalidraw/scene/Renderer.ts')
-rw-r--r--packages/excalidraw/scene/Renderer.ts167
1 files changed, 167 insertions, 0 deletions
diff --git a/packages/excalidraw/scene/Renderer.ts b/packages/excalidraw/scene/Renderer.ts
new file mode 100644
index 0000000..8048d20
--- /dev/null
+++ b/packages/excalidraw/scene/Renderer.ts
@@ -0,0 +1,167 @@
+import { isElementInViewport } from "../element/sizeHelpers";
+import { isImageElement } from "../element/typeChecks";
+import type {
+ ExcalidrawElement,
+ NonDeletedElementsMap,
+ NonDeletedExcalidrawElement,
+} from "../element/types";
+import { renderInteractiveSceneThrottled } from "../renderer/interactiveScene";
+import { renderStaticSceneThrottled } from "../renderer/staticScene";
+
+import type { AppState } from "../types";
+import { memoize, toBrandedType } from "../utils";
+import type Scene from "./Scene";
+import type { RenderableElementsMap } from "./types";
+
+export class Renderer {
+ private scene: Scene;
+
+ constructor(scene: Scene) {
+ this.scene = scene;
+ }
+
+ public getRenderableElements = (() => {
+ const getVisibleCanvasElements = ({
+ elementsMap,
+ zoom,
+ offsetLeft,
+ offsetTop,
+ scrollX,
+ scrollY,
+ height,
+ width,
+ }: {
+ elementsMap: NonDeletedElementsMap;
+ zoom: AppState["zoom"];
+ offsetLeft: AppState["offsetLeft"];
+ offsetTop: AppState["offsetTop"];
+ scrollX: AppState["scrollX"];
+ scrollY: AppState["scrollY"];
+ height: AppState["height"];
+ width: AppState["width"];
+ }): readonly NonDeletedExcalidrawElement[] => {
+ const visibleElements: NonDeletedExcalidrawElement[] = [];
+ for (const element of elementsMap.values()) {
+ if (
+ isElementInViewport(
+ element,
+ width,
+ height,
+ {
+ zoom,
+ offsetLeft,
+ offsetTop,
+ scrollX,
+ scrollY,
+ },
+ elementsMap,
+ )
+ ) {
+ visibleElements.push(element);
+ }
+ }
+ return visibleElements;
+ };
+
+ const getRenderableElements = ({
+ elements,
+ editingTextElement,
+ newElementId,
+ pendingImageElementId,
+ }: {
+ elements: readonly NonDeletedExcalidrawElement[];
+ editingTextElement: AppState["editingTextElement"];
+ newElementId: ExcalidrawElement["id"] | undefined;
+ pendingImageElementId: AppState["pendingImageElementId"];
+ }) => {
+ const elementsMap = toBrandedType<RenderableElementsMap>(new Map());
+
+ for (const element of elements) {
+ if (isImageElement(element)) {
+ if (
+ // => not placed on canvas yet (but in elements array)
+ pendingImageElementId === element.id
+ ) {
+ continue;
+ }
+ }
+
+ if (newElementId === element.id) {
+ continue;
+ }
+
+ // we don't want to render text element that's being currently edited
+ // (it's rendered on remote only)
+ if (
+ !editingTextElement ||
+ editingTextElement.type !== "text" ||
+ element.id !== editingTextElement.id
+ ) {
+ elementsMap.set(element.id, element);
+ }
+ }
+ return elementsMap;
+ };
+
+ return memoize(
+ ({
+ zoom,
+ offsetLeft,
+ offsetTop,
+ scrollX,
+ scrollY,
+ height,
+ width,
+ editingTextElement,
+ newElementId,
+ pendingImageElementId,
+ // cache-invalidation nonce
+ sceneNonce: _sceneNonce,
+ }: {
+ zoom: AppState["zoom"];
+ offsetLeft: AppState["offsetLeft"];
+ offsetTop: AppState["offsetTop"];
+ scrollX: AppState["scrollX"];
+ scrollY: AppState["scrollY"];
+ height: AppState["height"];
+ width: AppState["width"];
+ editingTextElement: AppState["editingTextElement"];
+ /** note: first render of newElement will always bust the cache
+ * (we'd have to prefilter elements outside of this function) */
+ newElementId: ExcalidrawElement["id"] | undefined;
+ pendingImageElementId: AppState["pendingImageElementId"];
+ sceneNonce: ReturnType<InstanceType<typeof Scene>["getSceneNonce"]>;
+ }) => {
+ const elements = this.scene.getNonDeletedElements();
+
+ const elementsMap = getRenderableElements({
+ elements,
+ editingTextElement,
+ newElementId,
+ pendingImageElementId,
+ });
+
+ const visibleElements = getVisibleCanvasElements({
+ elementsMap,
+ zoom,
+ offsetLeft,
+ offsetTop,
+ scrollX,
+ scrollY,
+ height,
+ width,
+ });
+
+ return { elementsMap, visibleElements };
+ },
+ );
+ })();
+
+ // NOTE Doesn't destroy everything (scene, rc, etc.) because it may not be
+ // safe to break TS contract here (for upstream cases)
+ public destroy() {
+ renderInteractiveSceneThrottled.cancel();
+ renderStaticSceneThrottled.cancel();
+ this.getRenderableElements.clear();
+ }
+}