mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-11 06:30:03 +00:00
fix(studio): retime flat tween keyframe diamonds
This commit is contained in:
@@ -56,6 +56,29 @@ const round3 = (n: number) => Math.round(n * 1000) / 1000;
|
||||
const round1 = (n: number) => Math.round(n * 10) / 10; // 0.1% precision
|
||||
const clamp = (n: number, lo: number, hi: number) => Math.max(lo, Math.min(hi, n));
|
||||
|
||||
/** Resolve timing for a flat tween's synthesized start/end diamond. */
|
||||
function resolveFlatTweenBoundaryRetime(opts: {
|
||||
keyframeCount: number;
|
||||
draggedTweenPct: number;
|
||||
tweenStart: number;
|
||||
tweenEnd: number;
|
||||
dropAbsTime: number;
|
||||
}): KeyframeRetimeResult | null {
|
||||
const { keyframeCount, draggedTweenPct, tweenStart, tweenEnd, dropAbsTime } = opts;
|
||||
if (keyframeCount > 0 || (draggedTweenPct !== 0 && draggedTweenPct !== 100)) return null;
|
||||
const draggedTime = draggedTweenPct === 0 ? tweenStart : tweenEnd;
|
||||
if (Math.abs(dropAbsTime - draggedTime) <= EPSILON_TIME) return { kind: "noop" };
|
||||
const newStart = draggedTweenPct === 0 ? dropAbsTime : tweenStart;
|
||||
const newEnd = draggedTweenPct === 100 ? dropAbsTime : tweenEnd;
|
||||
if (newEnd <= newStart + EPSILON_TIME) return { kind: "noop" };
|
||||
return {
|
||||
kind: "resize",
|
||||
position: round3(newStart),
|
||||
duration: round3(newEnd - newStart),
|
||||
pctRemap: [],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Decide move vs resize for a dragged keyframe and, for resize, return the new
|
||||
* tween window + remapped keyframes.
|
||||
@@ -76,15 +99,30 @@ export function resolveKeyframeRetime(opts: {
|
||||
if (tweenDuration <= 0) return { kind: "noop" };
|
||||
const tweenEnd = tweenStart + tweenDuration;
|
||||
|
||||
// Within the tween window → plain move (re-key the tween-%). This branch never
|
||||
// touches the keyframes array, so it still works for synthesized flat tweens.
|
||||
// A flat tween's synthesized diamonds are its real start/end boundaries —
|
||||
// there is no authored keyframe node to re-key. Moving either boundary must
|
||||
// therefore resize the tween window while keeping the opposite endpoint at
|
||||
// its absolute time. Returning `resize` with an empty remap lets the caller
|
||||
// update the flat tween's position/duration instead of sending a keyframe
|
||||
// mutation that would silently no-op.
|
||||
const flatBoundary = resolveFlatTweenBoundaryRetime({
|
||||
keyframeCount: keyframes.length,
|
||||
draggedTweenPct,
|
||||
tweenStart,
|
||||
tweenEnd,
|
||||
dropAbsTime,
|
||||
});
|
||||
if (flatBoundary) return flatBoundary;
|
||||
|
||||
// Within the tween window → plain move (re-key the tween-%).
|
||||
if (dropAbsTime >= tweenStart - EPSILON_TIME && dropAbsTime <= tweenEnd + EPSILON_TIME) {
|
||||
const toTweenPct = clamp(((dropAbsTime - tweenStart) / tweenDuration) * 100, 0, 100);
|
||||
if (Math.abs(toTweenPct - draggedTweenPct) < NOOP_EPSILON_PCT) return { kind: "noop" };
|
||||
return { kind: "move", toTweenPct };
|
||||
}
|
||||
|
||||
// Boundary resize needs the real keyframes to remap; a flat tween has none here.
|
||||
// Boundary resize needs the real keyframes to remap. Flat start/end boundaries
|
||||
// were handled above.
|
||||
if (keyframes.length === 0) return { kind: "noop" };
|
||||
|
||||
const newStart = Math.min(dropAbsTime, tweenStart);
|
||||
|
||||
Reference in New Issue
Block a user