feat: better time events

This commit is contained in:
aarthificial
2022-05-05 23:27:18 +02:00
parent 9aff955ef4
commit 1acd71bb4d
6 changed files with 99 additions and 44 deletions

View File

@@ -13,30 +13,33 @@ interface LabelProps {
export function Label({event, scene}: LabelProps) {
const {fullLength, duration} = useContext(TimelineContext);
const player = usePlayer();
const [frame, setFrame] = useState(event.offset);
const [handleDrag, isDragging] = useDrag(
const durationSeconds = player.project.framesToSeconds(duration);
const startSeconds = player.project.framesToSeconds(scene.firstFrame);
const [eventTime, setEventTime] = useState(event.offset);
const [handleDrag] = useDrag(
useCallback(
dx => {
setFrame(frame + (dx / fullLength) * duration);
setEventTime(eventTime + (dx / fullLength) * durationSeconds);
},
[frame, fullLength, duration],
[eventTime, fullLength, durationSeconds],
),
useCallback(
e => {
const newFrame = Math.max(0, Math.floor(eventTime));
setEventTime(newFrame);
if (event.offset !== newFrame) {
scene.setFrameEvent(event.name, newFrame, !e.shiftKey);
player.reload();
}
},
[event, eventTime],
),
);
useEffect(() => {
setFrame(event.offset);
setEventTime(event.offset);
}, [event]);
useEffect(() => {
if (isDragging) return;
const newFrame = Math.max(0, Math.floor(frame));
setFrame(newFrame);
if (event.offset !== newFrame) {
scene.setFrameEvent(event.name, newFrame);
player.reload();
}
}, [isDragging, frame, event]);
return (
<>
<div
@@ -45,19 +48,27 @@ export function Label({event, scene}: LabelProps) {
data-name={event.name}
style={{
left: `${
((scene.firstFrame + event.initialFrame + Math.max(0, frame)) /
duration) *
((startSeconds + event.initialTime + Math.max(0, eventTime)) /
durationSeconds) *
100
}%`,
}}
/>
<div
className={styles.labelClipTarget}
style={{
left: `${
((startSeconds + event.targetTime) / durationSeconds) * 100
}%`,
}}
/>
<div
className={styles.labelClipStart}
style={{
left: `${
((scene.firstFrame + event.initialFrame) / duration) * 100
((startSeconds + event.initialTime) / durationSeconds) * 100
}%`,
width: `${(Math.max(0, frame) / duration) * 100}%`,
width: `${(Math.max(0, eventTime) / durationSeconds) * 100}%`,
}}
/>
</>

View File

@@ -13,16 +13,32 @@ export function RangeTrack() {
const [start, setStart] = useState(state.startFrame);
const [end, setEnd] = useState(state.endFrame);
const [handleDragStart, isDraggingStart] = useDrag(
const onDrop = useCallback(() => {
const correctedStart = Math.max(0, Math.floor(start));
const correctedEnd =
end >= state.duration
? Infinity
: Math.min(state.duration, Math.floor(end));
setStart(correctedStart);
setEnd(correctedEnd);
player.updateState({
startFrame: correctedStart,
endFrame: correctedEnd,
});
}, [start, end]);
const [handleDragStart] = useDrag(
useCallback(
dx => {
setStart(start + (dx / fullLength) * state.duration);
},
[start, setStart, fullLength, state.duration],
),
onDrop,
);
const [handleDragEnd, isDraggingEnd] = useDrag(
const [handleDragEnd] = useDrag(
useCallback(
dx => {
setEnd(
@@ -31,9 +47,10 @@ export function RangeTrack() {
},
[end, setEnd, fullLength, state.duration],
),
onDrop,
);
const [handleDrag, isDragging] = useDrag(
const [handleDrag] = useDrag(
useCallback(
dx => {
setStart(start + (dx / fullLength) * state.duration);
@@ -43,6 +60,7 @@ export function RangeTrack() {
},
[start, end, fullLength, state.duration, setStart, setEnd],
),
onDrop,
);
useEffect(() => {
@@ -50,23 +68,6 @@ export function RangeTrack() {
setEnd(state.endFrame);
}, [state.startFrame, state.endFrame]);
useEffect(() => {
if (!isDragging && !isDraggingStart && !isDraggingEnd) {
const correctedStart = Math.max(0, Math.floor(start));
const correctedEnd =
end >= state.duration
? Infinity
: Math.min(state.duration, Math.floor(end));
setStart(correctedStart);
setEnd(correctedEnd);
player.updateState({
startFrame: correctedStart,
endFrame: correctedEnd,
});
}
}, [isDragging, isDraggingStart, isDraggingEnd, start, end]);
return (
<div
style={{

View File

@@ -123,6 +123,15 @@
padding: 0 8px;
color: rgba(0, 0, 0, 0.87);
background-color: var(--theme);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.54);
}
&:hover,
&:active {
&::before {
box-shadow: 0 0 0 2px white inset;
}
z-index: 1;
}
}
@@ -139,6 +148,30 @@
pointer-events: none;
}
.labelClipTarget {
.labelClip:hover + & {
display: block;
}
.labelClip:active + & {
display: none;
}
box-sizing: content-box;
position: absolute;
height: 16px;
margin-top: 4px;
margin-left: -16px;
padding-right: 16px;
cursor: pointer;
border-radius: 12px 0 12px 12px;
background-color: white;
pointer-events: none;
display: none;
z-index: 1;
}
.playhead {
position: absolute;
width: 2px;

View File

@@ -125,13 +125,14 @@ export function Timeline() {
const newScale = scale * ratio < 1 ? 1 : scale * ratio;
const pointer = offset + event.x - rect.x;
const newOffset = offset - pointer + pointer * ratio;
const newTrackSize = rect.width * newScale;
const maxOffset = newTrackSize - rect.width;
const newOffset = clamp(0, maxOffset, offset - pointer + pointer * ratio);
containerRef.current.scrollLeft = newOffset;
setScale(newScale);
setOffset(clamp(0, maxOffset, newOffset));
setOffset(newOffset);
playheadRef.current.style.left = `${event.x - rect.x + newOffset}px`;
}}
onMouseUp={event => {
if (event.button === 0) {

View File

@@ -28,6 +28,7 @@ export function View() {
}),
[setState, state],
),
undefined,
1,
);

View File

@@ -5,8 +5,13 @@ interface MoveCallback {
(dx: number, dy: number, x: number, y: number): any;
}
interface DropCallback {
(event: MouseEvent): any;
}
export function useDrag(
onMove: MoveCallback,
onDrop?: DropCallback,
button = 0,
): [(event: MouseEvent) => any, boolean] {
const [isDragging, setDragging] = useState(false);
@@ -15,10 +20,13 @@ export function useDrag(
'mouseup',
useCallback(
event => {
event.stopPropagation();
setDragging(false);
if (isDragging) {
event.stopPropagation();
setDragging(false);
onDrop?.(event);
}
},
[setDragging],
[isDragging, onDrop],
),
isDragging,
true,