Files
primitives_gallery/playground/tables/data_table.cpp
T
2026-05-11 16:30:43 +02:00

245 lines
8.6 KiB
C++

#include "data_table.h"
#include "imgui.h"
#include <cstdio>
#include <string>
#include <unordered_map>
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<int, std::string> filter_inputs; // col -> buffer
std::unordered_map<int, std::string> color_value_inputs; // col -> buffer
std::unordered_map<int, ImVec4> 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