---
name: timeline
kind: component
lang: cpp
domain: core
version: "1.0.0"
purity: pure
signature: "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)"
description: "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."
tags: [imgui, timeline, animation, keyframes, daw, tween]
uses_functions:
  - tokens_cpp_core
  - tween_curves_cpp_core
uses_types: []
returns: []
returns_optional: false
error_type: ""
imports: [imgui]
tested: false
tests: []
test_file_path: ""
file_path: "cpp/functions/core/timeline.cpp"
framework: imgui
params:
  - name: id
    desc: "ID ImGui unico"
  - name: state
    desc: "TimelineState con tracks (vector<Track>), current_time, duration, playing, loop. Modificada por el widget."
  - name: size
    desc: "Tamaño total. size.x = -1 toma el ancho disponible. size.y >= 100 recomendado."
output: "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

```cpp
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

```cpp
// 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

```cpp
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 `a` y `b`:
  - `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.0`
- `track_value_at(t, 0.5) == 0.5`
- `track_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_light` con 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 con `changed`), las keys se reordenan por tiempo.
- **Scrub**: click + drag en el ruler mueve `current_time`.
- **Play/Pause/Reset**: cambian `playing` y `current_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 `value` con 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 `ease` por keyframe en el MVP (queda en `Linear` salvo que el caller lo configure).
- No hay seleccion multiple ni copy/paste de keyframes.
- No hay zoom horizontal: la timeline siempre encaja `duration` completa al ancho del widget.
