---
name: app_settings
kind: function
lang: cpp
domain: core
version: "1.0.0"
purity: impure
signature: "AppSettings& fn_ui::settings(); void fn_ui::settings_load(); void fn_ui::settings_save(); void fn_ui::settings_window_render(); bool fn_ui::settings_window_menu_item(const char* label = \"Settings...\"); void fn_ui::settings_window_add_section(const char* id, const char* title, std::function<void()> render)"
description: "Settings globales de app C++ con ventana flotante (Display, Typography, secciones extra registrables) y persistencia en app_settings.ini junto al ejecutable. Carga/guardado automatico via fn::run_app."
tags: [imgui, settings, persistence, font, fps, window, ui]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: [imgui, cstdio, cstdlib, cstring, string, vector, functional]
tested: false
tests: []
test_file_path: ""
file_path: "cpp/functions/core/app_settings.cpp"
framework: imgui
params:
  - name: id
    desc: "Id estable de la seccion extra (usado para deduplicar entre llamadas)"
  - name: title
    desc: "Titulo del CollapsingHeader que envuelve la seccion"
  - name: render
    desc: "Callback que dibuja widgets ImGui dentro de la seccion. La app es responsable de persistir su estado"
  - name: label
    desc: "Texto del MenuItem en la menubar (default 'Settings...')"
output: "settings() devuelve referencia mutable al estado vivo. settings_window_render() es no-op si la ventana esta cerrada. add_section es idempotente por id"
notes: "consumido por cpp/framework/app_base.cpp"
---

# app_settings

Configuracion global para apps C++ del registry: FPS overlay, fuente y tamaño activos. Persistencia en `<cwd>/app_settings.ini`. UI = ventana flotante ImGui que las apps pueden extender con secciones propias.

## Estado expuesto

```cpp
struct AppSettings {
    bool   show_fps     = false;
    FontId font_id      = FontId::Karla;   // Karla / Roboto / DroidSans / Cousine / ProggyClean
    float  font_size_px = 15.0f;           // 12 / 13 / 14 / 15 / 16 / 18 / 20 (o cualquier valor 10..32 via slider)
};
```

## UI — ventana flotante

Click en `Settings...` (MenuItem en la MainMenuBar via `app_menubar`) → abre `Begin("Settings", &open)`. Contenido:

1. **Display** — toggle `Show FPS overlay`
2. **Typography** — combo de Font (5 opciones), combo de Size (presets) + slider libre 10..32 px
3. **<secciones extra registradas por la app>** (cada una en su `CollapsingHeader`)

Auto-save al .ini en cada mutacion. Cambio de fuente/tamaño dispara reconstruccion de atlas en el siguiente frame (`run_app` consume `settings_consume_font_dirty()`).

## Lifecycle (gestionado por `fn::run_app`)

1. `settings_load()` antes de cargar fuentes — lee el .ini si existe.
2. `icon_font::load_fonts_from_settings()` aplica la fuente del .ini.
3. Loop:
   - `app_menubar(...)` incluye el MenuItem "Settings..." que abre la ventana.
   - End of frame: `settings_window_render()` dibuja la ventana si abierta.
   - Si `settings_consume_font_dirty()`, `run_app` reconstruye atlas + GPU texture.
   - Si `settings().show_fps`, `run_app` llama `fps_overlay()`.
4. `settings_save()` al exit (idempotente con los auto-saves del menu).

## Persistencia

`<cwd>/app_settings.ini` (junto al exe, mismo patron que `imgui.ini`):

```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
```

Una `.ini` por app porque cada exe vive en su carpeta (`Desktop/apps/<app>/`). Permite que `shaders_lab` use Karla 18 px y `gallery` Cousine 13 px sin colision.

## Secciones extra (extender la ventana)

Una app que quiere añadir su propia categoria de settings:

```cpp
// En main.cpp, antes de fn::run_app(...):
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);
    if (ImGui::Button("Reset shader cache")) clear_cache();
});
```

- `id` debe ser estable. Si llamas `add_section` con el mismo id, reemplaza el render.
- La app es responsable de persistir su propio estado (en su SQLite, env, archivo aparte). El .ini de `app_settings` solo guarda los campos core.
- El render se invoca dentro de un `CollapsingHeader` abierto por defecto, dentro de `PushID(id)` para evitar colisiones de IDs ImGui.

## API rapida

```cpp
fn_ui::settings()                           // estado vivo
fn_ui::settings().show_fps = true
fn_ui::settings_window_set_open(true)       // abrir programaticamente
fn_ui::settings_window_toggle()             // p.ej. atajo F10
fn_ui::settings_window_add_section(...)     // extender
fn_ui::settings_save()                      // forzar flush al .ini
```

## Notas

- `font_id = ProggyClean` ignora `font_size_px` (bitmap a 13 px nativo).
- El slider libre permite tamaños no-preset (ej. 17 px). El combo Size solo lista presets comunes; el slider funciona como override.
- Si quieres añadir mas campos core (tema light/dark, locale, multi-viewport), añade al struct, parsing en `parse_line`, serializacion en `settings_save`. Claves desconocidas en el .ini se ignoran (forward compat).
