Skip to content

feat(inspector): keyframe editing UI + fine-grained backend commands (#95)#119

Merged
appergb merged 4 commits into
appergb:mainfrom
cuic19053-hue:feat-95-keyframe-editing
Jun 23, 2026
Merged

feat(inspector): keyframe editing UI + fine-grained backend commands (#95)#119
appergb merged 4 commits into
appergb:mainfrom
cuic19053-hue:feat-95-keyframe-editing

Conversation

@cuic19053-hue

Copy link
Copy Markdown
Contributor

What

Closes #95 — adds keyframe editing UI + fine-grained backend commands.

Before: the Inspector had a dead diamond toggle button that did nothing. Keyframes were read-only on the timeline (yellow diamonds). Users could not add, delete, move, or change interpolation.

After: clicking the diamond button opens a full keyframe editing panel with per-property rows, draggable diamonds, right-click context menu, and stamp/clear buttons.

How

Backend (Rust) — 4 new EditCommand variants:

Command Behavior
StampKeyframe Samples the clip's current value at frame and upserts a keyframe. Uses raw_opacity_at (not opacity_at) to avoid double fade, top_left_at for position, size_at for scale.
RemoveKeyframe Removes the keyframe at frame. Clears track to None when empty.
MoveKeyframe Moves a keyframe. Validates: source exists, target within clip range (half-open), target not occupied.
SetKeyframeInterpolation Changes a keyframe's interpolation_out (Linear/Smooth/Hold).

All commands use absolute timeline frames (public API), converted to clip-relative internally. Each returns affected_clip_ids to trigger frontend refresh.

Tauri boundary: 4 EditRequest variants + into_command mappings, reusing KeyframePropertyDto and domain Interpolation.

Frontend (React + TypeScript):

  • KeyframesPanel.tsx — ruler + per-property rows (video: 5 rows, audio: 1 row) + panel-wide playhead overlay
  • KeyframesLaneRow.tsx — HTML div diamonds (rotate 45deg, not SVG — avoids percentage coordinate issues), drag with window listeners + snap to playhead (±5 frames), right-click context menu (delete / set interpolation), stamp/clear buttons
  • Inspector.tsx — renders <KeyframesPanel> when keyframesOpen === true
  • clipRenderer.ts — fix: drawKeyframeMarkers now includes volumeTrack (was missing)

Key design decisions:

  • Diamonds use HTML div + transform: rotate(45deg) instead of SVG polygons (SVG points can't take percentage strings; viewBox + preserveAspectRatio="none" distorts diamonds)
  • Drag uses useRef cleanup to prevent window listener leaks on unmount
  • Drag target clamped to [startFrame, startFrame + duration - 1] (half-open clip range)

Testing

  • 12 Rust unit tests (stamp/remove/move/setInterpolation — happy paths + error paths)
  • pnpm tsc --noEmit — pass
  • pnpm build — pass
  • Rust Analyzer IDE diagnostics — 0 errors, 0 warnings
  • cargo test / cargo clippy — pending CI (no local Rust toolchain)

Limitations

  • SetClipProperties still clears keyframe tracks when setting opacity/volume scalars. The UI to disable scalar sliders for animated properties will be handled in [inspector] 三段式 live 预览缺失 + 字段不全(位置/裁剪/翻转/fade) #97 (Inspector three-section live preview).
  • No keyframe curve editor (upstream doesn't have one either).
  • No keyboard shortcuts for keyframe operations (Delete to remove, arrow keys to nudge) — future enhancement.

…ppergb#95)

Backend (Rust):
- Add 4 EditCommand variants: StampKeyframe, RemoveKeyframe,
  MoveKeyframe, SetKeyframeInterpolation (absolute timeline frames,
  converted to clip-relative internally)
- stamp_keyframe samples the clip current value via raw_opacity_at /
  top_left_at / size_at / crop_at / rotation_at / volume_track.sample
- move_keyframe validates target frame is within clip range (half-open)
- 12 unit tests covering happy paths + error paths

Tauri boundary:
- Add 4 EditRequest variants + into_command mappings

Frontend (React + TypeScript):
- Add 4 EditRequest types + 4 editActions functions
- New KeyframesPanel.tsx: ruler + per-property rows + playhead overlay
- New KeyframesLaneRow.tsx: draggable diamonds + right-click menu
  (delete / set interpolation) + stamp/clear buttons + snap to playhead
- Wire Inspector.tsx: render panel when keyframesOpen === true
- Fix clipRenderer.ts: drawKeyframeMarkers now includes volumeTrack
- 14 i18n keys (zh-CN + en)

Closes appergb#95
@cuic19053-hue cuic19053-hue requested a review from appergb as a code owner June 23, 2026 18:12

@appergb appergb left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

审核通过 ✅(自动审核流程 · 主控亲审 + 路线裁决)

实现 issue #95 关键帧编辑:后端 4 个细粒度 EditCommand(StampKeyframe/RemoveKeyframe/MoveKeyframe/SetKeyframeInterpolation)+ 前端 Inspector KeyframesPanel/KeyframesLaneRow 菱形可拖编辑。

  • 命令 apply 语义正确(亲读核对):clip 相对帧 rel=frame-start_frame、半开区间 clip.contains 校验、move 的『目标越界/被占用』守卫、Volume stamp 取 0.0 dB(unity 正确)、SetInterp 仅设 interpolation_out 对齐上游 per-keyframe out-interp。12 后端单测覆盖全部命令与边界。
  • 真接线非死代码,Inspector 在 keyframesOpen 渲染 KeyframesPanel(补齐消费缺口),每属性一行 + 拖动/删除/盖章/改插值。
  • 符号逐一在 main 存在且匹配,序列化兼容;不碰 #91 媒体重写区;CI 双绿。
  • 路线裁决:关键帧采用本 PR 的『后端命令』路线(更接近上游 1:1、走真命令+undo),优于 #120 的纯前端 read-modify-write。先合本 PR 确立该路线。

follow-up(非阻塞):① clipRenderer 把 volumeTrack 加入时间线菱形集,上游仅渲染 5 个视觉轨——与 #120 的音量包络 dot 并存时音频 clip 音量关键帧可能重复渲染,rebase #120 时一并按上游收口;② KeyframesLaneRow 拖拽吸附基于 mousedown 时的 activeFrame 快照(交互细节)。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[timeline] 关键帧编辑入口缺失(菱形/面板只读不可编辑)

2 participants