From 78a67ee6650d846fe5cc4770770b1511328033e6 Mon Sep 17 00:00:00 2001 From: themancalledjakob Date: Fri, 12 Apr 2024 19:15:59 +0200 Subject: [PATCH] add experimental keyframe functions --- theatre/studio/src/TheatreStudio.ts | 367 +++++++++++++++++- .../useEditingToolsForSimpleProp.tsx | 1 - theatre/studio/src/store/stateEditors.ts | 124 +++++- 3 files changed, 488 insertions(+), 4 deletions(-) diff --git a/theatre/studio/src/TheatreStudio.ts b/theatre/studio/src/TheatreStudio.ts index f769279..813cf9a 100644 --- a/theatre/studio/src/TheatreStudio.ts +++ b/theatre/studio/src/TheatreStudio.ts @@ -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(pointer: Pointer): void + __experimental_staticProp(pointer: Pointer): void + __experimental_deleteKeyframes( + pointer: Pointer, + from: number, + to: number, + ): void + __experimental_addKeyframes(pointer: Pointer, keyframes: []): void + // transaction + // + //__experimental_setSequenceDuration(pointer: Pointer, duration: number): void } /** * @@ -462,6 +487,14 @@ export interface IStudio { __experimental_createContentOfSaveFileTyped( projectId: string, ): __UNSTABLE_Project_OnDiskState + + __experimental_setPropAsSequenced(prop: Pointer): void + __experimental_setPropAsStatic(prop: Pointer): void + __experimental_isPropSequenced(prop: Pointer): boolean + + // __experimental + // + //__experimental_setSequenceDuration(prop: Pointer, 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(prop: Pointer): 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(prop: Pointer): 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(prop: Pointer): 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(prop: Pointer, 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 = (prop: Pointer) => { + 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 = (prop: Pointer) => { + 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 = ( + prop: Pointer, + 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 = (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 = ( + prop: Pointer, + 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 = (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 = (prop: Pointer, 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, }) }) } diff --git a/theatre/studio/src/propEditors/useEditingToolsForSimpleProp.tsx b/theatre/studio/src/propEditors/useEditingToolsForSimpleProp.tsx index 93fdeea..f9ed55c 100644 --- a/theatre/studio/src/propEditors/useEditingToolsForSimpleProp.tsx +++ b/theatre/studio/src/propEditors/useEditingToolsForSimpleProp.tsx @@ -293,7 +293,6 @@ function createPrism( callback: () => { getStudio()!.transaction(({stateEditors}) => { const propAddress = {...obj.address, pathToProp} - stateEditors.coreByProject.historic.sheetsById.sequence.setPrimitivePropAsSequenced( propAddress, propConfig, diff --git a/theatre/studio/src/store/stateEditors.ts b/theatre/studio/src/store/stateEditors.ts index 0aee371..da730b3 100644 --- a/theatre/studio/src/store/stateEditors.ts +++ b/theatre/studio/src/store/stateEditors.ts @@ -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, + ) { + 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( + p: WithoutSheetInstance & { + 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 & { 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 & { + trackId: SequenceTrackId + keyframeIds: KeyframeId[] + }, + ) { + const track = _getTrack(p) + if (!track) return track.keyframes = track.keyframes.filter( (kf) => p.keyframeIds.indexOf(kf.id) === -1, )