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 type {$IntentionalAny} from '@theatre/shared/utils/types'
|
||||||
import userReadableTypeOfValue from '@theatre/shared/utils/userReadableTypeOfValue'
|
import userReadableTypeOfValue from '@theatre/shared/utils/userReadableTypeOfValue'
|
||||||
|
import {mapValues} from 'lodash-es'
|
||||||
import type {
|
import type {
|
||||||
IShorthandCompoundProps,
|
IShorthandCompoundProps,
|
||||||
IValidCompoundProps,
|
IValidCompoundProps,
|
||||||
|
@ -8,10 +9,7 @@ import type {
|
||||||
import {sanitizeCompoundProps} from './internals'
|
import {sanitizeCompoundProps} from './internals'
|
||||||
import {propTypeSymbol} from './internals'
|
import {propTypeSymbol} from './internals'
|
||||||
|
|
||||||
const validateCommonOpts = <T>(
|
const validateCommonOpts = (fnCallSignature: string, opts?: CommonOpts) => {
|
||||||
fnCallSignature: string,
|
|
||||||
opts?: PropTypeConfigOpts<T>,
|
|
||||||
) => {
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
if (opts === undefined) return
|
if (opts === undefined) return
|
||||||
if (typeof opts !== 'object' || opts === null) {
|
if (typeof opts !== 'object' || opts === null) {
|
||||||
|
@ -70,20 +68,22 @@ const validateCommonOpts = <T>(
|
||||||
*/
|
*/
|
||||||
export const compound = <Props extends IShorthandCompoundProps>(
|
export const compound = <Props extends IShorthandCompoundProps>(
|
||||||
props: Props,
|
props: Props,
|
||||||
opts?: PropTypeConfigOpts<Props>,
|
opts?: {
|
||||||
|
label?: string
|
||||||
|
},
|
||||||
): PropTypeConfig_Compound<
|
): PropTypeConfig_Compound<
|
||||||
ShorthandCompoundPropsToLonghandCompoundProps<Props>,
|
ShorthandCompoundPropsToLonghandCompoundProps<Props>,
|
||||||
Props
|
Props
|
||||||
> => {
|
> => {
|
||||||
validateCommonOpts('t.compound(props, opts)', opts)
|
validateCommonOpts('t.compound(props, opts)', opts)
|
||||||
|
const sanitizedProps = sanitizeCompoundProps(props)
|
||||||
return {
|
return {
|
||||||
type: 'compound',
|
type: 'compound',
|
||||||
props: sanitizeCompoundProps(props),
|
props: sanitizedProps,
|
||||||
valueType: null as $IntentionalAny,
|
valueType: null as $IntentionalAny,
|
||||||
[propTypeSymbol]: 'TheatrePropType',
|
[propTypeSymbol]: 'TheatrePropType',
|
||||||
label: opts?.label,
|
label: opts?.label,
|
||||||
sanitize: opts?.sanitize,
|
default: mapValues(sanitizedProps, (p) => p.default) as $IntentionalAny,
|
||||||
interpolate: opts?.interpolate,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,7 +134,10 @@ export const number = (
|
||||||
nudgeFn?: PropTypeConfig_Number['nudgeFn']
|
nudgeFn?: PropTypeConfig_Number['nudgeFn']
|
||||||
range?: PropTypeConfig_Number['range']
|
range?: PropTypeConfig_Number['range']
|
||||||
nudgeMultiplier?: number
|
nudgeMultiplier?: number
|
||||||
} & PropTypeConfigOpts<number>,
|
label?: string
|
||||||
|
sanitize?: Sanitizer<number>
|
||||||
|
interpolate?: Interpolator<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)
|
||||||
|
@ -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 {
|
return {
|
||||||
type: 'number',
|
type: 'number',
|
||||||
valueType: 0,
|
valueType: 0,
|
||||||
|
@ -206,17 +218,22 @@ export const number = (
|
||||||
nudgeMultiplier:
|
nudgeMultiplier:
|
||||||
typeof opts?.nudgeMultiplier === 'number' ? opts.nudgeMultiplier : 1,
|
typeof opts?.nudgeMultiplier === 'number' ? opts.nudgeMultiplier : 1,
|
||||||
isScalar: true,
|
isScalar: true,
|
||||||
sanitize(value) {
|
sanitize: sanitize,
|
||||||
if (opts?.sanitize) return opts.sanitize(value)
|
interpolate: opts?.interpolate ?? _interpolateNumber,
|
||||||
return typeof value === 'number' ? value : undefined
|
|
||||||
},
|
|
||||||
interpolate(left, right, progression) {
|
|
||||||
if (opts?.interpolate) return opts.interpolate(left, right, progression)
|
|
||||||
return left + progression * (right - left)
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
* A boolean prop type
|
||||||
*
|
*
|
||||||
|
@ -239,7 +256,11 @@ export const number = (
|
||||||
*/
|
*/
|
||||||
export const boolean = (
|
export const boolean = (
|
||||||
defaultValue: boolean,
|
defaultValue: boolean,
|
||||||
opts?: PropTypeConfigOpts<boolean>,
|
opts?: {
|
||||||
|
label?: string
|
||||||
|
sanitize?: Sanitizer<boolean>
|
||||||
|
interpolate?: Interpolator<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)
|
||||||
|
@ -251,23 +272,26 @@ export const boolean = (
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
default: defaultValue,
|
default: defaultValue,
|
||||||
valueType: null as $IntentionalAny,
|
valueType: null as $IntentionalAny,
|
||||||
[propTypeSymbol]: 'TheatrePropType',
|
[propTypeSymbol]: 'TheatrePropType',
|
||||||
label: opts?.label,
|
label: opts?.label,
|
||||||
sanitize(value: unknown) {
|
sanitize: _sanitizeBoolean,
|
||||||
if (opts?.sanitize) return opts.sanitize(value)
|
interpolate: leftInterpolate,
|
||||||
return typeof value === 'boolean' ? value : undefined
|
|
||||||
},
|
|
||||||
interpolate(left, right, progression) {
|
|
||||||
if (opts?.interpolate) return opts.interpolate(left, right, progression)
|
|
||||||
return left
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const _sanitizeBoolean = (val: unknown): boolean | undefined => {
|
||||||
|
return typeof val === 'boolean' ? val : undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
function leftInterpolate<T>(left: T): T {
|
||||||
|
return left
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A string prop type
|
* A string prop type
|
||||||
*
|
*
|
||||||
|
@ -291,7 +315,11 @@ export const boolean = (
|
||||||
*/
|
*/
|
||||||
export const string = (
|
export const string = (
|
||||||
defaultValue: string,
|
defaultValue: string,
|
||||||
opts?: PropTypeConfigOpts<string>,
|
opts?: {
|
||||||
|
label?: string
|
||||||
|
sanitize?: Sanitizer<string>
|
||||||
|
interpolate?: Interpolator<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)
|
||||||
|
@ -313,7 +341,7 @@ export const string = (
|
||||||
if (opts?.sanitize) return opts.sanitize(value)
|
if (opts?.sanitize) return opts.sanitize(value)
|
||||||
return typeof value === 'string' ? value : undefined
|
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 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'; label?: string},
|
||||||
Extract<keyof Opts, string>
|
|
||||||
>,
|
|
||||||
): PropTypeConfig_StringLiteral<Extract<keyof Opts, string>> {
|
): PropTypeConfig_StringLiteral<Extract<keyof Opts, string>> {
|
||||||
return {
|
return {
|
||||||
type: 'stringLiteral',
|
type: 'stringLiteral',
|
||||||
|
@ -364,15 +390,14 @@ export function stringLiteral<Opts extends {[key in string]: string}>(
|
||||||
as: opts?.as ?? 'menu',
|
as: opts?.as ?? 'menu',
|
||||||
label: opts?.label,
|
label: opts?.label,
|
||||||
sanitize(value: unknown) {
|
sanitize(value: unknown) {
|
||||||
if (opts?.sanitize) return opts.sanitize(value)
|
if (typeof value !== 'string') return undefined
|
||||||
return typeof value === 'string' && Object.keys(options).includes(value)
|
if (Object.hasOwnProperty.call(options, value)) {
|
||||||
? (value as Extract<keyof Opts, string>)
|
return value as $IntentionalAny
|
||||||
: undefined
|
} else {
|
||||||
},
|
return undefined
|
||||||
interpolate(left, right, progression) {
|
}
|
||||||
if (opts?.interpolate) return opts.interpolate(left, right, progression)
|
|
||||||
return left
|
|
||||||
},
|
},
|
||||||
|
interpolate: leftInterpolate,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -386,6 +411,7 @@ interface IBasePropType<ValueType, PropTypes = ValueType> {
|
||||||
isScalar?: true
|
isScalar?: true
|
||||||
sanitize?: Sanitizer<PropTypes>
|
sanitize?: Sanitizer<PropTypes>
|
||||||
interpolate?: Interpolator<PropTypes>
|
interpolate?: Interpolator<PropTypes>
|
||||||
|
default: ValueType
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PropTypeConfig_Number extends IBasePropType<number> {
|
export interface PropTypeConfig_Number extends IBasePropType<number> {
|
||||||
|
@ -424,16 +450,8 @@ export interface PropTypeConfig_Boolean extends IBasePropType<boolean> {
|
||||||
default: boolean
|
default: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PropTypeConfig_Color<ColorObject>
|
interface CommonOpts {
|
||||||
extends IBasePropType<ColorObject> {
|
|
||||||
type: 'color'
|
|
||||||
default: ColorObject
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PropTypeConfigOpts<ValueType> {
|
|
||||||
label?: string
|
label?: string
|
||||||
sanitize?: Sanitizer<ValueType>
|
|
||||||
interpolate?: Interpolator<ValueType>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PropTypeConfig_String extends IBasePropType<string> {
|
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 logger from '@theatre/shared/logger'
|
||||||
import UnitBezier from 'timing-function/lib/UnitBezier'
|
import UnitBezier from 'timing-function/lib/UnitBezier'
|
||||||
|
|
||||||
export type KeyframeValueAtTime =
|
export type InterpolationTriple = {
|
||||||
| {left: unknown; right?: unknown; progression: number}
|
left: unknown
|
||||||
| undefined
|
right?: unknown
|
||||||
|
progression: number
|
||||||
|
}
|
||||||
|
|
||||||
export default function trackValueAtTime(
|
export default function interpolationTripleAtPosition(
|
||||||
trackP: Pointer<TrackData | undefined>,
|
trackP: Pointer<TrackData | undefined>,
|
||||||
timeD: IDerivation<number>,
|
timeD: IDerivation<number>,
|
||||||
): IDerivation<KeyframeValueAtTime> {
|
): IDerivation<InterpolationTriple | undefined> {
|
||||||
return prism(() => {
|
return prism(() => {
|
||||||
const track = val(trackP)
|
const track = val(trackP)
|
||||||
const driverD = prism.memo(
|
const driverD = prism.memo(
|
||||||
|
@ -24,7 +26,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_keyframedTrack(track, timeD)
|
return _forKeyframedTrack(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)
|
||||||
|
@ -41,15 +43,15 @@ type IStartedState = {
|
||||||
started: true
|
started: true
|
||||||
validFrom: number
|
validFrom: number
|
||||||
validTo: number
|
validTo: number
|
||||||
der: IDerivation<KeyframeValueAtTime>
|
der: IDerivation<InterpolationTriple | undefined>
|
||||||
}
|
}
|
||||||
|
|
||||||
type IState = {started: false} | IStartedState
|
type IState = {started: false} | IStartedState
|
||||||
|
|
||||||
function trackValueAtTime_keyframedTrack(
|
function _forKeyframedTrack(
|
||||||
track: BasicKeyframedTrack,
|
track: BasicKeyframedTrack,
|
||||||
timeD: IDerivation<number>,
|
timeD: IDerivation<number>,
|
||||||
): IDerivation<KeyframeValueAtTime> {
|
): IDerivation<InterpolationTriple | undefined> {
|
||||||
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
|
|
@ -1,5 +1,5 @@
|
||||||
import type {KeyframeValueAtTime} from '@theatre/core/sequences/trackValueAtTime'
|
import type {InterpolationTriple} from '@theatre/core/sequences/interpolationTripleAtPosition'
|
||||||
import trackValueAtTime from '@theatre/core/sequences/trackValueAtTime'
|
import interpolationTripleAtPosition from '@theatre/core/sequences/interpolationTripleAtPosition'
|
||||||
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'
|
||||||
import deepMergeWithCache from '@theatre/shared/utils/deepMergeWithCache'
|
import deepMergeWithCache from '@theatre/shared/utils/deepMergeWithCache'
|
||||||
|
@ -22,8 +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} from '@theatre/core/propTypes'
|
||||||
import type {Interpolator, PropTypeConfig} from '@theatre/core/propTypes'
|
import {getPropConfigByPath} from '@theatre/shared/propTypes/utils'
|
||||||
|
|
||||||
// type Everything = {
|
// type Everything = {
|
||||||
// final: SerializableMap
|
// final: SerializableMap
|
||||||
|
@ -154,34 +154,32 @@ export default class SheetObject implements IdentityDerivationProvider {
|
||||||
|
|
||||||
for (const {trackId, pathToProp} of tracksToProcess) {
|
for (const {trackId, pathToProp} of tracksToProcess) {
|
||||||
const derivation = this._trackIdToDerivation(trackId)
|
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 updateSequenceValueFromItsDerivation = () => {
|
||||||
const propConfig = get(this.template.config.props, pathToProp) as
|
const triple = derivation.getValue()
|
||||||
| PropTypeConfig
|
|
||||||
| undefined
|
if (!triple)
|
||||||
const value: KeyframeValueAtTime = derivation.getValue()
|
return valsAtom.setIn(pathToProp, propConfig!.default)
|
||||||
if (!value) return valsAtom.setIn(pathToProp, value)
|
|
||||||
if (value.right === undefined)
|
const left = sanitize(triple.left) || propConfig.default
|
||||||
return valsAtom.setIn(pathToProp, value.left)
|
|
||||||
if (propConfig?.interpolate) {
|
if (triple.right === undefined)
|
||||||
const interpolate =
|
return valsAtom.setIn(pathToProp, left)
|
||||||
propConfig.interpolate as Interpolator<unknown>
|
|
||||||
return valsAtom.setIn(
|
const right = sanitize(triple.right) || propConfig.default
|
||||||
pathToProp,
|
|
||||||
interpolate(value.left, value.right, value.progression),
|
return valsAtom.setIn(
|
||||||
)
|
pathToProp,
|
||||||
}
|
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
|
const untap = derivation
|
||||||
.changesWithoutValues()
|
.changesWithoutValues()
|
||||||
|
@ -205,12 +203,14 @@ export default class SheetObject implements IdentityDerivationProvider {
|
||||||
|
|
||||||
protected _trackIdToDerivation(
|
protected _trackIdToDerivation(
|
||||||
trackId: SequenceTrackId,
|
trackId: SequenceTrackId,
|
||||||
): IDerivation<KeyframeValueAtTime | undefined> {
|
): IDerivation<InterpolationTriple | 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 interpolationTripleAtPosition(trackP, timeD)
|
||||||
}
|
}
|
||||||
|
|
||||||
get propsP(): Pointer<$FixMe> {
|
get propsP(): Pointer<$FixMe> {
|
||||||
|
|
|
@ -129,8 +129,7 @@ const PrimitivePropRow: React.FC<{
|
||||||
}, [leaf])
|
}, [leaf])
|
||||||
|
|
||||||
const label = leaf.pathToProp[leaf.pathToProp.length - 1]
|
const label = leaf.pathToProp[leaf.pathToProp.length - 1]
|
||||||
const isSelectable =
|
const isSelectable = true
|
||||||
leaf.propConf.type !== 'boolean' && leaf.propConf.type !== 'stringLiteral'
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container depth={leaf.depth}>
|
<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 {$IntentionalAny, VoidFn} from '@theatre/shared/utils/types'
|
||||||
import type {Pointer} from '@theatre/dataverse'
|
import type {Pointer} from '@theatre/dataverse'
|
||||||
import React, {useMemo, useRef, useState} from 'react'
|
import React, {useMemo, useRef, useState} from 'react'
|
||||||
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 {graphEditorColors} from '@theatre/studio/panels/SequenceEditorPanel/GraphEditor/GraphEditor'
|
import {graphEditorColors} from '@theatre/studio/panels/SequenceEditorPanel/GraphEditor/GraphEditor'
|
||||||
import KeyframeEditor from './KeyframeEditor/KeyframeEditor'
|
import KeyframeEditor from './KeyframeEditor/KeyframeEditor'
|
||||||
|
import {getPropConfigByPath} from '@theatre/shared/propTypes/utils'
|
||||||
const Container = styled.div``
|
|
||||||
|
|
||||||
export type ExtremumSpace = {
|
export type ExtremumSpace = {
|
||||||
fromValueSpace: (v: number) => number
|
fromValueSpace: (v: number) => number
|
||||||
|
@ -29,83 +27,93 @@ const BasicKeyframedTrack: React.FC<{
|
||||||
trackId: SequenceTrackId
|
trackId: SequenceTrackId
|
||||||
trackData: TrackData
|
trackData: TrackData
|
||||||
color: keyof typeof graphEditorColors
|
color: keyof typeof graphEditorColors
|
||||||
}> = React.memo(({layoutP, trackData, sheetObject, trackId, color}) => {
|
}> = React.memo(
|
||||||
const [areExtremumsLocked, setAreExtremumsLocked] = useState<boolean>(false)
|
({layoutP, trackData, sheetObject, trackId, color, pathToProp}) => {
|
||||||
const lockExtremums = useMemo(() => {
|
const propConfig = getPropConfigByPath(
|
||||||
const locks = new Set<VoidFn>()
|
sheetObject.template.config,
|
||||||
return function lockExtremums() {
|
pathToProp,
|
||||||
const shouldLock = locks.size === 0
|
)!
|
||||||
locks.add(unlock)
|
|
||||||
if (shouldLock) setAreExtremumsLocked(true)
|
|
||||||
|
|
||||||
function unlock() {
|
const [areExtremumsLocked, setAreExtremumsLocked] = useState<boolean>(false)
|
||||||
const wasLocked = locks.size > 0
|
const lockExtremums = useMemo(() => {
|
||||||
locks.delete(unlock)
|
const locks = new Set<VoidFn>()
|
||||||
if (wasLocked && locks.size === 0) setAreExtremumsLocked(false)
|
return function lockExtremums() {
|
||||||
|
const shouldLock = locks.size === 0
|
||||||
|
locks.add(unlock)
|
||||||
|
if (shouldLock) setAreExtremumsLocked(true)
|
||||||
|
|
||||||
|
function unlock() {
|
||||||
|
const wasLocked = locks.size > 0
|
||||||
|
locks.delete(unlock)
|
||||||
|
if (wasLocked && locks.size === 0) setAreExtremumsLocked(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
return unlock
|
||||||
}
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
return unlock
|
const extremumSpace: ExtremumSpace = useMemo(() => {
|
||||||
|
const extremums =
|
||||||
|
propConfig.isScalar === true
|
||||||
|
? calculateScalarExtremums(trackData.keyframes)
|
||||||
|
: calculateNonScalarExtremums(trackData.keyframes)
|
||||||
|
|
||||||
|
const fromValueSpace = (val: number): number =>
|
||||||
|
(val - extremums[0]) / (extremums[1] - extremums[0])
|
||||||
|
|
||||||
|
const toValueSpace = (ex: number): number =>
|
||||||
|
extremums[0] + deltaToValueSpace(ex)
|
||||||
|
|
||||||
|
const deltaToValueSpace = (ex: number): number =>
|
||||||
|
ex * (extremums[1] - extremums[0])
|
||||||
|
|
||||||
|
return {
|
||||||
|
fromValueSpace,
|
||||||
|
toValueSpace,
|
||||||
|
deltaToValueSpace,
|
||||||
|
lock: lockExtremums,
|
||||||
|
}
|
||||||
|
}, [trackData.keyframes])
|
||||||
|
|
||||||
|
const cachedExtremumSpace = useRef<ExtremumSpace>(
|
||||||
|
undefined as $IntentionalAny,
|
||||||
|
)
|
||||||
|
if (!areExtremumsLocked) {
|
||||||
|
cachedExtremumSpace.current = extremumSpace
|
||||||
}
|
}
|
||||||
}, [])
|
|
||||||
|
|
||||||
const extremumSpace: ExtremumSpace = useMemo(() => {
|
const keyframeEditors = trackData.keyframes.map((kf, index) => (
|
||||||
const extremums = calculateExtremums(trackData.keyframes)
|
<KeyframeEditor
|
||||||
|
keyframe={kf}
|
||||||
|
index={index}
|
||||||
|
trackData={trackData}
|
||||||
|
layoutP={layoutP}
|
||||||
|
sheetObject={sheetObject}
|
||||||
|
trackId={trackId}
|
||||||
|
key={'keyframe-' + kf.id}
|
||||||
|
extremumSpace={cachedExtremumSpace.current}
|
||||||
|
color={color}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
|
||||||
const fromValueSpace = (val: number): number =>
|
return (
|
||||||
(val - extremums[0]) / (extremums[1] - extremums[0])
|
<g
|
||||||
|
style={{
|
||||||
const toValueSpace = (ex: number): number =>
|
// @ts-ignore
|
||||||
extremums[0] + deltaToValueSpace(ex)
|
'--main-color': graphEditorColors[color].iconColor,
|
||||||
|
}}
|
||||||
const deltaToValueSpace = (ex: number): number =>
|
>
|
||||||
ex * (extremums[1] - extremums[0])
|
{keyframeEditors}
|
||||||
|
</g>
|
||||||
return {
|
)
|
||||||
fromValueSpace,
|
},
|
||||||
toValueSpace,
|
)
|
||||||
deltaToValueSpace,
|
|
||||||
lock: lockExtremums,
|
|
||||||
}
|
|
||||||
}, [trackData.keyframes])
|
|
||||||
|
|
||||||
const cachedExtremumSpace = useRef<ExtremumSpace>(
|
|
||||||
undefined as $IntentionalAny,
|
|
||||||
)
|
|
||||||
if (!areExtremumsLocked) {
|
|
||||||
cachedExtremumSpace.current = extremumSpace
|
|
||||||
}
|
|
||||||
|
|
||||||
const keyframeEditors = trackData.keyframes.map((kf, index) => (
|
|
||||||
<KeyframeEditor
|
|
||||||
keyframe={kf}
|
|
||||||
index={index}
|
|
||||||
trackData={trackData}
|
|
||||||
layoutP={layoutP}
|
|
||||||
sheetObject={sheetObject}
|
|
||||||
trackId={trackId}
|
|
||||||
key={'keyframe-' + kf.id}
|
|
||||||
extremumSpace={cachedExtremumSpace.current}
|
|
||||||
color={color}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
|
|
||||||
return (
|
|
||||||
<g
|
|
||||||
style={{
|
|
||||||
// @ts-ignore
|
|
||||||
'--main-color': graphEditorColors[color].iconColor,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{keyframeEditors}
|
|
||||||
</g>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
export default BasicKeyframedTrack
|
export default BasicKeyframedTrack
|
||||||
|
|
||||||
type Extremums = [min: number, max: number]
|
type Extremums = [min: number, max: number]
|
||||||
|
|
||||||
function calculateExtremums(keyframes: Keyframe[]): Extremums {
|
function calculateScalarExtremums(keyframes: Keyframe[]): Extremums {
|
||||||
let min = Infinity,
|
let min = Infinity,
|
||||||
max = -Infinity
|
max = -Infinity
|
||||||
|
|
||||||
|
@ -127,3 +135,23 @@ function calculateExtremums(keyframes: Keyframe[]): Extremums {
|
||||||
|
|
||||||
return [min, max]
|
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 type {Pointer} from '@theatre/dataverse'
|
||||||
import {val} from '@theatre/dataverse'
|
import {val} from '@theatre/dataverse'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
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 PrimitivePropGraph: React.FC<{
|
const PrimitivePropGraph: React.FC<{
|
||||||
layoutP: Pointer<SequenceEditorPanelLayout>
|
layoutP: Pointer<SequenceEditorPanelLayout>
|
||||||
|
@ -36,9 +32,7 @@ const PrimitivePropGraph: React.FC<{
|
||||||
)
|
)
|
||||||
return <></>
|
return <></>
|
||||||
} else {
|
} else {
|
||||||
return (
|
return <BasicKeyframedTrack {...props} trackData={trackData} />
|
||||||
<BasicKeyframedTrack {...props} trackData={trackData as TrackData} />
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}, [props.trackId, props.layoutP])
|
}, [props.trackId, props.layoutP])
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue