All prop sequencing (#48)
This commit is contained in:
parent
52f65af689
commit
4a65c6e91c
19 changed files with 280 additions and 97 deletions
|
@ -3,14 +3,27 @@ import type {UseDragOpts} from './useDrag'
|
||||||
import useDrag from './useDrag'
|
import useDrag from './useDrag'
|
||||||
import React, {useLayoutEffect, useMemo, useState} from 'react'
|
import React, {useLayoutEffect, useMemo, useState} from 'react'
|
||||||
import type {IProject, ISheet} from '@theatre/core'
|
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'
|
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 = {
|
const boxObjectConfig = {
|
||||||
x: 0,
|
test: types.string('Hello?', {interpolate: textInterpolate}),
|
||||||
y: 0,
|
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<{
|
const Box: React.FC<{
|
||||||
|
@ -23,11 +36,17 @@ const Box: React.FC<{
|
||||||
|
|
||||||
const isSelected = selection.includes(obj)
|
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(() => {
|
useLayoutEffect(() => {
|
||||||
const unsubscribeFromChanges = onChange(obj.props, (newValues) => {
|
const unsubscribeFromChanges = onChange(obj.props, (newValues) => {
|
||||||
setPos(newValues)
|
setState(newValues)
|
||||||
})
|
})
|
||||||
return unsubscribeFromChanges
|
return unsubscribeFromChanges
|
||||||
}, [id])
|
}, [id])
|
||||||
|
@ -50,7 +69,13 @@ const Box: React.FC<{
|
||||||
firstOnDragCalled = true
|
firstOnDragCalled = true
|
||||||
}
|
}
|
||||||
scrub!.capture(({set}) => {
|
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) {
|
onDragEnd(dragHappened) {
|
||||||
|
@ -73,16 +98,20 @@ const Box: React.FC<{
|
||||||
}}
|
}}
|
||||||
ref={setDivRef}
|
ref={setDivRef}
|
||||||
style={{
|
style={{
|
||||||
width: 100,
|
width: 300,
|
||||||
height: 100,
|
height: 300,
|
||||||
background: 'gray',
|
color: 'white',
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
left: pos.x + 'px',
|
left: state.x + 'px',
|
||||||
top: pos.y + 'px',
|
top: state.y + 'px',
|
||||||
boxSizing: 'border-box',
|
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',
|
right: '0',
|
||||||
top: 0,
|
top: 0,
|
||||||
bottom: '0',
|
bottom: '0',
|
||||||
background: 'black',
|
background: '#333',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
|
|
|
@ -39,7 +39,7 @@ export type TrackData = BasicKeyframedTrack
|
||||||
|
|
||||||
export type Keyframe = {
|
export type Keyframe = {
|
||||||
id: KeyframeId
|
id: KeyframeId
|
||||||
value: number
|
value: unknown
|
||||||
position: number
|
position: number
|
||||||
handles: [leftX: number, leftY: number, rightX: number, rightY: number]
|
handles: [leftX: number, leftY: number, rightX: number, rightY: number]
|
||||||
connectedRight: boolean
|
connectedRight: boolean
|
||||||
|
|
|
@ -8,9 +8,9 @@ import type {
|
||||||
import {sanitizeCompoundProps} from './internals'
|
import {sanitizeCompoundProps} from './internals'
|
||||||
import {propTypeSymbol} from './internals'
|
import {propTypeSymbol} from './internals'
|
||||||
|
|
||||||
const validateCommonOpts = (
|
const validateCommonOpts = <T>(
|
||||||
fnCallSignature: string,
|
fnCallSignature: string,
|
||||||
opts?: PropTypeConfigOpts,
|
opts?: PropTypeConfigOpts<T>,
|
||||||
) => {
|
) => {
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
if (opts === undefined) return
|
if (opts === undefined) return
|
||||||
|
@ -70,9 +70,10 @@ const validateCommonOpts = (
|
||||||
*/
|
*/
|
||||||
export const compound = <Props extends IShorthandCompoundProps>(
|
export const compound = <Props extends IShorthandCompoundProps>(
|
||||||
props: Props,
|
props: Props,
|
||||||
opts?: PropTypeConfigOpts,
|
opts?: PropTypeConfigOpts<Props>,
|
||||||
): PropTypeConfig_Compound<
|
): PropTypeConfig_Compound<
|
||||||
ShorthandCompoundPropsToLonghandCompoundProps<Props>
|
ShorthandCompoundPropsToLonghandCompoundProps<Props>,
|
||||||
|
Props
|
||||||
> => {
|
> => {
|
||||||
validateCommonOpts('t.compound(props, opts)', opts)
|
validateCommonOpts('t.compound(props, opts)', opts)
|
||||||
return {
|
return {
|
||||||
|
@ -81,6 +82,9 @@ export const compound = <Props extends IShorthandCompoundProps>(
|
||||||
valueType: null as $IntentionalAny,
|
valueType: null as $IntentionalAny,
|
||||||
[propTypeSymbol]: 'TheatrePropType',
|
[propTypeSymbol]: 'TheatrePropType',
|
||||||
label: opts?.label,
|
label: opts?.label,
|
||||||
|
sanitize: opts?.sanitize,
|
||||||
|
interpolate: opts?.interpolate,
|
||||||
|
isScalar: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,7 +135,7 @@ export const number = (
|
||||||
nudgeFn?: PropTypeConfig_Number['nudgeFn']
|
nudgeFn?: PropTypeConfig_Number['nudgeFn']
|
||||||
range?: PropTypeConfig_Number['range']
|
range?: PropTypeConfig_Number['range']
|
||||||
nudgeMultiplier?: number
|
nudgeMultiplier?: number
|
||||||
} & PropTypeConfigOpts,
|
} & PropTypeConfigOpts<number>,
|
||||||
): PropTypeConfig_Number => {
|
): PropTypeConfig_Number => {
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
validateCommonOpts('t.number(defaultValue, opts)', opts)
|
validateCommonOpts('t.number(defaultValue, opts)', opts)
|
||||||
|
@ -202,6 +206,15 @@ export const number = (
|
||||||
nudgeFn: opts?.nudgeFn ?? defaultNumberNudgeFn,
|
nudgeFn: opts?.nudgeFn ?? defaultNumberNudgeFn,
|
||||||
nudgeMultiplier:
|
nudgeMultiplier:
|
||||||
typeof opts?.nudgeMultiplier === 'number' ? opts.nudgeMultiplier : 1,
|
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 = (
|
export const boolean = (
|
||||||
defaultValue: boolean,
|
defaultValue: boolean,
|
||||||
opts?: PropTypeConfigOpts,
|
opts?: PropTypeConfigOpts<boolean>,
|
||||||
): PropTypeConfig_Boolean => {
|
): PropTypeConfig_Boolean => {
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
validateCommonOpts('t.boolean(defaultValue, opts)', opts)
|
validateCommonOpts('t.boolean(defaultValue, opts)', opts)
|
||||||
|
@ -245,6 +258,15 @@ export const boolean = (
|
||||||
valueType: null as $IntentionalAny,
|
valueType: null as $IntentionalAny,
|
||||||
[propTypeSymbol]: 'TheatrePropType',
|
[propTypeSymbol]: 'TheatrePropType',
|
||||||
label: opts?.label,
|
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 = (
|
export const string = (
|
||||||
defaultValue: string,
|
defaultValue: string,
|
||||||
opts?: PropTypeConfigOpts,
|
opts?: PropTypeConfigOpts<string>,
|
||||||
): PropTypeConfig_String => {
|
): PropTypeConfig_String => {
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
validateCommonOpts('t.string(defaultValue, opts)', opts)
|
validateCommonOpts('t.string(defaultValue, opts)', opts)
|
||||||
|
@ -289,6 +311,12 @@ export const string = (
|
||||||
valueType: null as $IntentionalAny,
|
valueType: null as $IntentionalAny,
|
||||||
[propTypeSymbol]: 'TheatrePropType',
|
[propTypeSymbol]: 'TheatrePropType',
|
||||||
label: opts?.label,
|
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 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>> {
|
): PropTypeConfig_StringLiteral<Extract<keyof Opts, string>> {
|
||||||
return {
|
return {
|
||||||
type: 'stringLiteral',
|
type: 'stringLiteral',
|
||||||
|
@ -336,13 +366,30 @@ export function stringLiteral<Opts extends {[key in string]: string}>(
|
||||||
valueType: null as $IntentionalAny,
|
valueType: null as $IntentionalAny,
|
||||||
as: opts?.as ?? 'menu',
|
as: opts?.as ?? 'menu',
|
||||||
label: opts?.label,
|
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
|
valueType: ValueType
|
||||||
[propTypeSymbol]: 'TheatrePropType'
|
[propTypeSymbol]: 'TheatrePropType'
|
||||||
label: string | undefined
|
label: string | undefined
|
||||||
|
isScalar: boolean
|
||||||
|
sanitize?: Sanitizer<PropTypes>
|
||||||
|
interpolate?: Interpolator<PropTypes>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PropTypeConfig_Number extends IBasePropType<number> {
|
export interface PropTypeConfig_Number extends IBasePropType<number> {
|
||||||
|
@ -381,9 +428,18 @@ export interface PropTypeConfig_Boolean extends IBasePropType<boolean> {
|
||||||
default: boolean
|
default: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PropTypeConfigOpts {
|
export interface PropTypeConfig_Color<ColorObject>
|
||||||
label?: string
|
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> {
|
export interface PropTypeConfig_String extends IBasePropType<string> {
|
||||||
type: 'string'
|
type: 'string'
|
||||||
default: string
|
default: string
|
||||||
|
@ -400,8 +456,13 @@ export interface PropTypeConfig_StringLiteral<T extends string>
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export interface PropTypeConfig_Compound<Props extends IValidCompoundProps>
|
export interface PropTypeConfig_Compound<
|
||||||
extends IBasePropType<{[K in keyof Props]: Props[K]['valueType']}> {
|
Props extends IValidCompoundProps,
|
||||||
|
PropTypes = Props,
|
||||||
|
> extends IBasePropType<
|
||||||
|
{[K in keyof Props]: Props[K]['valueType']},
|
||||||
|
PropTypes
|
||||||
|
> {
|
||||||
type: 'compound'
|
type: 'compound'
|
||||||
props: Record<string, PropTypeConfig>
|
props: Record<string, PropTypeConfig>
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,10 +8,14 @@ import {ConstantDerivation, prism, val} from '@theatre/dataverse'
|
||||||
import logger from '@theatre/shared/logger'
|
import logger from '@theatre/shared/logger'
|
||||||
import UnitBezier from 'timing-function/lib/UnitBezier'
|
import UnitBezier from 'timing-function/lib/UnitBezier'
|
||||||
|
|
||||||
|
export type KeyframeValueAtTime =
|
||||||
|
| {left: unknown; right?: unknown; progression: number}
|
||||||
|
| undefined
|
||||||
|
|
||||||
export default function trackValueAtTime(
|
export default function trackValueAtTime(
|
||||||
trackP: Pointer<TrackData | undefined>,
|
trackP: Pointer<TrackData | undefined>,
|
||||||
timeD: IDerivation<number>,
|
timeD: IDerivation<number>,
|
||||||
): IDerivation<unknown> {
|
): IDerivation<KeyframeValueAtTime> {
|
||||||
return prism(() => {
|
return prism(() => {
|
||||||
const track = val(trackP)
|
const track = val(trackP)
|
||||||
const driverD = prism.memo(
|
const driverD = prism.memo(
|
||||||
|
@ -20,7 +24,7 @@ export default function trackValueAtTime(
|
||||||
if (!track) {
|
if (!track) {
|
||||||
return new ConstantDerivation(undefined)
|
return new ConstantDerivation(undefined)
|
||||||
} else if (track.type === 'BasicKeyframedTrack') {
|
} else if (track.type === 'BasicKeyframedTrack') {
|
||||||
return trackValueAtTime_basicKeyframedTrack(track, timeD)
|
return trackValueAtTime_keyframedTrack(track, timeD)
|
||||||
} else {
|
} else {
|
||||||
logger.error(`Track type not yet supported.`)
|
logger.error(`Track type not yet supported.`)
|
||||||
return new ConstantDerivation(undefined)
|
return new ConstantDerivation(undefined)
|
||||||
|
@ -37,14 +41,15 @@ type IStartedState = {
|
||||||
started: true
|
started: true
|
||||||
validFrom: number
|
validFrom: number
|
||||||
validTo: number
|
validTo: number
|
||||||
der: IDerivation<unknown>
|
der: IDerivation<KeyframeValueAtTime>
|
||||||
}
|
}
|
||||||
|
|
||||||
type IState = {started: false} | IStartedState
|
type IState = {started: false} | IStartedState
|
||||||
|
|
||||||
function trackValueAtTime_basicKeyframedTrack(
|
function trackValueAtTime_keyframedTrack(
|
||||||
track: BasicKeyframedTrack,
|
track: BasicKeyframedTrack,
|
||||||
timeD: IDerivation<number>,
|
timeD: IDerivation<number>,
|
||||||
): IDerivation<unknown> {
|
): IDerivation<KeyframeValueAtTime> {
|
||||||
return prism(() => {
|
return prism(() => {
|
||||||
let stateRef = prism.ref<IState>('state', {started: false})
|
let stateRef = prism.ref<IState>('state', {started: false})
|
||||||
let state = stateRef.current
|
let state = stateRef.current
|
||||||
|
@ -52,7 +57,7 @@ function trackValueAtTime_basicKeyframedTrack(
|
||||||
const time = timeD.getValue()
|
const time = timeD.getValue()
|
||||||
|
|
||||||
if (!state.started || time < state.validFrom || state.validTo <= time) {
|
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()
|
return state.der.getValue()
|
||||||
|
@ -61,7 +66,7 @@ function trackValueAtTime_basicKeyframedTrack(
|
||||||
|
|
||||||
const undefinedConstD = new ConstantDerivation(undefined)
|
const undefinedConstD = new ConstantDerivation(undefined)
|
||||||
|
|
||||||
const pp = (
|
const updateState = (
|
||||||
progressionD: IDerivation<number>,
|
progressionD: IDerivation<number>,
|
||||||
track: BasicKeyframedTrack,
|
track: BasicKeyframedTrack,
|
||||||
): IStartedState => {
|
): IStartedState => {
|
||||||
|
@ -138,7 +143,7 @@ const states = {
|
||||||
started: true,
|
started: true,
|
||||||
validFrom: -Infinity,
|
validFrom: -Infinity,
|
||||||
validTo: kf.position,
|
validTo: kf.position,
|
||||||
der: new ConstantDerivation(kf.value),
|
der: new ConstantDerivation({left: kf.value, progression: 0}),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
lastKeyframe(kf: Keyframe): IStartedState {
|
lastKeyframe(kf: Keyframe): IStartedState {
|
||||||
|
@ -146,7 +151,7 @@ const states = {
|
||||||
started: true,
|
started: true,
|
||||||
validFrom: kf.position,
|
validFrom: kf.position,
|
||||||
validTo: Infinity,
|
validTo: Infinity,
|
||||||
der: new ConstantDerivation(kf.value),
|
der: new ConstantDerivation({left: kf.value, progression: 0}),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
between(
|
between(
|
||||||
|
@ -159,7 +164,7 @@ const states = {
|
||||||
started: true,
|
started: true,
|
||||||
validFrom: left.position,
|
validFrom: left.position,
|
||||||
validTo: right.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)
|
const valueProgression = solver.solveSimple(progression)
|
||||||
return left.value + valueProgression * (right.value - left.value)
|
return {
|
||||||
|
left: left.value,
|
||||||
|
right: right.value,
|
||||||
|
progression: valueProgression,
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import type {KeyframeValueAtTime} from '@theatre/core/sequences/trackValueAtTime'
|
||||||
import trackValueAtTime from '@theatre/core/sequences/trackValueAtTime'
|
import trackValueAtTime from '@theatre/core/sequences/trackValueAtTime'
|
||||||
import type Sheet from '@theatre/core/sheets/Sheet'
|
import type Sheet from '@theatre/core/sheets/Sheet'
|
||||||
import type {SheetObjectAddress} from '@theatre/shared/utils/addresses'
|
import type {SheetObjectAddress} from '@theatre/shared/utils/addresses'
|
||||||
|
@ -21,6 +22,8 @@ import type {
|
||||||
import {Atom, getPointerParts, pointer, prism, val} from '@theatre/dataverse'
|
import {Atom, getPointerParts, pointer, prism, val} from '@theatre/dataverse'
|
||||||
import type SheetObjectTemplate from './SheetObjectTemplate'
|
import type SheetObjectTemplate from './SheetObjectTemplate'
|
||||||
import TheatreSheetObject from './TheatreSheetObject'
|
import TheatreSheetObject from './TheatreSheetObject'
|
||||||
|
import {get} from 'lodash-es'
|
||||||
|
import type {Interpolator, PropTypeConfig} from '@theatre/core/propTypes'
|
||||||
|
|
||||||
// type Everything = {
|
// type Everything = {
|
||||||
// final: SerializableMap
|
// final: SerializableMap
|
||||||
|
@ -153,7 +156,32 @@ export default class SheetObject implements IdentityDerivationProvider {
|
||||||
const derivation = this._trackIdToDerivation(trackId)
|
const derivation = this._trackIdToDerivation(trackId)
|
||||||
|
|
||||||
const updateSequenceValueFromItsDerivation = () => {
|
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
|
const untap = derivation
|
||||||
.changesWithoutValues()
|
.changesWithoutValues()
|
||||||
|
@ -177,12 +205,11 @@ export default class SheetObject implements IdentityDerivationProvider {
|
||||||
|
|
||||||
protected _trackIdToDerivation(
|
protected _trackIdToDerivation(
|
||||||
trackId: SequenceTrackId,
|
trackId: SequenceTrackId,
|
||||||
): IDerivation<unknown> {
|
): IDerivation<KeyframeValueAtTime | undefined> {
|
||||||
const trackP =
|
const trackP =
|
||||||
this.template.project.pointers.historic.sheetsById[this.address.sheetId]
|
this.template.project.pointers.historic.sheetsById[this.address.sheetId]
|
||||||
.sequence.tracksByObject[this.address.objectKey].trackData[trackId]
|
.sequence.tracksByObject[this.address.objectKey].trackData[trackId]
|
||||||
const timeD = this.sheet.getSequence().positionDerivation
|
const timeD = this.sheet.getSequence().positionDerivation
|
||||||
|
|
||||||
return trackValueAtTime(trackP, timeD)
|
return trackValueAtTime(trackP, timeD)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,10 @@ import set from 'lodash-es/set'
|
||||||
import getPropDefaultsOfSheetObject from './getPropDefaultsOfSheetObject'
|
import getPropDefaultsOfSheetObject from './getPropDefaultsOfSheetObject'
|
||||||
import SheetObject from './SheetObject'
|
import SheetObject from './SheetObject'
|
||||||
import logger from '@theatre/shared/logger'
|
import logger from '@theatre/shared/logger'
|
||||||
|
import type {
|
||||||
|
PropTypeConfig,
|
||||||
|
PropTypeConfig_Compound,
|
||||||
|
} from '@theatre/core/propTypes'
|
||||||
|
|
||||||
export type IPropPathToTrackIdTree = {
|
export type IPropPathToTrackIdTree = {
|
||||||
[key in string]?: SequenceTrackId | IPropPathToTrackIdTree
|
[key in string]?: SequenceTrackId | IPropPathToTrackIdTree
|
||||||
|
@ -32,7 +36,9 @@ export type IPropPathToTrackIdTree = {
|
||||||
export default class SheetObjectTemplate {
|
export default class SheetObjectTemplate {
|
||||||
readonly address: WithoutSheetInstance<SheetObjectAddress>
|
readonly address: WithoutSheetInstance<SheetObjectAddress>
|
||||||
readonly type: 'Theatre_SheetObjectTemplate' = 'Theatre_SheetObjectTemplate'
|
readonly type: 'Theatre_SheetObjectTemplate' = 'Theatre_SheetObjectTemplate'
|
||||||
protected _config: Atom<SheetObjectConfig<$IntentionalAny>>
|
protected _config: Atom<
|
||||||
|
SheetObjectConfig<PropTypeConfig_Compound<$IntentionalAny>>
|
||||||
|
>
|
||||||
readonly _cache = new SimpleCache()
|
readonly _cache = new SimpleCache()
|
||||||
readonly project: Project
|
readonly project: Project
|
||||||
|
|
||||||
|
@ -87,13 +93,14 @@ export default class SheetObjectTemplate {
|
||||||
this.address.sheetId
|
this.address.sheetId
|
||||||
]
|
]
|
||||||
|
|
||||||
return (
|
const value =
|
||||||
val(
|
val(
|
||||||
pointerToSheetState.staticOverrides.byObject[
|
pointerToSheetState.staticOverrides.byObject[
|
||||||
this.address.objectKey
|
this.address.objectKey
|
||||||
],
|
],
|
||||||
) || {}
|
) || {}
|
||||||
)
|
|
||||||
|
return value
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -139,8 +146,17 @@ export default class SheetObjectTemplate {
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const propConfig = get(this.config.props, pathToProp) as
|
||||||
|
| PropTypeConfig
|
||||||
|
| undefined
|
||||||
const defaultValue = get(defaults, pathToProp)
|
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
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
8
theatre/globals.d.ts
vendored
8
theatre/globals.d.ts
vendored
|
@ -46,7 +46,13 @@ declare module 'propose' {
|
||||||
// export default inspect
|
// 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 'clean-webpack-plugin'
|
||||||
declare module 'webpack-notifier'
|
declare module 'webpack-notifier'
|
||||||
declare module 'case-sensitive-paths-webpack-plugin'
|
declare module 'case-sensitive-paths-webpack-plugin'
|
||||||
|
|
|
@ -29,6 +29,7 @@ import {persistStateOfStudio} from './persistStateOfStudio'
|
||||||
import {isSheetObject} from '@theatre/shared/instanceTypes'
|
import {isSheetObject} from '@theatre/shared/instanceTypes'
|
||||||
import type {OnDiskState} from '@theatre/core/projects/store/storeTypes'
|
import type {OnDiskState} from '@theatre/core/projects/store/storeTypes'
|
||||||
import {generateDiskStateRevision} from './generateDiskStateRevision'
|
import {generateDiskStateRevision} from './generateDiskStateRevision'
|
||||||
|
import type {PropTypeConfig} from '@theatre/core/propTypes'
|
||||||
|
|
||||||
export type Drafts = {
|
export type Drafts = {
|
||||||
historic: Draft<StudioHistoricState>
|
historic: Draft<StudioHistoricState>
|
||||||
|
@ -146,6 +147,7 @@ export default class StudioStore {
|
||||||
if (typeof v === 'undefined' || v === null) {
|
if (typeof v === 'undefined' || v === null) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const propAddress = {...root.address, pathToProp}
|
const propAddress = {...root.address, pathToProp}
|
||||||
|
|
||||||
const trackId = get(
|
const trackId = get(
|
||||||
|
@ -154,6 +156,12 @@ export default class StudioStore {
|
||||||
) as $FixMe as SequenceTrackId | undefined
|
) as $FixMe as SequenceTrackId | undefined
|
||||||
|
|
||||||
if (typeof trackId === 'string') {
|
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()
|
const seq = root.sheet.getSequence()
|
||||||
seq.position = seq.closestGridPosition(seq.position)
|
seq.position = seq.closestGridPosition(seq.position)
|
||||||
stateEditors.coreByProject.historic.sheetsById.sequence.setKeyframeAtPosition(
|
stateEditors.coreByProject.historic.sheetsById.sequence.setKeyframeAtPosition(
|
||||||
|
|
|
@ -16,10 +16,10 @@ export const getPropTypeByPointer = (
|
||||||
pointerToProp: SheetObject['propsP'],
|
pointerToProp: SheetObject['propsP'],
|
||||||
obj: SheetObject,
|
obj: SheetObject,
|
||||||
): PropTypeConfig => {
|
): PropTypeConfig => {
|
||||||
const rootConf = obj.template.config.props
|
const rootConf = obj.template.config
|
||||||
|
|
||||||
const p = getPointerParts(pointerToProp).path
|
const p = getPointerParts(pointerToProp).path
|
||||||
let conf: PropTypeConfig = rootConf as PropTypeConfig
|
let conf = rootConf as PropTypeConfig
|
||||||
|
|
||||||
while (p.length !== 0) {
|
while (p.length !== 0) {
|
||||||
const key = p.shift()
|
const key = p.shift()
|
||||||
|
|
|
@ -248,8 +248,10 @@ export function useEditingToolsForPrimitiveProp<
|
||||||
callback: () => {
|
callback: () => {
|
||||||
getStudio()!.transaction(({stateEditors}) => {
|
getStudio()!.transaction(({stateEditors}) => {
|
||||||
const propAddress = {...obj.address, pathToProp}
|
const propAddress = {...obj.address, pathToProp}
|
||||||
|
|
||||||
stateEditors.coreByProject.historic.sheetsById.sequence.setPrimitivePropAsSequenced(
|
stateEditors.coreByProject.historic.sheetsById.sequence.setPrimitivePropAsSequenced(
|
||||||
propAddress,
|
propAddress,
|
||||||
|
propConfig,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
@ -309,5 +311,5 @@ type Shade =
|
||||||
| 'Sequened_NotBeingInterpolated'
|
| 'Sequened_NotBeingInterpolated'
|
||||||
|
|
||||||
function isPropConfSequencable(conf: PropTypeConfig): boolean {
|
function isPropConfSequencable(conf: PropTypeConfig): boolean {
|
||||||
return conf.type === 'number'
|
return conf.type === 'number' || (!!conf.sanitize && !!conf.interpolate)
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,7 +52,7 @@ const IconContainer = styled.button<{
|
||||||
? graphEditorColors[props.graphEditorColor].iconColor
|
? graphEditorColors[props.graphEditorColor].iconColor
|
||||||
: nextPrevCursorsTheme.offColor};
|
: nextPrevCursorsTheme.offColor};
|
||||||
|
|
||||||
&:hover {
|
&:not([disabled]):hover {
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
@ -129,6 +129,9 @@ const PrimitivePropRow: React.FC<{
|
||||||
}, [leaf])
|
}, [leaf])
|
||||||
|
|
||||||
const label = leaf.pathToProp[leaf.pathToProp.length - 1]
|
const label = leaf.pathToProp[leaf.pathToProp.length - 1]
|
||||||
|
const isSelectable =
|
||||||
|
leaf.propConf.type !== 'boolean' && leaf.propConf.type !== 'stringLiteral'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container depth={leaf.depth}>
|
<Container depth={leaf.depth}>
|
||||||
<Head
|
<Head
|
||||||
|
@ -144,6 +147,8 @@ const PrimitivePropRow: React.FC<{
|
||||||
onClick={toggleSelect}
|
onClick={toggleSelect}
|
||||||
isSelected={isSelected === true}
|
isSelected={isSelected === true}
|
||||||
graphEditorColor={possibleColor ?? '1'}
|
graphEditorColor={possibleColor ?? '1'}
|
||||||
|
style={{opacity: isSelectable ? 1 : 0.25}}
|
||||||
|
disabled={!isSelectable}
|
||||||
>
|
>
|
||||||
<GraphIcon />
|
<GraphIcon />
|
||||||
</IconContainer>
|
</IconContainer>
|
||||||
|
|
|
@ -5,7 +5,7 @@ import {usePrism} from '@theatre/react'
|
||||||
import type {Pointer} from '@theatre/dataverse'
|
import type {Pointer} from '@theatre/dataverse'
|
||||||
import {val} from '@theatre/dataverse'
|
import {val} from '@theatre/dataverse'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import BasicKeyframedTrack from './BasicKeyframedTrack/BasicKeyframedTrack'
|
import KeyframedTrack from './BasicKeyframedTrack/BasicKeyframedTrack'
|
||||||
import Row from './Row'
|
import Row from './Row'
|
||||||
|
|
||||||
const PrimitivePropRow: React.FC<{
|
const PrimitivePropRow: React.FC<{
|
||||||
|
@ -30,12 +30,9 @@ const PrimitivePropRow: React.FC<{
|
||||||
return <Row leaf={leaf} node={<div />}></Row>
|
return <Row leaf={leaf} node={<div />}></Row>
|
||||||
} else {
|
} else {
|
||||||
const node = (
|
const node = (
|
||||||
<BasicKeyframedTrack
|
<KeyframedTrack layoutP={layoutP} trackData={trackData} leaf={leaf} />
|
||||||
layoutP={layoutP}
|
|
||||||
trackData={trackData}
|
|
||||||
leaf={leaf}
|
|
||||||
/>
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return <Row leaf={leaf} node={node}></Row>
|
return <Row leaf={leaf} node={node}></Row>
|
||||||
}
|
}
|
||||||
}, [leaf, layoutP])
|
}, [leaf, layoutP])
|
||||||
|
|
|
@ -115,13 +115,14 @@ function calculateExtremums(keyframes: Keyframe[]): Extremums {
|
||||||
}
|
}
|
||||||
|
|
||||||
keyframes.forEach((cur, i) => {
|
keyframes.forEach((cur, i) => {
|
||||||
check(cur.value)
|
const curVal = typeof cur.value === 'number' ? cur.value : 0
|
||||||
|
check(curVal)
|
||||||
if (!cur.connectedRight) return
|
if (!cur.connectedRight) return
|
||||||
const next = keyframes[i + 1]
|
const next = keyframes[i + 1]
|
||||||
if (!next) return
|
if (!next) return
|
||||||
const diff = next.value - cur.value
|
const diff = (typeof next.value === 'number' ? next.value : 1) - curVal
|
||||||
check(cur.value + cur.handles[3] * diff)
|
check(curVal + cur.handles[3] * diff)
|
||||||
check(cur.value + next.handles[1] * diff)
|
check(curVal + next.handles[1] * diff)
|
||||||
})
|
})
|
||||||
|
|
||||||
return [min, max]
|
return [min, max]
|
||||||
|
|
|
@ -19,21 +19,16 @@ const Curve: React.FC<IProps> = (props) => {
|
||||||
const cur = trackData.keyframes[index]
|
const cur = trackData.keyframes[index]
|
||||||
const next = trackData.keyframes[index + 1]
|
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 connectorLengthInUnitSpace = next.position - cur.position
|
||||||
|
|
||||||
const [nodeRef, node] = useRefAndState<SVGPathElement | null>(null)
|
const [nodeRef, node] = useRefAndState<SVGPathElement | null>(null)
|
||||||
|
|
||||||
const [contextMenu] = useConnectorContextMenu(node, props)
|
const [contextMenu] = useConnectorContextMenu(node, props)
|
||||||
|
|
||||||
const leftYInExtremumSpace = props.extremumSpace.fromValueSpace(cur.value)
|
const curValue = typeof cur.value === 'number' ? cur.value : 0
|
||||||
const rightYInExtremumSpace = props.extremumSpace.fromValueSpace(next.value)
|
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
|
const heightInExtremumSpace = rightYInExtremumSpace - leftYInExtremumSpace
|
||||||
|
|
||||||
|
|
|
@ -72,20 +72,23 @@ const CurveHandle: React.FC<IProps> = (props) => {
|
||||||
// debugger
|
// 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 valInExtremumSpace = props.extremumSpace.fromValueSpace(value)
|
||||||
|
|
||||||
const heightInExtremumSpace =
|
const heightInExtremumSpace =
|
||||||
valInExtremumSpace -
|
valInExtremumSpace -
|
||||||
props.extremumSpace.fromValueSpace(
|
props.extremumSpace.fromValueSpace(
|
||||||
props.which === 'left' ? cur.value : next.value,
|
props.which === 'left' ? curValue : nextValue,
|
||||||
)
|
)
|
||||||
|
|
||||||
const lineTransform = transformBox(
|
const lineTransform = transformBox(
|
||||||
props.which === 'left' ? cur.position : next.position,
|
props.which === 'left' ? cur.position : next.position,
|
||||||
props.extremumSpace.fromValueSpace(
|
props.extremumSpace.fromValueSpace(
|
||||||
props.which === 'left' ? cur.value : next.value,
|
props.which === 'left' ? curValue : nextValue,
|
||||||
),
|
),
|
||||||
posInUnitSpace - (props.which === 'left' ? cur.position : next.position),
|
posInUnitSpace - (props.which === 'left' ? cur.position : next.position),
|
||||||
heightInExtremumSpace,
|
heightInExtremumSpace,
|
||||||
|
@ -165,7 +168,9 @@ function useOurDrags(node: SVGCircleElement | null, props: IProps): void {
|
||||||
const dYInValueSpace =
|
const dYInValueSpace =
|
||||||
propsAtStartOfDrag.extremumSpace.deltaToValueSpace(dYInExtremumSpace)
|
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') {
|
if (propsAtStartOfDrag.which === 'left') {
|
||||||
const handleX = clamp(cur.handles[2] + dPosInKeyframeDiffSpace, 0, 1)
|
const handleX = clamp(cur.handles[2] + dPosInKeyframeDiffSpace, 0, 1)
|
||||||
|
|
|
@ -59,9 +59,12 @@ const Dot: React.FC<IProps> = (props) => {
|
||||||
const next = trackData.keyframes[index + 1]
|
const next = trackData.keyframes[index + 1]
|
||||||
|
|
||||||
const [contextMenu] = useKeyframeContextMenu(node, props)
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -140,7 +143,7 @@ function useDragKeyframe(
|
||||||
const cur: Keyframe = {
|
const cur: Keyframe = {
|
||||||
...original,
|
...original,
|
||||||
position: original.position + deltaPos,
|
position: original.position + deltaPos,
|
||||||
value: original.value + dYInValueSpace,
|
value: (original.value as number) + dYInValueSpace,
|
||||||
handles: [...original.handles],
|
handles: [...original.handles],
|
||||||
}
|
}
|
||||||
updatedKeyframes.push(cur)
|
updatedKeyframes.push(cur)
|
||||||
|
@ -149,29 +152,41 @@ function useDragKeyframe(
|
||||||
const prev =
|
const prev =
|
||||||
propsAtStartOfDrag.trackData.keyframes[propsAtStartOfDrag.index - 1]
|
propsAtStartOfDrag.trackData.keyframes[propsAtStartOfDrag.index - 1]
|
||||||
|
|
||||||
if (prev && Math.abs(original.value - prev.value) > 0) {
|
if (
|
||||||
const newPrev: Keyframe = {...prev, handles: [...prev.handles]}
|
prev &&
|
||||||
|
Math.abs((original.value as number) - (prev.value as number)) > 0
|
||||||
|
) {
|
||||||
|
const newPrev: Keyframe = {
|
||||||
|
...prev,
|
||||||
|
handles: [...prev.handles],
|
||||||
|
}
|
||||||
updatedKeyframes.push(newPrev)
|
updatedKeyframes.push(newPrev)
|
||||||
newPrev.handles[3] = preserveRightHandle(
|
newPrev.handles[3] = preserveRightHandle(
|
||||||
prev.handles[3],
|
prev.handles[3],
|
||||||
prev.value,
|
prev.value as number,
|
||||||
prev.value,
|
prev.value as number,
|
||||||
original.value,
|
original.value as number,
|
||||||
cur.value,
|
cur.value as number,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
const next =
|
const next =
|
||||||
propsAtStartOfDrag.trackData.keyframes[propsAtStartOfDrag.index + 1]
|
propsAtStartOfDrag.trackData.keyframes[propsAtStartOfDrag.index + 1]
|
||||||
|
|
||||||
if (next && Math.abs(original.value - next.value) > 0) {
|
if (
|
||||||
const newNext: Keyframe = {...next, handles: [...next.handles]}
|
next &&
|
||||||
|
Math.abs((original.value as number) - (next.value as number)) > 0
|
||||||
|
) {
|
||||||
|
const newNext: Keyframe = {
|
||||||
|
...next,
|
||||||
|
handles: [...next.handles],
|
||||||
|
}
|
||||||
updatedKeyframes.push(newNext)
|
updatedKeyframes.push(newNext)
|
||||||
newNext.handles[1] = preserveLeftHandle(
|
newNext.handles[1] = preserveLeftHandle(
|
||||||
newNext.handles[1],
|
newNext.handles[1],
|
||||||
newNext.value,
|
newNext.value as number,
|
||||||
newNext.value,
|
newNext.value as number,
|
||||||
original.value,
|
original.value as number,
|
||||||
cur.value,
|
cur.value as number,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ const KeyframeEditor: React.FC<{
|
||||||
const next = trackData.keyframes[index + 1]
|
const next = trackData.keyframes[index + 1]
|
||||||
|
|
||||||
const connected = cur.connectedRight && !!next
|
const connected = cur.connectedRight && !!next
|
||||||
const shouldShowCurve = connected && next.value - cur.value !== 0
|
const shouldShowCurve = connected && next.value !== cur.value
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
|
|
|
@ -10,6 +10,7 @@ import styled from 'styled-components'
|
||||||
import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout'
|
import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout'
|
||||||
import BasicKeyframedTrack from './BasicKeyframedTrack/BasicKeyframedTrack'
|
import BasicKeyframedTrack from './BasicKeyframedTrack/BasicKeyframedTrack'
|
||||||
import type {graphEditorColors} from './GraphEditor'
|
import type {graphEditorColors} from './GraphEditor'
|
||||||
|
import type {TrackData} from '@theatre/core/projects/store/types/SheetState_Historic'
|
||||||
|
|
||||||
const Container = styled.div``
|
const Container = styled.div``
|
||||||
|
|
||||||
|
@ -21,7 +22,7 @@ const PrimitivePropGraph: React.FC<{
|
||||||
color: keyof typeof graphEditorColors
|
color: keyof typeof graphEditorColors
|
||||||
}> = (props) => {
|
}> = (props) => {
|
||||||
return usePrism(() => {
|
return usePrism(() => {
|
||||||
const {sheetObject, trackId, pathToProp} = props
|
const {sheetObject, trackId} = props
|
||||||
const trackData = val(
|
const trackData = val(
|
||||||
getStudio()!.atomP.historic.coreByProject[sheetObject.address.projectId]
|
getStudio()!.atomP.historic.coreByProject[sheetObject.address.projectId]
|
||||||
.sheetsById[sheetObject.address.sheetId].sequence.tracksByObject[
|
.sheetsById[sheetObject.address.sheetId].sequence.tracksByObject[
|
||||||
|
@ -35,7 +36,9 @@ const PrimitivePropGraph: React.FC<{
|
||||||
)
|
)
|
||||||
return <></>
|
return <></>
|
||||||
} else {
|
} else {
|
||||||
return <BasicKeyframedTrack {...props} trackData={trackData} />
|
return (
|
||||||
|
<BasicKeyframedTrack {...props} trackData={trackData as TrackData} />
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}, [props.trackId, props.layoutP])
|
}, [props.trackId, props.layoutP])
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,7 @@ import {
|
||||||
} from '@theatre/shared/instanceTypes'
|
} from '@theatre/shared/instanceTypes'
|
||||||
import type SheetTemplate from '@theatre/core/sheets/SheetTemplate'
|
import type SheetTemplate from '@theatre/core/sheets/SheetTemplate'
|
||||||
import type SheetObjectTemplate from '@theatre/core/sheetObjects/SheetObjectTemplate'
|
import type SheetObjectTemplate from '@theatre/core/sheetObjects/SheetObjectTemplate'
|
||||||
|
import type {PropTypeConfig} from '@theatre/core/propTypes'
|
||||||
|
|
||||||
export const setDrafts__onlyMeantToBeCalledByTransaction = (
|
export const setDrafts__onlyMeantToBeCalledByTransaction = (
|
||||||
drafts: undefined | Drafts,
|
drafts: undefined | Drafts,
|
||||||
|
@ -432,6 +433,7 @@ namespace stateEditors {
|
||||||
|
|
||||||
export function setPrimitivePropAsSequenced(
|
export function setPrimitivePropAsSequenced(
|
||||||
p: WithoutSheetInstance<PropAddress>,
|
p: WithoutSheetInstance<PropAddress>,
|
||||||
|
config: PropTypeConfig,
|
||||||
) {
|
) {
|
||||||
const tracks = _ensureTracksOfObject(p)
|
const tracks = _ensureTracksOfObject(p)
|
||||||
const pathEncoded = encodePathToProp(p.pathToProp)
|
const pathEncoded = encodePathToProp(p.pathToProp)
|
||||||
|
@ -439,6 +441,7 @@ namespace stateEditors {
|
||||||
if (typeof possibleTrackId === 'string') return
|
if (typeof possibleTrackId === 'string') return
|
||||||
|
|
||||||
const trackId = generateSequenceTrackId()
|
const trackId = generateSequenceTrackId()
|
||||||
|
|
||||||
tracks.trackData[trackId] = {
|
tracks.trackData[trackId] = {
|
||||||
type: 'BasicKeyframedTrack',
|
type: 'BasicKeyframedTrack',
|
||||||
keyframes: [],
|
keyframes: [],
|
||||||
|
@ -502,11 +505,11 @@ namespace stateEditors {
|
||||||
* Sets a keyframe at the exact specified position.
|
* Sets a keyframe at the exact specified position.
|
||||||
* Any position snapping should be done by the caller.
|
* Any position snapping should be done by the caller.
|
||||||
*/
|
*/
|
||||||
export function setKeyframeAtPosition(
|
export function setKeyframeAtPosition<T>(
|
||||||
p: WithoutSheetInstance<SheetObjectAddress> & {
|
p: WithoutSheetInstance<SheetObjectAddress> & {
|
||||||
trackId: string
|
trackId: string
|
||||||
position: number
|
position: number
|
||||||
value: number
|
value: T
|
||||||
snappingFunction: SnappingFunction
|
snappingFunction: SnappingFunction
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
|
@ -608,7 +611,7 @@ namespace stateEditors {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function replaceKeyframes(
|
export function replaceKeyframes<T>(
|
||||||
p: WithoutSheetInstance<SheetObjectAddress> & {
|
p: WithoutSheetInstance<SheetObjectAddress> & {
|
||||||
trackId: string
|
trackId: string
|
||||||
keyframes: Array<Keyframe>
|
keyframes: Array<Keyframe>
|
||||||
|
@ -620,7 +623,8 @@ namespace stateEditors {
|
||||||
const initialKeyframes = current(track.keyframes)
|
const initialKeyframes = current(track.keyframes)
|
||||||
const sanitizedKeyframes = p.keyframes
|
const sanitizedKeyframes = p.keyframes
|
||||||
.filter((kf) => {
|
.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)))
|
if (!kf.handles.every((handleValue) => isFinite(handleValue)))
|
||||||
return false
|
return false
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue