66f5ca1a4f
Timeline widget with:
- Header: play/pause + reset + duration drag + loop checkbox
- Ruler: 0.5s ticks, scrub via click+drag
- Tracks: horizontal rows with diamond-shaped draggable keyframes
- Playhead: vertical primary_light line + ruler triangle marker
State and types:
- Keyframe { time, value, ease }
- Track { name, vector<Keyframe> }
- TimelineState { tracks, current_time, duration, playing, loop }
Pure functions:
- track_value_at(track, t): interp between keys, ease applied via the
destination keyframe (Maya/AfterEffects convention)
- timeline_update(state, dt): advance current_time, wrap or saturate
Render with fn_tokens for visual coherence with the rest of the design
system. Keys are sorted by time on every changed frame to keep order
consistent during drag.
4.9 KiB
4.9 KiB
name, kind, lang, domain, version, purity, signature, description, tags, uses_functions, uses_types, returns, returns_optional, error_type, imports, tested, tests, test_file_path, file_path, framework, params, output
| name | kind | lang | domain | version | purity | signature | description | tags | uses_functions | uses_types | returns | returns_optional | error_type | imports | tested | tests | test_file_path | file_path | framework | params | output | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| timeline | component | cpp | core | 1.0.0 | pure | bool fn::timeline_widget(const char* id, fn::TimelineState&, ImVec2 size = {-1,200}) + float fn::track_value_at(const Track&, float t) + void fn::timeline_update(TimelineState&, float dt) | Widget tipo DAW: tracks horizontales con keyframes draggable, scrub, play/pause/loop, evaluacion track_value_at(time) interpolando entre keyframes con la Ease de cada keyframe destino. Estado puro (TimelineState) + render con tokens. |
|
|
false |
|
false | cpp/functions/core/timeline.cpp | imgui |
|
true en el frame en que el usuario interactua (drag de keyframe, scrub, play/pause/reset, cambio de duration o loop) |
timeline
Widget de timeline tipo DAW para animar valores escalares en el tiempo. Cada Track es un canal con keyframes (time, value, ease); track_value_at(time) interpola entre keyframes consecutivos aplicando la Ease de la keyframe destino.
Tipos
struct fn::Keyframe {
float time;
float value;
fn::tween::Ease ease = fn::tween::Ease::Linear;
};
struct fn::Track {
std::string name;
std::vector<Keyframe> keys; // ordenadas por time
};
struct fn::TimelineState {
std::vector<Track> tracks;
float current_time = 0.0f;
float duration = 5.0f;
bool playing = false;
bool loop = true;
};
API
// Pure: interp del valor del track en t
float fn::track_value_at(const Track&, float t);
// Avanza current_time si playing; loop o satura segun flag
void fn::timeline_update(TimelineState&, float dt);
// Render del widget. Devuelve true si hubo interaccion del usuario.
bool fn::timeline_widget(const char* id, TimelineState&, ImVec2 size = {-1, 200});
Ejemplo
static fn::TimelineState tl;
if (tl.tracks.empty()) {
tl.tracks.push_back({"hue", {{0, 0.0f}, {2.0f, 1.0f, fn::tween::Ease::OutCubic}, {4.0f, 0.0f}}});
tl.tracks.push_back({"amp", {{0, 0.2f}, {3.0f, 1.0f, fn::tween::Ease::InOutQuad}}});
tl.duration = 4.0f;
}
float dt = ImGui::GetIO().DeltaTime;
fn::timeline_update(tl, dt);
float hue = fn::track_value_at(tl.tracks[0], tl.current_time);
float amp = fn::track_value_at(tl.tracks[1], tl.current_time);
ImGui::Text("hue=%.3f amp=%.3f", hue, amp);
fn::timeline_widget("##my_tl", tl);
Comportamiento de track_value_at
- 0 keys → devuelve 0.0
- 1 key → devuelve siempre
keys[0].value - t antes de la primera key →
keys.front().value(clamp izq) - t despues de la ultima key →
keys.back().value(clamp der) - Entre dos keys
ayb:u = (t - a.time) / (b.time - a.time)k = tween::apply(b.ease, u)(la ease es la "curva entrante" hasta b)- resultado =
a.value + (b.value - a.value) * k
Smoke tests (linear)
Track con 2 keys {(0,0), (1,1)} con ease=Linear:
track_value_at(t, 0.0) == 0.0track_value_at(t, 0.5) == 0.5track_value_at(t, 1.0) == 1.0
Render
- Header: Play/Pause + Reset + DragFloat de duration + indicador
t=...s+ checkbox loop. - Ruler: ticks cada 0.5s, etiqueta cada segundo, scrub con click+drag.
- Tracks: filas horizontales con nombre a la izquierda (k_label_w=80px) y keyframes como diamantes draggable.
- Playhead: linea vertical
primary_lightcon triangulo en la cabeza del ruler. - Colores via
fn_tokens(surface bg, border, primary keys, text labels).
Interaccion
- Drag horizontal de un diamante: cambia
key.time. Al soltar (o cualquier frame conchanged), las keys se reordenan por tiempo. - Scrub: click + drag en el ruler mueve
current_time. - Play/Pause/Reset: cambian
playingycurrent_time. - Duration drag: clampada >= 0.1s.
Decisiones
- Ease en la keyframe destino: convencion comun en animation toolings (Maya, AfterEffects). La curva define como llegamos a esa key.
- Sin edicion vertical (value drag): para mantener el widget simple. Si hace falta editar
valuecon la UI, el caller puede mostrar un campo numerico al lado o dentro de un popup. - Sort en cada cambio: O(n log n) por track no es problema para timelines tipicas (<100 keys); evita estado intermedio "drag in progress".
Limitaciones / TODO
- No hay editor de
easepor keyframe en el MVP (queda enLinearsalvo que el caller lo configure). - No hay seleccion multiple ni copy/paste de keyframes.
- No hay zoom horizontal: la timeline siempre encaja
durationcompleta al ancho del widget.