Make it possible to move the playhead to an exact position (#92)

This commit is contained in:
Fulop 2022-03-03 09:54:54 +01:00 committed by GitHub
parent fb7467862b
commit 3f2a9032f1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 138 additions and 24 deletions

View file

@ -16,6 +16,9 @@ import {
useLockFrameStampPosition, useLockFrameStampPosition,
} from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider' } from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider'
import {pointerEventsAutoInNormalMode} from '@theatre/studio/css' import {pointerEventsAutoInNormalMode} from '@theatre/studio/css'
import usePopover from '@theatre/studio/uiComponents/Popover/usePopover'
import BasicPopover from '@theatre/studio/uiComponents/Popover/BasicPopover'
import PlayheadPositionPopover from './PlayheadPositionPopover'
const Container = styled.div<{isVisible: boolean}>` const Container = styled.div<{isVisible: boolean}>`
--thumbColor: #00e0ff; --thumbColor: #00e0ff;
@ -150,6 +153,20 @@ const Playhead: React.FC<{layoutP: Pointer<SequenceEditorPanelLayout>}> = ({
}) => { }) => {
const [thumbRef, thumbNode] = useRefAndState<HTMLElement | null>(null) const [thumbRef, thumbNode] = useRefAndState<HTMLElement | null>(null)
const [popoverNode, openPopover, closePopover, isPopoverOpen] = usePopover(
{},
() => {
return (
<BasicPopover>
<PlayheadPositionPopover
layoutP={layoutP}
onRequestClose={closePopover}
/>
</BasicPopover>
)
},
)
const gestureHandlers = useMemo((): Parameters<typeof useDrag>[1] => { const gestureHandlers = useMemo((): Parameters<typeof useDrag>[1] => {
const setIsSeeking = val(layoutP.seeker.setIsSeeking) const setIsSeeking = val(layoutP.seeker.setIsSeeking)
@ -215,32 +232,38 @@ const Playhead: React.FC<{layoutP: Pointer<SequenceEditorPanelLayout>}> = ({
posInClippedSpace <= val(layoutP.clippedSpace.width) posInClippedSpace <= val(layoutP.clippedSpace.width)
return ( return (
<Container <>
isVisible={isVisible} {popoverNode}
style={{transform: `translate3d(${posInClippedSpace}px, 0, 0)`}} <Container
className={isSeeking ? 'seeking' : ''} isVisible={isVisible}
{...{[attributeNameThatLocksFramestamp]: 'hide'}} style={{transform: `translate3d(${posInClippedSpace}px, 0, 0)`}}
>
<Thumb
ref={thumbRef as $IntentionalAny}
data-pos={posInUnitSpace.toFixed(3)}
>
<RoomToClick room={8} />
<Squinch />
<Tooltip>
{sequence.positionFormatter.formatForPlayhead(
sequence.closestGridPosition(posInUnitSpace),
)}
</Tooltip>
</Thumb>
<Rod
data-pos={posInUnitSpace.toFixed(3)}
className={isSeeking ? 'seeking' : ''} className={isSeeking ? 'seeking' : ''}
/> {...{[attributeNameThatLocksFramestamp]: 'hide'}}
</Container> >
<Thumb
ref={thumbRef as $IntentionalAny}
data-pos={posInUnitSpace.toFixed(3)}
onClick={(e) => {
openPopover(e, thumbNode!)
}}
>
<RoomToClick room={8} />
<Squinch />
<Tooltip>
{sequence.positionFormatter.formatForPlayhead(
sequence.closestGridPosition(posInUnitSpace),
)}
</Tooltip>
</Thumb>
<Rod
data-pos={posInUnitSpace.toFixed(3)}
className={isSeeking ? 'seeking' : ''}
/>
</Container>
</>
) )
}, [layoutP]) }, [layoutP, thumbRef, popoverNode])
} }
export default Playhead export default Playhead

View file

@ -0,0 +1,91 @@
import styled from 'styled-components'
import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout'
import {usePrism} from '@theatre/react'
import type {BasicNumberInputNudgeFn} from '@theatre/studio/uiComponents/form/BasicNumberInput'
import BasicNumberInput from '@theatre/studio/uiComponents/form/BasicNumberInput'
import {propNameText} from '@theatre/studio/panels/DetailPanel/propEditors/utils/SingleRowPropEditor'
import {useLayoutEffect, useMemo, useRef} from 'react'
import React from 'react'
import {val} from '@theatre/dataverse'
import type {Pointer} from '@theatre/dataverse'
import clamp from 'lodash-es/clamp'
const greaterThanZero = (v: number) => isFinite(v) && v > 0
const Container = styled.div`
display: flex;
gap: 8px;
padding: 4px 8px;
height: 28px;
align-items: center;
`
const Label = styled.div`
${propNameText};
white-space: nowrap;
`
const nudge: BasicNumberInputNudgeFn = ({deltaX}) => deltaX * 0.25
const PlayheadPositionPopover: React.FC<{
layoutP: Pointer<SequenceEditorPanelLayout>
/**
* Called when user hits enter/escape
*/
onRequestClose: () => void
}> = ({layoutP, onRequestClose}) => {
const sheet = val(layoutP.sheet)
const sequence = sheet.getSequence()
const fns = useMemo(() => {
let tempPosition: number | undefined
const originalPosition = sequence.position
return {
temporarilySetValue(newPosition: number): void {
if (tempPosition) {
tempPosition = undefined
}
tempPosition = clamp(newPosition, 0, sequence.length)
sequence.position = tempPosition
},
discardTemporaryValue(): void {
if (tempPosition) {
tempPosition = undefined
sequence.position = originalPosition
}
},
permenantlySetValue(newPosition: number): void {
if (tempPosition) {
tempPosition = undefined
}
sequence.position = clamp(newPosition, 0, sequence.length)
},
}
}, [layoutP, sequence])
const inputRef = useRef<HTMLInputElement>(null)
useLayoutEffect(() => {
inputRef.current!.focus()
}, [])
return usePrism(() => {
const sequence = sheet.getSequence()
return (
<Container>
<Label>Playhead position</Label>
<BasicNumberInput
value={sequence.position}
{...fns}
isValid={greaterThanZero}
inputRef={inputRef}
onBlur={onRequestClose}
nudge={nudge}
/>
</Container>
)
}, [sheet, fns, inputRef])
}
export default PlayheadPositionPopover