Moved r3f's toolbar out of the snapshot editor

This commit is contained in:
Aria Minaei 2021-07-10 12:15:27 +02:00
parent 7f30f08bd0
commit 86547aa4cb
8 changed files with 19 additions and 20 deletions

View file

@ -0,0 +1,170 @@
import type {VFC} from 'react'
import {useState} from 'react'
import React from 'react'
import TransformControlsModeSelect from './TransformControlsModeSelect'
import {useEditorStore} from '../../store'
import shallow from 'zustand/shallow'
import TransformControlsSpaceSelect from './TransformControlsSpaceSelect'
import ViewportShadingSelect from './ViewportShadingSelect'
import {GiPocketBow, RiFocus3Line} from 'react-icons/all'
import {Vector3} from 'three'
import type {$FixMe} from '@theatre/shared/utils/types'
import studio from '@theatre/studio'
import {getSelected} from '../useSelected'
import {useVal} from '@theatre/dataverse-react'
import IconButton from './utils/IconButton'
import {PortalContext} from 'reakit'
import styled from 'styled-components'
const Container = styled.div`
z-index: 50;
position: absolute;
top: 0px;
right: 0px;
bottom: 0px;
left: 0px;
pointer-events: none;
`
const TopRow = styled.div`
position: relative;
margin: 1.25rem;
height: 100%;
display: flex;
flex: 1 1 0%;
justify-content: space-between;
align-items: flex-start;
`
const Tools = styled.div`
display: flex;
gap: 1rem;
`
const ToolGroup = styled.div`
pointer-events: auto;
`
const Toolbar: VFC = () => {
const [editorObject] = useEditorStore(
(state) => [state.editorObject],
shallow,
)
const transformControlsMode =
useVal(editorObject?.props.transformControls.mode) ?? 'translate'
const transformControlsSpace =
useVal(editorObject?.props.transformControls.space) ?? 'world'
const viewportShading =
useVal(editorObject?.props.viewport.shading) ?? 'rendered'
const [wrapper, setWrapper] = useState<null | HTMLDivElement>(null)
if (!editorObject) return <></>
return (
<PortalContext.Provider value={wrapper}>
<Container ref={setWrapper}>
<TopRow>
<Tools>
<ToolGroup>
<TransformControlsModeSelect
value={transformControlsMode}
onChange={(value) =>
studio.transaction(({set}) =>
set(editorObject!.props.transformControls.mode, value),
)
}
/>
</ToolGroup>
<ToolGroup>
<TransformControlsSpaceSelect
value={transformControlsSpace}
onChange={(space) => {
studio.transaction(({set}) => {
set(editorObject.props.transformControls.space, space)
})
}}
/>
</ToolGroup>
<ToolGroup>
<ViewportShadingSelect
value={viewportShading}
onChange={(shading) => {
studio.transaction(({set}) => {
set(editorObject.props.viewport.shading, shading)
})
}}
/>
</ToolGroup>
<ToolGroup>
<IconButton
label="Focus on selected"
icon={<RiFocus3Line />}
onClick={() => {
const orbitControls =
useEditorStore.getState().orbitControlsRef?.current
const selected = getSelected()
let focusObject
if (selected) {
focusObject =
useEditorStore.getState().editablesSnapshot![selected]
.proxyObject
}
if (orbitControls && focusObject) {
focusObject.getWorldPosition(
// @ts-ignore TODO
orbitControls.target as Vector3,
)
}
}}
/>
</ToolGroup>
<ToolGroup>
<IconButton
label="Align object to view"
icon={<GiPocketBow />}
onClick={() => {
const camera = (
useEditorStore.getState().orbitControlsRef
?.current as $FixMe
)?.object
const selected = getSelected()
let proxyObject
if (selected) {
proxyObject =
useEditorStore.getState().editablesSnapshot![selected]
.proxyObject
if (proxyObject && camera) {
const direction = new Vector3()
const position = camera.position.clone()
camera.getWorldDirection(direction)
proxyObject.position.set(0, 0, 0)
proxyObject.lookAt(direction)
proxyObject.parent!.worldToLocal(position)
proxyObject.position.copy(position)
proxyObject.updateMatrix()
}
}
}}
/>
</ToolGroup>
</Tools>
</TopRow>
</Container>
</PortalContext.Provider>
)
}
export default Toolbar

View file

@ -0,0 +1,39 @@
import type {VFC} from 'react'
import React from 'react'
import {GiClockwiseRotation, GiMove, GiResize} from 'react-icons/all'
import type {TransformControlsMode} from '../../store'
import CompactModeSelect from './utils/CompactModeSelect'
export interface TransformControlsModeSelectProps {
value: TransformControlsMode
onChange: (value: TransformControlsMode) => void
}
const TransformControlsModeSelect: VFC<TransformControlsModeSelectProps> = ({
value,
onChange,
}) => (
<CompactModeSelect
value={value}
onChange={onChange}
options={[
{
option: 'translate',
label: 'Tool: Translate',
icon: <GiMove />,
},
{
option: 'rotate',
label: 'Tool: Rotate',
icon: <GiClockwiseRotation />,
},
{
option: 'scale',
label: 'Tool: Scale',
icon: <GiResize />,
},
]}
/>
)
export default TransformControlsModeSelect

View file

@ -0,0 +1,34 @@
import type {VFC} from 'react'
import React from 'react'
import type {TransformControlsSpace} from '../../store'
import {BiCube, BiGlobe} from 'react-icons/all'
import CompactModeSelect from './utils/CompactModeSelect'
export interface TransformControlsSpaceSelectProps {
value: TransformControlsSpace
onChange: (value: TransformControlsSpace) => void
}
const TransformControlsSpaceSelect: VFC<TransformControlsSpaceSelectProps> = ({
value,
onChange,
}) => (
<CompactModeSelect
value={value}
onChange={onChange}
options={[
{
option: 'world',
label: 'Space: World',
icon: <BiGlobe />,
},
{
option: 'local',
label: 'Space: Local',
icon: <BiCube />,
},
]}
/>
)
export default TransformControlsSpaceSelect

View file

@ -0,0 +1,44 @@
import type {VFC} from 'react'
import React from 'react'
import type {ViewportShading} from '../../store'
import {FaCube, GiCube, GiIceCube, BiCube} from 'react-icons/all'
import CompactModeSelect from './utils/CompactModeSelect'
export interface ViewportShadingSelectProps {
value: ViewportShading
onChange: (value: ViewportShading) => void
}
const ViewportShadingSelect: VFC<ViewportShadingSelectProps> = ({
value,
onChange,
}) => (
<CompactModeSelect
value={value}
onChange={onChange}
options={[
{
option: 'wireframe',
label: 'Display: Wireframe',
icon: <BiCube />,
},
{
option: 'flat',
label: 'Display: Flat',
icon: <GiCube />,
},
{
option: 'solid',
label: 'Display: Solid',
icon: <FaCube />,
},
{
option: 'rendered',
label: 'Display: Rendered',
icon: <GiIceCube />,
},
]}
/>
)
export default ViewportShadingSelect

View file

@ -0,0 +1,115 @@
import type {ReactElement, ReactNode} from 'react'
import React from 'react'
import type {IconType} from 'react-icons'
import {Group, Button} from 'reakit'
import styled from 'styled-components'
import {Tooltip, TooltipReference, useTooltipState} from './Tooltip'
interface OptionButtonProps<Option> {
value: Option
option: Option
label: string
icon: ReactElement<IconType>
onClick: () => void
}
const _TooltipRef = styled(TooltipReference)<{selected: boolean}>`
display: flex;
position: relative;
align-items: center;
justify-content: center;
vertical-align: middle;
width: auto;
font-size: 0.875rem;
line-height: 1.25rem;
font-weight: 600;
height: 1.75rem;
padding-left: 0.5rem;
padding-right: 0.5rem;
border: 0 transparent;
&:first-child {
border-top-left-radius: 0.25rem;
border-bottom-left-radius: 0.25rem;
}
&:last-child {
border-top-right-radius: 0.25rem;
border-bottom-right-radius: 0.25rem;
}
&:focus {
outline: none;
}
color: ${({selected}) => (selected ? 'white' : 'rgba(55, 65, 81, 1)')};
background-color: ${({selected}) =>
selected ? 'rgba(6, 95, 70, 1)' : 'rgba(243, 244, 246, 1);'};
&:hover {
background-color: ${({selected}) =>
selected ? 'rgba(6, 78, 59, 1)' : 'rgba(229, 231, 235, 1);'};
}
`
function OptionButton<Option>({
value,
option,
label,
icon,
onClick,
}: OptionButtonProps<Option>) {
const tooltip = useTooltipState()
return (
<>
<_TooltipRef
{...tooltip}
forwardedAs={Button}
selected={option === value}
aria-label={label}
onClick={onClick}
>
{icon}
</_TooltipRef>
<Tooltip {...tooltip}>{label}</Tooltip>
</>
)
}
interface CompactModeSelectProps<Option> {
value: Option
onChange: (value: Option) => void
options: {
option: Option
label: string
icon: ReactElement<IconType>
}[]
settingsPanel?: ReactNode
}
const Container = styled(Group)`
display: flex;
`
const CompactModeSelect = <Option extends string | number>({
value,
onChange,
options,
}: CompactModeSelectProps<Option>) => {
return (
<Container>
{options.map(({label, icon, option}) => (
<OptionButton
key={option}
value={value}
option={option}
label={label}
icon={icon}
onClick={() => onChange(option)}
/>
))}
</Container>
)
}
export default CompactModeSelect

View file

@ -0,0 +1,70 @@
import type {ReactElement} from 'react'
import React, {forwardRef} from 'react'
import type {ButtonProps} from 'reakit'
import {Button} from 'reakit'
import type {IconType} from 'react-icons'
import {Tooltip, TooltipReference, useTooltipState} from './Tooltip'
import styled from 'styled-components'
export interface IconButtonProps extends Exclude<ButtonProps, 'children'> {
icon: ReactElement<IconType>
label: string
}
const _TooltipRef = styled(TooltipReference)`
display: flex;
position: relative;
align-items: center;
justify-content: center;
vertical-align: middle;
width: auto;
font-size: 0.875rem;
line-height: 1.25rem;
font-weight: 600;
height: 1.75rem;
padding-left: 0.5rem;
padding-right: 0.5rem;
&:first-child {
border-top-left-radius: 0.25rem;
border-bottom-left-radius: 0.25rem;
}
&:last-child {
border-top-right-radius: 0.25rem;
border-bottom-right-radius: 0.25rem;
}
&:focus {
outline: none;
}
color: rgba(55, 65, 81, 1);
background-color: rgba(243, 244, 246, 1);
&:hover {
background-color: rgba(229, 231, 235, 1);
}
border: 0 transparent;
`
const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(
({label, icon, className, ...props}, ref) => {
const tooltip = useTooltipState()
return (
<>
<_TooltipRef
{...props}
{...tooltip}
forwardedAs={Button}
aria-label={label}
>
{icon}
</_TooltipRef>
<Tooltip {...tooltip}>{label}</Tooltip>
</>
)
},
)
export default IconButton

View file

@ -0,0 +1,26 @@
import type {VFC} from 'react'
import React from 'react'
import {Tooltip as TooltipImpl, TooltipReference, useTooltipState} from 'reakit'
import type {TooltipProps} from 'reakit'
import styled from 'styled-components'
export {TooltipReference, useTooltipState}
const Container = styled(TooltipImpl)`
padding-left: 0.5rem;
padding-right: 0.5rem;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
font-size: 0.875rem;
line-height: 1.25rem;
border-radius: 0.125rem;
background-color: rgba(55, 65, 81, 1);
color: white;
pointer-events: none;
`
export const Tooltip: VFC<TooltipProps> = ({className, ...props}) => (
<Container {...props} className={className as string} />
)