data_table: Phase 2 — Button + events + tooltip + RightClick + TQL persist column_specs (issue 0081-O)
- CellRenderer::Button=5: renders SmallButton per cell; emits TableEvent::ButtonClick on click - TableEventKind enum (ButtonClick/RowDoubleClick/RowRightClick/CellEdit) + TableEvent struct - render() extended overload: adds events_out parameter (nullptr = back-compat, no events) - RowDoubleClick and RowRightClick detection in raw table loop (stage 0) - RowRightClick also detected in aggregated stage table (stage 1+) - Tooltip per cell: tooltip_on_hover + tooltip fields on ColumnSpec; "auto" = show cell value - State::aux_column_specs: TQL-persisted column specs sidecar per table - tql_emit: serializes aux_column_specs[0] as column_specs block (badge/progress/duration/icon/button/tooltip) - tql_apply: parses column_specs block back into state.aux_column_specs[0] - render() merges aux_column_specs into TableInput when caller passes empty column_specs - test_column_specs: 5->8 tests (Button struct, tooltip fields, both render() signatures link) - tql_emit_test: 3 new tests (column_specs badge/button/tooltip emit) — 52 passed - tql_apply_test: 3 new tests (column_specs badge/button/tooltip roundtrip) — 106 passed - Back-compat: existing apps (graph_explorer, registry_dashboard) unchanged - Version bump: data_table v1.1.0 -> v1.2.0 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,21 +1,25 @@
|
||||
# data_table_renderers — declarative cell renderers (v1.1.0)
|
||||
# data_table_renderers — declarative cell renderers (v1.2.0)
|
||||
|
||||
Tag: `cpp-tables` (mismo grupo que TQL; los renderers son parte del stack `data_table`).
|
||||
|
||||
Extiende `data_table_cpp_viz` con una API declarativa para renderizar columnas con
|
||||
Badge, Progress, Duration e Icon **sin escribir ImGui inline**. Activado via el
|
||||
campo opcional `column_specs` de `TableInput`. Back-compat 100%: apps sin
|
||||
`column_specs` no necesitan cambios.
|
||||
Badge, Progress, Duration, Icon y **Button** (Phase 2), emitir eventos de interaccion
|
||||
(ButtonClick, RowDoubleClick, RowRightClick), mostrar tooltips por celda y persistir
|
||||
los specs en TQL (`aux_column_specs` roundtrip). Back-compat 100%: apps sin
|
||||
`column_specs` ni `events_out` no necesitan cambios.
|
||||
|
||||
## Tipos nuevos en `data_table_types.h`
|
||||
## Tipos nuevos / actualizados en `data_table_types.h`
|
||||
|
||||
| Tipo | Que es |
|
||||
|---|---|
|
||||
| `CellRenderer` | enum class: `Text=0`, `Badge=1`, `Progress=2`, `Duration=3`, `Icon=4` |
|
||||
| `CellRenderer` | enum class: `Text=0`, `Badge=1`, `Progress=2`, `Duration=3`, `Icon=4`, **`Button=5`** |
|
||||
| `BadgeRule` | value (exact match) + color_hex + label opcional |
|
||||
| `IconMapEntry` | value + icon_name (ej. `"TI_BOLT"`) + color_hex opcional |
|
||||
| `ColumnSpec` | id + renderer + badges / progress fields / duration thresholds / icon_map |
|
||||
| `ColumnSpec` | id + renderer + badges / progress / duration / icon_map / **button_action, button_label, button_color_hex** / **tooltip, tooltip_on_hover** |
|
||||
| `TableInput::column_specs` | `std::vector<ColumnSpec>` sidecar opcional (size 0 o == cols) |
|
||||
| **`TableEventKind`** | enum class: ButtonClick=1, RowDoubleClick=2, RowRightClick=3, CellEdit=4 (reservado) |
|
||||
| **`TableEvent`** | kind + row + col + column_id + action_id + value |
|
||||
| **`State::aux_column_specs`** | specs persistidos en TQL (roundtrip via tql_emit/tql_apply) |
|
||||
|
||||
## Ejemplo canonico: Recent Executions (status Badge + duration Duration)
|
||||
|
||||
@@ -107,21 +111,78 @@ TI_COPY TI_EXTERNAL_LINK
|
||||
|
||||
Si el `icon_name` no esta en la tabla, la celda se renderiza como texto plano.
|
||||
|
||||
## Ejemplo Phase 2: Button + events + tooltip
|
||||
|
||||
```cpp
|
||||
// --- Setup ---
|
||||
t.column_specs.resize(t.cols);
|
||||
|
||||
// Columna "actions": boton Cancel por fila
|
||||
t.column_specs[col_actions].renderer = data_table::CellRenderer::Button;
|
||||
t.column_specs[col_actions].button_action = "cancel";
|
||||
t.column_specs[col_actions].button_label = "Cancel";
|
||||
t.column_specs[col_actions].button_color_hex = "#ef4444";
|
||||
|
||||
// Columna "status": tooltip automatico (muestra valor truncado)
|
||||
t.column_specs[col_status].tooltip = "auto";
|
||||
t.column_specs[col_status].tooltip_on_hover = true;
|
||||
|
||||
// --- Render loop ---
|
||||
events.clear();
|
||||
data_table::render("##t", {t}, st, &events);
|
||||
|
||||
// --- Procesar eventos ---
|
||||
for (const auto& ev : events) {
|
||||
if (ev.kind == data_table::TableEventKind::ButtonClick &&
|
||||
ev.action_id == "cancel") {
|
||||
cancel_row(ev.row);
|
||||
}
|
||||
if (ev.kind == data_table::TableEventKind::RowDoubleClick) {
|
||||
open_detail(ev.row);
|
||||
}
|
||||
if (ev.kind == data_table::TableEventKind::RowRightClick) {
|
||||
// Abrir menu propio via ImGui::BeginPopup
|
||||
ctx_menu_row = ev.row;
|
||||
ImGui::OpenPopup("##ctx");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Ejemplo Phase 2: TQL roundtrip de column_specs
|
||||
|
||||
```cpp
|
||||
// Persistir specs en State.aux_column_specs para guardar en .tql
|
||||
data_table::ColumnSpec cs;
|
||||
cs.id = "status"; cs.renderer = data_table::CellRenderer::Badge;
|
||||
cs.badges = {{"ok","#22c55e","OK"},{"error","#ef4444",""}};
|
||||
st.aux_column_specs = {{cs}}; // [tabla_0: {spec_col_0}]
|
||||
|
||||
// tql_emit serializa aux_column_specs automaticamente
|
||||
std::string tql = tql::emit(st, headers, types);
|
||||
// tql_apply lo recupera en res.state.aux_column_specs
|
||||
auto res = tql::apply(tql, headers);
|
||||
// render() lo aplica si TableInput.column_specs esta vacio
|
||||
data_table::render("##t", {t}, res.state, &events);
|
||||
```
|
||||
|
||||
## Fronteras
|
||||
|
||||
- **Solo Column 0..N posicional**: `column_specs[i]` aplica a la columna en posicion `i` del `TableInput` original. No se mapea por nombre (Phase 2).
|
||||
- **No persiste en TQL**: `column_specs` son responsabilidad del caller — se construyen cada frame o en el setup. `tql_emit`/`tql_apply` no los tocan (Phase 2 planificado).
|
||||
- **No implementa Button/TextInput/Custom** (Phase 2-3 separados).
|
||||
- **Solo Column 0..N posicional**: `column_specs[i]` aplica a la columna en posicion `i` del `TableInput` original. No se mapea por nombre.
|
||||
- **Button con celda vacia**: si el cell value es vacio, NO se dibuja boton. Poner un valor no vacio en la celda para habilitar el boton.
|
||||
- **No implementa TextInput/Custom** (Phase 3 separado).
|
||||
- **Stage N (agregado)**: los renderers se aplican por posicion de columna del output agregado — si el breakout cambia el numero de columnas, revisar los indices.
|
||||
- **RowRightClick**: en el raw table (stage 0) el evento se emite pero el popup de drill-down interno sigue abriendose. La app puede abrir su propio popup al detectar el evento.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- `column_specs.size()` debe ser 0 (sin specs) o igual a `t.cols`. Mezcla de tamaños puede causar out-of-bounds silencioso (el render hace `c < column_specs.size()` guard, pero es mejor ser expliciito).
|
||||
- `column_specs.size()` debe ser 0 (sin specs) o igual a `t.cols`. Mezcla de tamaños puede causar out-of-bounds silencioso (el render hace `c < column_specs.size()` guard, pero es mejor ser explicitoo).
|
||||
- `hex_to_imcolor` acepta `"#rrggbb"` o `"rrggbb"`. Alpha siempre 1.0. Sin soporte para `rgba`.
|
||||
- El ColorRule existente de State (`st.color_rules`) sigue funcionando — ambos sistemas coexisten. Si hay conflicto, `column_specs` toma prioridad para el contenido de la celda; `color_rules` pinta el fondo via `TableSetBgColor`.
|
||||
- En el renderer Badge el `Selectable` con background coloreado consume el item para hover/click — la seleccion de rango con drag puede verse afectada visualmente en columnas Badge.
|
||||
- `events_out` no se limpia en `render()` — el caller debe llamar `events.clear()` antes de cada frame.
|
||||
- `aux_column_specs` solo se persiste para `tables[0]` (el main table). Specs para tablas extra deben gestionarse por el caller.
|
||||
|
||||
## Notas
|
||||
|
||||
- Tests: `cpp/tests/test_column_specs.cpp` (5 tests: 1 back-compat + 4 renderer types). Smoke/linker; no requieren ImGui context.
|
||||
- TQL roundtrip pendiente: issue 0081-O (Phase 2).
|
||||
- Tests: `cpp/tests/test_column_specs.cpp` (8 tests: 1 back-compat + 4 renderer types + 3 Phase 2). Smoke/linker; no requieren ImGui context.
|
||||
- TQL roundtrip implementado en Phase 2 (issue 0081-O, v1.2.0): `tql_emit_test` (3 tests) + `tql_apply_test` (9 tests nuevos).
|
||||
|
||||
Reference in New Issue
Block a user