All prop sequencing (#48)

This commit is contained in:
cory-glooh 2021-11-02 13:50:08 +00:00 committed by GitHub
parent 52f65af689
commit 4a65c6e91c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 280 additions and 97 deletions

View file

@ -3,14 +3,27 @@ import type {UseDragOpts} from './useDrag'
import useDrag from './useDrag'
import React, {useLayoutEffect, useMemo, useState} from 'react'
import type {IProject, ISheet} from '@theatre/core'
import {onChange} from '@theatre/core'
import {onChange, types} from '@theatre/core'
import type {IScrub, IStudio} from '@theatre/studio'
studio.initialize()
studio.initialize({usePersistentStorage: false})
const textInterpolate = (left: string, right: string, progression: number) => {
if (!left || right.startsWith(left)) {
const length = Math.floor(
Math.max(0, (right.length - left.length) * progression),
)
return left + right.slice(left.length, left.length + length)
}
return left
}
const boxObjectConfig = {
x: 0,
y: 0,
test: types.string('Hello?', {interpolate: textInterpolate}),
testLiteral: types.stringLiteral('a', {a: 'Option A', b: 'Option B'}),
bool: types.boolean(false),
x: types.number(200),
y: types.number(200),
}
const Box: React.FC<{
@ -23,11 +36,17 @@ const Box: React.FC<{
const isSelected = selection.includes(obj)
const [pos, setPos] = useState<{x: number; y: number}>({x: 0, y: 0})
const [state, setState] = useState<{
x: number
y: number
test: string
testLiteral: string
bool: boolean
}>(obj.value)
useLayoutEffect(() => {
const unsubscribeFromChanges = onChange(obj.props, (newValues) => {
setPos(newValues)
setState(newValues)
})
return unsubscribeFromChanges
}, [id])
@ -50,7 +69,13 @@ const Box: React.FC<{
firstOnDragCalled = true
}
scrub!.capture(({set}) => {
set(obj.props, {x: x + initial.x, y: y + initial.y})
set(obj.props, {
x: x + initial.x,
y: y + initial.y,
test: initial.test,
testLiteral: initial.testLiteral,
bool: initial.bool,
})
})
},
onDragEnd(dragHappened) {
@ -73,16 +98,20 @@ const Box: React.FC<{
}}
ref={setDivRef}
style={{
width: 100,
height: 100,
background: 'gray',
width: 300,
height: 300,
color: 'white',
position: 'absolute',
left: pos.x + 'px',
top: pos.y + 'px',
left: state.x + 'px',
top: state.y + 'px',
boxSizing: 'border-box',
border: isSelected ? '1px solid #5a92fa' : '1px solid transparent',
border: isSelected ? '1px solid #5a92fa' : '1px solid white',
}}
></div>
>
<pre style={{margin: 0, padding: '1rem'}}>
{JSON.stringify(state, null, 4)}
</pre>
</div>
)
}
@ -109,7 +138,7 @@ export const Scene: React.FC<{project: IProject}> = ({project}) => {
right: '0',
top: 0,
bottom: '0',
background: 'black',
background: '#333',
}}
>
<button

View file

@ -39,7 +39,7 @@ export type TrackData = BasicKeyframedTrack
export type Keyframe = {
id: KeyframeId
value: number
value: unknown
position: number
handles: [leftX: number, leftY: number, rightX: number, rightY: number]
connectedRight: boolean

View file

@ -8,9 +8,9 @@ import type {
import {sanitizeCompoundProps} from './internals'
import {propTypeSymbol} from './internals'
const validateCommonOpts = (
const validateCommonOpts = <T>(
fnCallSignature: string,
opts?: PropTypeConfigOpts,
opts?: PropTypeConfigOpts<T>,
) => {
if (process.env.NODE_ENV !== 'production') {
if (opts === undefined) return
@ -70,9 +70,10 @@ const validateCommonOpts = (
*/
export const compound = <Props extends IShorthandCompoundProps>(
props: Props,
opts?: PropTypeConfigOpts,
opts?: PropTypeConfigOpts<Props>,
): PropTypeConfig_Compound<
ShorthandCompoundPropsToLonghandCompoundProps<Props>
ShorthandCompoundPropsToLonghandCompoundProps<Props>,
Props
> => {
validateCommonOpts('t.compound(props, opts)', opts)
return {
@ -81,6 +82,9 @@ export const compound = <Props extends IShorthandCompoundProps>(
valueType: null as $IntentionalAny,
[propTypeSymbol]: 'TheatrePropType',
label: opts?.label,
sanitize: opts?.sanitize,
interpolate: opts?.interpolate,
isScalar: false,
}
}
@ -131,7 +135,7 @@ export const number = (
nudgeFn?: PropTypeConfig_Number['nudgeFn']
range?: PropTypeConfig_Number['range']
nudgeMultiplier?: number
} & PropTypeConfigOpts,
} & PropTypeConfigOpts<number>,
): PropTypeConfig_Number => {
if (process.env.NODE_ENV !== 'production') {
validateCommonOpts('t.number(defaultValue, opts)', opts)
@ -202,6 +206,15 @@ export const number = (
nudgeFn: opts?.nudgeFn ?? defaultNumberNudgeFn,
nudgeMultiplier:
typeof opts?.nudgeMultiplier === 'number' ? opts.nudgeMultiplier : 1,
isScalar: true,
sanitize(value) {
if (opts?.sanitize) return opts.sanitize(value)
return typeof value === 'number' ? value : undefined
},
interpolate(left, right, progression) {
if (opts?.interpolate) return opts.interpolate(left, right, progression)
return left + progression * (right - left)
},
}
}
@ -227,7 +240,7 @@ export const number = (
*/
export const boolean = (
defaultValue: boolean,
opts?: PropTypeConfigOpts,
opts?: PropTypeConfigOpts<boolean>,
): PropTypeConfig_Boolean => {
if (process.env.NODE_ENV !== 'production') {
validateCommonOpts('t.boolean(defaultValue, opts)', opts)
@ -245,6 +258,15 @@ export const boolean = (
valueType: null as $IntentionalAny,
[propTypeSymbol]: 'TheatrePropType',
label: opts?.label,
isScalar: false,
sanitize(value: unknown) {
if (opts?.sanitize) return opts.sanitize(value)
return typeof value === 'boolean' ? value : undefined
},
interpolate(left, right, progression) {
if (opts?.interpolate) return opts.interpolate(left, right, progression)
return left
},
}
}
@ -271,7 +293,7 @@ export const boolean = (
*/
export const string = (
defaultValue: string,
opts?: PropTypeConfigOpts,
opts?: PropTypeConfigOpts<string>,
): PropTypeConfig_String => {
if (process.env.NODE_ENV !== 'production') {
validateCommonOpts('t.string(defaultValue, opts)', opts)
@ -289,6 +311,12 @@ export const string = (
valueType: null as $IntentionalAny,
[propTypeSymbol]: 'TheatrePropType',
label: opts?.label,
isScalar: false,
sanitize(value: unknown) {
if (opts?.sanitize) return opts.sanitize(value)
return typeof value === 'string' ? value : undefined
},
interpolate: opts?.interpolate,
}
}
@ -326,7 +354,9 @@ export function stringLiteral<Opts extends {[key in string]: string}>(
/**
* opts.as Determines if editor is shown as a menu or a switch. Either 'menu' or 'switch'. Default: 'menu'
*/
opts?: {as?: 'menu' | 'switch'} & PropTypeConfigOpts,
opts?: {as?: 'menu' | 'switch'} & PropTypeConfigOpts<
Extract<keyof Opts, string>
>,
): PropTypeConfig_StringLiteral<Extract<keyof Opts, string>> {
return {
type: 'stringLiteral',
@ -336,13 +366,30 @@ export function stringLiteral<Opts extends {[key in string]: string}>(
valueType: null as $IntentionalAny,
as: opts?.as ?? 'menu',
label: opts?.label,
isScalar: false,
sanitize(value: unknown) {
if (opts?.sanitize) return opts.sanitize(value)
return typeof value === 'string' && Object.keys(options).includes(value)
? (value as Extract<keyof Opts, string>)
: undefined
},
interpolate(left, right, progression) {
if (opts?.interpolate) return opts.interpolate(left, right, progression)
return left
},
}
}
interface IBasePropType<ValueType> {
export type Sanitizer<T> = (value: unknown) => T | undefined
export type Interpolator<T> = (left: T, right: T, progression: number) => T
interface IBasePropType<ValueType, PropTypes = ValueType> {
valueType: ValueType
[propTypeSymbol]: 'TheatrePropType'
label: string | undefined
isScalar: boolean
sanitize?: Sanitizer<PropTypes>
interpolate?: Interpolator<PropTypes>
}
export interface PropTypeConfig_Number extends IBasePropType<number> {
@ -381,9 +428,18 @@ export interface PropTypeConfig_Boolean extends IBasePropType<boolean> {
default: boolean
}
export interface PropTypeConfigOpts {
label?: string
export interface PropTypeConfig_Color<ColorObject>
extends IBasePropType<ColorObject> {
type: 'color'
default: ColorObject
}
export interface PropTypeConfigOpts<ValueType> {
label?: string
sanitize?: Sanitizer<ValueType>
interpolate?: Interpolator<ValueType>
}
export interface PropTypeConfig_String extends IBasePropType<string> {
type: 'string'
default: string
@ -400,8 +456,13 @@ export interface PropTypeConfig_StringLiteral<T extends string>
/**
*
*/
export interface PropTypeConfig_Compound<Props extends IValidCompoundProps>
extends IBasePropType<{[K in keyof Props]: Props[K]['valueType']}> {
export interface PropTypeConfig_Compound<
Props extends IValidCompoundProps,
PropTypes = Props,
> extends IBasePropType<
{[K in keyof Props]: Props[K]['valueType']},
PropTypes
> {
type: 'compound'
props: Record<string, PropTypeConfig>
}

View file

@ -8,10 +8,14 @@ import {ConstantDerivation, prism, val} from '@theatre/dataverse'
import logger from '@theatre/shared/logger'
import UnitBezier from 'timing-function/lib/UnitBezier'
export type KeyframeValueAtTime =
| {left: unknown; right?: unknown; progression: number}
| undefined
export default function trackValueAtTime(
trackP: Pointer<TrackData | undefined>,
timeD: IDerivation<number>,
): IDerivation<unknown> {
): IDerivation<KeyframeValueAtTime> {
return prism(() => {
const track = val(trackP)
const driverD = prism.memo(
@ -20,7 +24,7 @@ export default function trackValueAtTime(
if (!track) {
return new ConstantDerivation(undefined)
} else if (track.type === 'BasicKeyframedTrack') {
return trackValueAtTime_basicKeyframedTrack(track, timeD)
return trackValueAtTime_keyframedTrack(track, timeD)
} else {
logger.error(`Track type not yet supported.`)
return new ConstantDerivation(undefined)
@ -37,14 +41,15 @@ type IStartedState = {
started: true
validFrom: number
validTo: number
der: IDerivation<unknown>
der: IDerivation<KeyframeValueAtTime>
}
type IState = {started: false} | IStartedState
function trackValueAtTime_basicKeyframedTrack(
function trackValueAtTime_keyframedTrack(
track: BasicKeyframedTrack,
timeD: IDerivation<number>,
): IDerivation<unknown> {
): IDerivation<KeyframeValueAtTime> {
return prism(() => {
let stateRef = prism.ref<IState>('state', {started: false})
let state = stateRef.current
@ -52,7 +57,7 @@ function trackValueAtTime_basicKeyframedTrack(
const time = timeD.getValue()
if (!state.started || time < state.validFrom || state.validTo <= time) {
stateRef.current = state = pp(timeD, track)
stateRef.current = state = updateState(timeD, track)
}
return state.der.getValue()
@ -61,7 +66,7 @@ function trackValueAtTime_basicKeyframedTrack(
const undefinedConstD = new ConstantDerivation(undefined)
const pp = (
const updateState = (
progressionD: IDerivation<number>,
track: BasicKeyframedTrack,
): IStartedState => {
@ -138,7 +143,7 @@ const states = {
started: true,
validFrom: -Infinity,
validTo: kf.position,
der: new ConstantDerivation(kf.value),
der: new ConstantDerivation({left: kf.value, progression: 0}),
}
},
lastKeyframe(kf: Keyframe): IStartedState {
@ -146,7 +151,7 @@ const states = {
started: true,
validFrom: kf.position,
validTo: Infinity,
der: new ConstantDerivation(kf.value),
der: new ConstantDerivation({left: kf.value, progression: 0}),
}
},
between(
@ -159,7 +164,7 @@ const states = {
started: true,
validFrom: left.position,
validTo: right.position,
der: new ConstantDerivation(left.value),
der: new ConstantDerivation({left: left.value, progression: 0}),
}
}
@ -182,7 +187,11 @@ const states = {
)
const valueProgression = solver.solveSimple(progression)
return left.value + valueProgression * (right.value - left.value)
return {
left: left.value,
right: right.value,
progression: valueProgression,
}
})
return {

View file

@ -1,3 +1,4 @@
import type {KeyframeValueAtTime} from '@theatre/core/sequences/trackValueAtTime'
import trackValueAtTime from '@theatre/core/sequences/trackValueAtTime'
import type Sheet from '@theatre/core/sheets/Sheet'
import type {SheetObjectAddress} from '@theatre/shared/utils/addresses'
@ -21,6 +22,8 @@ import type {
import {Atom, getPointerParts, pointer, prism, val} from '@theatre/dataverse'
import type SheetObjectTemplate from './SheetObjectTemplate'
import TheatreSheetObject from './TheatreSheetObject'
import {get} from 'lodash-es'
import type {Interpolator, PropTypeConfig} from '@theatre/core/propTypes'
// type Everything = {
// final: SerializableMap
@ -153,7 +156,32 @@ export default class SheetObject implements IdentityDerivationProvider {
const derivation = this._trackIdToDerivation(trackId)
const updateSequenceValueFromItsDerivation = () => {
valsAtom.setIn(pathToProp, derivation.getValue())
const propConfig = get(this.template.config.props, pathToProp) as
| PropTypeConfig
| undefined
const value: KeyframeValueAtTime = derivation.getValue()
if (!value) return valsAtom.setIn(pathToProp, value)
if (value.right === undefined)
return valsAtom.setIn(pathToProp, value.left)
if (propConfig?.interpolate) {
const interpolate =
propConfig.interpolate as Interpolator<unknown>
return valsAtom.setIn(
pathToProp,
interpolate(value.left, value.right, value.progression),
)
}
if (
typeof value.left === 'number' &&
typeof value.right === 'number'
) {
//@Because tests don't provide prop config, and fail if this is omitted.
return valsAtom.setIn(
pathToProp,
value.left + value.progression * (value.right - value.left),
)
}
valsAtom.setIn(pathToProp, value.left)
}
const untap = derivation
.changesWithoutValues()
@ -177,12 +205,11 @@ export default class SheetObject implements IdentityDerivationProvider {
protected _trackIdToDerivation(
trackId: SequenceTrackId,
): IDerivation<unknown> {
): IDerivation<KeyframeValueAtTime | undefined> {
const trackP =
this.template.project.pointers.historic.sheetsById[this.address.sheetId]
.sequence.tracksByObject[this.address.objectKey].trackData[trackId]
const timeD = this.sheet.getSequence().positionDerivation
return trackValueAtTime(trackP, timeD)
}

View file

@ -24,6 +24,10 @@ import set from 'lodash-es/set'
import getPropDefaultsOfSheetObject from './getPropDefaultsOfSheetObject'
import SheetObject from './SheetObject'
import logger from '@theatre/shared/logger'
import type {
PropTypeConfig,
PropTypeConfig_Compound,
} from '@theatre/core/propTypes'
export type IPropPathToTrackIdTree = {
[key in string]?: SequenceTrackId | IPropPathToTrackIdTree
@ -32,7 +36,9 @@ export type IPropPathToTrackIdTree = {
export default class SheetObjectTemplate {
readonly address: WithoutSheetInstance<SheetObjectAddress>
readonly type: 'Theatre_SheetObjectTemplate' = 'Theatre_SheetObjectTemplate'
protected _config: Atom<SheetObjectConfig<$IntentionalAny>>
protected _config: Atom<
SheetObjectConfig<PropTypeConfig_Compound<$IntentionalAny>>
>
readonly _cache = new SimpleCache()
readonly project: Project
@ -87,13 +93,14 @@ export default class SheetObjectTemplate {
this.address.sheetId
]
return (
const value =
val(
pointerToSheetState.staticOverrides.byObject[
this.address.objectKey
],
) || {}
)
return value
}),
)
}
@ -139,8 +146,17 @@ export default class SheetObjectTemplate {
)
continue
}
const propConfig = get(this.config.props, pathToProp) as
| PropTypeConfig
| undefined
const defaultValue = get(defaults, pathToProp)
if (typeof defaultValue !== 'number') {
if (
typeof defaultValue !== 'number' &&
(!propConfig?.sanitize || !propConfig.interpolate)
) {
//@checking defaultValue because tests don't provide prop config, and fail if this is omitted.
continue
}

View file

@ -46,7 +46,13 @@ declare module 'propose' {
// export default inspect
// }
declare module 'timing-function/lib/UnitBezier'
declare module 'timing-function/lib/UnitBezier' {
export default class UnitBezier {
constructor(p1x: numbe, p1y: number, p2x: number, p2y: number)
solve(progression: number, epsilon: number)
solveSimple(progression: number)
}
}
declare module 'clean-webpack-plugin'
declare module 'webpack-notifier'
declare module 'case-sensitive-paths-webpack-plugin'

View file

@ -29,6 +29,7 @@ import {persistStateOfStudio} from './persistStateOfStudio'
import {isSheetObject} from '@theatre/shared/instanceTypes'
import type {OnDiskState} from '@theatre/core/projects/store/storeTypes'
import {generateDiskStateRevision} from './generateDiskStateRevision'
import type {PropTypeConfig} from '@theatre/core/propTypes'
export type Drafts = {
historic: Draft<StudioHistoricState>
@ -146,6 +147,7 @@ export default class StudioStore {
if (typeof v === 'undefined' || v === null) {
return
}
const propAddress = {...root.address, pathToProp}
const trackId = get(
@ -154,6 +156,12 @@ export default class StudioStore {
) as $FixMe as SequenceTrackId | undefined
if (typeof trackId === 'string') {
const propConfig = get(
root.template.config.props,
pathToProp,
) as PropTypeConfig | undefined
if (propConfig?.sanitize) v = propConfig.sanitize(v)
const seq = root.sheet.getSequence()
seq.position = seq.closestGridPosition(seq.position)
stateEditors.coreByProject.historic.sheetsById.sequence.setKeyframeAtPosition(

View file

@ -16,10 +16,10 @@ export const getPropTypeByPointer = (
pointerToProp: SheetObject['propsP'],
obj: SheetObject,
): PropTypeConfig => {
const rootConf = obj.template.config.props
const rootConf = obj.template.config
const p = getPointerParts(pointerToProp).path
let conf: PropTypeConfig = rootConf as PropTypeConfig
let conf = rootConf as PropTypeConfig
while (p.length !== 0) {
const key = p.shift()

View file

@ -248,8 +248,10 @@ export function useEditingToolsForPrimitiveProp<
callback: () => {
getStudio()!.transaction(({stateEditors}) => {
const propAddress = {...obj.address, pathToProp}
stateEditors.coreByProject.historic.sheetsById.sequence.setPrimitivePropAsSequenced(
propAddress,
propConfig,
)
})
},
@ -309,5 +311,5 @@ type Shade =
| 'Sequened_NotBeingInterpolated'
function isPropConfSequencable(conf: PropTypeConfig): boolean {
return conf.type === 'number'
return conf.type === 'number' || (!!conf.sanitize && !!conf.interpolate)
}

View file

@ -52,7 +52,7 @@ const IconContainer = styled.button<{
? graphEditorColors[props.graphEditorColor].iconColor
: nextPrevCursorsTheme.offColor};
&:hover {
&:not([disabled]):hover {
color: white;
}
`
@ -129,6 +129,9 @@ const PrimitivePropRow: React.FC<{
}, [leaf])
const label = leaf.pathToProp[leaf.pathToProp.length - 1]
const isSelectable =
leaf.propConf.type !== 'boolean' && leaf.propConf.type !== 'stringLiteral'
return (
<Container depth={leaf.depth}>
<Head
@ -144,6 +147,8 @@ const PrimitivePropRow: React.FC<{
onClick={toggleSelect}
isSelected={isSelected === true}
graphEditorColor={possibleColor ?? '1'}
style={{opacity: isSelectable ? 1 : 0.25}}
disabled={!isSelectable}
>
<GraphIcon />
</IconContainer>

View file

@ -5,7 +5,7 @@ import {usePrism} from '@theatre/react'
import type {Pointer} from '@theatre/dataverse'
import {val} from '@theatre/dataverse'
import React from 'react'
import BasicKeyframedTrack from './BasicKeyframedTrack/BasicKeyframedTrack'
import KeyframedTrack from './BasicKeyframedTrack/BasicKeyframedTrack'
import Row from './Row'
const PrimitivePropRow: React.FC<{
@ -30,12 +30,9 @@ const PrimitivePropRow: React.FC<{
return <Row leaf={leaf} node={<div />}></Row>
} else {
const node = (
<BasicKeyframedTrack
layoutP={layoutP}
trackData={trackData}
leaf={leaf}
/>
<KeyframedTrack layoutP={layoutP} trackData={trackData} leaf={leaf} />
)
return <Row leaf={leaf} node={node}></Row>
}
}, [leaf, layoutP])

View file

@ -115,13 +115,14 @@ function calculateExtremums(keyframes: Keyframe[]): Extremums {
}
keyframes.forEach((cur, i) => {
check(cur.value)
const curVal = typeof cur.value === 'number' ? cur.value : 0
check(curVal)
if (!cur.connectedRight) return
const next = keyframes[i + 1]
if (!next) return
const diff = next.value - cur.value
check(cur.value + cur.handles[3] * diff)
check(cur.value + next.handles[1] * diff)
const diff = (typeof next.value === 'number' ? next.value : 1) - curVal
check(curVal + cur.handles[3] * diff)
check(curVal + next.handles[1] * diff)
})
return [min, max]

View file

@ -19,21 +19,16 @@ const Curve: React.FC<IProps> = (props) => {
const cur = trackData.keyframes[index]
const next = trackData.keyframes[index + 1]
const handles = [
cur.handles[2],
cur.handles[3],
next.handles[0],
next.handles[1],
]
const connectorLengthInUnitSpace = next.position - cur.position
const [nodeRef, node] = useRefAndState<SVGPathElement | null>(null)
const [contextMenu] = useConnectorContextMenu(node, props)
const leftYInExtremumSpace = props.extremumSpace.fromValueSpace(cur.value)
const rightYInExtremumSpace = props.extremumSpace.fromValueSpace(next.value)
const curValue = typeof cur.value === 'number' ? cur.value : 0
const nextValue = typeof next.value === 'number' ? next.value : 1
const leftYInExtremumSpace = props.extremumSpace.fromValueSpace(curValue)
const rightYInExtremumSpace = props.extremumSpace.fromValueSpace(nextValue)
const heightInExtremumSpace = rightYInExtremumSpace - leftYInExtremumSpace

View file

@ -72,20 +72,23 @@ const CurveHandle: React.FC<IProps> = (props) => {
// debugger
}
const value = cur.value + (next.value - cur.value) * valInDiffSpace
const curValue = typeof cur.value === 'number' ? cur.value : 0
const nextValue = typeof next.value === 'number' ? next.value : 1
const value = curValue + (nextValue - curValue) * valInDiffSpace
const valInExtremumSpace = props.extremumSpace.fromValueSpace(value)
const heightInExtremumSpace =
valInExtremumSpace -
props.extremumSpace.fromValueSpace(
props.which === 'left' ? cur.value : next.value,
props.which === 'left' ? curValue : nextValue,
)
const lineTransform = transformBox(
props.which === 'left' ? cur.position : next.position,
props.extremumSpace.fromValueSpace(
props.which === 'left' ? cur.value : next.value,
props.which === 'left' ? curValue : nextValue,
),
posInUnitSpace - (props.which === 'left' ? cur.position : next.position),
heightInExtremumSpace,
@ -165,7 +168,9 @@ function useOurDrags(node: SVGCircleElement | null, props: IProps): void {
const dYInValueSpace =
propsAtStartOfDrag.extremumSpace.deltaToValueSpace(dYInExtremumSpace)
const dyInKeyframeDiffSpace = dYInValueSpace / (next.value - cur.value)
const curValue = typeof cur.value === 'number' ? cur.value : 0
const nextValue = typeof next.value === 'number' ? next.value : 1
const dyInKeyframeDiffSpace = dYInValueSpace / (nextValue - curValue)
if (propsAtStartOfDrag.which === 'left') {
const handleX = clamp(cur.handles[2] + dPosInKeyframeDiffSpace, 0, 1)

View file

@ -59,9 +59,12 @@ const Dot: React.FC<IProps> = (props) => {
const next = trackData.keyframes[index + 1]
const [contextMenu] = useKeyframeContextMenu(node, props)
const isDragging = useDragKeyframe(node, props)
const isDragging =
typeof cur.value === 'number' ? useDragKeyframe(node, props) : false
const cyInExtremumSpace = props.extremumSpace.fromValueSpace(cur.value)
const cyInExtremumSpace = props.extremumSpace.fromValueSpace(
typeof cur.value === 'number' ? cur.value : 0,
)
return (
<>
@ -140,7 +143,7 @@ function useDragKeyframe(
const cur: Keyframe = {
...original,
position: original.position + deltaPos,
value: original.value + dYInValueSpace,
value: (original.value as number) + dYInValueSpace,
handles: [...original.handles],
}
updatedKeyframes.push(cur)
@ -149,29 +152,41 @@ function useDragKeyframe(
const prev =
propsAtStartOfDrag.trackData.keyframes[propsAtStartOfDrag.index - 1]
if (prev && Math.abs(original.value - prev.value) > 0) {
const newPrev: Keyframe = {...prev, handles: [...prev.handles]}
if (
prev &&
Math.abs((original.value as number) - (prev.value as number)) > 0
) {
const newPrev: Keyframe = {
...prev,
handles: [...prev.handles],
}
updatedKeyframes.push(newPrev)
newPrev.handles[3] = preserveRightHandle(
prev.handles[3],
prev.value,
prev.value,
original.value,
cur.value,
prev.value as number,
prev.value as number,
original.value as number,
cur.value as number,
)
}
const next =
propsAtStartOfDrag.trackData.keyframes[propsAtStartOfDrag.index + 1]
if (next && Math.abs(original.value - next.value) > 0) {
const newNext: Keyframe = {...next, handles: [...next.handles]}
if (
next &&
Math.abs((original.value as number) - (next.value as number)) > 0
) {
const newNext: Keyframe = {
...next,
handles: [...next.handles],
}
updatedKeyframes.push(newNext)
newNext.handles[1] = preserveLeftHandle(
newNext.handles[1],
newNext.value,
newNext.value,
original.value,
cur.value,
newNext.value as number,
newNext.value as number,
original.value as number,
cur.value as number,
)
}
}

View file

@ -35,7 +35,7 @@ const KeyframeEditor: React.FC<{
const next = trackData.keyframes[index + 1]
const connected = cur.connectedRight && !!next
const shouldShowCurve = connected && next.value - cur.value !== 0
const shouldShowCurve = connected && next.value !== cur.value
return (
<Container>

View file

@ -10,6 +10,7 @@ import styled from 'styled-components'
import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout'
import BasicKeyframedTrack from './BasicKeyframedTrack/BasicKeyframedTrack'
import type {graphEditorColors} from './GraphEditor'
import type {TrackData} from '@theatre/core/projects/store/types/SheetState_Historic'
const Container = styled.div``
@ -21,7 +22,7 @@ const PrimitivePropGraph: React.FC<{
color: keyof typeof graphEditorColors
}> = (props) => {
return usePrism(() => {
const {sheetObject, trackId, pathToProp} = props
const {sheetObject, trackId} = props
const trackData = val(
getStudio()!.atomP.historic.coreByProject[sheetObject.address.projectId]
.sheetsById[sheetObject.address.sheetId].sequence.tracksByObject[
@ -35,7 +36,9 @@ const PrimitivePropGraph: React.FC<{
)
return <></>
} else {
return <BasicKeyframedTrack {...props} trackData={trackData} />
return (
<BasicKeyframedTrack {...props} trackData={trackData as TrackData} />
)
}
}, [props.trackId, props.layoutP])
}

View file

@ -46,6 +46,7 @@ import {
} from '@theatre/shared/instanceTypes'
import type SheetTemplate from '@theatre/core/sheets/SheetTemplate'
import type SheetObjectTemplate from '@theatre/core/sheetObjects/SheetObjectTemplate'
import type {PropTypeConfig} from '@theatre/core/propTypes'
export const setDrafts__onlyMeantToBeCalledByTransaction = (
drafts: undefined | Drafts,
@ -432,6 +433,7 @@ namespace stateEditors {
export function setPrimitivePropAsSequenced(
p: WithoutSheetInstance<PropAddress>,
config: PropTypeConfig,
) {
const tracks = _ensureTracksOfObject(p)
const pathEncoded = encodePathToProp(p.pathToProp)
@ -439,6 +441,7 @@ namespace stateEditors {
if (typeof possibleTrackId === 'string') return
const trackId = generateSequenceTrackId()
tracks.trackData[trackId] = {
type: 'BasicKeyframedTrack',
keyframes: [],
@ -502,11 +505,11 @@ namespace stateEditors {
* Sets a keyframe at the exact specified position.
* Any position snapping should be done by the caller.
*/
export function setKeyframeAtPosition(
export function setKeyframeAtPosition<T>(
p: WithoutSheetInstance<SheetObjectAddress> & {
trackId: string
position: number
value: number
value: T
snappingFunction: SnappingFunction
},
) {
@ -608,7 +611,7 @@ namespace stateEditors {
)
}
export function replaceKeyframes(
export function replaceKeyframes<T>(
p: WithoutSheetInstance<SheetObjectAddress> & {
trackId: string
keyframes: Array<Keyframe>
@ -620,7 +623,8 @@ namespace stateEditors {
const initialKeyframes = current(track.keyframes)
const sanitizedKeyframes = p.keyframes
.filter((kf) => {
if (!isFinite(kf.value)) return false
if (typeof kf.value === 'number' && !isFinite(kf.value))
return false
if (!kf.handles.every((handleValue) => isFinite(handleValue)))
return false