#include "data_table.h" #include "imgui.h" #include #include #include namespace data_table { namespace { // Estado UI por-celda/por-header — sobrevive entre frames pero NO se persiste // a disco. Si se promueve al registry hay que pasarlo al State del caller. struct UiState { int pending_col = -1; std::string pending_value; bool open_cell_popup = false; int header_popup_col = -1; std::unordered_map filter_inputs; // col -> buffer std::unordered_map color_value_inputs; // col -> buffer std::unordered_map color_picker_vals; // col -> color }; UiState& ui() { static UiState s; return s; } void ensure_init(State& st, int cols) { if ((int)st.col_visible.size() != cols) st.col_visible.assign(cols, true); } void draw_chips(State& st, const char* const* headers, int cols) { if (st.filters.empty()) { ImGui::TextDisabled("Sin filtros. Click en celda -> elige operador."); return; } for (size_t i = 0; i < st.filters.size(); ) { const auto& f = st.filters[i]; const char* hdr = (f.col >= 0 && f.col < cols) ? headers[f.col] : "?"; char buf[256]; std::snprintf(buf, sizeof(buf), "%s %s %s x##chip%zu", hdr, op_label(f.op), f.value.c_str(), i); ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(60, 100, 160, 220)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, IM_COL32(80, 130, 200, 240)); ImGui::PushStyleColor(ImGuiCol_ButtonActive, IM_COL32(45, 80, 130, 240)); bool clicked = ImGui::SmallButton(buf); ImGui::PopStyleColor(3); if (clicked) { st.filters.erase(st.filters.begin() + i); continue; } ImGui::SameLine(); ++i; } ImGui::NewLine(); } // Devuelve true y rellena out si el usuario eligio un operador. bool draw_op_menu_items(Op& out) { const Op ops[] = {Op::Eq, Op::Neq, Op::Gt, Op::Gte, Op::Lt, Op::Lte}; for (Op o : ops) { if (ImGui::MenuItem(op_label(o))) { out = o; return true; } } return false; } void draw_header_menu(State& st, int col, const char* const* headers, int col_count) { auto& U = ui(); auto& fbuf = U.filter_inputs[col]; fbuf.resize(256, '\0'); if (ImGui::BeginMenu("Filter...")) { ImGui::SetNextItemWidth(180); ImGui::InputText("##filterval", fbuf.data(), fbuf.size()); std::string val(fbuf.c_str()); const Op ops[] = {Op::Eq, Op::Neq, Op::Gt, Op::Gte, Op::Lt, Op::Lte}; for (size_t i = 0; i < sizeof(ops)/sizeof(ops[0]); ++i) { if (i > 0) ImGui::SameLine(); if (ImGui::SmallButton(op_label(ops[i]))) { st.filters.push_back({col, ops[i], val}); ImGui::CloseCurrentPopup(); } } ImGui::EndMenu(); } if (ImGui::BeginMenu("Conditional color")) { auto& vbuf = U.color_value_inputs[col]; vbuf.resize(256, '\0'); auto it = U.color_picker_vals.find(col); if (it == U.color_picker_vals.end()) { U.color_picker_vals[col] = ImVec4(0.85f, 0.40f, 0.30f, 0.60f); } ImVec4& cv = U.color_picker_vals[col]; ImGui::SetNextItemWidth(180); ImGui::InputText("equals", vbuf.data(), vbuf.size()); ImGui::ColorEdit4("color", &cv.x, ImGuiColorEditFlags_NoInputs); if (ImGui::Button("Apply")) { ImU32 c = ImGui::ColorConvertFloat4ToU32(cv); st.color_rules.push_back({col, std::string(vbuf.c_str()), (unsigned int)c}); ImGui::CloseCurrentPopup(); } ImGui::SameLine(); if (ImGui::Button("Clear col")) { for (size_t i = 0; i < st.color_rules.size();) { if (st.color_rules[i].col == col) st.color_rules.erase(st.color_rules.begin() + i); else ++i; } } ImGui::EndMenu(); } if (ImGui::MenuItem("Hide column")) { st.col_visible[col] = false; } ImGui::Separator(); if (ImGui::BeginMenu("Columns")) { for (int k = 0; k < col_count; ++k) { bool v = st.col_visible[k]; if (ImGui::Checkbox(headers[k], &v)) st.col_visible[k] = v; } if (ImGui::MenuItem("Show all")) { for (int k = 0; k < col_count; ++k) st.col_visible[k] = true; } ImGui::EndMenu(); } } } // namespace void render(const char* id, const char* const* headers, int col_count, const char* const* cells, int row_count, State& st) { ensure_init(st, col_count); auto& U = ui(); draw_chips(st, headers, col_count); auto visible_rows = compute_visible_rows(cells, row_count, col_count, st); int visible_cols = 0; for (bool v : st.col_visible) if (v) ++visible_cols; ImGui::Text("Filas: %d / %d Columnas: %d / %d", (int)visible_rows.size(), row_count, visible_cols, col_count); if (visible_cols == 0) { ImGui::TextDisabled("(todas las columnas ocultas - click derecho en cabecera anterior)"); return; } ImGuiTableFlags flags = ImGuiTableFlags_Borders | ImGuiTableFlags_Sortable | ImGuiTableFlags_SortMulti | ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY | ImGuiTableFlags_Reorderable; if (!ImGui::BeginTable(id, visible_cols, flags, ImVec2(0, 0))) return; // Setup columns with UserID = column index del dataset original. for (int c = 0; c < col_count; ++c) { if (!st.col_visible[c]) continue; ImGui::TableSetupColumn(headers[c], ImGuiTableColumnFlags_None, 0.0f, (ImGuiID)c); } ImGui::TableSetupScrollFreeze(0, 1); // Custom header row para soportar right-click context menu por columna. ImGui::TableNextRow(ImGuiTableRowFlags_Headers); int draw_col = 0; for (int c = 0; c < col_count; ++c) { if (!st.col_visible[c]) continue; ImGui::TableSetColumnIndex(draw_col++); ImGui::PushID(c); ImGui::TableHeader(headers[c]); if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { U.header_popup_col = c; ImGui::OpenPopup("##hdr_menu"); } if (ImGui::BeginPopup("##hdr_menu") && U.header_popup_col == c) { draw_header_menu(st, c, headers, col_count); ImGui::EndPopup(); } ImGui::PopID(); } // Aplicar sort specs de ImGui -> State. if (ImGuiTableSortSpecs* specs = ImGui::TableGetSortSpecs()) { if (specs->SpecsDirty && specs->SpecsCount > 0) { const ImGuiTableColumnSortSpecs& s = specs->Specs[0]; st.sort_col = (int)s.ColumnUserID; st.sort_desc = (s.SortDirection == ImGuiSortDirection_Descending); specs->SpecsDirty = false; visible_rows = compute_visible_rows(cells, row_count, col_count, st); } else if (specs->SpecsCount == 0 && st.sort_col >= 0) { st.sort_col = -1; visible_rows = compute_visible_rows(cells, row_count, col_count, st); } } // Body. for (int r : visible_rows) { ImGui::TableNextRow(); int dc = 0; for (int c = 0; c < col_count; ++c) { if (!st.col_visible[c]) continue; ImGui::TableSetColumnIndex(dc++); const char* cell = cells[r * col_count + c]; for (const auto& cr : st.color_rules) { if (cr.col == c && cell && cr.equals == cell) { ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, (ImU32)cr.color); break; } } ImGui::PushID(r * col_count + c); if (ImGui::Selectable(cell ? cell : "", false, ImGuiSelectableFlags_AllowDoubleClick)) { U.pending_col = c; U.pending_value = cell ? cell : ""; U.open_cell_popup = true; } ImGui::PopID(); } } ImGui::EndTable(); if (U.open_cell_popup) { ImGui::OpenPopup("##cell_op"); U.open_cell_popup = false; } if (ImGui::BeginPopup("##cell_op")) { const char* hdr = (U.pending_col >= 0 && U.pending_col < col_count) ? headers[U.pending_col] : "?"; ImGui::TextDisabled("%s ?? \"%s\"", hdr, U.pending_value.c_str()); ImGui::Separator(); Op picked; if (draw_op_menu_items(picked)) { st.filters.push_back({U.pending_col, picked, U.pending_value}); ImGui::CloseCurrentPopup(); } ImGui::EndPopup(); } } } // namespace data_table