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>
This commit is contained in:
Frank Noirot
2025-02-28 17:27:35 -05:00
committed by GitHub
parent f238f3882b
commit 9ce591a1a1
10 changed files with 78 additions and 18 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 76 KiB

View File

@ -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()
})
})
})

View File

@ -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)

View File

@ -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
}