diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index 749aab33..b565e5f7 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -105,6 +105,7 @@ add_library(fn_framework STATIC functions/core/panel_menu.cpp functions/core/layouts_menu.cpp functions/core/app_menubar.cpp + functions/gfx/gl_loader.cpp ) target_include_directories(fn_framework PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/framework diff --git a/cpp/PATTERNS.md b/cpp/PATTERNS.md new file mode 100644 index 00000000..ba3fdab8 --- /dev/null +++ b/cpp/PATTERNS.md @@ -0,0 +1,112 @@ +# 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 `glfwInit` directo**. La app SOLO usa `fn::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 explicitamente `fn_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 a `AppConfig::panels` + `AppConfig::panel_count = 2`. +- [ ] **Layouts persistentes** (si aplica). Si la app guarda layouts: + implementa `fn_ui::LayoutCallbacks` y pasalas en `AppConfig::layouts_cb`. +- [ ] **GL loader** (si la app usa OpenGL >= 2.0 directamente). Pasar + `AppConfig::init_gl_loader = true` para que `fn::run_app()` llame + `fn::gfx::gl_loader_init()` tras crear el contexto. +- [ ] **Tokens en lugar de hex literales**. Usar `fn_tokens::colors`, + `fn_tokens::spacing`, `fn_tokens::radius`. Nunca `IM_COL32(0x12,0x34,...)`, + nunca `ImVec4(0.5f, 0.5f, 0.5f, 1.0f)` ad-hoc. +- [ ] **Componentes del registry, no raw ImGui con styling manual**. Evitar + `ImGui::BeginTable / Selectable / BeginPopupModal / BeginChild` con estilos + pegados a mano cuando ya existe primitiva: + - `fn_ui::dashboard_grid` / `fn_ui::dashboard_panel` para layouts grid. + - `fn_ui::tree_view` / `fn_ui::select` para listas seleccionables. + - `fn_ui::modal_dialog` para popups modales. +- [ ] **Iconos via `TI_*`** (Tabler). Nunca emojis ni hex UTF-8 inline. + Ver `cpp/functions/core/icons_tabler.h`. +- [ ] **Build incremental**. La app aparece en `cpp/CMakeLists.txt` con su + `add_subdirectory(apps/)`. Sin warnings nuevos. + +## Esqueleto minimo + +```cpp +#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/Settings/About), ventana About, +ventana Settings, FPS overlay configurable, theming `FnDark`, fuentes vectoriales ++ iconos Tabler mergeados, multi-viewport opcional. + +## 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 `googletest` directo); +- 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. diff --git a/cpp/framework/app_base.cpp b/cpp/framework/app_base.cpp index 94a28820..3ad3d74c 100644 --- a/cpp/framework/app_base.cpp +++ b/cpp/framework/app_base.cpp @@ -9,7 +9,9 @@ #include "core/icon_font.h" #include "core/app_settings.h" #include "core/app_about.h" +#include "core/app_menubar.h" #include "core/fps_overlay.h" +#include "gfx/gl_loader.h" #include #include @@ -49,6 +51,17 @@ int run_app(AppConfig config, std::function render_fn) { glfwMakeContextCurrent(window); glfwSwapInterval(config.vsync ? 1 : 0); + // Carga punteros a funciones GL >= 2.0 si la app lo pide. En Linux es + // no-op; en Windows usa wglGetProcAddress (requiere ctx GL activo). + if (config.init_gl_loader) { + if (!fn::gfx::gl_loader_init()) { + fprintf(stderr, "Failed to initialize GL function loader\n"); + glfwDestroyWindow(window); + glfwTerminate(); + return 1; + } + } + // Setup ImGui IMGUI_CHECKVERSION(); ImGui::CreateContext(); @@ -63,6 +76,14 @@ int run_app(AppConfig config, std::function render_fn) { // fuentes. Si no existe el .ini, los defaults se aplican. fn_ui::settings_load(); + // Registra info de la ventana About si la app la proveyo en AppConfig. + if (config.about.name != nullptr) { + fn_ui::about_window_set_info( + config.about.name, + config.about.version ? config.about.version : "", + config.about.description ? config.about.description : ""); + } + // Texto vectorial (Karla / Roboto / DroidSans / Cousine, segun settings) // + iconos Tabler mergeados al mismo tamaño en el mismo ImFont. fn_ui::load_fonts_from_settings(); @@ -127,6 +148,13 @@ int run_app(AppConfig config, std::function render_fn) { ImGui_ImplGlfw_NewFrame(); ImGui::NewFrame(); + // Menubar canonica (View / Layouts / Settings / About) si la app la + // configuro en AppConfig. Se renderiza ANTES del render_fn para que + // el render_fn pueda hacer DockSpaceOverViewport debajo. + if (config.panels != nullptr || config.layouts_cb != nullptr) { + fn_ui::app_menubar(config.panels, config.panel_count, config.layouts_cb); + } + render_fn(); // Ventana de Settings (no-op si esta cerrada). diff --git a/cpp/framework/app_base.h b/cpp/framework/app_base.h index 18ab8d04..7aa157be 100644 --- a/cpp/framework/app_base.h +++ b/cpp/framework/app_base.h @@ -1,7 +1,24 @@ #pragma once +#include #include +// Forward declarations para evitar incluir headers pesados aqui. Las +// definiciones reales viven en cpp/functions/core/*.h y se incluyen desde +// app_base.cpp. +namespace fn_ui { + struct PanelToggle; + struct LayoutCallbacks; + + // Info estatica para la ventana About. Si name != nullptr, fn::run_app + // llama about_window_set_info(name, version, description) tras settings_load(). + struct AppAboutInfo { + const char* name = nullptr; + const char* version = nullptr; + const char* description = nullptr; + }; +} + namespace fn { // Modos de tema para run_app. @@ -22,6 +39,25 @@ struct AppConfig { float bg_r = 0.102f; // fn_tokens::colors::bg (dark.7 #1A1B1E) float bg_g = 0.106f; float bg_b = 0.118f; + + // About window. Si about.name != nullptr, run_app llama + // fn_ui::about_window_set_info(name, version, description) tras settings_load(). + fn_ui::AppAboutInfo about{}; + + // Paneles toggleables del menubar. Si panels != nullptr y panel_count > 0, + // run_app llama fn_ui::app_menubar(panels, panel_count, layouts_cb) cada frame + // ANTES de render_fn(). + const fn_ui::PanelToggle* panels = nullptr; + std::size_t panel_count = 0; + + // Callbacks de layouts persistentes. Si layouts_cb != nullptr, run_app + // llama fn_ui::app_menubar(panels, panel_count, layouts_cb) cada frame. + fn_ui::LayoutCallbacks* layouts_cb = nullptr; + + // Si true, run_app llama fn::gfx::gl_loader_init() tras crear el contexto + // GL y antes del primer frame. Necesario para apps que llaman gl* directo + // en Windows (en Linux es no-op). + bool init_gl_loader = false; }; // Run an ImGui application. The render_fn is called every frame