diff --git a/.changeset/cold-toes-warn.md b/.changeset/cold-toes-warn.md new file mode 100644 index 00000000..e0504737 --- /dev/null +++ b/.changeset/cold-toes-warn.md @@ -0,0 +1,5 @@ +--- +'leva': minor +--- + +Add a joystick to Vector3d diff --git a/demo/src/sandboxes/leva-busy/src/App.tsx b/demo/src/sandboxes/leva-busy/src/App.tsx index 714947a8..74f27b7d 100644 --- a/demo/src/sandboxes/leva-busy/src/App.tsx +++ b/demo/src/sandboxes/leva-busy/src/App.tsx @@ -43,6 +43,8 @@ const ExtraControls = () => { function Controls() { const data = useControls({ + vector2D: [10, 10], + vector3D: [10, 10, 10], dimension: '4px', string: { value: 'something', optional: true, order: -2 }, range: { value: 0, min: -10, max: 10, order: -3 }, diff --git a/packages/leva/src/components/Button/Button.tsx b/packages/leva/src/components/Button/Button.tsx index 599fb3d0..d275e100 100644 --- a/packages/leva/src/components/Button/Button.tsx +++ b/packages/leva/src/components/Button/Button.tsx @@ -5,7 +5,7 @@ import { Row } from '../UI' import { StyledButton } from './StyledButton' type ButtonProps = { - label: string + label: string | JSX.Element } & Omit export function Button({ onClick, settings, label }: ButtonProps) { diff --git a/packages/leva/src/components/UI/JoyCube.tsx b/packages/leva/src/components/UI/JoyCube.tsx new file mode 100644 index 00000000..6177901b --- /dev/null +++ b/packages/leva/src/components/UI/JoyCube.tsx @@ -0,0 +1,51 @@ +import React from 'react' +import { StyledJoyCubeFace, StyledJoyCube } from './StyledJoystick3d' + +export function JoyCube({ + isTop, + isRight, + showFront = true, + showMid = true, + showRear = false, +}: { + isTop?: boolean + isRight?: boolean + showFront?: boolean + showMid?: boolean + showRear?: boolean +}) { + return ( + + {showFront && ( + <> + + + + + + + + )} + {showMid && ( + <> + + + + + + + + )} + {showRear && ( + <> + + + + + + + + )} + + ) +} diff --git a/packages/leva/src/components/Vector2d/Joystick.tsx b/packages/leva/src/components/UI/Joystick.tsx similarity index 86% rename from packages/leva/src/components/Vector2d/Joystick.tsx rename to packages/leva/src/components/UI/Joystick.tsx index 20290241..127b7c07 100644 --- a/packages/leva/src/components/Vector2d/Joystick.tsx +++ b/packages/leva/src/components/UI/Joystick.tsx @@ -1,16 +1,16 @@ import React, { useState, useRef, useCallback, useEffect, useLayoutEffect } from 'react' import { useDrag } from '../../hooks' import { clamp, multiplyStep } from '../../utils' -import { JoystickTrigger, JoystickPlayground } from './StyledJoystick' +import { JoystickTrigger, JoystickPlayground, JoystickGrid } from './StyledJoystick' import { useTh } from '../../styles' -import { Portal } from '../UI' +import { Portal } from '.' import { useTransform } from '../../hooks' -import type { Vector2d } from '../../types' -import type { Vector2dProps } from './vector2d-types' +import type { Vector2d, Vector3d } from '../../types' +import type { Vector2dProps } from '../Vector2d/vector2d-types' -type JoystickProps = { value: Vector2d } & Pick +type JoystickProps = { value: Vector2d | Vector3d } & Pick & { children?: any } -export function Joystick({ value, settings, onUpdate }: JoystickProps) { +export function Joystick({ value, settings, onUpdate, children }: JoystickProps) { const timeout = useRef() const outOfBoundsX = useRef(0) const outOfBoundsY = useRef(0) @@ -52,13 +52,14 @@ export function Joystick({ value, settings, onUpdate }: JoystickProps) { if (outOfBoundsX.current) set({ x: outOfBoundsX.current * w }) if (outOfBoundsY.current) set({ y: outOfBoundsY.current * -h }) timeout.current = window.setInterval(() => { - onUpdate((v: Vector2d) => { + onUpdate((v: Vector2d | Vector3d) => { const incX = stepV1 * outOfBoundsX.current * stepMultiplier.current const incY = yFactor * stepV2 * outOfBoundsY.current * stepMultiplier.current + return Array.isArray(v) ? { - [v1]: v[0] + incX, - [v2]: v[1] + incY, + [v1]: v[['x', 'y', 'z'].indexOf(v1)] + incX, + [v2]: v[['x', 'y', 'z'].indexOf(v2)] + incY, } : { [v1]: v[v1] + incX, @@ -128,7 +129,7 @@ export function Joystick({ value, settings, onUpdate }: JoystickProps) { {joystickShown && ( -
+ {children ? children : } diff --git a/packages/leva/src/components/UI/Joystick3d.tsx b/packages/leva/src/components/UI/Joystick3d.tsx new file mode 100644 index 00000000..0b434283 --- /dev/null +++ b/packages/leva/src/components/UI/Joystick3d.tsx @@ -0,0 +1,63 @@ +import React from 'react' +import { Joystick } from './Joystick' +import { useKeyPress } from '../../hooks/useKeyPress' +import { JoystickButtons, KeyLabel } from './StyledJoystick3d' +import { Button } from '../Button' +import type { InternalVector2dSettings } from '../Vector2d/vector2d-types' +import type { Vector3d } from '../../types' +import type { Vector3dProps } from '../Vector3d/vector3d-types' +import { JoyCube } from './JoyCube' + +type Joystick3dProps = { value: Vector3d } & Pick + +const joystick3dKeyBindings = [ + { key: 'Control', keyLabel: 'ctrl', plane: 'xz', label: 'XZ' }, + { key: '', keyLabel: '', plane: 'xy', label: 'XY' }, + { key: 'Meta', keyLabel: 'meta', plane: 'zy', label: 'ZY' }, +] + +export function Joystick3d({ value, settings, onUpdate }: Joystick3dProps) { + const [plane, setPlane] = React.useState('xy') + const keyPress0 = useKeyPress(joystick3dKeyBindings[0].key) + // const keyPress1 = useKeyPress(joystick3dKeyBindings[1].key) + const keyPress2 = useKeyPress(joystick3dKeyBindings[2].key) + + React.useEffect(() => { + if (keyPress0) setPlane(joystick3dKeyBindings[0].plane) + else if (keyPress2) setPlane(joystick3dKeyBindings[2].plane) + else setPlane(joystick3dKeyBindings[1].plane) + }, [keyPress0, keyPress2]) + + const settings2d = React.useMemo(() => { + const { keys, ...rest } = settings + return { keys: plane, ...rest } as unknown as InternalVector2dSettings + }, [settings, plane]) + + return ( + <> + + + + + {joystick3dKeyBindings.map((kb) => ( +