feat(infra): auto-commit con 11 cambios
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -35,6 +35,10 @@ bool ensure_schema(sqlite3* db) {
|
||||
" name TEXT PRIMARY KEY,"
|
||||
" ini TEXT NOT NULL,"
|
||||
" updated_at INTEGER NOT NULL"
|
||||
");"
|
||||
"CREATE TABLE IF NOT EXISTS layout_meta ("
|
||||
" key TEXT PRIMARY KEY,"
|
||||
" value TEXT NOT NULL"
|
||||
");";
|
||||
char* errmsg = nullptr;
|
||||
int rc = sqlite3_exec(db, sql, nullptr, nullptr, &errmsg);
|
||||
@@ -42,6 +46,17 @@ bool ensure_schema(sqlite3* db) {
|
||||
return rc == SQLITE_OK;
|
||||
}
|
||||
|
||||
bool layout_exists(sqlite3* db, const std::string& name) {
|
||||
sqlite3_stmt* stmt = nullptr;
|
||||
if (sqlite3_prepare_v2(db,
|
||||
"SELECT 1 FROM imgui_layouts WHERE name = ? LIMIT 1;",
|
||||
-1, &stmt, nullptr) != SQLITE_OK) return false;
|
||||
sqlite3_bind_text(stmt, 1, name.c_str(), -1, SQLITE_TRANSIENT);
|
||||
bool found = (sqlite3_step(stmt) == SQLITE_ROW);
|
||||
sqlite3_finalize(stmt);
|
||||
return found;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// ── API ───────────────────────────────────────────────────────────────────
|
||||
@@ -154,6 +169,42 @@ bool layout_storage_delete(LayoutStorage* s, const std::string& name) {
|
||||
return rc == SQLITE_DONE && changes > 0;
|
||||
}
|
||||
|
||||
bool layout_storage_set_last_active(LayoutStorage* s, const std::string& name) {
|
||||
if (!s || !s->db) return false;
|
||||
sqlite3_stmt* stmt = nullptr;
|
||||
if (name.empty()) {
|
||||
if (sqlite3_prepare_v2(s->db,
|
||||
"DELETE FROM layout_meta WHERE key = 'last_active';",
|
||||
-1, &stmt, nullptr) != SQLITE_OK) return false;
|
||||
} else {
|
||||
if (sqlite3_prepare_v2(s->db,
|
||||
"INSERT INTO layout_meta (key, value) VALUES ('last_active', ?) "
|
||||
"ON CONFLICT(key) DO UPDATE SET value = excluded.value;",
|
||||
-1, &stmt, nullptr) != SQLITE_OK) return false;
|
||||
sqlite3_bind_text(stmt, 1, name.c_str(), -1, SQLITE_TRANSIENT);
|
||||
}
|
||||
int rc = sqlite3_step(stmt);
|
||||
sqlite3_finalize(stmt);
|
||||
return rc == SQLITE_DONE;
|
||||
}
|
||||
|
||||
std::string layout_storage_get_last_active(LayoutStorage* s) {
|
||||
if (!s || !s->db) return "";
|
||||
sqlite3_stmt* stmt = nullptr;
|
||||
if (sqlite3_prepare_v2(s->db,
|
||||
"SELECT value FROM layout_meta WHERE key = 'last_active';",
|
||||
-1, &stmt, nullptr) != SQLITE_OK) return "";
|
||||
std::string name;
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
const unsigned char* t = sqlite3_column_text(stmt, 0);
|
||||
if (t) name.assign(reinterpret_cast<const char*>(t));
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
if (name.empty()) return "";
|
||||
if (!layout_exists(s->db, name)) return ""; // referencia colgante: ignorar
|
||||
return name;
|
||||
}
|
||||
|
||||
void layout_storage_make_callbacks(LayoutStorage* s, LayoutCallbacks& out) {
|
||||
out.list = [s]() {
|
||||
return layout_storage_list(s);
|
||||
@@ -164,23 +215,29 @@ void layout_storage_make_callbacks(LayoutStorage* s, LayoutCallbacks& out) {
|
||||
// confirme que se aplico (la app debe tomar el valor de retorno
|
||||
// y propagarlo). Aqui ya lo dejamos optimistamente.
|
||||
out.active_name = name;
|
||||
layout_storage_set_last_active(s, name);
|
||||
}
|
||||
};
|
||||
out.on_save = [s, &out](const std::string& name) {
|
||||
if (layout_storage_save(s, name)) {
|
||||
out.active_name = name;
|
||||
layout_storage_set_last_active(s, name);
|
||||
}
|
||||
};
|
||||
out.on_delete = [s, &out](const std::string& name) {
|
||||
layout_storage_delete(s, name);
|
||||
if (out.active_name == name) out.active_name.clear();
|
||||
if (out.active_name == name) {
|
||||
out.active_name.clear();
|
||||
layout_storage_set_last_active(s, "");
|
||||
}
|
||||
};
|
||||
out.on_reset = [&out]() {
|
||||
out.on_reset = [s, &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();
|
||||
layout_storage_set_last_active(s, "");
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -67,6 +67,15 @@ 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);
|
||||
|
||||
// Persiste el nombre del ultimo layout activo en la BD (tabla layout_meta,
|
||||
// key='last_active'). Pasar string vacio limpia la entrada. Retorna true si
|
||||
// la operacion completo. Idempotente.
|
||||
bool layout_storage_set_last_active(LayoutStorage* s, const std::string& name);
|
||||
|
||||
// Lee el nombre del ultimo layout activo (o "" si no hay). Si el nombre
|
||||
// almacenado ya no existe en imgui_layouts (porque se borro), retorna "".
|
||||
std::string layout_storage_get_last_active(LayoutStorage* s);
|
||||
|
||||
// Rellena `out` con callbacks que envuelven este storage. Patron de uso:
|
||||
//
|
||||
// auto* st = fn_ui::layout_storage_open("app.db");
|
||||
|
||||
@@ -3,10 +3,10 @@ name: layout_storage
|
||||
kind: function
|
||||
lang: cpp
|
||||
domain: core
|
||||
version: "1.0.0"
|
||||
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&); 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."
|
||||
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: []
|
||||
@@ -14,9 +14,9 @@ returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_go_core"
|
||||
imports: [sqlite3, imgui]
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
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
|
||||
@@ -42,10 +42,23 @@ CREATE TABLE IF NOT EXISTS imgui_layouts (
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user