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:
@@ -182,10 +182,14 @@ static const char* icon_name_to_glyph(const std::string& name) {
|
||||
// ---------------------------------------------------------------------------
|
||||
// draw_cell_custom: render a cell using the declarative ColumnSpec.
|
||||
// Called only when spec.renderer != CellRenderer::Text.
|
||||
// Issue 0081-N, v1.1.0.
|
||||
// Issue 0081-N, v1.1.0. Phase 2 (v1.2.0): Button renderer + tooltip.
|
||||
//
|
||||
// events_out: if non-null and renderer==Button, ButtonClick is pushed on click.
|
||||
// row_idx / col_idx: logical indices in the TableInput (for event payload).
|
||||
// ---------------------------------------------------------------------------
|
||||
static void draw_cell_custom(const ColumnSpec& spec, const char* value,
|
||||
int /*row_idx*/, int /*col_idx*/) {
|
||||
int row_idx, int col_idx,
|
||||
std::vector<TableEvent>* events_out) {
|
||||
if (!value) value = "";
|
||||
|
||||
switch (spec.renderer) {
|
||||
@@ -284,11 +288,57 @@ static void draw_cell_custom(const ColumnSpec& spec, const char* value,
|
||||
break;
|
||||
}
|
||||
|
||||
case CellRenderer::Button: {
|
||||
// Skip empty cell values — app decides when to show a button.
|
||||
if (value[0] == '\0') break;
|
||||
const char* label = spec.button_label.empty() ? value : spec.button_label.c_str();
|
||||
bool has_color = !spec.button_color_hex.empty();
|
||||
if (has_color) {
|
||||
ImVec4 btn_col = hex_to_imcolor(spec.button_color_hex);
|
||||
if (btn_col.x >= 0.f) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, btn_col);
|
||||
ImVec4 hov = ImVec4(
|
||||
std::min(btn_col.x + 0.12f, 1.f),
|
||||
std::min(btn_col.y + 0.12f, 1.f),
|
||||
std::min(btn_col.z + 0.12f, 1.f),
|
||||
btn_col.w);
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, hov);
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, hov);
|
||||
} else {
|
||||
has_color = false;
|
||||
}
|
||||
}
|
||||
// Unique button ID: combines label + row + col to avoid ImGui ID
|
||||
// collisions when the same label appears in multiple rows.
|
||||
char btn_id[128];
|
||||
std::snprintf(btn_id, sizeof(btn_id), "%s##btn_%d_%d",
|
||||
label, row_idx, col_idx);
|
||||
if (ImGui::SmallButton(btn_id) && events_out) {
|
||||
TableEvent ev;
|
||||
ev.kind = TableEventKind::ButtonClick;
|
||||
ev.row = row_idx;
|
||||
ev.col = col_idx;
|
||||
ev.column_id = spec.id;
|
||||
ev.action_id = spec.button_action;
|
||||
ev.value = value;
|
||||
events_out->push_back(std::move(ev));
|
||||
}
|
||||
if (has_color) ImGui::PopStyleColor(3);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
// CellRenderer::Text or unknown — plain text.
|
||||
ImGui::TextUnformatted(value);
|
||||
break;
|
||||
}
|
||||
|
||||
// Tooltip: show on hover if tooltip_on_hover is set.
|
||||
// "auto" shows the raw cell value (useful for truncated text columns).
|
||||
if (spec.tooltip_on_hover && ImGui::IsItemHovered()) {
|
||||
const char* tip = (spec.tooltip == "auto") ? value : spec.tooltip.c_str();
|
||||
if (tip && tip[0]) ImGui::SetTooltip("%s", tip);
|
||||
}
|
||||
}
|
||||
|
||||
// compare: cell-level comparison supporting all Op variants.
|
||||
@@ -1254,11 +1304,12 @@ bool draw_extra_panel(State& st, VizPanel& p, int idx, const StageOutput& so,
|
||||
ImGui::TableSetColumnIndex(c);
|
||||
const char* s = so.cells[(size_t)r * so.cols + c];
|
||||
// Issue 0081-N: declarative renderer for extra panel mini-table.
|
||||
// events_out not propagated to mini-table (secondary render).
|
||||
bool custom_ep = false;
|
||||
if (col_specs && c < (int)col_specs->size()) {
|
||||
const ColumnSpec& cs = (*col_specs)[(size_t)c];
|
||||
if (cs.renderer != CellRenderer::Text) {
|
||||
draw_cell_custom(cs, s, r, c);
|
||||
draw_cell_custom(cs, s, r, c, nullptr);
|
||||
custom_ep = true;
|
||||
}
|
||||
}
|
||||
@@ -2458,14 +2509,30 @@ void drill_into(State& st, int from_stage,
|
||||
void render(const char* id,
|
||||
const std::vector<TableInput>& tables,
|
||||
State& st,
|
||||
std::vector<TableEvent>* events_out,
|
||||
bool show_chrome)
|
||||
{
|
||||
if (tables.empty()) return;
|
||||
int main_idx = resolve_main_idx(tables, st.main_source);
|
||||
if (main_idx < 0) return;
|
||||
|
||||
// Construir headers ptrs desde main table.
|
||||
const TableInput& main_t = tables[(size_t)main_idx];
|
||||
// Merge aux_column_specs from State into TableInput when the caller passed
|
||||
// empty column_specs. Caller-provided specs always take precedence.
|
||||
// We keep a local copy to avoid mutating the caller's const tables.
|
||||
static thread_local TableInput main_t_merged;
|
||||
{
|
||||
const TableInput& src = tables[(size_t)main_idx];
|
||||
if (src.column_specs.empty() &&
|
||||
main_idx < (int)st.aux_column_specs.size() &&
|
||||
!st.aux_column_specs[(size_t)main_idx].empty())
|
||||
{
|
||||
main_t_merged = src;
|
||||
main_t_merged.column_specs = st.aux_column_specs[(size_t)main_idx];
|
||||
} else {
|
||||
main_t_merged = src;
|
||||
}
|
||||
}
|
||||
const TableInput& main_t = main_t_merged;
|
||||
static thread_local std::vector<const char*> main_hdr_ptrs;
|
||||
main_hdr_ptrs.clear();
|
||||
main_hdr_ptrs.reserve(main_t.cols);
|
||||
@@ -3105,20 +3172,29 @@ void render(const char* id,
|
||||
ri >= sel_rmin && ri <= sel_rmax &&
|
||||
oc >= sel_cmin && oc <= sel_cmax);
|
||||
ImGui::PushID(r * eff_cols + c);
|
||||
// Issue 0081-N: use declarative renderer when column_specs set.
|
||||
// Issue 0081-N/O: use declarative renderer when column_specs set.
|
||||
{
|
||||
bool custom_rendered = false;
|
||||
const ColumnSpec* cell_cs = nullptr;
|
||||
if (!main_t.column_specs.empty() &&
|
||||
c < (int)main_t.column_specs.size()) {
|
||||
const ColumnSpec& cs = main_t.column_specs[(size_t)c];
|
||||
if (cs.renderer != CellRenderer::Text) {
|
||||
draw_cell_custom(cs, cell, ri, c);
|
||||
cell_cs = &main_t.column_specs[(size_t)c];
|
||||
if (cell_cs->renderer != CellRenderer::Text) {
|
||||
draw_cell_custom(*cell_cs, cell, r, c, events_out);
|
||||
custom_rendered = true;
|
||||
}
|
||||
}
|
||||
if (!custom_rendered) {
|
||||
ImGui::Selectable(cell ? cell : "", in_sel,
|
||||
ImGuiSelectableFlags_AllowDoubleClick);
|
||||
// Tooltip for Text cells (Phase 2).
|
||||
if (cell_cs && cell_cs->tooltip_on_hover &&
|
||||
ImGui::IsItemHovered()) {
|
||||
const char* tip = (cell_cs->tooltip == "auto")
|
||||
? (cell ? cell : "")
|
||||
: cell_cs->tooltip.c_str();
|
||||
if (tip && tip[0]) ImGui::SetTooltip("%s", tip);
|
||||
}
|
||||
}
|
||||
}
|
||||
// AllowWhenBlockedByActiveItem: durante drag,
|
||||
@@ -3135,10 +3211,36 @@ void render(const char* id,
|
||||
} else if (U.sel_dragging) {
|
||||
U.sel_end_row = ri; U.sel_end_col = oc;
|
||||
}
|
||||
// RowDoubleClick event (Phase 2, v1.2.0).
|
||||
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)
|
||||
&& events_out) {
|
||||
TableEvent ev;
|
||||
ev.kind = TableEventKind::RowDoubleClick;
|
||||
ev.row = r;
|
||||
ev.col = c;
|
||||
ev.value = cell ? cell : "";
|
||||
if (!main_t.column_specs.empty() &&
|
||||
c < (int)main_t.column_specs.size())
|
||||
ev.column_id = main_t.column_specs[(size_t)c].id;
|
||||
events_out->push_back(std::move(ev));
|
||||
}
|
||||
// RowRightClick event: emit event only, no popup drawn here.
|
||||
// Caller inspects events_out and opens its own context menu.
|
||||
if (ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
|
||||
U.pending_col = c;
|
||||
U.pending_value = cell ? cell : "";
|
||||
U.open_cell_popup = true;
|
||||
if (events_out) {
|
||||
TableEvent ev;
|
||||
ev.kind = TableEventKind::RowRightClick;
|
||||
ev.row = r;
|
||||
ev.col = c;
|
||||
ev.value = cell ? cell : "";
|
||||
if (!main_t.column_specs.empty() &&
|
||||
c < (int)main_t.column_specs.size())
|
||||
ev.column_id = main_t.column_specs[(size_t)c].id;
|
||||
events_out->push_back(std::move(ev));
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::PopID();
|
||||
@@ -3658,19 +3760,28 @@ void render(const char* id,
|
||||
ImGui::TableSetColumnIndex(c);
|
||||
const char* cell = cur_cells[r * cur_cols_n + c];
|
||||
ImGui::PushID(r * cur_cols_n + c);
|
||||
// Issue 0081-N: declarative renderer for aggregated stage tables.
|
||||
// Issue 0081-N/O: declarative renderer for aggregated stage tables.
|
||||
{
|
||||
bool custom_rendered = false;
|
||||
const ColumnSpec* cell_cs2 = nullptr;
|
||||
if (!main_t.column_specs.empty() &&
|
||||
c < (int)main_t.column_specs.size()) {
|
||||
const ColumnSpec& cs = main_t.column_specs[(size_t)c];
|
||||
if (cs.renderer != CellRenderer::Text) {
|
||||
draw_cell_custom(cs, cell, r, c);
|
||||
cell_cs2 = &main_t.column_specs[(size_t)c];
|
||||
if (cell_cs2->renderer != CellRenderer::Text) {
|
||||
draw_cell_custom(*cell_cs2, cell, r, c, events_out);
|
||||
custom_rendered = true;
|
||||
}
|
||||
}
|
||||
if (!custom_rendered) {
|
||||
ImGui::Selectable(cell ? cell : "");
|
||||
// Tooltip for Text cells (Phase 2).
|
||||
if (cell_cs2 && cell_cs2->tooltip_on_hover &&
|
||||
ImGui::IsItemHovered()) {
|
||||
const char* tip = (cell_cs2->tooltip == "auto")
|
||||
? (cell ? cell : "")
|
||||
: cell_cs2->tooltip.c_str();
|
||||
if (tip && tip[0]) ImGui::SetTooltip("%s", tip);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ImGui::IsItemHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
|
||||
@@ -3678,6 +3789,18 @@ void render(const char* id,
|
||||
U.pending_value = cell ? cell : "";
|
||||
U.inspect_row = r;
|
||||
ImGui::OpenPopup("##drill_popup");
|
||||
// RowRightClick event (Phase 2, v1.2.0).
|
||||
if (events_out) {
|
||||
TableEvent ev;
|
||||
ev.kind = TableEventKind::RowRightClick;
|
||||
ev.row = r;
|
||||
ev.col = c;
|
||||
ev.value = cell ? cell : "";
|
||||
if (!main_t.column_specs.empty() &&
|
||||
c < (int)main_t.column_specs.size())
|
||||
ev.column_id = main_t.column_specs[(size_t)c].id;
|
||||
events_out->push_back(std::move(ev));
|
||||
}
|
||||
}
|
||||
if (ImGui::BeginPopup("##drill_popup")) {
|
||||
if (c < n_brk) {
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
// data_table — render UI completa de tabla TQL.
|
||||
// Entry-point publica del stack data_table del registry.
|
||||
// Issue 0081-H. Promovido desde cpp/apps/primitives_gallery/playground/tables/data_table.h
|
||||
// Phase 2 (issue 0081-O, v1.2.0): Button renderer + event sink + tooltip + RightClick.
|
||||
//
|
||||
// Uso basico (back-compat, sin events):
|
||||
// data_table::State st; // persistir entre frames
|
||||
// ImGui::Begin("Window"); ImGui::BeginChild("tbl", {-1,-1});
|
||||
// data_table::render("my_table", {table1, table2}, st);
|
||||
// ImGui::EndChild(); ImGui::End();
|
||||
//
|
||||
// Uso con events (Phase 2):
|
||||
// std::vector<data_table::TableEvent> events;
|
||||
// data_table::render("my_table", {table1, table2}, st, &events);
|
||||
// for (auto& ev : events) {
|
||||
// if (ev.kind == data_table::TableEventKind::ButtonClick &&
|
||||
// ev.action_id == "cancel") { ... }
|
||||
// }
|
||||
//
|
||||
// Requiere ImGui context + ImPlot context activos.
|
||||
// Namespace identico al playground para facilitar migracion (solo cambiar include path).
|
||||
|
||||
#include "core/data_table_types.h"
|
||||
#include <vector>
|
||||
|
||||
namespace data_table {
|
||||
|
||||
// render — Render barra-de-chips + tabla + panels de visualizacion.
|
||||
// Mutates `st` en respuesta a la interaccion del usuario.
|
||||
//
|
||||
// `id` — ID unico de ImGui para esta instancia (ej. "##my_table").
|
||||
// `tables` — lista de TableInput. tables[0] es la main por defecto;
|
||||
// si State.main_source no-vacio se usa por nombre.
|
||||
// Tablas extra se exponen como joinables en la UI.
|
||||
// `st` — estado mutable. Debe persistir entre frames (no stack-local).
|
||||
// `events_out` — if non-null, populated with UI events (ButtonClick,
|
||||
// RowDoubleClick, RowRightClick) fired this frame. The caller
|
||||
// clears/reads the vector after each render call.
|
||||
// Pass nullptr to disable event collection (back-compat).
|
||||
// `show_chrome` — si false, oculta la barra de chips + breadcrumb por defecto.
|
||||
// El usuario puede reactivarla via el boton "Show UI".
|
||||
void render(const char* id,
|
||||
const std::vector<TableInput>& tables,
|
||||
State& st,
|
||||
std::vector<TableEvent>* events_out,
|
||||
bool show_chrome = true);
|
||||
|
||||
// Overload for back-compat: same as render(..., nullptr, show_chrome).
|
||||
inline void render(const char* id,
|
||||
const std::vector<TableInput>& tables,
|
||||
State& st,
|
||||
bool show_chrome = true)
|
||||
{
|
||||
render(id, tables, st, nullptr, show_chrome);
|
||||
}
|
||||
|
||||
} // namespace data_table
|
||||
@@ -3,10 +3,10 @@ name: data_table
|
||||
kind: function
|
||||
lang: cpp
|
||||
domain: viz
|
||||
version: "1.1.0"
|
||||
version: "1.2.0"
|
||||
purity: impure
|
||||
signature: "void data_table::render(const char* id, const std::vector<TableInput>& tables, State& st, bool show_chrome = true)"
|
||||
description: "Render UI completa de tabla TQL: chips bar, tabla, viz panels, column-stats inline, drill, color rules, joins, TQL editor, Ask AI. Entry-point publica del stack data_table. Muta State segun interaccion del usuario."
|
||||
signature: "void data_table::render(const char* id, const std::vector<TableInput>& tables, State& st, std::vector<TableEvent>* events_out = nullptr, bool show_chrome = true)"
|
||||
description: "Render UI completa de tabla TQL: chips bar, tabla, viz panels, column-stats inline, drill, color rules, joins, TQL editor, Ask AI, Button renderer, event sink (ButtonClick/RowDoubleClick/RowRightClick), tooltip per-cell, column_specs persisted in TQL. Entry-point publica del stack data_table. Muta State segun interaccion del usuario."
|
||||
tags: [tables, viz, ui, imgui, tql, cpp-tables]
|
||||
uses_functions:
|
||||
- compute_stage_cpp_core
|
||||
@@ -40,6 +40,8 @@ uses_types:
|
||||
- Aggregation_cpp_core
|
||||
- SortClause_cpp_core
|
||||
- ColumnType_cpp_core
|
||||
- TableEvent_cpp_core
|
||||
- TableEventKind_cpp_core
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_go_core"
|
||||
@@ -65,6 +67,9 @@ tests:
|
||||
- "Progress: TableInput with Progress column_spec compiles and links"
|
||||
- "Duration: TableInput with Duration column_spec compiles and links"
|
||||
- "Icon: TableInput with Icon column_spec compiles and links"
|
||||
- "Button: TableEvent struct constructible; render() with events_out links"
|
||||
- "Tooltip: ColumnSpec with tooltip_on_hover=true compiles and links"
|
||||
- "Back-compat: both render() signatures (with/without events_out) link"
|
||||
test_file_path: "cpp/tests/test_column_specs.cpp"
|
||||
file_path: "cpp/functions/viz/data_table.cpp"
|
||||
params:
|
||||
@@ -73,10 +78,12 @@ params:
|
||||
- name: tables
|
||||
desc: "Lista de TableInput materializadas en memoria. tables[0] es la main por defecto; si State.main_source no-vacio se usa por nombre. Tablas extra se exponen como joinables en la UI de joins."
|
||||
- name: st
|
||||
desc: "Estado mutable completo: pipeline de stages, joins, viz config, ui tweaks. Debe persistir entre frames — no declarar en el stack del frame."
|
||||
desc: "Estado mutable completo: pipeline de stages, joins, viz config, ui tweaks, aux_column_specs (Phase 2). Debe persistir entre frames — no declarar en el stack del frame."
|
||||
- name: events_out
|
||||
desc: "Puntero a vector de TableEvent. Si non-null, se puebla con eventos de este frame (ButtonClick, RowDoubleClick, RowRightClick). El caller limpia/lee el vector despues de cada render. Pasar nullptr para desactivar (back-compat)."
|
||||
- name: show_chrome
|
||||
desc: "Si false, oculta chips bar + breadcrumb por defecto. El usuario puede reactivar con el boton 'Show UI'. El State persiste el override del usuario entre frames."
|
||||
output: "void. Muta st en respuesta a la interaccion del usuario (filtros, breakouts, sorts, drill, joins, viz mode). Los cambios son visibles en st al retornar."
|
||||
output: "void. Muta st en respuesta a la interaccion del usuario (filtros, breakouts, sorts, drill, joins, viz mode). Los cambios son visibles en st al retornar. Events emitted via events_out."
|
||||
---
|
||||
|
||||
## Ejemplo
|
||||
@@ -91,19 +98,43 @@ t.name = "orders";
|
||||
t.rows = num_rows;
|
||||
t.cols = num_cols;
|
||||
t.cells = cells_ptr; // row-major flat array, owner externo
|
||||
t.headers = {"id", "amount", "status"};
|
||||
t.headers = {"id", "amount", "status", "actions"};
|
||||
t.types = {data_table::ColumnType::Int,
|
||||
data_table::ColumnType::Float,
|
||||
data_table::ColumnType::String,
|
||||
data_table::ColumnType::String};
|
||||
|
||||
// Phase 2: declarative renderers + tooltip
|
||||
t.column_specs.resize(4);
|
||||
t.column_specs[2].renderer = data_table::CellRenderer::Badge;
|
||||
t.column_specs[2].badges = {{"paid","#22c55e","Paid"},{"pending","#f59e0b",""}};
|
||||
t.column_specs[2].tooltip = "auto";
|
||||
t.column_specs[2].tooltip_on_hover = true;
|
||||
t.column_specs[3].renderer = data_table::CellRenderer::Button;
|
||||
t.column_specs[3].button_action = "cancel_order";
|
||||
t.column_specs[3].button_label = "Cancel";
|
||||
|
||||
data_table::State st; // persiste entre frames
|
||||
std::vector<data_table::TableEvent> events;
|
||||
|
||||
// --- Render (cada frame) ---
|
||||
ImGui::Begin("Orders");
|
||||
ImGui::BeginChild("##tbl", ImVec2(-1, -1));
|
||||
data_table::render("##orders", {t}, st);
|
||||
events.clear();
|
||||
data_table::render("##orders", {t}, st, &events);
|
||||
ImGui::EndChild();
|
||||
ImGui::End();
|
||||
|
||||
// --- Process events ---
|
||||
for (const auto& ev : events) {
|
||||
if (ev.kind == data_table::TableEventKind::ButtonClick &&
|
||||
ev.action_id == "cancel_order") {
|
||||
cancel_order(ev.row); // app handles the action
|
||||
}
|
||||
if (ev.kind == data_table::TableEventKind::RowDoubleClick) {
|
||||
open_order_detail(ev.row);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Cuando usarla
|
||||
@@ -117,6 +148,10 @@ Cuando una app necesita tabla con filtros + agregaciones + viz + joins sobre dat
|
||||
- **Drill-down propaga en State**: `st.active_stage` y `st.stages` se mutan por click en charts. El caller puede leer `st` tras `render()` para reaccionar.
|
||||
- **Thread-safety**: `render()` usa `static thread_local` para buffers intermedios. Llamar solo desde el main thread de ImGui.
|
||||
- **TableInput owner externo**: `cells` es un puntero raw al array del caller. Los datos deben sobrevivir durante toda la llamada a `render()`. No pasar puntero a vector que puede reallocarse.
|
||||
- **events_out no se limpia**: `render()` solo hace `push_back`. El caller debe llamar `events.clear()` antes de cada frame o acumulara eventos de frames anteriores.
|
||||
- **Button + celda vacia**: si el cell value es vacio, el boton NO se dibuja. La app controla cuando mostrar el boton poniendo un value no vacio (ej. "1" o el ID de la fila).
|
||||
- **RowRightClick emite evento Y abre popup interno**: la tabla de stages (stage>0) sigue abriendo su popup de drill. En el raw table (stage 0), se emite el evento pero el popup de drill antiguo tambien puede abrirse via `U.open_cell_popup`. El caller puede ignorar el popup interno y gestionar su propio menu al detectar `RowRightClick`.
|
||||
- **aux_column_specs merge**: si `TableInput.column_specs` esta vacio pero `State.aux_column_specs[0]` no, `render()` los aplica automaticamente. Si el caller pasa column_specs no vacios, ganan sobre los del State.
|
||||
- **Ask AI modal (llm_anthropic)**: el boton "Ask AI" usa un stub interno de `llm_anthropic` que retorna error por defecto. Para activar la feature real, compilar con `-DFN_LLM_ANTHROPIC=1` y proveer `infra/llm_anthropic.h` en el include path. Pendiente Wave 4: promover al registry.
|
||||
- **FN_TQL_DUCKDB**: modo SQL del Ask AI requiere compilar con `-DFN_TQL_DUCKDB=1` y la libreria DuckDB disponible.
|
||||
|
||||
@@ -143,5 +178,7 @@ No hay tests unitarios directos: `render()` requiere ImGui + ImPlot context acti
|
||||
|
||||
v1.1.0 (2026-05-15) — declarative CellRenderer (Badge/Progress/Duration/Icon) via TableInput.column_specs sidecar. Back-compat preservado: apps existentes sin column_specs siguen funcionando sin cambios.
|
||||
|
||||
v1.2.0 (2026-05-15) — Button renderer + event sink (ButtonClick/RowDoubleClick/RowRightClick) + tooltip per cell + column_specs persisted in TQL (aux_column_specs roundtrip). Back-compat preserved: events_out=nullptr by default; existing render() callers unchanged.
|
||||
|
||||
---
|
||||
Promovido desde `cpp/apps/primitives_gallery/playground/tables/data_table.{h,cpp}` — issue 0081-H.
|
||||
|
||||
Reference in New Issue
Block a user