Files
fn_registry/cpp/apps/primitives_gallery/main.cpp
T
egutierrez eae1829923 refactor(primitives_gallery): usar tree_view en sidebar (issue 0046)
El sidebar agrupaba demos por categoria con un Selectable+PushStyleColor
manual por item. Ahora usa fn_ui::tree_view con las categorias como
ramas (default-open via SetNextItemOpen + ImGuiCond_FirstUseEver) y las
demos como hojas seleccionables. Visualmente equivalente: separadores
por categoria, item activo coloreado.

Raw ImGui::Begin*/Selectable: 4 -> 3 (Selectable eliminado).
2026-04-29 00:29:44 +02:00

210 lines
8.8 KiB
C++

// primitives_gallery — catalogo visual interactivo de los primitivos UI
// del registry (cpp/functions/core y cpp/functions/viz).
//
// Sidebar izquierdo con lista de primitivos agrupados por dominio; panel
// derecho renderiza la demo del item seleccionado (+ snippet de codigo).
//
// Rol: smoke test visual + documentacion viva + build gate en CI.
// NO se conecta a sqlite_api ni a ningun backend. Datos sinteticos.
#include "app_base.h"
#include "imgui.h"
#include "core/fullscreen_window.h"
#include "core/tokens.h"
#include "core/page_header.h"
#include "core/toast.h"
#include "core/app_menubar.h"
#include "core/tree_view.h"
#include "demos.h"
#include "demo.h"
#include "capture.h"
#include <cstdio>
#include <cstring>
#include <string>
#include <sys/stat.h>
#include <vector>
struct DemoEntry {
const char* id; // id estable, apto para comparar seleccion
const char* label; // texto en sidebar
const char* category; // "Core" o "Viz"
void (*fn)(); // puntero a la demo_xxx
};
static const DemoEntry k_demos[] = {
// Core
{"button", "button", "Core", &gallery::demo_button},
{"icon_button", "icon_button", "Core", &gallery::demo_icon_button},
{"toolbar", "toolbar", "Core", &gallery::demo_toolbar},
{"modal_dialog", "modal_dialog", "Core", &gallery::demo_modal},
{"text_input", "text_input", "Core", &gallery::demo_text_input},
{"select", "select", "Core", &gallery::demo_select},
{"toast", "toast + inbox", "Core", &gallery::demo_toast},
{"tree_view", "tree_view", "Core", &gallery::demo_tree_view},
{"badge", "badge", "Core", &gallery::demo_badge},
{"empty_state", "empty_state", "Core", &gallery::demo_empty_state},
{"page_header", "page_header", "Core", &gallery::demo_page_header},
{"dashboard_panel", "dashboard_panel", "Core", &gallery::demo_dashboard_panel},
{"kpi_card", "kpi_card", "Core", &gallery::demo_kpi_card},
{"text_editor", "text_editor", "Core", &gallery::demo_text_editor}, // wave 1
{"file_watcher", "file_watcher", "Core", &gallery::demo_file_watcher}, // wave 1
{"process_runner", "process_runner", "Core", &gallery::demo_process_runner},
{"tween", "tween_curves", "Core", &gallery::demo_tween},
{"bezier_editor", "bezier_editor", "Core", &gallery::demo_bezier_editor},
{"timeline", "timeline", "Core", &gallery::demo_timeline},
{"sql_workbench", "sql_workbench", "Core", &gallery::demo_sql_workbench}, // issue 0032
// Viz
{"bar_chart", "bar_chart", "Viz", &gallery::demo_bar_chart},
{"pie_chart", "pie_chart", "Viz", &gallery::demo_pie_chart},
{"line_plot", "line_plot", "Viz", &gallery::demo_line_plot},
{"scatter_plot", "scatter_plot", "Viz", &gallery::demo_scatter_plot},
{"histogram", "histogram", "Viz", &gallery::demo_histogram},
{"sparkline", "sparkline", "Viz", &gallery::demo_sparkline},
{"graph_viewport", "graph_viewport", "Viz", &gallery::demo_graph},
{"candlestick", "candlestick", "Viz", &gallery::demo_candlestick},
{"gauge", "gauge", "Viz", &gallery::demo_gauge},
{"heatmap", "heatmap", "Viz", &gallery::demo_heatmap},
{"table_view", "table_view", "Viz", &gallery::demo_table_view},
{"surface_plot_3d", "surface_plot_3d", "Viz", &gallery::demo_surface_plot_3d},
{"scatter_3d", "scatter_3d", "Viz", &gallery::demo_scatter_3d},
{"mesh_viewer", "mesh_viewer", "Viz", &gallery::demo_mesh_viewer},
{"treemap", "treemap", "Viz", &gallery::demo_treemap},
{"sankey", "sankey", "Viz", &gallery::demo_sankey},
{"chord", "chord", "Viz", &gallery::demo_chord},
{"contour", "contour", "Viz", &gallery::demo_contour},
{"voronoi", "voronoi", "Viz", &gallery::demo_voronoi},
// Gfx (shaders_lab core)
{"shader_canvas", "shader_canvas", "Gfx", &gallery::demo_shader_canvas},
{"gl_texture", "gl_texture_load", "Gfx", &gallery::demo_gl_texture}, // wave 1
};
static constexpr int k_demo_count = sizeof(k_demos) / sizeof(k_demos[0]);
static std::string g_selected_id = "button";
static const DemoEntry* find_demo(const std::string& id) {
for (int i = 0; i < k_demo_count; i++) {
if (id == k_demos[i].id) return &k_demos[i];
}
return &k_demos[0];
}
static void draw_sidebar() {
ImGui::BeginChild("##gallery_sidebar", ImVec2(220, 0),
ImGuiChildFlags_Borders);
// Agrupar por categoria como rama del tree_view (categorias abiertas por
// defecto). Cada demo es una hoja seleccionable.
int i = 0;
while (i < k_demo_count) {
const char* category = k_demos[i].category;
// Default-open la rama la primera vez que se abre el sidebar.
ImGui::SetNextItemOpen(true, ImGuiCond_FirstUseEver);
if (fn_ui::tree_branch_begin(category, category, /*selected=*/false)) {
// Recorrer todas las demos consecutivas con esta misma categoria.
while (i < k_demo_count
&& std::strcmp(k_demos[i].category, category) == 0) {
const auto& d = k_demos[i];
const bool selected = (g_selected_id == d.id);
fn_ui::tree_leaf(d.id, d.label, selected);
if (fn_ui::tree_node_clicked()) {
g_selected_id = d.id;
}
i++;
}
fn_ui::tree_branch_end();
} else {
// Rama colapsada — saltar todos sus items.
while (i < k_demo_count
&& std::strcmp(k_demos[i].category, category) == 0) {
i++;
}
}
}
ImGui::EndChild();
}
static void render() {
// Theme y gl_loader gestionados por fn::run_app (theme=FnDark por defecto,
// init_gl_loader=true en AppConfig).
// MainMenuBar (solo Settings — la gallery no tiene paneles toggleables ni layouts)
fn_ui::app_menubar(nullptr, 0, nullptr);
fullscreen_window_begin("##gallery");
page_header_begin("Primitives Gallery",
"Visual catalog of fn_registry C++ UI primitives");
page_header_end();
if (ImGui::BeginTable("##layout", 2,
ImGuiTableFlags_Resizable | ImGuiTableFlags_SizingFixedFit)) {
ImGui::TableSetupColumn("sidebar", ImGuiTableColumnFlags_WidthFixed, 220.0f);
ImGui::TableSetupColumn("content", ImGuiTableColumnFlags_WidthStretch);
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
draw_sidebar();
ImGui::TableSetColumnIndex(1);
ImGui::BeginChild("##gallery_content", ImVec2(0, 0),
ImGuiChildFlags_Borders,
ImGuiWindowFlags_HorizontalScrollbar);
const DemoEntry* d = find_demo(g_selected_id);
if (d && d->fn) d->fn();
ImGui::EndChild();
ImGui::EndTable();
}
fullscreen_window_end();
// Toasts se renderizan encima para que el demo de toast funcione aqui tambien.
fn_ui::toast_render();
}
int main(int argc, char** argv) {
// Capture mode: `primitives_gallery --capture <output_dir>` corre cada
// demo en una ventana GLFW invisible y guarda PNG por demo. Para CI/golden.
for (int i = 1; i < argc; i++) {
if (std::strcmp(argv[i], "--capture") == 0) {
if (i + 1 >= argc) {
std::fprintf(stderr, "--capture requires an output dir argument\n");
return 2;
}
const char* out_dir = argv[i + 1];
// Best-effort mkdir (idempotente).
mkdir(out_dir, 0755);
std::vector<gallery::CaptureItem> items;
items.reserve(k_demo_count);
for (int j = 0; j < k_demo_count; j++) {
items.push_back({k_demos[j].id, k_demos[j].fn});
}
gallery::CaptureConfig cfg;
cfg.output_dir = out_dir;
cfg.warmup_frames = 3;
cfg.capture_w = 800;
cfg.capture_h = 600;
const bool ok = gallery::run_capture(cfg, items);
return ok ? 0 : 1;
}
}
return fn::run_app(
{.title = "fn_registry · Primitives Gallery",
.width = 1400,
.height = 900,
.viewports = true,
.about = {.name = "Primitives Gallery",
.version = "0.1.0",
.description = "Visual catalog of fn_registry C++ UI primitives"},
.init_gl_loader = true},
render
);
}