From dfe2cdf666df986748732adc4d4e5428161be25d Mon Sep 17 00:00:00 2001 From: jenstandstad Date: Sun, 23 Aug 2026 22:11:29 +0200 Subject: [PATCH] Improved animation on the inventory tools --- src/inventory.tsx | 51 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/src/inventory.tsx b/src/inventory.tsx index 2a2ee45..124517a 100644 --- a/src/inventory.tsx +++ b/src/inventory.tsx @@ -53,7 +53,9 @@ function ToolRack({ index }: { index: number }) { const scene = new THREE.Scene() const camera = new THREE.PerspectiveCamera(34, 1, 0.1, 100) - camera.position.set(0.5, 0.45, 3.3) + // Pulled back along the same view direction (was z 3.3) so the tools sit smaller in + // frame — full-screen models were hard to read as objects. + camera.position.set(0.63, 0.57, 8.15) camera.lookAt(0, 0, 0) scene.add(new THREE.AmbientLight(0x40483a, 0.9)) const key = new THREE.DirectionalLight(0xfff4e0, 1.1); key.position.set(2, 3, 3); scene.add(key) @@ -63,11 +65,41 @@ function ToolRack({ index }: { index: number }) { const resize = () => { const w = stage.clientWidth, h = stage.clientHeight; if (w && h) { camera.aspect = w / h; camera.updateProjectionMatrix(); renderer.setSize(w, h, false) } } resize(); const ro = new ResizeObserver(resize); ro.observe(stage) + // Drag to spin: a swipe rotates the holder 1:1 with the pointer and hands its speed + // to `spin`, which the idle loop keeps applying and eases back to the resting rate — + // so interfering with a flick sends it spinning faster, then it settles. + const BASE_SPIN = 0.008 + let spin = BASE_SPIN + let dragging = false + let lastX = 0 + const dom = renderer.domElement + dom.style.touchAction = 'none' + dom.style.cursor = 'grab' + const onDown = (event: PointerEvent) => { dragging = true; lastX = event.clientX; dom.style.cursor = 'grabbing'; dom.setPointerCapture(event.pointerId) } + const onMove = (event: PointerEvent) => { + if (!dragging) return + const delta = (event.clientX - lastX) * 0.01 + lastX = event.clientX + holder.rotation.y += delta + spin = THREE.MathUtils.clamp(delta, -0.35, 0.35) // carry the swipe's speed as momentum + } + const onUp = (event: PointerEvent) => { dragging = false; dom.style.cursor = 'grab'; try { dom.releasePointerCapture(event.pointerId) } catch { /* pointer already released */ } } + dom.addEventListener('pointerdown', onDown) + dom.addEventListener('pointermove', onMove) + dom.addEventListener('pointerup', onUp) + dom.addEventListener('pointercancel', onUp) + let raf = 0 - const tick = () => { holder.rotation.y += 0.008; renderer.render(scene, camera); raf = requestAnimationFrame(tick) } + const tick = () => { + if (!dragging) { holder.rotation.y += spin; spin += (BASE_SPIN - spin) * 0.04 } // momentum eases back to idle + renderer.render(scene, camera); raf = requestAnimationFrame(tick) + } tick() return () => { - cancelAnimationFrame(raf); ro.disconnect(); renderer.dispose(); renderer.domElement.remove() + cancelAnimationFrame(raf); ro.disconnect() + dom.removeEventListener('pointerdown', onDown); dom.removeEventListener('pointermove', onMove) + dom.removeEventListener('pointerup', onUp); dom.removeEventListener('pointercancel', onUp) + renderer.dispose(); renderer.domElement.remove() scene.traverse(o => { if (o instanceof THREE.Mesh) { o.geometry.dispose(); (o.material as THREE.Material).dispose() } }) holderRef.current = null } @@ -89,6 +121,19 @@ export function Inventory({ onClose, session, onTearToBoard }: { onClose?: () => const [index, setIndex] = useState(0) const [active, setActive] = useState(null) + // Keyboard rack controls: ← / → cycle tools, Space uses the selected one. Inert once a + // tool is open, so its own inputs (e.g. the notebook textarea) keep the keys. + useEffect(() => { + if (active) return + const onKey = (event: KeyboardEvent) => { + if (event.key === 'ArrowLeft') { event.preventDefault(); setIndex(i => (i - 1 + TOOLS.length) % TOOLS.length) } + else if (event.key === 'ArrowRight') { event.preventDefault(); setIndex(i => (i + 1) % TOOLS.length) } + else if (event.key === ' ') { event.preventDefault(); setActive(TOOLS[index].key) } + } + window.addEventListener('keydown', onKey) + return () => window.removeEventListener('keydown', onKey) + }, [index, active]) + if (active) return
{onClose && }