diff --git a/docs/getting-started/components-and-properties.md b/docs/getting-started/components-and-properties.md index ac114391..4562011c 100644 --- a/docs/getting-started/components-and-properties.md +++ b/docs/getting-started/components-and-properties.md @@ -93,6 +93,9 @@ In addition to the flexbox properties, the container has properties for styling | zIndexOffset | number | | receiveShadow | boolean | | castShadow | boolean | +| depthTest | boolean | +| depthWrite | boolean | +| renderOrder | number | | backgroundColor | ColorRepresentation | | backgroundOpacity | number | | panelMaterialClass | Material class | @@ -126,7 +129,7 @@ In addition to the flexbox properties, the container has properties for styling ## Root -Every layout needs to start with a `Root` component. The `Root` component has all the properties of a `Container` component. The `pixelSize` property of the `Root` component allows you to specify the relation of pixels inside the layout with the three.js units in the scene. The `anchorX` and `anchorY` properties allow you to specify where the `Root` component is anchored in relation to its position. The `sizeX` and `sizeY` properties can be used to give the layout a fixed size in three.js units. The `Root` component also allows to control the `renderOrder` and `depthTest` of the whole user interface. +Every layout needs to start with a `Root` component. The `Root` component has all the properties of a `Container` component. The `pixelSize` property of the `Root` component allows you to specify the relation of pixels inside the layout with the three.js units in the scene. The `anchorX` and `anchorY` properties allow you to specify where the `Root` component is anchored in relation to its position. The `sizeX` and `sizeY` properties can be used to give the layout a fixed size in three.js units. ```jsx showLineNumbers @@ -138,14 +141,12 @@ Every layout needs to start with a `Root` component. The `Root` component has al
View all properties specific to the `Root` component -| Property | Type | -| ----------- | ------------------------- | -| anchorX | "left", "center", "right" | -| anchorY | "top", "center", "bottom" | -| sizeX | number | -| sizeY | number | -| renderOrder | number | -| depthTest | boolean | +| Property | Type | +| -------- | ------------------------- | +| anchorX | "left", "center", "right" | +| anchorY | "top", "center", "bottom" | +| sizeX | number | +| sizeY | number |
@@ -167,8 +168,6 @@ The `Fullscreen` component wraps the `Root` component and binds its content dire | ---------------- | ------- | | attachCamera | boolean | | distanceToCamera | number | -| renderOrder | number | -| depthTest | boolean | @@ -544,5 +543,5 @@ Each component exposes the `ComponentInternals` when using a `ref`. The componen | maxScrollPosition | the maximum x/y scroll position, based on the size of the children | | isClipped | exploses whether the element is fully clipped by some ancestor | | setStyle | modifies the styles of the element (the provided styles have a higher precedence then the element's properties) | -| getStyle | get the current style of the object | +| getStyle | get the current style of the object | | getComputedProperty | read the current value for any property (combines default properties, direct properties, and styles) | diff --git a/package.json b/package.json index b16cbc9d..3b8b082d 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,6 @@ "typescript-json-schema": "^0.63.0", "vite": "^5.0.12", "vercel": "^34.1.8", - "@react-three/xr": "6.4.3" + "@react-three/xr": "6.4.4" } } diff --git a/packages/react/src/container.tsx b/packages/react/src/container.tsx index cb2a2a1f..7aac7afb 100644 --- a/packages/react/src/container.tsx +++ b/packages/react/src/container.tsx @@ -55,7 +55,7 @@ export const Container: ( - + {properties.children} diff --git a/packages/react/src/default.tsx b/packages/react/src/default.tsx index 6b792f16..cddf59d9 100644 --- a/packages/react/src/default.tsx +++ b/packages/react/src/default.tsx @@ -16,6 +16,7 @@ export function DefaultProperties(properties: DefaultPropertiesProperties) { if (key === 'children') { continue } + //TODO: this is not correctly merged but rather overwritten const value = properties[key as keyof AllOptionalProperties] if (value == null) { continue diff --git a/packages/react/src/image.tsx b/packages/react/src/image.tsx index c6cf0e62..8ca8162a 100644 --- a/packages/react/src/image.tsx +++ b/packages/react/src/image.tsx @@ -55,7 +55,7 @@ export const Image: ( - + {properties.children} diff --git a/packages/react/src/root.tsx b/packages/react/src/root.tsx index cf8ee59c..d53fa4f7 100644 --- a/packages/react/src/root.tsx +++ b/packages/react/src/root.tsx @@ -91,7 +91,7 @@ export const Root: ( - + {properties.children} diff --git a/packages/react/src/svg.tsx b/packages/react/src/svg.tsx index 4e524302..a7c82e6a 100644 --- a/packages/react/src/svg.tsx +++ b/packages/react/src/svg.tsx @@ -48,7 +48,7 @@ export const Svg: ( - + {properties.children} diff --git a/packages/uikit/src/components/container.ts b/packages/uikit/src/components/container.ts index d8ff47e7..fe2b2460 100644 --- a/packages/uikit/src/components/container.ts +++ b/packages/uikit/src/components/container.ts @@ -37,7 +37,7 @@ import { darkPropertyTransformers } from '../dark.js' import { getDefaultPanelMaterialConfig } from '../panel/index.js' import { computedInheritableProperty, - computePointerEventsProperties, + computeOutgoingDefaultProperties, setupInteractableDecendant, setupPointerEvents, UpdateMatrixWorldProperties, @@ -66,7 +66,7 @@ export function createContainer( parentCtx: ParentContext, style: Signal, properties: Signal, - defaultProperties: Signal, + incommingDefaultProperties: Signal, object: Object3DRef, childrenContainer: Object3DRef, ) { @@ -79,7 +79,7 @@ export function createContainer( setupCursorCleanup(hoveredSignal, initializers) //properties - const mergedProperties = computedMergedProperties(style, properties, defaultProperties, { + const mergedProperties = computedMergedProperties(style, properties, incommingDefaultProperties, { ...darkPropertyTransformers, ...createResponsivePropertyTransformers(parentCtx.root.size), ...createHoverPropertyTransformers(hoveredSignal), @@ -144,9 +144,8 @@ export function createContainer( globalMatrix, initializers, ) - const pointerEventsProperties = computePointerEventsProperties(mergedProperties) - setupPointerEvents(pointerEventsProperties, interactionPanel, initializers) - setupInteractableDecendant(pointerEventsProperties.pointerEvents, parentCtx.root, interactionPanel, initializers) + setupPointerEvents(mergedProperties, interactionPanel, initializers) + setupInteractableDecendant(mergedProperties, parentCtx.root, interactionPanel, initializers) const scrollHandlers = computedScrollHandlers( scrollPosition, parentCtx.anyAncestorScrollable, @@ -166,7 +165,7 @@ export function createContainer( setupClippedListeners(style, properties, isClipped, initializers) return Object.assign(flexState, { - pointerEventsProperties, + defaultProperties: computeOutgoingDefaultProperties(mergedProperties), globalMatrix, isClipped, isVisible, @@ -179,7 +178,14 @@ export function createContainer( root: parentCtx.root, scrollPosition, interactionPanel, - handlers: computedHandlers(style, properties, defaultProperties, hoveredSignal, activeSignal, scrollHandlers), + handlers: computedHandlers( + style, + properties, + incommingDefaultProperties, + hoveredSignal, + activeSignal, + scrollHandlers, + ), initializers, }) } diff --git a/packages/uikit/src/components/content.ts b/packages/uikit/src/components/content.ts index 16997b78..fa609a8a 100644 --- a/packages/uikit/src/components/content.ts +++ b/packages/uikit/src/components/content.ts @@ -9,11 +9,10 @@ import { AllOptionalProperties, WithClasses, WithReactive } from '../properties/ import { createResponsivePropertyTransformers } from '../responsive.js' import { ElementType, OrderInfo, ZIndexProperties, computedOrderInfo, setupRenderOrder } from '../order.js' import { createActivePropertyTransfomers } from '../active.js' -import { Signal, computed, effect, signal } from '@preact/signals-core' +import { Signal, computed, effect, signal, untracked } from '@preact/signals-core' import { VisibilityProperties, WithConditionals, - computePointerEventsProperties, computedGlobalMatrix, computedHandlers, computedIsVisible, @@ -27,7 +26,11 @@ import { import { Initializers, alignmentZMap } from '../utils.js' import { Listeners, setupLayoutListeners, setupClippedListeners } from '../listeners.js' import { Object3DRef, ParentContext, RootContext } from '../context.js' -import { PanelGroupProperties, computedPanelGroupDependencies } from '../panel/instanced-panel-group.js' +import { + PanelGroupProperties, + RenderProperties, + computedPanelGroupDependencies, +} from '../panel/instanced-panel-group.js' import { createInteractionPanel } from '../panel/instanced-panel-mesh.js' import { Box3, Material, Mesh, Object3D, Vector3 } from 'three' import { darkPropertyTransformers } from '../dark.js' @@ -47,7 +50,8 @@ export type InheritableContentProperties = WithClasses< PanelGroupProperties & DepthAlignProperties & KeepAspectRatioProperties & - VisibilityProperties + VisibilityProperties & + RenderProperties > > > @@ -136,15 +140,13 @@ export function createContent( ) setupMatrixWorldUpdate(true, true, object, parentCtx.root, globalMatrix, initializers, false) - const pointerEventsProperties = computePointerEventsProperties(mergedProperties) - setupPointerEvents(pointerEventsProperties, object, initializers) - setupInteractableDecendant(pointerEventsProperties.pointerEvents, parentCtx.root, object, initializers) + setupPointerEvents(mergedProperties, object, initializers) + setupInteractableDecendant(mergedProperties, parentCtx.root, object, initializers) setupLayoutListeners(style, properties, flexState.size, initializers) setupClippedListeners(style, properties, isClipped, initializers) return Object.assign(flexState, { - pointerEventsProperties, globalMatrix, isClipped, isVisible, @@ -196,6 +198,7 @@ function createMeasureContent( visible: boolean, renderOrder: number, depthTest: boolean, + depthWrite: boolean, ) => { if (content == null) { return @@ -210,6 +213,7 @@ function createMeasureContent( return } object.material.depthTest = depthTest + object.material.depthWrite = depthWrite }) root.requestRender() } @@ -243,11 +247,13 @@ function createMeasureContent( initializers.push( () => effect(() => { + const properties = propertiesSignal.value updateRenderProperties( contentContainerRef.current, isVisible.value, - root.renderOrder.value, - root.depthTest.value, + properties.read('renderOrder', 0), + properties.read('depthTest', true), + properties.read('depthWrite', false), ) root.requestRender() }), @@ -302,11 +308,13 @@ function createMeasureContent( }, ) return () => { + const properties = propertiesSignal.peek() updateRenderProperties( contentContainerRef.current, isVisible.peek(), - root.renderOrder.peek(), - root.depthTest.peek(), + untracked(() => properties.read('renderOrder', 0)), + untracked(() => properties.read('depthTest', true)), + untracked(() => properties.read('depthWrite', false)), ) measureContent() } diff --git a/packages/uikit/src/components/custom.ts b/packages/uikit/src/components/custom.ts index 03ad7004..9596f3a1 100644 --- a/packages/uikit/src/components/custom.ts +++ b/packages/uikit/src/components/custom.ts @@ -21,14 +21,14 @@ import { setupMatrixWorldUpdate, setupInteractableDecendant, setupPointerEvents, + computeOutgoingDefaultProperties, } from './utils.js' import { Initializers } from '../utils.js' import { Listeners, setupLayoutListeners, setupClippedListeners } from '../listeners.js' import { Object3DRef, ParentContext } from '../context.js' import { FrontSide, Material, Mesh } from 'three' import { darkPropertyTransformers } from '../dark.js' -import { ShadowProperties, makeClippedCast } from '../panel/index.js' -import { computePointerEventsProperties } from '../internals.js' +import { RenderProperties, ShadowProperties, makeClippedCast } from '../panel/index.js' export type InheritableCustomContainerProperties = WithClasses< WithConditionals< @@ -40,7 +40,8 @@ export type InheritableCustomContainerProperties = WithClasses< TransformProperties & ScrollbarProperties & ShadowProperties & - VisibilityProperties + VisibilityProperties & + RenderProperties > > > @@ -98,18 +99,24 @@ export function createCustomContainer( material.clippingPlanes = clippingPlanes material.needsUpdate = true material.shadowSide = FrontSide - subscriptions.push(() => - effect(() => { - material.depthTest = parentCtx.root.depthTest.value - parentCtx.root.requestRender() - }), + subscriptions.push( + () => + effect(() => { + material.depthTest = mergedProperties.value.read('depthTest', true) + parentCtx.root.requestRender() + }), + () => + effect(() => { + material.depthWrite = mergedProperties.value.read('depthWrite', false) + parentCtx.root.requestRender() + }), ) } mesh.raycast = makeClippedCast(mesh, mesh.raycast, parentCtx.root.object, parentCtx.clippingRect, orderInfo) setupRenderOrder(mesh, parentCtx.root, orderInfo) subscriptions.push( effect(() => { - mesh.renderOrder = parentCtx.root.renderOrder.value + mesh.renderOrder = mergedProperties.value.read('renderOrder', 0) parentCtx.root.requestRender() }), effect(() => { @@ -140,15 +147,13 @@ export function createCustomContainer( setupMatrixWorldUpdate(true, true, object, parentCtx.root, globalMatrix, initializers, false) - const pointerEventsProperties = computePointerEventsProperties(mergedProperties) - setupPointerEvents(pointerEventsProperties, object, initializers) - setupInteractableDecendant(pointerEventsProperties.pointerEvents, parentCtx.root, object, initializers) + setupPointerEvents(mergedProperties, object, initializers) + setupInteractableDecendant(mergedProperties, parentCtx.root, object, initializers) setupLayoutListeners(style, properties, flexState.size, initializers) setupClippedListeners(style, properties, isClipped, initializers) return Object.assign(flexState, { - pointerEventsProperties, globalMatrix, isClipped, isVisible, diff --git a/packages/uikit/src/components/icon.ts b/packages/uikit/src/components/icon.ts index 879d916b..eba16b9f 100644 --- a/packages/uikit/src/components/icon.ts +++ b/packages/uikit/src/components/icon.ts @@ -13,7 +13,7 @@ import { VisibilityProperties, WithConditionals, applyAppearancePropertiesToGroup, - computePointerEventsProperties, + computeOutgoingDefaultProperties, computedGlobalMatrix, computedHandlers, computedIsVisible, @@ -138,15 +138,13 @@ export function createIcon( setupMatrixWorldUpdate(true, true, object, parentCtx.root, globalMatrix, initializers, false) - const pointerEventsProperties = computePointerEventsProperties(mergedProperties) - setupPointerEvents(pointerEventsProperties, object, initializers) - setupInteractableDecendant(pointerEventsProperties.pointerEvents, parentCtx.root, object, initializers) + setupPointerEvents(mergedProperties, object, initializers) + setupInteractableDecendant(mergedProperties, parentCtx.root, object, initializers) setupLayoutListeners(style, properties, flexState.size, initializers) setupClippedListeners(style, properties, isClipped, initializers) return Object.assign(flexState, { - pointerEventsProperties, globalMatrix, isClipped, isVisible, @@ -243,6 +241,6 @@ function createIconGroup( parentContext.root.requestRender() }), ) - applyAppearancePropertiesToGroup(propertiesSignal, group, initializers, parentContext.root) + applyAppearancePropertiesToGroup(propertiesSignal, group, initializers) return group } diff --git a/packages/uikit/src/components/image.ts b/packages/uikit/src/components/image.ts index 753bf868..f0cb9939 100644 --- a/packages/uikit/src/components/image.ts +++ b/packages/uikit/src/components/image.ts @@ -41,7 +41,7 @@ import { UpdateMatrixWorldProperties, VisibilityProperties, WithConditionals, - computePointerEventsProperties, + computeOutgoingDefaultProperties, computedGlobalMatrix, computedHandlers, computedIsVisible, @@ -188,9 +188,8 @@ export function createImage( initializers, ) - const pointerEventsProperties = computePointerEventsProperties(mergedProperties) - setupPointerEvents(pointerEventsProperties, imageMesh, initializers) - setupInteractableDecendant(pointerEventsProperties.pointerEvents, parentCtx.root, imageMesh, initializers) + setupPointerEvents(mergedProperties, imageMesh, initializers) + setupInteractableDecendant(mergedProperties, parentCtx.root, imageMesh, initializers) const scrollHandlers = computedScrollHandlers( scrollPosition, parentCtx.anyAncestorScrollable, @@ -210,7 +209,7 @@ export function createImage( setupClippedListeners(style, properties, isClipped, initializers) return Object.assign(flexState, { - pointerEventsProperties, + defaultProperties: computeOutgoingDefaultProperties(mergedProperties), globalMatrix, scrollPosition, isClipped, @@ -270,6 +269,7 @@ function createImageMesh( ) setupImageMaterials( propertiesSignal, + textureSignal, mesh, flexState.size, flexState.borderInset, @@ -345,16 +345,6 @@ function createImageMesh( mesh.visible = isMeshVisible.value parentContext.root.requestRender() }), - () => - effect(() => { - const map = textureSignal.value ?? null - if (mesh.material.map === map) { - return - } - mesh.material.map = map - mesh.material.needsUpdate = true - parentContext.root.requestRender() - }), () => effect(() => { if (flexState.size.value == null) { @@ -418,6 +408,7 @@ async function loadTextureImpl(src?: string | Texture): Promise<(Texture & { dis function setupImageMaterials( propertiesSignal: Signal, + textureSignal: Signal, target: Mesh, size: Signal, borderInset: Signal, @@ -433,24 +424,34 @@ function setupImageMaterials( target.customDepthMaterial.clippingPlanes = clippingPlanes target.customDistanceMaterial.clippingPlanes = clippingPlanes - const panelMaterialClass = computedInheritableProperty(propertiesSignal, 'panelMaterialClass', MeshBasicMaterial) initializers.push((subscriptions) => { subscriptions.push( effect(() => { - const material = createPanelMaterial(panelMaterialClass.value, info) + const material = createPanelMaterial(propertiesSignal.value.read('panelMaterialClass', MeshBasicMaterial), info) material.clippingPlanes = clippingPlanes target.material = material const cleanupDepthTestEffect = effect(() => { - material.depthTest = root.depthTest.value + material.depthTest = propertiesSignal.value.read('depthTest', true) + root.requestRender() + }) + const cleanupDepthWriteEffect = effect(() => { + material.depthWrite = propertiesSignal.value.read('depthWrite', false) + root.requestRender() + }) + const cleanupTextureEffect = effect(() => { + ;(material as any).map = textureSignal.value ?? null + material.needsUpdate = true root.requestRender() }) return () => { + cleanupTextureEffect() + cleanupDepthWriteEffect() cleanupDepthTestEffect() material.dispose() } }), effect(() => { - target.renderOrder = root.renderOrder.value + target.renderOrder = propertiesSignal.value.read('renderOrder', 0) root.requestRender() }), effect(() => { diff --git a/packages/uikit/src/components/input.ts b/packages/uikit/src/components/input.ts index 19ad4f3c..ce12b3b7 100644 --- a/packages/uikit/src/components/input.ts +++ b/packages/uikit/src/components/input.ts @@ -21,7 +21,7 @@ import { UpdateMatrixWorldProperties, VisibilityProperties, WithConditionals, - computePointerEventsProperties, + computeOutgoingDefaultProperties, computedGlobalMatrix, computedHandlers, computedIsVisible, @@ -250,9 +250,8 @@ export function createInput( initializers, ) - const pointerEventsProperties = computePointerEventsProperties(mergedProperties) - setupPointerEvents(pointerEventsProperties, interactionPanel, initializers) - setupInteractableDecendant(pointerEventsProperties.pointerEvents, parentCtx.root, interactionPanel, initializers) + setupPointerEvents(mergedProperties, interactionPanel, initializers) + setupInteractableDecendant(mergedProperties, parentCtx.root, interactionPanel, initializers) const updateMatrixWorld = computedInheritableProperty(mergedProperties, 'updateMatrixWorld', false) setupMatrixWorldUpdate(updateMatrixWorld, false, object, parentCtx.root, globalMatrix, initializers, false) @@ -307,7 +306,6 @@ export function createInput( const selectionHandlers = computedSelectionHandlers(type, valueSignal, flexState, instancedTextRef, focus, disabled) return Object.assign(flexState, { - pointerEventsProperties, globalMatrix, isClipped, isVisible, diff --git a/packages/uikit/src/components/root.ts b/packages/uikit/src/components/root.ts index 3c24923c..ce9e108d 100644 --- a/packages/uikit/src/components/root.ts +++ b/packages/uikit/src/components/root.ts @@ -25,7 +25,7 @@ import { UpdateMatrixWorldProperties, VisibilityProperties, WithConditionals, - computePointerEventsProperties, + computeOutgoingDefaultProperties, computedHandlers, computedIsVisible, computedMergedProperties, @@ -54,8 +54,6 @@ export type InheritableRootProperties = WithClasses< PanelProperties & ScrollbarProperties & PanelGroupProperties & { - renderOrder?: number - depthTest?: boolean sizeX?: number sizeY?: number anchorX?: keyof typeof alignmentXMap @@ -119,9 +117,6 @@ export function createRoot( }, ) - const renderOrder = computedInheritableProperty(mergedProperties, 'renderOrder', 0) - const depthTest = computedInheritableProperty(mergedProperties, 'depthTest', true) - const ctx: WithCameraDistance & Pick = { cameraDistance: 0, onFrameSet, @@ -148,7 +143,7 @@ export function createRoot( const orderInfo = computedOrderInfo(undefined, ElementType.Panel, groupDeps, undefined) - const panelGroupManager = new PanelGroupManager(renderOrder, depthTest, pixelSize, ctx, object, initializers) + const panelGroupManager = new PanelGroupManager(pixelSize, ctx, object, initializers) const onCameraDistanceFrame = () => { if (object.current == null) { @@ -204,7 +199,7 @@ export function createRoot( setupLayoutListeners(style, properties, flexState.size, initializers) - const gylphGroupManager = new GlyphGroupManager(renderOrder, depthTest, pixelSize, ctx, object, initializers) + const gylphGroupManager = new GlyphGroupManager(pixelSize, ctx, object, initializers) const rootCtx: RootContext = Object.assign(ctx, { objectInvertedWorldMatrix: new Matrix4(), @@ -219,8 +214,6 @@ export function createRoot( object, panelGroupManager, pixelSize, - renderOrder, - depthTest, renderer, size: flexState.size, }) @@ -234,9 +227,9 @@ export function createRoot( initializers, ) - const pointerEventsProperties = computePointerEventsProperties(mergedProperties) - setupPointerEvents(pointerEventsProperties, interactionPanel, initializers) - setupInteractableDecendant(pointerEventsProperties.pointerEvents, rootCtx, interactionPanel, initializers) + const outgoingDefaultProperties = computeOutgoingDefaultProperties(mergedProperties) + setupPointerEvents(mergedProperties, interactionPanel, initializers) + setupInteractableDecendant(mergedProperties, rootCtx, interactionPanel, initializers) //setup matrix world updates initializers.push(() => { @@ -269,8 +262,10 @@ export function createRoot( initializers, ) + console.log(outgoingDefaultProperties) + return Object.assign(flexState, { - pointerEventsProperties, + defaultProperties: outgoingDefaultProperties, globalMatrix, isVisible, scrollPosition, diff --git a/packages/uikit/src/components/svg.ts b/packages/uikit/src/components/svg.ts index 8ea51ff1..f3d5976f 100644 --- a/packages/uikit/src/components/svg.ts +++ b/packages/uikit/src/components/svg.ts @@ -55,7 +55,7 @@ import { PanelGroupProperties, computedPanelGroupDependencies, getDefaultPanelMa import { KeepAspectRatioProperties } from './image.js' import { computedInheritableProperty, - computePointerEventsProperties, + computeOutgoingDefaultProperties, setupInteractableDecendant, setupMatrixWorldUpdate, setupPointerEvents, @@ -166,7 +166,7 @@ export function createSvg( orderInfo, aspectRatio, ) - applyAppearancePropertiesToGroup(mergedProperties, svgObject, initializers, parentCtx.root) + applyAppearancePropertiesToGroup(mergedProperties, svgObject, initializers) const centerGroup = createCenterGroup(parentCtx.root, flexState, svgObject, aspectRatio, isVisible, initializers) const scrollPosition = createScrollPosition() @@ -209,17 +209,17 @@ export function createSvg( initializers, ) - const pointerEventsProperties = computePointerEventsProperties(mergedProperties) - setupPointerEvents(pointerEventsProperties, centerGroup, initializers) - setupPointerEvents(pointerEventsProperties, interactionPanel, initializers) - setupInteractableDecendant(pointerEventsProperties.pointerEvents, parentCtx.root, centerGroup, initializers) - setupInteractableDecendant(pointerEventsProperties.pointerEvents, parentCtx.root, interactionPanel, initializers) + const outgoingDefaultProperties = computeOutgoingDefaultProperties(mergedProperties) + setupPointerEvents(mergedProperties, centerGroup, initializers) + setupPointerEvents(mergedProperties, interactionPanel, initializers) + setupInteractableDecendant(mergedProperties, parentCtx.root, centerGroup, initializers) + setupInteractableDecendant(mergedProperties, parentCtx.root, interactionPanel, initializers) setupLayoutListeners(style, properties, flexState.size, initializers) setupClippedListeners(style, properties, isClipped, initializers) return Object.assign(flexState, { - pointerEventsProperties, + defaultProperties: outgoingDefaultProperties, globalMatrix, scrollPosition, isClipped, diff --git a/packages/uikit/src/components/text.ts b/packages/uikit/src/components/text.ts index 6aec2017..2431c32b 100644 --- a/packages/uikit/src/components/text.ts +++ b/packages/uikit/src/components/text.ts @@ -37,7 +37,7 @@ import { import { darkPropertyTransformers } from '../dark.js' import { computedInheritableProperty, - computePointerEventsProperties, + computeOutgoingDefaultProperties, UpdateMatrixWorldProperties, } from '../internals.js' @@ -150,9 +150,8 @@ export function createText( initializers, ) - const pointerEventsProperties = computePointerEventsProperties(mergedProperties) - setupPointerEvents(pointerEventsProperties, interactionPanel, initializers) - setupInteractableDecendant(pointerEventsProperties.pointerEvents, parentCtx.root, interactionPanel, initializers) + setupPointerEvents(mergedProperties, interactionPanel, initializers) + setupInteractableDecendant(mergedProperties, parentCtx.root, interactionPanel, initializers) const updateMatrixWorld = computedInheritableProperty(mergedProperties, 'updateMatrixWorld', false) setupMatrixWorldUpdate(updateMatrixWorld, false, object, parentCtx.root, globalMatrix, initializers, false) @@ -162,7 +161,6 @@ export function createText( setupClippedListeners(style, properties, isClipped, initializers) return Object.assign(flexState, { - pointerEventsProperties, globalMatrix, isClipped, isVisible, diff --git a/packages/uikit/src/components/utils.ts b/packages/uikit/src/components/utils.ts index 9bb3b904..512c8c0f 100644 --- a/packages/uikit/src/components/utils.ts +++ b/packages/uikit/src/components/utils.ts @@ -1,4 +1,4 @@ -import { Signal, computed, effect } from '@preact/signals-core' +import { ReadonlySignal, Signal, computed, effect } from '@preact/signals-core' import { BufferGeometry, Color, Material, Matrix4, Mesh, MeshBasicMaterial, Object3D } from 'three' import { WithActive, addActiveHandlers } from '../active.js' import { WithPreferredColorScheme } from '../dark.js' @@ -15,7 +15,7 @@ import { PropertyTransformers, computedInheritableProperty, } from '../properties/index.js' -import { PointerEventsProperties } from '../internals.js' +import { AllowedPointerEventsType, PointerEventsProperties } from '../internals.js' export function disposeGroup(object: Object3D | undefined) { object?.traverse((mesh) => { @@ -221,27 +221,31 @@ export function applyAppearancePropertiesToGroup( propertiesSignal: Signal, group: Signal | Object3D, initializers: Initializers, - root: RootContext, ) { - const color = computedInheritableProperty(propertiesSignal, 'color', undefined) - const opacity = computedInheritableProperty(propertiesSignal, 'opacity', 1) initializers.push(() => effect(() => { + const properties = propertiesSignal.value + const color = properties.read('color', undefined) let c: Color | undefined - if (Array.isArray(color.value)) { - c = colorHelper.setRGB(...color.value) - } else if (color.value != null) { - c = colorHelper.set(color.value) + if (Array.isArray(color)) { + c = colorHelper.setRGB(...color) + } else if (color != null) { + c = colorHelper.set(color) } + const opacity = properties.read('opacity', 1) + const depthTest = properties.read('depthTest', true) + const depthWrite = properties.read('depthWrite', false) + const renderOrder = properties.read('renderOrder', 0) readReactive(group)?.traverse((mesh) => { if (!(mesh instanceof Mesh)) { return } - mesh.renderOrder = root.renderOrder.value + mesh.renderOrder = renderOrder const material: MeshBasicMaterial = mesh.material material.color.copy(c ?? mesh.userData.color) - material.opacity = opacity.value - material.depthTest = root.depthTest.value + material.opacity = opacity + material.depthTest = depthTest + material.depthWrite = depthWrite }) }), ) @@ -308,29 +312,40 @@ export function setupMatrixWorldUpdate( ) } -export type ComputedPointerEventsProperties = ReturnType +export function computeOutgoingDefaultProperties(propertiesSignal: Signal) { + return { + pointerEvents: computedInheritableProperty( + propertiesSignal, + 'pointerEvents', + undefined, + ), + pointerEventsOrder: computedInheritableProperty( + propertiesSignal, + 'pointerEventsOrder', + undefined, + ), + pointerEventsType: computedInheritableProperty( + propertiesSignal, + 'pointerEventsType', + undefined, + ), + renderOrder: computedInheritableProperty(propertiesSignal, 'renderOrder', 0), + depthTest: computedInheritableProperty(propertiesSignal, 'depthTest', true), + depthWrite: computedInheritableProperty(propertiesSignal, 'depthWrite', false), + } +} -export function computePointerEventsProperties(mergedProperties: Signal) { - const pointerEvents = computedInheritableProperty( - mergedProperties, - 'pointerEvents', - undefined, - ) - const pointerEventsOrder = computedInheritableProperty( - mergedProperties, - 'pointerEventsOrder', - undefined, - ) - const pointerEventsType = computedInheritableProperty( - mergedProperties, - 'pointerEventsType', - undefined, - ) - return { pointerEvents, pointerEventsOrder, pointerEventsType } +export type OutgoingDefaultProperties = { + renderOrder: ReadonlySignal + depthTest: ReadonlySignal + depthWrite: ReadonlySignal + pointerEvents: ReadonlySignal<'none' | 'auto' | 'listener'> + pointerEventsType: ReadonlySignal + pointerEventsOrder: ReadonlySignal } export function setupPointerEvents( - properties: ComputedPointerEventsProperties, + propertiesSignal: Signal, targetRef: Object3D | Object3DRef, initializers: Initializers, ) { @@ -339,24 +354,33 @@ export function setupPointerEvents( if (target == null) { return () => {} } + const properties = propertiesSignal.value target.defaultPointerEvents = 'auto' return effect(() => { - target.pointerEvents = properties.pointerEvents.value - target.pointerEventsOrder = properties.pointerEventsOrder.value - target.pointerEventsType = properties.pointerEventsType.value + target.pointerEvents = properties.read('pointerEvents', undefined) + target.pointerEventsOrder = properties.read( + 'pointerEventsOrder', + undefined, + ) + target.pointerEventsType = properties.read( + 'pointerEventsType', + undefined, + ) }) }) } export function setupInteractableDecendant( - pointerEvents: ComputedPointerEventsProperties['pointerEvents'], + propertiesSignal: Signal, rootContext: RootContext, targetRef: Object3D | Object3DRef, initializers: Initializers, ) { initializers.push(() => effect(() => { - if (pointerEvents.value === 'none') { + if ( + propertiesSignal.value.read('pointerEvents', undefined) === 'none' + ) { return } const descendants = rootContext.interactableDescendants diff --git a/packages/uikit/src/context.ts b/packages/uikit/src/context.ts index f46309e8..6276d04c 100644 --- a/packages/uikit/src/context.ts +++ b/packages/uikit/src/context.ts @@ -24,8 +24,6 @@ export type RootContext = WithCameraDistance & gylphGroupManager: GlyphGroupManager panelGroupManager: PanelGroupManager pixelSize: Signal - renderOrder: Signal - depthTest: Signal onFrameSet: Set<(delta: number) => void> onUpdateMatrixWorldSet: Set<() => void> interactableDescendants: Array diff --git a/packages/uikit/src/panel/instanced-panel-group.ts b/packages/uikit/src/panel/instanced-panel-group.ts index 20cef063..db4fbb21 100644 --- a/packages/uikit/src/panel/instanced-panel-group.ts +++ b/packages/uikit/src/panel/instanced-panel-group.ts @@ -14,40 +14,50 @@ import { Signal, computed, effect } from '@preact/signals-core' import { MergedProperties } from '../properties/merged.js' import { Object3DRef, RootContext } from '../context.js' import { Initializers } from '../utils.js' -import { computedInheritableProperty } from '../properties/index.js' export type ShadowProperties = { receiveShadow?: boolean castShadow?: boolean } +export type RenderProperties = { + depthWrite?: boolean + depthTest?: boolean + renderOrder?: number +} + export type PanelGroupProperties = { panelMaterialClass?: MaterialClass -} & ShadowProperties +} & ShadowProperties & + RenderProperties export function computedPanelGroupDependencies(propertiesSignal: Signal) { - const panelMaterialClass = computedInheritableProperty(propertiesSignal, 'panelMaterialClass', MeshBasicMaterial) - const castShadow = computedInheritableProperty(propertiesSignal, 'castShadow', false) - const receiveShadow = computedInheritableProperty(propertiesSignal, 'receiveShadow', false) - return computed>(() => ({ - panelMaterialClass: panelMaterialClass.value, - castShadow: castShadow.value, - receiveShadow: receiveShadow.value, - })) + return computed>(() => { + const properties = propertiesSignal.value + return { + panelMaterialClass: properties.read('panelMaterialClass', MeshBasicMaterial), + castShadow: properties.read('castShadow', false), + receiveShadow: properties.read('receiveShadow', false), + depthWrite: properties.read('depthWrite', false), + depthTest: properties.read('depthTest', true), + renderOrder: properties.read('renderOrder', 0), + } + }) } export const defaultPanelDependencies: Required = { panelMaterialClass: MeshBasicMaterial, castShadow: false, receiveShadow: false, + depthWrite: false, + depthTest: true, + renderOrder: 0, } export class PanelGroupManager { - private map = new Map>() + private map = new Map>() constructor( - private renderOrder: Signal, - private depthTest: Signal, private pixelSize: Signal, private root: WithCameraDistance & Pick, private object: Object3DRef, @@ -60,18 +70,6 @@ export class PanelGroupManager { return () => root.onFrameSet.delete(onFrame) }, () => () => this.traverse((group) => group.destroy()), - () => - effect(() => { - const ro = renderOrder.value - this.traverse((group) => group.setRenderOrder(ro)) - this.root.requestRender() - }), - () => - effect(() => { - const dt = depthTest.value - this.traverse((group) => group.setDepthTest(dt)) - this.root.requestRender() - }), ) } @@ -83,24 +81,25 @@ export class PanelGroupManager { } } - getGroup( - majorIndex: number, - { panelMaterialClass, receiveShadow, castShadow }: Required = defaultPanelDependencies, - ) { - let groups = this.map.get(panelMaterialClass) + getGroup(majorIndex: number, properties: Required = defaultPanelDependencies) { + let groups = this.map.get(properties.panelMaterialClass) if (groups == null) { - this.map.set(panelMaterialClass, (groups = new Map())) + this.map.set(properties.panelMaterialClass, (groups = new Map())) } - const key = (majorIndex << 2) + ((receiveShadow ? 1 : 0) << 1) + (castShadow ? 1 : 0) + const key = [ + majorIndex, + properties.renderOrder, + properties.depthTest, + properties.depthWrite, + properties.receiveShadow, + properties.castShadow, + ].join(',') let panelGroup = groups.get(key) if (panelGroup == null) { groups.set( key, (panelGroup = new InstancedPanelGroup( - this.renderOrder.peek(), - this.depthTest.peek(), this.object, - panelMaterialClass, this.pixelSize, this.root, { @@ -108,8 +107,7 @@ export class PanelGroupManager { majorIndex, minorIndex: 0, }, - receiveShadow, - castShadow, + properties, )), ) } @@ -162,18 +160,15 @@ export class InstancedPanelGroup { } constructor( - private renderOrder: number, - depthTest: boolean, private readonly object: Object3DRef, - materialClass: MaterialClass, public readonly pixelSize: Signal, public readonly root: WithCameraDistance & Pick, private readonly orderInfo: OrderInfo, - private readonly meshReceiveShadow: boolean, - private readonly meshCastShadow: boolean, + private readonly panelGroupProperties: Required, ) { - this.instanceMaterial = createPanelMaterial(materialClass, { type: 'instanced' }) - this.instanceMaterial.depthTest = depthTest + this.instanceMaterial = createPanelMaterial(panelGroupProperties.panelMaterialClass, { type: 'instanced' }) + this.instanceMaterial.depthTest = panelGroupProperties.depthTest + this.instanceMaterial.depthWrite = panelGroupProperties.depthWrite } private updateCount(): void { @@ -209,18 +204,6 @@ export class InstancedPanelGroup { this.root.requestRender() } - setDepthTest(depthTest: boolean) { - this.instanceMaterial.depthTest = depthTest - } - - setRenderOrder(renderOrder: number) { - this.renderOrder = renderOrder - if (this.mesh == null) { - return - } - this.mesh.renderOrder = renderOrder - } - insert(bucketIndex: number, panel: InstancedPanel): void { this.elementCount += 1 if (!addToSortedBuckets(this.buckets, bucketIndex, panel, this.activateElement)) { @@ -315,11 +298,11 @@ export class InstancedPanelGroup { this.instanceClipping = new InstancedBufferAttribute(clippingArray, 16, false) this.instanceClipping.setUsage(DynamicDrawUsage) this.mesh = new InstancedPanelMesh(this.instanceMatrix, this.instanceData, this.instanceClipping) - this.mesh.renderOrder = this.renderOrder + this.mesh.renderOrder = this.panelGroupProperties.renderOrder setupRenderOrder(this.mesh, this.root, { value: this.orderInfo }) this.mesh.material = this.instanceMaterial - this.mesh.receiveShadow = this.meshReceiveShadow - this.mesh.castShadow = this.meshCastShadow + this.mesh.receiveShadow = this.panelGroupProperties.receiveShadow + this.mesh.castShadow = this.panelGroupProperties.castShadow this.object.current?.add(this.mesh) } diff --git a/packages/uikit/src/panel/panel-material.ts b/packages/uikit/src/panel/panel-material.ts index 041dc458..ca85d737 100644 --- a/packages/uikit/src/panel/panel-material.ts +++ b/packages/uikit/src/panel/panel-material.ts @@ -208,7 +208,6 @@ export function createPanelMaterial>(MaterialCla material.clipShadows = true material.transparent = true material.toneMapped = false - material.depthWrite = false material.shadowSide = FrontSide material.defines.USE_UV = '' material.defines.USE_TANGENT = '' diff --git a/packages/uikit/src/scroll.ts b/packages/uikit/src/scroll.ts index 4ba1f765..784b2ae2 100644 --- a/packages/uikit/src/scroll.ts +++ b/packages/uikit/src/scroll.ts @@ -202,21 +202,25 @@ export function computedScrollHandlers( } return { onPointerDown: (event) => { - if (event.nativeEvent.pointerType === 'mouse') { - return - } event.stopImmediatePropagation?.() const localPoint = object.current!.worldToLocal(event.point.clone()) - const scrollbarAxisIndex = getIntersectedScrollbarIndex( - localPoint, - root.pixelSize.peek(), - scrollbarWidth.peek(), - nodeState.size.peek(), - nodeState.maxScrollPosition.peek(), - nodeState.borderInset.peek(), - scrollPosition.peek(), - ) + const scrollbarAxisIndex = + event.nativeEvent.pointerType != 'mouse' + ? undefined + : getIntersectedScrollbarIndex( + localPoint, + root.pixelSize.peek(), + scrollbarWidth.peek(), + nodeState.size.peek(), + nodeState.maxScrollPosition.peek(), + nodeState.borderInset.peek(), + scrollPosition.peek(), + ) + + if (event.nativeEvent.pointerType === 'mouse' && scrollbarAxisIndex == null) { + return + } if ('setPointerCapture' in event.object && typeof event.object.setPointerCapture === 'function') { event.object.setPointerCapture(event.pointerId) diff --git a/packages/uikit/src/text/render/instanced-glyph-group.ts b/packages/uikit/src/text/render/instanced-glyph-group.ts index 251b13b2..1dd37641 100644 --- a/packages/uikit/src/text/render/instanced-glyph-group.ts +++ b/packages/uikit/src/text/render/instanced-glyph-group.ts @@ -5,14 +5,12 @@ import { InstancedGlyphMaterial } from './instanced-gylph-material.js' import { Font } from '../font.js' import { ElementType, OrderInfo, WithCameraDistance, setupRenderOrder } from '../../order.js' import { Object3DRef, RootContext } from '../../context.js' -import { Signal, effect } from '@preact/signals-core' +import { Signal } from '@preact/signals-core' import { Initializers } from '../../utils.js' export class GlyphGroupManager { - private map = new Map>() + private map = new Map>() constructor( - private renderOrder: Signal, - private depthTest: Signal, private pixelSize: Signal, private root: WithCameraDistance & Pick, private object: Object3DRef, @@ -25,16 +23,6 @@ export class GlyphGroupManager { return () => root.onFrameSet.delete(onFrame) }, () => () => this.traverse((group) => group.destroy()), - () => - effect(() => { - const ro = renderOrder.value - this.traverse((group) => group.setRenderOrder(ro)) - }), - () => - effect(() => { - const dt = depthTest.value - this.traverse((group) => group.setDepthTest(dt)) - }), ) } @@ -46,18 +34,17 @@ export class GlyphGroupManager { } } - getGroup(majorIndex: number, font: Font) { + getGroup(majorIndex: number, depthTest: boolean, depthWrite: boolean, renderOrder: number, font: Font) { let groups = this.map.get(font) if (groups == null) { this.map.set(font, (groups = new Map())) } - let glyphGroup = groups?.get(majorIndex) + const key = [majorIndex, depthTest, depthWrite, renderOrder].join(',') + let glyphGroup = groups?.get(key) if (glyphGroup == null) { groups.set( - majorIndex, + key, (glyphGroup = new InstancedGlyphGroup( - this.renderOrder.peek(), - this.depthTest.peek(), this.object, font, this.pixelSize, @@ -67,6 +54,9 @@ export class GlyphGroupManager { elementType: ElementType.Text, minorIndex: 0, }, + depthTest, + depthWrite, + renderOrder, )), ) } @@ -90,30 +80,18 @@ export class InstancedGlyphGroup { private timeTillDecimate?: number constructor( - private renderOrder: number, - depthTest: boolean, private object: Object3DRef, font: Font, public readonly pixelSize: Signal, public readonly root: WithCameraDistance & Pick, private orderInfo: OrderInfo, + depthTest: boolean, + depthWrite: boolean, + private renderOrder: number, ) { this.instanceMaterial = new InstancedGlyphMaterial(font) this.instanceMaterial.depthTest = depthTest - } - - setDepthTest(depthTest: boolean) { - this.instanceMaterial.depthTest = depthTest - this.root.requestRender() - } - - setRenderOrder(renderOrder: number) { - this.renderOrder = renderOrder - if (this.mesh == null) { - return - } - this.mesh.renderOrder = renderOrder - this.root.requestRender() + this.instanceMaterial.depthWrite = depthWrite } requestActivate(glyph: InstancedGlyph): void { diff --git a/packages/uikit/src/text/render/instanced-text.ts b/packages/uikit/src/text/render/instanced-text.ts index b6cfdeea..c80398c4 100644 --- a/packages/uikit/src/text/render/instanced-text.ts +++ b/packages/uikit/src/text/render/instanced-text.ts @@ -1,8 +1,8 @@ -import { Signal, effect, signal, untracked } from '@preact/signals-core' +import { Signal, effect, signal } from '@preact/signals-core' import { InstancedGlyph } from './instanced-glyph.js' import { Matrix4, Vector2Tuple, Vector3Tuple } from 'three' import { ClippingRect } from '../../clipping.js' -import { ColorRepresentation, Initializers, Subscriptions, alignmentXMap, alignmentYMap } from '../../utils.js' +import { ColorRepresentation, Initializers, alignmentXMap, alignmentYMap } from '../../utils.js' import { getGlyphLayoutHeight, getGlyphOffsetX, @@ -92,7 +92,13 @@ export function createInstancedText( return } const instancedText = new InstancedText( - glyphGroupManager.getGroup(orderInfo.value.majorIndex, font), + glyphGroupManager.getGroup( + orderInfo.value.majorIndex, + properties.value.read('depthTest', true), + properties.value.read('depthWrite', false), + properties.value.read('renderOrder', 0), + font, + ), textAlign, verticalAlign, color, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index efc1d7af..41f6668f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,8 +15,8 @@ importers: specifier: ^8.16.2 version: 8.16.2(react-dom@18.3.1)(react@18.3.1)(three@0.161.0) '@react-three/xr': - specifier: 6.4.3 - version: 6.4.3(@react-three/fiber@8.16.2)(@types/react@18.3.1)(react-dom@18.3.1)(react@18.3.1)(three@0.161.0) + specifier: 6.4.4 + version: 6.4.4(@react-three/fiber@8.16.2)(@types/react@18.3.1)(react-dom@18.3.1)(react@18.3.1)(three@0.161.0) '@types/chai': specifier: ^4.3.10 version: 4.3.11 @@ -2753,17 +2753,17 @@ packages: engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} dev: true - /@pmndrs/pointer-events@6.4.3: - resolution: {integrity: sha512-NRn3K6CvYurA084n4S7ua6/xz9EG+OwFMCIXRRCi2cW7PLv7xgS+1lBlTUzeCFeG1tITTRRYDYZatK1v26cNxQ==} + /@pmndrs/pointer-events@6.4.4: + resolution: {integrity: sha512-3FYlHBItoYgrDvyh/pzDbFfA5eu5ByF0IhqVquqRcG6RbiT97uCiyGqjlSKSGcqGaqTMdMNVH1NpwS9OiXdXLA==} dev: true - /@pmndrs/xr@6.4.3(@types/react@18.3.1)(react@18.3.1)(three@0.161.0): - resolution: {integrity: sha512-deTmIzx3g6IJVS4jCwzFY5orXSVpntlN3bpg49xBfWxASe6jOAmqryLfKGUmCD5bMRxFsxktVH7HuYm56JT3/g==} + /@pmndrs/xr@6.4.4(@types/react@18.3.1)(react@18.3.1)(three@0.161.0): + resolution: {integrity: sha512-SYSxrZ5+dCZYvWcGvPY97PuQk0v48zuCSBY7WQIuwrpvo+ZclGrGA02flZvhdIAqUXW2Z1YO0d0QsM4m6rH6Yg==} peerDependencies: three: '*' dependencies: '@iwer/devui': 0.2.1(iwer@1.0.4) - '@pmndrs/pointer-events': 6.4.3 + '@pmndrs/pointer-events': 6.4.4 iwer: 1.0.4 meshline: 3.3.1(three@0.161.0) three: 0.161.0 @@ -3923,16 +3923,16 @@ packages: use-asset: 1.0.4(react@18.3.1) dev: false - /@react-three/xr@6.4.3(@react-three/fiber@8.16.2)(@types/react@18.3.1)(react-dom@18.3.1)(react@18.3.1)(three@0.161.0): - resolution: {integrity: sha512-TKoKxDPNFIsCZqAD8qBZ2DRhAJUm4iPiWGeQeagb5Ole7nssRb7Mse5Hkc9XZ2/G+hodYfIHLiMJGP5wHGgPig==} + /@react-three/xr@6.4.4(@react-three/fiber@8.16.2)(@types/react@18.3.1)(react-dom@18.3.1)(react@18.3.1)(three@0.161.0): + resolution: {integrity: sha512-50zIM8XY2okbpIrsIZYi9/4A7hcXW/bfV3Yojx/XyaIZBVylJDDD33KrVIRDBrC7+TGjTgHJFZE5+hnT0UBN5Q==} peerDependencies: '@react-three/fiber': '>=8' react: '>=18' react-dom: '>=18' three: '*' dependencies: - '@pmndrs/pointer-events': 6.4.3 - '@pmndrs/xr': 6.4.3(@types/react@18.3.1)(react@18.3.1)(three@0.161.0) + '@pmndrs/pointer-events': 6.4.4 + '@pmndrs/xr': 6.4.4(@types/react@18.3.1)(react@18.3.1)(three@0.161.0) '@react-three/fiber': 8.16.2(react-dom@18.3.1)(react@18.3.1)(three@0.161.0) react: 18.3.1 react-dom: 18.3.1(react@18.3.1)