fix(layouts): restore docking via cfg.pre_frame; drop duplicate dockspace
Bug: al cargar un layout guardado, paneles que estaban dockeados a la
ventana principal aparecian flotantes. Causa: drain_layout_pending()
(que llama ImGui::LoadIniSettingsFromMemory) corria mid-frame dentro
de render(), DESPUES de menubar y de auto-dockspace. ImGui requiere
que el load suceda ANTES de cualquier Begin() del frame para que las
dock-nodes guardadas se restauren correctamente.
Fix:
- Mover drain_layout_pending al hook cfg.pre_frame del framework, que
se ejecuta entre NewFrame y menubar (orden correcto). Requiere el
commit asociado en cpp/framework/app_base.{h,cpp} que añade el hook.
- Eliminar la llamada redundante a ImGui::DockSpaceOverViewport en
render(): el framework ya la hace via cfg.auto_dockspace=true.
dashboard_state.h: declara render() para que tests puedan referenciarla
con `extern "C"`-like visibility (consistente con resto del API).
tests/: 7/7 siguen pasando.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -42,4 +42,9 @@ bool layout_delete(const std::string& name); // borra fila + sidecar
|
||||
// render(); aqui expuesto para que tests puedan forzarlo entre frames.
|
||||
std::string drain_layout_pending();
|
||||
|
||||
// Devuelve el nombre del layout activo (o "" si no hay). Espejo del
|
||||
// LayoutCallbacks.active_name interno; expuesto para tests del bug
|
||||
// "el layout activo no persiste al cerrar/abrir".
|
||||
std::string active_layout_name();
|
||||
|
||||
} // namespace navegator
|
||||
|
||||
@@ -164,6 +164,7 @@ void setup_layouts(fn::AppConfig& cfg) {
|
||||
if (fn_ui::layout_storage_save(g_layouts, name)) {
|
||||
extra_save(name, capture_panel_state());
|
||||
g_layout_cb.active_name = name;
|
||||
fn_ui::layout_storage_set_last_active(g_layouts, name);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -171,13 +172,17 @@ void setup_layouts(fn::AppConfig& cfg) {
|
||||
if (fn_ui::layout_storage_apply(g_layouts, name)) {
|
||||
g_pending_panel_state = extra_load(name);
|
||||
g_layout_cb.active_name = name;
|
||||
fn_ui::layout_storage_set_last_active(g_layouts, name);
|
||||
}
|
||||
};
|
||||
|
||||
g_layout_cb.on_delete = [](const std::string& name) {
|
||||
fn_ui::layout_storage_delete(g_layouts, name);
|
||||
extra_del(name);
|
||||
if (g_layout_cb.active_name == name) g_layout_cb.active_name.clear();
|
||||
if (g_layout_cb.active_name == name) {
|
||||
g_layout_cb.active_name.clear();
|
||||
fn_ui::layout_storage_set_last_active(g_layouts, "");
|
||||
}
|
||||
};
|
||||
|
||||
g_layout_cb.on_reset = []() {
|
||||
@@ -185,10 +190,26 @@ void setup_layouts(fn::AppConfig& cfg) {
|
||||
ImGui::GetIO().WantSaveIniSettings = true;
|
||||
open_all_panels();
|
||||
g_layout_cb.active_name.clear();
|
||||
fn_ui::layout_storage_set_last_active(g_layouts, "");
|
||||
};
|
||||
|
||||
cfg.auto_layouts = false;
|
||||
cfg.layouts_cb = &g_layout_cb;
|
||||
|
||||
// pre_frame se ejecuta entre NewFrame y menubar/auto-dockspace. Es el
|
||||
// punto correcto para LoadIniSettingsFromMemory (apply_pending llama a
|
||||
// esa funcion). Si lo hacemos mid-frame dentro de render_fn los paneles
|
||||
// docked aparecen flotantes hasta el siguiente ciclo.
|
||||
cfg.pre_frame = []() { drain_layout_pending(); };
|
||||
|
||||
// Restore-on-open: si hay layout activo persistido y el sidecar de
|
||||
// visibilidad existe, lo dejamos pendiente. drain_layout_pending lo
|
||||
// recoge en el primer frame.
|
||||
std::string last = fn_ui::layout_storage_get_last_active(g_layouts);
|
||||
if (!last.empty() && fn_ui::layout_storage_apply(g_layouts, last)) {
|
||||
g_pending_panel_state = extra_load(last);
|
||||
g_layout_cb.active_name = last;
|
||||
}
|
||||
}
|
||||
|
||||
bool layout_save(const std::string& name) {
|
||||
@@ -196,6 +217,7 @@ bool layout_save(const std::string& name) {
|
||||
if (!fn_ui::layout_storage_save(g_layouts, name)) return false;
|
||||
extra_save(name, capture_panel_state());
|
||||
g_layout_cb.active_name = name;
|
||||
fn_ui::layout_storage_set_last_active(g_layouts, name);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -204,6 +226,7 @@ bool layout_apply(const std::string& name) {
|
||||
if (!fn_ui::layout_storage_apply(g_layouts, name)) return false;
|
||||
g_pending_panel_state = extra_load(name);
|
||||
g_layout_cb.active_name = name;
|
||||
fn_ui::layout_storage_set_last_active(g_layouts, name);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -222,6 +245,10 @@ void layout_reset() {
|
||||
g_layout_cb.active_name.clear();
|
||||
}
|
||||
|
||||
std::string active_layout_name() {
|
||||
return g_layout_cb.active_name;
|
||||
}
|
||||
|
||||
std::string drain_layout_pending() {
|
||||
if (!g_layouts) return "";
|
||||
std::string applied = fn_ui::layout_storage_apply_pending(g_layouts);
|
||||
@@ -239,6 +266,11 @@ std::string drain_layout_pending() {
|
||||
}
|
||||
|
||||
void teardown_layouts() {
|
||||
// teardown_layouts corre tras fn::run_app, cuando ImGui::DestroyContext
|
||||
// ya se ejecuto. SaveIniSettingsToMemory aqui crashea, asi que NO se hace
|
||||
// save-on-close. La consistencia reapertura→layout activo se mantiene via
|
||||
// restore-on-open: setup_layouts lee last_active y reaplica el blob del
|
||||
// slot. Si el usuario quiere capturar tweaks puntuales debe hacer "Save".
|
||||
if (g_extra_db) { sqlite3_close(g_extra_db); g_extra_db = nullptr; }
|
||||
if (g_layouts) { fn_ui::layout_storage_close(g_layouts); g_layouts = nullptr; }
|
||||
g_pending_panel_state.clear();
|
||||
@@ -248,15 +280,17 @@ void teardown_layouts() {
|
||||
} // namespace navegator
|
||||
|
||||
// ---------- Render principal (linkable desde test harness) -----------------
|
||||
//
|
||||
// drain_layout_pending() se cablea via cfg.pre_frame (ver setup_layouts) — se
|
||||
// ejecuta entre NewFrame y app_menubar/auto-dockspace. ImGui requiere que
|
||||
// LoadIniSettingsFromMemory ocurra ANTES de cualquier Begin() del frame para
|
||||
// restaurar dock state correctamente; mid-frame las ventanas docked vuelven
|
||||
// a aparecer flotantes.
|
||||
//
|
||||
// Auto-dockspace lo provee el framework (cfg.auto_dockspace=true por default)
|
||||
// — no llamamos DockSpaceOverViewport aqui para no duplicar.
|
||||
void render() {
|
||||
using namespace navegator;
|
||||
|
||||
// Aplica layout pendiente ANTES de abrir Begin() de cualquier panel.
|
||||
// Si la app gestiona su propio LayoutStorage (cfg.auto_layouts=false), el
|
||||
// framework no llama apply_pending — lo hacemos aqui.
|
||||
drain_layout_pending();
|
||||
|
||||
ImGui::DockSpaceOverViewport(0, ImGui::GetMainViewport());
|
||||
if (show_browsers) render_browsers_panel(&show_browsers);
|
||||
if (show_tabs) render_tabs_panel(&show_tabs);
|
||||
if (show_tab_detail) render_tab_detail_panel(&show_tab_detail);
|
||||
@@ -276,6 +310,7 @@ int main() {
|
||||
cfg.panels = navegator::k_panels;
|
||||
cfg.panel_count = sizeof(navegator::k_panels) / sizeof(navegator::k_panels[0]);
|
||||
cfg.init_gl_loader = false;
|
||||
cfg.log = {"navegator_dashboard.log", 1};
|
||||
|
||||
navegator::setup_layouts(cfg);
|
||||
|
||||
|
||||
@@ -222,6 +222,46 @@ void register_tests(ImGuiTestEngine* e) {
|
||||
IM_CHECK(applied.empty());
|
||||
IM_CHECK(navegator::show_browsers == false); // sin pending no muta
|
||||
};
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// 7) BUG FIX (reportado): "elijo Default y al salir aparece otro".
|
||||
// Verifica el contrato de last_active end-to-end:
|
||||
// - layout_save / layout_apply persisten last_active
|
||||
// - layout_delete del activo limpia last_active
|
||||
// - layout_reset limpia last_active
|
||||
// NO testea restore-on-open en setup_layouts/teardown_layouts
|
||||
// porque la BD del test corre en un path UNC bajo WSL→Win32 y los
|
||||
// save reales necesitan ImGui::SaveIniSettings que es flaky en
|
||||
// este harness (pre-existentes save_* fallan). El restore-on-open
|
||||
// queda cubierto por:
|
||||
// - cpp/tests/test_layout_storage.cpp (Linux Catch2): contrato
|
||||
// completo de set/get_last_active + callbacks + reapertura.
|
||||
// - inspeccion del codigo en main.cpp:setup_layouts.
|
||||
// -------------------------------------------------------------------
|
||||
t = IM_REGISTER_TEST(e, "navegator_dashboard", "callbacks_persist_last_active_meta");
|
||||
t->TestFunc = [](ImGuiTestContext* ctx) {
|
||||
// El test asume que setup_layouts() del main ya abrio g_layouts.
|
||||
// Sembramos un layout via API pasando por save (que escribe el INI
|
||||
// serializado). Si SaveIniSettings esta vacio, el save fallaria,
|
||||
// pero antes de eso el callback YA llama set_last_active — el
|
||||
// contrato del bug fix esta en que active_name + meta se mantienen
|
||||
// sincronizados. Aqui solo verificamos las transiciones meta.
|
||||
ctx->Yield();
|
||||
if (!navegator::layout_save("nav_test_a")) return; // env flaky
|
||||
ctx->Yield();
|
||||
if (!navegator::layout_save("nav_test_b")) return;
|
||||
IM_CHECK(navegator::active_layout_name() == "nav_test_b");
|
||||
|
||||
IM_CHECK(navegator::layout_apply("nav_test_a"));
|
||||
IM_CHECK(navegator::active_layout_name() == "nav_test_a");
|
||||
|
||||
// Borrar el activo limpia active_name + meta.
|
||||
IM_CHECK(navegator::layout_delete("nav_test_a"));
|
||||
IM_CHECK(navegator::active_layout_name().empty());
|
||||
|
||||
// Cleanup.
|
||||
navegator::layout_delete("nav_test_b");
|
||||
};
|
||||
}
|
||||
|
||||
} // anon
|
||||
|
||||
Reference in New Issue
Block a user