@pmndrs/uikit
Version:
Build performant 3D user interfaces with Three.js and yoga.
173 lines (172 loc) • 9.96 kB
JavaScript
import { createFlexNodeState } from '../flex/node.js';
import { createHoverPropertyTransformers, setupCursorCleanup } from '../hover.js';
import { computedIsClipped, createGlobalClippingPlanes } from '../clipping.js';
import { setupInstancedPanel } from '../panel/instanced-panel.js';
import { setupObjectTransform, computedTransformMatrix } from '../transform.js';
import { createResponsivePropertyTransformers } from '../responsive.js';
import { ElementType, computedOrderInfo, setupRenderOrder } from '../order.js';
import { createActivePropertyTransfomers } from '../active.js';
import { computed, signal, untracked } from '@preact/signals-core';
import { computedGlobalMatrix, computedHandlers, computedIsVisible, computedMergedProperties, setupNode, keepAspectRatioPropertyTransformer, setupMatrixWorldUpdate, setupPointerEvents, computedAncestorsHaveListeners, } from './utils.js';
import { abortableEffect, alignmentZMap } from '../utils.js';
import { setupLayoutListeners, setupClippedListeners } from '../listeners.js';
import { computedPanelGroupDependencies, } from '../panel/instanced-panel-group.js';
import { createInteractionPanel, setupInteractionPanel } from '../panel/instanced-panel-mesh.js';
import { Box3, Material, Mesh, Vector3 } from 'three';
import { darkPropertyTransformers } from '../dark.js';
import { getDefaultPanelMaterialConfig, makeClippedCast } from '../panel/index.js';
import { computedInheritableProperty } from '../properties/index.js';
export function createContentState(parentCtx, style, properties, defaultProperties, contentContainerRef) {
const flexState = createFlexNodeState();
const hoveredList = signal([]);
const pressedList = signal([]);
const sizeSignal = signal(new Vector3(1, 1, 1));
const aspectRatio = computed(() => sizeSignal.value.x / sizeSignal.value.y);
//properties
const mergedProperties = computedMergedProperties(style, properties, defaultProperties, {
...darkPropertyTransformers,
...createResponsivePropertyTransformers(parentCtx.root.size),
...createHoverPropertyTransformers(hoveredList),
...createActivePropertyTransfomers(pressedList),
}, keepAspectRatioPropertyTransformer, (m) => m.add('aspectRatio', aspectRatio));
const transformMatrix = computedTransformMatrix(mergedProperties, flexState, parentCtx.root.pixelSize);
const globalMatrix = computedGlobalMatrix(parentCtx.childrenMatrix, transformMatrix);
const isClipped = computedIsClipped(parentCtx.clippingRect, globalMatrix, flexState.size, parentCtx.root.pixelSize);
const isVisible = computedIsVisible(flexState, isClipped, mergedProperties);
const groupDeps = computedPanelGroupDependencies(mergedProperties);
const backgroundOrderInfo = computedOrderInfo(mergedProperties, 'zIndexOffset', ElementType.Panel, groupDeps, parentCtx.orderInfo);
const orderInfo = computedOrderInfo(undefined, 'zIndexOffset', ElementType.Object, undefined, backgroundOrderInfo);
const handlers = computedHandlers(style, properties, defaultProperties, hoveredList, pressedList);
const ancestorsHaveListeners = computedAncestorsHaveListeners(parentCtx, handlers);
const measuredSize = new Vector3();
const measuredCenter = new Vector3();
return Object.assign(flexState, {
measuredSize,
measuredCenter,
globalMatrix,
isClipped,
isVisible,
mergedProperties,
hoveredSignal: hoveredList,
sizeSignal,
orderInfo,
backgroundOrderInfo,
groupDeps,
handlers,
ancestorsHaveListeners,
transformMatrix,
root: parentCtx.root,
interactionPanel: createInteractionPanel(backgroundOrderInfo, parentCtx.root, parentCtx.clippingRect, globalMatrix, flexState),
remeasureContent: createMeasureContent(flexState, measuredSize, measuredCenter, mergedProperties, parentCtx.root, parentCtx.clippingRect, isVisible, orderInfo, sizeSignal, contentContainerRef),
});
}
export function setupContent(state, parentCtx, style, properties, object, contentContainer, abortSignal) {
setupCursorCleanup(state.hoveredSignal, abortSignal);
//create node
setupNode(state, parentCtx, object, true, abortSignal);
//transform
setupObjectTransform(parentCtx.root, object, state.transformMatrix, abortSignal);
//instanced panel
setupInstancedPanel(state.mergedProperties, state.backgroundOrderInfo, state.groupDeps, parentCtx.root.panelGroupManager, state.globalMatrix, state.size, undefined, state.borderInset, parentCtx.clippingRect, state.isVisible, getDefaultPanelMaterialConfig(), abortSignal);
setupMatrixWorldUpdate(true, true, object, parentCtx.root, state.globalMatrix, false, abortSignal);
setupPointerEvents(state.mergedProperties, state.ancestorsHaveListeners, parentCtx.root, object, true, abortSignal);
setupLayoutListeners(style, properties, state.size, abortSignal);
setupClippedListeners(style, properties, state.isClipped, abortSignal);
setupInteractionPanel(state.interactionPanel, state.root, state.globalMatrix, state.size, abortSignal);
setupContentContainer(state.remeasureContent, state.measuredSize, state.measuredCenter, state.mergedProperties, state.root, state, state.isVisible, contentContainer, abortSignal);
}
const vectorHelper = new Vector3();
function setupContentContainer(measureContent, measuredSize, measuredCenter, propertiesSignal, root, flexState, isVisible, contentContainer, abortSignal) {
measureContent();
const depthAlign = computedInheritableProperty(propertiesSignal, 'depthAlign', defaultDepthAlign);
const keepAspectRatio = computedInheritableProperty(propertiesSignal, 'keepAspectRatio', true);
abortableEffect(() => {
const properties = propertiesSignal.value;
updateRenderProperties({ current: contentContainer }, isVisible.value, properties.read('renderOrder', 0), properties.read('depthTest', true), properties.read('depthWrite', false));
root.requestRender();
}, abortSignal);
abortableEffect(() => {
const { size: { value: size }, paddingInset: { value: paddingInset }, borderInset: { value: borderInset }, } = flexState;
if (size == null || paddingInset == null || borderInset == null) {
return;
}
const [width, height] = size;
const [pTop, pRight, pBottom, pLeft] = paddingInset;
const [bTop, bRight, bBottom, bLeft] = borderInset;
const topInset = pTop + bTop;
const rightInset = pRight + bRight;
const bottomInset = pBottom + bBottom;
const leftInset = pLeft + bLeft;
const innerWidth = width - leftInset - rightInset;
const innerHeight = height - topInset - bottomInset;
const pixelSize = root.pixelSize.value;
contentContainer.scale
.set(innerWidth * pixelSize, innerHeight * pixelSize, keepAspectRatio.value ? (innerHeight * pixelSize * measuredSize.z) / measuredSize.y : measuredSize.z)
.divide(measuredSize);
contentContainer.position.copy(measuredCenter).negate();
contentContainer.position.z -= alignmentZMap[depthAlign.value] * measuredSize.z;
contentContainer.position.multiply(contentContainer.scale);
contentContainer.position.add(vectorHelper.set((leftInset - rightInset) * 0.5 * pixelSize, (bottomInset - topInset) * 0.5 * pixelSize, 0));
contentContainer.updateMatrix();
root.requestRender();
}, abortSignal);
}
const box3Helper = new Box3();
const smallValue = new Vector3().setScalar(0.001);
const defaultDepthAlign = 'back';
/**
* normalizes the content so it has a height of 1
*/
function createMeasureContent(flexState, measuredSize, measuredCenter, propertiesSignal, root, parentClippingRect, isVisible, orderInfo, sizeSignal, contentContainerRef) {
const clippingPlanes = createGlobalClippingPlanes(root, parentClippingRect);
const measureContent = () => {
const contentContainer = contentContainerRef.current;
if (contentContainer == null) {
return;
}
contentContainer.traverse((object) => {
if (object instanceof Mesh) {
setupRenderOrder(object, root, orderInfo);
object.material.clippingPlanes = clippingPlanes;
object.material.needsUpdate = true;
object.raycast = makeClippedCast(object, object.raycast, root.objectRef, parentClippingRect, orderInfo, flexState);
}
});
const parent = contentContainer.parent;
contentContainer.parent = null;
box3Helper.setFromObject(contentContainer);
box3Helper.getSize(measuredSize).max(smallValue);
sizeSignal.value = measuredSize;
if (parent != null) {
contentContainer.parent = parent;
}
box3Helper.getCenter(measuredCenter);
root.requestRender();
};
return () => {
const properties = propertiesSignal.peek();
updateRenderProperties(contentContainerRef, isVisible.peek(), untracked(() => properties.read('renderOrder', 0)), untracked(() => properties.read('depthTest', true)), untracked(() => properties.read('depthWrite', false)));
measureContent();
};
}
function updateRenderProperties(contentContainerRef, visible, renderOrder, depthTest, depthWrite) {
const contentContainer = contentContainerRef.current;
if (contentContainer == null) {
return;
}
contentContainer.visible = visible;
contentContainer.traverse((object) => {
if (!(object instanceof Mesh)) {
return;
}
if (renderOrder != null) {
object.renderOrder = renderOrder;
}
if (!(object.material instanceof Material)) {
return;
}
object.material.depthTest = depthTest;
object.material.depthWrite = depthWrite;
object.material.transparent = true;
});
}