WIP: refactor sequencing non-scalars
This commit is contained in:
parent
67b8a708dc
commit
4ec6dd1181
6 changed files with 208 additions and 167 deletions
|
@ -1,5 +1,6 @@
|
|||
import type {$IntentionalAny} from '@theatre/shared/utils/types'
|
||||
import userReadableTypeOfValue from '@theatre/shared/utils/userReadableTypeOfValue'
|
||||
import {mapValues} from 'lodash-es'
|
||||
import type {
|
||||
IShorthandCompoundProps,
|
||||
IValidCompoundProps,
|
||||
|
@ -8,10 +9,7 @@ import type {
|
|||
import {sanitizeCompoundProps} from './internals'
|
||||
import {propTypeSymbol} from './internals'
|
||||
|
||||
const validateCommonOpts = <T>(
|
||||
fnCallSignature: string,
|
||||
opts?: PropTypeConfigOpts<T>,
|
||||
) => {
|
||||
const validateCommonOpts = (fnCallSignature: string, opts?: CommonOpts) => {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
if (opts === undefined) return
|
||||
if (typeof opts !== 'object' || opts === null) {
|
||||
|
@ -70,20 +68,22 @@ const validateCommonOpts = <T>(
|
|||
*/
|
||||
export const compound = <Props extends IShorthandCompoundProps>(
|
||||
props: Props,
|
||||
opts?: PropTypeConfigOpts<Props>,
|
||||
opts?: {
|
||||
label?: string
|
||||
},
|
||||
): PropTypeConfig_Compound<
|
||||
ShorthandCompoundPropsToLonghandCompoundProps<Props>,
|
||||
Props
|
||||
> => {
|
||||
validateCommonOpts('t.compound(props, opts)', opts)
|
||||
const sanitizedProps = sanitizeCompoundProps(props)
|
||||
return {
|
||||
type: 'compound',
|
||||
props: sanitizeCompoundProps(props),
|
||||
props: sanitizedProps,
|
||||
valueType: null as $IntentionalAny,
|
||||
[propTypeSymbol]: 'TheatrePropType',
|
||||
label: opts?.label,
|
||||
sanitize: opts?.sanitize,
|
||||
interpolate: opts?.interpolate,
|
||||
default: mapValues(sanitizedProps, (p) => p.default) as $IntentionalAny,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -134,7 +134,10 @@ export const number = (
|
|||
nudgeFn?: PropTypeConfig_Number['nudgeFn']
|
||||
range?: PropTypeConfig_Number['range']
|
||||
nudgeMultiplier?: number
|
||||
} & PropTypeConfigOpts<number>,
|
||||
label?: string
|
||||
sanitize?: Sanitizer<number>
|
||||
interpolate?: Interpolator<number>
|
||||
},
|
||||
): PropTypeConfig_Number => {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
validateCommonOpts('t.number(defaultValue, opts)', opts)
|
||||
|
@ -195,6 +198,15 @@ export const number = (
|
|||
}
|
||||
}
|
||||
}
|
||||
const sanitize = !opts?.sanitize
|
||||
? _sanitizeNumber
|
||||
: (val: unknown): number | undefined => {
|
||||
const n = _sanitizeNumber(val)
|
||||
if (typeof n === 'number') {
|
||||
return opts.sanitize!(n)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'number',
|
||||
valueType: 0,
|
||||
|
@ -206,17 +218,22 @@ export const number = (
|
|||
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)
|
||||
},
|
||||
sanitize: sanitize,
|
||||
interpolate: opts?.interpolate ?? _interpolateNumber,
|
||||
}
|
||||
}
|
||||
|
||||
const _sanitizeNumber = (value: unknown): undefined | number =>
|
||||
typeof value === 'number' && isFinite(value) ? value : undefined
|
||||
|
||||
const _interpolateNumber = (
|
||||
left: number,
|
||||
right: number,
|
||||
progression: number,
|
||||
): number => {
|
||||
return left + progression * (right - left)
|
||||
}
|
||||
|
||||
/**
|
||||
* A boolean prop type
|
||||
*
|
||||
|
@ -239,7 +256,11 @@ export const number = (
|
|||
*/
|
||||
export const boolean = (
|
||||
defaultValue: boolean,
|
||||
opts?: PropTypeConfigOpts<boolean>,
|
||||
opts?: {
|
||||
label?: string
|
||||
sanitize?: Sanitizer<boolean>
|
||||
interpolate?: Interpolator<boolean>
|
||||
},
|
||||
): PropTypeConfig_Boolean => {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
validateCommonOpts('t.boolean(defaultValue, opts)', opts)
|
||||
|
@ -251,23 +272,26 @@ export const boolean = (
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'boolean',
|
||||
default: defaultValue,
|
||||
valueType: null as $IntentionalAny,
|
||||
[propTypeSymbol]: 'TheatrePropType',
|
||||
label: opts?.label,
|
||||
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
|
||||
},
|
||||
sanitize: _sanitizeBoolean,
|
||||
interpolate: leftInterpolate,
|
||||
}
|
||||
}
|
||||
|
||||
const _sanitizeBoolean = (val: unknown): boolean | undefined => {
|
||||
return typeof val === 'boolean' ? val : undefined
|
||||
}
|
||||
|
||||
function leftInterpolate<T>(left: T): T {
|
||||
return left
|
||||
}
|
||||
|
||||
/**
|
||||
* A string prop type
|
||||
*
|
||||
|
@ -291,7 +315,11 @@ export const boolean = (
|
|||
*/
|
||||
export const string = (
|
||||
defaultValue: string,
|
||||
opts?: PropTypeConfigOpts<string>,
|
||||
opts?: {
|
||||
label?: string
|
||||
sanitize?: Sanitizer<string>
|
||||
interpolate?: Interpolator<string>
|
||||
},
|
||||
): PropTypeConfig_String => {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
validateCommonOpts('t.string(defaultValue, opts)', opts)
|
||||
|
@ -313,7 +341,7 @@ export const string = (
|
|||
if (opts?.sanitize) return opts.sanitize(value)
|
||||
return typeof value === 'string' ? value : undefined
|
||||
},
|
||||
interpolate: opts?.interpolate,
|
||||
interpolate: opts?.interpolate ?? leftInterpolate,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -351,9 +379,7 @@ 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<
|
||||
Extract<keyof Opts, string>
|
||||
>,
|
||||
opts?: {as?: 'menu' | 'switch'; label?: string},
|
||||
): PropTypeConfig_StringLiteral<Extract<keyof Opts, string>> {
|
||||
return {
|
||||
type: 'stringLiteral',
|
||||
|
@ -364,15 +390,14 @@ export function stringLiteral<Opts extends {[key in string]: string}>(
|
|||
as: opts?.as ?? 'menu',
|
||||
label: opts?.label,
|
||||
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
|
||||
if (typeof value !== 'string') return undefined
|
||||
if (Object.hasOwnProperty.call(options, value)) {
|
||||
return value as $IntentionalAny
|
||||
} else {
|
||||
return undefined
|
||||
}
|
||||
},
|
||||
interpolate: leftInterpolate,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -386,6 +411,7 @@ interface IBasePropType<ValueType, PropTypes = ValueType> {
|
|||
isScalar?: true
|
||||
sanitize?: Sanitizer<PropTypes>
|
||||
interpolate?: Interpolator<PropTypes>
|
||||
default: ValueType
|
||||
}
|
||||
|
||||
export interface PropTypeConfig_Number extends IBasePropType<number> {
|
||||
|
@ -424,16 +450,8 @@ export interface PropTypeConfig_Boolean extends IBasePropType<boolean> {
|
|||
default: boolean
|
||||
}
|
||||
|
||||
export interface PropTypeConfig_Color<ColorObject>
|
||||
extends IBasePropType<ColorObject> {
|
||||
type: 'color'
|
||||
default: ColorObject
|
||||
}
|
||||
|
||||
export interface PropTypeConfigOpts<ValueType> {
|
||||
interface CommonOpts {
|
||||
label?: string
|
||||
sanitize?: Sanitizer<ValueType>
|
||||
interpolate?: Interpolator<ValueType>
|
||||
}
|
||||
|
||||
export interface PropTypeConfig_String extends IBasePropType<string> {
|
||||
|
|
|
@ -8,14 +8,16 @@ 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 type InterpolationTriple = {
|
||||
left: unknown
|
||||
right?: unknown
|
||||
progression: number
|
||||
}
|
||||
|
||||
export default function trackValueAtTime(
|
||||
export default function interpolationTripleAtPosition(
|
||||
trackP: Pointer<TrackData | undefined>,
|
||||
timeD: IDerivation<number>,
|
||||
): IDerivation<KeyframeValueAtTime> {
|
||||
): IDerivation<InterpolationTriple | undefined> {
|
||||
return prism(() => {
|
||||
const track = val(trackP)
|
||||
const driverD = prism.memo(
|
||||
|
@ -24,7 +26,7 @@ export default function trackValueAtTime(
|
|||
if (!track) {
|
||||
return new ConstantDerivation(undefined)
|
||||
} else if (track.type === 'BasicKeyframedTrack') {
|
||||
return trackValueAtTime_keyframedTrack(track, timeD)
|
||||
return _forKeyframedTrack(track, timeD)
|
||||
} else {
|
||||
logger.error(`Track type not yet supported.`)
|
||||
return new ConstantDerivation(undefined)
|
||||
|
@ -41,15 +43,15 @@ type IStartedState = {
|
|||
started: true
|
||||
validFrom: number
|
||||
validTo: number
|
||||
der: IDerivation<KeyframeValueAtTime>
|
||||
der: IDerivation<InterpolationTriple | undefined>
|
||||
}
|
||||
|
||||
type IState = {started: false} | IStartedState
|
||||
|
||||
function trackValueAtTime_keyframedTrack(
|
||||
function _forKeyframedTrack(
|
||||
track: BasicKeyframedTrack,
|
||||
timeD: IDerivation<number>,
|
||||
): IDerivation<KeyframeValueAtTime> {
|
||||
): IDerivation<InterpolationTriple | undefined> {
|
||||
return prism(() => {
|
||||
let stateRef = prism.ref<IState>('state', {started: false})
|
||||
let state = stateRef.current
|
|
@ -1,5 +1,5 @@
|
|||
import type {KeyframeValueAtTime} from '@theatre/core/sequences/trackValueAtTime'
|
||||
import trackValueAtTime from '@theatre/core/sequences/trackValueAtTime'
|
||||
import type {InterpolationTriple} from '@theatre/core/sequences/interpolationTripleAtPosition'
|
||||
import interpolationTripleAtPosition from '@theatre/core/sequences/interpolationTripleAtPosition'
|
||||
import type Sheet from '@theatre/core/sheets/Sheet'
|
||||
import type {SheetObjectAddress} from '@theatre/shared/utils/addresses'
|
||||
import deepMergeWithCache from '@theatre/shared/utils/deepMergeWithCache'
|
||||
|
@ -22,8 +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'
|
||||
import type {Interpolator} from '@theatre/core/propTypes'
|
||||
import {getPropConfigByPath} from '@theatre/shared/propTypes/utils'
|
||||
|
||||
// type Everything = {
|
||||
// final: SerializableMap
|
||||
|
@ -154,35 +154,33 @@ export default class SheetObject implements IdentityDerivationProvider {
|
|||
|
||||
for (const {trackId, pathToProp} of tracksToProcess) {
|
||||
const derivation = this._trackIdToDerivation(trackId)
|
||||
const propConfig = getPropConfigByPath(
|
||||
this.template.config,
|
||||
pathToProp,
|
||||
)!
|
||||
|
||||
const sanitize = propConfig.sanitize!
|
||||
const interpolate =
|
||||
propConfig.interpolate! as Interpolator<$IntentionalAny>
|
||||
|
||||
const updateSequenceValueFromItsDerivation = () => {
|
||||
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>
|
||||
const triple = derivation.getValue()
|
||||
|
||||
if (!triple)
|
||||
return valsAtom.setIn(pathToProp, propConfig!.default)
|
||||
|
||||
const left = sanitize(triple.left) || propConfig.default
|
||||
|
||||
if (triple.right === undefined)
|
||||
return valsAtom.setIn(pathToProp, left)
|
||||
|
||||
const right = sanitize(triple.right) || propConfig.default
|
||||
|
||||
return valsAtom.setIn(
|
||||
pathToProp,
|
||||
interpolate(value.left, value.right, value.progression),
|
||||
interpolate(left, right, triple.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()
|
||||
.tap(updateSequenceValueFromItsDerivation)
|
||||
|
@ -205,12 +203,14 @@ export default class SheetObject implements IdentityDerivationProvider {
|
|||
|
||||
protected _trackIdToDerivation(
|
||||
trackId: SequenceTrackId,
|
||||
): IDerivation<KeyframeValueAtTime | undefined> {
|
||||
): IDerivation<InterpolationTriple | 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)
|
||||
|
||||
return interpolationTripleAtPosition(trackP, timeD)
|
||||
}
|
||||
|
||||
get propsP(): Pointer<$FixMe> {
|
||||
|
|
|
@ -129,8 +129,7 @@ const PrimitivePropRow: React.FC<{
|
|||
}, [leaf])
|
||||
|
||||
const label = leaf.pathToProp[leaf.pathToProp.length - 1]
|
||||
const isSelectable =
|
||||
leaf.propConf.type !== 'boolean' && leaf.propConf.type !== 'stringLiteral'
|
||||
const isSelectable = true
|
||||
|
||||
return (
|
||||
<Container depth={leaf.depth}>
|
||||
|
|
|
@ -8,12 +8,10 @@ import type {SequenceTrackId} from '@theatre/shared/utils/ids'
|
|||
import type {$IntentionalAny, VoidFn} from '@theatre/shared/utils/types'
|
||||
import type {Pointer} from '@theatre/dataverse'
|
||||
import React, {useMemo, useRef, useState} from 'react'
|
||||
import styled from 'styled-components'
|
||||
import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout'
|
||||
import {graphEditorColors} from '@theatre/studio/panels/SequenceEditorPanel/GraphEditor/GraphEditor'
|
||||
import KeyframeEditor from './KeyframeEditor/KeyframeEditor'
|
||||
|
||||
const Container = styled.div``
|
||||
import {getPropConfigByPath} from '@theatre/shared/propTypes/utils'
|
||||
|
||||
export type ExtremumSpace = {
|
||||
fromValueSpace: (v: number) => number
|
||||
|
@ -29,7 +27,13 @@ const BasicKeyframedTrack: React.FC<{
|
|||
trackId: SequenceTrackId
|
||||
trackData: TrackData
|
||||
color: keyof typeof graphEditorColors
|
||||
}> = React.memo(({layoutP, trackData, sheetObject, trackId, color}) => {
|
||||
}> = React.memo(
|
||||
({layoutP, trackData, sheetObject, trackId, color, pathToProp}) => {
|
||||
const propConfig = getPropConfigByPath(
|
||||
sheetObject.template.config,
|
||||
pathToProp,
|
||||
)!
|
||||
|
||||
const [areExtremumsLocked, setAreExtremumsLocked] = useState<boolean>(false)
|
||||
const lockExtremums = useMemo(() => {
|
||||
const locks = new Set<VoidFn>()
|
||||
|
@ -49,7 +53,10 @@ const BasicKeyframedTrack: React.FC<{
|
|||
}, [])
|
||||
|
||||
const extremumSpace: ExtremumSpace = useMemo(() => {
|
||||
const extremums = calculateExtremums(trackData.keyframes)
|
||||
const extremums =
|
||||
propConfig.isScalar === true
|
||||
? calculateScalarExtremums(trackData.keyframes)
|
||||
: calculateNonScalarExtremums(trackData.keyframes)
|
||||
|
||||
const fromValueSpace = (val: number): number =>
|
||||
(val - extremums[0]) / (extremums[1] - extremums[0])
|
||||
|
@ -99,13 +106,14 @@ const BasicKeyframedTrack: React.FC<{
|
|||
{keyframeEditors}
|
||||
</g>
|
||||
)
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
export default BasicKeyframedTrack
|
||||
|
||||
type Extremums = [min: number, max: number]
|
||||
|
||||
function calculateExtremums(keyframes: Keyframe[]): Extremums {
|
||||
function calculateScalarExtremums(keyframes: Keyframe[]): Extremums {
|
||||
let min = Infinity,
|
||||
max = -Infinity
|
||||
|
||||
|
@ -127,3 +135,23 @@ function calculateExtremums(keyframes: Keyframe[]): Extremums {
|
|||
|
||||
return [min, max]
|
||||
}
|
||||
|
||||
function calculateNonScalarExtremums(keyframes: Keyframe[]): Extremums {
|
||||
let min = 0,
|
||||
max = 1
|
||||
|
||||
function check(n: number): void {
|
||||
min = Math.min(n, min)
|
||||
max = Math.max(n, max)
|
||||
}
|
||||
|
||||
keyframes.forEach((cur, i) => {
|
||||
if (!cur.connectedRight) return
|
||||
const next = keyframes[i + 1]
|
||||
if (!next) return
|
||||
check(cur.handles[3])
|
||||
check(next.handles[1])
|
||||
})
|
||||
|
||||
return [min, max]
|
||||
}
|
||||
|
|
|
@ -6,13 +6,9 @@ import {usePrism} from '@theatre/react'
|
|||
import type {Pointer} from '@theatre/dataverse'
|
||||
import {val} from '@theatre/dataverse'
|
||||
import React from 'react'
|
||||
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``
|
||||
|
||||
const PrimitivePropGraph: React.FC<{
|
||||
layoutP: Pointer<SequenceEditorPanelLayout>
|
||||
|
@ -36,9 +32,7 @@ const PrimitivePropGraph: React.FC<{
|
|||
)
|
||||
return <></>
|
||||
} else {
|
||||
return (
|
||||
<BasicKeyframedTrack {...props} trackData={trackData as TrackData} />
|
||||
)
|
||||
return <BasicKeyframedTrack {...props} trackData={trackData} />
|
||||
}
|
||||
}, [props.trackId, props.layoutP])
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue