Implement studio.globalToolbar

This commit is contained in:
Aria Minaei 2021-07-13 16:13:15 +02:00
parent 86547aa4cb
commit 921bc44270
18 changed files with 433 additions and 924 deletions

View file

@ -43,6 +43,7 @@
"@theatre/core": "workspace:*",
"@theatre/studio": "workspace:*",
"lodash-es": "^4.17.21",
"polished": "^4.1.3",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-icons": "^4.2.0",

View file

@ -6,11 +6,10 @@ import {useEditorStore} from '../store'
import {OrbitControls} from '@react-three/drei'
import shallow from 'zustand/shallow'
import root from 'react-shadow/styled-components'
import Toolbar from './Toolbar/Toolbar'
import ProxyManager from './ProxyManager'
import studio from '@theatre/studio'
import {useVal} from '@theatre/dataverse-react'
import styled, {createGlobalStyle} from 'styled-components'
import styled, {createGlobalStyle, StyleSheetManager} from 'styled-components'
const GlobalStyle = createGlobalStyle`
:host {
@ -52,12 +51,13 @@ const EditorScene = () => {
return (
<>
{showGrid && <gridHelper args={[1000, 1000, 0x444444, 0x888888]} />}
{showGrid && <gridHelper args={[20, 20, '#6e6e6e', '#4a4b4b']} />}
{showAxes && <axesHelper args={[500]} />}
{/* @ts-ignore */}
<OrbitControls ref={orbitControlsRef} enableDamping={false} />
<primitive object={helpersRoot}></primitive>
<ProxyManager orbitControlsRef={orbitControlsRef} />
<color attach="background" args={[0.24, 0.24, 0.24]} />
</>
)
}
@ -106,29 +106,36 @@ const Editor: VFC = () => {
return (
<root.div>
<GlobalStyle />
<Wrapper id="theatre-plugin-r3f-root" visible={true}>
<Toolbar />
{sceneSnapshot ? (
<>
<CanvasWrapper>
<Canvas
// @ts-ignore
colorManagement
camera={initialEditorCamera}
onCreated={({gl}) => {
gl.setClearColor('white')
}}
shadowMap
pixelRatio={window.devicePixelRatio}
onPointerMissed={() => studio.__experimental_setSelection([])}
>
<EditorScene />
</Canvas>
</CanvasWrapper>
</>
) : null}
</Wrapper>
<StyleSheetManager disableVendorPrefixes>
<>
<GlobalStyle />
<Wrapper id="theatre-plugin-r3f-root" visible={true}>
{/* <Toolbar /> */}
{sceneSnapshot ? (
<>
<CanvasWrapper>
<Canvas
// @ts-ignore
colorManagement
camera={initialEditorCamera}
onCreated={({gl}) => {
gl.setClearColor('white')
}}
shadowMap
dpr={[1, 2]}
fog={'red'}
onPointerMissed={() =>
studio.__experimental_setSelection([])
}
>
<EditorScene />
</Canvas>
</CanvasWrapper>
</>
) : null}
</Wrapper>
</>
</StyleSheetManager>
</root.div>
)
}

View file

@ -1,5 +1,4 @@
import type {VFC} from 'react'
import {useState} from 'react'
import React from 'react'
import TransformControlsModeSelect from './TransformControlsModeSelect'
import {useEditorStore} from '../../store'
@ -13,35 +12,8 @@ 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;
`
@ -59,111 +31,102 @@ const Toolbar: VFC = () => {
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()
<>
<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
let focusObject
if (selected) {
focusObject =
useEditorStore.getState().editablesSnapshot![selected]
.proxyObject
}
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
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()
const selected = getSelected()
let proxyObject
let proxyObject
if (selected) {
proxyObject =
useEditorStore.getState().editablesSnapshot![selected]
.proxyObject
if (selected) {
proxyObject =
useEditorStore.getState().editablesSnapshot![selected]
.proxyObject
if (proxyObject && camera) {
const direction = new Vector3()
const position = camera.position.clone()
if (proxyObject && camera) {
const direction = new Vector3()
const position = camera.position.clone()
camera.getWorldDirection(direction)
proxyObject.position.set(0, 0, 0)
proxyObject.lookAt(direction)
camera.getWorldDirection(direction)
proxyObject.position.set(0, 0, 0)
proxyObject.lookAt(direction)
proxyObject.parent!.worldToLocal(position)
proxyObject.position.copy(position)
proxyObject.parent!.worldToLocal(position)
proxyObject.position.copy(position)
proxyObject.updateMatrix()
}
}
}}
/>
</ToolGroup>
</Tools>
</TopRow>
</Container>
</PortalContext.Provider>
proxyObject.updateMatrix()
}
}
}}
/>
</ToolGroup>
</>
)
}

View file

@ -13,42 +13,38 @@ interface OptionButtonProps<Option> {
onClick: () => void
}
const _TooltipRef = styled(TooltipReference)<{selected: boolean}>`
const TheButton = 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-size: 11px;
line-height: 1.25em;
font-weight: 600;
height: 1.75rem;
padding-left: 0.5rem;
padding-right: 0.5rem;
border: 0 transparent;
height: 24px;
padding-left: 0.5em;
padding-right: 0.5em;
border: 1px solid #22222238;
&: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;
}
border-radius: 2px;
&: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);'};
color: #e6e6e5;
background-color: rgba(0, 0, 0, 0.1);
&:hover {
background-color: ${({selected}) =>
selected ? 'rgba(6, 78, 59, 1)' : 'rgba(229, 231, 235, 1);'};
background-color: rgba(0, 0, 0, 0.5);
color: white;
}
&.selected,
&.selected:hover {
color: white;
background-color: rgba(0, 0, 0, 0.5);
}
`
@ -62,15 +58,16 @@ function OptionButton<Option>({
const tooltip = useTooltipState()
return (
<>
<_TooltipRef
<TheButton
{...tooltip}
forwardedAs={Button}
selected={option === value}
className={option === value ? 'selected' : undefined}
aria-label={label}
onClick={onClick}
>
{icon}
</_TooltipRef>
</TheButton>
<Tooltip {...tooltip}>{label}</Tooltip>
</>
)
@ -89,6 +86,7 @@ interface CompactModeSelectProps<Option> {
const Container = styled(Group)`
display: flex;
gap: 2px;
`
const CompactModeSelect = <Option extends string | number>({

View file

@ -5,45 +5,48 @@ import {Button} from 'reakit'
import type {IconType} from 'react-icons'
import {Tooltip, TooltipReference, useTooltipState} from './Tooltip'
import styled from 'styled-components'
import {transparentize} from 'polished'
export interface IconButtonProps extends Exclude<ButtonProps, 'children'> {
icon: ReactElement<IconType>
label: string
}
const _TooltipRef = styled(TooltipReference)`
const TheButton = 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-size: 11px;
line-height: 1.25em;
font-weight: 600;
height: 1.75rem;
padding-left: 0.5rem;
padding-right: 0.5rem;
height: 24px;
padding-left: 0.5em;
padding-right: 0.5em;
color: #e6e6e5;
background-color: #313131ba;
border: 0 transparent;
&:first-child {
border-top-left-radius: 0.25rem;
border-bottom-left-radius: 0.25rem;
border-top-left-radius: 3px;
border-bottom-left-radius: 3px;
}
&:last-child {
border-top-right-radius: 0.25rem;
border-bottom-right-radius: 0.25rem;
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
}
&:focus {
outline: none;
}
color: rgba(55, 65, 81, 1);
background-color: rgba(243, 244, 246, 1);
color: #e6e6e5;
background-color: #313131;
&:hover {
background-color: rgba(229, 231, 235, 1);
background-color: ${transparentize(0.5, '#313131')};
}
border: 0 transparent;
@ -53,14 +56,14 @@ const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(
const tooltip = useTooltipState()
return (
<>
<_TooltipRef
<TheButton
{...props}
{...tooltip}
forwardedAs={Button}
aria-label={label}
>
{icon}
</_TooltipRef>
</TheButton>
<Tooltip {...tooltip}>{label}</Tooltip>
</>
)

View file

@ -1,24 +1,22 @@
import type {VFC} from 'react'
import React from 'react'
import {Tooltip as TooltipImpl, TooltipReference, useTooltipState} from 'reakit'
import {transparentize} from 'polished'
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;
padding: 3px 5px;
font-size: 0.875rem;
line-height: 1.25rem;
border-radius: 0.125rem;
background-color: rgba(55, 65, 81, 1);
font-size: 11px;
line-height: 1.25em;
border-radius: 2px;
background-color: ${transparentize(0.5, '#313131')};
color: white;
pointer-events: none;
font-weight: 500;
`
export const Tooltip: VFC<TooltipProps> = ({className, ...props}) => (

View file

@ -8,8 +8,16 @@ export type {EditorHelperProps} from './components/EditorHelper'
export {default as editable} from './components/editable'
export {bindToCanvas} from './store'
export type {EditableState, BindFunction} from './store'
import studio from '@theatre/studio'
import Toolbar from './components/Toolbar/Toolbar'
if (process.env.NODE_ENV === 'development') {
studio.extend({
id: '@theatre/plugin-r3f',
globalToolbar: {
component: Toolbar,
},
})
const editorRoot = document.createElement('div')
document.body.appendChild(editorRoot)

View file

@ -306,6 +306,23 @@ const editorSheetObjectConfig = types.compound({
},
{as: 'menu', label: 'Shading'},
),
mode: types.stringLiteral(
'translate',
{
translate: 'Translate',
rotate: 'Rotate',
scale: 'Scale',
},
{as: 'switch', label: 'Mode'},
),
space: types.stringLiteral(
'world',
{
local: 'Local',
world: 'World',
},
{as: 'switch', label: 'Space'},
),
},
{label: 'Viewport Config'},
),