Skip to content

Commit a55e689

Browse files
authored
Merge pull request #110 from Kitware/picking-uses-react-events
Picking uses react synthetic events
2 parents f8a7e62 + 0bc5c85 commit a55e689

File tree

4 files changed

+124
-25
lines changed

4 files changed

+124
-25
lines changed

src/core/Picking.ts

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@ import vtkOpenGLHardwareSelector from '@kitware/vtk.js/Rendering/OpenGL/Hardware
33
import { Vector2, Vector3 } from '@kitware/vtk.js/types';
44
import {
55
forwardRef,
6+
MouseEvent,
7+
PointerEvent,
68
useCallback,
79
useContext,
810
useImperativeHandle,
911
useMemo,
1012
} from 'react';
1113
import deletionRegistry from '../utils/DeletionRegistry';
1214
import useDebounce from '../utils/useDebounce';
13-
import { useEventListener } from '../utils/useEventListener';
1415
import useGetterRef from '../utils/useGetterRef';
1516
import useMount from '../utils/useMount';
1617
import useUnmount from '../utils/useUnmount';
@@ -19,6 +20,7 @@ import {
1920
RendererContext,
2021
ViewContext,
2122
} from './contexts';
23+
import { useViewEventListener } from './modules/useViewEvents';
2224

2325
export interface PickResult {
2426
representationId?: string;
@@ -118,7 +120,7 @@ export default forwardRef(function ViewPicking(props: PickingProps, fwdRef) {
118120
const viewAPI = useContext(ViewContext);
119121

120122
if (!openGLRenderWindowAPI || !rendererAPI || !viewAPI) {
121-
throw new Error('<ViewPicking> must have a <View> ancestor');
123+
throw new Error('<Picking> must have a <View> ancestor');
122124
}
123125

124126
const getSelector = useOpenGLHardwareSelector();
@@ -375,8 +377,7 @@ export default forwardRef(function ViewPicking(props: PickingProps, fwdRef) {
375377
const { onHoverDebounceWait = DefaultProps.onHoverDebounceWait } = props;
376378

377379
// TODO last selection? (see View.js)
378-
useEventListener(
379-
viewAPI.getViewContainer,
380+
useViewEventListener(
380381
'pointermove',
381382
useDebounce(
382383
useCallback(
@@ -390,8 +391,7 @@ export default forwardRef(function ViewPicking(props: PickingProps, fwdRef) {
390391
)
391392
);
392393

393-
useEventListener(
394-
viewAPI.getViewContainer,
394+
useViewEventListener(
395395
'pointerdown',
396396
useCallback(
397397
(ev: PointerEvent) => {
@@ -402,8 +402,7 @@ export default forwardRef(function ViewPicking(props: PickingProps, fwdRef) {
402402
)
403403
);
404404

405-
useEventListener(
406-
viewAPI.getViewContainer,
405+
useViewEventListener(
407406
'pointerup',
408407
useCallback(
409408
(ev: PointerEvent) => {
@@ -414,8 +413,7 @@ export default forwardRef(function ViewPicking(props: PickingProps, fwdRef) {
414413
)
415414
);
416415

417-
useEventListener(
418-
viewAPI.getViewContainer,
416+
useViewEventListener(
419417
'click',
420418
useCallback(
421419
(ev: MouseEvent) => {

src/core/internal/ParentedView.tsx

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
useInteractorStyle,
2626
useInteractorStyleManipulatorSettings,
2727
} from '../modules/useInteractorStyle';
28+
import useViewEvents, { ViewEvents } from '../modules/useViewEvents';
2829
import Renderer from '../Renderer';
2930
import { DefaultProps, ViewProps } from './view-shared';
3031

@@ -154,6 +155,10 @@ const ParentedView = forwardRef(function ParentedView(
154155
}
155156
}, [onResize, openGLRenderWindowAPI, resizeWatcher]);
156157

158+
// --- events --- //
159+
160+
const { rootListeners, registerEventListener } = useViewEvents();
161+
157162
// --- api --- //
158163

159164
const api = useMemo<IView>(
@@ -195,11 +200,13 @@ const ParentedView = forwardRef(function ParentedView(
195200

196201
return (
197202
<ViewContext.Provider value={api}>
198-
<div style={style} ref={containerRef}>
199-
<Renderer {...rendererProps} ref={rendererRef}>
200-
{props.children}
201-
</Renderer>
202-
</div>
203+
<ViewEvents.Provider value={registerEventListener}>
204+
<div style={style} ref={containerRef} {...rootListeners}>
205+
<Renderer {...rendererProps} ref={rendererRef}>
206+
{props.children}
207+
</Renderer>
208+
</div>
209+
</ViewEvents.Provider>
203210
</ViewContext.Provider>
204211
);
205212
});

src/core/internal/SingleView.tsx

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
useInteractorStyle,
2121
useInteractorStyleManipulatorSettings,
2222
} from '../modules/useInteractorStyle';
23+
import useViewEvents, { ViewEvents } from '../modules/useViewEvents';
2324
import OpenGLRenderWindow from '../OpenGLRenderWindow';
2425
import Renderer from '../Renderer';
2526
import RenderWindow from '../RenderWindow';
@@ -77,6 +78,10 @@ const SingleView = forwardRef(function SingleView(props: ViewProps, fwdRef) {
7778
autoCenterOfRotation
7879
);
7980

81+
// --- events --- //
82+
83+
const { rootListeners, registerEventListener } = useViewEvents();
84+
8085
// --- api --- //
8186

8287
const api = useMemo<IView>(
@@ -101,16 +106,19 @@ const SingleView = forwardRef(function SingleView(props: ViewProps, fwdRef) {
101106

102107
return (
103108
<ViewContext.Provider value={api}>
104-
<OpenGLRenderWindow
105-
{...openGLRenderWindowProps}
106-
ref={openGLRenderWindowRef}
107-
>
108-
<RenderWindow ref={renderWindowRef}>
109-
<Renderer {...rendererProps} ref={rendererRef}>
110-
{props.children}
111-
</Renderer>
112-
</RenderWindow>
113-
</OpenGLRenderWindow>
109+
<ViewEvents.Provider value={registerEventListener}>
110+
<OpenGLRenderWindow
111+
{...openGLRenderWindowProps}
112+
{...rootListeners}
113+
ref={openGLRenderWindowRef}
114+
>
115+
<RenderWindow ref={renderWindowRef}>
116+
<Renderer {...rendererProps} ref={rendererRef}>
117+
{props.children}
118+
</Renderer>
119+
</RenderWindow>
120+
</OpenGLRenderWindow>
121+
</ViewEvents.Provider>
114122
</ViewContext.Provider>
115123
);
116124
});

src/core/modules/useViewEvents.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import {
2+
createContext,
3+
EventHandler,
4+
SyntheticEvent,
5+
useContext,
6+
useEffect,
7+
useRef,
8+
} from 'react';
9+
10+
type HandledEvents = 'pointermove' | 'pointerdown' | 'pointerup' | 'click';
11+
12+
type Handler = EventHandler<SyntheticEvent<unknown>>;
13+
14+
function createEvent() {
15+
const handlers: Handler[] = [];
16+
const add = (callback: Handler) => {
17+
handlers.push(callback);
18+
};
19+
const remove = (callback: Handler) => {
20+
const idx = handlers.indexOf(callback);
21+
if (idx < 0) return;
22+
handlers.splice(idx, 1);
23+
};
24+
const trigger = (ev: SyntheticEvent<unknown>) => {
25+
handlers.forEach((h) => h(ev));
26+
};
27+
return { add, remove, trigger };
28+
}
29+
30+
export default function useViewEvents() {
31+
const eventMap = useRef<
32+
Record<HandledEvents, ReturnType<typeof createEvent>>
33+
>({
34+
pointermove: createEvent(),
35+
pointerdown: createEvent(),
36+
pointerup: createEvent(),
37+
click: createEvent(),
38+
});
39+
40+
const rootListeners = useRef({
41+
onPointerDown: eventMap.current.pointerdown.trigger,
42+
onPointerUp: eventMap.current.pointerup.trigger,
43+
onPointerMove: eventMap.current.pointermove.trigger,
44+
onClick: eventMap.current.click.trigger,
45+
});
46+
47+
const registerEventListener = (
48+
eventName: HandledEvents,
49+
callback: Handler
50+
) => {
51+
const bus = eventMap.current[eventName];
52+
if (!bus) {
53+
throw new Error(`${eventName} is not supported in useViewEvents`);
54+
}
55+
56+
bus.add(callback);
57+
return () => bus.remove(callback);
58+
};
59+
60+
return {
61+
rootListeners: rootListeners.current,
62+
registerEventListener,
63+
};
64+
}
65+
66+
export type ViewEventRegistrar = ReturnType<
67+
typeof useViewEvents
68+
>['registerEventListener'];
69+
70+
export const ViewEvents = createContext<ViewEventRegistrar | null>(null);
71+
72+
export function useViewEventListener(
73+
eventName: HandledEvents,
74+
callback: Handler
75+
) {
76+
const registerEventListener = useContext<ViewEventRegistrar | null>(
77+
ViewEvents
78+
);
79+
if (!registerEventListener) {
80+
throw new Error('useViewEventListener needs ViewEventRegistrar!');
81+
}
82+
83+
useEffect(() => {
84+
return registerEventListener(eventName, callback);
85+
}, [eventName, callback, registerEventListener]);
86+
}

0 commit comments

Comments
 (0)