feat(0036c): doble click en Group abre NodeGroups; cleanup Table panel

Cambia el dispatch del doble click sobre nodos del viewport: si el tipo
es Group o Table, ahora abre/enfoca la NodeGroups window correspondiente
via views_node_groups_open(...). El branch de Group ya no carga el
panel Table generico con un filtro group_id (logica heredada de 0035d
que provocaba el bug de "tabla vacia").

Limpieza correlativa en views_table:
  - Eliminado el breadcrumb "Group: <name> (N)" + boton Clear filter.
  - Eliminado el filtro r.group_id != table_filter_group_id en
    build_visible y la restriccion de types_present.
  - Eliminado el reset on-close de los campos de filtro.

Eliminados los campos AppState::table_filter_group_id y
table_filter_group_name (audit: git grep table_filter_group_id devuelve
vacio fuera de issues/).

Render de NodeGroups ahora consume focus_request: llama
SetNextWindowFocus() antes de Begin y SetWindowFocus() dentro, asi la
window queda al frente tanto al crearse como al re-enfocarse.

El right-click "Open NodeGroups" del context menu sigue intacto
(want_toggle_nodegroups + node_groups_set_expanded). El doble click es
flujo paralelo nuevo.

Refs: issues/0036c-double-click-group-opens-nodegroups.md
This commit is contained in:
2026-05-04 00:56:44 +02:00
parent 7277f63985
commit 8f91b4ed23
3 changed files with 27 additions and 62 deletions
+13 -42
View File
@@ -1733,41 +1733,12 @@ void render_one_table(AppState& app, std::vector<int>& visible_indices) {
} // namespace
void views_table(AppState& app) {
// Si el panel se cierra, limpiamos el filtro de grupo (RAM-only).
if (!app.panel_table) {
if (!app.table_filter_group_id.empty()) {
app.table_filter_group_id.clear();
app.table_filter_group_name.clear();
}
return;
}
if (!app.panel_table) return;
if (!ImGui::Begin("Table", &app.panel_table)) {
ImGui::End();
return;
}
// Breadcrumb de drill-in por grupo (issue 0035d).
if (!app.table_filter_group_id.empty()) {
// Contar filas que pertenecen a este grupo (sin aplicar otros filtros).
size_t n_group = 0;
for (const auto& r : app.table_rows) {
if (r.group_id == app.table_filter_group_id) ++n_group;
}
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.85f, 0.75f, 0.35f, 1.0f));
ImGui::Text(TI_FOLDER " Group: %s (%zu)",
app.table_filter_group_name.empty()
? app.table_filter_group_id.c_str()
: app.table_filter_group_name.c_str(),
n_group);
ImGui::PopStyleColor();
ImGui::SameLine();
if (fn_ui::button("Clear group filter", fn_ui::ButtonVariant::Subtle)) {
app.table_filter_group_id.clear();
app.table_filter_group_name.clear();
}
ImGui::Separator();
}
// Toolbar superior: search + show all.
ImGui::SetNextItemWidth(220);
ImGui::InputTextWithHint("##tsearch", TI_SEARCH " filter name/id...",
@@ -1806,15 +1777,12 @@ void views_table(AppState& app) {
return;
}
// Indices por tipo. Si hay filtro de grupo (issue 0035d) acotamos los
// types tabulables a los presentes dentro del grupo.
// Indices por tipo presentes en el snapshot.
std::vector<std::string> types_present;
types_present.reserve(8);
{
std::unordered_set<std::string> seen;
for (const auto& r : app.table_rows) {
if (!app.table_filter_group_id.empty()
&& r.group_id != app.table_filter_group_id) continue;
if (seen.insert(r.type_ref).second) types_present.push_back(r.type_ref);
}
std::sort(types_present.begin(), types_present.end());
@@ -1825,10 +1793,6 @@ void views_table(AppState& app) {
v.reserve(app.table_rows.size());
for (size_t i = 0; i < app.table_rows.size(); ++i) {
const auto& r = app.table_rows[i];
// Drill-in por grupo (issue 0035d): si hay filtro activo, solo
// pasan filas cuyo group_id coincide.
if (!app.table_filter_group_id.empty()
&& r.group_id != app.table_filter_group_id) continue;
if (type_filter && r.type_ref != type_filter) continue;
if (app.table_search_buf[0]
&& !ci_contains(r.name, app.table_search_buf)
@@ -2036,13 +2000,20 @@ void views_node_groups_window(AppState& app) {
m.entity_id.c_str());
}
ImGui::SetNextWindowSize(ImVec2(640, 460), ImGuiCond_FirstUseEver);
if (w.focus_request) {
// El render lo consume 0036c — por ahora simplemente lo limpiamos
// tras un frame para que no se quede pegado. Cuando 0036c llegue
// anadira ImGui::SetNextWindowFocus() aqui.
const bool focus_now = w.focus_request;
if (focus_now) {
// 0036c: forzar foco antes de Begin para que la window quede al
// frente al abrirse o reabrirse desde doble click.
ImGui::SetNextWindowFocus();
w.focus_request = false;
}
if (!ImGui::Begin(title, &w.open)) { ImGui::End(); continue; }
if (focus_now) {
// Doble seguridad: tambien lo llamamos dentro del Begin/End por
// si la window estaba ya abierta y ImGui ignora SetNextWindowFocus
// en ese caso (raro, pero barato).
ImGui::SetWindowFocus();
}
// Header de info (varia por kind)
if (is_group) {