// 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 #include #include #include #include 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 ` 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). Windows mkdir() solo acepta el path. #if defined(_WIN32) mkdir(out_dir); #else mkdir(out_dir, 0755); #endif std::vector 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.3.0", .description = "Visual catalog of fn_registry C++ UI primitives. Modo --capture para golden screenshots, sidebar via tree_view, candlestick fix."}, .init_gl_loader = true}, render ); }