feat(studio): after-effects-style keyframe timeline with mid-segment easing#2387
Open
miguel-heygen wants to merge 11 commits into
Open
feat(studio): after-effects-style keyframe timeline with mid-segment easing#2387miguel-heygen wants to merge 11 commits into
miguel-heygen wants to merge 11 commits into
Conversation
Register a runtime ease shim (packages/core/src/runtime/customEase + wiggleEase) that resolves custom(bezier), hold (step-end), spring(bounce), and wiggle(n,type,amp) ease forms via hand-rolled deterministic samplers bound to the composition's own GSAP, running in both preview and capture. Add the parser support (array-form keyframe ease preservation, hold in SUPPORTED_EASES) and the Figma HOLD->hold mapping.
Right-panel ease editor with a Curve|Spring toggle, a 28-entry Graphs preset grid (Linear..Flow 7, Bounce 1-4) whose tiles render each ease via a shared mini-curve renderer, and the true Hold step glyph. Flow/Bounce map to the wiggle family; springs to the deterministic spring model; all previews and the segment-button glyph share one evaluator with the runtime.
…se button) Expandable per-property-group keyframe lanes rendered as flat dope-sheet rows under a single clip bar, with an AE-style left column (name, keyframe toggle, prev/next nav, value, eye), variable-height rows, a reserved label column, group-aware keyframe identity reading source per-group tweens, and a mid-segment ease button that drives the right-panel editor. Fixes array/vertical lane persistence, and consolidates the pointer-x->content-x mapping.
…emetry Draw the on-canvas motion path on pure layer selection via a dedicated shouldShowMotionPath flag (leaving the selection-outline gate unchanged), and add telemetry for lane expand/collapse and segment-ease edits.
…ti-element tracks) Bundled QA-round fixes on top of the pushed feature branch: - Figma 1:1 ease panel (preset dropdown popover, Curve/Spring, graph, editable bezier) - hover-gated mid-segment ease button, shown regardless of selection; click no longer seeks - keyframes no longer cut off (0% diamond centered on the playhead via a reserved gutter) - unified keyframe-diamond size across clips and lanes - keyframed clips expanded by default - disclosure moved to the left-column layer tree; clip-bar chevron removed - property-row controls reordered to Figma order (prev / toggle / next) - multi-element tracks scope lanes + left column to the active clip, no cramming - extracted useAutoExpandKeyframedClips + LayerDisclosureRow to keep files under cap
U1: pure helper turning a flat to/from/fromTo tween (no keyframes array) into 2 percentage keyframes (0% start = base/from, 100% end = target, ease on the destination), so the timeline can render + edit single tweens as 2-keyframe lanes.
U2: flat to/from/fromTo tweens (no keyframes array) build an expandable property lane via synthesizeFlatTweenKeyframes (start=base, end=target) and count toward lane count / row height. Shared animationContributesLane predicate (used by sourceGroups + extracted computeLaneCounts) dedups the check and keeps the row-height memo under the complexity cap. Exports the helper via the parsers barrel.
U3: the cache builder resolves a keyframe source of anim.keyframes ?? synthesizeFlatTweenKeyframes(anim), skipping only when empty, so a flat tween's collapsed-clip diamonds come from the same synthesized start/end the lanes use (single source of truth, no divergence).
U4: no code change needed — a keyframeless animation already renders the flat
EaseCurveSection branch, which commits via onUpdateMeta({ease}), and selecting a
flat tween's segment sets focusedEaseSegment without seeking. Adds coverage for
both so the behavior is pinned.
U5: adding a keyframe to a flat-tween lane converts it to a real keyframes tween via the existing addKeyframeToScript writer; after conversion all keyframe ops apply. Deleting a still-flat tween's boundary routes to the guarded animation delete; a boundary drag while still flat is a safe no-op (add a keyframe first to edit freely). Write-path tests re-parse the mutated script to prove no corruption.
Flat tweens (a lone to/from/fromTo with no keyframes array) now populate the gsapAnimations store and auto-expand into 2-keyframe lanes, on par with keyframed tweens. Consolidates synthesis onto the existing studio gsapTweenSynth and drops the duplicate parsers helper (single source).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
An After-Effects / Figma-Motion-style keyframe timeline for Studio, with segment easing moved onto the segment between two keyframes.
▾ ◇ LayerName, with each property nested under it via a tree connector and its own keyframe toggle, prev/next nav, live value, and visibility eye. The expand/collapse disclosure lives here (not on the clip bar).Custom bezier ▾dropdown opens the "Graphs" preset grid as a popover; Curve/Spring tabs; a draggable cubic-bezier graph; an editable bezier value field.custom()/hold/spring()/wiggle()render identically in preview and capture.Why
Easing is a property of the segment between two keyframes (as in After Effects / Figma Motion), not of individual keyframes — the previous flow edited it per-keyframe in the design panel. The timeline also lacked per-property keyframe visibility, a preset ease library, and a runtime that could render custom / hold / spring / wiggle eases (selecting one produced no motion). Because HyperFrames commonly places several elements on one track (shared z-index), the keyframe view has to handle that without cramming.
How
position | scale | size | rotation | visual | other) are the finest independent-time unit, each its own tween, so a keyframe is addressed byelement:group:percentage. Lanes read sourcegsapAnimationsdirectly, not a lossy merged cache.kf.ease); the mid-segment button reads and commits it.window.gsap.parseEaseto resolvecustom(<path>),hold,spring(<bounce>),wiggle(<n>,<type>,<amp>)— compositions pin GSAP 3.14.2 (noCustomEase), so the shim runs in both the preview iframe and the capture path for parity.resolveTrackKeyframeClip): one keyframed element per track drives the lanes + left column + row height, so shared-track elements don't overlap. No changes to the track/drag/drop model.Test plan
Not covered / deferred
towith an ease and no keyframes) don't get a timeline mid-segment button — they have no between-keyframes segment. Their ease is edited in the Inspector's Animation panel (the same Figma ease editor). Representing single tweens as 2-keyframe lanes is a deferred follow-up.EASE_CURVESand the parser constants); this pre-existing divergence is untouched here.