diff --git a/theatre/core/src/sequences/Sequence.test.ts b/theatre/core/src/sequences/Sequence.test.ts new file mode 100644 index 0000000..69f203c --- /dev/null +++ b/theatre/core/src/sequences/Sequence.test.ts @@ -0,0 +1,62 @@ +/* + * @jest-environment jsdom + */ +import {setupTestSheet} from '@theatre/shared/testUtils' +import {encodePathToProp} from '@theatre/shared/utils/addresses' +import {asKeyframeId, asSequenceTrackId} from '@theatre/shared/utils/ids' +import type {ObjectAddressKey, SequenceTrackId} from '@theatre/shared/utils/ids' + +describe(`Sequence`, () => { + test('sequence.getKeyframesOfSimpleProp()', async () => { + const {objPublicAPI, sheet} = await setupTestSheet({ + staticOverrides: { + byObject: {}, + }, + sequence: { + type: 'PositionalSequence', + length: 20, + subUnitsPerUnit: 30, + tracksByObject: { + ['obj' as ObjectAddressKey]: { + trackIdByPropPath: { + [encodePathToProp(['position', 'y'])]: asSequenceTrackId('1'), + }, + trackData: { + ['1' as SequenceTrackId]: { + type: 'BasicKeyframedTrack', + keyframes: [ + { + id: asKeyframeId('0'), + position: 10, + connectedRight: true, + handles: [0.5, 0.5, 0.5, 0.5], + type: 'bezier', + value: 3, + }, + { + id: asKeyframeId('1'), + position: 20, + connectedRight: false, + handles: [0.5, 0.5, 0.5, 0.5], + type: 'bezier', + value: 6, + }, + ], + }, + }, + }, + }, + }, + }) + + const seq = sheet.publicApi.sequence + + const keyframes = seq.__experimental_getKeyframes( + objPublicAPI.props.position.y, + ) + expect(keyframes).toHaveLength(2) + expect(keyframes[0].value).toEqual(3) + expect(keyframes[1].value).toEqual(6) + expect(keyframes[0].position).toEqual(10) + }) +}) diff --git a/theatre/core/src/sequences/Sequence.ts b/theatre/core/src/sequences/Sequence.ts index 13421d9..a804e99 100644 --- a/theatre/core/src/sequences/Sequence.ts +++ b/theatre/core/src/sequences/Sequence.ts @@ -1,5 +1,6 @@ import type Project from '@theatre/core/projects/Project' import type Sheet from '@theatre/core/sheets/Sheet' +import {encodePathToProp} from '@theatre/shared/utils/addresses' import type {SequenceAddress} from '@theatre/shared/utils/addresses' import didYouMean from '@theatre/shared/utils/didYouMean' import {InvalidArgumentError} from '@theatre/shared/utils/errors' @@ -20,10 +21,12 @@ import type { } from './playbackControllers/DefaultPlaybackController' import DefaultPlaybackController from './playbackControllers/DefaultPlaybackController' import TheatreSequence from './TheatreSequence' +import type {Keyframe} from '@theatre/core/projects/store/types/SheetState_Historic' import type {ILogger} from '@theatre/shared/logger' import type {ISequence} from '..' import {notify} from '@theatre/shared/notify' import type {$IntentionalAny} from '@theatre/dataverse/src/types' +import {isSheetObject} from '@theatre/shared/instanceTypes' export type IPlaybackRange = [from: number, to: number] @@ -114,6 +117,46 @@ export default class Sequence implements PointerToPrismProvider { } } + /** + * Takes a pointer to a property of a SheetObject and returns the keyframes of that property. + * + * Theoretically, this method can be called from inside a prism so it can be reactive. + */ + getKeyframesOfSimpleProp(prop: Pointer): Keyframe[] { + const {path, root} = getPointerParts(prop) + + if (!isSheetObject(root)) { + throw new InvalidArgumentError( + 'Argument prop must be a pointer to a SheetObject property', + ) + } + + const trackP = val( + this._project.pointers.historic.sheetsById[this._sheet.address.sheetId] + .sequence.tracksByObject[root.address.objectKey], + ) + + if (!trackP) { + return [] + } + + const {trackData, trackIdByPropPath} = trackP + const objectAddress = encodePathToProp(path) + const id = trackIdByPropPath[objectAddress] + + if (!id) { + return [] + } + + const track = trackData[id] + + if (!track) { + return [] + } + + return track.keyframes + } + get positionFormatter(): ISequencePositionFormatter { return this._positionFormatterD.getValue() } diff --git a/theatre/core/src/sequences/TheatreSequence.ts b/theatre/core/src/sequences/TheatreSequence.ts index 3cd8a15..0121810 100644 --- a/theatre/core/src/sequences/TheatreSequence.ts +++ b/theatre/core/src/sequences/TheatreSequence.ts @@ -2,6 +2,7 @@ import {privateAPI, setPrivateAPI} from '@theatre/core/privateAPIs' import {defer} from '@theatre/shared/utils/defer' import type Sequence from './Sequence' import type {IPlaybackDirection, IPlaybackRange} from './Sequence' +import type {Keyframe} from '@theatre/core/projects/store/types/SheetState_Historic' import AudioPlaybackController from './playbackControllers/AudioPlaybackController' import {getCoreTicker} from '@theatre/core/coreTicker' import type {Pointer} from '@theatre/dataverse' @@ -133,6 +134,19 @@ export interface ISequence { position: number }> + /** + * Given a property, returns a list of keyframes that affect that property. + * + * @example + * Usage: + * ```ts + * // let's assume `sheet` is a sheet and obj is one of its objects + * const keyframes = sheet.sequence.__experimental_getKeyframes(obj.pointer.x) + * console.log(keyframes) // an array of keyframes + * ``` + */ + __experimental_getKeyframes(prop: Pointer<{}>): Keyframe[] + /** * Attaches an audio source to the sequence. Playing the sequence automatically * plays the audio source and their times are kept in sync. @@ -291,6 +305,10 @@ export default class TheatreSequence implements ISequence { privateAPI(this).position = position } + __experimental_getKeyframes(prop: Pointer): Keyframe[] { + return privateAPI(this).getKeyframesOfSimpleProp(prop) + } + async attachAudio(args: IAttachAudioArgs): Promise<{ decodedBuffer: AudioBuffer audioContext: AudioContext