@pmndrs/handle
Version:
framework agnostic expandable handle implementation for threejs
103 lines (102 loc) • 3.09 kB
JavaScript
import { Object3D } from 'three';
import { defaultApply } from '../store.js';
export class HandlesContext {
target;
getOptions;
handles = [];
hoveredTagMap = new Map();
hoverSubscriptions = [];
applySubscriptions = [];
space;
constructor(target, getOptions) {
this.target = target;
this.getOptions = getOptions;
}
getSpace() {
return this.space ?? 'world';
}
getTarget() {
if (this.target instanceof Object3D) {
return this.target;
}
return this.target.current;
}
getHandleOptions(tag, getOverrideOptions) {
const providedOptions = this.getOptions?.();
const overrideOptions = getOverrideOptions?.();
return {
...providedOptions,
...overrideOptions,
apply: (state, target) => {
this.onApply(tag, state, target);
return (overrideOptions?.apply ?? providedOptions?.apply ?? defaultApply)?.(state, target);
},
};
}
registerHandle(store, object, tag) {
const entry = {
object,
store,
tag,
};
this.handles.push(entry);
const unbind = store.bind(object);
const enterListener = this.onPointerEnter.bind(this, tag);
const leaveListener = this.onPointerLeave.bind(this);
object.addEventListener('pointerenter', enterListener);
object.addEventListener('pointerleave', leaveListener);
return () => {
const index = this.handles.indexOf(entry);
if (index != -1) {
this.handles.splice(index, 1);
}
unbind();
store.cancel();
};
}
subscribeHover(fn) {
this.hoverSubscriptions.push(fn);
fn(Array.from(this.hoveredTagMap.values()));
return () => {
const index = this.hoverSubscriptions.indexOf(fn);
if (index === -1) {
return;
}
this.hoverSubscriptions.splice(index, 1);
};
}
subscribeApply(fn) {
this.applySubscriptions.push(fn);
return () => {
const index = this.applySubscriptions.indexOf(fn);
if (index === -1) {
return;
}
this.applySubscriptions.splice(index, 1);
};
}
update(time) {
for (const { store } of this.handles) {
store.update(time);
}
}
onPointerEnter(tag, e) {
this.hoveredTagMap.set(e.pointerId, tag);
this.updateHover();
}
onPointerLeave(e) {
this.hoveredTagMap.delete(e.pointerId);
this.updateHover();
}
updateHover() {
const tags = Array.from(this.hoveredTagMap.values());
for (const hoverSubscription of this.hoverSubscriptions) {
hoverSubscription(tags);
}
}
onApply(tag, state, target) {
for (const applySubscription of this.applySubscriptions) {
applySubscription(tag, state, target);
}
}
}