feat(core): add timeline — DAW-style keyframe widget
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.
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
#pragma once
|
||||
|
||||
// timeline — widget tipo DAW: tracks horizontales con keyframes interpolados,
|
||||
// scrub y play/pause. Sirve para animar valores escalares (uniforms shader,
|
||||
// parametros UI, etc) a lo largo del tiempo.
|
||||
//
|
||||
// Estado puro (TimelineState) + funciones puras de interpolacion
|
||||
// (track_value_at) + render impuro (timeline_widget).
|
||||
//
|
||||
// Uso:
|
||||
// static fn::TimelineState tl;
|
||||
// tl.tracks.push_back({"hue", {{0,0}, {2,1}, {4,0}}});
|
||||
// tl.duration = 4.0f;
|
||||
//
|
||||
// fn::timeline_update(tl, dt);
|
||||
// float h = fn::track_value_at(tl.tracks[0], tl.current_time);
|
||||
// fn::timeline_widget("##tl", tl);
|
||||
|
||||
#include "core/tween_curves.h"
|
||||
|
||||
#include <imgui.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace fn {
|
||||
|
||||
struct Keyframe {
|
||||
float time;
|
||||
float value;
|
||||
tween::Ease ease = tween::Ease::Linear;
|
||||
};
|
||||
|
||||
struct Track {
|
||||
std::string name;
|
||||
std::vector<Keyframe> keys;
|
||||
};
|
||||
|
||||
struct TimelineState {
|
||||
std::vector<Track> tracks;
|
||||
float current_time = 0.0f;
|
||||
float duration = 5.0f;
|
||||
bool playing = false;
|
||||
bool loop = true;
|
||||
};
|
||||
|
||||
// --- Pure -------------------------------------------------------------------
|
||||
|
||||
// Interpola el valor de `track` en el tiempo `t`. Asume keys ordenadas por
|
||||
// time. Si `t` cae antes del primer keyframe devuelve el value del primero;
|
||||
// si cae despues del ultimo, devuelve el value del ultimo. Entre keyframes
|
||||
// usa el ease de la SEGUNDA key (la "curva entrante" hasta esa key).
|
||||
float track_value_at(const Track& track, float t);
|
||||
|
||||
// --- Update -----------------------------------------------------------------
|
||||
|
||||
// Avanza current_time si playing. Si loop=true hace wrap; si no, satura en
|
||||
// duration y pone playing=false al llegar.
|
||||
void timeline_update(TimelineState& s, float dt);
|
||||
|
||||
// --- Render -----------------------------------------------------------------
|
||||
|
||||
// Widget completo: cabecera con play/pause + tiempo, ruler con scrub, y un
|
||||
// panel por track con keyframes draggable. Devuelve true si el usuario hizo
|
||||
// algun cambio (drag de keyframe, scrub, play/pause).
|
||||
bool timeline_widget(const char* id, TimelineState& s, ImVec2 size = ImVec2(-1, 200));
|
||||
|
||||
} // namespace fn
|
||||
Reference in New Issue
Block a user