diff --git a/theatre/shared/src/utils/ids.ts b/theatre/shared/src/utils/ids.ts
index 194a99d..a8021cb 100644
--- a/theatre/shared/src/utils/ids.ts
+++ b/theatre/shared/src/utils/ids.ts
@@ -3,6 +3,7 @@ import type {PathToProp} from './addresses'
import stableValueHash from './stableJsonStringify'
import {nanoid as generateNonSecure} from 'nanoid/non-secure'
import type {Nominal} from './Nominal'
+import type Sheet from '@theatre/core/sheets/Sheet'
export type KeyframeId = Nominal<'KeyframeId'>
@@ -106,6 +107,12 @@ export const createStudioSheetItemKey = {
position,
)
},
+ forSheetAggregateKeyframe(obj: Sheet, position: number): StudioSheetItemKey {
+ return stableValueHash({
+ o: obj.address.sheetId,
+ pos: position,
+ }) as StudioSheetItemKey
+ },
forCompoundPropAggregateKeyframe(
obj: SheetObject,
pathToProp: PathToProp,
diff --git a/theatre/studio/src/panels/SequenceEditorPanel/AGGREGATE_COPY_PASTE.md b/theatre/studio/src/panels/SequenceEditorPanel/AGGREGATE_COPY_PASTE.md
index abf018a..b0b2bcc 100644
--- a/theatre/studio/src/panels/SequenceEditorPanel/AGGREGATE_COPY_PASTE.md
+++ b/theatre/studio/src/panels/SequenceEditorPanel/AGGREGATE_COPY_PASTE.md
@@ -1,14 +1,30 @@
## The keyframe copy/paste algorithm
-```
-copy algorithm: find the closest common acnestor for the tracks selected
+The copy and paste algorithms are specified below. Note that the copy algorithm
+is written with some capital letters to emphasize organization, its not an
+actual language or anything. The copy algorithm changed recently and the
+examples are more up-to-date than the paste algorithm because pasting stayed the
+same.
-- obj1.props.transform.position.x => simple
+```
+ALGORITHM copy:
+
+LET PATH =
+ CASE copy selection / single track THEN the path relative to the closest common ancestor for the tracks selected
+ CASE copy aggregate track THEN the path relative the aggregate track compoundProp/sheetObject/sheet
+
+FOR EXAMPLE CASE copy selection / single track:
+- obj1.props.transform.position.x => x
- obj1.props.transform.position.{x, z} => {x, z}
- obj1.props.transform.position.{x, z} + obj1.props.transform.rotation.z =>
{position: {x, z}, rotation: {z}}
-paste:
+FOR EXAMPLE CASE copy aggregate track:
+- sheet.obj1.props.transform.position => {x, y, z}
+- sheet.obj1.props.transform => {position: {x, y, z}, rotation: {x, y, z}}
+- sheet => { obj1: { props: { transform: {position: {x, y, z}, rotation: {x, y, z}}}}}
+
+ALGORITHM: paste:
- simple => simple => 1-1
- simple => {x, y} => {x: simple, y: simple} (distribute to all)
@@ -25,4 +41,4 @@ paste:
- {x, y} => {object(not a prop): {x, y}} => {x, y}
- What this means is that, in case of objects and sheets, we do a forEach
at each object, then try pasting onto its object.props
-```
\ No newline at end of file
+```
diff --git a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Left/AnyCompositeRow.tsx b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Left/AnyCompositeRow.tsx
index 5b3bad0..7f9db6f 100644
--- a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Left/AnyCompositeRow.tsx
+++ b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Left/AnyCompositeRow.tsx
@@ -2,6 +2,7 @@ import {theme} from '@theatre/studio/css'
import type {
SequenceEditorTree_PrimitiveProp,
SequenceEditorTree_PropWithChildren,
+ SequenceEditorTree_Sheet,
SequenceEditorTree_SheetObject,
} from '@theatre/studio/panels/SequenceEditorPanel/layout/tree'
import type {VoidFn} from '@theatre/shared/utils/types'
@@ -76,6 +77,7 @@ const LeftRowChildren = styled.ul`
const AnyCompositeRow: React.FC<{
leaf:
+ | SequenceEditorTree_Sheet
| SequenceEditorTree_PrimitiveProp
| SequenceEditorTree_PropWithChildren
| SequenceEditorTree_SheetObject
diff --git a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Left/PropWithChildrenRow.tsx b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Left/PropWithChildrenRow.tsx
index 9c85ff6..b850b14 100644
--- a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Left/PropWithChildrenRow.tsx
+++ b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Left/PropWithChildrenRow.tsx
@@ -5,7 +5,7 @@ import type {
import React from 'react'
import AnyCompositeRow from './AnyCompositeRow'
import PrimitivePropRow from './PrimitivePropRow'
-import {setCollapsedSheetObjectOrCompoundProp} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/setCollapsedSheetObjectOrCompoundProp'
+import {setCollapsedSheetItem} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/setCollapsedSheetObjectOrCompoundProp'
export const decideRowByPropType = (
leaf: SequenceEditorTree_PropWithChildren | SequenceEditorTree_PrimitiveProp,
@@ -31,7 +31,7 @@ const PropWithChildrenRow: React.VFC<{
label={leaf.pathToProp[leaf.pathToProp.length - 1]}
isCollapsed={leaf.isCollapsed}
toggleCollapsed={() =>
- setCollapsedSheetObjectOrCompoundProp(!leaf.isCollapsed, {
+ setCollapsedSheetItem(!leaf.isCollapsed, {
sheetAddress: leaf.sheetObject.address,
sheetItemKey: leaf.sheetItemKey,
})
diff --git a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Left/SheetObjectRow.tsx b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Left/SheetObjectRow.tsx
index a5075b1..4ae810a 100644
--- a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Left/SheetObjectRow.tsx
+++ b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Left/SheetObjectRow.tsx
@@ -2,7 +2,7 @@ import type {SequenceEditorTree_SheetObject} from '@theatre/studio/panels/Sequen
import React from 'react'
import AnyCompositeRow from './AnyCompositeRow'
import {decideRowByPropType} from './PropWithChildrenRow'
-import {setCollapsedSheetObjectOrCompoundProp} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/setCollapsedSheetObjectOrCompoundProp'
+import {setCollapsedSheetItem} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/setCollapsedSheetObjectOrCompoundProp'
import getStudio from '@theatre/studio/getStudio'
const LeftSheetObjectRow: React.VFC<{
@@ -22,7 +22,7 @@ const LeftSheetObjectRow: React.VFC<{
})
}}
toggleCollapsed={() =>
- setCollapsedSheetObjectOrCompoundProp(!leaf.isCollapsed, {
+ setCollapsedSheetItem(!leaf.isCollapsed, {
sheetAddress: leaf.sheetObject.address,
sheetItemKey: leaf.sheetItemKey,
})
diff --git a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Left/SheetRow.tsx b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Left/SheetRow.tsx
index 49c9b91..6be3adc 100644
--- a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Left/SheetRow.tsx
+++ b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Left/SheetRow.tsx
@@ -2,20 +2,32 @@ import type {SequenceEditorTree_Sheet} from '@theatre/studio/panels/SequenceEdit
import {usePrism} from '@theatre/react'
import React from 'react'
import LeftSheetObjectRow from './SheetObjectRow'
+import AnyCompositeRow from './AnyCompositeRow'
+import {setCollapsedSheetItem} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/setCollapsedSheetObjectOrCompoundProp'
const SheetRow: React.VFC<{
leaf: SequenceEditorTree_Sheet
}> = ({leaf}) => {
return usePrism(() => {
return (
- <>
+ {
+ setCollapsedSheetItem(!leaf.isCollapsed, {
+ sheetAddress: leaf.sheet.address,
+ sheetItemKey: leaf.sheetItemKey,
+ })
+ }}
+ >
{leaf.children.map((sheetObjectLeaf) => (
))}
- >
+
)
}, [leaf])
}
diff --git a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/AggregatedKeyframeTrack/AggregateKeyframeEditor/AggregateKeyframeConnector.tsx b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/AggregatedKeyframeTrack/AggregateKeyframeEditor/AggregateKeyframeConnector.tsx
index 218602d..cf6f53e 100644
--- a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/AggregatedKeyframeTrack/AggregateKeyframeEditor/AggregateKeyframeConnector.tsx
+++ b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/AggregatedKeyframeTrack/AggregateKeyframeEditor/AggregateKeyframeConnector.tsx
@@ -124,16 +124,20 @@ function useDragKeyframe(
const keyframes = props.aggregateKeyframes[props.index].keyframes
+ const {selection, viewModel} = props
+ const address =
+ viewModel.type === 'sheet'
+ ? viewModel.sheet.address
+ : viewModel.sheetObject.address
+
if (
- props.selection &&
+ selection &&
props.aggregateKeyframes[props.index].selected ===
AggregateKeyframePositionIsSelected.AllSelected
) {
- const {selection, viewModel} = props
- const {sheetObject} = viewModel
return selection
.getDragHandlers({
- ...sheetObject.address,
+ ...address,
domNode: node!,
positionAtStartOfDrag:
props.aggregateKeyframes[props.index].position,
@@ -159,7 +163,7 @@ function useDragKeyframe(
for (const keyframe of keyframes) {
stateEditors.coreByProject.historic.sheetsById.sequence.transformKeyframes(
{
- ...propsAtStartOfDrag.viewModel.sheetObject.address,
+ ...keyframe.track.sheetObject.address,
trackId: keyframe.track.id,
keyframeIds: [
keyframe.kf.id,
@@ -209,8 +213,7 @@ function useConnectorContextMenu(
(acc, con) =>
acc.concat(
keyframesWithPaths({
- ...props.editorProps.viewModel.sheetObject.address,
- trackId: con.trackId,
+ ...con,
keyframeIds: [con.left.id, con.right.id],
}) ?? [],
),
@@ -226,14 +229,20 @@ function useConnectorContextMenu(
pathToProp: pathToProp.slice(commonPath.length),
}))
+ const viewModel = props.editorProps.viewModel
+ const address =
+ viewModel.type === 'sheet'
+ ? viewModel.sheet.address
+ : viewModel.sheetObject.address
+
return [
{
label: 'Copy',
callback: () => {
if (props.editorProps.selection) {
const copyableKeyframes = copyableKeyframesFromSelection(
- props.editorProps.viewModel.sheetObject.address.projectId,
- props.editorProps.viewModel.sheetObject.address.sheetId,
+ address.projectId,
+ address.sheetId,
props.editorProps.selection,
)
getStudio().transaction((api) => {
@@ -260,7 +269,8 @@ function useConnectorContextMenu(
for (const con of props.utils.allConnections) {
stateEditors.coreByProject.historic.sheetsById.sequence.deleteKeyframes(
{
- ...props.editorProps.viewModel.sheetObject.address,
+ ...address,
+ objectKey: con.objectKey,
keyframeIds: [con.left.id, con.right.id],
trackId: con.trackId,
},
diff --git a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/AggregatedKeyframeTrack/AggregateKeyframeEditor/AggregateKeyframeDot.tsx b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/AggregatedKeyframeTrack/AggregateKeyframeEditor/AggregateKeyframeDot.tsx
index d2c01c8..88ebdfc 100644
--- a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/AggregatedKeyframeTrack/AggregateKeyframeEditor/AggregateKeyframeDot.tsx
+++ b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/AggregatedKeyframeTrack/AggregateKeyframeEditor/AggregateKeyframeDot.tsx
@@ -3,7 +3,6 @@ import useRefAndState from '@theatre/studio/utils/useRefAndState'
import usePresence, {
PresenceFlag,
} from '@theatre/studio/uiComponents/usePresence'
-import {useLogger} from '@theatre/studio/uiComponents/useLogger'
import useContextMenu from '@theatre/studio/uiComponents/simpleContextMenu/useContextMenu'
import type {IAggregateKeyframeEditorProps} from './AggregateKeyframeEditor'
import type {IAggregateKeyframeEditorUtils} from './useAggregateKeyframeEditorUtils'
@@ -15,7 +14,6 @@ import {
} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/selections'
import type {KeyframeWithPathToPropFromCommonRoot} from '@theatre/studio/store/types/ahistoric'
import {commonRootOfPathsToProps} from '@theatre/shared/utils/addresses'
-import type {ILogger} from '@theatre/shared/logger'
import DopeSnap from '@theatre/studio/panels/SequenceEditorPanel/RightOverlay/DopeSnap'
import type {
PrimitivePropEditingOptions,
@@ -96,11 +94,12 @@ function primitivePropBuild(
export function AggregateKeyframeDot(
props: React.PropsWithChildren,
) {
- const logger = useLogger('AggregateKeyframeDot')
const {cur} = props.utils
const inlineEditorPopover = useKeyframeInlineEditorPopover(
- props.editorProps.viewModel.type === 'sheetObject'
+ props.editorProps.viewModel.type === 'sheet'
+ ? null
+ : props.editorProps.viewModel.type === 'sheetObject'
? sheetObjectBuild(props.editorProps.viewModel, cur.keyframes)
?.children ?? null
: propWithChildrenBuild(props.editorProps.viewModel, cur.keyframes)
@@ -125,7 +124,7 @@ export function AggregateKeyframeDot(
const [ref, node] = useRefAndState(null)
- const [contextMenu] = useAggregateKeyframeContextMenu(props, logger, node)
+ const [contextMenu] = useAggregateKeyframeContextMenu(props, node)
return (
<>
@@ -135,7 +134,11 @@ export function AggregateKeyframeDot(
// Need this for the dragging logic to be able to get the keyframe props
// based on the position.
{...DopeSnap.includePositionSnapAttrs(cur.position)}
- onClick={(e) => inlineEditorPopover.open(e, ref.current!)}
+ onClick={(e) =>
+ props.editorProps.viewModel.type !== 'sheet'
+ ? inlineEditorPopover.open(e, ref.current!)
+ : null
+ }
/>
{
- // see AGGREGATE_COPY_PASTE.md for explanation of this
- // code that makes some keyframes with paths for copying
- // to clipboard
- const kfs = props.utils.cur.keyframes.reduce(
- (acc, kfWithTrack) =>
- acc.concat(
- keyframesWithPaths({
- ...props.editorProps.viewModel.sheetObject.address,
- trackId: kfWithTrack.track.id,
- keyframeIds: [kfWithTrack.kf.id],
- }) ?? [],
- ),
- [] as KeyframeWithPathToPropFromCommonRoot[],
- )
-
- const commonPath = commonRootOfPathsToProps(
- kfs.map((kf) => kf.pathToProp),
- )
-
- const keyframesWithCommonRootPath = kfs.map(({keyframe, pathToProp}) => ({
- keyframe,
- pathToProp: pathToProp.slice(commonPath.length),
- }))
+ const viewModel = props.editorProps.viewModel
+ const selection = props.editorProps.selection
return [
{
- label: props.editorProps.selection ? 'Copy (selection)' : 'Copy',
+ label: selection ? 'Copy (selection)' : 'Copy',
callback: () => {
- if (props.editorProps.selection) {
+ // see AGGREGATE_COPY_PASTE.md for explanation of this
+ // code that makes some keyframes with paths for copying
+ // to clipboard
+ if (selection) {
+ const {projectId, sheetId} =
+ viewModel.type === 'sheet'
+ ? viewModel.sheet.address
+ : viewModel.sheetObject.address
const copyableKeyframes = copyableKeyframesFromSelection(
- props.editorProps.viewModel.sheetObject.address.projectId,
- props.editorProps.viewModel.sheetObject.address.sheetId,
- props.editorProps.selection,
+ projectId,
+ sheetId,
+ selection,
)
getStudio().transaction((api) => {
api.stateEditors.studio.ahistoric.setClipboardKeyframes(
@@ -196,6 +184,38 @@ function useAggregateKeyframeContextMenu(
)
})
} else {
+ const kfs: KeyframeWithPathToPropFromCommonRoot[] =
+ props.utils.cur.keyframes.flatMap(
+ (kfWithTrack) =>
+ keyframesWithPaths({
+ ...kfWithTrack.track.sheetObject.address,
+ trackId: kfWithTrack.track.id,
+ keyframeIds: [kfWithTrack.kf.id],
+ }) ?? [],
+ )
+
+ const basePathRelativeToSheet =
+ viewModel.type === 'sheet'
+ ? []
+ : viewModel.type === 'sheetObject'
+ ? [viewModel.sheetObject.address.objectKey]
+ : viewModel.type === 'propWithChildren'
+ ? [
+ viewModel.sheetObject.address.objectKey,
+ ...viewModel.pathToProp,
+ ]
+ : [] // should be unreachable unless new viewModel/leaf types are added
+ const commonPath = commonRootOfPathsToProps([
+ basePathRelativeToSheet,
+ ...kfs.map((kf) => kf.pathToProp),
+ ])
+
+ const keyframesWithCommonRootPath = kfs.map(
+ ({keyframe, pathToProp}) => ({
+ keyframe,
+ pathToProp: pathToProp.slice(commonPath.length),
+ }),
+ )
getStudio().transaction((api) => {
api.stateEditors.studio.ahistoric.setClipboardKeyframes(
keyframesWithCommonRootPath,
@@ -205,16 +225,16 @@ function useAggregateKeyframeContextMenu(
},
},
{
- label: props.editorProps.selection ? 'Delete (selection)' : 'Delete',
+ label: selection ? 'Delete (selection)' : 'Delete',
callback: () => {
- if (props.editorProps.selection) {
- props.editorProps.selection.delete()
+ if (selection) {
+ selection.delete()
} else {
getStudio().transaction(({stateEditors}) => {
for (const kfWithTrack of props.utils.cur.keyframes) {
stateEditors.coreByProject.historic.sheetsById.sequence.deleteKeyframes(
{
- ...props.editorProps.viewModel.sheetObject.address,
+ ...kfWithTrack.track.sheetObject.address,
keyframeIds: [kfWithTrack.kf.id],
trackId: kfWithTrack.track.id,
},
@@ -226,8 +246,5 @@ function useAggregateKeyframeContextMenu(
},
]
},
- onOpen() {
- logger._debug('Show aggregate keyframe', props)
- },
})
}
diff --git a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/AggregatedKeyframeTrack/AggregateKeyframeEditor/AggregateKeyframeEditor.tsx b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/AggregatedKeyframeTrack/AggregateKeyframeEditor/AggregateKeyframeEditor.tsx
index 1c461d2..5e2f84e 100644
--- a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/AggregatedKeyframeTrack/AggregateKeyframeEditor/AggregateKeyframeEditor.tsx
+++ b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/AggregatedKeyframeTrack/AggregateKeyframeEditor/AggregateKeyframeEditor.tsx
@@ -5,6 +5,7 @@ import type {
} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout'
import type {
SequenceEditorTree_PropWithChildren,
+ SequenceEditorTree_Sheet,
SequenceEditorTree_SheetObject,
} from '@theatre/studio/panels/SequenceEditorPanel/layout/tree'
import type {Pointer} from '@theatre/dataverse'
@@ -44,6 +45,7 @@ export type IAggregateKeyframeEditorProps = {
viewModel:
| SequenceEditorTree_PropWithChildren
| SequenceEditorTree_SheetObject
+ | SequenceEditorTree_Sheet
selection: undefined | DopeSheetSelection
}
diff --git a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/AggregatedKeyframeTrack/AggregateKeyframeEditor/useAggregateKeyframeEditorUtils.tsx b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/AggregatedKeyframeTrack/AggregateKeyframeEditor/useAggregateKeyframeEditorUtils.tsx
index 56a57b8..b7a4bf3 100644
--- a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/AggregatedKeyframeTrack/AggregateKeyframeEditor/useAggregateKeyframeEditorUtils.tsx
+++ b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/AggregatedKeyframeTrack/AggregateKeyframeEditor/useAggregateKeyframeEditorUtils.tsx
@@ -23,13 +23,12 @@ export function useAggregateKeyframeEditorUtils(
>,
) {
const {index, aggregateKeyframes, selection} = props
- const sheetObjectAddress = props.viewModel.sheetObject.address
return usePrism(getAggregateKeyframeEditorUtilsPrismFn(props), [
index,
aggregateKeyframes,
selection,
- sheetObjectAddress,
+ props.viewModel,
])
}
@@ -42,7 +41,11 @@ export function getAggregateKeyframeEditorUtilsPrismFn(
>,
) {
const {index, aggregateKeyframes, selection} = props
- const sheetObjectAddress = props.viewModel.sheetObject.address
+
+ const {projectId, sheetId} =
+ props.viewModel.type === 'sheet'
+ ? props.viewModel.sheet.address
+ : props.viewModel.sheetObject.address
return () => {
const cur = aggregateKeyframes[index]
@@ -65,24 +68,17 @@ export function getAggregateKeyframeEditorUtilsPrismFn(
const aggregatedConnections: AggregatedKeyframeConnection[] = !connected
? []
: cur.keyframes.map(({kf, track}, i) => ({
- ...sheetObjectAddress,
+ ...track.sheetObject.address,
trackId: track.id,
left: kf,
right: next.keyframes[i].kf,
}))
const allConnections = iif(() => {
- const {projectId, sheetId} = sheetObjectAddress
-
const selectedConnections = prism
.memo(
'selectedConnections',
- () =>
- selectedKeyframeConnections(
- sheetObjectAddress.projectId,
- sheetObjectAddress.sheetId,
- selection,
- ),
+ () => selectedKeyframeConnections(projectId, sheetId, selection),
[projectId, sheetId, selection],
)
.getValue()
@@ -97,7 +93,12 @@ export function getAggregateKeyframeEditorUtilsPrismFn(
const itemKey = prism.memo(
'itemKey',
() => {
- if (props.viewModel.type === 'sheetObject') {
+ if (props.viewModel.type === 'sheet') {
+ return createStudioSheetItemKey.forSheetAggregateKeyframe(
+ props.viewModel.sheet,
+ cur.position,
+ )
+ } else if (props.viewModel.type === 'sheetObject') {
return createStudioSheetItemKey.forSheetObjectAggregateKeyframe(
props.viewModel.sheetObject,
cur.position,
@@ -110,7 +111,7 @@ export function getAggregateKeyframeEditorUtilsPrismFn(
)
}
},
- [props.viewModel.sheetObject, cur.position],
+ [props.viewModel, cur.position],
)
return {
diff --git a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/AggregatedKeyframeTrack/AggregatedKeyframeTrack.tsx b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/AggregatedKeyframeTrack/AggregatedKeyframeTrack.tsx
index 2693561..bc9eb92 100644
--- a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/AggregatedKeyframeTrack/AggregatedKeyframeTrack.tsx
+++ b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/AggregatedKeyframeTrack/AggregatedKeyframeTrack.tsx
@@ -4,10 +4,11 @@ import type {
} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout'
import type {
SequenceEditorTree_PropWithChildren,
+ SequenceEditorTree_Sheet,
SequenceEditorTree_SheetObject,
} from '@theatre/studio/panels/SequenceEditorPanel/layout/tree'
import {usePrism, useVal} from '@theatre/react'
-import type {Pointer} from '@theatre/dataverse'
+import type {IDerivation, Pointer} from '@theatre/dataverse'
import {prism, val, valueDerivation} from '@theatre/dataverse'
import React, {useMemo, Fragment} from 'react'
import styled from 'styled-components'
@@ -19,7 +20,11 @@ import type {
IAggregateKeyframeEditorProps,
} from './AggregateKeyframeEditor/AggregateKeyframeEditor'
import AggregateKeyframeEditor from './AggregateKeyframeEditor/AggregateKeyframeEditor'
-import type {AggregatedKeyframes} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/collectAggregateKeyframes'
+import type {
+ AggregatedKeyframes,
+ KeyframeWithTrack,
+} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/collectAggregateKeyframes'
+import {collectAggregateSnapPositionsObjectOrCompound} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/collectAggregateKeyframes'
import {useLogger} from '@theatre/studio/uiComponents/useLogger'
import {getAggregateKeyframeEditorUtilsPrismFn} from './AggregateKeyframeEditor/useAggregateKeyframeEditorUtils'
import DopeSnap from '@theatre/studio/panels/SequenceEditorPanel/RightOverlay/DopeSnap'
@@ -35,7 +40,7 @@ import {
doesPathStartWith,
encodePathToProp,
} from '@theatre/shared/utils/addresses'
-import type {SequenceTrackId} from '@theatre/shared/utils/ids'
+import type {ObjectAddressKey, SequenceTrackId} from '@theatre/shared/utils/ids'
import type Sequence from '@theatre/core/sequences/Sequence'
import KeyframeSnapTarget, {
snapPositionsStateD,
@@ -47,7 +52,7 @@ import {
snapToNone,
snapToSome,
} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/KeyframeSnapTarget'
-import {collectAggregateSnapPositions} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/collectAggregateKeyframes'
+import {collectAggregateSnapPositionsSheet} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/collectAggregateKeyframes'
import type {Keyframe} from '@theatre/core/projects/store/types/SheetState_Historic'
const AggregatedKeyframeTrackContainer = styled.div`
@@ -60,6 +65,7 @@ type IAggregatedKeyframeTracksProps = {
viewModel:
| SequenceEditorTree_PropWithChildren
| SequenceEditorTree_SheetObject
+ | SequenceEditorTree_Sheet
aggregatedKeyframes: AggregatedKeyframes
layoutP: Pointer
}
@@ -83,7 +89,6 @@ function AggregatedKeyframeTrack_memo(props: IAggregatedKeyframeTracksProps) {
const {selectedPositions, selection} = useCollectedSelectedPositions(
layoutP,
- viewModel,
aggregatedKeyframes,
)
@@ -118,7 +123,13 @@ function AggregatedKeyframeTrack_memo(props: IAggregatedKeyframeTracksProps) {
: emptyObject
const aggregateSnapPositions = useMemo(
- () => collectAggregateSnapPositions(viewModel, snapPositions),
+ () =>
+ viewModel.type === 'sheet'
+ ? collectAggregateSnapPositionsSheet(viewModel, snapPositions)
+ : collectAggregateSnapPositionsObjectOrCompound(
+ viewModel,
+ snapPositions,
+ ),
[snapPositions],
)
@@ -208,19 +219,22 @@ const {AllSelected, AtLeastOneUnselected, NoneSelected} =
/** Helper to put together the selected positions */
function useCollectedSelectedPositions(
layoutP: Pointer,
- viewModel:
- | SequenceEditorTree_PropWithChildren
- | SequenceEditorTree_SheetObject,
aggregatedKeyframes: AggregatedKeyframes,
): _AggSelection {
- return usePrism(() => {
+ return usePrism(
+ () => val(collectedSelectedPositions(layoutP, aggregatedKeyframes)),
+ [layoutP, aggregatedKeyframes],
+ )
+}
+
+function collectedSelectedPositions(
+ layoutP: Pointer,
+ aggregatedKeyframes: AggregatedKeyframes,
+): IDerivation<_AggSelection> {
+ return prism(() => {
const selectionAtom = val(layoutP.selectionAtom)
- const sheetObjectSelection = val(
- selectionAtom.pointer.current.byObjectKey[
- viewModel.sheetObject.address.objectKey
- ],
- )
- if (!sheetObjectSelection) return EMPTY_SELECTION
+ const selection = val(selectionAtom.pointer.current)
+ if (!selection) return EMPTY_SELECTION
const selectedAtPositions = new Map<
number,
@@ -228,34 +242,14 @@ function useCollectedSelectedPositions(
>()
for (const [position, kfsWithTrack] of aggregatedKeyframes.byPosition) {
- let positionIsSelected: undefined | AggregateKeyframePositionIsSelected =
- undefined
- for (const kfWithTrack of kfsWithTrack) {
- const kfIsSelected =
- sheetObjectSelection.byTrackId[kfWithTrack.track.id]?.byKeyframeId?.[
- kfWithTrack.kf.id
- ] === true
- // -1/10: This sux
- // undefined = have not encountered
- if (positionIsSelected === undefined) {
- // first item
- if (kfIsSelected) {
- positionIsSelected = AllSelected
- } else {
- positionIsSelected = NoneSelected
- }
- } else if (kfIsSelected) {
- if (positionIsSelected === NoneSelected) {
- positionIsSelected = AtLeastOneUnselected
- }
- } else {
- if (positionIsSelected === AllSelected) {
- positionIsSelected = AtLeastOneUnselected
- }
- }
- }
-
- if (positionIsSelected != null) {
+ const positionIsSelected = allOrSomeOrNoneSelected(
+ kfsWithTrack,
+ selection,
+ )
+ if (
+ positionIsSelected !== undefined &&
+ positionIsSelected !== NoneSelected
+ ) {
selectedAtPositions.set(position, positionIsSelected)
}
}
@@ -264,7 +258,38 @@ function useCollectedSelectedPositions(
selectedPositions: selectedAtPositions,
selection: val(selectionAtom.pointer.current),
}
- }, [layoutP, aggregatedKeyframes])
+ })
+}
+
+function allOrSomeOrNoneSelected(
+ keyframeWithTracks: KeyframeWithTrack[],
+ selection: DopeSheetSelection,
+): AggregateKeyframePositionIsSelected | undefined {
+ let positionIsSelected: undefined | AggregateKeyframePositionIsSelected =
+ undefined
+
+ for (const {track, kf} of keyframeWithTracks) {
+ const kfIsSelected =
+ selection.byObjectKey[track.sheetObject.address.objectKey]?.byTrackId[
+ track.id
+ ]?.byKeyframeId?.[kf.id] === true
+ if (positionIsSelected === undefined) {
+ if (kfIsSelected) {
+ positionIsSelected = AllSelected
+ } else {
+ positionIsSelected = NoneSelected
+ }
+ } else if (kfIsSelected) {
+ if (positionIsSelected === NoneSelected) {
+ positionIsSelected = AtLeastOneUnselected
+ }
+ } else {
+ if (positionIsSelected === AllSelected) {
+ positionIsSelected = AtLeastOneUnselected
+ }
+ }
+ }
+ return positionIsSelected
}
function useAggregatedKeyframeTrackContextMenu(
@@ -297,7 +322,11 @@ function pasteKeyframesContextMenuItem(
const sheet = val(props.layoutP.sheet)
const sequence = sheet.getSequence()
- pasteKeyframes(props.viewModel, keyframes, sequence)
+ if (props.viewModel.type === 'sheet') {
+ pasteKeyframesSheet(props.viewModel, keyframes, sequence)
+ } else {
+ pasteKeyframesObjectOrCompound(props.viewModel, keyframes, sequence)
+ }
},
}
}
@@ -313,7 +342,74 @@ function pasteKeyframesContextMenuItem(
* @see StudioAhistoricState.clipboard
* @see setClipboardNestedKeyframes
*/
-function pasteKeyframes(
+function pasteKeyframesSheet(
+ viewModel: SequenceEditorTree_Sheet,
+ keyframes: KeyframeWithPathToPropFromCommonRoot[],
+ sequence: Sequence,
+) {
+ const {projectId, sheetId, sheetInstanceId} = viewModel.sheet.address
+
+ const areKeyframesAllOnSingleTrack = keyframes.every(
+ ({pathToProp}) => pathToProp.length === 0,
+ )
+
+ if (areKeyframesAllOnSingleTrack) {
+ for (const object of viewModel.children.map((child) => child.sheetObject)) {
+ const tracksByObject = valueDerivation(
+ getStudio().atomP.historic.coreByProject[projectId].sheetsById[sheetId]
+ .sequence.tracksByObject[object.address.objectKey],
+ ).getValue()
+
+ const trackIdsOnObject = Object.keys(tracksByObject?.trackData ?? {})
+
+ pasteKeyframesToMultipleTracks(
+ object.address,
+ trackIdsOnObject,
+ keyframes,
+ sequence,
+ )
+ }
+ } else {
+ const tracksByObject = valueDerivation(
+ getStudio().atomP.historic.coreByProject[projectId].sheetsById[sheetId]
+ .sequence.tracksByObject,
+ ).getValue()
+
+ const placeableKeyframes = keyframes
+ .map(({keyframe, pathToProp}) => {
+ const objectKey = pathToProp[0] as ObjectAddressKey
+ const relativePathToProp = pathToProp.slice(1)
+ const pathToPropEncoded = encodePathToProp([...relativePathToProp])
+
+ const trackIdByPropPath =
+ tracksByObject?.[objectKey]?.trackIdByPropPath ?? {}
+
+ const maybeTrackId = trackIdByPropPath[pathToPropEncoded]
+
+ return maybeTrackId
+ ? {
+ keyframe,
+ trackId: maybeTrackId,
+ address: {
+ objectKey,
+ projectId,
+ sheetId,
+ sheetInstanceId,
+ },
+ }
+ : null
+ })
+ .filter((result) => result !== null) as {
+ keyframe: Keyframe
+ trackId: SequenceTrackId
+ address: SheetObjectAddress
+ }[]
+
+ pasteKeyframesToSpecificTracks(placeableKeyframes, sequence)
+ }
+}
+
+function pasteKeyframesObjectOrCompound(
viewModel:
| SequenceEditorTree_PropWithChildren
| SequenceEditorTree_SheetObject,
@@ -322,7 +418,7 @@ function pasteKeyframes(
) {
const {projectId, sheetId, objectKey} = viewModel.sheetObject.address
- const tracksByObject = valueDerivation(
+ const trackRecords = valueDerivation(
getStudio().atomP.historic.coreByProject[projectId].sheetsById[sheetId]
.sequence.tracksByObject[objectKey],
).getValue()
@@ -332,7 +428,7 @@ function pasteKeyframes(
)
if (areKeyframesAllOnSingleTrack) {
- const trackIdsOnObject = Object.keys(tracksByObject?.trackData ?? {})
+ const trackIdsOnObject = Object.keys(trackRecords?.trackData ?? {})
if (viewModel.type === 'sheetObject') {
pasteKeyframesToMultipleTracks(
@@ -342,7 +438,7 @@ function pasteKeyframes(
sequence,
)
} else {
- const trackIdByPropPath = tracksByObject?.trackIdByPropPath || {}
+ const trackIdByPropPath = trackRecords?.trackIdByPropPath || {}
const trackIdsOnCompoundProp = Object.entries(trackIdByPropPath)
.filter(
@@ -364,7 +460,7 @@ function pasteKeyframes(
)
}
} else {
- const trackIdByPropPath = tracksByObject?.trackIdByPropPath || {}
+ const trackIdByPropPath = trackRecords?.trackIdByPropPath || {}
const rootPath =
viewModel.type === 'propWithChildren' ? viewModel.pathToProp : []
@@ -382,19 +478,17 @@ function pasteKeyframes(
? {
keyframe,
trackId: maybeTrackId,
+ address: viewModel.sheetObject.address,
}
: null
})
.filter((result) => result !== null) as {
keyframe: Keyframe
trackId: SequenceTrackId
+ address: SheetObjectAddress
}[]
- pasteKeyframesToSpecificTracks(
- viewModel.sheetObject.address,
- placeableKeyframes,
- sequence,
- )
+ pasteKeyframesToSpecificTracks(placeableKeyframes, sequence)
}
}
@@ -428,10 +522,10 @@ function pasteKeyframesToMultipleTracks(
}
function pasteKeyframesToSpecificTracks(
- address: SheetObjectAddress,
keyframesWithTracksToPlaceThemIn: {
keyframe: Keyframe
trackId: SequenceTrackId
+ address: SheetObjectAddress
}[],
sequence: Sequence,
) {
@@ -441,7 +535,11 @@ function pasteKeyframesToSpecificTracks(
)?.position!
getStudio()!.transaction(({stateEditors}) => {
- for (const {keyframe, trackId} of keyframesWithTracksToPlaceThemIn) {
+ for (const {
+ keyframe,
+ trackId,
+ address,
+ } of keyframesWithTracksToPlaceThemIn) {
stateEditors.coreByProject.historic.sheetsById.sequence.setKeyframeAtPosition(
{
...address,
@@ -499,11 +597,14 @@ function useDragForAggregateKeyframeDot(
getAggregateKeyframeEditorUtilsPrismFn(props),
).getValue().cur.keyframes
+ const address =
+ props.viewModel.type === 'sheet'
+ ? props.viewModel.sheet.address
+ : props.viewModel.sheetObject.address
+
const tracksByObject = val(
- getStudio()!.atomP.historic.coreByProject[
- props.viewModel.sheetObject.address.projectId
- ].sheetsById[props.viewModel.sheetObject.address.sheetId].sequence
- .tracksByObject,
+ getStudio()!.atomP.historic.coreByProject[address.projectId]
+ .sheetsById[address.sheetId].sequence.tracksByObject,
)!
// Calculate all the valid snap positions in the sequence editor,
@@ -537,10 +638,9 @@ function useDragForAggregateKeyframeDot(
AggregateKeyframePositionIsSelected.AllSelected
) {
const {selection, viewModel} = props
- const {sheetObject} = viewModel
const handlers = selection
.getDragHandlers({
- ...sheetObject.address,
+ ...address,
domNode: containerNode!,
positionAtStartOfDrag: keyframes[0].kf.position,
})
@@ -587,7 +687,7 @@ function useDragForAggregateKeyframeDot(
const original = keyframe.kf
stateEditors.coreByProject.historic.sheetsById.sequence.replaceKeyframes(
{
- ...propsAtStartOfDrag.viewModel.sheetObject.address,
+ ...keyframe.track.sheetObject.address,
trackId: keyframe.track.id,
keyframes: [{...original, position: newPosition}],
snappingFunction: val(
diff --git a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/BasicKeyframedTrack.tsx b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/BasicKeyframedTrack.tsx
index 0a3dfd3..c11dc75 100644
--- a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/BasicKeyframedTrack.tsx
+++ b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/BasicKeyframedTrack.tsx
@@ -74,7 +74,11 @@ const BasicKeyframedTrack: React.VFC = React.memo(
const snapToAllKeyframes = snapPositionsState.mode === 'snapToAll'
const track = useMemo(
- () => ({data: trackData, id: leaf.trackId}),
+ () => ({
+ data: trackData,
+ id: leaf.trackId,
+ sheetObject: props.leaf.sheetObject,
+ }),
[trackData, leaf.trackId],
)
diff --git a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/KeyframeEditor/DeterminePropEditorForSingleKeyframe.tsx b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/KeyframeEditor/DeterminePropEditorForSingleKeyframe.tsx
index ef5022c..c25ac2b 100644
--- a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/KeyframeEditor/DeterminePropEditorForSingleKeyframe.tsx
+++ b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/KeyframeEditor/DeterminePropEditorForSingleKeyframe.tsx
@@ -10,6 +10,7 @@ import type {
} from './useSingleKeyframeInlineEditorPopover'
import last from 'lodash-es/last'
import {useTempTransactionEditingTools} from './useTempTransactionEditingTools'
+import {valueInProp} from '@theatre/shared/propTypes/utils'
const SingleKeyframePropEditorContainer = styled.div`
display: flex;
@@ -127,7 +128,7 @@ function PrimitivePropEditor(
@@ -136,6 +137,10 @@ function PrimitivePropEditor(
}
}
+// These editing tools are distinct from the editing tools used in the
+// prop editors in the details panel: These editing tools edit the value of a keyframe
+// while the details editing tools edit the value of the sequence at the playhead
+// (potentially creating a new keyframe).
function useEditingToolsForKeyframeEditorPopover(
props: PrimitivePropEditingOptions,
) {
diff --git a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/DopeSheetSelectionView.tsx b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/DopeSheetSelectionView.tsx
index 2956163..dadf020 100644
--- a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/DopeSheetSelectionView.tsx
+++ b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/DopeSheetSelectionView.tsx
@@ -18,6 +18,7 @@ import type {
import type {
SequenceEditorTree_AllRowTypes,
SequenceEditorTree_PropWithChildren,
+ SequenceEditorTree_Sheet,
SequenceEditorTree_SheetObject,
} from '@theatre/studio/panels/SequenceEditorPanel/layout/tree'
import DopeSnap from '@theatre/studio/panels/SequenceEditorPanel/RightOverlay/DopeSnap'
@@ -157,28 +158,25 @@ namespace utils {
const collectForAggregatedChildren = (
logger: IUtilLogger,
layout: SequenceEditorPanelLayout,
- leaf: SequenceEditorTree_SheetObject | SequenceEditorTree_PropWithChildren,
+ leaf:
+ | SequenceEditorTree_SheetObject
+ | SequenceEditorTree_PropWithChildren
+ | SequenceEditorTree_Sheet,
bounds: SelectionBounds,
selectionByObjectKey: DopeSheetSelection['byObjectKey'],
) => {
- const sheetObject = leaf.sheetObject
- const aggregatedKeyframes = collectAggregateKeyframesInPrism(logger, leaf)
+ const aggregatedKeyframes = collectAggregateKeyframesInPrism(leaf)
if (
leaf.top + leaf.nodeHeight / 2 + HITBOX_SIZE_PX > bounds.v[0] &&
leaf.top + leaf.nodeHeight / 2 - HITBOX_SIZE_PX < bounds.v[1]
) {
for (const [position, keyframes] of aggregatedKeyframes.byPosition) {
- if (
- position + layout.scaledSpace.toUnitSpace(HITBOX_SIZE_PX) <=
- bounds.h[0]
- )
- continue
- if (
- position - layout.scaledSpace.toUnitSpace(HITBOX_SIZE_PX) >=
- bounds.h[1]
- )
- break
+ const hitboxWidth = layout.scaledSpace.toUnitSpace(HITBOX_SIZE_PX)
+ const isHitboxOutsideSelection =
+ position + hitboxWidth <= bounds.h[0] ||
+ position - hitboxWidth >= bounds.h[1]
+ if (isHitboxOutsideSelection) continue
for (const keyframeWithTrack of keyframes) {
mutableSetDeep(
@@ -186,9 +184,11 @@ namespace utils {
(selectionByObjectKeyP) =>
// convenience for accessing a deep path which might not actually exist
// through the use of pointer proxy (so we don't have to deal with undeifned )
- selectionByObjectKeyP[sheetObject.address.objectKey].byTrackId[
- keyframeWithTrack.track.id
- ].byKeyframeId[keyframeWithTrack.kf.id],
+ selectionByObjectKeyP[
+ keyframeWithTrack.track.sheetObject.address.objectKey
+ ].byTrackId[keyframeWithTrack.track.id].byKeyframeId[
+ keyframeWithTrack.kf.id
+ ],
true,
)
}
@@ -207,6 +207,15 @@ namespace utils {
selectionByObjectKey: DopeSheetSelection['byObjectKey'],
) => void
} = {
+ sheet(logger, layout, leaf, bounds, selectionByObjectKey) {
+ collectForAggregatedChildren(
+ logger,
+ layout,
+ leaf,
+ bounds,
+ selectionByObjectKey,
+ )
+ },
propWithChildren(logger, layout, leaf, bounds, selectionByObjectKey) {
collectForAggregatedChildren(
logger,
diff --git a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/PropWithChildrenRow.tsx b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/PropWithChildrenRow.tsx
index 335949b..ac88f4b 100644
--- a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/PropWithChildrenRow.tsx
+++ b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/PropWithChildrenRow.tsx
@@ -39,10 +39,7 @@ const RightPropWithChildrenRow: React.VFC<{
viewModel.pathToProp.join(),
)
return usePrism(() => {
- const aggregatedKeyframes = collectAggregateKeyframesInPrism(
- logger.utilFor.internal(),
- viewModel,
- )
+ const aggregatedKeyframes = collectAggregateKeyframesInPrism(viewModel)
const node = (
`
- position: absolute;
- top: 0;
- right: 0;
- width: ${(props) => props.width}px;
- bottom: 0;
- z-index: ${() => zIndexes.rightBackground};
- overflow: hidden;
- background: ${darken(1 * 0.03, theme.panel.bg)};
-`
-
const Right: React.FC<{
layoutP: Pointer
}> = ({layoutP}) => {
diff --git a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/SheetObjectRow.tsx b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/SheetObjectRow.tsx
index 1be0414..27eddb2 100644
--- a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/SheetObjectRow.tsx
+++ b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/SheetObjectRow.tsx
@@ -5,7 +5,6 @@ import type {Pointer} from '@theatre/dataverse'
import React from 'react'
import {decideRowByPropType} from './PropWithChildrenRow'
import RightRow from './Row'
-import {useLogger} from '@theatre/studio/uiComponents/useLogger'
import {collectAggregateKeyframesInPrism} from './collectAggregateKeyframes'
import AggregatedKeyframeTrack from './AggregatedKeyframeTrack/AggregatedKeyframeTrack'
@@ -13,15 +12,8 @@ const RightSheetObjectRow: React.VFC<{
leaf: SequenceEditorTree_SheetObject
layoutP: Pointer
}> = ({leaf, layoutP}) => {
- const logger = useLogger(
- `RightSheetObjectRow`,
- leaf.sheetObject.address.objectKey,
- )
return usePrism(() => {
- const aggregatedKeyframes = collectAggregateKeyframesInPrism(
- logger.utilFor.internal(),
- leaf,
- )
+ const aggregatedKeyframes = collectAggregateKeyframesInPrism(leaf)
const node = (
}> = ({leaf, layoutP}) => {
return usePrism(() => {
+ const aggregatedKeyframes = collectAggregateKeyframesInPrism(leaf)
+
+ const node = (
+
+ )
+
return (
- <>
+
{leaf.children.map((sheetObjectLeaf) => (
))}
- >
+
)
}, [leaf, layoutP])
}
diff --git a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/collectAggregateKeyframes.tsx b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/collectAggregateKeyframes.tsx
index 333b242..81702e5 100644
--- a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/collectAggregateKeyframes.tsx
+++ b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/collectAggregateKeyframes.tsx
@@ -1,7 +1,9 @@
import getStudio from '@theatre/studio/getStudio'
import {val} from '@theatre/dataverse'
import type {
+ SequenceEditorTree_PrimitiveProp,
SequenceEditorTree_PropWithChildren,
+ SequenceEditorTree_Sheet,
SequenceEditorTree_SheetObject,
} from '@theatre/studio/panels/SequenceEditorPanel/layout/tree'
import type {
@@ -13,9 +15,9 @@ import type {
Keyframe,
TrackData,
} from '@theatre/core/projects/store/types/SheetState_Historic'
-import type {IUtilLogger} from '@theatre/shared/logger'
import {encodePathToProp} from '@theatre/shared/utils/addresses'
import {uniq} from 'lodash-es'
+import type SheetObject from '@theatre/core/sheetObjects/SheetObject'
/**
* An index over a series of keyframes that have been collected from different tracks.
@@ -30,6 +32,7 @@ export type AggregatedKeyframes = {
export type TrackWithId = {
id: SequenceTrackId
data: TrackData
+ sheetObject: SheetObject
}
export type KeyframeWithTrack = {
@@ -59,67 +62,27 @@ export type KeyframeWithTrack = {
*
*/
export function collectAggregateKeyframesInPrism(
- logger: IUtilLogger,
- leaf: SequenceEditorTree_PropWithChildren | SequenceEditorTree_SheetObject,
+ leaf:
+ | SequenceEditorTree_Sheet
+ | SequenceEditorTree_PropWithChildren
+ | SequenceEditorTree_SheetObject,
): AggregatedKeyframes {
- const sheetObject = leaf.sheetObject
+ const tracks =
+ leaf.type === 'sheet'
+ ? collectAggregateKeyframesSheet(leaf)
+ : collectAggregateKeyframesCompoundOrObject(leaf)
- const projectId = sheetObject.address.projectId
-
- const sheetObjectTracksP =
- getStudio().atomP.historic.coreByProject[projectId].sheetsById[
- sheetObject.address.sheetId
- ].sequence.tracksByObject[sheetObject.address.objectKey]
-
- const aggregatedKeyframes: AggregatedKeyframes[] = []
- const childSimpleTracks: TrackWithId[] = []
- for (const childLeaf of leaf.children) {
- if (childLeaf.type === 'primitiveProp') {
- const trackId = val(
- sheetObjectTracksP.trackIdByPropPath[
- encodePathToProp(childLeaf.pathToProp)
- ],
- )
- if (!trackId) {
- logger.trace('missing track id?', {childLeaf})
- continue
- }
-
- const trackData = val(sheetObjectTracksP.trackData[trackId])
- if (!trackData) {
- logger.trace('missing track data?', {trackId, childLeaf})
- continue
- }
-
- childSimpleTracks.push({id: trackId, data: trackData})
- } else if (childLeaf.type === 'propWithChildren') {
- aggregatedKeyframes.push(
- collectAggregateKeyframesInPrism(
- logger.named('propWithChildren', childLeaf.pathToProp.join()),
- childLeaf,
- ),
- )
- } else {
- const _exhaustive: never = childLeaf
- logger.error('unexpected kind of prop', {childLeaf})
- }
+ return {
+ byPosition: keyframesByPositionFromTrackWithIds(tracks),
+ tracks,
}
+}
- logger.trace('see collected of children', {
- aggregatedKeyframes,
- childSimpleTracks,
- })
-
- const tracks = aggregatedKeyframes
- .flatMap((a) => a.tracks)
- .concat(childSimpleTracks)
-
+function keyframesByPositionFromTrackWithIds(tracks: TrackWithId[]) {
const byPosition = new Map()
for (const track of tracks) {
- const kfs = track.data.keyframes
- for (let i = 0; i < kfs.length; i++) {
- const kf = kfs[i]
+ for (const kf of track.data.keyframes) {
let existing = byPosition.get(kf.position)
if (!existing) {
existing = []
@@ -129,7 +92,7 @@ export function collectAggregateKeyframesInPrism(
kf,
track,
itemKey: createStudioSheetItemKey.forTrackKeyframe(
- sheetObject,
+ track.sheetObject,
track.id,
kf.id,
),
@@ -137,19 +100,28 @@ export function collectAggregateKeyframesInPrism(
}
}
- return {
- byPosition,
- tracks,
- }
+ return byPosition
}
-/**
- * Collects all the snap positions for an aggregate track.
- */
-export function collectAggregateSnapPositions(
+function collectAggregateKeyframesSheet(
+ leaf: SequenceEditorTree_Sheet,
+): TrackWithId[] {
+ return leaf.children.flatMap(collectAggregateKeyframesCompoundOrObject)
+}
+
+function collectAggregateKeyframesCompoundOrObject(
leaf: SequenceEditorTree_PropWithChildren | SequenceEditorTree_SheetObject,
- snapTargetPositions: {[key: string]: {[key: string]: number[]}},
-): number[] {
+): TrackWithId[] {
+ return leaf.children.flatMap((childLeaf) =>
+ childLeaf.type === 'propWithChildren'
+ ? collectAggregateKeyframesCompoundOrObject(childLeaf)
+ : collectAggregateKeyframesPrimitiveProp(childLeaf),
+ )
+}
+
+function collectAggregateKeyframesPrimitiveProp(
+ leaf: SequenceEditorTree_PrimitiveProp,
+): TrackWithId[] {
const sheetObject = leaf.sheetObject
const projectId = sheetObject.address.projectId
@@ -158,29 +130,67 @@ export function collectAggregateSnapPositions(
getStudio().atomP.historic.coreByProject[projectId].sheetsById[
sheetObject.address.sheetId
].sequence.tracksByObject[sheetObject.address.objectKey]
+ const trackId = val(
+ sheetObjectTracksP.trackIdByPropPath[encodePathToProp(leaf.pathToProp)],
+ )
+ if (!trackId) return []
- const positions: number[] = []
- for (const childLeaf of leaf.children) {
- if (childLeaf.type === 'primitiveProp') {
- const trackId = val(
- sheetObjectTracksP.trackIdByPropPath[
- encodePathToProp(childLeaf.pathToProp)
- ],
- )
- if (!trackId) {
- continue
- }
+ const trackData = val(sheetObjectTracksP.trackData[trackId])
+ if (!trackData) return []
- positions.push(
- ...(snapTargetPositions[sheetObject.address.objectKey]?.[trackId] ??
- []),
- )
- } else if (childLeaf.type === 'propWithChildren') {
- positions.push(
- ...collectAggregateSnapPositions(childLeaf, snapTargetPositions),
- )
- }
- }
-
- return uniq(positions)
+ return [{id: trackId, data: trackData, sheetObject}]
+}
+
+/**
+ * Collects all the snap positions for an aggregate track.
+ */
+export function collectAggregateSnapPositionsSheet(
+ leaf: SequenceEditorTree_Sheet,
+ snapTargetPositions: {[key: string]: {[key: string]: number[]}},
+): number[] {
+ return uniq(
+ leaf.children.flatMap((childLeaf) =>
+ collectAggregateSnapPositionsObjectOrCompound(
+ childLeaf,
+ snapTargetPositions,
+ ),
+ ),
+ )
+}
+
+export function collectAggregateSnapPositionsObjectOrCompound(
+ leaf: SequenceEditorTree_PropWithChildren | SequenceEditorTree_SheetObject,
+ snapTargetPositions: {[key: string]: {[key: string]: number[]}},
+): number[] {
+ return uniq(
+ leaf.children.flatMap((childLeaf) =>
+ childLeaf.type === 'propWithChildren'
+ ? collectAggregateSnapPositionsObjectOrCompound(
+ childLeaf,
+ snapTargetPositions,
+ )
+ : collectAggregateSnapPositionsPrimitiveProp(
+ childLeaf,
+ snapTargetPositions,
+ ),
+ ),
+ )
+}
+
+function collectAggregateSnapPositionsPrimitiveProp(
+ leaf: SequenceEditorTree_PrimitiveProp,
+ snapTargetPositions: {[key: string]: {[key: string]: number[]}},
+): number[] {
+ const sheetObject = leaf.sheetObject
+ const projectId = sheetObject.address.projectId
+ const sheetObjectTracksP =
+ getStudio().atomP.historic.coreByProject[projectId].sheetsById[
+ sheetObject.address.sheetId
+ ].sequence.tracksByObject[sheetObject.address.objectKey]
+ const trackId = val(
+ sheetObjectTracksP.trackIdByPropPath[encodePathToProp(leaf.pathToProp)],
+ )
+ if (!trackId) return []
+
+ return snapTargetPositions[sheetObject.address.objectKey]?.[trackId] ?? []
}
diff --git a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/selections.ts b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/selections.ts
index ce1a4da..7af802a 100644
--- a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/selections.ts
+++ b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/selections.ts
@@ -108,9 +108,6 @@ export function selectedKeyframeConnections(
* {keyframe, pathToProp: ['scale', 'x']},
* ]
* ```
- *
- * TODO - we don't yet support copying/pasting keyframes from multiple objects to multiple objects.
- * The main reason is that we don't yet have an aggregate track for several objects.
*/
export function copyableKeyframesFromSelection(
projectId: ProjectId,
@@ -175,7 +172,7 @@ export function keyframesWithPaths({
const encodedPropPath = propPathByTrackId[trackId]
if (!encodedPropPath) return null
- const pathToProp = decodePathToProp(encodedPropPath)
+ const pathToProp = [objectKey, ...decodePathToProp(encodedPropPath)]
return keyframeIds
.map((keyframeId) => ({
diff --git a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/setCollapsedSheetObjectOrCompoundProp.tsx b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/setCollapsedSheetObjectOrCompoundProp.tsx
index bf01ec4..834f11c 100644
--- a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/setCollapsedSheetObjectOrCompoundProp.tsx
+++ b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/setCollapsedSheetObjectOrCompoundProp.tsx
@@ -5,7 +5,7 @@ import type {
WithoutSheetInstance,
} from '@theatre/shared/utils/addresses'
-export function setCollapsedSheetObjectOrCompoundProp(
+export function setCollapsedSheetItem(
isCollapsed: boolean,
toCollapse: {
sheetAddress: WithoutSheetInstance
diff --git a/theatre/studio/src/panels/SequenceEditorPanel/layout/layout.ts b/theatre/studio/src/panels/SequenceEditorPanel/layout/layout.ts
index b7b0586..5318c11 100644
--- a/theatre/studio/src/panels/SequenceEditorPanel/layout/layout.ts
+++ b/theatre/studio/src/panels/SequenceEditorPanel/layout/layout.ts
@@ -1,7 +1,7 @@
import type Sheet from '@theatre/core/sheets/Sheet'
import getStudio from '@theatre/studio/getStudio'
import type useDrag from '@theatre/studio/uiComponents/useDrag'
-import type {SheetObjectAddress} from '@theatre/shared/utils/addresses'
+import type {SheetAddress} from '@theatre/shared/utils/addresses'
import subPrism from '@theatre/shared/utils/subPrism'
import type {
IRange,
@@ -57,7 +57,7 @@ export type DopeSheetSelection = {
}
>
getDragHandlers(
- origin: SheetObjectAddress & {
+ origin: SheetAddress & {
positionAtStartOfDrag: number
domNode: Element
},
diff --git a/theatre/studio/src/panels/SequenceEditorPanel/layout/tree.ts b/theatre/studio/src/panels/SequenceEditorPanel/layout/tree.ts
index ecbf42d..8991b77 100644
--- a/theatre/studio/src/panels/SequenceEditorPanel/layout/tree.ts
+++ b/theatre/studio/src/panels/SequenceEditorPanel/layout/tree.ts
@@ -58,6 +58,7 @@ export type SequenceEditorTree = SequenceEditorTree_Sheet
export type SequenceEditorTree_Sheet = SequenceEditorTree_Row<'sheet'> & {
sheet: Sheet
+ isCollapsed: boolean
children: SequenceEditorTree_SheetObject[]
}
@@ -106,34 +107,44 @@ export const calculateSequenceEditorTree = (
studio: Studio,
): SequenceEditorTree => {
prism.ensurePrism()
- let topSoFar = titleBarHeight
- let nSoFar = 0
const rootShouldRender = true
+ let topSoFar = titleBarHeight + (rootShouldRender ? HEIGHT_OF_ANY_TITLE : 0)
+ let nSoFar = 0
+
+ const collapsableItemSetP =
+ studio.atomP.ahistoric.projects.stateByProjectId[sheet.address.projectId]
+ .stateBySheetId[sheet.address.sheetId].sequence.collapsableItems
+
+ const isCollapsedP =
+ collapsableItemSetP.byId[createStudioSheetItemKey.forSheet()].isCollapsed
+ const isCollapsed = valueDerivation(isCollapsedP).getValue() ?? false
const tree: SequenceEditorTree = {
type: 'sheet',
+ isCollapsed,
sheet,
children: [],
sheetItemKey: createStudioSheetItemKey.forSheet(),
shouldRender: rootShouldRender,
- top: topSoFar,
- depth: -1,
+ top: titleBarHeight,
+ depth: 0,
n: nSoFar,
- nodeHeight: 0, // always 0
- heightIncludingChildren: -1, // will define this later
+ nodeHeight: rootShouldRender ? HEIGHT_OF_ANY_TITLE : 0,
+ heightIncludingChildren: -1, // calculated below
}
if (rootShouldRender) {
nSoFar += 1
}
- const collapsableItemSetP =
- studio.atomP.ahistoric.projects.stateByProjectId[sheet.address.projectId]
- .stateBySheetId[sheet.address.sheetId].sequence.collapsableItems
-
for (const sheetObject of Object.values(val(sheet.objectsP))) {
if (sheetObject) {
- addObject(sheetObject, tree.children, tree.depth + 1, rootShouldRender)
+ addObject(
+ sheetObject,
+ tree.children,
+ tree.depth + 1,
+ rootShouldRender && !isCollapsed,
+ )
}
}
tree.heightIncludingChildren = topSoFar - tree.top
@@ -167,9 +178,7 @@ export const calculateSequenceEditorTree = (
n: nSoFar,
sheetObject: sheetObject,
nodeHeight: shouldRender ? HEIGHT_OF_ANY_TITLE : 0,
- // Question: Why -1? Is this relevant for "shouldRender"?
- // Perhaps this is to indicate this does not have a valid value.
- heightIncludingChildren: -1,
+ heightIncludingChildren: -1, // calculated below
}
arrayOfChildren.push(row)
@@ -350,5 +359,6 @@ export const calculateSequenceEditorTree = (
topSoFar += row.nodeHeight
}
+ console.log('tree :)', tree)
return tree
}
diff --git a/theatre/studio/src/propEditors/useEditingToolsForCompoundProp.tsx b/theatre/studio/src/propEditors/useEditingToolsForCompoundProp.tsx
index 2f88560..e1c8f28 100644
--- a/theatre/studio/src/propEditors/useEditingToolsForCompoundProp.tsx
+++ b/theatre/studio/src/propEditors/useEditingToolsForCompoundProp.tsx
@@ -257,7 +257,7 @@ function ControlIndicators({
...s,
nearbies: getNearbyKeyframesOfTrack(
obj,
- {id: s.trackId, data: s.track!},
+ {id: s.trackId, data: s.track!, sheetObject: obj},
sequencePosition,
),
}))
diff --git a/theatre/studio/src/propEditors/useEditingToolsForSimpleProp.tsx b/theatre/studio/src/propEditors/useEditingToolsForSimpleProp.tsx
index 8d5be14..1d5ed87 100644
--- a/theatre/studio/src/propEditors/useEditingToolsForSimpleProp.tsx
+++ b/theatre/studio/src/propEditors/useEditingToolsForSimpleProp.tsx
@@ -176,6 +176,7 @@ function createDerivation(
track && {
data: track,
id: sequenceTrackId,
+ sheetObject: obj,
},
sequencePosition,
)