fix(editor): don't flag camera as dragging on ACTION.NONE controlstart (#536)

camera-controls fires controlstart for every pointerdown — including
buttons mapped to ACTION.NONE (plain left click in edit mode). Since
#535 that set cameraDragging=true with no rest/sleep ever following to
clear it, so every canvas click (selection, wall placement) was
suppressed once the camera was at rest.

Only flag dragging when currentAction actually drives the camera, and
clear the flag on controlend for mapped-button taps with zero movement
(no wake -> no rest/sleep).

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Wassim SAMAD
2026-07-23 09:58:28 -04:00
committed by GitHub
co-authored by Claude Fable 5
parent fcac55ca30
commit c3659acdf8
@@ -457,11 +457,14 @@ export const CustomCameraControls = () => {
} }
}, [freezeActivePoseInterpolation]) }, [freezeActivePoseInterpolation])
const beginLocalCameraInteraction = useCallback(() => { const beginLocalCameraInteraction = useCallback(
cancelPoseApplication() ({ dragging = true }: { dragging?: boolean } = {}) => {
cameraDraggingLifecycle.begin() cancelPoseApplication()
emitter.emit('camera-controls:interaction-start', undefined) if (dragging) cameraDraggingLifecycle.begin()
}, [cameraDraggingLifecycle, cancelPoseApplication]) emitter.emit('camera-controls:interaction-start', undefined)
},
[cameraDraggingLifecycle, cancelPoseApplication],
)
const applyPendingPose = useCallback(() => { const applyPendingPose = useCallback(() => {
if (isFirstPersonMode) { if (isFirstPersonMode) {
@@ -1123,10 +1126,18 @@ export const CustomCameraControls = () => {
// Cancel any in-progress 2D-origin navigation pose when the user starts // Cancel any in-progress 2D-origin navigation pose when the user starts
// dragging (right-click orbit, middle-click pan, touch). `controlstart` // dragging (right-click orbit, middle-click pan, touch). `controlstart`
// fires only for user pointer interactions — not for programmatic // fires only for user pointer interactions — not for programmatic
// moveTo/rotateTo which emit `transitionstart` instead. // moveTo/rotateTo which emit `transitionstart` instead. It also fires for
// pointerdowns whose button is mapped to ACTION.NONE (plain left click in
// edit mode); those must not flag the camera as dragging — no rest/sleep
// ever follows to clear the flag, which would leave canvas clicks
// (selection, placement) suppressed until the next real camera move.
const handleControlStart = useCallback(() => { const handleControlStart = useCallback(() => {
clearPendingFloorplanNavigationPose() clearPendingFloorplanNavigationPose()
beginLocalCameraInteraction() beginLocalCameraInteraction({
dragging: controls.current
? controls.current.currentAction !== CameraControlsImpl.ACTION.NONE
: false,
})
}, [beginLocalCameraInteraction, clearPendingFloorplanNavigationPose]) }, [beginLocalCameraInteraction, clearPendingFloorplanNavigationPose])
// Preview mode: auto-navigate camera to selected node (viewer behavior) // Preview mode: auto-navigate camera to selected node (viewer behavior)
@@ -1435,6 +1446,16 @@ export const CustomCameraControls = () => {
cameraDraggingLifecycle.end() cameraDraggingLifecycle.end()
}, [cameraDraggingLifecycle]) }, [cameraDraggingLifecycle])
const onControlEnd = useCallback(() => {
// A mapped-button tap with zero camera movement never wakes the
// controls, so no rest/sleep follows — clear the dragging flag on
// release. While damping is still settling (`active`), rest/sleep
// clears it instead.
if (!controls.current?.active) {
cameraDraggingLifecycle.end()
}
}, [cameraDraggingLifecycle])
// Preset capture mode frames a single subtree (often a 0.32m preset), // Preset capture mode frames a single subtree (often a 0.32m preset),
// so the default 2m minDistance prevents the user from getting close // so the default 2m minDistance prevents the user from getting close
// enough to compose a good thumbnail. Relax the clamp to 0.5m while // enough to compose a good thumbnail. Relax the clamp to 0.5m while
@@ -1455,6 +1476,7 @@ export const CustomCameraControls = () => {
minDistance={minDistance} minDistance={minDistance}
minPolarAngle={0} minPolarAngle={0}
mouseButtons={mouseButtons} mouseButtons={mouseButtons}
onControlEnd={onControlEnd}
onControlStart={handleControlStart} onControlStart={handleControlStart}
onUpdate={handleCameraUpdate} onUpdate={handleCameraUpdate}
onRest={onRest} onRest={onRest}