feat(primitives_gallery): demos for tween_curves + bezier_editor + timeline
Adds three new demos to the Core section of primitives_gallery: - demo_tween: dropdown of all 16 Ease modes + animated plot showing the curve and a moving marker that traverses t=0..1 in a loop. - demo_bezier_editor: live editor with reset + ease-out / ease-in-out presets, current control points displayed, slider over t showing bezier_eval(curve, t). - demo_timeline: 2 tracks (hue, amp) with mixed eases, live progress bars showing track_value_at(current_time) updating each frame. Wires the three demos into k_demos[] in main.cpp and adds the new sources (plus the three function .cpp files) to CMakeLists.txt.
This commit is contained in:
@@ -0,0 +1,249 @@
|
||||
// Demos para los primitivos de animacion (issue 0031):
|
||||
// - tween_curves
|
||||
// - bezier_editor
|
||||
// - timeline
|
||||
|
||||
#include "demos.h"
|
||||
#include "demo.h"
|
||||
|
||||
#include "core/tween_curves.h"
|
||||
#include "core/bezier_editor.h"
|
||||
#include "core/timeline.h"
|
||||
#include "core/tokens.h"
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <cmath>
|
||||
|
||||
namespace gallery {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// demo_tween — dropdown + plot animado
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void demo_tween() {
|
||||
using namespace fn_tokens;
|
||||
using fn::tween::Ease;
|
||||
|
||||
demo_header("tween_curves", "v1.0.0",
|
||||
"Funciones de easing (Penner): linear, quad, cubic, expo, elastic, "
|
||||
"bounce con variantes in/out/inOut. Header-mostly: el compilador "
|
||||
"inlinea cada curva en el sitio de llamada.");
|
||||
|
||||
section("Selector + plot");
|
||||
|
||||
static int ease_idx = (int)Ease::OutCubic;
|
||||
static float anim_t = 0.0f;
|
||||
anim_t += ImGui::GetIO().DeltaTime * 0.5f;
|
||||
if (anim_t > 1.5f) anim_t = -0.25f; // hold un poco antes de reiniciar
|
||||
|
||||
// Build labels
|
||||
const char* labels[fn::tween::ease_count];
|
||||
for (int i = 0; i < fn::tween::ease_count; i++) {
|
||||
labels[i] = fn::tween::name((Ease)i);
|
||||
}
|
||||
ImGui::SetNextItemWidth(220.0f);
|
||||
ImGui::Combo("##tween_ease", &ease_idx, labels, fn::tween::ease_count);
|
||||
|
||||
Ease ease = (Ease)ease_idx;
|
||||
float t_clamped = anim_t;
|
||||
if (t_clamped < 0.0f) t_clamped = 0.0f;
|
||||
if (t_clamped > 1.0f) t_clamped = 1.0f;
|
||||
float v = fn::tween::apply(ease, t_clamped);
|
||||
|
||||
ImGui::SameLine();
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, colors::text_muted);
|
||||
ImGui::Text(" t=%.2f f(t)=%.3f", t_clamped, v);
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
// Canvas plot
|
||||
ImVec2 canvas_min = ImGui::GetCursorScreenPos();
|
||||
ImVec2 canvas_size(360.0f, 220.0f);
|
||||
ImVec2 canvas_max = ImVec2(canvas_min.x + canvas_size.x, canvas_min.y + canvas_size.y);
|
||||
|
||||
ImDrawList* dl = ImGui::GetWindowDrawList();
|
||||
dl->AddRectFilled(canvas_min, canvas_max, ImGui::GetColorU32(colors::bg), radius::sm);
|
||||
dl->AddRect(canvas_min, canvas_max, ImGui::GetColorU32(colors::border), radius::sm);
|
||||
|
||||
auto to_px = [&](float tx, float ty) {
|
||||
// ty puede salir de [0,1] (elastic/bounce); damos algo de margen vertical.
|
||||
return ImVec2(canvas_min.x + tx * canvas_size.x,
|
||||
canvas_min.y + (1.0f - ty) * canvas_size.y);
|
||||
};
|
||||
|
||||
// Grid 4x4
|
||||
ImU32 grid = ImGui::GetColorU32(colors::border);
|
||||
for (int i = 1; i < 4; i++) {
|
||||
float fx = canvas_min.x + canvas_size.x * (float)i / 4.0f;
|
||||
float fy = canvas_min.y + canvas_size.y * (float)i / 4.0f;
|
||||
dl->AddLine(ImVec2(fx, canvas_min.y), ImVec2(fx, canvas_max.y), grid);
|
||||
dl->AddLine(ImVec2(canvas_min.x, fy), ImVec2(canvas_max.x, fy), grid);
|
||||
}
|
||||
|
||||
// Diagonal linear
|
||||
dl->AddLine(to_px(0.0f, 0.0f), to_px(1.0f, 1.0f),
|
||||
ImGui::GetColorU32(colors::text_dim), 1.0f);
|
||||
|
||||
// Curva
|
||||
constexpr int N = 96;
|
||||
ImVec2 prev = to_px(0.0f, fn::tween::apply(ease, 0.0f));
|
||||
ImU32 col = ImGui::GetColorU32(colors::primary);
|
||||
for (int i = 1; i <= N; i++) {
|
||||
float x = (float)i / (float)N;
|
||||
float y = fn::tween::apply(ease, x);
|
||||
ImVec2 cur = to_px(x, y);
|
||||
dl->AddLine(prev, cur, col, 2.0f);
|
||||
prev = cur;
|
||||
}
|
||||
|
||||
// Marker animado
|
||||
ImVec2 m = to_px(t_clamped, v);
|
||||
dl->AddCircleFilled(m, 5.0f, ImGui::GetColorU32(colors::primary_light));
|
||||
dl->AddCircle(m, 6.0f, ImGui::GetColorU32(colors::text), 0, 1.5f);
|
||||
|
||||
// Avanzar cursor
|
||||
ImGui::Dummy(canvas_size);
|
||||
|
||||
code_block(
|
||||
"#include \"core/tween_curves.h\"\n\n"
|
||||
"float k = fn::tween::apply(fn::tween::Ease::OutCubic, t);\n"
|
||||
"// o named:\n"
|
||||
"float k2 = fn::tween::out_cubic(t);"
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// demo_bezier_editor — editor + plot evaluado
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void demo_bezier_editor() {
|
||||
using namespace fn_tokens;
|
||||
|
||||
demo_header("bezier_editor", "v1.0.0",
|
||||
"Editor visual de curva Bezier cubica (4 puntos). Para diseñar "
|
||||
"easing curves custom. p1/p2 son draggable; p0/p3 fijos en (0,0)/(1,1).");
|
||||
|
||||
section("Editor");
|
||||
|
||||
static fn::BezierCurve curve; // identidad por defecto: ease lineal con handles desplazados
|
||||
|
||||
if (ImGui::Button("Reset##bz_reset")) {
|
||||
curve = fn::BezierCurve{};
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Ease-out preset##bz_eo")) {
|
||||
curve = {{0,0}, {0.0f, 0.0f}, {0.58f, 1.0f}, {1,1}};
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Ease-in-out preset##bz_eio")) {
|
||||
curve = {{0,0}, {0.42f, 0.0f}, {0.58f, 1.0f}, {1,1}};
|
||||
}
|
||||
|
||||
fn::bezier_editor("##bz_editor", curve, ImVec2(220, 220));
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, colors::text_muted);
|
||||
ImGui::Text("p0=(%.2f,%.2f) p1=(%.2f,%.2f) p2=(%.2f,%.2f) p3=(%.2f,%.2f)",
|
||||
curve.p0.x, curve.p0.y, curve.p1.x, curve.p1.y,
|
||||
curve.p2.x, curve.p2.y, curve.p3.x, curve.p3.y);
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
// Plot evaluation
|
||||
section("bezier_eval(curve, t)");
|
||||
static float t = 0.0f;
|
||||
ImGui::SetNextItemWidth(360.0f);
|
||||
ImGui::SliderFloat("t##bz_t", &t, 0.0f, 1.0f, "%.3f");
|
||||
float y = fn::bezier_eval(curve, t);
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, colors::text_muted);
|
||||
ImGui::Text("y(t=%.3f) = %.3f", t, y);
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
code_block(
|
||||
"#include \"core/bezier_editor.h\"\n\n"
|
||||
"static fn::BezierCurve curve;\n"
|
||||
"if (fn::bezier_editor(\"##my\", curve, ImVec2(220, 220))) {\n"
|
||||
" // user dragged a control point\n"
|
||||
"}\n"
|
||||
"float k = fn::bezier_eval(curve, t);"
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// demo_timeline — 2 tracks + display
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void demo_timeline() {
|
||||
using namespace fn_tokens;
|
||||
using fn::tween::Ease;
|
||||
|
||||
demo_header("timeline", "v1.0.0",
|
||||
"Timeline tipo DAW: tracks horizontales con keyframes draggable, "
|
||||
"scrub con el ruler, play/pause/loop. track_value_at(t) interpola "
|
||||
"aplicando la Ease de cada keyframe destino.");
|
||||
|
||||
static fn::TimelineState tl;
|
||||
static bool inited = false;
|
||||
if (!inited) {
|
||||
tl.duration = 4.0f;
|
||||
tl.playing = true;
|
||||
tl.tracks.push_back({"hue", {
|
||||
{0.0f, 0.0f, Ease::Linear},
|
||||
{2.0f, 1.0f, Ease::OutCubic},
|
||||
{4.0f, 0.0f, Ease::InOutCubic},
|
||||
}});
|
||||
tl.tracks.push_back({"amp", {
|
||||
{0.0f, 0.2f, Ease::Linear},
|
||||
{3.0f, 1.0f, Ease::OutElastic},
|
||||
}});
|
||||
inited = true;
|
||||
}
|
||||
|
||||
// Update
|
||||
fn::timeline_update(tl, ImGui::GetIO().DeltaTime);
|
||||
|
||||
// Display values
|
||||
section("Live values");
|
||||
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::PushStyleColor(ImGuiCol_Text, colors::text);
|
||||
ImGui::Text("t = %.3fs", tl.current_time);
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
auto draw_bar = [&](const char* name, float value, float vmin, float vmax) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, colors::text_muted);
|
||||
ImGui::Text("%-4s", name);
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::SameLine();
|
||||
ImVec2 cmin = ImGui::GetCursorScreenPos();
|
||||
ImVec2 csize = ImVec2(280.0f, 14.0f);
|
||||
ImDrawList* dl = ImGui::GetWindowDrawList();
|
||||
dl->AddRectFilled(cmin, ImVec2(cmin.x + csize.x, cmin.y + csize.y),
|
||||
ImGui::GetColorU32(colors::surface_active), radius::sm);
|
||||
float k = (value - vmin) / (vmax - vmin);
|
||||
if (k < 0.0f) k = 0.0f;
|
||||
if (k > 1.0f) k = 1.0f;
|
||||
dl->AddRectFilled(cmin, ImVec2(cmin.x + csize.x * k, cmin.y + csize.y),
|
||||
ImGui::GetColorU32(colors::primary), radius::sm);
|
||||
ImGui::Dummy(csize);
|
||||
ImGui::SameLine();
|
||||
ImGui::Text("%.3f", value);
|
||||
};
|
||||
draw_bar("hue", hue, 0.0f, 1.0f);
|
||||
draw_bar("amp", amp, 0.0f, 1.0f);
|
||||
|
||||
section("Widget");
|
||||
fn::timeline_widget("##gallery_tl", tl, ImVec2(-1, 220));
|
||||
|
||||
code_block(
|
||||
"#include \"core/timeline.h\"\n\n"
|
||||
"static fn::TimelineState tl;\n"
|
||||
"tl.tracks.push_back({\"hue\", {{0,0}, {2,1, fn::tween::Ease::OutCubic}, {4,0}}});\n"
|
||||
"tl.duration = 4.0f; tl.playing = true;\n\n"
|
||||
"fn::timeline_update(tl, ImGui::GetIO().DeltaTime);\n"
|
||||
"float h = fn::track_value_at(tl.tracks[0], tl.current_time);\n"
|
||||
"fn::timeline_widget(\"##tl\", tl);"
|
||||
);
|
||||
}
|
||||
|
||||
} // namespace gallery
|
||||
Reference in New Issue
Block a user