PageScene: move change_mode + toggleTransformSpace into GizmoController

GizmoController now reads/writes the store's transformSpace through a
new GizmoControllerDeps surface that Editor wires up in initialize().

- changeMode(type) reads the desired space from the store internally
  (forces local for scale), so callers don't plumb space through.
- toggleTransformSpace() owns the read/flip/write/apply loop that used
  to live on Editor.toggleTransformSpace.

Editor.change_mode and Editor.toggleTransformSpace are deleted.

Callers updated:
- actions/setTransformMode.ts → editor.gizmo.changeMode(...)
- Controls3D.tsx → editorEngine?.gizmo.toggleTransformSpace()
- keymap.ts → editor.gizmo.changeMode + .toggleTransformSpace; the
  inline setGizmoMode body shrinks to one line and the duplicate
  toggleTransformSpace helper is gone.
This commit is contained in:
Tanish Manku committed 2026-04-30 23:50:16 -07:00
1 parent 89ac302392
commit 5ec73e362d
5 files changed
+40 -41

No files matched your search

@@ -8,7 +8,7 @@ const ENGINE_MODE: Record<TransformMode, "translate" | "rotate" | "scale"> = {
};
export function setTransformMode(editor: Editor, mode: TransformMode): void {
editor.change_mode(ENGINE_MODE[mode]);
editor.gizmo.changeMode(ENGINE_MODE[mode]);
const store = usePageSceneStore.getState();
store.setTransformMode(mode);
store.setSelectedMode(mode);
@@ -318,7 +318,7 @@ export const Controls3D = () => {
>
<button
className="h-9 rounded-[10px] px-2.5 text-[10px] font-semibold font-mono bg-white/15 hover:bg-white/25 transition-colors uppercase tracking-wide"
onClick={() => editorEngine?.toggleTransformSpace()}
onClick={() => editorEngine?.gizmo.toggleTransformSpace()}
>
{transformSpace === "world" ? "World" : "Local"}
</button>
@@ -136,7 +136,11 @@ class Editor {
// PostProcessingPipeline must exist before Scene because Scene's
// load paths invoke updateSurfaceIdAttributeToMesh as a callback.
this.postProcessing = new PostProcessingPipeline();
this.gizmo = new GizmoController();
this.gizmo = new GizmoController({
getTransformSpace: () => usePageSceneStore.getState().transformSpace,
setTransformSpace: (space) =>
usePageSceneStore.getState().setTransformSpace(space),
});
this.cameraController = new CameraController({
getThreeScene: () => this.activeScene.scene,
getHotItems: () => this.activeScene.hot_items ?? null,
@@ -823,24 +827,6 @@ class Editor {
console.log("3D Editor Engine unmounted");
}
// Facade for actions/setTransformMode + keymap dispatch. Reads the
// store's current transform space and forwards to the gizmo.
change_mode(type: "translate" | "rotate" | "scale") {
const space =
type === "scale" ? "local" : usePageSceneStore.getState().transformSpace;
this.gizmo.changeMode(type, space);
}
// Facade for Controls3D button + keymap. Toggles the store's
// transform space and pushes the new value into the gizmo.
toggleTransformSpace() {
if (!this.gizmo.canToggleSpace()) return;
const store = usePageSceneStore.getState();
const next = store.transformSpace === "world" ? "local" : "world";
store.setTransformSpace(next);
this.gizmo.setSpace(next);
}
// Facades — external callers in actions/* and helper.ts target these
// names. Internals reach the bridge directly via this.selection.*.
updateOutliner() {
@@ -4,9 +4,8 @@
// on Editor), then `configure()` wires it to the camera/renderer once
// initialize() has produced them.
//
// No `editor` reference, no `import type Editor`. Store access stays on
// Editor's facades (change_mode, toggleTransformSpace); this module only
// translates them into TransformControls calls.
// Store access for the transform-space pref is injected via Deps so the
// controller doesn't import the Zustand store directly.
import type * as THREE from "three";
import { TransformControls } from "../TransformControls.js";
@@ -22,9 +21,23 @@ export type GizmoCallbacks = {
onDraggingChanged: (dragging: boolean) => void;
};
// Capabilities the gizmo needs from outside its own state.
// Editor wires these in initialize() so GizmoController never imports
// the store directly.
export type GizmoControllerDeps = {
getTransformSpace: () => TransformSpace;
setTransformSpace: (space: TransformSpace) => void;
};
export class GizmoController {
control: TransformControls | undefined;
private deps: GizmoControllerDeps;
constructor(deps: GizmoControllerDeps) {
this.deps = deps;
}
configure(
camera: THREE.PerspectiveCamera | null,
domElement: HTMLElement,
@@ -45,8 +58,11 @@ export class GizmoController {
scene.add(this.control);
}
changeMode(type: TransformMode, space: TransformSpace) {
// Apply a transform mode. Reads the desired space from the store so
// callers don't need to plumb it through. Scale is always local.
changeMode(type: TransformMode) {
if (this.control == undefined) return;
const space = type === "scale" ? "local" : this.deps.getTransformSpace();
this.control.mode = type;
this.control.space = space;
}
@@ -56,6 +72,17 @@ export class GizmoController {
this.control.space = space;
}
// Flip the store's transform-space preference and push the new value
// into the live gizmo. No-op when the gizmo is in scale mode (scale
// is always local).
toggleTransformSpace() {
if (!this.canToggleSpace()) return;
const next =
this.deps.getTransformSpace() === "world" ? "local" : "world";
this.deps.setTransformSpace(next);
this.setSpace(next);
}
// True when the gizmo exists and is in a mode where toggling between
// world/local space is meaningful (scale is always local).
canToggleSpace(): boolean {
@@ -26,25 +26,11 @@ const setGizmoMode = (
gizmoMode: "translate" | "rotate" | "scale",
storeMode: "move" | "rotate" | "scale",
) => {
editor.mouse_controls?.control?.setMode(gizmoMode);
const space = usePageSceneStore.getState().transformSpace;
if (editor.mouse_controls?.control) {
editor.mouse_controls.control.space = space;
}
editor.gizmo.changeMode(gizmoMode);
usePageSceneStore.getState().setTransformMode(storeMode);
usePageSceneStore.getState().setSelectedMode(storeMode);
};
const toggleTransformSpace = (editor: Editor) => {
if (editor.mouse_controls?.control?.mode === "scale") return;
const next =
usePageSceneStore.getState().transformSpace === "world" ? "local" : "world";
usePageSceneStore.getState().setTransformSpace(next);
if (editor.mouse_controls?.control) {
editor.mouse_controls.control.space = next;
}
};
const deleteSelected = (editor: Editor) => {
const mc = editor.mouse_controls;
if (!mc?.selected) return;
@@ -108,7 +94,7 @@ export const buildKeymap = (): KeyBinding[] => [
{ code: "KeyG", label: "Scale", group: "Transform",
run: (e) => setGizmoMode(e, "scale", "scale") },
{ code: "KeyX", label: "Toggle local/world", group: "Transform",
run: toggleTransformSpace },
run: (e) => e.gizmo.toggleTransformSpace() },
{ code: "KeyK", label: "Toggle pose (FK)", group: "Transform",
run: (e) => e.mouse_controls?.toggleFKMode() },