Phase 5 Stage D wall: port CurveWallTool to DragAction (first wall D)
Direct copy of the fence curve recipe — pure `curveWallDragAction` (chord-perpendicular projection + clamp + normalize + single-undo dance) plus a thin wrapper feeding `useDragAction`. Mounted via `def.affordanceTools.curve`. Slight precision difference vs legacy: the legacy CurveWallTool snapped the pointer position to `getWallGridStep()` before projecting onto the chord normal; the ported action skips that pre-snap and relies on `normalizeWallCurveOffset` to settle the final value. The user-visible result is the same magnitude of step, just with the snap applied at the offset level instead of the position level. Remaining wall D affordances (endpoint move, whole-wall move, placement) are larger and queued for future sessions — wall's move tool alone is 804 LoC with the linked-wall corner-cascade logic. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
de3efa1b53
commit
7f2f9c685b
@@ -0,0 +1,62 @@
|
||||
'use client'
|
||||
|
||||
import { getWallMidpointHandlePoint, useScene, type WallNode } from '@pascal-app/core'
|
||||
import { CursorSphere, triggerSFX, useDragAction, useEditor } from '@pascal-app/editor'
|
||||
import { useViewer } from '@pascal-app/viewer'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { curveWallDragAction } from './actions/curve'
|
||||
|
||||
/**
|
||||
* Phase 5 Stage D — thin React wrapper around `curveWallDragAction`.
|
||||
*
|
||||
* Replaces the legacy `CurveWallTool` (178 LoC). Same UX as the fence
|
||||
* curve port — cursor sphere follows the chord-perpendicular projection
|
||||
* of the pointer, dragging the wall's `curveOffset` live; grid:click
|
||||
* commits with the single-undo dance, Esc cancels.
|
||||
*
|
||||
* Mounted by ToolManager via `def.affordanceTools.curve` when
|
||||
* `useEditor.curvingWall` activates.
|
||||
*/
|
||||
export const WallCurveTool: React.FC<{ node: WallNode }> = ({ node }) => {
|
||||
const initialHandle = getWallMidpointHandlePoint(node)
|
||||
const [cursorPos, setCursorPos] = useState<[number, number, number]>([
|
||||
initialHandle.x,
|
||||
0,
|
||||
initialHandle.y,
|
||||
])
|
||||
|
||||
const exitCurveMode = (committed: boolean) => {
|
||||
if (committed) triggerSFX('sfx:item-place')
|
||||
useViewer.getState().setSelection({ selectedIds: [node.id] })
|
||||
useEditor.getState().setCurvingWall(null)
|
||||
}
|
||||
|
||||
useDragAction({
|
||||
active: true,
|
||||
action: curveWallDragAction,
|
||||
initial: {
|
||||
node,
|
||||
point: [initialHandle.x, initialHandle.y],
|
||||
},
|
||||
onCommit: () => exitCurveMode(true),
|
||||
onCancel: () => exitCurveMode(false),
|
||||
})
|
||||
|
||||
const liveCurveOffset = useScene((s) => {
|
||||
const live = s.nodes[node.id]
|
||||
return live?.type === 'wall' ? ((live as WallNode).curveOffset ?? 0) : 0
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
const handlePoint = getWallMidpointHandlePoint({ ...node, curveOffset: liveCurveOffset })
|
||||
setCursorPos([handlePoint.x, 0, handlePoint.y])
|
||||
}, [liveCurveOffset, node])
|
||||
|
||||
return (
|
||||
<group>
|
||||
<CursorSphere position={cursorPos} showTooltip={false} />
|
||||
</group>
|
||||
)
|
||||
}
|
||||
|
||||
export default WallCurveTool
|
||||
Reference in New Issue
Block a user