- .claude/CLAUDE.md - .claude/agents/fn-recopilador/SKILL.md - .claude/rules/INDEX.md - .claude/rules/cpp_apps.md - bash/functions/infra/build_cpp_windows.sh - cpp/CMakeLists.txt - cpp/PATTERNS.md - cpp/framework/app_base.cpp - cpp/framework/app_base.h - dev/issues/README.md - ... Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
7.2 KiB
cpp/PATTERNS.md — App shell canonico
Patron obligatorio para apps C++ del registry (cpp/apps/*, projects/*/apps/*).
Cumplir estas reglas garantiza coherencia visual, theming uniforme, About/Settings
funcionando, paneles toggleables y layouts persistentes con cero codigo boilerplate.
Checklist obligatorio
Antes de mergear una app, verificar uno por uno:
- No
glfwInitdirecto. La app SOLO usafn::run_app(AppConfig{...}, render_fn). El framework gestiona GLFW + ImGui + ImPlot + theming + Settings + About + FPS overlay. - About registrado. La app pasa
AppConfig::about = {name, version, description}o llama explicitamentefn_ui::about_window_set_info(...)en su init. - Settings extras (si aplica). Si la app expone settings propios (toggles,
sliders, paths…), los registra con
fn_ui::settings_window_add_section("Mi App", cb). - Paneles toggleables (si aplica). Si la app tiene >=1 panel:
cpp static constexpr fn_ui::PanelToggle panels[] = { {"Inspector", "Ctrl+1", &show_inspector}, {"Console", "Ctrl+2", &show_console}, };Pasarlo aAppConfig::panels+AppConfig::panel_count = 2. - Layouts persistentes. Vienen activos por defecto:
fn::run_appabre unLayoutStorageSQLite en<exe_dir>/local_files/layouts.dby enchufa el menu Layouts (Save / Apply / Delete / Reset) sin codigo. La app solo pasaAppConfig::layouts_cbsi quiere personalizar (ej. on_reset que restaure paneles especificos como enshaders_lab). Para apagar el auto-storage:cfg.auto_layouts = false. Para cambiar el nombre del archivo:cfg.auto_layouts_db = "myapp_layouts.db". - GL loader (si la app usa OpenGL >= 2.0 directamente). Pasar
AppConfig::init_gl_loader = truepara quefn::run_app()llamefn::gfx::gl_loader_init()tras crear el contexto. - Tokens en lugar de hex literales. Usar
fn_tokens::colors,fn_tokens::spacing,fn_tokens::radius. NuncaIM_COL32(0x12,0x34,...), nuncaImVec4(0.5f, 0.5f, 0.5f, 1.0f)ad-hoc. - Componentes del registry, no raw ImGui con styling manual. Evitar
ImGui::BeginTable / Selectable / BeginPopupModal / BeginChildcon estilos pegados a mano cuando ya existe primitiva: -fn_ui::dashboard_grid/fn_ui::dashboard_panelpara layouts grid. -fn_ui::tree_view/fn_ui::selectpara listas seleccionables. -fn_ui::modal_dialogpara popups modales. - Iconos via
TI_*(Tabler). Nunca emojis ni hex UTF-8 inline. Vercpp/functions/core/icons_tabler.h. - Build incremental. La app aparece en
cpp/CMakeLists.txtcon suadd_subdirectory(apps/<nombre>). Sin warnings nuevos.
Esqueleto minimo
#include "framework/app_base.h"
#include "core/icons_tabler.h"
#include "core/panel_menu.h"
#include "core/app_settings.h"
#include "core/tokens.h"
#include "imgui.h"
namespace {
bool show_inspector = true;
bool show_console = false;
constexpr fn_ui::PanelToggle k_panels[] = {
{"Inspector", "Ctrl+1", &show_inspector},
{"Console", "Ctrl+2", &show_console},
};
} // namespace
static void render_my_app() {
ImGui::DockSpaceOverViewport(0, ImGui::GetMainViewport());
if (show_inspector) {
ImGui::Begin(TI_INFO_CIRCLE " Inspector", &show_inspector);
ImGui::TextUnformatted("Inspector contents");
ImGui::End();
}
if (show_console) {
ImGui::Begin(TI_TERMINAL_2 " Console", &show_console);
ImGui::TextUnformatted("Console contents");
ImGui::End();
}
}
int main() {
fn::AppConfig cfg;
cfg.title = "My App";
cfg.about = {"My App", "0.1.0", "Demo de app shell canonica"};
cfg.panels = k_panels;
cfg.panel_count = sizeof(k_panels) / sizeof(k_panels[0]);
cfg.init_gl_loader = false; // ponerlo en true si usas OpenGL directo
return fn::run_app(cfg, render_my_app);
}
Con esto la app obtiene gratis: MainMenuBar (View/Layouts/Settings/About),
ventana About, ventana Settings, ventana Logs, FPS overlay configurable, theming
FnDark, fuentes vectoriales + iconos Tabler mergeados, multi-viewport opcional,
y persistencia de layouts ImGui en <exe_dir>/local_files/layouts.db sin
escribir una linea de codigo.
Anti-patrones
| Mal patron | Patron correcto |
|---|---|
glfwInit() en main |
fn::run_app() |
ImVec4(0.5,0.5,0.5,1) ad-hoc |
fn_tokens::colors::text_dim |
| Crear menubar a mano en cada frame | AppConfig::panels + AppConfig::layouts_cb |
ImGui::Begin(u8"\xEF\xA0\x83 ...") |
ImGui::Begin(TI_HOME " ...") |
| Settings dispersos por la app | settings_window_add_section() |
About hardcoded en un Begin/End |
AppConfig::about o about_window_set_info() |
Llamar gl* sin loader en Windows |
AppConfig::init_gl_loader = true |
Cuando NO usar fn::run_app
Solo si la app es:
- un test headless que no necesita ventana (usar
googletestdirecto); - un binario CLI sin UI (no es una "app C++" en este sentido).
En cualquier otro caso, usar fn::run_app. Si AppConfig no expone algo que
necesitas, abrir un issue para extender el shell, no duplicar boilerplate.
Tests visuales y CI gate (issue 0048)
primitives_gallery soporta un modo --capture <output_dir> que renderiza
cada demo en una ventana GLFW invisible y guarda un PNG por demo. Se usa
para tests visuales tipo golden-image:
# Regenerar goldens (cuando tu cambio es intencional):
cpp/scripts/update_goldens.sh
# Equivalente manual:
LIBGL_ALWAYS_SOFTWARE=1 \
cpp/build/apps/primitives_gallery/primitives_gallery \
--capture cpp/tests/golden
cpp/tests/test_visual.cpp corre la captura sobre un tmpdir y compara contra
cpp/tests/golden/<demo>.png con tolerancia 1% de pixels distintos
(threshold 5/255 por canal). Skipea si:
cpp/tests/golden/esta vacio (no hay goldens todavia).- El binario
primitives_galleryno se construyo. - El entorno no puede crear contexto GL (WSL minimo, container sin Mesa) — el test reporta SKIP en lugar de FAIL.
Para diagnosticar un diff: revisar el PNG actual en
cpp/build/tests/visual_actual/<demo>.png vs el golden en
cpp/tests/golden/<demo>.png.
CI gate check_tested.sh
cpp/scripts/check_tested.sh [days] (default 30) consulta registry.db y
falla con codigo != 0 si alguna funcion C++ creada en los ultimos N dias no
tiene tested: true en su frontmatter. Esta hookeado al final de
cpp/scripts/run_tests.sh, por lo que el flujo CI (./scripts/run_tests.sh)
falla si se anade una funcion C++ nueva sin test asociado.
Para satisfacer el gate:
- Crear
cpp/tests/test_<name>.cpp(puede ser placeholder Catch2 si la logica visual se cubre viaprimitives_gallery). - Anadirlo a
cpp/tests/CMakeLists.txtconadd_fn_test(test_<name> ...). - Marcar
tested: true+test_file_path: cpp/tests/test_<name>.cppen el frontmatter del.mdde la funcion. - Correr
fn indexpara refrescarregistry.db.