merge: issue/0042-cpp-layout-storage-public — implementación paralela
This commit is contained in:
+18
-16
@@ -91,6 +91,22 @@ endif()
|
||||
|
||||
target_link_libraries(imgui PUBLIC ${PLATFORM_LIBS})
|
||||
|
||||
# --- SQLite3 (shared by every app that uses it, including fn_framework for
|
||||
# layout_storage) ---
|
||||
# System on Linux, vendored amalgamation on Windows cross-compile.
|
||||
find_package(SQLite3 QUIET)
|
||||
if(NOT SQLite3_FOUND AND NOT TARGET sqlite3_vendored)
|
||||
set(SQLITE3_AMALG_DIR ${CMAKE_CURRENT_SOURCE_DIR}/vendor/sqlite3)
|
||||
add_library(sqlite3_vendored STATIC ${SQLITE3_AMALG_DIR}/sqlite3.c)
|
||||
target_include_directories(sqlite3_vendored PUBLIC ${SQLITE3_AMALG_DIR})
|
||||
target_compile_definitions(sqlite3_vendored PRIVATE
|
||||
SQLITE_THREADSAFE=1
|
||||
SQLITE_ENABLE_FTS5
|
||||
SQLITE_ENABLE_JSON1
|
||||
)
|
||||
add_library(SQLite::SQLite3 ALIAS sqlite3_vendored)
|
||||
endif()
|
||||
|
||||
# --- Framework ---
|
||||
# Incluye tokens.cpp (identidad visual Mantine dark + indigo), icon_font.cpp
|
||||
# (Karla/Roboto/... + Tabler), app_settings.cpp (persistencia y ventana de
|
||||
@@ -106,6 +122,7 @@ add_library(fn_framework STATIC
|
||||
functions/core/layouts_menu.cpp
|
||||
functions/core/app_menubar.cpp
|
||||
functions/gfx/gl_loader.cpp
|
||||
functions/core/layout_storage.cpp
|
||||
)
|
||||
target_include_directories(fn_framework PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/framework
|
||||
@@ -116,7 +133,7 @@ target_include_directories(fn_framework PUBLIC
|
||||
target_compile_definitions(fn_framework PUBLIC
|
||||
FN_CPP_ROOT="${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
)
|
||||
target_link_libraries(fn_framework PUBLIC imgui implot implot3d)
|
||||
target_link_libraries(fn_framework PUBLIC imgui implot implot3d SQLite::SQLite3)
|
||||
if(TRACY_ENABLE)
|
||||
target_link_libraries(fn_framework PUBLIC tracy)
|
||||
endif()
|
||||
@@ -155,21 +172,6 @@ function(add_imgui_app target)
|
||||
)
|
||||
endfunction()
|
||||
|
||||
# --- SQLite3 (shared by every app that uses it) ---
|
||||
# System on Linux, vendored amalgamation on Windows cross-compile.
|
||||
find_package(SQLite3 QUIET)
|
||||
if(NOT SQLite3_FOUND AND NOT TARGET sqlite3_vendored)
|
||||
set(SQLITE3_AMALG_DIR ${CMAKE_CURRENT_SOURCE_DIR}/vendor/sqlite3)
|
||||
add_library(sqlite3_vendored STATIC ${SQLITE3_AMALG_DIR}/sqlite3.c)
|
||||
target_include_directories(sqlite3_vendored PUBLIC ${SQLITE3_AMALG_DIR})
|
||||
target_compile_definitions(sqlite3_vendored PRIVATE
|
||||
SQLITE_THREADSAFE=1
|
||||
SQLITE_ENABLE_FTS5
|
||||
SQLITE_ENABLE_JSON1
|
||||
)
|
||||
add_library(SQLite::SQLite3 ALIAS sqlite3_vendored)
|
||||
endif()
|
||||
|
||||
# --- Function libraries (headers for composition) ---
|
||||
# Functions are compiled as part of apps that use them via add_imgui_app.
|
||||
# Each function is a .h/.cpp pair included by the app's CMakeLists.txt.
|
||||
|
||||
@@ -16,8 +16,8 @@ add_imgui_app(shaders_lab
|
||||
${CMAKE_SOURCE_DIR}/functions/gfx/dag_node_previews.cpp
|
||||
${CMAKE_SOURCE_DIR}/functions/gfx/shaderlab_db.cpp
|
||||
${CMAKE_SOURCE_DIR}/functions/gfx/code_to_generator.cpp
|
||||
# fps_overlay, panel_menu, layouts_menu, app_menubar ya viven en fn_framework
|
||||
${CMAKE_SOURCE_DIR}/functions/core/layout_storage_sqlite.cpp
|
||||
# fps_overlay, panel_menu, layouts_menu, app_menubar, layout_storage ya
|
||||
# viven en fn_framework.
|
||||
)
|
||||
target_include_directories(shaders_lab PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
#include "core/panel_menu.h"
|
||||
#include "core/layouts_menu.h"
|
||||
#include "core/app_menubar.h"
|
||||
#include "core/layout_storage_sqlite.h"
|
||||
#include "core/layout_storage.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <cctype>
|
||||
@@ -71,9 +71,10 @@ static bool g_show_functions = true;
|
||||
static bool g_show_generated = true;
|
||||
|
||||
// ── Layouts (named ImGui ini snapshots persisted in shaders_lab.db) ───────
|
||||
// El storage opaco encapsula la BD y el blob pendiente. Los callbacks
|
||||
// envuelven save/apply/delete/reset y se pasan a app_menubar tal cual.
|
||||
static fn_ui::LayoutStorage* g_layouts = nullptr;
|
||||
static fn_ui::LayoutCallbacks g_layout_cb;
|
||||
static std::string g_pending_layout_blob; // applied at start of next frame
|
||||
static std::string g_pending_layout_name; // becomes active_name after apply
|
||||
|
||||
// ── Save-as-generator modal state ─────────────────────────────────────────
|
||||
static bool g_save_modal_open = false;
|
||||
@@ -222,13 +223,8 @@ static void load_user_generators_into_catalog() {
|
||||
static void render() {
|
||||
// Apply pending layout BEFORE any ImGui::Begin this frame.
|
||||
// (LoadIniSettingsFromMemory must happen before windows are submitted.)
|
||||
if (!g_pending_layout_blob.empty()) {
|
||||
ImGui::LoadIniSettingsFromMemory(g_pending_layout_blob.c_str(),
|
||||
g_pending_layout_blob.size());
|
||||
g_layout_cb.active_name = g_pending_layout_name;
|
||||
g_pending_layout_blob.clear();
|
||||
g_pending_layout_name.clear();
|
||||
}
|
||||
std::string applied = fn_ui::layout_storage_apply_pending(g_layouts);
|
||||
if (!applied.empty()) g_layout_cb.active_name = applied;
|
||||
|
||||
if (!g_canvas_code.initialized) fn::gfx::canvas_init(g_canvas_code);
|
||||
if (!g_canvas_dag.initialized) fn::gfx::canvas_init(g_canvas_dag);
|
||||
@@ -412,37 +408,20 @@ int main() {
|
||||
load_user_generators_into_catalog();
|
||||
ensure_dag_default();
|
||||
|
||||
// Layout persistence on the same shaders_lab.db connection.
|
||||
sqlite3* db = fn::gfx::shaderlab_db_handle();
|
||||
fn_ui::layout_storage_init(db);
|
||||
// Layout persistence: handle opaco que crea su propia tabla
|
||||
// imgui_layouts en shaders_lab.db (CREATE IF NOT EXISTS, no toca la
|
||||
// tabla ui_layouts heredada). Cualquier app del registry puede usar
|
||||
// este patron.
|
||||
g_layouts = fn_ui::layout_storage_open("shaders_lab.db");
|
||||
fn_ui::layout_storage_make_callbacks(g_layouts, g_layout_cb);
|
||||
|
||||
g_layout_cb.list = [db]() {
|
||||
return fn_ui::layout_storage_list(db);
|
||||
};
|
||||
g_layout_cb.on_apply = [db](const std::string& name) {
|
||||
std::string blob = fn_ui::layout_storage_load_blob(db, name);
|
||||
if (!blob.empty()) {
|
||||
g_pending_layout_blob = std::move(blob);
|
||||
g_pending_layout_name = name;
|
||||
}
|
||||
};
|
||||
g_layout_cb.on_save = [db](const std::string& name) {
|
||||
size_t size = 0;
|
||||
const char* blob = ImGui::SaveIniSettingsToMemory(&size);
|
||||
if (blob && size > 0) {
|
||||
fn_ui::layout_storage_save(db, name, std::string(blob, size));
|
||||
g_layout_cb.active_name = name;
|
||||
}
|
||||
};
|
||||
g_layout_cb.on_delete = [db](const std::string& name) {
|
||||
fn_ui::layout_storage_delete(db, name);
|
||||
if (g_layout_cb.active_name == name) g_layout_cb.active_name.clear();
|
||||
};
|
||||
// Override de on_reset: ademas de limpiar el INI, re-mostrar todos
|
||||
// los paneles especificos de shaders_lab.
|
||||
g_layout_cb.on_reset = []() {
|
||||
// Default reset: open every panel and clear active layout marker.
|
||||
// The actual dock layout is whatever ImGui rebuilt on first launch.
|
||||
g_show_code = g_show_dag = g_show_canvas_c = g_show_canvas_d =
|
||||
g_show_controls = g_show_functions = g_show_generated = true;
|
||||
ImGui::LoadIniSettingsFromMemory("", 0);
|
||||
ImGui::GetIO().WantSaveIniSettings = true;
|
||||
g_layout_cb.active_name.clear();
|
||||
};
|
||||
|
||||
@@ -456,6 +435,7 @@ int main() {
|
||||
fn::gfx::canvas_destroy(g_canvas_dag);
|
||||
fn::gfx::dag_node_editor_destroy();
|
||||
fn::gfx::dag_previews_destroy();
|
||||
fn_ui::layout_storage_close(g_layouts);
|
||||
fn::gfx::shaderlab_db_close();
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
#include "core/layout_storage.h"
|
||||
|
||||
#include <imgui.h>
|
||||
#include <sqlite3.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace fn_ui {
|
||||
|
||||
// ── handle interno ────────────────────────────────────────────────────────
|
||||
|
||||
struct LayoutStorage {
|
||||
sqlite3* db = nullptr;
|
||||
std::string pending_blob; // INI a aplicar al inicio del proximo frame
|
||||
std::string pending_name; // nombre del layout pendiente
|
||||
};
|
||||
|
||||
// ── helpers ───────────────────────────────────────────────────────────────
|
||||
|
||||
namespace {
|
||||
|
||||
int64_t now_unix() {
|
||||
using namespace std::chrono;
|
||||
return duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
|
||||
}
|
||||
|
||||
bool ensure_schema(sqlite3* db) {
|
||||
// CREATE TABLE IF NOT EXISTS — no toca tablas pre-existentes con otro
|
||||
// schema (ej. ui_layouts heredada de layout_storage_sqlite).
|
||||
const char* sql =
|
||||
"CREATE TABLE IF NOT EXISTS imgui_layouts ("
|
||||
" name TEXT PRIMARY KEY,"
|
||||
" ini TEXT NOT NULL,"
|
||||
" updated_at INTEGER NOT NULL"
|
||||
");";
|
||||
char* errmsg = nullptr;
|
||||
int rc = sqlite3_exec(db, sql, nullptr, nullptr, &errmsg);
|
||||
if (errmsg) sqlite3_free(errmsg);
|
||||
return rc == SQLITE_OK;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// ── API ───────────────────────────────────────────────────────────────────
|
||||
|
||||
LayoutStorage* layout_storage_open(const char* db_path) {
|
||||
if (!db_path || !*db_path) return nullptr;
|
||||
sqlite3* db = nullptr;
|
||||
int rc = sqlite3_open(db_path, &db);
|
||||
if (rc != SQLITE_OK || !db) {
|
||||
if (db) sqlite3_close(db);
|
||||
return nullptr;
|
||||
}
|
||||
if (!ensure_schema(db)) {
|
||||
sqlite3_close(db);
|
||||
return nullptr;
|
||||
}
|
||||
auto* s = new LayoutStorage{};
|
||||
s->db = db;
|
||||
return s;
|
||||
}
|
||||
|
||||
void layout_storage_close(LayoutStorage* s) {
|
||||
if (!s) return;
|
||||
if (s->db) sqlite3_close(s->db);
|
||||
delete s;
|
||||
}
|
||||
|
||||
std::vector<std::string> layout_storage_list(LayoutStorage* s) {
|
||||
std::vector<std::string> out;
|
||||
if (!s || !s->db) return out;
|
||||
const char* sql =
|
||||
"SELECT name FROM imgui_layouts ORDER BY updated_at DESC;";
|
||||
sqlite3_stmt* stmt = nullptr;
|
||||
if (sqlite3_prepare_v2(s->db, sql, -1, &stmt, nullptr) != SQLITE_OK)
|
||||
return out;
|
||||
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
const unsigned char* t = sqlite3_column_text(stmt, 0);
|
||||
if (t) out.emplace_back(reinterpret_cast<const char*>(t));
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
return out;
|
||||
}
|
||||
|
||||
bool layout_storage_save(LayoutStorage* s, const std::string& name) {
|
||||
if (!s || !s->db || name.empty()) return false;
|
||||
|
||||
std::size_t sz = 0;
|
||||
const char* ini = ImGui::SaveIniSettingsToMemory(&sz);
|
||||
if (!ini || sz == 0) return false;
|
||||
|
||||
const char* sql =
|
||||
"INSERT INTO imgui_layouts (name, ini, updated_at) "
|
||||
"VALUES (?, ?, ?) "
|
||||
"ON CONFLICT(name) DO UPDATE SET "
|
||||
" ini = excluded.ini, "
|
||||
" updated_at = excluded.updated_at;";
|
||||
sqlite3_stmt* stmt = nullptr;
|
||||
if (sqlite3_prepare_v2(s->db, sql, -1, &stmt, nullptr) != SQLITE_OK)
|
||||
return false;
|
||||
sqlite3_bind_text(stmt, 1, name.c_str(), -1, SQLITE_TRANSIENT);
|
||||
sqlite3_bind_text(stmt, 2, ini, static_cast<int>(sz), SQLITE_TRANSIENT);
|
||||
sqlite3_bind_int64(stmt, 3, now_unix());
|
||||
int rc = sqlite3_step(stmt);
|
||||
sqlite3_finalize(stmt);
|
||||
return rc == SQLITE_DONE;
|
||||
}
|
||||
|
||||
bool layout_storage_apply(LayoutStorage* s, const std::string& name) {
|
||||
if (!s || !s->db || name.empty()) return false;
|
||||
const char* sql = "SELECT ini FROM imgui_layouts WHERE name = ?;";
|
||||
sqlite3_stmt* stmt = nullptr;
|
||||
if (sqlite3_prepare_v2(s->db, sql, -1, &stmt, nullptr) != SQLITE_OK)
|
||||
return false;
|
||||
sqlite3_bind_text(stmt, 1, name.c_str(), -1, SQLITE_TRANSIENT);
|
||||
bool found = false;
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
const unsigned char* t = sqlite3_column_text(stmt, 0);
|
||||
int len = sqlite3_column_bytes(stmt, 0);
|
||||
if (t && len > 0) {
|
||||
s->pending_blob.assign(reinterpret_cast<const char*>(t),
|
||||
static_cast<std::size_t>(len));
|
||||
s->pending_name = name;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
return found;
|
||||
}
|
||||
|
||||
std::string layout_storage_apply_pending(LayoutStorage* s) {
|
||||
if (!s || s->pending_blob.empty()) return "";
|
||||
ImGui::LoadIniSettingsFromMemory(s->pending_blob.data(),
|
||||
s->pending_blob.size());
|
||||
std::string applied = std::move(s->pending_name);
|
||||
s->pending_blob.clear();
|
||||
s->pending_name.clear();
|
||||
return applied;
|
||||
}
|
||||
|
||||
bool layout_storage_delete(LayoutStorage* s, const std::string& name) {
|
||||
if (!s || !s->db || name.empty()) return false;
|
||||
const char* sql = "DELETE FROM imgui_layouts WHERE name = ?;";
|
||||
sqlite3_stmt* stmt = nullptr;
|
||||
if (sqlite3_prepare_v2(s->db, sql, -1, &stmt, nullptr) != SQLITE_OK)
|
||||
return false;
|
||||
sqlite3_bind_text(stmt, 1, name.c_str(), -1, SQLITE_TRANSIENT);
|
||||
int rc = sqlite3_step(stmt);
|
||||
int changes = sqlite3_changes(s->db);
|
||||
sqlite3_finalize(stmt);
|
||||
return rc == SQLITE_DONE && changes > 0;
|
||||
}
|
||||
|
||||
void layout_storage_make_callbacks(LayoutStorage* s, LayoutCallbacks& out) {
|
||||
out.list = [s]() {
|
||||
return layout_storage_list(s);
|
||||
};
|
||||
out.on_apply = [s, &out](const std::string& name) {
|
||||
if (layout_storage_apply(s, name)) {
|
||||
// active_name se ajustara cuando layout_storage_apply_pending
|
||||
// confirme que se aplico (la app debe tomar el valor de retorno
|
||||
// y propagarlo). Aqui ya lo dejamos optimistamente.
|
||||
out.active_name = name;
|
||||
}
|
||||
};
|
||||
out.on_save = [s, &out](const std::string& name) {
|
||||
if (layout_storage_save(s, name)) {
|
||||
out.active_name = name;
|
||||
}
|
||||
};
|
||||
out.on_delete = [s, &out](const std::string& name) {
|
||||
layout_storage_delete(s, name);
|
||||
if (out.active_name == name) out.active_name.clear();
|
||||
};
|
||||
out.on_reset = [&out]() {
|
||||
// Limpia el INI en memoria. La app puede ademas re-mostrar paneles.
|
||||
ImGui::LoadIniSettingsFromMemory("", 0);
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.WantSaveIniSettings = true;
|
||||
out.active_name.clear();
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace fn_ui
|
||||
@@ -0,0 +1,83 @@
|
||||
#pragma once
|
||||
//
|
||||
// layout_storage — API publica de persistencia de layouts ImGui en SQLite.
|
||||
//
|
||||
// Ofrece un handle opaco (LayoutStorage*) para que las apps no tengan que
|
||||
// gestionar sqlite3 ni el cableado save/load/list/delete contra el menu de
|
||||
// layouts. Una app obtiene un LayoutCallbacks listo para pasarse a
|
||||
// app_menubar() / layouts_menu_items() con dos llamadas:
|
||||
//
|
||||
// auto* st = fn_ui::layout_storage_open("my_app.db");
|
||||
// fn_ui::LayoutCallbacks cb;
|
||||
// fn_ui::layout_storage_make_callbacks(st, cb);
|
||||
// // ...
|
||||
// fn_ui::layout_storage_close(st);
|
||||
//
|
||||
// Internamente:
|
||||
// * Tabla SQLite "imgui_layouts(name TEXT PRIMARY KEY, ini TEXT NOT NULL,
|
||||
// updated_at INTEGER NOT NULL)".
|
||||
// * save: serializa ImGui::SaveIniSettingsToMemory() y hace UPSERT por
|
||||
// nombre.
|
||||
// * load (on_apply): NO llama a ImGui::LoadIniSettingsFromMemory directo;
|
||||
// guarda el blob pendiente y lo aplica al inicio del proximo frame
|
||||
// (ver layout_storage_apply_pending).
|
||||
// * list: SELECT name FROM imgui_layouts ORDER BY updated_at DESC.
|
||||
// * remove: DELETE FROM imgui_layouts WHERE name = ?.
|
||||
//
|
||||
// La API es impure: hace I/O contra disco. Nunca lanza excepciones; en error
|
||||
// las operaciones devuelven false / vector vacio / string vacia.
|
||||
|
||||
#include "core/layouts_menu.h" // for fn_ui::LayoutCallbacks
|
||||
|
||||
namespace fn_ui {
|
||||
|
||||
struct LayoutStorage; // opaco — definido en layout_storage.cpp
|
||||
|
||||
// Abre (o crea) la BD en `db_path` y garantiza la tabla `imgui_layouts`.
|
||||
// Retorna nullptr si no se puede abrir el fichero.
|
||||
// El caller posee el handle y debe cerrarlo con layout_storage_close.
|
||||
LayoutStorage* layout_storage_open(const char* db_path);
|
||||
|
||||
// Cierra la BD y libera el handle. Seguro con nullptr (no-op).
|
||||
void layout_storage_close(LayoutStorage* s);
|
||||
|
||||
// Lista nombres de layouts guardados, ordenados por updated_at DESC (mas
|
||||
// recientes primero). Retorna vector vacio en error o si s == nullptr.
|
||||
std::vector<std::string> layout_storage_list(LayoutStorage* s);
|
||||
|
||||
// Guarda (UPSERT) un layout serializando ImGui::SaveIniSettingsToMemory().
|
||||
// Debe llamarse desde un contexto con ImGui inicializado.
|
||||
// Retorna true si la fila se inserto/actualizo, false en error.
|
||||
bool layout_storage_save(LayoutStorage* s, const std::string& name);
|
||||
|
||||
// Marca un layout como "pendiente de aplicar". Carga el blob INI desde la BD
|
||||
// pero NO llama a ImGui::LoadIniSettingsFromMemory todavia: aplicar settings
|
||||
// dentro de un frame es inseguro, asi que se aplica en el proximo
|
||||
// layout_storage_apply_pending.
|
||||
// Retorna true si encontro el layout y quedo pendiente.
|
||||
bool layout_storage_apply(LayoutStorage* s, const std::string& name);
|
||||
|
||||
// Aplica el blob pendiente (si lo hay) llamando a
|
||||
// ImGui::LoadIniSettingsFromMemory. Llamar al INICIO del frame, antes de
|
||||
// crear ventanas. No-op si no hay nada pendiente.
|
||||
// Retorna el nombre del layout que se acaba de aplicar (vacio si no se
|
||||
// aplico nada). Util para que la app actualice su `active_name`.
|
||||
std::string layout_storage_apply_pending(LayoutStorage* s);
|
||||
|
||||
// Borra un layout por nombre. Retorna true si se borro al menos una fila.
|
||||
bool layout_storage_delete(LayoutStorage* s, const std::string& name);
|
||||
|
||||
// Rellena `out` con callbacks que envuelven este storage. Patron de uso:
|
||||
//
|
||||
// auto* st = fn_ui::layout_storage_open("app.db");
|
||||
// fn_ui::LayoutCallbacks cb;
|
||||
// fn_ui::layout_storage_make_callbacks(st, cb);
|
||||
// // pasar cb a app_menubar() o layouts_menu_items() en cada frame
|
||||
// // y llamar fn_ui::layout_storage_apply_pending(st) al inicio del frame
|
||||
//
|
||||
// El handle `s` debe permanecer vivo durante toda la vida de los callbacks.
|
||||
// `out.active_name` se actualiza automaticamente en on_save/on_apply/on_reset
|
||||
// /on_delete.
|
||||
void layout_storage_make_callbacks(LayoutStorage* s, LayoutCallbacks& out);
|
||||
|
||||
} // namespace fn_ui
|
||||
@@ -0,0 +1,101 @@
|
||||
---
|
||||
name: layout_storage
|
||||
kind: function
|
||||
lang: cpp
|
||||
domain: core
|
||||
version: "1.0.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&); 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."
|
||||
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: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
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
|
||||
);
|
||||
```
|
||||
|
||||
`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).
|
||||
|
||||
## 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.
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
id: "0042"
|
||||
title: "C++ layout_storage: extraer y publicar como API reutilizable"
|
||||
status: completed
|
||||
created_at: 2026-04-28
|
||||
tags: [cpp, ui, refactor, layouts, sqlite]
|
||||
---
|
||||
|
||||
# 0042 — C++ layout_storage: extraer y publicar como API reutilizable
|
||||
|
||||
## Objetivo
|
||||
|
||||
Extraer la persistencia de layouts ImGui (privada en `shaders_lab/main.cpp`)
|
||||
a una funcion publica del registry: `layout_storage_cpp_core`. Cualquier app
|
||||
puede pasarla a `app_menubar` via `LayoutCallbacks` con un solo setup.
|
||||
|
||||
## Resultado
|
||||
|
||||
- Nueva funcion publica `cpp/functions/core/layout_storage.{h,cpp,md}` con
|
||||
handle opaco `LayoutStorage*` y helper `layout_storage_make_callbacks` que
|
||||
rellena un `LayoutCallbacks` listo para `app_menubar`.
|
||||
- Tabla SQLite `imgui_layouts(name, ini, updated_at)` creada con
|
||||
`CREATE TABLE IF NOT EXISTS` para no chocar con tablas pre-existentes.
|
||||
- `shaders_lab` migrado a la nueva API. La capa low-level
|
||||
`layout_storage_sqlite` se deja intacta para casos que comparten conexion.
|
||||
- `fn_framework` ahora enlaza `SQLite::SQLite3` para que cualquier app que
|
||||
use el framework tenga acceso a `layout_storage` sin trabajo extra.
|
||||
Reference in New Issue
Block a user