diff --git a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/LengthIndicator.tsx b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/LengthIndicator.tsx index 70a3a92..9ccf1be 100644 --- a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/LengthIndicator.tsx +++ b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/LengthIndicator.tsx @@ -1,24 +1,20 @@ import {usePrism} from '@theatre/dataverse-react' import type {Pointer} from '@theatre/dataverse' +import {Box} from '@theatre/dataverse' import {val} from '@theatre/dataverse' -import React from 'react' +import React, {useMemo, useRef} from 'react' import styled from 'styled-components' import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout' import {zIndexes} from '@theatre/studio/panels/SequenceEditorPanel/SequenceEditorPanel' import {topStripHeight} from '@theatre/studio/panels/SequenceEditorPanel/RightOverlay/TopStrip' +import useRefAndState from '@theatre/studio/utils/useRefAndState' +import type {CommitOrDiscard} from '@theatre/studio/StudioStore/StudioStore' +import useDrag from '@theatre/studio/uiComponents/useDrag' +import getStudio from '@theatre/studio/getStudio' +import type Sheet from '@theatre/core/sheets/Sheet' const coverWidth = 1000 -const Cover = styled.div` - position: absolute; - top: 0; - left: 0; - background-color: rgb(23 23 23 / 43%); - width: ${coverWidth}px; - z-index: ${() => zIndexes.lengthIndicatorCover}; - transform-origin: left top; -` - const Strip = styled.div` position: absolute; top: 0; @@ -26,7 +22,7 @@ const Strip = styled.div` width: 4px; z-index: ${() => zIndexes.lengthIndicatorStrip}; pointer-events: auto; - cursor: pointer; + cursor: ew-resize; &:after { display: block; @@ -39,7 +35,8 @@ const Strip = styled.div` background-color: #000000a6; } - &:hover:after { + &:hover:after, + &.dragging:after { background-color: #000000; } ` @@ -49,23 +46,43 @@ const Info = styled.div` top: ${topStripHeight + 4}px; font-size: 10px; left: 4px; - color: gray; + color: #eee; white-space: nowrap; display: none; - ${Strip}:hover & { + ${Strip}:hover &, ${Strip}.dragging & { display: block; } ` -const LengthIndicator: React.FC<{ +const Cover = styled.div` + position: absolute; + top: 0; + left: 0; + background-color: rgb(23 23 23 / 43%); + width: ${coverWidth}px; + z-index: ${() => zIndexes.lengthIndicatorCover}; + transform-origin: left top; + + ${Strip}:hover ~ &, ${Strip}.dragging ~ & { + background-color: rgb(23 23 23 / 60%); + } +` + +type IProps = { layoutP: Pointer -}> = ({layoutP}) => { +} + +const LengthIndicator: React.FC = ({layoutP}) => { + const [stripRef, stripNode] = useRefAndState(null) + const [isDraggingD] = useDragStrip(stripNode, {layoutP}) + return usePrism(() => { const sheet = val(layoutP.sheet) const height = val(layoutP.rightDims.height) - const sequenceLength = sheet.getSequence().length + const sequence = sheet.getSequence() + const sequenceLength = sequence.length const startInUnitSpace = sequenceLength let startX = val(layoutP.clippedSpace.fromUnitSpace)(startInUnitSpace) @@ -86,12 +103,17 @@ const LengthIndicator: React.FC<{ <> - Sequence Length: {sequenceLength} + + sequence.length:{' '} + {sequence.positionFormatter.formatForPlayhead(sequenceLength)} + ) - }, [layoutP]) + }, [layoutP, stripRef, isDraggingD]) +} + +function useDragStrip(node: HTMLDivElement | null, props: IProps) { + const propsRef = useRef(props) + propsRef.current = props + const isDragging = useMemo(() => new Box(false), []) + + const gestureHandlers = useMemo[1]>(() => { + let toUnitSpace: SequenceEditorPanelLayout['scaledSpace']['toUnitSpace'] + let tempTransaction: CommitOrDiscard | undefined + let propsAtStartOfDrag: IProps + let sheet: Sheet + let initialLength: number + + return { + lockCursorTo: 'ew-resize', + onDragStart(event) { + propsAtStartOfDrag = propsRef.current + sheet = val(propsRef.current.layoutP.sheet) + initialLength = sheet.getSequence().length + + toUnitSpace = val(propsAtStartOfDrag.layoutP.scaledSpace.toUnitSpace) + isDragging.set(true) + }, + onDrag(dx, dy, event) { + const delta = toUnitSpace(dx) + if (tempTransaction) { + tempTransaction.discard() + tempTransaction = undefined + } + tempTransaction = getStudio()!.tempTransaction(({stateEditors}) => { + stateEditors.coreByProject.historic.sheetsById.sequence.setLength({ + ...sheet.address, + length: initialLength + delta, + }) + }) + }, + onDragEnd(dragHappened) { + isDragging.set(false) + if (dragHappened) { + if (tempTransaction) { + tempTransaction.commit() + } + } else { + if (tempTransaction) { + tempTransaction.discard() + } + } + tempTransaction = undefined + }, + } + }, []) + + useDrag(node, gestureHandlers) + + return [isDragging.derivation] } export default LengthIndicator