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.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, context,
page, page,
homePage, 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, ViewControlContextMenu,
} from './ViewControlMenu' } from './ViewControlMenu'
import { AxisNames } from 'lib/constants' import { AxisNames } from 'lib/constants'
import { useModelingContext } from 'hooks/useModelingContext'
import { useSettings } from 'machines/appMachine'
const CANVAS_SIZE = 80 const CANVAS_SIZE = 80
const FRUSTUM_SIZE = 0.5 const FRUSTUM_SIZE = 0.5
@ -40,12 +42,33 @@ enum AxisColors {
} }
export default function Gizmo() { export default function Gizmo() {
const { state: modelingState } = useModelingContext()
const settings = useSettings()
const menuItems = useViewControlMenuItems() const menuItems = useViewControlMenuItems()
const wrapperRef = useRef<HTMLDivElement | null>(null) const wrapperRef = useRef<HTMLDivElement | null>(null)
const canvasRef = useRef<HTMLCanvasElement | null>(null) const canvasRef = useRef<HTMLCanvasElement | null>(null)
const raycasterIntersect = useRef<Intersection<Object3D> | null>(null) const raycasterIntersect = useRef<Intersection<Object3D> | null>(null)
const cameraPassiveUpdateTimer = useRef(0) const cameraPassiveUpdateTimer = useRef(0)
const raycasterPassiveUpdateTimer = 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(() => { useEffect(() => {
if (!canvasRef.current) return if (!canvasRef.current) return
@ -64,7 +87,8 @@ export default function Gizmo() {
const { mouse, disposeMouseEvents } = initializeMouseEvents( const { mouse, disposeMouseEvents } = initializeMouseEvents(
canvas, canvas,
raycasterIntersect, raycasterIntersect,
sceneInfra sceneInfra,
disableOrbitRef
) )
const raycasterObjects = [...gizmoAxisHeads] const raycasterObjects = [...gizmoAxisHeads]
@ -81,15 +105,21 @@ export default function Gizmo() {
delta, delta,
cameraPassiveUpdateTimer cameraPassiveUpdateTimer
) )
updateRayCaster( // If orbits are disabled, skip click logic
raycasterObjects, if (!disableOrbitRef.current) {
raycaster, updateRayCaster(
mouse, raycasterObjects,
camera, raycaster,
raycasterIntersect, mouse,
delta, camera,
raycasterPassiveUpdateTimer raycasterIntersect,
) delta,
raycasterPassiveUpdateTimer
)
} else {
raycasterObjects.forEach((object) => object.scale.set(1, 1, 1)) // Reset scales
raycasterIntersect.current = null // Clear intersection
}
renderer.render(scene, camera) renderer.render(scene, camera)
requestAnimationFrame(animate) requestAnimationFrame(animate)
} }
@ -246,7 +276,8 @@ const quaternionsEqual = (
const initializeMouseEvents = ( const initializeMouseEvents = (
canvas: HTMLCanvasElement, canvas: HTMLCanvasElement,
raycasterIntersect: MutableRefObject<Intersection<Object3D> | null>, raycasterIntersect: MutableRefObject<Intersection<Object3D> | null>,
sceneInfra: SceneInfra sceneInfra: SceneInfra,
disableOrbitRef: MutableRefObject<boolean>
): { mouse: Vector2; disposeMouseEvents: () => void } => { ): { mouse: Vector2; disposeMouseEvents: () => void } => {
const mouse = new Vector2() const mouse = new Vector2()
mouse.x = 1 // fix initial mouse position issue mouse.x = 1 // fix initial mouse position issue
@ -258,10 +289,10 @@ const initializeMouseEvents = (
} }
const handleClick = () => { const handleClick = () => {
if (raycasterIntersect.current) { // If orbits are disabled, skip click logic
const axisName = raycasterIntersect.current.object.name as AxisNames if (disableOrbitRef.current || !raycasterIntersect.current) return
sceneInfra.camControls.updateCameraToAxis(axisName).catch(reportRejection) const axisName = raycasterIntersect.current.object.name as AxisNames
} sceneInfra.camControls.updateCameraToAxis(axisName).catch(reportRejection)
} }
window.addEventListener('mousemove', handleMouseMove) window.addEventListener('mousemove', handleMouseMove)

View File

@ -10,9 +10,14 @@ import { AxisNames, VIEW_NAMES_SEMANTIC } from 'lib/constants'
import { useModelingContext } from 'hooks/useModelingContext' import { useModelingContext } from 'hooks/useModelingContext'
import { useMemo } from 'react' import { useMemo } from 'react'
import { sceneInfra } from 'lib/singletons' import { sceneInfra } from 'lib/singletons'
import { useSettings } from 'machines/appMachine'
export function useViewControlMenuItems() { 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( const menuItems = useMemo(
() => [ () => [
...Object.entries(VIEW_NAMES_SEMANTIC).map(([axisName, axisSemantic]) => ( ...Object.entries(VIEW_NAMES_SEMANTIC).map(([axisName, axisSemantic]) => (
@ -23,6 +28,7 @@ export function useViewControlMenuItems() {
.updateCameraToAxis(axisName as AxisNames) .updateCameraToAxis(axisName as AxisNames)
.catch(reportRejection) .catch(reportRejection)
}} }}
disabled={shouldLockView}
> >
{axisSemantic} view {axisSemantic} view
</ContextMenuItem> </ContextMenuItem>
@ -32,6 +38,7 @@ export function useViewControlMenuItems() {
onClick={() => { onClick={() => {
sceneInfra.camControls.resetCameraPosition().catch(reportRejection) sceneInfra.camControls.resetCameraPosition().catch(reportRejection)
}} }}
disabled={shouldLockView}
> >
Reset view Reset view
</ContextMenuItem>, </ContextMenuItem>,
@ -39,13 +46,14 @@ export function useViewControlMenuItems() {
onClick={() => { onClick={() => {
modelingSend({ type: 'Center camera on selection' }) modelingSend({ type: 'Center camera on selection' })
}} }}
disabled={shouldLockView}
> >
Center view on selection Center view on selection
</ContextMenuItem>, </ContextMenuItem>,
<ContextMenuDivider />, <ContextMenuDivider />,
<ContextMenuItemRefresh />, <ContextMenuItemRefresh />,
], ],
[VIEW_NAMES_SEMANTIC] [VIEW_NAMES_SEMANTIC, shouldLockView]
) )
return menuItems return menuItems
} }