{track.keyframes.length} 个关键帧
) : (
- track.keyframes.map((kf) => {
+ track.keyframes.map((kf, index) => {
const leftPercent = duration === 0 ? 0 : (kf.time / duration) * 100;
const isSelected =
selectedKeyframe?.kind === "object" &&
@@ -2318,6 +2370,8 @@ export function TimelinePanel() {
role="button"
tabIndex={0}
aria-label={`关键帧 ${formatTime(kf.time)}`}
+ title={`关键帧 · ${formatTime(kf.time)}(左右拖动调整时间)`}
+ {...getTimeDragProps(objectOwner, "point", index, objectTimes)}
onClick={() => handleSelectObjectKeyframe(track.objectId, kf.id, kf.time, kf.value)}
>
@@ -2381,7 +2435,7 @@ export function TimelinePanel() {
style={{ background }}
ariaLabel={`运镜过渡 ${fromName} → ${toName}`}
title={`${fromName} → ${toName}(${smooth ? "平滑运镜" : "硬切"})· 点击编辑前后机位`}
- onClick={() => setSelectedTransitionClipId(isSelected ? null : clip.id)}
+ onClick={() => setSelectedTransitionClipId(clip.id)}
onChange={(next) => updateTransitionClip(clip.id, next)}
snapTargets={[
playhead,
@@ -2517,9 +2571,7 @@ export function TimelinePanel() {
title={`${trackName} 片段 ${formatTime(clip.start)} → ${formatTime(
clip.end
)}(点击编辑起止)`}
- onClick={() =>
- setSelectedCameraClipKey(isSelected ? null : `${track.cameraId}:${clip.id}`)
- }
+ onClick={() => setSelectedCameraClipKey(`${track.cameraId}:${clip.id}`)}
onChange={(next) => updateCameraClip(track.cameraId, clip.id, next)}
snapTargets={[
playhead,
@@ -2631,6 +2683,11 @@ export function TimelinePanel() {
const trajectoryStart = getTrajectoryStartTime(character, duration);
const trajectoryTimes = getTrajectoryPointTimes(trajectoryPoints, duration, trajectoryStart);
const trajectoryEndTime = getTrajectoryClipEndTime(character, duration, trajectoryStart);
+ // 角色姿势关键帧:与物体姿势同构,落在 timeline.tracks(objectId === 角色 id)。
+ // 头部车道顺手把它们画出来,左右拖动即可改时间(走的是 object 拖拽分支)。
+ const poseTrack = timeline.tracks.find((track) => track.objectId === character.id);
+ const poseKeyframes = poseTrack?.keyframes ?? [];
+ const poseTimes = poseKeyframes.map((keyframe) => keyframe.time);
// 轨迹点连线:在「轨迹」轨道上把各轨迹点用折线连起来(视图坐标 0~100 对应整条时间轴宽度,
// 50 = 轨道垂直中点),直观呈现走位路径(最后一个点只是途经点,后面是停留段)
const trajectoryConnectorPoints = [trajectoryStart, ...trajectoryTimes]
@@ -2670,6 +2727,7 @@ export function TimelinePanel() {
{clips.length > 0 ? `${clips.length} 段动作 · ` : ""}
{trajectoryPoints.length} 个轨迹点
+ {poseKeyframes.length > 0 ? ` · ${poseKeyframes.length} 个姿势` : ""}
) : trajectoryTimes.length > 0 && duration > 0 ? (
) : null}
+ {/* 角色姿势关键帧:画在头部车道上,左右拖动改时间(走 object 拖拽分支),选中后按 Delete 删除 */}
+ {!collapsed && poseKeyframes.length > 0 && duration > 0
+ ? poseKeyframes.map((kf, index) => {
+ const leftPercent = duration === 0 ? 0 : (kf.time / duration) * 100;
+ const isSelected =
+ selectedKeyframe?.kind === "object" &&
+ selectedKeyframe.targetId === character.id &&
+ selectedKeyframe.keyframeId === kf.id;
+ return (
+ handleSelectObjectKeyframe(character.id, kf.id, kf.time, kf.value)}
+ >
+
+
+ );
+ })
+ : null}