cb6d9e61d1
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
231 lines
9.9 KiB
C++
231 lines
9.9 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 <cmath>
|
|
#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},
|
|
{"graph_styles", "graph_styles", "Viz", &gallery::demo_graph_styles}, // issue 0049f
|
|
{"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
|
|
{"gl_info", "gl_info", "Gfx", &gallery::demo_gl_info}, // issue 0049b
|
|
};
|
|
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). Menubar via run_app.
|
|
// auto_dockspace=false porque usamos fullscreen_window que ocupa todo.
|
|
|
|
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);
|
|
// Cuando cambia el tamaño de fuente (Settings > Size), el contenido
|
|
// del child crece/encoge pero la posicion de scroll en pixeles
|
|
// no — efecto: lo visible "se baja". Escalamos scroll_y por el
|
|
// ratio de fuentes para mantener la misma linea logica arriba.
|
|
{
|
|
static float s_prev_font_size = 0.0f;
|
|
float cur_font_size = ImGui::GetStyle().FontSizeBase;
|
|
if (s_prev_font_size > 0.0f &&
|
|
std::fabs(s_prev_font_size - cur_font_size) > 0.01f) {
|
|
ImGui::SetScrollY(ImGui::GetScrollY() *
|
|
(cur_font_size / s_prev_font_size));
|
|
}
|
|
s_prev_font_size = cur_font_size;
|
|
}
|
|
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). Windows mkdir() solo acepta el path.
|
|
#if defined(_WIN32)
|
|
mkdir(out_dir);
|
|
#else
|
|
mkdir(out_dir, 0755);
|
|
#endif
|
|
|
|
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.4.0",
|
|
.description = "Visual catalog of fn_registry C++ UI primitives. Now on OpenGL 4.3 core (compute, SSBOs, image load/store) — ver demo gl_info."},
|
|
.init_gl_loader = true,
|
|
.auto_dockspace = false,
|
|
.log = {"primitives_gallery.log", 1}},
|
|
render
|
|
);
|
|
}
|