---
name: layout_storage
kind: function
lang: cpp
domain: core
version: "1.1.0"
purity: impure
signature: "fn_ui::LayoutStorage* layout_storage_open(const char*); void layout_storage_close(LayoutStorage*); std::vector<std::string> layout_storage_list(LayoutStorage*); bool layout_storage_save(LayoutStorage*, const std::string&); bool layout_storage_apply(LayoutStorage*, const std::string&); std::string layout_storage_apply_pending(LayoutStorage*); bool layout_storage_delete(LayoutStorage*, const std::string&); bool layout_storage_set_last_active(LayoutStorage*, const std::string&); std::string layout_storage_get_last_active(LayoutStorage*); void layout_storage_make_callbacks(LayoutStorage*, LayoutCallbacks&)"
description: "Persistencia de layouts ImGui en SQLite con handle opaco. Una app abre el storage con un path, obtiene un LayoutCallbacks listo para pasar al menu de layouts (app_menubar/layouts_menu_items) y solo necesita llamar a layout_storage_apply_pending() al inicio de cada frame para activar layouts cargados. Persiste tambien el ultimo layout activo (tabla layout_meta) para restore-on-open."
tags: [imgui, sqlite, layouts, persistence, dockspace, public-api]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: [sqlite3, imgui]
tested: true
tests: ["layout_storage: last_active survives reopen", "layout_storage: get_last_active ignora referencia colgante", "layout_storage: callbacks on_apply persiste last_active", "layout_storage: callbacks on_delete del activo limpia last_active", "layout_storage: callbacks on_reset limpia last_active", "layout_storage: open con BD legacy (sin layout_meta) no rompe"]
test_file_path: "cpp/tests/test_layout_storage.cpp"
file_path: "cpp/functions/core/layout_storage.cpp"
params:
  - name: db_path
    desc: "Ruta a la BD SQLite. Si no existe, se crea. Acepta ':memory:'."
  - name: s
    desc: "Handle opaco LayoutStorage* obtenido con layout_storage_open."
  - name: name
    desc: "Nombre del layout (clave primaria en imgui_layouts)."
  - name: out
    desc: "LayoutCallbacks a rellenar con los handlers que envuelven el storage."
output: "open retorna LayoutStorage* o nullptr si falla. list retorna nombres ordenados por updated_at DESC. save/apply/delete retornan bool de exito. apply_pending retorna el nombre del layout aplicado (vacio si no habia pendiente). make_callbacks no retorna nada y deja `out` listo para pasar a layouts_menu_items."
---

# layout_storage

API publica de persistencia de layouts ImGui en SQLite. Reemplaza al patron manual donde cada app gestionaba su propia conexion `sqlite3*` y cableaba los callbacks contra `layout_storage_sqlite` (que sigue disponible como capa de bajo nivel).

## Schema

```sql
CREATE TABLE IF NOT EXISTS imgui_layouts (
    name       TEXT PRIMARY KEY,
    ini        TEXT NOT NULL,
    updated_at INTEGER NOT NULL  -- unix epoch en segundos
);

-- Meta key/value para "last_active" (1.1.0+). Aditiva: BBDD viejas se
-- migran al primer open() sin romper datos existentes.
CREATE TABLE IF NOT EXISTS layout_meta (
    key   TEXT PRIMARY KEY,
    value TEXT NOT NULL
);
```

`CREATE TABLE IF NOT EXISTS` significa que la nueva API convive con tablas pre-existentes en la misma BD (por ejemplo `ui_layouts` heredada o cualquier otra).

## Restore-on-open / save-on-close

`fn::run_app` (cuando `auto_layouts = true`) lee `layout_meta.last_active` justo despues de abrir el storage y, si apunta a un layout existente, lo deja pendiente con `layout_storage_apply` para que el primer frame lo cargue. Al salir, antes de cerrar el storage, reescribe el slot activo con `layout_storage_save(active_name)` para que los retoques de docking que el usuario hizo durante la sesion sobrevivan al cierre.

`make_callbacks` mantiene `layout_meta` sincronizado: `on_apply`/`on_save` apuntan al nuevo nombre, `on_delete` del layout activo y `on_reset` limpian la entrada. Si el `last_active` apunta a un layout que ya no existe (referencia colgante), `get_last_active` retorna `""`.

## Patron de uso (recomendado)

```cpp
#include "core/layout_storage.h"
#include "core/app_menubar.h"

static fn_ui::LayoutStorage* g_layouts = nullptr;
static fn_ui::LayoutCallbacks g_layout_cb;

void app_init() {
    g_layouts = fn_ui::layout_storage_open("my_app.db");
    fn_ui::layout_storage_make_callbacks(g_layouts, g_layout_cb);
}

void on_frame() {
    // 1) Aplicar layout pendiente al INICIO del frame (antes de NewFrame
    //    no es estrictamente necesario; ImGui acepta LoadIniSettingsFromMemory
    //    durante el frame, pero hacerlo aqui evita "saltos" visuales).
    std::string applied = fn_ui::layout_storage_apply_pending(g_layouts);
    if (!applied.empty()) g_layout_cb.active_name = applied;

    // 2) Renderizar la menubar pasando los callbacks. on_save/on_apply/etc
    //    ya estan cableados internamente.
    fn::app_menubar(/* ..., */ &g_layout_cb /* , ... */);

    // 3) Tu UI normal.
}

void app_shutdown() {
    fn_ui::layout_storage_close(g_layouts);
}
```

## Flujo del save/load

- **Save**: `on_save(name)` llama internamente a `ImGui::SaveIniSettingsToMemory()` y hace UPSERT por nombre. El blob es el formato INI nativo de ImGui (windows, dock layout, columns, etc.).
- **Apply**: `on_apply(name)` lee el blob de la BD pero NO llama a `LoadIniSettingsFromMemory` directamente. Lo deja "pendiente" para que `layout_storage_apply_pending()` lo aplique al inicio del proximo frame, fuera de cualquier render. Esto evita inconsistencias si el usuario cambia de layout en mitad de un frame.
- **Delete**: borra la fila por nombre. Si era el layout activo, `active_name` se limpia.
- **Reset**: limpia el INI en memoria con `LoadIniSettingsFromMemory("", 0)` y marca dirty para que ImGui regenere su layout default.

## Errores

Ninguna funcion lanza excepciones. En error:
- `open` retorna `nullptr`.
- `save`, `apply`, `delete` retornan `false`.
- `list` retorna vector vacio.
- `apply_pending` retorna string vacia.

Pasar `nullptr` como handle es siempre seguro (no-op).

## Relacion con `layout_storage_sqlite`

Esta API es la capa publica recomendada para apps. `layout_storage_sqlite` sigue disponible como CRUD de bajo nivel para casos que necesiten compartir una conexion sqlite3 con otras tablas (por ejemplo `shaders_lab` que tambien tiene `shaderlab_db`). Las dos APIs usan tablas distintas (`imgui_layouts` vs `ui_layouts`) y pueden coexistir en la misma BD.
