f61d2834e8
Documenta el sistema de design tokens del registry C++: - Identidad visual unica (Mantine v9 dark + indigo) compartida entre apps - Como se aplican los tokens via fn::run_app - Convenciones de uso (cuando usar tokens.colors.surface vs accent, etc.) Es la fuente de verdad cuando se crean nuevas primitivas o apps fn_ui.
452 lines
19 KiB
Markdown
452 lines
19 KiB
Markdown
# fn_registry C++ — Design System
|
|
|
|
> **Fuente de verdad de la identidad visual para todas las apps C++ del registry.**
|
|
> Espejo directo de `frontend/DESIGN_SYSTEM.md` en mundo ImGui / OpenGL.
|
|
|
|
---
|
|
|
|
## 1. Principio
|
|
|
|
Todas las apps C++ del registry comparten **la misma identidad visual que las apps web**: Mantine v9 dark + indigo primary + Geist typography + `radius.md = 8` por defecto. No existen apps C++ con tema propio — si una app quiere diferenciarse, lo hace eligiendo colores semanticos (success, warning, primary...), nunca redefiniendo la paleta base.
|
|
|
|
El equivalente C++ de `FnMantineProvider` + `createTheme(...)` es **una sola llamada**:
|
|
|
|
```cpp
|
|
fn::run_app(config, render); // AppConfig::theme = ThemeMode::FnDark por defecto
|
|
```
|
|
|
|
Dentro, `app_base` invoca `fn_tokens::apply_dark_theme()` tras crear el contexto ImGui. Equivale exactamente a:
|
|
|
|
```tsx
|
|
<FnMantineProvider theme={createTheme({
|
|
primaryColor: 'indigo',
|
|
primaryShade: { light: 6, dark: 4 },
|
|
defaultRadius: 'md',
|
|
})} defaultColorScheme="dark">
|
|
```
|
|
|
|
---
|
|
|
|
## 2. Stack
|
|
|
|
| Capa | Libreria | Notas |
|
|
|------|----------|-------|
|
|
| UI base | **Dear ImGui** (vendored) | immediate mode, docking branch |
|
|
| Charts | **ImPlot** (vendored) | line/bar/pie/histogram/heatmap/sparkline |
|
|
| Node editor | `imgui-node-editor` (vendored) | solo para `shader_canvas` / DAGs |
|
|
| Gráficos gfx | OpenGL 3.3 core via GLFW | `gl_shader`, `gl_framebuffer`, `fullscreen_quad` |
|
|
| Profiling | Tracy (opt-in con `TRACY_ENABLE=ON`) | `tracy_zone_cpp_core` |
|
|
| Tokens | `cpp/functions/core/tokens.{h,cpp}` | equivalente a `@fn_library` |
|
|
| Componentes | `cpp/functions/core/*`, `viz/*`, `gfx/*` | wrappers que leen `fn_tokens` |
|
|
| Framework | `cpp/framework/app_base.{h,cpp}` | bootstrap unico de GLFW + ImGui + tema |
|
|
| Build | CMake 3.16+ | un `add_subdirectory` por app |
|
|
|
|
### Deny-list (no usar en apps C++)
|
|
|
|
- No `ImGui::StyleColorsDark/Light/Classic()` en el main de una app — el tema lo fija `app_base`
|
|
- No `ImVec4(...)` hardcoded en widgets — usar `fn_tokens::colors::*`
|
|
- No pixeles magicos para spacing/radius — usar `fn_tokens::spacing::*` / `radius::*`
|
|
- No ICU / gettext — textos directos, UTF-8 en el fuente
|
|
- No libs de UI ajenas (Qt, Nuklear, Gtk, etc.)
|
|
- No CSS, QSS ni recursos externos de estilo
|
|
|
|
---
|
|
|
|
## 3. `AppConfig` y modos de tema
|
|
|
|
```cpp
|
|
#include "app_base.h"
|
|
|
|
int main() {
|
|
fn::AppConfig cfg;
|
|
cfg.title = "my_app";
|
|
cfg.width = 1400;
|
|
cfg.height = 900;
|
|
cfg.viewports = true; // multi-viewport OK, el tema se mantiene
|
|
// cfg.theme = fn::ThemeMode::FnDark; // ya es el default
|
|
return fn::run_app(cfg, render);
|
|
}
|
|
```
|
|
|
|
| `ThemeMode` | Uso |
|
|
|---|---|
|
|
| `FnDark` (**default**) | Identidad del registry — Mantine v9 dark + indigo |
|
|
| `ImGuiDark` | Dark de stock (gris, rounding 0). Solo si estas depurando estilos |
|
|
| `ImGuiLight` | Light de stock. Para capturas o documentacion |
|
|
| `None` | No toca `ImGuiStyle`. La app se encarga (evitar salvo caso excepcional) |
|
|
|
|
**Regla:** nunca cambies `ThemeMode` salvo que tengas justificacion; si tu app necesita variaciones, expon-las como props semanticos (`kind: "danger"`, `variant: "subtle"`) en tus widgets, no como tema alternativo.
|
|
|
|
---
|
|
|
|
## 4. Tokens — `fn_tokens`
|
|
|
|
Todos en `cpp/functions/core/tokens.h`. Namespaces:
|
|
|
|
### 4.1 Colors — Mantine v9 dark + indigo
|
|
|
|
| Token | Mantine | Hex | Uso |
|
|
|---|---|---|---|
|
|
| `colors::primary` | indigo.6 | `#4C6EF5` | Botones, tabs activos, selecciones |
|
|
| `colors::primary_hover` | indigo.5 | `#5C7CFA` | Hover de primary |
|
|
| `colors::primary_light` | indigo.4 | `#748FFC` | Nav highlight, checkmarks, separator activo |
|
|
| `colors::primary_active` | indigo.7 | `#4263EB` | Pulsado |
|
|
| `colors::success` | green.6 | `#40C057` | Badges / toasts de exito |
|
|
| `colors::warning` | yellow.6 | `#FAB005` | Avisos |
|
|
| `colors::error` | red.6 | `#FA5252` | Errores, destructive |
|
|
| `colors::info` | blue.6 | `#228BE6` | Info, enlaces |
|
|
| `colors::bg` | dark.7 | `#1A1B1E` | Window bg (= Mantine body) |
|
|
| `colors::surface` | dark.6 | `#25262B` | Paper, Card, FrameBg |
|
|
| `colors::surface_hover` | dark.5 | `#2C2E33` | Hover de surface |
|
|
| `colors::surface_active` | dark.4 | `#373A40` | Pulsado de surface |
|
|
| `colors::border` | dark.4 | `#373A40` | Borders estandar |
|
|
| `colors::border_strong` | dark.3 | `#5C5F66` | Scrollbar grab active |
|
|
| `colors::text` | dark.0 | `#C1C2C5` | Texto primario |
|
|
| `colors::text_muted` | dark.2 | `#909296` | Subtitulos, axis labels |
|
|
| `colors::text_dim` | dark.3 | `#5C5F66` | Disabled |
|
|
|
|
### 4.2 Radius — `defaultRadius: 'md'`
|
|
|
|
| Token | Valor | Equivalente Mantine |
|
|
|---|---|---|
|
|
| `radius::none` | 0 | — |
|
|
| `radius::xs` | 2 | `--mantine-radius-xs` |
|
|
| `radius::sm` | 4 | `--mantine-radius-sm` |
|
|
| `radius::md` | **8** | `--mantine-radius-md` (**default**) |
|
|
| `radius::lg` | 12 | `--mantine-radius-lg` (adaptado, Mantine usa 16) |
|
|
| `radius::xl` | 16 | `--mantine-radius-xl` (adaptado, Mantine usa 32) |
|
|
|
|
ImGui aplica `md` en `WindowRounding`, `ChildRounding`, `PopupRounding`, `FrameRounding`, `ScrollbarRounding`. `sm` en `GrabRounding`, `TabRounding`.
|
|
|
|
### 4.3 Spacing — densificado para ImGui
|
|
|
|
Mantine usa 10/12/16/20/32 px para CSS; ImGui vive en densidad mayor (TUI-ish), asi que el mapeo se densifica:
|
|
|
|
| Token | ImGui | Mantine CSS |
|
|
|---|---|---|
|
|
| `spacing::xs` | 4 | 10 |
|
|
| `spacing::sm` | 8 | 12 |
|
|
| `spacing::md` | 12 | 16 |
|
|
| `spacing::lg` | 16 | 20 |
|
|
| `spacing::xl` | 24 | 32 |
|
|
|
|
Se usan en `WindowPadding`, `FramePadding`, `ItemSpacing`, `CellPadding`, `IndentSpacing`.
|
|
|
|
### 4.4 Font size
|
|
|
|
`font_size::xs|sm|md|lg|xl|xxl` = `10|12|14|18|24|32`. El default (14) coincide con `--mantine-font-size-sm` (tiempos mas compactos que el body web de 16).
|
|
|
|
---
|
|
|
|
## 5. Qué aplica `apply_dark_theme()`
|
|
|
|
Se invoca una vez desde `app_base` (o manualmente si usas `ThemeMode::None`). Configura:
|
|
|
|
- **Todos los `ImGuiCol_*`**: WindowBg, ChildBg, PopupBg, FrameBg (+Hovered/Active), Button (+Hovered/Active), Header (+Hovered/Active), Tab (+Hovered/Active/Unfocused), Separator (+Hovered/Active), Border, Text (+Disabled/Selected), CheckMark, SliderGrab (+Active), ResizeGrip (+Hovered/Active), Scrollbar (+Grab +GrabHovered +GrabActive), Table (Header +BorderLight +BorderStrong +RowBg +RowBgAlt), Docking (Preview +EmptyBg), PlotLines/PlotHistogram, DragDropTarget, Nav (Highlight +WindowingHighlight +WindowingDimBg), ModalWindowDimBg, TextSelectedBg, MenuBarBg, TitleBg (+Active +Collapsed)
|
|
- **Rounding**: Window, Child, Popup, Frame, Grab, Scrollbar, Tab
|
|
- **Padding/spacing**: WindowPadding, FramePadding, CellPadding, ItemSpacing, ItemInnerSpacing, IndentSpacing, ScrollbarSize, GrabMinSize
|
|
- **Borders**: WindowBorderSize/ChildBorderSize/PopupBorderSize = 1, FrameBorderSize/TabBorderSize = 0 (Mantine no pinta border en frames)
|
|
- **ImPlot** si esta linkado: FrameBg, PlotBg, PlotBorder, LegendBg/Border/Text, TitleText, InlayText, AxisText/Grid/Tick/Bg, Selection, Crosshairs + paddings
|
|
|
|
### Qué NO toca (capacidades ImGui intactas)
|
|
|
|
- Ni `io.ConfigFlags` (docking, viewports, keyboard nav, multi-viewport): los gestiona `AppConfig`
|
|
- Ni backends (GLFW/OpenGL init): los gestiona `app_base`
|
|
- Ni `ImGui::CreateContext` / `DestroyContext`
|
|
- Ni fuentes (si quieres Geist o una font custom, llama `io.Fonts->AddFontFromFileTTF(...)` antes de `apply_dark_theme`; el tema no pisa `io.Fonts`)
|
|
- Ni `ImPlot` colormaps/markers (solo colores de chrome)
|
|
|
|
---
|
|
|
|
## 6. Equivalencias `@fn_library` ↔ C++
|
|
|
|
| Web (`@fn_library`) | C++ (`cpp/functions/...`) | Notas |
|
|
|---|---|---|
|
|
| `FnMantineProvider` + `createTheme` | `fn::run_app` con `ThemeMode::FnDark` | Punto de entrada unico |
|
|
| `Button` | `core/button.h` — `fn::core::button(label, kind, size)` | `kind: Primary\|Secondary\|Subtle\|Danger`, `size: Sm\|Md\|Lg` |
|
|
| `FnActionIcon` | `core/icon_button.h` | 28x28 + tooltip |
|
|
| `Badge` | `core/badge.h` | `Default\|Success\|Warning\|Error\|Info\|Outline` |
|
|
| `Input` / `TextInput` | `core/text_input.h` | label muted arriba |
|
|
| `Select` | `core/select.h` | |
|
|
| `Dialog` | `core/modal_dialog.h` | patron begin/end |
|
|
| `Toast` / `notifications.show` | `core/toast.h` | stack bottom-right con fade-out |
|
|
| `PageHeader` | `core/page_header.h` | begin/end para meter acciones |
|
|
| `EmptyState` | `core/empty_state.h` | icono grande + titulo + desc |
|
|
| `Sheet` / `Drawer` | `core/sidebar.h` | panel lateral colapsable |
|
|
| `Tabs` | `core/tab_container.h` | |
|
|
| `AppShell` | `core/docking_layout.h` | preset de docking space |
|
|
| `Card` / `Paper` | `core/dashboard_panel.h` | surface + border + radius md |
|
|
| `SimpleGrid` | `core/dashboard_grid.h` | N columnas uniformes |
|
|
| `FnTree` / nav anidada | `core/tree_view.h` | |
|
|
| `KPICard` | `viz/kpi_card.h` | valor + delta + sparkline |
|
|
| `Sparkline` | `viz/sparkline.h` | mini chart inline |
|
|
| `LineChart` | `viz/line_plot.h` | |
|
|
| `BarChart` | `viz/bar_chart.h` | |
|
|
| `AreaChart` | `viz/line_plot.h` + flags ImPlot | |
|
|
| `PieChart` | `viz/pie_chart.h` | pie/donut |
|
|
| `DataTable` | `viz/table_view.h` | sorting + scroll |
|
|
| `GraphContainer` | `viz/graph_viewport.h` + `viz/graph_renderer.h` | sigma → ImGui GPU |
|
|
| `Tabler icons` | Glyphs UTF-8 en `icon_button` | evitar bundles de icon atlas en apps pequeñas |
|
|
|
|
---
|
|
|
|
## 7. Plantilla `main.cpp` para una app nueva
|
|
|
|
```cpp
|
|
#include "app_base.h"
|
|
#include "imgui.h"
|
|
#include "core/tokens.h"
|
|
#include "core/fullscreen_window.h"
|
|
#include "core/page_header.h"
|
|
#include "core/dashboard_grid.h"
|
|
#include "viz/kpi_card.h"
|
|
|
|
static void render() {
|
|
fullscreen_window_begin("##my_app");
|
|
|
|
page_header_begin("My App", "subtitle opcional");
|
|
page_header_end();
|
|
|
|
ImGui::Dummy(ImVec2(0, fn_tokens::spacing::md));
|
|
|
|
dashboard_grid_begin(4);
|
|
kpi_card("Total", "1,284", /* delta */ 12.0f, /* positive */ true);
|
|
kpi_card("OK", "97.3%", 0.4f, true);
|
|
kpi_card("P95", "842ms", -8.0f, true);
|
|
kpi_card("Err", "34", 5.0f, false);
|
|
dashboard_grid_end();
|
|
|
|
fullscreen_window_end();
|
|
}
|
|
|
|
int main() {
|
|
fn::AppConfig cfg;
|
|
cfg.title = "my_app";
|
|
cfg.width = 1400;
|
|
cfg.height = 900;
|
|
return fn::run_app(cfg, render);
|
|
}
|
|
```
|
|
|
|
CMakeLists minimo de la app:
|
|
|
|
```cmake
|
|
add_imgui_app(my_app
|
|
main.cpp
|
|
${CMAKE_SOURCE_DIR}/functions/core/fullscreen_window.cpp
|
|
${CMAKE_SOURCE_DIR}/functions/core/page_header.cpp
|
|
${CMAKE_SOURCE_DIR}/functions/core/dashboard_grid.cpp
|
|
${CMAKE_SOURCE_DIR}/functions/viz/kpi_card.cpp
|
|
)
|
|
```
|
|
|
|
`fn_framework` ya incluye `tokens.cpp`, no hace falta añadirlo.
|
|
|
|
---
|
|
|
|
## 8. Anti-patrones (rechazar en review)
|
|
|
|
```cpp
|
|
// ❌ Hardcode de paleta
|
|
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.3f, 0.4f, 0.9f, 1.0f));
|
|
// ✅
|
|
ImGui::PushStyleColor(ImGuiCol_Button, fn_tokens::colors::primary);
|
|
|
|
// ❌ StyleColorsDark en la app — sobreescribe la identidad
|
|
ImGui::StyleColorsDark();
|
|
|
|
// ❌ apply_dark_theme en cada frame (no es caro pero es ruido)
|
|
if (!theme_applied) { fn_tokens::apply_dark_theme(); ... }
|
|
// ✅ run_app lo hace una vez al inicio
|
|
|
|
// ❌ Spacing magico
|
|
ImGui::Dummy(ImVec2(0, 13));
|
|
// ✅
|
|
ImGui::Dummy(ImVec2(0, fn_tokens::spacing::md));
|
|
|
|
// ❌ Mezclar Qt, Nuklear u otra lib de UI en apps/
|
|
// ✅ ImGui + ImPlot + los wrappers de cpp/functions/
|
|
|
|
// ❌ Meter un sub-tema para "destacar" una pantalla
|
|
cfg.theme = fn::ThemeMode::ImGuiLight; // romperia consistencia
|
|
// ✅ usar tokens semanticos (success/warning/...)
|
|
```
|
|
|
|
---
|
|
|
|
## 9. Checklist para una app C++ nueva
|
|
|
|
- [ ] `main.cpp` llama a `fn::run_app(...)` con `ThemeMode::FnDark` (default) — **no** `ImGui::StyleColorsDark`
|
|
- [ ] Cero `ImVec4(0.x, 0.y, ..., 1.0f)` — solo `fn_tokens::colors::*`
|
|
- [ ] Cero floats magicos en padding/spacing/rounding — solo `fn_tokens::spacing::*` / `radius::*`
|
|
- [ ] Componentes comunes desde `cpp/functions/core/`, `viz/`, `gfx/`
|
|
- [ ] Si creas un widget reutilizable, extraelo a `cpp/functions/<domain>/{name}.{h,cpp,md}` y `./fn index`
|
|
- [ ] `app.md` con frontmatter (`name`, `description`, `tags`, `lang: cpp`)
|
|
- [ ] CMakeLists usa `add_imgui_app(...)` — no `add_executable` directo
|
|
- [ ] Multi-viewport (`cfg.viewports = true`) si la app espera ser undock-able; el tema se adapta solo
|
|
- [ ] Tracy opt-in solo si interesa profiling (`-DTRACY_ENABLE=ON`)
|
|
|
|
---
|
|
|
|
## 10. Cambios respecto al estado previo (2026-04)
|
|
|
|
- Tokens alineados a valores **exactos** de Mantine v9 dark + indigo (antes aproximaban a ojo)
|
|
- `radius::md` ahora es 8 (antes 5) para casar con `defaultRadius: 'md'`
|
|
- `app_base::AppConfig` añade campo `theme` (default `FnDark`) y aplica el tema en `run_app`
|
|
- `fn_framework` incluye `tokens.cpp` — cualquier app que enlace el framework obtiene la identidad por default
|
|
- `fn_tokens::apply_dark_theme()` ahora estiliza tambien ImPlot si esta enlazado (colores de plot bg, axis, legend, crosshairs) y añade entradas que faltaban (docking preview, nav highlight, modal dim, scrollbars)
|
|
- `registry_dashboard` y `shaders_lab` ahora comparten look 100% sin cambios en sus propios mains
|
|
|
|
---
|
|
|
|
## 11. Iconos — Tabler (auto-cargado)
|
|
|
|
Espejo del frontend web (`@tabler/icons-react`). Set unico para todas las apps C++ del registry. **Sin glyph atlas custom por app, sin emojis Unicode arbitrarios.**
|
|
|
|
### Regla
|
|
|
|
Cualquier glyph en boton, menu, toolbar, tree o status line **DEBE** salir de los macros `TI_*` de `cpp/functions/core/icons_tabler.h`. No se permite:
|
|
|
|
- `"\xe2\x9a\x99"` o cualquier UTF-8 hex inline → usar `TI_SETTINGS`.
|
|
- Emojis Unicode (✓, ⚠, ❌, ⚙) → muchos no estan en el atlas y salen como `?` o cuadritos.
|
|
- `"?"` / `"!"` ASCII como sustituto → usar `TI_HELP`, `TI_ALERT_CIRCLE`.
|
|
|
|
### Como funciona
|
|
|
|
`fn::run_app` (en `framework/app_base.cpp`) llama a `fn_ui::load_default_fonts()` justo despues de crear el contexto ImGui. Carga **Karla-Regular** (texto vectorial, 13 px, vendoreado por ImGui en `vendor/imgui/misc/fonts/`) + mergea `tabler-icons.ttf` (5093 glyphs, U+E000..U+FCFF) al mismo tamaño en el mismo `ImFont`. Texto e iconos comparten line-height. A partir de ahi:
|
|
|
|
```cpp
|
|
#include "core/icons_tabler.h"
|
|
|
|
button(TI_PLUS " New", V::Primary);
|
|
button(TI_DEVICE_FLOPPY " Save", V::Secondary);
|
|
icon_button("##del", TI_TRASH, "Delete");
|
|
icon_button("##cfg", TI_SETTINGS, "Settings");
|
|
```
|
|
|
|
Texto y glyph se mezclan en la misma string porque comparten atlas.
|
|
|
|
### Catalogo
|
|
|
|
`cpp/functions/core/icons_tabler.h` (generado por `cpp/vendor/tabler-icons/gen_header.py` desde el CSS oficial v3.41.1). Buscar un icono:
|
|
|
|
```bash
|
|
grep -i "TI_TRASH\|TI_DELETE" cpp/functions/core/icons_tabler.h
|
|
# o explorar en https://tabler.io/icons (mismo set)
|
|
```
|
|
|
|
Nombres: `TI_<NAME_UPPER_SNAKE>` derivado del nombre kebab-case del icono Tabler (`tabler-trash` → `TI_TRASH`, `tabler-device-floppy` → `TI_DEVICE_FLOPPY`).
|
|
|
|
### Path resolution en runtime
|
|
|
|
`icon_font.cpp` busca `Karla-Regular.ttf` y `tabler-icons.ttf` con el mismo orden (primer match gana):
|
|
|
|
1. `./<filename>` (cwd / junto al exe — `add_imgui_app` copia ambas post-build)
|
|
2. `./assets/<filename>`
|
|
3. `$FN_ASSETS_DIR/<filename>`
|
|
4. `${FN_CPP_ROOT}/<repo_subpath>` (compile-time define):
|
|
- Karla → `vendor/imgui/misc/fonts/Karla-Regular.ttf`
|
|
- Tabler → `vendor/tabler-icons/tabler-icons.ttf`
|
|
|
|
Fallbacks: si falta Karla, ImGui usa ProggyClean (bitmap, default historico). Si falta Tabler, los `TI_*` salen como cuadritos. Indicadores programaticos: `fn_ui::text_font_loaded()`, `fn_ui::tabler_font_loaded()`.
|
|
|
|
### Upgrade de version
|
|
|
|
```bash
|
|
cd cpp/vendor/tabler-icons
|
|
curl -sL -o tabler-icons.ttf "https://cdn.jsdelivr.net/npm/@tabler/icons-webfont@<NEW_VERSION>/dist/fonts/tabler-icons.ttf"
|
|
curl -sL -o tabler-icons.css "https://cdn.jsdelivr.net/npm/@tabler/icons-webfont@<NEW_VERSION>/dist/tabler-icons.css"
|
|
python3 gen_header.py # regenera ../../functions/core/icons_tabler.h
|
|
```
|
|
|
|
Bump tambien `TI_FONT_VERSION` queda actualizado automaticamente.
|
|
|
|
### Anti-patrones (rechazar en review)
|
|
|
|
```cpp
|
|
// ❌ Hex UTF-8 inline — pre-Tabler, no renderiza en el atlas actual
|
|
icon_button("##rl", "\xe2\x86\xbb", "Reload");
|
|
// ✅
|
|
icon_button("##rl", TI_REFRESH, "Reload");
|
|
|
|
// ❌ Emoji
|
|
button("✓ OK", V::Primary);
|
|
// ✅
|
|
button(TI_CHECK " OK", V::Primary);
|
|
|
|
// ❌ Cargar otra fuente de iconos en el app (FontAwesome, Material...)
|
|
io.Fonts->AddFontFromFileTTF("fa-solid.ttf", ...);
|
|
// ✅ usar TI_* — ya esta cargado por fn::run_app
|
|
|
|
// ❌ Bypass del path resolver hardcodeando una ruta absoluta
|
|
io.Fonts->AddFontFromFileTTF("/home/lucas/.../tabler.ttf", ...);
|
|
// ✅ confiar en load_default_fonts() / FN_ASSETS_DIR
|
|
```
|
|
|
|
### Checklist (añadir a la del punto 9)
|
|
|
|
- [ ] Cero `"\x..\x.."` en codigo de UI — solo `TI_*` de `core/icons_tabler.h`
|
|
- [ ] Cero emojis Unicode en strings de boton / menu / toolbar
|
|
- [ ] Si el icono que necesito no existe en Tabler, replantear el flujo (en general hay equivalente)
|
|
- [ ] No cargar fuentes adicionales sin justificacion (el atlas merge es delicado)
|
|
|
|
---
|
|
|
|
## 12. Settings — ventana flotante + persistencia
|
|
|
|
Toda app C++ del registry hereda de `fn::run_app` un menu **Settings...** en la `MainMenuBar` (junto a `View` / `Layouts`) que abre una ventana flotante con:
|
|
|
|
- **Display** → toggle `Show FPS overlay` (overlay top-right con fps + ms)
|
|
- **Typography** → combo de fuente (Karla / Roboto / DroidSans / Cousine / ProggyClean) + combo de tamaño (12/13/14/15/16/18/20 px) + slider libre 10..32 px
|
|
- **Secciones extra registradas por la app** (si las hay)
|
|
|
|
### Persistencia — `app_settings.ini`
|
|
|
|
Junto al ejecutable. Auto-save al cambiar (sin botones). Cada app tiene su propio `.ini` porque cada exe corre desde su carpeta:
|
|
|
|
```ini
|
|
# fn_registry app_settings.ini — autogenerado, editable
|
|
show_fps = 1
|
|
font_id = 0 # 0=Karla 1=Roboto 2=DroidSans 3=Cousine 4=ProggyClean
|
|
font_size_px = 15.0
|
|
```
|
|
|
|
### Extender desde una app
|
|
|
|
```cpp
|
|
#include "core/app_settings.h"
|
|
|
|
int main() {
|
|
fn_ui::settings_window_add_section("shader_compiler", "Shader compiler", []{
|
|
ImGui::Checkbox("Auto-compile on save", &g_auto_compile);
|
|
ImGui::SliderInt("Debounce (ms)", &g_debounce_ms, 50, 2000);
|
|
});
|
|
return fn::run_app({...}, render);
|
|
}
|
|
```
|
|
|
|
La seccion aparece debajo de las core, dentro de un `CollapsingHeader` abierto por defecto. La app es responsable de persistir su estado (su SQLite, env, archivo aparte) — el `.ini` core solo guarda `show_fps`/`font_id`/`font_size_px`.
|
|
|
|
### Cambio de fuente en runtime
|
|
|
|
`run_app` consume `settings_consume_font_dirty()` al inicio de cada frame: si hay cambio, ejecuta `io.Fonts->Clear()` + `load_fonts_from_settings()`. ImGui 1.92+ refresca la GPU texture automaticamente via `ImGui_ImplOpenGL3_UpdateTexture` al siguiente `NewFrame`. Coste: un frame de hiccup, sin reinicio.
|
|
|
|
### Defaults
|
|
|
|
- `font_id = Karla` — humanist sans-serif vectorial, vendoreado por ImGui (cero descargas), nitida a 13..18 px.
|
|
- `font_size_px = 15` — un punto por encima de ImGui default (13) para legibilidad sin sacrificar densidad. Si una app necesita mas detalle, el usuario lo bumpea a 18/20.
|
|
- `show_fps = false` — overlay opcional, no contamina capturas por defecto.
|
|
|
|
### Anti-patron
|
|
|
|
```cpp
|
|
// ❌ Fps overlay hardcoded en el render — el toggle de Settings no lo apaga
|
|
fps_overlay();
|
|
// ✅ El overlay lo gestiona run_app via settings().show_fps. La app no llama nada.
|
|
|
|
// ❌ AddFontFromFileTTF directo en main — pisa load_fonts_from_settings
|
|
io.Fonts->AddFontFromFileTTF("MyFont.ttf", 14);
|
|
// ✅ Si necesitas una fuente fija no-configurable, hazlo en una nueva FontId
|
|
// y un nuevo archivo .ttf vendoreado, no en codigo de app.
|
|
```
|