Skip to content

Commit 0a28da9

Browse files
committed
highligher added to dashboard
1 parent 98aa455 commit 0a28da9

15 files changed

+195
-23
lines changed

examples/dashboard/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"type": "module",
33
"dependencies": {
4+
"@preact/signals-core": "^1.5.1",
45
"@react-three/uikit": "workspace:^",
56
"@react-three/uikit-lucide": "workspace:^",
67
"vite-plugin-mkcert": "^1.17.4",

examples/dashboard/src/App.tsx

+10-7
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { TeamSwitcher } from './components/TeamSwitcher.js'
1616
import { UserNav } from './components/UserNav.js'
1717
import { create } from 'zustand'
1818
import { noEvents, PointerEvents } from '@react-three/xr'
19+
import { Highlighter } from './components/Highlighter.js'
1920

2021
setPreferredColorScheme('light')
2122

@@ -36,13 +37,15 @@ export default function App() {
3637
<CountFrames />
3738
<PointerEvents />
3839
<Fullscreen distanceToCamera={1} backgroundColor={0xffffff} dark={{ backgroundColor: 0x0 }}>
39-
<Defaults>
40-
<DialogAnchor>
41-
<Container flexDirection="column" width="100%" height="100%" overflow="scroll">
42-
<DashboardPage open={open} setOpen={setOpen} />
43-
</Container>
44-
</DialogAnchor>
45-
</Defaults>
40+
<Highlighter>
41+
<Defaults>
42+
<DialogAnchor>
43+
<Container flexDirection="column" width="100%" height="100%" overflow="scroll">
44+
<DashboardPage open={open} setOpen={setOpen} />
45+
</Container>
46+
</DialogAnchor>
47+
</Defaults>
48+
</Highlighter>
4649
</Fullscreen>
4750
</Canvas>
4851
</>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { Container, ContainerProperties, ContainerRef, isInteractionPanel } from '@react-three/uikit'
2+
import { forwardRef, useRef } from 'react'
3+
import { computed } from '@preact/signals-core'
4+
import { Euler, Matrix4, Quaternion, Vector3 } from 'three'
5+
6+
const matrixHelper = new Matrix4()
7+
const quaternionHelper = new Quaternion()
8+
9+
export const Highlighter = forwardRef<ContainerRef, ContainerProperties>(
10+
({ onPointerOver, onPointerLeave, children, ...props }, ref) => {
11+
const highlightRef = useRef<ContainerRef | null>(null)
12+
return (
13+
<Container
14+
ref={ref}
15+
{...props}
16+
onPointerOver={(e) => {
17+
if (!isInteractionPanel(e.object) || highlightRef.current == null) {
18+
return
19+
}
20+
const {
21+
internals: { globalMatrix, root, size },
22+
} = e.object
23+
const transformation = computed(() => {
24+
const { value } = globalMatrix
25+
if (value == null) {
26+
return { translation: new Vector3(), scale: new Vector3(), rotation: new Euler() }
27+
}
28+
matrixHelper.copy(value)
29+
const translation = new Vector3()
30+
const scale = new Vector3()
31+
matrixHelper.decompose(translation, quaternionHelper, scale)
32+
const rotation = new Euler().setFromQuaternion(quaternionHelper)
33+
return { translation, scale, rotation }
34+
})
35+
const width = computed(() => transformation.value.scale.x * (size.value?.[0] ?? 0))
36+
const height = computed(() => transformation.value.scale.y * (size.value?.[1] ?? 0) * 1)
37+
highlightRef.current.setStyle({
38+
visibility: 'visible',
39+
transformTranslateX: computed(
40+
() => transformation.value.translation.x / root.pixelSize.value - 0.5 * width.value,
41+
),
42+
transformTranslateY: computed(
43+
() => -transformation.value.translation.y / root.pixelSize.value - 0.5 * height.value,
44+
),
45+
transformTranslateZ: computed(() => transformation.value.translation.z / root.pixelSize.value),
46+
47+
transformScaleZ: computed(() => transformation.value.scale.z),
48+
transformRotateX: computed(() => transformation.value.rotation.x),
49+
transformRotateZ: computed(() => transformation.value.rotation.y),
50+
transformRotateY: computed(() => transformation.value.rotation.z),
51+
width,
52+
height,
53+
})
54+
onPointerOver?.(e)
55+
}}
56+
onPointerLeave={(e) => {
57+
highlightRef.current?.setStyle({ visibility: 'hidden' })
58+
59+
onPointerLeave?.(e)
60+
}}
61+
>
62+
{children}
63+
<Container
64+
ref={highlightRef}
65+
pointerEvents="none"
66+
positionType="absolute"
67+
positionLeft="50%"
68+
positionTop="50%"
69+
zIndexOffset={Infinity}
70+
borderColor="red"
71+
borderWidth={1}
72+
/>
73+
</Container>
74+
)
75+
},
76+
)

packages/uikit/src/components/container.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,13 @@ export function createContainerState<EM extends ThreeEventMap = ThreeEventMap>(
133133

134134
const handlers = computedHandlers(style, properties, defaultProperties, hoveredList, pressedList, scrollHandlers)
135135
return Object.assign(componentState, {
136-
interactionPanel: createInteractionPanel(orderInfo, parentCtx.root, parentCtx.clippingRect, globalMatrix),
136+
interactionPanel: createInteractionPanel(
137+
orderInfo,
138+
parentCtx.root,
139+
parentCtx.clippingRect,
140+
globalMatrix,
141+
flexState,
142+
),
137143
handlers,
138144
ancestorsHaveListeners: computedAncestorsHaveListeners(parentCtx, handlers),
139145
}) satisfies ParentContext

packages/uikit/src/components/content.ts

+17-2
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,15 @@ export function createContentState<EM extends ThreeEventMap = ThreeEventMap>(
137137
ancestorsHaveListeners,
138138
transformMatrix,
139139
root: parentCtx.root,
140-
interactionPanel: createInteractionPanel(backgroundOrderInfo, parentCtx.root, parentCtx.clippingRect, globalMatrix),
140+
interactionPanel: createInteractionPanel(
141+
backgroundOrderInfo,
142+
parentCtx.root,
143+
parentCtx.clippingRect,
144+
globalMatrix,
145+
flexState,
146+
),
141147
remeasureContent: createMeasureContent(
148+
flexState,
142149
measuredSize,
143150
measuredCenter,
144151
mergedProperties,
@@ -284,6 +291,7 @@ const defaultDepthAlign: keyof typeof alignmentZMap = 'back'
284291
* normalizes the content so it has a height of 1
285292
*/
286293
function createMeasureContent(
294+
flexState: FlexNodeState,
287295
measuredSize: Vector3,
288296
measuredCenter: Vector3,
289297
propertiesSignal: Signal<MergedProperties>,
@@ -306,7 +314,14 @@ function createMeasureContent(
306314
setupRenderOrder(object, root, orderInfo)
307315
object.material.clippingPlanes = clippingPlanes
308316
object.material.needsUpdate = true
309-
object.raycast = makeClippedCast(object, object.raycast, root.objectRef, parentClippingRect, orderInfo)
317+
object.raycast = makeClippedCast(
318+
object,
319+
object.raycast,
320+
root.objectRef,
321+
parentClippingRect,
322+
orderInfo,
323+
flexState,
324+
)
310325
}
311326
})
312327
const parent = contentContainer.parent

packages/uikit/src/components/custom.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,14 @@ export function setupCustomContainer<EM extends ThreeEventMap = ThreeEventMap>(
138138
}, abortSignal)
139139
}
140140

141-
mesh.raycast = makeClippedCast(mesh, mesh.raycast, parentCtx.root.objectRef, parentCtx.clippingRect, state.orderInfo)
141+
mesh.raycast = makeClippedCast(
142+
mesh,
143+
mesh.raycast,
144+
parentCtx.root.objectRef,
145+
parentCtx.clippingRect,
146+
state.orderInfo,
147+
state,
148+
)
142149
setupRenderOrder(mesh, parentCtx.root, state.orderInfo)
143150

144151
abortableEffect(() => {

packages/uikit/src/components/icon.ts

+10-2
Original file line numberDiff line numberDiff line change
@@ -129,8 +129,14 @@ export function createIconState<EM extends ThreeEventMap = ThreeEventMap>(
129129
text,
130130
svgWidth,
131131
svgHeight,
132-
interactionPanel: createInteractionPanel(orderInfo, parentCtx.root, parentCtx.clippingRect, globalMatrix),
133-
iconGroup: createIconGroup(text, parentCtx, orderInfo, clippingPlanes),
132+
interactionPanel: createInteractionPanel(
133+
orderInfo,
134+
parentCtx.root,
135+
parentCtx.clippingRect,
136+
globalMatrix,
137+
flexState,
138+
),
139+
iconGroup: createIconGroup(flexState, text, parentCtx, orderInfo, clippingPlanes),
134140
})
135141
}
136142

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

188194
function createIconGroup(
195+
flexState: FlexNodeState,
189196
text: string,
190197
parentContext: ParentContext,
191198
orderInfo: Signal<OrderInfo | undefined>,
@@ -214,6 +221,7 @@ function createIconGroup(
214221
parentContext.root.objectRef,
215222
parentContext.clippingRect,
216223
orderInfo,
224+
flexState,
217225
)
218226
setupRenderOrder(mesh, parentContext.root, orderInfo)
219227
mesh.userData.color = path.color

packages/uikit/src/components/image.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ export function createImageState<EM extends ThreeEventMap = ThreeEventMap>(
200200
return Object.assign(componentState, {
201201
handlers,
202202
ancestorsHaveListeners,
203-
interactionPanel: createImageMesh(globalMatrix, parentCtx, orderInfo, parentCtx.root),
203+
interactionPanel: createImageMesh(componentState, globalMatrix, parentCtx, orderInfo, parentCtx.root),
204204
clippingRect: computedClippingRect(globalMatrix, componentState, parentCtx.root.pixelSize, parentCtx.clippingRect),
205205
}) satisfies ParentContext
206206
}
@@ -287,6 +287,7 @@ function getImageMaterialConfig() {
287287
}
288288

289289
function createImageMesh(
290+
flexState: FlexNodeState,
290291
globalMatrix: Signal<Matrix4 | undefined>,
291292
parentContext: ParentContext,
292293
orderInfo: Signal<OrderInfo | undefined>,
@@ -303,13 +304,15 @@ function createImageMesh(
303304
root.objectRef,
304305
parentContext.clippingRect,
305306
orderInfo,
307+
flexState,
306308
)
307309
mesh.spherecast = makeClippedCast(
308310
mesh,
309311
makePanelSpherecast(root.objectRef, mesh.boundingSphere, globalMatrix, mesh),
310312
root.objectRef,
311313
parentContext.clippingRect,
312314
orderInfo,
315+
flexState,
313316
)
314317

315318
setupRenderOrder(mesh, root, orderInfo)

packages/uikit/src/components/input.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,13 @@ export function createInputState<EM extends ThreeEventMap = ThreeEventMap>(
221221
multiline,
222222
element,
223223
instancedTextRef,
224-
interactionPanel: createInteractionPanel(backgroundOrderInfo, parentCtx.root, parentCtx.clippingRect, globalMatrix),
224+
interactionPanel: createInteractionPanel(
225+
backgroundOrderInfo,
226+
parentCtx.root,
227+
parentCtx.clippingRect,
228+
globalMatrix,
229+
flexState,
230+
),
225231
hoveredSignal,
226232
activeSignal,
227233
hasFocusSignal,

packages/uikit/src/components/root.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ export function createRootState<EM extends ThreeEventMap = ThreeEventMap>(
148148
}) satisfies RootContext
149149

150150
const componentState = Object.assign(flexState, {
151-
interactionPanel: createInteractionPanel(orderInfo, root, undefined, globalMatrix),
151+
interactionPanel: createInteractionPanel(orderInfo, root, undefined, globalMatrix, flexState),
152152
root,
153153
scrollState: createScrollState(),
154154
anyAncestorScrollable: signal<[boolean, boolean]>([false, false]),

packages/uikit/src/components/svg.ts

+10-2
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,13 @@ export function createSvgState<EM extends ThreeEventMap = ThreeEventMap>(
137137

138138
const componentState = Object.assign(flexState, {
139139
centerGroup: createCenterGroup(),
140-
interactionPanel: createInteractionPanel(orderInfo, parentCtx.root, parentCtx.clippingRect, globalMatrix),
140+
interactionPanel: createInteractionPanel(
141+
orderInfo,
142+
parentCtx.root,
143+
parentCtx.clippingRect,
144+
globalMatrix,
145+
flexState,
146+
),
141147
scrollState: createScrollState(),
142148
hoveredSignal,
143149
activeSignal,
@@ -215,6 +221,7 @@ export function setupSvg<EM extends ThreeEventMap = ThreeEventMap>(
215221
parentCtx.clippingRect,
216222
state.orderInfo,
217223
state.aspectRatio,
224+
state,
218225
)
219226

220227
applyAppearancePropertiesToGroup(state.mergedProperties, state.svgObject, abortSignal)
@@ -337,6 +344,7 @@ async function loadSvg(
337344
clippedRect: Signal<ClippingRect | undefined> | undefined,
338345
orderInfo: Signal<OrderInfo | undefined>,
339346
aspectRatio: Signal<number | undefined>,
347+
flexState: FlexNodeState,
340348
) {
341349
if (url == null) {
342350
return undefined
@@ -362,7 +370,7 @@ async function loadSvg(
362370
box3Helper.union(geometry.boundingBox!)
363371
const mesh = new Mesh(geometry, material)
364372
mesh.matrixAutoUpdate = false
365-
mesh.raycast = makeClippedCast(mesh, mesh.raycast, root.objectRef, clippedRect, orderInfo)
373+
mesh.raycast = makeClippedCast(mesh, mesh.raycast, root.objectRef, clippedRect, orderInfo, flexState)
366374
setupRenderOrder(mesh, root, orderInfo)
367375
mesh.userData.color = path.color
368376
mesh.scale.y = -1

packages/uikit/src/components/text.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,13 @@ export function createTextState<EM extends ThreeEventMap = ThreeEventMap>(
119119
const updateMatrixWorld = computedInheritableProperty(mergedProperties, 'updateMatrixWorld', false)
120120

121121
return Object.assign(flexState, {
122-
interactionPanel: createInteractionPanel(backgroundOrderInfo, parentCtx.root, parentCtx.clippingRect, globalMatrix),
122+
interactionPanel: createInteractionPanel(
123+
backgroundOrderInfo,
124+
parentCtx.root,
125+
parentCtx.clippingRect,
126+
globalMatrix,
127+
flexState,
128+
),
123129
hoveredSignal,
124130
activeSignal,
125131
mergedProperties,

packages/uikit/src/panel/instanced-panel-mesh.ts

+4
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,14 @@ import {
1212
import { OrderInfo } from '../order.js'
1313
import { ClippingRect } from '../clipping.js'
1414
import { RootContext } from '../context.js'
15+
import { FlexNodeState } from '../internals.js'
1516

1617
export function createInteractionPanel(
1718
orderInfo: Signal<OrderInfo | undefined>,
1819
rootContext: RootContext,
1920
parentClippingRect: Signal<ClippingRect | undefined> | undefined,
2021
globalMatrix: Signal<Matrix4 | undefined>,
22+
flexState: FlexNodeState,
2123
) {
2224
const boundingSphere = new Sphere()
2325
const panel = Object.assign(new Mesh(panelGeometry), { boundingSphere })
@@ -30,13 +32,15 @@ export function createInteractionPanel(
3032
rootContext.objectRef,
3133
parentClippingRect,
3234
orderInfo,
35+
flexState,
3336
)
3437
panel.spherecast = makeClippedCast(
3538
panel,
3639
makePanelSpherecast(rootObjectRef, boundingSphere, globalMatrix, panel),
3740
rootContext.objectRef,
3841
parentClippingRect,
3942
orderInfo,
43+
flexState,
4044
)
4145
panel.visible = false
4246
return panel

0 commit comments

Comments
 (0)