merge: issue/0041-cpp-app-best-practices — implementación paralela
This commit is contained in:
@@ -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
|
||||
|
||||
+112
@@ -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/<nombre>)`. 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.
|
||||
@@ -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 <GLFW/glfw3.h>
|
||||
#include <cstdio>
|
||||
@@ -49,6 +51,17 @@ int run_app(AppConfig config, std::function<void()> 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<void()> 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<void()> 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).
|
||||
|
||||
@@ -1,7 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
|
||||
// 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
|
||||
|
||||
Reference in New Issue
Block a user