Compare commits

...

2 commits

Author SHA1 Message Date
themancalledjakob
78a67ee665 add experimental keyframe functions 2024-04-12 19:15:59 +02:00
themancalledjakob
7874d3c291 draw booleans as 0-1 2024-04-12 19:15:01 +02:00
4 changed files with 496 additions and 8 deletions

View file

@ -1,13 +1,26 @@
import get from 'lodash-es/get'
import type {IProject, IRafDriver, ISheet, ISheetObject} from '@theatre/core'
import type {Prism, Pointer} from '@theatre/dataverse'
import {prism} from '@theatre/dataverse'
import {getPointerParts, prism, val} from '@theatre/dataverse'
import SimpleCache from '@theatre/shared/utils/SimpleCache'
import type {$IntentionalAny, VoidFn} from '@theatre/shared/utils/types'
import {
getPropConfigByPath,
isPropConfigComposite,
iteratePropType,
} from '@theatre/shared/propTypes/utils'
import type {PropTypeConfig} from '@theatre/core/propTypes'
import type {PathToProp} from '@theatre/shared/src/utils/addresses'
import type {KeyframeId, SequenceTrackId} from '@theatre/shared/utils/ids'
import pointerDeep from '@theatre/shared/utils/pointerDeep'
import forEachPropDeep from '@theatre/shared/utils/forEachDeep'
import getDeep from '@theatre/shared/utils/getDeep'
import type {$FixMe, $IntentionalAny, VoidFn} from '@theatre/shared/utils/types'
import type {IScrub} from '@theatre/studio/Scrub'
import type {Studio} from '@theatre/studio/Studio'
import {
isSheetObjectPublicAPI,
isSheetPublicAPI,
isSheetObject,
} from '@theatre/shared/instanceTypes'
import {getOutlineSelection} from './selectors'
import type SheetObject from '@theatre/core/sheetObjects/SheetObject'
@ -78,6 +91,18 @@ export interface ITransactionAPI {
* Makes Theatre forget about this sheet.
*/
__experimental_forgetSheet(sheet: TheatreSheet): void
__experimental_sequenceProp<V>(pointer: Pointer<V>): void
__experimental_staticProp<V>(pointer: Pointer<V>): void
__experimental_deleteKeyframes<V>(
pointer: Pointer<V>,
from: number,
to: number,
): void
__experimental_addKeyframes<V>(pointer: Pointer<V>, keyframes: []): void
// transaction
//
//__experimental_setSequenceDuration<V>(pointer: Pointer<V>, duration: number): void
}
/**
*
@ -462,6 +487,14 @@ export interface IStudio {
__experimental_createContentOfSaveFileTyped(
projectId: string,
): __UNSTABLE_Project_OnDiskState
__experimental_setPropAsSequenced<V>(prop: Pointer<V>): void
__experimental_setPropAsStatic<V>(prop: Pointer<V>): void
__experimental_isPropSequenced<V>(prop: Pointer<V>): boolean
// __experimental
//
//__experimental_setSequenceDuration<V>(prop: Pointer<V>, duration: number): boolean
}
}
@ -507,6 +540,97 @@ export default class TheatreStudio implements IStudio {
): __UNSTABLE_Project_OnDiskState {
return getStudio().createContentOfSaveFile(projectId) as $IntentionalAny
},
// WARNING: for some reason this is not immediately applied
// but needs a timeout.. urgh
__experimental_setPropAsSequenced<V>(prop: Pointer<V>): void {
const {path, root} = getPointerParts(prop)
if (!isSheetObject(root)) {
throw new Error(
'Argument prop must be a pointer to a SheetObject property',
)
}
const propAddress = {...root.address, pathToProp: path}
const propConfig = getPropConfigByPath(root.template.staticConfig, path)
if (typeof propConfig !== 'undefined')
getStudio()!.transaction(({stateEditors}) => {
stateEditors.coreByProject.historic.sheetsById.sequence.setPrimitivePropAsSequenced(
propAddress,
propConfig,
)
console.log('inner setPropAsSequenced', performance.now())
})
},
__experimental_setPropAsStatic<V>(prop: Pointer<V>): void {
const {path, root} = getPointerParts(prop)
if (!isSheetObject(root)) {
throw new Error(
'Argument prop must be a pointer to a SheetObject property',
)
}
const propAddress = {...root.address, pathToProp: path}
const propConfig = getPropConfigByPath(root.template.staticConfig, path)
if (typeof propConfig !== 'undefined') {
for (const {path: subPath, conf} of iteratePropType(propConfig, [])) {
if (isPropConfigComposite(conf)) continue
getStudio()!.transaction(({stateEditors}) => {
const pointerToSub = pointerDeep(prop, subPath)
stateEditors.coreByProject.historic.sheetsById.sequence.setPrimitivePropAsStatic(
{
...propAddress,
value: root.getValueByPointer(pointerToSub as $IntentionalAny),
},
)
})
}
}
},
__experimental_isPropSequenced<V>(prop: Pointer<V>): boolean {
const {path, root} = getPointerParts(prop)
if (!isSheetObject(root)) {
throw new Error(
'Argument prop must be a pointer to a SheetObject property',
)
}
const propAddress = {...root.address, pathToProp: path}
const propConfig = getPropConfigByPath(root.template.staticConfig, path)
const validTracks = root.template
.getArrayOfValidSequenceTracks()
.getValue()
tf: for (let t = 0; t < validTracks.length; t++) {
const otherPath = validTracks[t].pathToProp
if (otherPath.length === path.length) {
for (let p = 0; p < path.length; p++) {
if (path[p] !== otherPath[p]) {
continue tf
}
}
return true
}
}
return false
},
// __experimental
//
//__experimental_setSequenceDuration<V>(prop: Pointer<V>, duration: number): boolean {
//const {path, root} = getPointerParts(prop)
//if (!isSheet(root)) {
//throw new Error(
//'Argument prop must be a pointer to a Sheet property',
//)
//}
//getStudio()!.transaction(({stateEditors}) => {
//stateEditors.coreByProject.historic.sheetsById.sequence.setLength({
//...root.address,
//length: duration,
//})
//})
//return false
//},
}
/**
@ -552,11 +676,250 @@ export default class TheatreStudio implements IStudio {
)
}
const __experimental_sequenceProp = <V>(prop: Pointer<V>) => {
const {path, root} = getPointerParts(prop)
// NOT IMPLEMENTED FULLY
// only works for simple props. not something like color
if (!isSheetObject(root)) {
throw new Error(
'Argument prop must be a pointer to a SheetObject property',
)
}
const propAddress = {...root.address, pathToProp: path}
const propConfig = getPropConfigByPath(root.template.staticConfig, path)
if (propConfig === undefined) {
throw new Error('propConfig is undefined. so, yeah.')
}
console.log({propConfig, propAddress})
stateEditors.coreByProject.historic.sheetsById.sequence.setPrimitivePropAsSequenced(
propAddress,
propConfig,
)
}
const __experimental_staticProp = <V>(prop: Pointer<V>) => {
const {path, root} = getPointerParts(prop)
// NOT IMPLEMENTED FULLY
// only works for simple props. not something like color
if (!isSheetObject(root)) {
throw new Error(
'Argument prop must be a pointer to a SheetObject property',
)
}
const propAddress = {...root.address, pathToProp: path}
const propConfig = getPropConfigByPath(root.template.staticConfig, path)
if (propConfig === undefined) {
throw new Error('propConfig is undefined. so, yeah.')
}
for (const {path: subPath, conf} of iteratePropType(propConfig, [])) {
if (isPropConfigComposite(conf)) continue
const pointerToSub = pointerDeep(prop, subPath)
stateEditors.coreByProject.historic.sheetsById.sequence.setPrimitivePropAsStatic(
{
...propAddress,
value: root.getValueByPointer(pointerToSub as $IntentionalAny),
},
)
}
}
const __experimental_deleteKeyframes = <V>(
prop: Pointer<V>,
from: number = 0,
to: number = 0,
) => {
const {root, path} = getPointerParts(prop as Pointer<$FixMe>)
if (isSheetObject(root)) {
const sequenceTracksTree = root.template
.getMapOfValidSequenceTracks_forStudio()
.getValue()
const defaultValue = getDeep(
root.template.getDefaultValues().getValue(),
path,
)
const propConfig = getPropConfigByPath(
root.template.staticConfig,
path,
) as PropTypeConfig
const unsetStaticOrKeyframeProp = <T>(value: T, path: PathToProp) => {
const propAddress = {...root.address, pathToProp: path}
const trackId = get(sequenceTracksTree, path) as $FixMe as
| SequenceTrackId
| undefined
const sequence = root.sheet.getSequence()
const trackP = val(
sequence._project.pointers.historic.sheetsById[
sequence._sheet.address.sheetId
].sequence.tracksByObject[root.address.objectKey],
)
if (!trackP) {
throw new Error('whatever, man')
}
const {trackData, trackIdByPropPath} = trackP
//const objectAddress = encodePathToProp(path)
//const id = trackIdByPropPath[objectAddress]
if (
typeof trackId === 'string' &&
typeof trackData !== 'undefined'
) {
const track = trackData[trackId]
if (!track) {
throw new Error('whatever, man')
}
const keyframeIds: KeyframeId[] = []
if (to > from) {
track.keyframes.forEach((kf) => {
if (kf.position >= from && kf.position <= to) {
} else {
keyframeIds.push(kf.id)
}
})
}
const objectKey = propAddress.objectKey
stateEditors.coreByProject.historic.sheetsById.sequence.keepKeyframes(
{
...root.address,
objectKey,
trackId,
keyframeIds,
},
)
} else if (propConfig !== undefined) {
stateEditors.coreByProject.historic.sheetsById.staticOverrides.byObject.unsetValueOfPrimitiveProp(
propAddress,
)
}
}
if (propConfig.type === 'compound') {
forEachPropDeep(
defaultValue,
(v, pathToProp) => {
unsetStaticOrKeyframeProp(v, pathToProp)
},
getPointerParts(prop).path,
)
} else {
unsetStaticOrKeyframeProp(defaultValue, path)
}
} else {
throw new Error(
'Only setting props of SheetObject-s is supported in a transaction so far',
)
}
}
const __experimental_addKeyframes = <V>(
prop: Pointer<V>,
keyframes: [],
) => {
const {root, path} = getPointerParts(prop as Pointer<$FixMe>)
if (isSheetObject(root)) {
let sequenceTracksTree = root.template
.getMapOfValidSequenceTracks_forStudio()
.getValue()
const defaultValue = getDeep(
root.template.getDefaultValues().getValue(),
path,
)
const propConfig = getPropConfigByPath(
root.template.staticConfig,
path,
) as PropTypeConfig
console.log(path, propConfig)
const addStaticOrKeyframeProp = <T>(value: T, path: PathToProp) => {
const propAddress = {...root.address, pathToProp: path}
let trackId = get(sequenceTracksTree, path) as $FixMe as
| SequenceTrackId
| undefined
if (typeof trackId !== 'string' && propConfig !== undefined) {
throw Error('can only add keyframes to sequenced prop')
}
if (typeof trackId === 'string') {
const objectKey = propAddress.objectKey
stateEditors.coreByProject.historic.sheetsById.sequence.addKeyframes(
{
...root.address,
objectKey,
trackId,
keyframes,
},
)
console.log({
a: {...root.address},
objectKey,
trackId,
keyframes,
})
} else if (propConfig !== undefined) {
throw Error('hmmm')
}
}
if (propConfig.type === 'compound') {
forEachPropDeep(
defaultValue,
(v, pathToProp) => {
console.log('comp compoumnD')
addStaticOrKeyframeProp(v, pathToProp)
},
getPointerParts(prop).path,
)
} else {
console.log('singlerer', {defaultValue, path})
addStaticOrKeyframeProp(defaultValue, path)
}
} else {
throw new Error(
'Only setting props of SheetObject-s is supported in a transaction so far',
)
}
}
// transaction
//
//const __experimental_setSequenceDuration = <V>(prop: Pointer<V>, duration: number) => {
//const {root, path} = getPointerParts(prop as Pointer<$FixMe>)
//if (isSheet(root)) {
//stateEditors.coreByProject.historic.sheetsById.sequence.setLength({
//...root.address,
//length: duration,
//})
//}
//}
return fn({
set,
unset,
__experimental_forgetObject,
__experimental_forgetSheet,
__experimental_sequenceProp,
__experimental_staticProp,
__experimental_deleteKeyframes,
__experimental_addKeyframes,
// transaction
//
//__experimental_setSequenceDuration,
})
})
}

View file

@ -66,7 +66,7 @@ const BasicKeyframedTrack: React.VFC<{
const extremumSpace: ExtremumSpace = useMemo(() => {
const extremums =
propConfig.type === 'number'
propConfig.type === 'number' || propConfig.type === 'boolean'
? calculateScalarExtremums(trackData.keyframes, propConfig)
: calculateNonScalarExtremums(trackData.keyframes)
@ -109,7 +109,7 @@ const BasicKeyframedTrack: React.VFC<{
layoutP={layoutP}
sheetObject={sheetObject}
trackId={trackId}
isScalar={propConfig.type === 'number'}
isScalar={propConfig.type === 'number' || propConfig.type === 'boolean'}
key={kf.id}
extremumSpace={cachedExtremumSpace.current}
color={color}
@ -146,12 +146,16 @@ function calculateScalarExtremums(
}
keyframes.forEach((cur, i) => {
const curVal = valueInProp(cur.value, propConfig) as number
const cv = typeof cur.value === 'boolean' ? (cur.value ? 1 : 0) : cur.value
const curVal = valueInProp(cv, propConfig) as number
check(curVal)
if (!cur.connectedRight) return
const next = keyframes[i + 1]
if (!next) return
const diff = (typeof next.value === 'number' ? next.value : 1) - curVal
const nv =
typeof next.value === 'boolean' ? (next.value ? 1 : 0) : next.value
const diff = (typeof nv === 'number' ? nv : 1) - curVal
check(curVal + cur.handles[3] * diff)
check(curVal + next.handles[1] * diff)
})

View file

@ -293,7 +293,6 @@ function createPrism<T extends SerializablePrimitive>(
callback: () => {
getStudio()!.transaction(({stateEditors}) => {
const propAddress = {...obj.address, pathToProp}
stateEditors.coreByProject.historic.sheetsById.sequence.setPrimitivePropAsSequenced(
propAddress,
propConfig,

View file

@ -772,6 +772,63 @@ namespace stateEditors {
stateEditors.coreByProject.historic.sheetsById.staticOverrides.byObject.setValueOfCompoundProp(
p,
)
const toVT = {
sequenced: false,
panelID: p.objectKey,
prop: p.pathToProp,
origin: 'stateEditors.ts',
}
const event = new CustomEvent('sequenceEvent', {
bubbles: false,
detail: toVT,
})
window.dispatchEvent(event)
}
export function setCompoundPropAsSequenced(
p: WithoutSheetInstance<PropAddress>,
) {
const tracks = _ensureTracksOfObject(p)
for (const encodedPropPath of Object.keys(
tracks.trackIdByPropPath,
)) {
const propPath = JSON.parse(encodedPropPath)
const isSubOfTargetPath = p.pathToProp.every(
(key, i) => propPath[i] === key,
)
if (isSubOfTargetPath) {
const possibleTrackId =
tracks.trackIdByPropPath[encodedPropPath]
if (typeof possibleTrackId === 'string') return
const trackId = generateSequenceTrackId()
const track: BasicKeyframedTrack = {
type: 'BasicKeyframedTrack',
__debugName: `${p.objectKey}:${encodedPropPath}`,
keyframes: [],
}
tracks.trackData[trackId] = track
tracks.trackIdByPropPath[encodedPropPath] = trackId
}
}
//stateEditors.coreByProject.historic.sheetsById.staticOverrides.byObject.setValueOfCompoundProp(
//p,
//)
const toVT = {
sequenced: false,
panelID: p.objectKey,
prop: p.pathToProp,
origin: 'stateEditors.ts',
}
const event = new CustomEvent('sequenceEvent', {
bubbles: false,
detail: toVT,
})
window.dispatchEvent(event)
}
function _getTrack(
@ -979,7 +1036,54 @@ namespace stateEditors {
}
}
export function deleteKeyframes(
export function addKeyframes<T extends SerializableValue>(
p: WithoutSheetInstance<SheetObjectAddress> & {
trackId: SequenceTrackId
keyframes: {
position: number
handles?: [number, number, number, number]
value: T
type?: KeyframeType
}[]
},
override: boolean = true,
) {
const track = _getTrack(p)
if (!track) return
if (p.keyframes.length < 1)
throw new Error(
'holy shit, you are trying to add non-existing keyframes',
)
//const {keyframes} = track
if (!override)
throw new Error('whoopsie, not overriding is not implemented')
// is it necessary to be so stupid here?
const oldKeyframes = track.keyframes
track.keyframes = []
oldKeyframes.forEach((kf) => {
if (p.keyframes[0].position > kf.position) {
track.keyframes.push(kf)
}
})
p.keyframes.forEach((pkf) => {
track.keyframes.push({
id: generateKeyframeId(),
position: pkf.position,
connectedRight: true,
handles: pkf.handles || [0.5, 1, 0.5, 0],
type: pkf.type || 'bezier',
value: pkf.value,
})
})
oldKeyframes.forEach((kf) => {
if (p.keyframes[p.keyframes.length - 1].position < kf.position) {
track.keyframes.push(kf)
}
})
}
export function keepKeyframes(
p: WithoutSheetInstance<SheetObjectAddress> & {
trackId: SequenceTrackId
keyframeIds: KeyframeId[]
@ -988,6 +1092,24 @@ namespace stateEditors {
const track = _getTrack(p)
if (!track) return
// is it necessary to be so stupid here?
const keyframes = track.keyframes
track.keyframes = []
keyframes.forEach((kf) => {
if (p.keyframeIds.indexOf(kf.id) >= 0) {
track.keyframes.push(kf)
}
})
}
export function deleteKeyframes(
p: WithoutSheetInstance<SheetObjectAddress> & {
trackId: SequenceTrackId
keyframeIds: KeyframeId[]
},
) {
const track = _getTrack(p)
if (!track) return
track.keyframes = track.keyframes.filter(
(kf) => p.keyframeIds.indexOf(kf.id) === -1,
)