8f91b4ed23
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
445 lines
22 KiB
C++
445 lines
22 KiB
C++
#pragma once
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "types_registry.h"
|
|
#include "entity_ops.h"
|
|
#include "node_groups.h"
|
|
|
|
#include <cstdint>
|
|
#include <unordered_map>
|
|
|
|
struct GraphData;
|
|
struct GraphViewportState;
|
|
|
|
namespace ge {
|
|
|
|
// Discriminador de la NodeGroups window (issue 0036b). Una window puede
|
|
// estar respaldada por una tabla DuckDB (kind=Table) o por una agrupacion
|
|
// de entidades en operations.db via `entities.group_id` (kind=Group).
|
|
enum class NodeGroupsKind { Table, Group };
|
|
|
|
// Estado compartido entre las vistas y el bucle render. Pasado por puntero
|
|
// desde main.cpp.
|
|
struct AppState {
|
|
// Datos
|
|
GraphData* graph = nullptr;
|
|
GraphViewportState* viewport = nullptr;
|
|
|
|
// Layout activo — default grid (1) para que los grafos cargados de
|
|
// operations.db se distribuyan ordenadamente al abrir.
|
|
// Default: fixed (5) — respeta posiciones guardadas y physics off por
|
|
// defecto. El usuario activa fisicas con el boton Physics y/o cambia
|
|
// layout desde el dropdown Layout en la toolbar.
|
|
int layout_mode = 5; // 0=force, 1=grid, 2=circular, 3=radial, 4=hierarchical, 5=fixed
|
|
int apply_layout_tick = 0; // se incrementa cuando hay que reaplicar layout
|
|
bool want_unpin_all = false; // Reset layout: limpia NF_PINNED y reaplica
|
|
|
|
// Force layout — config + GPU toggle. Repulsion bajada de 1500→800
|
|
// (issue 0006 follow-up) para evitar movimiento excesivo al cargar
|
|
// grafos pequenos. Combinado con damping=0.7 y max_velocity=8 en
|
|
// run_force_step.
|
|
float repulsion = 800.0f;
|
|
float attraction = 0.04f;
|
|
float gravity = 0.005f;
|
|
bool use_gpu = false;
|
|
|
|
// Stats UI
|
|
int fps_estimate = 0; // sintetico, calculado en main loop
|
|
|
|
// Filters / visibility por tipo (longitud = graph->type_count o rel_type_count)
|
|
bool type_visible[256] = {};
|
|
bool rel_type_visible[256] = {};
|
|
int type_visible_n = 0;
|
|
int rel_type_visible_n = 0;
|
|
|
|
// Inspector
|
|
bool panel_legend = true;
|
|
bool panel_inspector = true;
|
|
bool panel_stats = true;
|
|
bool panel_viewport = true;
|
|
bool panel_note = false;
|
|
bool panel_jobs = false; // issue 0026
|
|
bool panel_chat = false; // claude -p chat (issue 0001)
|
|
bool show_filters_modal = false;
|
|
bool show_open_modal = false;
|
|
|
|
// Triggers — main.cpp lee estos flags y actua
|
|
bool want_fit = false;
|
|
bool want_save_layout = false;
|
|
bool want_reload = false;
|
|
bool want_open_file = false; // marcado al confirmar el modal Open
|
|
char open_buf[512] = {};
|
|
|
|
// Project system (issue 0006)
|
|
std::string active_project; // slug del proyecto activo
|
|
bool want_switch_project = false;
|
|
std::string switch_project_target; // slug objetivo del switch
|
|
bool show_new_project_modal = false;
|
|
char new_project_buf[80] = {};
|
|
std::string new_project_error; // mensaje a mostrar en el modal
|
|
std::vector<std::string> project_list_cache; // refrescado al abrir el menu Project
|
|
std::vector<std::string> project_recent_cache;
|
|
|
|
// Labels overlay
|
|
bool labels_enabled = true;
|
|
|
|
// Path activo de operations.db (para CRUD desde toolbar / contextmenu).
|
|
// main.cpp lo escribe tras cargar y los handlers lo leen.
|
|
std::string input_db_path;
|
|
|
|
// ---- Grouping (issue 0035b) ---------------------------------------------
|
|
// Estado de expansion de nodos `Group` en RAM. Default vacio = todos los
|
|
// grupos colapsados. No persiste entre sesiones (fase 1). El filtro del
|
|
// loader (apply_group_filter) consulta este map: si una entidad tiene
|
|
// `group_id != NULL` y el grupo padre no esta en este map con valor true,
|
|
// la entidad se oculta del grafo. Sin UI todavia para togglear; se setean
|
|
// valores manualmente desde tests/debug.
|
|
std::unordered_map<std::string, bool> group_expanded;
|
|
|
|
// Add-node toolbar input.
|
|
char add_buf[256] = {};
|
|
|
|
// Triggers de mutacion — main.cpp los procesa y dispara reload.
|
|
bool want_add_node = false; // commit del input add_buf
|
|
bool want_delete_node = false; // delete del nodo en ctx_node
|
|
bool want_duplicate_node = false;
|
|
bool want_change_type = false; // a ctx_new_type
|
|
int ctx_node = -1; // node_idx objetivo
|
|
char ctx_new_type[64] = {};
|
|
|
|
// Context menu state — popup global identificado por nombre.
|
|
bool ctx_open_request = false; // se setea en on_context_menu
|
|
|
|
// Note editor (panel "Note" abierto con doble click sobre nodo).
|
|
int note_node = -1; // node_idx siendo editado
|
|
std::string note_entity_id; // sql id resuelto
|
|
std::string note_entity_label; // display
|
|
std::string note_entity_type;
|
|
std::vector<char> note_buf; // editable, NUL-terminated
|
|
bool note_dirty = false;
|
|
bool want_save_note = false;
|
|
bool want_open_note = false; // doble click → cargar y abrir
|
|
int open_note_target = -1; // node_idx a abrir
|
|
|
|
// ---- Inspector editable (issue 0008) ----------------------------------
|
|
// Schema vivo del proyecto activo (load/save desde types.yaml).
|
|
ParsedTypes parsed_types;
|
|
|
|
// Draft del inspector — todo lo que el usuario esta editando para el
|
|
// nodo seleccionado. Se carga desde BD al cambiar la seleccion (si no
|
|
// hay cambios pendientes) y se persiste con entity_update al guardar.
|
|
int insp_node_idx = -1;
|
|
std::string insp_entity_id;
|
|
char insp_name_buf[256] = {};
|
|
char insp_type_buf[64] = {};
|
|
std::vector<char> insp_desc_buf; // multiline
|
|
int insp_status_idx = 0; // 0=active 1=stale 2=corrupted 3=archived
|
|
|
|
// Listas paralelas: keys + valores actuales de los campos de metadata.
|
|
// Las claves del schema del tipo van primero (en su orden), las "extras"
|
|
// van detras. `is_extra[i]` distingue para render diferenciado y para
|
|
// permitir borrar solo extras desde la UI.
|
|
std::vector<std::string> insp_field_keys;
|
|
std::vector<std::string> insp_field_values;
|
|
std::vector<unsigned char> insp_is_extra;
|
|
|
|
std::vector<std::string> insp_tags;
|
|
char insp_tag_input[64] = {};
|
|
char insp_extra_key[64] = {};
|
|
|
|
bool insp_dirty = false;
|
|
bool insp_show_unsaved = false;
|
|
int insp_pending_target = -1;
|
|
|
|
bool want_inspector_save = false;
|
|
bool want_inspector_discard = false;
|
|
|
|
// Caches refrescadas tras cargar grafo o tras Save.
|
|
std::vector<std::string> insp_tag_suggestions;
|
|
std::vector<std::string> insp_type_options;
|
|
|
|
// ---- Table node (issue 0010) ------------------------------------------
|
|
// Cache de conteo de filas por nodo Table indexado por user_data hash.
|
|
// Refrescado tras load_input y tras mutaciones que afecten a Tables.
|
|
std::unordered_map<uint64_t, int64_t> node_groups_counts;
|
|
|
|
// ---- NodeGroups window (issue 0011, renombrado en 0036a, kind en 0036b) -
|
|
// Estado runtime por ventana de NodeGroups. Hay dos kinds (issue 0036b):
|
|
// - Table: respaldada por DuckDB (el comportamiento original — un nodo
|
|
// `Table` del grafo que apunta a un .duckdb + table_name).
|
|
// - Group: respaldada por la propia operations.db. Lista las entidades
|
|
// hijas (`entities.group_id = container_id`) con columnas fijas
|
|
// id/name/type_ref/status/updated_at.
|
|
//
|
|
// Una entrada por container_id (entity_id del nodo contenedor — Table o
|
|
// Group). La ventana se cierra al pulsar la X de ImGui o, en kind=Table,
|
|
// al hacer set_expanded(false) desde el menu contextual.
|
|
struct NodeGroupsWindowState {
|
|
NodeGroupsKind kind = NodeGroupsKind::Table; // default compat 0036a
|
|
NodeGroupsMeta meta; // refrescada cada vez que entity cambia
|
|
int64_t total_rows = 0;
|
|
int64_t offset = 0;
|
|
std::vector<NodeGroupsRow> page;
|
|
bool page_dirty = true;
|
|
bool open = true; // bound a ImGui::Begin
|
|
bool focus_request = false; // 0036c: pedir SetWindowFocus
|
|
std::string last_error; // ultimo error de query (vacio = OK)
|
|
};
|
|
std::unordered_map<std::string, NodeGroupsWindowState> node_groups_windows;
|
|
|
|
// Triggers consumidos por main.cpp tras click en filas.
|
|
bool want_promote_row = false;
|
|
std::string promote_table_id; // entity_id del Table de origen
|
|
std::string promote_row_id; // valor del id_column
|
|
|
|
bool want_demote_entity = false;
|
|
std::string demote_entity_id;
|
|
|
|
bool want_focus_entity = false; // tras promote+open inspector
|
|
std::string focus_entity_id;
|
|
|
|
// Modal "Import dataset..." (issue 0011 Ingesta).
|
|
bool show_import_modal = false;
|
|
char import_path_buf[512] = {};
|
|
char import_table_buf[64] = {};
|
|
char import_duckdb_buf[256] = {}; // relativo a project root
|
|
char import_row_type_buf[64] = {};
|
|
bool want_import = false;
|
|
std::string import_error;
|
|
|
|
// Toggle NodeGroups window desde context menu del viewport.
|
|
bool want_toggle_nodegroups = false;
|
|
std::string toggle_nodegroups_id;
|
|
|
|
// ---- Table view (issue 0004) -------------------------------------------
|
|
// Vista tabular dockeable. Tabs por type_ref del grafo activo + opcional
|
|
// "All". Click selecciona el nodo en el viewport (mismo flujo que el
|
|
// Selectable del Inspector).
|
|
struct TableRow {
|
|
std::string id;
|
|
std::string name;
|
|
std::string type_ref;
|
|
std::string status;
|
|
std::string updated_at;
|
|
std::string group_id; // si pertenece a un Group, su sql id; "" si no
|
|
int neighbors = 0;
|
|
int node_idx = -1;
|
|
};
|
|
bool panel_table = false;
|
|
std::vector<TableRow> table_rows; // snapshot, refrescado tras load/reload
|
|
bool table_cache_dirty = true;
|
|
char table_search_buf[96] = {};
|
|
bool table_show_all = false;
|
|
int table_active_tab = 0;
|
|
// Filtros por columna: column_user_id (0..5) -> substring filter.
|
|
// Visible UX: right-click sobre header de columna abre popup con input;
|
|
// chips con filtros activos por encima de la tabla.
|
|
std::unordered_map<int, std::string> table_col_filters;
|
|
char table_filter_input[96] = {}; // buffer del popup activo
|
|
int table_filter_pending_col = -1; // col_user_id en edicion
|
|
|
|
// ---- Type Editor (issue 0007) ------------------------------------------
|
|
// Draft del editor de tipos. Se inicializa con una copia de parsed_types
|
|
// tras cargar el grafo. Save reescribe `types.yaml` y dispara
|
|
// apply_types_yaml + rebuild de IconAtlas.
|
|
bool panel_type_editor = false;
|
|
ParsedTypes types_draft;
|
|
bool types_dirty = false;
|
|
int te_tab_idx = 0; // 0=Entities 1=Relations
|
|
int te_entity_idx = -1; // seleccion entity
|
|
int te_relation_idx = -1; // seleccion relation
|
|
bool want_types_save = false;
|
|
bool want_types_reload = false;
|
|
int te_pending_delete_e = -1; // entity idx pendiente de confirmar
|
|
int te_pending_delete_r = -1;
|
|
int te_delete_use_count = 0; // entidades afectadas
|
|
bool show_te_delete_modal = false;
|
|
std::string types_save_error; // mensaje a renderizar bajo Save
|
|
|
|
// ---- Filtros y busqueda FTS5 (issue 0009) ------------------------------
|
|
// Modos: 0 = highlight (no-match dimmed), 1 = hide (no-match invisible).
|
|
enum FilterMode { FM_HIGHLIGHT = 0, FM_HIDE = 1 };
|
|
int filter_mode = FM_HIGHLIGHT;
|
|
char filter_query_buf[128] = {};
|
|
std::vector<std::string> filter_tags; // chips activos
|
|
std::vector<EntityHit> filter_hits; // dropdown FTS (max 20)
|
|
bool filter_dropdown_open = false;
|
|
bool filter_dirty = false; // pide reapply
|
|
int filter_focus_target = -1; // node_idx a centrar
|
|
char filter_tag_input[64] = {}; // input de chip nuevo
|
|
|
|
// ---- Enricher config window --------------------------------------------
|
|
// Cuando el usuario clica un enricher con `params` no vacios en el
|
|
// context menu, se rellena este bloque y se abre una ventana ImGui
|
|
// (no modal) que permite ajustar los valores antes de submitear el
|
|
// job. La ventana es dockeable y movible; cerrar la X cancela.
|
|
// Si el enricher no declara params, se submitea directamente con `{}`
|
|
// sin pasar por aqui.
|
|
bool enr_window_open = false; // visibilidad
|
|
std::string enr_modal_id; // enricher.id
|
|
std::string enr_modal_node_id; // sql_id del nodo
|
|
std::string enr_modal_node_label; // label visible
|
|
// Buffer editable por param. Tamano fijo 256 para inputs de texto;
|
|
// suficiente para queries y URLs cortas. Indices alineados con
|
|
// EnricherSpec::params del enricher seleccionado.
|
|
std::vector<std::vector<char>> enr_modal_param_bufs;
|
|
};
|
|
|
|
// Toolbar superior (Open file, Layout selector, Filters..., Fit, Save layout).
|
|
void views_toolbar(AppState& app);
|
|
|
|
// Panel Legend — checkboxes por tipo (entity / relation) con color swatch.
|
|
void views_legend(AppState& app);
|
|
|
|
// Panel Inspector — metadata del nodo seleccionado + vecinos.
|
|
void views_inspector(AppState& app);
|
|
|
|
// Stats line — counts + fps + energy + selection.
|
|
void views_stats(AppState& app);
|
|
|
|
// Note editor — abre con doble click sobre un nodo. Edita la columna `notes`
|
|
// (markdown) de la entidad y guarda con un boton.
|
|
void views_note(AppState& app);
|
|
|
|
// Modal Filters — toggles por tipo agrupados en columnas. Devuelve true si
|
|
// el usuario togglo algo.
|
|
bool views_filters_modal(AppState& app);
|
|
|
|
// Modal Open file — text input + boton Open.
|
|
bool views_open_modal(AppState& app);
|
|
|
|
// Modal "New project..." — slug input con validacion. Devuelve true si el
|
|
// usuario confirma con un slug valido.
|
|
bool views_new_project_modal(AppState& app);
|
|
|
|
// Refresca los flags `flags` de cada nodo/arista segun el array
|
|
// `type_visible[]` / `rel_type_visible[]`. Lineal en N+M.
|
|
void views_apply_visibility(AppState& app);
|
|
|
|
// Inicializa los arrays type_visible / rel_type_visible a true para todos
|
|
// los tipos del grafo activo. Llamar tras cargar/recargar el grafo.
|
|
void views_reset_visibility(AppState& app);
|
|
|
|
// ---- Inspector editable helpers (issue 0008) ------------------------------
|
|
|
|
// Refresca insp_tag_suggestions e insp_type_options leyendo BD y schema.
|
|
// Llamar tras cargar el grafo o tras un Save.
|
|
void views_inspector_refresh_caches(AppState& app);
|
|
|
|
// Carga el draft del Inspector desde la BD para el nodo `node_idx`. Si el
|
|
// nodo no es resoluble o no existe, deja el draft vacio. No respeta dirty:
|
|
// el caller debe haberlo manejado ya.
|
|
void views_inspector_load_draft(AppState& app, int node_idx,
|
|
const char* entity_id);
|
|
|
|
// Construye un EntityRecord desde el draft actual respetando el schema
|
|
// del type_ref para decidir is_string de cada metadata field.
|
|
EntityRecord views_inspector_build_record(const AppState& app);
|
|
|
|
// Resetea el draft (todos los buffers + dirty=false). Util tras Save o
|
|
// al cambiar de proyecto.
|
|
void views_inspector_clear_draft(AppState& app);
|
|
|
|
// ---- NodeGroups window (issue 0011, renombrado en 0036a) ----------------
|
|
|
|
// Renderiza una ventana ImGui dockeable por cada NodeGroups en
|
|
// node_groups_windows con `open=true`. Cabecera con nombres de columnas.
|
|
// Filas paginadas con ImGuiListClipper consumiendo el page cache; al
|
|
// cambiar el offset, marca dirty para que main.cpp refresque via
|
|
// node_groups_page. Doble click en fila no promovida -> setea
|
|
// promote_table_id/promote_row_id; promovida -> focus_entity_id. Cerrar la
|
|
// ventana setea expanded=false en BD.
|
|
void views_node_groups_window(AppState& app);
|
|
|
|
// Modal "Import dataset..." — formulario para crear una tabla DuckDB
|
|
// desde CSV/Parquet/JSON y registrar el nodo Table correspondiente.
|
|
bool views_import_dataset_modal(AppState& app);
|
|
|
|
// Sincroniza node_groups_windows con la metadata.expanded de cada nodo
|
|
// Table. Llamar tras load + tras mutaciones que cambien expanded. Crea
|
|
// entradas para nuevos expanded y borra las que ya no aplican.
|
|
void views_node_groups_windows_sync(AppState& app, const char* ops_db);
|
|
|
|
// Crea o reusa una entrada en `app.node_groups_windows[container_id]` y la
|
|
// marca con el `kind` indicado. Setea `focus_request = true` para que el
|
|
// render pueda llamar a ImGui::SetWindowFocus (lo consume 0036c). Si la
|
|
// entry ya existe se respeta su kind anterior y solo se setea
|
|
// focus_request — no recarga ni resetea offset. Si es nueva, llama a
|
|
// `node_groups_load_metadata` para popular `meta` (en kind=Group eso pre-
|
|
// puebla las columnas fijas; en kind=Table lee la metadata del Table-typed
|
|
// node de operations.db).
|
|
//
|
|
// `ops_db` es el path de operations.db. Si esta vacio, no se carga
|
|
// metadata pero la entry se crea de todos modos (caller puede rellenar
|
|
// despues). Devuelve puntero a la entry — nunca nullptr salvo
|
|
// container_id vacio.
|
|
AppState::NodeGroupsWindowState*
|
|
views_node_groups_open(AppState& app,
|
|
const std::string& container_id,
|
|
NodeGroupsKind kind,
|
|
const char* ops_db);
|
|
|
|
// ---- Table node overlay (issue 0010) ------------------------------------
|
|
|
|
// Dibuja un overlay rectangulo redondeado sobre cada nodo `Table` del grafo
|
|
// con etiqueta "Table · N rows" leyendo de app.node_groups_counts. Llamar
|
|
// despues de graph_viewport(...) — usa GetItemRectMin/Max + GetWindowDrawList
|
|
// del item viewport. No interactua con eventos; el hit-testing del nodo
|
|
// sigue usandolo el viewport circular de fondo.
|
|
void views_node_groups_overlay(AppState& app);
|
|
|
|
// ---- Table view (issue 0004) --------------------------------------------
|
|
|
|
// Renderiza el panel "Table". Lee de app.table_rows; el caller ya ha hecho el
|
|
// build/refresh tras cargar el grafo. Click en fila selecciona el nodo en el
|
|
// viewport (mismo flujo que el Selectable del Inspector). Filtro por
|
|
// substring sobre name/id en la cabecera.
|
|
void views_table(AppState& app);
|
|
|
|
// Recompute neighbors[] y node_idx[] de las filas existentes a partir del
|
|
// grafo cargado. Llamar tras cargar el grafo o tras una mutacion.
|
|
void views_table_refresh_indices(AppState& app);
|
|
|
|
// ---- Type Editor (issue 0007) -------------------------------------------
|
|
|
|
// Renderiza el panel "Types" — tabs Entities/Relations, lista a la izquierda
|
|
// con +/-, panel de edicion a la derecha. Marca app.types_dirty al cambiar y
|
|
// activa app.want_types_save / app.want_types_reload desde el footer.
|
|
void views_type_editor(AppState& app);
|
|
|
|
// Modal de confirmacion para borrar un tipo en uso. Se abre cuando
|
|
// app.show_te_delete_modal = true. main.cpp es responsable de poblar
|
|
// te_delete_use_count via consulta a operations.db antes de mostrarlo.
|
|
bool views_type_editor_delete_modal(AppState& app);
|
|
|
|
// ---- Jobs panel (issue 0026) ---------------------------------------------
|
|
|
|
// Renderiza el panel "Jobs" — tabla con todos los jobs (queued/running/done/
|
|
// error/cancelled). Botones por fila para cancelar / reintentar / borrar.
|
|
// Click en target_node centra el viewport sobre ese nodo (futuro). Polling
|
|
// cada N frames para no spammear la BD.
|
|
void views_jobs(AppState& app);
|
|
|
|
// ---- Filter helpers (issue 0009) -----------------------------------------
|
|
|
|
// True si el filtro tiene query no vacia o al menos un tag activo.
|
|
bool views_filter_active(const AppState& app);
|
|
|
|
// Anade un tag como chip si no existe ya. Marca filter_dirty = true.
|
|
void views_filter_add_tag(AppState& app, const char* tag);
|
|
|
|
// Limpia query + tags y marca filter_dirty.
|
|
void views_filter_clear(AppState& app);
|
|
|
|
// Reaplica el filtro al grafo: recompute la mascara de visibilidad/alpha
|
|
// segun filter_query, filter_tags, filter_mode. Llama a la BD si hace
|
|
// falta (FTS + tags). Resetea color_override a 0 en los nodos que pasan
|
|
// el filtro y aplica un alpha bajo en los que no (modo highlight) o limpia
|
|
// NF_VISIBLE (modo hide). Tambien refresca filter_hits para el dropdown
|
|
// con max 20 resultados ordenados por rank.
|
|
void views_filter_apply(AppState& app);
|
|
|
|
} // namespace ge
|