controlsPane.ts
examples/fxedit/controlsPane.ts · raw · open the example · all examples · site home
Exports: addControlsFolder
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
// Tweakpane folder for Radix's CameraControls: every setting, saved as it
// changes (personal settings for all pages, the rest for the page's
// storageKey), plus buttons to reset them.
import type { FolderApi } from "tweakpane";
import type { CameraControls, CameraControlsSettings } from "@sparkjsdev/radix";
export function addControlsFolder(parent: Pick<FolderApi, "addFolder">, controls: CameraControls) {
const folder = parent.addFolder({ title: "Controls", expanded: false });
// Bindings edit a copy; set() applies and saves each change (not the
// changes refresh() makes while following the controls)
const state = structuredClone(controls.settings);
let syncing = false;
const apply = (settings: Partial<CameraControlsSettings>) => {
if (!syncing) {
controls.set(settings);
}
};
const bind = <K extends keyof CameraControlsSettings>(key: K, options: Record<string, unknown> = {}) =>
folder.addBinding(state, key, options).on("change", () => apply({ [key]: structuredClone(state[key]) }));
bind("mode", { options: { fly: "fly", orbit: "orbit" } });
bind("pointerLock", { label: "lock pointer (fly)" });
bind("moveSpeed", { label: "move speed", min: 0.05, max: 20 });
bind("capsMultiplier", { label: "caps lock x", min: 1, max: 100 });
bind("lookSpeed", { label: "turn speed", min: 0.1, max: 6 });
bind("pointerLookSpeed", { label: "drag look", min: 0.0005, max: 0.01, step: 0.0005 });
bind("pointerPanSpeed", { label: "drag pan", min: 0.0002, max: 0.01, step: 0.0002 });
bind("zoomSpeed", { label: "zoom", min: 0.0002, max: 0.01, step: 0.0001 });
bind("doublePress", { label: "double press ms", min: 0, max: 600, step: 10 });
const invert = folder.addFolder({ title: "invert", expanded: false });
for (const [key, label] of [["invertLookX", "look x"], ["invertLookY", "look y"], ["invertPan", "pan"], ["invertZoom", "zoom"], ["invertStickY", "stick y"]] as const) {
invert.addBinding(state, key, { label }).on("change", () => apply({ [key]: state[key] }));
}
const inertia = folder.addFolder({ title: "inertia (s)", expanded: false });
for (const [key, label] of [["pointerLook", "drag look"], ["pointerMove", "drag pan"], ["look", "key turn"], ["move", "key move"]] as const) {
inertia.addBinding(state.inertia, key, { label, min: 0, max: 1 }).on("change", () => apply({ inertia: { ...state.inertia } }));
}
folder.addButton({ title: "reset this page" }).on("click", () => controls.reset());
folder.addButton({ title: "reset all (personal too)" }).on("click", () => controls.reset({ personal: true }));
// Follow changes made elsewhere (the mode key, the quick bar, reset, code)
controls.onChange = (settings) => {
const { inertia: inertiaValues, ...rest } = structuredClone(settings);
Object.assign(state, rest);
Object.assign(state.inertia, inertiaValues);
syncing = true;
folder.refresh();
syncing = false;
};
}