Disable view control menu items and gizmo clicks conditionally (#5537)
* Disable view control menu items and gizmo clicks conditionally * Actually just turn off all mouse events, moves too * A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) * fix gizmo flickering * fmt * A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) * Add test step for disabling gizmo in sketch mode * Use grayscale to indicate disabled state? @max-mrgrsk feel free to discard if you prefer the opacity, I don't have a strong opinion at the moment. * A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: max-mrgrsk <156543465+max-mrgrsk@users.noreply.github.com>
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
@ -243,7 +243,7 @@ test.describe('Testing Gizmo', { tag: ['@skipWin'] }, () => {
|
||||
})
|
||||
|
||||
test.describe(`Testing gizmo, fixture-based`, () => {
|
||||
test('Center on selection from menu', async ({
|
||||
test('Center on selection from menu, disable interaction in sketch mode', async ({
|
||||
context,
|
||||
page,
|
||||
homePage,
|
||||
@ -318,5 +318,26 @@ test.describe(`Testing gizmo, fixture-based`, () => {
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
await test.step(`Gizmo should be disabled when in sketch mode`, async () => {
|
||||
const sketchModeButton = page.getByRole('button', {
|
||||
name: 'Edit sketch',
|
||||
})
|
||||
const exitSketchButton = page.getByRole('button', {
|
||||
name: 'Exit sketch',
|
||||
})
|
||||
|
||||
await sketchModeButton.click()
|
||||
await expect(exitSketchButton).toBeVisible()
|
||||
const gizmoPopoverButton = page.getByRole('button', {
|
||||
name: 'view settings',
|
||||
})
|
||||
await gizmoPopoverButton.click()
|
||||
const buttonToTest = page.getByRole('button', {
|
||||
name: 'right view',
|
||||
})
|
||||
await expect(buttonToTest).toBeVisible()
|
||||
await expect(buttonToTest).toBeDisabled()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -27,6 +27,8 @@ import {
|
||||
ViewControlContextMenu,
|
||||
} from './ViewControlMenu'
|
||||
import { AxisNames } from 'lib/constants'
|
||||
import { useModelingContext } from 'hooks/useModelingContext'
|
||||
import { useSettings } from 'machines/appMachine'
|
||||
|
||||
const CANVAS_SIZE = 80
|
||||
const FRUSTUM_SIZE = 0.5
|
||||
@ -40,12 +42,33 @@ enum AxisColors {
|
||||
}
|
||||
|
||||
export default function Gizmo() {
|
||||
const { state: modelingState } = useModelingContext()
|
||||
const settings = useSettings()
|
||||
const menuItems = useViewControlMenuItems()
|
||||
const wrapperRef = useRef<HTMLDivElement | null>(null)
|
||||
const canvasRef = useRef<HTMLCanvasElement | null>(null)
|
||||
const raycasterIntersect = useRef<Intersection<Object3D> | null>(null)
|
||||
const cameraPassiveUpdateTimer = useRef(0)
|
||||
const raycasterPassiveUpdateTimer = useRef(0)
|
||||
const disableOrbitRef = useRef(false)
|
||||
|
||||
// Temporary fix for #4040:
|
||||
// Disable gizmo orbiting in sketch mode
|
||||
// This effect updates disableOrbitRef whenever the user
|
||||
// toggles between Sketch mode and 3D mode
|
||||
useEffect(() => {
|
||||
disableOrbitRef.current =
|
||||
modelingState.matches('Sketch') &&
|
||||
!settings.app.allowOrbitInSketchMode.current
|
||||
if (wrapperRef.current) {
|
||||
wrapperRef.current.style.filter = disableOrbitRef.current
|
||||
? 'grayscale(100%)'
|
||||
: 'none'
|
||||
wrapperRef.current.style.cursor = disableOrbitRef.current
|
||||
? 'not-allowed'
|
||||
: 'auto'
|
||||
}
|
||||
}, [modelingState, settings.app.allowOrbitInSketchMode.current])
|
||||
|
||||
useEffect(() => {
|
||||
if (!canvasRef.current) return
|
||||
@ -64,7 +87,8 @@ export default function Gizmo() {
|
||||
const { mouse, disposeMouseEvents } = initializeMouseEvents(
|
||||
canvas,
|
||||
raycasterIntersect,
|
||||
sceneInfra
|
||||
sceneInfra,
|
||||
disableOrbitRef
|
||||
)
|
||||
const raycasterObjects = [...gizmoAxisHeads]
|
||||
|
||||
@ -81,6 +105,8 @@ export default function Gizmo() {
|
||||
delta,
|
||||
cameraPassiveUpdateTimer
|
||||
)
|
||||
// If orbits are disabled, skip click logic
|
||||
if (!disableOrbitRef.current) {
|
||||
updateRayCaster(
|
||||
raycasterObjects,
|
||||
raycaster,
|
||||
@ -90,6 +116,10 @@ export default function Gizmo() {
|
||||
delta,
|
||||
raycasterPassiveUpdateTimer
|
||||
)
|
||||
} else {
|
||||
raycasterObjects.forEach((object) => object.scale.set(1, 1, 1)) // Reset scales
|
||||
raycasterIntersect.current = null // Clear intersection
|
||||
}
|
||||
renderer.render(scene, camera)
|
||||
requestAnimationFrame(animate)
|
||||
}
|
||||
@ -246,7 +276,8 @@ const quaternionsEqual = (
|
||||
const initializeMouseEvents = (
|
||||
canvas: HTMLCanvasElement,
|
||||
raycasterIntersect: MutableRefObject<Intersection<Object3D> | null>,
|
||||
sceneInfra: SceneInfra
|
||||
sceneInfra: SceneInfra,
|
||||
disableOrbitRef: MutableRefObject<boolean>
|
||||
): { mouse: Vector2; disposeMouseEvents: () => void } => {
|
||||
const mouse = new Vector2()
|
||||
mouse.x = 1 // fix initial mouse position issue
|
||||
@ -258,11 +289,11 @@ const initializeMouseEvents = (
|
||||
}
|
||||
|
||||
const handleClick = () => {
|
||||
if (raycasterIntersect.current) {
|
||||
// If orbits are disabled, skip click logic
|
||||
if (disableOrbitRef.current || !raycasterIntersect.current) return
|
||||
const axisName = raycasterIntersect.current.object.name as AxisNames
|
||||
sceneInfra.camControls.updateCameraToAxis(axisName).catch(reportRejection)
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('mousemove', handleMouseMove)
|
||||
window.addEventListener('click', handleClick)
|
||||
|
@ -10,9 +10,14 @@ import { AxisNames, VIEW_NAMES_SEMANTIC } from 'lib/constants'
|
||||
import { useModelingContext } from 'hooks/useModelingContext'
|
||||
import { useMemo } from 'react'
|
||||
import { sceneInfra } from 'lib/singletons'
|
||||
import { useSettings } from 'machines/appMachine'
|
||||
|
||||
export function useViewControlMenuItems() {
|
||||
const { send: modelingSend } = useModelingContext()
|
||||
const { state: modelingState, send: modelingSend } = useModelingContext()
|
||||
const settings = useSettings()
|
||||
const shouldLockView =
|
||||
modelingState.matches('Sketch') &&
|
||||
!settings.app.allowOrbitInSketchMode.current
|
||||
const menuItems = useMemo(
|
||||
() => [
|
||||
...Object.entries(VIEW_NAMES_SEMANTIC).map(([axisName, axisSemantic]) => (
|
||||
@ -23,6 +28,7 @@ export function useViewControlMenuItems() {
|
||||
.updateCameraToAxis(axisName as AxisNames)
|
||||
.catch(reportRejection)
|
||||
}}
|
||||
disabled={shouldLockView}
|
||||
>
|
||||
{axisSemantic} view
|
||||
</ContextMenuItem>
|
||||
@ -32,6 +38,7 @@ export function useViewControlMenuItems() {
|
||||
onClick={() => {
|
||||
sceneInfra.camControls.resetCameraPosition().catch(reportRejection)
|
||||
}}
|
||||
disabled={shouldLockView}
|
||||
>
|
||||
Reset view
|
||||
</ContextMenuItem>,
|
||||
@ -39,13 +46,14 @@ export function useViewControlMenuItems() {
|
||||
onClick={() => {
|
||||
modelingSend({ type: 'Center camera on selection' })
|
||||
}}
|
||||
disabled={shouldLockView}
|
||||
>
|
||||
Center view on selection
|
||||
</ContextMenuItem>,
|
||||
<ContextMenuDivider />,
|
||||
<ContextMenuItemRefresh />,
|
||||
],
|
||||
[VIEW_NAMES_SEMANTIC]
|
||||
[VIEW_NAMES_SEMANTIC, shouldLockView]
|
||||
)
|
||||
return menuItems
|
||||
}
|
||||
|