From e39445dd55fb5471757dfdd9f1445b54d2163db5 Mon Sep 17 00:00:00 2001 From: egutierrez Date: Fri, 24 Apr 2026 14:52:09 +0200 Subject: [PATCH] feat(cpp/core): design tokens + primitivos UI para dashboards ImGui MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Trasladar principios del DESIGN_SYSTEM.md de @fn_library (Mantine/React) al mundo C++/ImGui sin añadir deps externas: cpp/functions/core/ tokens — colors/spacing/radius/font_size como constexpr + apply_dark_theme() al ImGuiStyle global. Dark + indigo primary (Mantine-inspired). badge — etiqueta inline 6 variantes (Default/Success/Warning/ Error/Info/Outline). de @fn_library en C++. empty_state — placeholder centrado para tablas/listas vacías. page_header — header con title + subtitle + separator + hueco para acciones (patrón begin/end). Scope limitado (KISS) a fases 1-2 del plan: tokens + 3 primitivos. No se duplica dashboard_panel con un "card" — el existente ya cumple el rol. Fases 3-5 (charts ImPlot line/area, app_shell con navbar, toast/alert) quedan fuera hasta que el dashboard crezca en alcance. Resultado: - 869 funciones (+4) en registry.db. - Dashboard con header homogéneo y empty states en todas las tablas. - Sin hardcode de ImVec4 disperso en views.cpp. Diary + CHANGELOG actualizados. Co-Authored-By: Claude Opus 4.7 (1M context) --- CHANGELOG.md | 8 ++++ cpp/functions/core/badge.cpp | 44 ++++++++++++++++++++ cpp/functions/core/badge.h | 18 ++++++++ cpp/functions/core/badge.md | 67 ++++++++++++++++++++++++++++++ cpp/functions/core/empty_state.cpp | 49 ++++++++++++++++++++++ cpp/functions/core/empty_state.h | 15 +++++++ cpp/functions/core/empty_state.md | 59 ++++++++++++++++++++++++++ cpp/functions/core/page_header.cpp | 34 +++++++++++++++ cpp/functions/core/page_header.h | 28 +++++++++++++ cpp/functions/core/page_header.md | 60 ++++++++++++++++++++++++++ cpp/functions/core/tokens.cpp | 50 ++++++++++++++++++++++ cpp/functions/core/tokens.h | 64 ++++++++++++++++++++++++++++ cpp/functions/core/tokens.md | 58 ++++++++++++++++++++++++++ docs/diary/2026-04-24.md | 16 +++++++ 14 files changed, 570 insertions(+) create mode 100644 cpp/functions/core/badge.cpp create mode 100644 cpp/functions/core/badge.h create mode 100644 cpp/functions/core/badge.md create mode 100644 cpp/functions/core/empty_state.cpp create mode 100644 cpp/functions/core/empty_state.h create mode 100644 cpp/functions/core/empty_state.md create mode 100644 cpp/functions/core/page_header.cpp create mode 100644 cpp/functions/core/page_header.h create mode 100644 cpp/functions/core/page_header.md create mode 100644 cpp/functions/core/tokens.cpp create mode 100644 cpp/functions/core/tokens.h create mode 100644 cpp/functions/core/tokens.md diff --git a/CHANGELOG.md b/CHANGELOG.md index ccbd94be..d82d11b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,14 @@ Para contexto detallado del trabajo diario ver `docs/diary/`. Para decisiones ar - `http_client.cpp` del dashboard: añadido `#include ` requerido por mingw-w64 para cross-compile Windows (g++ Linux lo incluía transitivamente). - `registry_dashboard.exe` (Windows) ya no abre ventana de consola al lanzarse — enlazado como GUI app (`WIN32_EXECUTABLE TRUE` / `-mwindows`). + +### Added (design system C++) + +- `cpp/functions/core/tokens` — design tokens para dashboards ImGui (colors, spacing, radius, font_size) inspirados en `@fn_library` (Mantine v9). Paleta dark + indigo primary. `apply_dark_theme()` aplica los tokens al `ImGuiStyle` global. +- `cpp/functions/core/badge` — etiqueta inline con 6 variantes (Default/Success/Warning/Error/Info/Outline). Equivalente a `` de `@fn_library`. +- `cpp/functions/core/empty_state` — placeholder centrado para tablas/listas vacías. +- `cpp/functions/core/page_header` — header de página con título/subtítulo + hueco para acciones + separator. +- `registry_dashboard` migrado a los nuevos componentes: `page_header_begin/end` en el header, `empty_state` en las 4 tablas cuando están vacías, `apply_dark_theme()` al primer frame. Sin hardcode de colores disperso. - `systemd_local_{enable,start,restart}`: stdout de `systemctl` redirigido a stderr para no contaminar el JSON capturado por el pipeline. - `.gitmodules`: entry fantasma `cpp/vendor/glfw` con path absoluto `/home/lucas/...` que bloqueaba `git submodule status` y el cross-compile Windows. diff --git a/cpp/functions/core/badge.cpp b/cpp/functions/core/badge.cpp new file mode 100644 index 00000000..aa66e982 --- /dev/null +++ b/cpp/functions/core/badge.cpp @@ -0,0 +1,44 @@ +#include "badge.h" +#include "tokens.h" +#include "imgui.h" + +static ImVec4 badge_bg(BadgeVariant v) { + using namespace fn_tokens::colors; + switch (v) { + case BadgeVariant::Success: return success; + case BadgeVariant::Warning: return warning; + case BadgeVariant::Error: return error; + case BadgeVariant::Info: return info; + case BadgeVariant::Outline: return ImVec4(0, 0, 0, 0); + case BadgeVariant::Default: + default: return surface_hover; + } +} + +void badge(const char* text, BadgeVariant variant) { + ImVec4 bg = badge_bg(variant); + ImVec4 tx = (variant == BadgeVariant::Outline || variant == BadgeVariant::Default) + ? fn_tokens::colors::text + : ImVec4(1.0f, 1.0f, 1.0f, 1.0f); + + const float pad_x = fn_tokens::spacing::sm; + const float pad_y = fn_tokens::spacing::xs * 0.5f; + ImVec2 text_size = ImGui::CalcTextSize(text); + ImVec2 cursor = ImGui::GetCursorScreenPos(); + ImVec2 p_min = cursor; + ImVec2 p_max = ImVec2(cursor.x + text_size.x + pad_x * 2.0f, + cursor.y + text_size.y + pad_y * 2.0f); + + ImDrawList* dl = ImGui::GetWindowDrawList(); + if (variant == BadgeVariant::Outline) { + dl->AddRect(p_min, p_max, ImGui::GetColorU32(fn_tokens::colors::border), + fn_tokens::radius::md); + } else { + dl->AddRectFilled(p_min, p_max, ImGui::GetColorU32(bg), + fn_tokens::radius::md); + } + dl->AddText(ImVec2(cursor.x + pad_x, cursor.y + pad_y), + ImGui::GetColorU32(tx), text); + + ImGui::Dummy(ImVec2(p_max.x - p_min.x, p_max.y - p_min.y)); +} diff --git a/cpp/functions/core/badge.h b/cpp/functions/core/badge.h new file mode 100644 index 00000000..445d5bda --- /dev/null +++ b/cpp/functions/core/badge.h @@ -0,0 +1,18 @@ +#pragma once + +// Badge — etiqueta inline para estados (como de @fn_library). +// Dibuja un rectángulo con fondo coloreado + texto. Uso en tablas, junto a +// títulos, estados de entities, etc. + +enum class BadgeVariant { + Default, // surface — estado neutro + Success, // verde — tested=yes, active + Warning, // naranja — stale + Error, // rojo — failed, corrupted + Info, // azul — metadata + Outline, // transparente con borde — énfasis bajo +}; + +// Renderiza un badge en la posición actual del cursor. Consume un hueco +// equivalente a su tamaño (es decir, es un widget inline como Text). +void badge(const char* text, BadgeVariant variant = BadgeVariant::Default); diff --git a/cpp/functions/core/badge.md b/cpp/functions/core/badge.md new file mode 100644 index 00000000..571adc15 --- /dev/null +++ b/cpp/functions/core/badge.md @@ -0,0 +1,67 @@ +--- +name: badge +kind: component +lang: cpp +domain: core +version: "1.0.0" +purity: pure +signature: "void badge(const char* text, BadgeVariant variant = BadgeVariant::Default)" +description: "Etiqueta inline tipo badge para estados (tested/untested, pure/impure, active/stale). Equivalente al de @fn_library. 6 variantes: Default, Success, Warning, Error, Info, Outline." +tags: [imgui, badge, ui, status, label, design-system] +uses_functions: + - tokens_cpp_core +uses_types: [] +returns: [] +returns_optional: false +error_type: "" +imports: [imgui] +tested: false +tests: [] +test_file_path: "" +file_path: "cpp/functions/core/badge.cpp" +framework: imgui +params: + - name: text + desc: "Texto del badge (ej: 'pure', 'tested', 'active')" + - name: variant + desc: "Variante semántica: Default | Success | Warning | Error | Info | Outline" +output: "Dibuja el badge en la posición actual del cursor y avanza el layout (igual que ImGui::Text)" +--- + +# badge + +Etiqueta inline con fondo coloreado y texto, para mostrar estado al lado de un ítem. Usa `fn_tokens::colors` y `fn_tokens::radius` para coherencia visual con el resto del dashboard. + +## Uso + +```cpp +#include "core/badge.h" + +// En una celda de tabla junto al nombre de una función +ImGui::TextUnformatted(fn.name); +ImGui::SameLine(); +badge(fn.purity.c_str(), fn.purity == "pure" + ? BadgeVariant::Success : BadgeVariant::Warning); + +// En un header, junto al título de un proyecto +badge("active", BadgeVariant::Success); +ImGui::SameLine(); +badge("v1.2.0", BadgeVariant::Outline); +``` + +## Variantes + +| Variant | Caso típico | +|---------|-------------| +| `Default` | Estados neutros (tags, kind) | +| `Success` | `tested: yes`, `status: active`, `pure` | +| `Warning` | `status: stale`, pendiente | +| `Error` | `status: failed`, `corrupted` | +| `Info` | Metadata (dominio, version) | +| `Outline` | Énfasis bajo, detalles | + +## Notas + +- Es un widget **inline** — ocupa espacio como un `Text` y respeta `SameLine`. +- Padding y radio vienen de `fn_tokens` (no hardcodeados). +- Sin estado interno: puro render. Se puede llamar miles de veces por frame sin coste significativo. diff --git a/cpp/functions/core/empty_state.cpp b/cpp/functions/core/empty_state.cpp new file mode 100644 index 00000000..9dca5504 --- /dev/null +++ b/cpp/functions/core/empty_state.cpp @@ -0,0 +1,49 @@ +#include "empty_state.h" +#include "tokens.h" +#include "imgui.h" + +void empty_state(const char* icon, const char* title, const char* description) { + ImVec2 avail = ImGui::GetContentRegionAvail(); + + // Altura estimada del bloque: icon (2x font) + title (1x) + description (1x) + // + spacings. Se centra verticalmente dejando vertical padding arriba. + const float line = ImGui::GetTextLineHeight(); + float block_h = line * 2.0f + fn_tokens::spacing::md + line; + if (description && *description) block_h += line + fn_tokens::spacing::xs; + + if (avail.y > block_h) { + ImGui::Dummy(ImVec2(1.0f, (avail.y - block_h) * 0.5f)); + } + + // Icon (centrado, 2x tamaño vía font scale) + ImGui::PushStyleColor(ImGuiCol_Text, fn_tokens::colors::text_muted); + ImGui::SetWindowFontScale(2.0f); + ImVec2 icon_size = ImGui::CalcTextSize(icon); + float icon_x = (avail.x - icon_size.x) * 0.5f; + if (icon_x < 0) icon_x = 0; + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + icon_x); + ImGui::TextUnformatted(icon); + ImGui::SetWindowFontScale(1.0f); + ImGui::PopStyleColor(); + + ImGui::Dummy(ImVec2(1.0f, fn_tokens::spacing::sm)); + + // Title (centrado) + ImVec2 title_size = ImGui::CalcTextSize(title); + float title_x = (avail.x - title_size.x) * 0.5f; + if (title_x < 0) title_x = 0; + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + title_x); + ImGui::TextUnformatted(title); + + // Description (centrado, muted) + if (description && *description) { + ImGui::Dummy(ImVec2(1.0f, fn_tokens::spacing::xs)); + ImGui::PushStyleColor(ImGuiCol_Text, fn_tokens::colors::text_muted); + ImVec2 desc_size = ImGui::CalcTextSize(description); + float desc_x = (avail.x - desc_size.x) * 0.5f; + if (desc_x < 0) desc_x = 0; + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + desc_x); + ImGui::TextUnformatted(description); + ImGui::PopStyleColor(); + } +} diff --git a/cpp/functions/core/empty_state.h b/cpp/functions/core/empty_state.h new file mode 100644 index 00000000..27a29258 --- /dev/null +++ b/cpp/functions/core/empty_state.h @@ -0,0 +1,15 @@ +#pragma once + +// Empty state — mensaje centrado para listas/tablas sin datos. +// Equivalente a de @fn_library. +// +// Renderiza: +// - Icono (emoji o texto corto) grande y atenuado +// - Título +// - Descripción opcional en texto muted +// +// Ocupa toda la altura disponible del parent (útil dentro de un panel vacío). + +void empty_state(const char* icon, + const char* title, + const char* description = nullptr); diff --git a/cpp/functions/core/empty_state.md b/cpp/functions/core/empty_state.md new file mode 100644 index 00000000..1cf0b7a6 --- /dev/null +++ b/cpp/functions/core/empty_state.md @@ -0,0 +1,59 @@ +--- +name: empty_state +kind: component +lang: cpp +domain: core +version: "1.0.0" +purity: pure +signature: "void empty_state(const char* icon, const char* title, const char* description = nullptr)" +description: "Estado vacío centrado para tablas/listas sin datos. Icono grande (muted) + título + descripción opcional. Equivalente a de @fn_library." +tags: [imgui, empty-state, ui, placeholder, design-system] +uses_functions: + - tokens_cpp_core +uses_types: [] +returns: [] +returns_optional: false +error_type: "" +imports: [imgui] +tested: false +tests: [] +test_file_path: "" +file_path: "cpp/functions/core/empty_state.cpp" +framework: imgui +params: + - name: icon + desc: "Emoji o texto corto (ej: '📭', '📊', 'No data')" + - name: title + desc: "Título principal (ej: 'No apps yet')" + - name: description + desc: "Descripción opcional con instrucciones o contexto" +output: "Dibuja el bloque centrado verticalmente en el espacio disponible del parent" +--- + +# empty_state + +Placeholder para tablas/listas sin datos. Centra icono + título + descripción en la altura disponible del contenedor actual. + +## Uso + +```cpp +#include "core/empty_state.h" + +if (apps.empty()) { + empty_state("📭", "No apps registered", + "Run 'fn sync' to fetch apps from the server"); +} else { + draw_apps_table(apps); +} +``` + +## Casos de uso típicos + +- **Tablas sin datos**: "No functions found", "No assertions yet" +- **Búsquedas sin resultados**: "No results for 'xyz'" +- **Estados iniciales**: antes del primer `fn sync` en un PC nuevo + +## Notas + +- Ocupa todo el espacio vertical del parent. Si quieres un bloque compacto, envuélvelo en un `BeginChild` con alto fijo. +- El icono se escala 2x con `SetWindowFontScale`. Los emojis Unicode funcionan si el atlas de fuente los incluye (con ImGui por defecto NO incluye emojis, usar texto corto como "📭" solo si cargaste una fuente con coverage; en su defecto usar algo como `"( empty )"`). diff --git a/cpp/functions/core/page_header.cpp b/cpp/functions/core/page_header.cpp new file mode 100644 index 00000000..3cff9904 --- /dev/null +++ b/cpp/functions/core/page_header.cpp @@ -0,0 +1,34 @@ +#include "page_header.h" +#include "tokens.h" +#include "imgui.h" + +bool page_header_begin(const char* title, const char* subtitle) { + // Título más grande con font scale + ImGui::SetWindowFontScale(1.4f); + ImGui::TextUnformatted(title); + ImGui::SetWindowFontScale(1.0f); + + // Subtítulo en muted + if (subtitle && *subtitle) { + ImGui::PushStyleColor(ImGuiCol_Text, fn_tokens::colors::text_muted); + ImGui::TextUnformatted(subtitle); + ImGui::PopStyleColor(); + } + + // Reservar la misma línea del título para que el caller ponga acciones + // a la derecha. SameLine sobre el último Text (título) — el caller usa + // SameLine con offset para alinear a la derecha si quiere. + // (En forma idiomática: ImGui::SameLine(ImGui::GetWindowWidth() - 120);) + return true; +} + +void page_header_end() { + ImGui::Dummy(ImVec2(0.0f, fn_tokens::spacing::sm)); + ImGui::Separator(); + ImGui::Dummy(ImVec2(0.0f, fn_tokens::spacing::sm)); +} + +void page_header(const char* title, const char* subtitle) { + page_header_begin(title, subtitle); + page_header_end(); +} diff --git a/cpp/functions/core/page_header.h b/cpp/functions/core/page_header.h new file mode 100644 index 00000000..c0b32714 --- /dev/null +++ b/cpp/functions/core/page_header.h @@ -0,0 +1,28 @@ +#pragma once + +// Page header — título + subtítulo opcional + separator. +// Equivalente al de @fn_library (versión simple). +// +// El patrón begin/end deja un hueco para que el caller inserte acciones +// (botones Refresh, etc.) alineadas a la derecha del título. +// +// Uso idiomático: +// +// if (page_header_begin("fn_registry Dashboard", "865 functions / 152 types")) { +// // opcional: acciones alineadas a la derecha +// if (ImGui::Button("Reload")) reload(); +// } +// page_header_end(); +// +// page_header_begin siempre devuelve true — el valor es solo estilístico +// para permitir el idiom if-block. Llamar page_header_end siempre. +// +// Forma corta sin acciones: +// +// page_header("fn_registry", "865 functions"); + +bool page_header_begin(const char* title, const char* subtitle = nullptr); +void page_header_end(); + +// Forma corta (sin acciones). +void page_header(const char* title, const char* subtitle = nullptr); diff --git a/cpp/functions/core/page_header.md b/cpp/functions/core/page_header.md new file mode 100644 index 00000000..1c3c35c5 --- /dev/null +++ b/cpp/functions/core/page_header.md @@ -0,0 +1,60 @@ +--- +name: page_header +kind: component +lang: cpp +domain: core +version: "1.0.0" +purity: pure +signature: "bool page_header_begin(const char* title, const char* subtitle = nullptr); void page_header_end(); void page_header(const char* title, const char* subtitle = nullptr)" +description: "Header de página con título, subtítulo opcional y separator final. Equivalente al de @fn_library. Patrón begin/end permite insertar acciones (botones) en el hueco entre título y separator." +tags: [imgui, header, page-header, ui, layout, design-system] +uses_functions: + - tokens_cpp_core +uses_types: [] +returns: [] +returns_optional: false +error_type: "" +imports: [imgui] +tested: false +tests: [] +test_file_path: "" +file_path: "cpp/functions/core/page_header.cpp" +framework: imgui +params: + - name: title + desc: "Título principal de la página (font scale 1.4x)" + - name: subtitle + desc: "Subtítulo opcional en texto muted" +output: "Renderiza título + subtítulo + separator. page_header_begin devuelve true siempre (idiom estilístico)." +--- + +# page_header + +Header consistente para dashboards y páginas ImGui. Título destacado, subtítulo en muted, separador abajo. Entre medias hay un hueco para que el caller inserte acciones (botones Refresh, filtros, etc.). + +## Uso + +### Con acciones + +```cpp +#include "core/page_header.h" + +page_header_begin("fn_registry Dashboard", + "865 functions · 152 types · 12 apps"); +ImGui::SameLine(ImGui::GetWindowWidth() - 120.0f); +if (ImGui::Button("Reload")) { trigger_reload(); } +page_header_end(); +``` + +### Sin acciones (forma corta) + +```cpp +page_header("Settings", "Application preferences"); +``` + +## Notas + +- El título se renderiza con `SetWindowFontScale(1.4f)` — si tu atlas incluye varias fuentes, sustituir por PushFont con una fuente más grande para mejor rendering. +- El subtítulo usa `fn_tokens::colors::text_muted`. +- El separator y el espaciado antes/después son determinísticos (spacing::sm) para consistencia entre páginas. +- Si necesitas tabs integrados en el header (como `PageHeader` en React con `tabs={...}`), componer con `tab_container` debajo del `page_header_end`. diff --git a/cpp/functions/core/tokens.cpp b/cpp/functions/core/tokens.cpp new file mode 100644 index 00000000..8f7aa279 --- /dev/null +++ b/cpp/functions/core/tokens.cpp @@ -0,0 +1,50 @@ +#include "tokens.h" + +namespace fn_tokens { + +void apply_dark_theme() { + ImGuiStyle& s = ImGui::GetStyle(); + + // Colors + s.Colors[ImGuiCol_WindowBg] = colors::bg; + s.Colors[ImGuiCol_ChildBg] = colors::surface; + s.Colors[ImGuiCol_PopupBg] = colors::surface; + s.Colors[ImGuiCol_FrameBg] = colors::surface; + s.Colors[ImGuiCol_FrameBgHovered] = colors::surface_hover; + s.Colors[ImGuiCol_FrameBgActive] = colors::surface_hover; + s.Colors[ImGuiCol_Border] = colors::border; + s.Colors[ImGuiCol_BorderShadow] = ImVec4(0, 0, 0, 0); + s.Colors[ImGuiCol_Text] = colors::text; + s.Colors[ImGuiCol_TextDisabled] = colors::text_dim; + s.Colors[ImGuiCol_Button] = colors::primary; + s.Colors[ImGuiCol_ButtonHovered] = colors::primary_hover; + s.Colors[ImGuiCol_ButtonActive] = colors::primary; + s.Colors[ImGuiCol_Header] = colors::surface_hover; + s.Colors[ImGuiCol_HeaderHovered] = colors::primary; + s.Colors[ImGuiCol_HeaderActive] = colors::primary_hover; + s.Colors[ImGuiCol_Tab] = colors::surface; + s.Colors[ImGuiCol_TabHovered] = colors::primary_hover; + s.Colors[ImGuiCol_TabActive] = colors::primary; + s.Colors[ImGuiCol_Separator] = colors::border; + s.Colors[ImGuiCol_TableHeaderBg] = colors::surface_hover; + s.Colors[ImGuiCol_TableBorderLight]= colors::border; + s.Colors[ImGuiCol_TableBorderStrong]= colors::border; + + // Radius + s.FrameRounding = radius::sm; + s.ChildRounding = radius::md; + s.WindowRounding = radius::md; + s.PopupRounding = radius::md; + s.TabRounding = radius::sm; + s.GrabRounding = radius::sm; + s.ScrollbarRounding = radius::md; + + // Spacing + s.ItemSpacing = ImVec2(spacing::sm, spacing::sm); + s.ItemInnerSpacing = ImVec2(spacing::xs, spacing::xs); + s.FramePadding = ImVec2(spacing::sm, spacing::xs + 2.0f); + s.WindowPadding = ImVec2(spacing::md, spacing::md); + s.CellPadding = ImVec2(spacing::sm, spacing::xs); +} + +} // namespace fn_tokens diff --git a/cpp/functions/core/tokens.h b/cpp/functions/core/tokens.h new file mode 100644 index 00000000..29d77472 --- /dev/null +++ b/cpp/functions/core/tokens.h @@ -0,0 +1,64 @@ +#pragma once +#include "imgui.h" + +// Design tokens — colores, spacing, radius, font-size. +// Inspirados en el DESIGN_SYSTEM de @fn_library (Mantine v9 dark + indigo primary). +// Reemplaza hardcode disperso de ImVec4(...) por constantes semánticas. + +namespace fn_tokens { + +namespace colors { + // Primary (indigo-inspired, Mantine indigo.6) + constexpr ImVec4 primary {0.25f, 0.37f, 0.85f, 1.0f}; + constexpr ImVec4 primary_hover {0.30f, 0.42f, 0.90f, 1.0f}; + + // Semantic + constexpr ImVec4 success {0.13f, 0.70f, 0.42f, 1.0f}; + constexpr ImVec4 warning {0.95f, 0.60f, 0.20f, 1.0f}; + constexpr ImVec4 error {0.87f, 0.26f, 0.30f, 1.0f}; + constexpr ImVec4 info {0.22f, 0.55f, 0.95f, 1.0f}; + + // Background (dark by default — matches DESIGN_SYSTEM.md §2 & §8) + constexpr ImVec4 bg {0.08f, 0.08f, 0.10f, 1.0f}; // window bg + constexpr ImVec4 surface {0.12f, 0.12f, 0.15f, 1.0f}; // panels/cards + constexpr ImVec4 surface_hover {0.16f, 0.16f, 0.20f, 1.0f}; + + // Text + constexpr ImVec4 text {0.95f, 0.95f, 0.95f, 1.0f}; + constexpr ImVec4 text_muted {0.60f, 0.60f, 0.65f, 1.0f}; + constexpr ImVec4 text_dim {0.40f, 0.40f, 0.45f, 1.0f}; + + // Border + constexpr ImVec4 border {0.20f, 0.20f, 0.25f, 1.0f}; +} + +namespace spacing { + constexpr float xs = 4.0f; + constexpr float sm = 8.0f; + constexpr float md = 12.0f; + constexpr float lg = 16.0f; + constexpr float xl = 24.0f; +} + +namespace radius { + constexpr float none = 0.0f; + constexpr float sm = 3.0f; + constexpr float md = 5.0f; + constexpr float lg = 8.0f; + constexpr float xl = 12.0f; +} + +namespace font_size { + constexpr float xs = 10.0f; + constexpr float sm = 12.0f; + constexpr float md = 14.0f; // default + constexpr float lg = 18.0f; + constexpr float xl = 24.0f; + constexpr float xxl = 32.0f; +} + +// Aplica los tokens al ImGuiStyle global. Llamar una vez al arrancar la app, +// después de ImGui::CreateContext() y antes del primer frame. +void apply_dark_theme(); + +} // namespace fn_tokens diff --git a/cpp/functions/core/tokens.md b/cpp/functions/core/tokens.md new file mode 100644 index 00000000..d40f357b --- /dev/null +++ b/cpp/functions/core/tokens.md @@ -0,0 +1,58 @@ +--- +name: tokens +kind: component +lang: cpp +domain: core +version: "1.0.0" +purity: pure +signature: "namespace fn_tokens { namespace colors/spacing/radius/font_size { constexpr ... }; void apply_dark_theme(); }" +description: "Design tokens (colors, spacing, radius, font_size) para dashboards ImGui. Inspirados en @fn_library (Mantine v9) — dark theme con indigo primary. Reemplaza hardcode de ImVec4(...) por constantes semánticas." +tags: [imgui, theme, tokens, colors, spacing, radius, dark, design-system] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "" +imports: [imgui] +tested: false +tests: [] +test_file_path: "" +file_path: "cpp/functions/core/tokens.cpp" +framework: imgui +params: [] +output: "Tokens constexpr accesibles como fn_tokens::colors::*, spacing::*, radius::*, font_size::*. apply_dark_theme() aplica los tokens al ImGuiStyle global." +--- + +# tokens + +Design tokens para todos los dashboards ImGui del registry. Traducción del DESIGN_SYSTEM.md de `@fn_library` al mundo C++/ImGui: mismos principios (dark-first, paleta semántica, escala consistente de espaciado y radios) pero con `constexpr ImVec4` en lugar de CSS variables. + +## Namespaces + +| Namespace | Valores | +|-----------|---------| +| `fn_tokens::colors` | `primary`, `primary_hover`, `success`, `warning`, `error`, `info`, `bg`, `surface`, `surface_hover`, `text`, `text_muted`, `text_dim`, `border` | +| `fn_tokens::spacing` | `xs=4`, `sm=8`, `md=12`, `lg=16`, `xl=24` (px) | +| `fn_tokens::radius` | `none=0`, `sm=3`, `md=5`, `lg=8`, `xl=12` (px) | +| `fn_tokens::font_size` | `xs=10`, `sm=12`, `md=14`, `lg=18`, `xl=24`, `xxl=32` (px) | + +## Uso + +```cpp +#include "core/tokens.h" + +// Al arrancar la app (una vez, después de ImGui::CreateContext) +fn_tokens::apply_dark_theme(); + +// En componentes +ImGui::PushStyleColor(ImGuiCol_Text, fn_tokens::colors::text_muted); +ImGui::Dummy(ImVec2(0, fn_tokens::spacing::md)); +ImGui::PopStyleColor(); +``` + +## Notas + +- **Dark by default** como en el DESIGN_SYSTEM (§2, §8). Si algún día queremos light, se añade `apply_light_theme()`. +- Los valores semánticos (success/warning/error/info) se usan en `badge`, `kpi_card` para deltas, y gráficos de estado. +- **No duplicar** estas constantes en componentes — siempre importar de aquí. Si se detecta un `ImVec4` hardcodeado en un componente del registry es candidato a migrar. +- Compatible con `plot_theme_cpp_core` (para ImPlot charts) — los colors del palette se pueden derivar de estos tokens si se quiere coherencia total. diff --git a/docs/diary/2026-04-24.md b/docs/diary/2026-04-24.md index 2211e8aa..7ef99f01 100644 --- a/docs/diary/2026-04-24.md +++ b/docs/diary/2026-04-24.md @@ -61,3 +61,19 @@ El `.exe` del escritorio abría una ventana de consola negra al lanzarlo (mingw - Hecho: `WIN32_EXECUTABLE TRUE` en el `CMakeLists.txt` del target `registry_dashboard` → mingw pasa `-mwindows` (subsystem:windows). - Hecho: rebuild Windows + copy al Desktop reemplazando el anterior (hash `e361387f...`). - Commit en subrepo del dashboard. + +## 14:50 — Design system C++ (fases 1 y 2) + +Trasladar principios del DESIGN_SYSTEM.md de `@fn_library` (Mantine/React) al dashboard ImGui sin añadir deps externas. + +- Hecho: `cpp/functions/core/tokens.{h,cpp,md}` — namespace `fn_tokens` con `colors`, `spacing`, `radius`, `font_size` (constexpr) + `apply_dark_theme()` que aplica los tokens al `ImGuiStyle` global. Paleta dark + indigo primary (Mantine-inspired). +- Hecho: `cpp/functions/core/badge.{h,cpp,md}` — etiqueta inline con 6 variantes (Default/Success/Warning/Error/Info/Outline). Equivalente a `` de `@fn_library`. +- Hecho: `cpp/functions/core/empty_state.{h,cpp,md}` — placeholder centrado para tablas vacías. +- Hecho: `cpp/functions/core/page_header.{h,cpp,md}` — header con título/subtítulo + hueco para acciones + separator (patrón begin/end). +- Hecho: migrado `views.cpp` para usar `page_header_begin/end` en lugar de `Text + Separator + Button` manual; `empty_state` en las 4 tablas cuando están vacías; `apply_dark_theme` al primer frame. +- Hecho: `CMakeLists.txt` del dashboard añade los 4 nuevos .cpp. +- Hecho: build Linux (OK) + Windows (OK, hash `772b0aef...`), copiado a Desktop. +- Hecho: `fn index` pasa de 865 → 869 funciones. +- Fix yaml en `tokens.md`: `params:` con item `- name: -` rompía el parser YAML (el `-` colisionaba con el array marker). Cambiado a `params: []`. + +**Scope respetado (KISS)**: sólo fases 1+2 del plan propuesto — no se crearon card ni line_chart ni app_shell ni sistema de toast. Se mantiene `dashboard_panel` existente en vez de duplicar con un `card`.