Skip to content

Commit

Permalink
highligher added to dashboard
Browse files Browse the repository at this point in the history
  • Loading branch information
bbohlender committed Mar 6, 2025
1 parent 98aa455 commit 0a28da9
Show file tree
Hide file tree
Showing 15 changed files with 195 additions and 23 deletions.
1 change: 1 addition & 0 deletions examples/dashboard/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"type": "module",
"dependencies": {
"@preact/signals-core": "^1.5.1",
"@react-three/uikit": "workspace:^",
"@react-three/uikit-lucide": "workspace:^",
"vite-plugin-mkcert": "^1.17.4",
Expand Down
17 changes: 10 additions & 7 deletions examples/dashboard/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { TeamSwitcher } from './components/TeamSwitcher.js'
import { UserNav } from './components/UserNav.js'
import { create } from 'zustand'
import { noEvents, PointerEvents } from '@react-three/xr'
import { Highlighter } from './components/Highlighter.js'

setPreferredColorScheme('light')

Expand All @@ -36,13 +37,15 @@ export default function App() {
<CountFrames />
<PointerEvents />
<Fullscreen distanceToCamera={1} backgroundColor={0xffffff} dark={{ backgroundColor: 0x0 }}>
<Defaults>
<DialogAnchor>
<Container flexDirection="column" width="100%" height="100%" overflow="scroll">
<DashboardPage open={open} setOpen={setOpen} />
</Container>
</DialogAnchor>
</Defaults>
<Highlighter>
<Defaults>
<DialogAnchor>
<Container flexDirection="column" width="100%" height="100%" overflow="scroll">
<DashboardPage open={open} setOpen={setOpen} />
</Container>
</DialogAnchor>
</Defaults>
</Highlighter>
</Fullscreen>
</Canvas>
</>
Expand Down
76 changes: 76 additions & 0 deletions examples/dashboard/src/components/Highlighter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { Container, ContainerProperties, ContainerRef, isInteractionPanel } from '@react-three/uikit'
import { forwardRef, useRef } from 'react'
import { computed } from '@preact/signals-core'
import { Euler, Matrix4, Quaternion, Vector3 } from 'three'

const matrixHelper = new Matrix4()
const quaternionHelper = new Quaternion()

export const Highlighter = forwardRef<ContainerRef, ContainerProperties>(
({ onPointerOver, onPointerLeave, children, ...props }, ref) => {
const highlightRef = useRef<ContainerRef | null>(null)
return (
<Container
ref={ref}
{...props}
onPointerOver={(e) => {
if (!isInteractionPanel(e.object) || highlightRef.current == null) {
return
}
const {
internals: { globalMatrix, root, size },
} = e.object
const transformation = computed(() => {
const { value } = globalMatrix
if (value == null) {
return { translation: new Vector3(), scale: new Vector3(), rotation: new Euler() }
}
matrixHelper.copy(value)
const translation = new Vector3()
const scale = new Vector3()
matrixHelper.decompose(translation, quaternionHelper, scale)
const rotation = new Euler().setFromQuaternion(quaternionHelper)
return { translation, scale, rotation }
})
const width = computed(() => transformation.value.scale.x * (size.value?.[0] ?? 0))
const height = computed(() => transformation.value.scale.y * (size.value?.[1] ?? 0) * 1)
highlightRef.current.setStyle({
visibility: 'visible',
transformTranslateX: computed(
() => transformation.value.translation.x / root.pixelSize.value - 0.5 * width.value,
),
transformTranslateY: computed(
() => -transformation.value.translation.y / root.pixelSize.value - 0.5 * height.value,
),
transformTranslateZ: computed(() => transformation.value.translation.z / root.pixelSize.value),

transformScaleZ: computed(() => transformation.value.scale.z),
transformRotateX: computed(() => transformation.value.rotation.x),
transformRotateZ: computed(() => transformation.value.rotation.y),
transformRotateY: computed(() => transformation.value.rotation.z),
width,
height,
})
onPointerOver?.(e)
}}
onPointerLeave={(e) => {
highlightRef.current?.setStyle({ visibility: 'hidden' })

onPointerLeave?.(e)
}}
>
{children}
<Container
ref={highlightRef}
pointerEvents="none"
positionType="absolute"
positionLeft="50%"
positionTop="50%"
zIndexOffset={Infinity}
borderColor="red"
borderWidth={1}
/>
</Container>
)
},
)
8 changes: 7 additions & 1 deletion packages/uikit/src/components/container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,13 @@ export function createContainerState<EM extends ThreeEventMap = ThreeEventMap>(

const handlers = computedHandlers(style, properties, defaultProperties, hoveredList, pressedList, scrollHandlers)
return Object.assign(componentState, {
interactionPanel: createInteractionPanel(orderInfo, parentCtx.root, parentCtx.clippingRect, globalMatrix),
interactionPanel: createInteractionPanel(
orderInfo,
parentCtx.root,
parentCtx.clippingRect,
globalMatrix,
flexState,
),
handlers,
ancestorsHaveListeners: computedAncestorsHaveListeners(parentCtx, handlers),
}) satisfies ParentContext
Expand Down
19 changes: 17 additions & 2 deletions packages/uikit/src/components/content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,15 @@ export function createContentState<EM extends ThreeEventMap = ThreeEventMap>(
ancestorsHaveListeners,
transformMatrix,
root: parentCtx.root,
interactionPanel: createInteractionPanel(backgroundOrderInfo, parentCtx.root, parentCtx.clippingRect, globalMatrix),
interactionPanel: createInteractionPanel(
backgroundOrderInfo,
parentCtx.root,
parentCtx.clippingRect,
globalMatrix,
flexState,
),
remeasureContent: createMeasureContent(
flexState,
measuredSize,
measuredCenter,
mergedProperties,
Expand Down Expand Up @@ -284,6 +291,7 @@ const defaultDepthAlign: keyof typeof alignmentZMap = 'back'
* normalizes the content so it has a height of 1
*/
function createMeasureContent(
flexState: FlexNodeState,
measuredSize: Vector3,
measuredCenter: Vector3,
propertiesSignal: Signal<MergedProperties>,
Expand All @@ -306,7 +314,14 @@ function createMeasureContent(
setupRenderOrder(object, root, orderInfo)
object.material.clippingPlanes = clippingPlanes
object.material.needsUpdate = true
object.raycast = makeClippedCast(object, object.raycast, root.objectRef, parentClippingRect, orderInfo)
object.raycast = makeClippedCast(
object,
object.raycast,
root.objectRef,
parentClippingRect,
orderInfo,
flexState,
)
}
})
const parent = contentContainer.parent
Expand Down
9 changes: 8 additions & 1 deletion packages/uikit/src/components/custom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,14 @@ export function setupCustomContainer<EM extends ThreeEventMap = ThreeEventMap>(
}, abortSignal)
}

mesh.raycast = makeClippedCast(mesh, mesh.raycast, parentCtx.root.objectRef, parentCtx.clippingRect, state.orderInfo)
mesh.raycast = makeClippedCast(
mesh,
mesh.raycast,
parentCtx.root.objectRef,
parentCtx.clippingRect,
state.orderInfo,
state,
)
setupRenderOrder(mesh, parentCtx.root, state.orderInfo)

abortableEffect(() => {
Expand Down
12 changes: 10 additions & 2 deletions packages/uikit/src/components/icon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,14 @@ export function createIconState<EM extends ThreeEventMap = ThreeEventMap>(
text,
svgWidth,
svgHeight,
interactionPanel: createInteractionPanel(orderInfo, parentCtx.root, parentCtx.clippingRect, globalMatrix),
iconGroup: createIconGroup(text, parentCtx, orderInfo, clippingPlanes),
interactionPanel: createInteractionPanel(
orderInfo,
parentCtx.root,
parentCtx.clippingRect,
globalMatrix,
flexState,
),
iconGroup: createIconGroup(flexState, text, parentCtx, orderInfo, clippingPlanes),
})
}

Expand Down Expand Up @@ -186,6 +192,7 @@ export function setupIcon<EM extends ThreeEventMap = ThreeEventMap>(
const loader = new SVGLoader()

function createIconGroup(
flexState: FlexNodeState,
text: string,
parentContext: ParentContext,
orderInfo: Signal<OrderInfo | undefined>,
Expand Down Expand Up @@ -214,6 +221,7 @@ function createIconGroup(
parentContext.root.objectRef,
parentContext.clippingRect,
orderInfo,
flexState,
)
setupRenderOrder(mesh, parentContext.root, orderInfo)
mesh.userData.color = path.color
Expand Down
5 changes: 4 additions & 1 deletion packages/uikit/src/components/image.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ export function createImageState<EM extends ThreeEventMap = ThreeEventMap>(
return Object.assign(componentState, {
handlers,
ancestorsHaveListeners,
interactionPanel: createImageMesh(globalMatrix, parentCtx, orderInfo, parentCtx.root),
interactionPanel: createImageMesh(componentState, globalMatrix, parentCtx, orderInfo, parentCtx.root),
clippingRect: computedClippingRect(globalMatrix, componentState, parentCtx.root.pixelSize, parentCtx.clippingRect),
}) satisfies ParentContext
}
Expand Down Expand Up @@ -287,6 +287,7 @@ function getImageMaterialConfig() {
}

function createImageMesh(
flexState: FlexNodeState,
globalMatrix: Signal<Matrix4 | undefined>,
parentContext: ParentContext,
orderInfo: Signal<OrderInfo | undefined>,
Expand All @@ -303,13 +304,15 @@ function createImageMesh(
root.objectRef,
parentContext.clippingRect,
orderInfo,
flexState,
)
mesh.spherecast = makeClippedCast(
mesh,
makePanelSpherecast(root.objectRef, mesh.boundingSphere, globalMatrix, mesh),
root.objectRef,
parentContext.clippingRect,
orderInfo,
flexState,
)

setupRenderOrder(mesh, root, orderInfo)
Expand Down
8 changes: 7 additions & 1 deletion packages/uikit/src/components/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,13 @@ export function createInputState<EM extends ThreeEventMap = ThreeEventMap>(
multiline,
element,
instancedTextRef,
interactionPanel: createInteractionPanel(backgroundOrderInfo, parentCtx.root, parentCtx.clippingRect, globalMatrix),
interactionPanel: createInteractionPanel(
backgroundOrderInfo,
parentCtx.root,
parentCtx.clippingRect,
globalMatrix,
flexState,
),
hoveredSignal,
activeSignal,
hasFocusSignal,
Expand Down
2 changes: 1 addition & 1 deletion packages/uikit/src/components/root.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ export function createRootState<EM extends ThreeEventMap = ThreeEventMap>(
}) satisfies RootContext

const componentState = Object.assign(flexState, {
interactionPanel: createInteractionPanel(orderInfo, root, undefined, globalMatrix),
interactionPanel: createInteractionPanel(orderInfo, root, undefined, globalMatrix, flexState),
root,
scrollState: createScrollState(),
anyAncestorScrollable: signal<[boolean, boolean]>([false, false]),
Expand Down
12 changes: 10 additions & 2 deletions packages/uikit/src/components/svg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,13 @@ export function createSvgState<EM extends ThreeEventMap = ThreeEventMap>(

const componentState = Object.assign(flexState, {
centerGroup: createCenterGroup(),
interactionPanel: createInteractionPanel(orderInfo, parentCtx.root, parentCtx.clippingRect, globalMatrix),
interactionPanel: createInteractionPanel(
orderInfo,
parentCtx.root,
parentCtx.clippingRect,
globalMatrix,
flexState,
),
scrollState: createScrollState(),
hoveredSignal,
activeSignal,
Expand Down Expand Up @@ -215,6 +221,7 @@ export function setupSvg<EM extends ThreeEventMap = ThreeEventMap>(
parentCtx.clippingRect,
state.orderInfo,
state.aspectRatio,
state,
)

applyAppearancePropertiesToGroup(state.mergedProperties, state.svgObject, abortSignal)
Expand Down Expand Up @@ -337,6 +344,7 @@ async function loadSvg(
clippedRect: Signal<ClippingRect | undefined> | undefined,
orderInfo: Signal<OrderInfo | undefined>,
aspectRatio: Signal<number | undefined>,
flexState: FlexNodeState,
) {
if (url == null) {
return undefined
Expand All @@ -362,7 +370,7 @@ async function loadSvg(
box3Helper.union(geometry.boundingBox!)
const mesh = new Mesh(geometry, material)
mesh.matrixAutoUpdate = false
mesh.raycast = makeClippedCast(mesh, mesh.raycast, root.objectRef, clippedRect, orderInfo)
mesh.raycast = makeClippedCast(mesh, mesh.raycast, root.objectRef, clippedRect, orderInfo, flexState)
setupRenderOrder(mesh, root, orderInfo)
mesh.userData.color = path.color
mesh.scale.y = -1
Expand Down
8 changes: 7 additions & 1 deletion packages/uikit/src/components/text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,13 @@ export function createTextState<EM extends ThreeEventMap = ThreeEventMap>(
const updateMatrixWorld = computedInheritableProperty(mergedProperties, 'updateMatrixWorld', false)

return Object.assign(flexState, {
interactionPanel: createInteractionPanel(backgroundOrderInfo, parentCtx.root, parentCtx.clippingRect, globalMatrix),
interactionPanel: createInteractionPanel(
backgroundOrderInfo,
parentCtx.root,
parentCtx.clippingRect,
globalMatrix,
flexState,
),
hoveredSignal,
activeSignal,
mergedProperties,
Expand Down
4 changes: 4 additions & 0 deletions packages/uikit/src/panel/instanced-panel-mesh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ import {
import { OrderInfo } from '../order.js'
import { ClippingRect } from '../clipping.js'
import { RootContext } from '../context.js'
import { FlexNodeState } from '../internals.js'

export function createInteractionPanel(
orderInfo: Signal<OrderInfo | undefined>,
rootContext: RootContext,
parentClippingRect: Signal<ClippingRect | undefined> | undefined,
globalMatrix: Signal<Matrix4 | undefined>,
flexState: FlexNodeState,
) {
const boundingSphere = new Sphere()
const panel = Object.assign(new Mesh(panelGeometry), { boundingSphere })
Expand All @@ -30,13 +32,15 @@ export function createInteractionPanel(
rootContext.objectRef,
parentClippingRect,
orderInfo,
flexState,
)
panel.spherecast = makeClippedCast(
panel,
makePanelSpherecast(rootObjectRef, boundingSphere, globalMatrix, panel),
rootContext.objectRef,
parentClippingRect,
orderInfo,
flexState,
)
panel.visible = false
return panel
Expand Down
Loading

0 comments on commit 0a28da9

Please sign in to comment.