Files
primitives_gallery/demos_core.cpp
T
2026-05-11 16:28:44 +02:00

448 lines
17 KiB
C++

#include "demos.h"
#include "demo.h"
#include "core/button.h"
#include "core/icon_button.h"
#include "core/toolbar.h"
#include "core/modal_dialog.h"
#include "core/text_input.h"
#include "core/select.h"
#include "core/toast.h"
#include "core/tree_view.h"
#include "core/badge.h"
#include "core/empty_state.h"
#include "core/page_header.h"
#include "core/dashboard_panel.h"
#include "core/tokens.h"
#include "core/icons_tabler.h"
#include "viz/kpi_card.h"
#include <imgui.h>
#include <cstdio>
using namespace fn_ui;
using V = ButtonVariant;
using S = ButtonSize;
namespace gallery {
// ---------------------------------------------------------------------------
// button
// ---------------------------------------------------------------------------
void demo_button() {
demo_header("button", "v1.0.0",
"Boton con 4 variantes semanticas y 3 tamanos. Usa tokens para colores, "
"radius y padding — estilo consistente en toda la app.");
section("Variants x Sizes");
const V variants[] = {V::Primary, V::Secondary, V::Subtle, V::Danger};
const char* variant_names[] = {"Primary", "Secondary", "Subtle", "Danger"};
const S sizes[] = {S::Sm, S::Md, S::Lg};
const char* size_names[] = {"sm", "md", "lg"};
if (ImGui::BeginTable("##btn_grid", 5, ImGuiTableFlags_SizingFixedFit)) {
ImGui::TableSetupColumn("size");
for (int c = 0; c < 4; c++) ImGui::TableSetupColumn(variant_names[c]);
ImGui::TableHeadersRow();
for (int s = 0; s < 3; s++) {
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
variant_label(size_names[s]);
for (int v = 0; v < 4; v++) {
ImGui::TableSetColumnIndex(v + 1);
char id[32];
std::snprintf(id, sizeof(id), "%s##%d%d", variant_names[v], s, v);
button(id, variants[v], sizes[s]);
}
}
ImGui::EndTable();
}
code_block(
"#include \"core/button.h\"\n"
"using fn_ui::button;\n"
"using V = fn_ui::ButtonVariant;\n\n"
"if (button(\"Save\", V::Primary)) save();\n"
"if (button(\"Cancel\", V::Subtle)) close();\n"
"if (button(\"Delete\", V::Danger)) confirm();"
);
}
// ---------------------------------------------------------------------------
// icon_button
// ---------------------------------------------------------------------------
void demo_icon_button() {
demo_header("icon_button", "v1.0.0",
"Boton cuadrado 28x28 con un glyph centrado y tooltip opcional. "
"Usa los TI_* de core/icons_tabler.h (Tabler Icons cargado automaticamente "
"por fn::run_app via icon_font.cpp).");
section("Tabler icon set");
struct { const char* id; const char* glyph; const char* tip; } ic[] = {
{"##rl", TI_REFRESH, "Reload"},
{"##ad", TI_PLUS, "Add"},
{"##dl", TI_TRASH, "Delete"},
{"##dn", TI_CHEVRON_DOWN, "Dropdown"},
{"##cf", TI_SETTINGS, "Settings"},
{"##ok", TI_CHECK, "Check"},
{"##cl", TI_X, "Close"},
{"##ed", TI_PENCIL, "Edit"},
{"##sv", TI_DEVICE_FLOPPY, "Save"},
{"##sr", TI_SEARCH, "Search"},
{"##hp", TI_HELP, "Help"},
{"##hm", TI_HOME, "Home"},
};
for (auto& b : ic) {
icon_button(b.id, b.glyph, b.tip);
ImGui::SameLine();
}
ImGui::NewLine();
code_block(
"#include \"core/icons_tabler.h\"\n\n"
"if (icon_button(\"##reload\", TI_REFRESH, \"Reload\"))\n"
" reload_data();\n\n"
"// Mas de 5000 iconos disponibles — ver core/icons_tabler.h"
);
}
// ---------------------------------------------------------------------------
// toolbar
// ---------------------------------------------------------------------------
void demo_toolbar() {
demo_header("toolbar", "v1.0.0",
"Grupo horizontal con spacing consistente y separadores verticales sutiles. "
"El caller usa ImGui::SameLine entre items y toolbar_separator entre grupos.");
section("Example with two groups");
toolbar_begin();
button(TI_PLUS " New", V::Primary); ImGui::SameLine();
button(TI_FOLDER_OPEN " Open", V::Secondary); ImGui::SameLine();
button(TI_DEVICE_FLOPPY " Save",V::Secondary);
toolbar_separator();
icon_button("##set", TI_SETTINGS, "Settings");
ImGui::SameLine();
icon_button("##help", TI_HELP, "Help");
toolbar_end();
code_block(
"#include \"core/icons_tabler.h\"\n\n"
"toolbar_begin();\n"
" button(TI_PLUS \" New\", V::Primary); ImGui::SameLine();\n"
" button(TI_FOLDER_OPEN \" Open\", V::Secondary);\n"
" toolbar_separator();\n"
" icon_button(\"##set\", TI_SETTINGS, \"Settings\");\n"
"toolbar_end();"
);
}
// ---------------------------------------------------------------------------
// modal_dialog
// ---------------------------------------------------------------------------
void demo_modal() {
demo_header("modal_dialog", "v1.0.0",
"Popup modal centrada con estilo surface+border. Close con Escape o click en X. "
"Patron begin/end — modal_dialog_end debe llamarse siempre.");
static bool show = false;
if (button("Open modal", V::Primary)) show = true;
if (modal_dialog_begin("Demo modal", &show, ImVec2(380, 0))) {
ImGui::TextWrapped(
"Modal centrada en el viewport principal, con estilo tokens.");
ImGui::Dummy(ImVec2(0, fn_tokens::spacing::sm));
static char buf[64] = {};
text_input("Name", buf, sizeof(buf), "escribe algo");
ImGui::Separator();
if (button("Cancel", V::Subtle)) show = false;
ImGui::SameLine();
if (button("Done", V::Primary)) show = false;
}
modal_dialog_end();
code_block(
"static bool show = false;\n"
"if (button(\"Open\", Primary)) show = true;\n"
"if (modal_dialog_begin(\"Title\", &show, ImVec2(380,0))) {\n"
" // ... campos del form ...\n"
" if (button(\"Done\", Primary)) show = false;\n"
"}\n"
"modal_dialog_end();"
);
}
// ---------------------------------------------------------------------------
// text_input
// ---------------------------------------------------------------------------
void demo_text_input() {
demo_header("text_input", "v1.0.0",
"Label muted + input estilizado con tokens. Full-width dentro del contenedor. "
"Placeholder opcional mostrado en text_dim cuando el buffer esta vacio.");
static char name[128] = {};
static char desc[256] = {};
static char tags[128] = {};
ImGui::BeginChild("##ti_wrap", ImVec2(420, 0), ImGuiChildFlags_AutoResizeY);
text_input("Name", name, sizeof(name), "my-new-thing");
ImGui::Dummy(ImVec2(0, fn_tokens::spacing::xs));
text_input("Description", desc, sizeof(desc));
ImGui::Dummy(ImVec2(0, fn_tokens::spacing::xs));
text_input("Tags (CSV)", tags, sizeof(tags), "imgui,ui,form");
ImGui::EndChild();
code_block(
"static char name[128] = {};\n"
"text_input(\"Name\", name, sizeof(name), \"my-new-thing\");\n"
"// true on change — se usa mas para validar en vivo\n"
"// que para leer el valor (que vive en el buffer)."
);
}
// ---------------------------------------------------------------------------
// select
// ---------------------------------------------------------------------------
void demo_select() {
demo_header("select", "v1.0.0",
"Dropdown con label muted y opcion (none) opcional. Mismo estilo tokens que text_input.");
static int lang_idx = 0;
static int domain_idx = -1;
const char* langs[] = {"go", "py", "ts", "sh", "cpp"};
const char* domains[] = {"core", "infra", "finance", "datascience", "viz"};
ImGui::BeginChild("##sl_wrap", ImVec2(420, 0), ImGuiChildFlags_AutoResizeY);
select("Language", &lang_idx, langs, 5);
ImGui::Dummy(ImVec2(0, fn_tokens::spacing::xs));
select("Domain (optional)", &domain_idx, domains, 5, true);
ImGui::EndChild();
code_block(
"static int lang = 0;\n"
"const char* langs[] = {\"go\",\"py\",\"ts\",\"sh\",\"cpp\"};\n"
"select(\"Language\", &lang, langs, 5);"
);
}
// ---------------------------------------------------------------------------
// toast + inbox
// ---------------------------------------------------------------------------
void demo_toast() {
demo_header("toast", "v1.1.0",
"Notificaciones efimeras (~3.5s con fade-out) + inbox con campana. "
"La campana muestra badge con no-leidos y popover con las ultimas 50.");
section("Trigger toasts");
if (button("Info", V::Secondary)) toast_push(ToastKind::Info, "this is an info toast");
ImGui::SameLine();
if (button("Success", V::Primary)) toast_push(ToastKind::Success, "operation completed");
ImGui::SameLine();
if (button("Warning", V::Secondary)) toast_push(ToastKind::Warning, "heads up about something");
ImGui::SameLine();
if (button("Error", V::Danger)) toast_push(ToastKind::Error, "operation failed: reason");
section("Inbox (bell with unread badge)");
toast_inbox_button("##inbox_demo");
code_block(
"toast_push(ToastKind::Success, \"Reindexed 891 functions\");\n"
"toast_push(ToastKind::Error, \"HTTP 503: server down\");\n\n"
"// En la toolbar:\n"
"toast_inbox_button(\"##inbox\");\n\n"
"// Una vez por frame al final del render:\n"
"toast_render();"
);
}
// ---------------------------------------------------------------------------
// tree_view
// ---------------------------------------------------------------------------
void demo_tree_view() {
demo_header("tree_view", "v1.0.0",
"Tree low-level para jerarquias (ej. projects -> apps/analysis/vaults). "
"Sin estado interno: el caller gestiona seleccion y pasa 'selected' por parametro.");
static std::string selected;
section("Projects (fake)");
ImGui::BeginChild("##tv", ImVec2(360, 200), ImGuiChildFlags_Borders);
struct FakeProject { const char* id; const char* name; const char* apps[3]; };
const FakeProject projs[] = {
{"app_turismo", "app_turismo", {"guide_es", "offline_maps", nullptr}},
{"element_agents", "element_agents", {"matrix_bot", nullptr, nullptr}},
{"fn_monitoring", "fn_monitoring", {"sqlite_api", "registry_dashboard", nullptr}},
};
for (auto& p : projs) {
bool sel = (selected == p.id);
if (tree_branch_begin(p.id, p.name, sel)) {
if (tree_node_clicked()) selected = p.id;
for (int i = 0; i < 3 && p.apps[i]; i++) {
bool asel = (selected == p.apps[i]);
tree_leaf(p.apps[i], p.apps[i], asel);
if (tree_node_clicked()) selected = p.apps[i];
}
tree_branch_end();
} else if (tree_node_clicked()) {
selected = p.id;
}
}
ImGui::EndChild();
ImGui::PushStyleColor(ImGuiCol_Text, fn_tokens::colors::text_muted);
ImGui::Text("Selected: %s", selected.empty() ? "(none)" : selected.c_str());
ImGui::PopStyleColor();
code_block(
"static std::string sel;\n"
"if (tree_branch_begin(p.id, p.name, sel == p.id)) {\n"
" if (tree_node_clicked()) sel = p.id;\n"
" for (auto& a : p.apps) {\n"
" tree_leaf(a.id, a.name, sel == a.id);\n"
" if (tree_node_clicked()) sel = a.id;\n"
" }\n"
" tree_branch_end();\n"
"}"
);
}
// ---------------------------------------------------------------------------
// kpi_card
// ---------------------------------------------------------------------------
void demo_kpi_card() {
demo_header("kpi_card", "v1.3.0",
"Card compacta 86px con icono opcional + label muted, valor x1.4, trend con "
"TI_TRENDING_UP/DOWN y sparkline. Usa tokens: surface bg, border, radius md.");
if (ImGui::BeginTable("##kpi_grid", 4, ImGuiTableFlags_SizingStretchSame)) {
float history[] = {10, 12, 11, 15, 18, 17, 20};
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0); kpi_card("Revenue", 20000.0f, 12.5f, history, 7, "$%.0f", TI_CASH);
ImGui::TableSetColumnIndex(1); kpi_card("Users", 1250.0f, 3.4f, history, 7, "%.0f", TI_USERS);
ImGui::TableSetColumnIndex(2); kpi_card("Churn", 2.1f, -0.3f, history, 7, "%.1f%%", TI_CHART_BAR);
ImGui::TableSetColumnIndex(3); kpi_card("Errors", 0.0f, 0.0f, nullptr, 0, "%.0f", TI_ALERT_CIRCLE);
ImGui::EndTable();
}
code_block(
"#include \"core/icons_tabler.h\"\n\n"
"float history[] = {10,12,11,15,18,17,20};\n"
"kpi_card(\"Revenue\", 20000.0f, 12.5f, history, 7, \"$%.0f\", TI_CASH);\n"
"kpi_card(\"Users\", 1250.0f, 3.4f, history, 7, \"%.0f\", TI_USERS);\n"
"// Sin delta ni history: muestra TI_MINUS como placeholder\n"
"kpi_card(\"Errors\", 0.0f, 0.0f, nullptr, 0, \"%.0f\", TI_ALERT_CIRCLE);"
);
}
// ---------------------------------------------------------------------------
// badge
// ---------------------------------------------------------------------------
void demo_badge() {
demo_header("badge", "v1.0.0",
"Etiqueta inline con 6 variantes semanticas. Equivalente a <Badge> de fn_library.");
section("Variants");
badge("Default", BadgeVariant::Default); ImGui::SameLine();
badge("Success", BadgeVariant::Success); ImGui::SameLine();
badge("Warning", BadgeVariant::Warning); ImGui::SameLine();
badge("Error", BadgeVariant::Error); ImGui::SameLine();
badge("Info", BadgeVariant::Info); ImGui::SameLine();
badge("Outline", BadgeVariant::Outline);
section("In context (table row)");
ImGui::Text("filter_slice_go_core"); ImGui::SameLine();
badge("pure", BadgeVariant::Success); ImGui::SameLine();
badge("tested", BadgeVariant::Info);
code_block(
"badge(\"pure\", BadgeVariant::Success);\n"
"badge(\"stale\", BadgeVariant::Warning);\n"
"badge(\"broken\", BadgeVariant::Error);"
);
}
// ---------------------------------------------------------------------------
// empty_state
// ---------------------------------------------------------------------------
void demo_empty_state() {
demo_header("empty_state", "v1.0.0",
"Icono grande muted + titulo + descripcion opcional. Para listas/tablas vacias.");
ImGui::BeginChild("##es", ImVec2(0, 180), ImGuiChildFlags_Borders);
empty_state("( no data )", "No projects yet",
"Create one under projects/{name}/ with project.md and reindex");
ImGui::EndChild();
code_block(
"if (apps.empty()) {\n"
" empty_state(\"( no data )\", \"No apps yet\",\n"
" \"Click + Add to create one\");\n"
" return;\n"
"}"
);
}
// ---------------------------------------------------------------------------
// page_header
// ---------------------------------------------------------------------------
void demo_page_header() {
demo_header("page_header", "v1.0.0",
"Header de pagina con titulo, subtitulo opcional y separador final. "
"Patron begin/end permite insertar acciones entre titulo y separador.");
page_header_begin("Dashboard", "13 apps, 3 projects, 2 analyses");
ImGui::SameLine(ImGui::GetContentRegionAvail().x - 140.0f);
toolbar_begin();
button("Reload", V::Subtle); ImGui::SameLine();
button("+ Add", V::Secondary);
toolbar_end();
page_header_end();
code_block(
"page_header_begin(\"Dashboard\", subtitle);\n"
"ImGui::SameLine(ImGui::GetContentRegionAvail().x - 140);\n"
"toolbar_begin();\n"
" button(\"Reload\", Subtle);\n"
"toolbar_end();\n"
"page_header_end();"
);
}
// ---------------------------------------------------------------------------
// dashboard_panel
// ---------------------------------------------------------------------------
void demo_dashboard_panel() {
demo_header("dashboard_panel", "v1.0.0",
"Contenedor tipo panel con titulo, bordes redondeados, bg surface. "
"Auto-resize-Y segun contenido. Usa min_width/min_height como piso.");
if (dashboard_panel_begin("Revenue", 0, 120.0f)) {
ImGui::Text("Some panel content goes here.");
ImGui::Text("Anything drawn inside lives in the child window.");
}
dashboard_panel_end();
code_block(
"if (dashboard_panel_begin(\"Revenue\", 0, 120.0f)) {\n"
" ImGui::Text(\"content\");\n"
"}\n"
"dashboard_panel_end();"
);
}
} // namespace gallery