#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 #include 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 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