theatre/theatre/studio/src/panels/SequenceEditorPanel/layout/layout.ts
2021-07-16 15:42:01 +02:00

353 lines
9.9 KiB
TypeScript

import type Sheet from '@theatre/core/sheets/Sheet'
import getStudio from '@theatre/studio/getStudio'
import type useDrag from '@theatre/studio/uiComponents/useDrag'
import type {PropAddress} from '@theatre/shared/utils/addresses'
import subPrism from '@theatre/shared/utils/subPrism'
import type {
IRange,
PositionInScreenSpace,
StrictRecord,
} from '@theatre/shared/utils/types'
import {valToAtom} from '@theatre/shared/utils/valToAtom'
import type {IDerivation, Pointer} from '@theatre/dataverse'
import {Atom, prism, val} from '@theatre/dataverse'
import type {SequenceEditorTree} from './tree'
import {calculateSequenceEditorTree} from './tree'
import {clamp} from 'lodash-es'
// A Side is either the left side of the panel or the right side
type DimsOfPanelPart = {
width: number
height: number
/**
* In absolute pixels, relative to getBoundingClientRect()
*/
screenX: PositionInScreenSpace
/**
* In absolute pixels, relative to getBoundingClientRect()
*/
screenY: PositionInScreenSpace
}
export type PanelDims = {
width: number
height: number
widthWithoutBorder: number
heightWithoutBorder: number
screenX: PositionInScreenSpace
screenY: PositionInScreenSpace
}
export type DopeSheetSelection = {
type: 'DopeSheetSelection'
byObjectKey: StrictRecord<
string,
{
byTrackId: StrictRecord<
string,
{
byKeyframeId: StrictRecord<string, true>
}
>
}
>
getDragHandlers(
origin: PropAddress & {trackId: string; keyframeId: string},
): Parameters<typeof useDrag>[1]
delete(): void
}
export type SequenceEditorPanelLayout = {
sheet: Sheet
tree: SequenceEditorTree
panelDims: PanelDims
leftDims: DimsOfPanelPart
rightDims: DimsOfPanelPart
dopeSheetDims: DimsOfPanelPart
graphEditorDims: DimsOfPanelPart & {
isOpen: boolean
padding: {top: number; bottom: number}
}
bottomRectangleThingyDims: DimsOfPanelPart & {bottom: number}
horizontalScrollbarDims: {bottom: number}
graphEditorVerticalSpace: {
space: number
fromExtremumSpace(e: number): number
toExtremumSpace(e: number): number
}
seeker: {
isSeeking: boolean
setIsSeeking: (isSeeking: boolean) => void
}
unitSpace: {}
scaledSpace: {
leftPadding: number
fromUnitSpace(u: number): number
toUnitSpace(s: number): number
}
clippedSpace: {
/**
* The width of the visible area of the sequence (pretty much the right side of the panel)
*/
width: number
fromUnitSpace(u: number): number
toUnitSpace(c: number): number
range: IRange
setRange(range: IRange): void
}
selectionAtom: Atom<{current?: DopeSheetSelection}>
}
// type UnitSpaceProression = Nominal<number, 'unitSpaceProgression'>
// type ClippedSpaceProgression = Nominal<number, 'ClippedSpaceProgression'>
/**
* This means the left side of the panel is 20% of its width, and the
* right side is 80%
*/
const panelSplitRatio = 0.2
const initialClippedSpaceRange: IRange = {start: 0, end: 10}
export function sequenceEditorPanelLayout(
sheet: Sheet,
panelDimsP: Pointer<PanelDims>,
): IDerivation<Pointer<SequenceEditorPanelLayout>> {
const studio = getStudio()!
const ahistoricStateP =
studio.atomP.ahistoric.projects.stateByProjectId[sheet.address.projectId]
.stateBySheetId[sheet.address.sheetId]
const historicStateP =
studio.atomP.historic.projects.stateByProjectId[sheet.address.projectId]
.stateBySheetId[sheet.address.sheetId]
return prism(() => {
const tree = subPrism('tree', () => calculateSequenceEditorTree(sheet), [])
const panelDims = val(panelDimsP)
const graphEditorState = val(
studio.atomP.historic.panels.sequenceEditor.graphEditor,
)
const {
leftDims,
rightDims,
graphEditorDims,
dopeSheetDims,
bottomRectangleThingyDims,
horizontalScrollbarDims,
} = prism.memo(
'leftDims',
() => {
const leftDims: DimsOfPanelPart = {
width: Math.floor(panelDims.width * panelSplitRatio),
height: panelDims.height,
screenX: panelDims.screenX,
screenY: panelDims.screenY,
}
const rightDims: DimsOfPanelPart = {
width: panelDims.width - leftDims.width,
height: panelDims.height,
screenX: (panelDims.screenX +
leftDims.width) as PositionInScreenSpace,
screenY: panelDims.screenY,
}
const graphEditorOpen = graphEditorState?.isOpen === true
const bottomRectangleThingyHeight = 0
const graphEditorHeight = Math.floor(
(graphEditorOpen
? clamp(graphEditorState?.height ?? 0.5, 0.1, 0.7)
: 0) * panelDims.heightWithoutBorder,
)
const bottomHeight = bottomRectangleThingyHeight + graphEditorHeight
const dopeSheetHeight = panelDims.height - bottomHeight
const bottomRectangleThingyDims: SequenceEditorPanelLayout['bottomRectangleThingyDims'] =
{
width: panelDims.width,
height: bottomRectangleThingyHeight,
screenX: panelDims.screenX,
screenY: panelDims.screenY + dopeSheetHeight,
bottom: graphEditorHeight,
}
const dopeSheetDims: SequenceEditorPanelLayout['dopeSheetDims'] = {
width: panelDims.width,
height: dopeSheetHeight,
screenX: panelDims.screenX,
screenY: panelDims.screenY,
}
// const graphEditorHeight = panelDims.height - dopeSheetDims.height
const graphEditorDims: SequenceEditorPanelLayout['graphEditorDims'] = {
isOpen: graphEditorOpen,
width: rightDims.width,
height: graphEditorHeight,
screenX: panelDims.screenX,
screenY:
bottomRectangleThingyDims.screenY +
bottomRectangleThingyDims.height,
padding: {
top: 20,
bottom: 20,
},
}
const horizontalScrollbarDims: SequenceEditorPanelLayout['horizontalScrollbarDims'] =
{
bottom: graphEditorOpen ? 0 : bottomRectangleThingyDims.height,
}
return {
leftDims,
rightDims,
graphEditorDims,
dopeSheetDims,
bottomRectangleThingyDims,
horizontalScrollbarDims,
}
},
[panelDims, graphEditorState],
)
const graphEditorVerticalSpace = prism.memo(
'graphEditorVerticalSpace',
(): SequenceEditorPanelLayout['graphEditorVerticalSpace'] => {
const space =
graphEditorDims.height -
graphEditorDims.padding.top -
graphEditorDims.padding.bottom
return {
space,
fromExtremumSpace(ex: number): number {
return ex * space
},
toExtremumSpace(s: number): number {
return s / space
},
}
},
[graphEditorDims],
)
const [isSeeking, setIsSeeking] = prism.state('isSeeking', false)
const seeker = {
isSeeking,
setIsSeeking,
}
const unitSpace = {}
const clippedSpaceRange =
val(ahistoricStateP.sequence.clippedSpaceRange) ??
initialClippedSpaceRange
const scaledSpace: SequenceEditorPanelLayout['scaledSpace'] = prism.memo(
'scaledSpace',
() => {
const unitsShownInClippedSpace =
clippedSpaceRange.end - clippedSpaceRange.start
const pixelsShownInClippedSpace = rightDims.width
const unitToPixelRatio =
unitsShownInClippedSpace / pixelsShownInClippedSpace
const pixelToUnitRatio =
pixelsShownInClippedSpace / unitsShownInClippedSpace
return {
fromUnitSpace(u: number): number {
return u * pixelToUnitRatio
},
toUnitSpace(s: number): number {
return s * unitToPixelRatio
},
leftPadding: 10,
}
},
[clippedSpaceRange, rightDims.width],
)
const setClippedSpaceRange = prism.memo(
'setClippedSpaceRange',
() => {
return function setClippedSpaceRange(_range: IRange): void {
studio.transaction(({stateEditors}) => {
const range = {..._range}
if (range.end <= range.start) {
range.end = range.start + 1
}
if (range.start < 0) {
const length = range.end - range.start
range.start = 0
range.end = length
}
stateEditors.studio.ahistoric.projects.stateByProjectId.stateBySheetId.sequence.clippedSpaceRange.set(
{...sheet.address, range},
)
})
}
},
[],
)
const clippedSpace: SequenceEditorPanelLayout['clippedSpace'] = prism.memo(
'clippedSpace',
() => {
return {
range: clippedSpaceRange,
width: rightDims.width,
fromUnitSpace(u: number): number {
return (
scaledSpace.fromUnitSpace(u - clippedSpaceRange.start) +
scaledSpace.leftPadding
)
},
toUnitSpace(c: number): number {
return (
scaledSpace.toUnitSpace(c - scaledSpace.leftPadding) +
clippedSpaceRange.start
)
},
setRange: setClippedSpaceRange,
}
},
[clippedSpaceRange, rightDims.width, scaledSpace, setClippedSpaceRange],
)
const selectionAtom = prism.memo(
'selection.current',
(): SequenceEditorPanelLayout['selectionAtom'] => {
return new Atom({})
},
[],
)
const finalAtom = valToAtom<SequenceEditorPanelLayout>('finalAtom', {
sheet,
tree,
panelDims,
leftDims,
rightDims,
dopeSheetDims,
bottomRectangleThingyDims,
horizontalScrollbarDims,
seeker,
unitSpace,
scaledSpace,
clippedSpace,
graphEditorDims,
graphEditorVerticalSpace,
selectionAtom,
})
return finalAtom.pointer
})
}