Add a little dropdown arrow menu to gizmo with view settings (#3300)
This commit is contained in:
		@ -7603,7 +7603,7 @@ test.describe('Testing Gizmo', () => {
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  test('Context menu', async ({ page }) => {
 | 
			
		||||
  test('Context menu and popover menu', async ({ page }) => {
 | 
			
		||||
    const testCase = {
 | 
			
		||||
      testDescription: 'Right view',
 | 
			
		||||
      expectedCameraPosition: { x: 5660.02, y: -152, z: 26 },
 | 
			
		||||
@ -7698,6 +7698,16 @@ test.describe('Testing Gizmo', () => {
 | 
			
		||||
        testCase.expectedCameraTarget.z.toString()
 | 
			
		||||
      ),
 | 
			
		||||
    ])
 | 
			
		||||
 | 
			
		||||
    // Now test the popover menu.
 | 
			
		||||
    // It has the same click handlers, so we can just
 | 
			
		||||
    // test that it opens and contains the same content.
 | 
			
		||||
    const gizmoPopoverButton = page.getByRole('button', {
 | 
			
		||||
      name: 'view settings',
 | 
			
		||||
    })
 | 
			
		||||
    await expect(gizmoPopoverButton).toBeVisible()
 | 
			
		||||
    await gizmoPopoverButton.click()
 | 
			
		||||
    await expect(buttonToTest).toBeVisible()
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
import { SceneInfra } from 'clientSideScene/sceneInfra'
 | 
			
		||||
import { sceneInfra } from 'lib/singletons'
 | 
			
		||||
import { MutableRefObject, useEffect, useRef } from 'react'
 | 
			
		||||
import { MutableRefObject, useEffect, useMemo, useRef } from 'react'
 | 
			
		||||
import {
 | 
			
		||||
  WebGLRenderer,
 | 
			
		||||
  Scene,
 | 
			
		||||
@ -25,6 +25,8 @@ import {
 | 
			
		||||
  ContextMenuItem,
 | 
			
		||||
  ContextMenuItemRefresh,
 | 
			
		||||
} from './ContextMenu'
 | 
			
		||||
import { Popover } from '@headlessui/react'
 | 
			
		||||
import { CustomIcon } from './CustomIcon'
 | 
			
		||||
 | 
			
		||||
const CANVAS_SIZE = 80
 | 
			
		||||
const FRUSTUM_SIZE = 0.5
 | 
			
		||||
@ -59,6 +61,30 @@ export default function Gizmo() {
 | 
			
		||||
  const raycasterIntersect = useRef<Intersection<Object3D> | null>(null)
 | 
			
		||||
  const cameraPassiveUpdateTimer = useRef(0)
 | 
			
		||||
  const raycasterPassiveUpdateTimer = useRef(0)
 | 
			
		||||
  const menuItems = useMemo(
 | 
			
		||||
    () => [
 | 
			
		||||
      ...Object.entries(axisNamesSemantic).map(([axisName, axisSemantic]) => (
 | 
			
		||||
        <ContextMenuItem
 | 
			
		||||
          key={axisName}
 | 
			
		||||
          onClick={() => {
 | 
			
		||||
            sceneInfra.camControls.updateCameraToAxis(axisName as AxisNames)
 | 
			
		||||
          }}
 | 
			
		||||
        >
 | 
			
		||||
          {axisSemantic} view
 | 
			
		||||
        </ContextMenuItem>
 | 
			
		||||
      )),
 | 
			
		||||
      <ContextMenuItem
 | 
			
		||||
        onClick={() => {
 | 
			
		||||
          sceneInfra.camControls.resetCameraPosition()
 | 
			
		||||
        }}
 | 
			
		||||
      >
 | 
			
		||||
        Reset view
 | 
			
		||||
      </ContextMenuItem>,
 | 
			
		||||
      <ContextMenuDivider />,
 | 
			
		||||
      <ContextMenuItemRefresh />,
 | 
			
		||||
    ],
 | 
			
		||||
    [axisNamesSemantic]
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!canvasRef.current) return
 | 
			
		||||
@ -115,43 +141,48 @@ export default function Gizmo() {
 | 
			
		||||
  }, [])
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <>
 | 
			
		||||
    <div className="relative">
 | 
			
		||||
      <div
 | 
			
		||||
        ref={wrapperRef}
 | 
			
		||||
        aria-label="View orientation gizmo"
 | 
			
		||||
        className="grid place-content-center rounded-full overflow-hidden border border-solid border-primary/50 pointer-events-auto bg-chalkboard-10/70 dark:bg-chalkboard-100/80 backdrop-blur-sm"
 | 
			
		||||
      >
 | 
			
		||||
        <canvas ref={canvasRef} />
 | 
			
		||||
        <ContextMenu
 | 
			
		||||
          menuTargetElement={wrapperRef}
 | 
			
		||||
          items={[
 | 
			
		||||
            ...Object.entries(axisNamesSemantic).map(
 | 
			
		||||
              ([axisName, axisSemantic]) => (
 | 
			
		||||
                <ContextMenuItem
 | 
			
		||||
                  key={axisName}
 | 
			
		||||
                  onClick={() => {
 | 
			
		||||
                    sceneInfra.camControls.updateCameraToAxis(
 | 
			
		||||
                      axisName as AxisNames
 | 
			
		||||
                    )
 | 
			
		||||
                  }}
 | 
			
		||||
                >
 | 
			
		||||
                  {axisSemantic} view
 | 
			
		||||
                </ContextMenuItem>
 | 
			
		||||
              )
 | 
			
		||||
            ),
 | 
			
		||||
            <ContextMenuItem
 | 
			
		||||
              onClick={() => {
 | 
			
		||||
                sceneInfra.camControls.resetCameraPosition()
 | 
			
		||||
              }}
 | 
			
		||||
            >
 | 
			
		||||
              Reset view
 | 
			
		||||
            </ContextMenuItem>,
 | 
			
		||||
            <ContextMenuDivider />,
 | 
			
		||||
            <ContextMenuItemRefresh />,
 | 
			
		||||
          ]}
 | 
			
		||||
        />
 | 
			
		||||
        <ContextMenu menuTargetElement={wrapperRef} items={menuItems} />
 | 
			
		||||
      </div>
 | 
			
		||||
    </>
 | 
			
		||||
      <GizmoDropdown items={menuItems} />
 | 
			
		||||
    </div>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function GizmoDropdown({ items }: { items: React.ReactNode[] }) {
 | 
			
		||||
  return (
 | 
			
		||||
    <Popover className="absolute top-0 right-0 pointer-events-auto">
 | 
			
		||||
      {({ close }) => (
 | 
			
		||||
        <>
 | 
			
		||||
          <Popover.Button className="border-none p-0 m-0 -translate-y-1/4 translate-x-1/4">
 | 
			
		||||
            <CustomIcon
 | 
			
		||||
              name="caretDown"
 | 
			
		||||
              className="w-4 h-4 ui-open:rotate-180"
 | 
			
		||||
            />
 | 
			
		||||
            <span className="sr-only">View settings</span>
 | 
			
		||||
          </Popover.Button>
 | 
			
		||||
          <Popover.Panel
 | 
			
		||||
            className={`absolute bottom-full right-0 mb-2 w-48 bg-chalkboard-10 dark:bg-chalkboard-90
 | 
			
		||||
      border border-solid border-chalkboard-10 dark:border-chalkboard-90 rounded
 | 
			
		||||
      shadow-lg`}
 | 
			
		||||
          >
 | 
			
		||||
            <ul className="relative flex flex-col items-stretch content-stretch p-0.5">
 | 
			
		||||
              {items.map((item, index) => (
 | 
			
		||||
                <li key={index} className="contents" onClick={() => close()}>
 | 
			
		||||
                  {item}
 | 
			
		||||
                </li>
 | 
			
		||||
              ))}
 | 
			
		||||
            </ul>
 | 
			
		||||
          </Popover.Panel>
 | 
			
		||||
        </>
 | 
			
		||||
      )}
 | 
			
		||||
    </Popover>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user