#include "gfx/dag_panel.h" #include "gfx/dag_catalog.h" #include "imgui.h" #include #include namespace fn::gfx { static constexpr int MAX_NODES = 16; static uint64_t s_next_id = 1; static std::string make_id() { return "n" + std::to_string(s_next_id++); } static ImVec4 kind_color(DagKind kind) { switch (kind) { case DagKind::Gen: return ImVec4(0.25f, 0.55f, 0.90f, 1.0f); case DagKind::Op: return ImVec4(0.65f, 0.40f, 0.90f, 1.0f); case DagKind::Blend: return ImVec4(0.90f, 0.65f, 0.15f, 1.0f); case DagKind::Output: return ImVec4(0.85f, 0.25f, 0.25f, 1.0f); } return ImVec4(1, 1, 1, 1); } static const char* kind_label(DagKind kind) { switch (kind) { case DagKind::Gen: return "Gen"; case DagKind::Op: return "Op"; case DagKind::Blend: return "Blend"; case DagKind::Output: return "Output"; } return "?"; } bool dag_panel(std::vector& pipeline) { bool changed = false; // Toolbar int sz = static_cast(pipeline.size()); if (ImGui::Button("+ Add Node")) { ImGui::OpenPopup("dag_add_popup"); } ImGui::SameLine(); ImGui::Text("%d/%d nodes", sz, MAX_NODES); ImGui::SameLine(); if (ImGui::Button("Clear") && !pipeline.empty()) { ImGui::OpenPopup("dag_clear_confirm"); } if (ImGui::BeginPopupModal("dag_clear_confirm", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::Text("Vaciar el pipeline?"); if (ImGui::Button("Si")) { pipeline.clear(); changed = true; ImGui::CloseCurrentPopup(); } ImGui::SameLine(); if (ImGui::Button("No")) { ImGui::CloseCurrentPopup(); } ImGui::EndPopup(); } if (ImGui::BeginPopup("dag_add_popup")) { const char* kind_names[] = { "Gen", "Op", "Blend" }; DagKind kinds[] = { DagKind::Gen, DagKind::Op, DagKind::Blend }; for (int k = 0; k < 3; ++k) { if (ImGui::BeginMenu(kind_names[k])) { for (const auto& def : dag_catalog()) { if (def.kind != kinds[k]) continue; if (ImGui::MenuItem(def.label.c_str())) { if (static_cast(pipeline.size()) < MAX_NODES) { DagStep step; step.id = make_id(); step.name = def.name; step.params = def.param_defaults; if (def.kind == DagKind::Blend && !pipeline.empty()) { int src = std::max(0, static_cast(pipeline.size()) - 2); step.source_ids[1] = pipeline[static_cast(src)].id; } pipeline.push_back(step); changed = true; } } } ImGui::EndMenu(); } } ImGui::EndPopup(); } ImGui::Separator(); // Pipeline list int delete_idx = -1; int move_up_idx = -1; int move_down_idx = -1; int drop_src_idx = -1; int drop_target_idx = -1; ImGui::BeginChild("dag_pipeline_scroll", ImVec2(0, 0), false); for (int i = 0; i < static_cast(pipeline.size()); ++i) { DagStep& step = pipeline[static_cast(i)]; const DagNodeDef* def = dag_find(step.name); if (!def) continue; ImVec4 col = kind_color(def->kind); ImGui::PushID(i); ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(col.x*0.4f, col.y*0.4f, col.z*0.4f, 0.8f)); ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(col.x*0.5f, col.y*0.5f, col.z*0.5f, 0.9f)); ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(col.x*0.6f, col.y*0.6f, col.z*0.6f, 1.0f)); std::string header = std::string("#") + std::to_string(i) + " " + def->label + " [" + kind_label(def->kind) + "]"; bool open = ImGui::CollapsingHeader(header.c_str()); ImGui::PopStyleColor(3); if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_SourceAllowNullID)) { ImGui::SetDragDropPayload("DAG_STEP", &i, sizeof(int)); ImGui::Text("Move %s", def->label.c_str()); ImGui::EndDragDropSource(); } if (ImGui::BeginDragDropTarget()) { if (const ImGuiPayload* pl = ImGui::AcceptDragDropPayload("DAG_STEP")) { drop_src_idx = *static_cast(pl->Data); drop_target_idx = i; } ImGui::EndDragDropTarget(); } if (open) { ImGui::Indent(8.0f); // Source selector for blends if (def->kind == DagKind::Blend) { std::vector prev_labels; std::vector prev_indices; for (int j = 0; j < i; ++j) { const DagNodeDef* pdef = dag_find(pipeline[static_cast(j)].name); std::string lbl = "out_" + std::to_string(j); if (pdef) lbl += " (" + pdef->label + ")"; prev_labels.push_back(lbl); prev_indices.push_back(j); } int current_src = std::max(0, i - 2); if (!step.source_ids[1].empty()) { for (int j = 0; j < i; ++j) { if (pipeline[static_cast(j)].id == step.source_ids[1]) { current_src = j; break; } } } if (!prev_labels.empty()) { std::vector items; for (const auto& l : prev_labels) items.push_back(l.c_str()); int sel = current_src; if (ImGui::Combo("Source", &sel, items.data(), static_cast(items.size()))) { if (sel >= 0 && sel < i) { step.source_ids[1] = pipeline[static_cast(sel)].id; changed = true; } } } else { ImGui::TextDisabled("(requiere al menos un nodo antes)"); } } // Controls int pcount = static_cast(step.params.size()); for (const auto& ctrl : def->controls) { std::string uid_label = ctrl.label + "##" + step.id + std::to_string(&ctrl - def->controls.data()); if (ctrl.kind == DagControl::Kind::Slider) { int pidx = ctrl.param_idx[0]; if (pidx >= 0 && pidx < pcount) { ImGui::SliderFloat(uid_label.c_str(), &step.params[static_cast(pidx)], ctrl.min, ctrl.max); } } else if (ctrl.kind == DagControl::Kind::XY) { int px = ctrl.param_idx[0]; int py = ctrl.param_idx[1]; if (px >= 0 && px < pcount && py >= 0 && py < pcount && py == px + 1) { ImGui::SliderFloat2(uid_label.c_str(), &step.params[static_cast(px)], ctrl.min, ctrl.max); } } else if (ctrl.kind == DagControl::Kind::Color) { int pr = ctrl.param_idx[0]; if (pr >= 0 && pr + 2 < pcount) { ImGui::ColorEdit3(uid_label.c_str(), &step.params[static_cast(pr)]); } } } // Action buttons ImGui::Spacing(); ImGui::BeginDisabled(i == 0); if (ImGui::SmallButton("Move Up")) { move_up_idx = i; } ImGui::EndDisabled(); ImGui::SameLine(); ImGui::BeginDisabled(i == static_cast(pipeline.size()) - 1); if (ImGui::SmallButton("Move Down")) { move_down_idx = i; } ImGui::EndDisabled(); ImGui::SameLine(); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.7f, 0.15f, 0.15f, 0.8f)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.9f, 0.2f, 0.2f, 1.0f)); ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1.0f, 0.3f, 0.3f, 1.0f)); if (ImGui::SmallButton("Delete")) { delete_idx = i; } ImGui::PopStyleColor(3); ImGui::Unindent(8.0f); } ImGui::PopID(); ImGui::Spacing(); } ImGui::EndChild(); // Apply deferred mutations if (delete_idx >= 0) { pipeline.erase(pipeline.begin() + delete_idx); changed = true; } if (move_up_idx > 0) { std::swap(pipeline[static_cast(move_up_idx)], pipeline[static_cast(move_up_idx - 1)]); changed = true; } if (move_down_idx >= 0 && move_down_idx < static_cast(pipeline.size()) - 1) { std::swap(pipeline[static_cast(move_down_idx)], pipeline[static_cast(move_down_idx + 1)]); changed = true; } if (drop_src_idx >= 0 && drop_target_idx >= 0 && drop_src_idx != drop_target_idx && drop_src_idx < static_cast(pipeline.size()) && drop_target_idx < static_cast(pipeline.size())) { DagStep moved = pipeline[static_cast(drop_src_idx)]; pipeline.erase(pipeline.begin() + drop_src_idx); int target = drop_target_idx; if (drop_src_idx < drop_target_idx) target--; pipeline.insert(pipeline.begin() + target, moved); changed = true; } return changed; } } // namespace fn::gfx