#include "common/widgets/CircularProgressBar.h" #include "core/style.h" #include "dsp/block.h" #include "dsp/block_helpers.h" #include "flowgraph.h" #include "imgui/imgui.h" #include "imgui/imnodes/imnodes.h" #include "utils/string.h" #include namespace satdump { namespace ndsp { namespace flowgraph { void Flowgraph::renderAddMenu(std::pair &opt, std::vector cats, int pos) { if (pos == (cats.size() - 1)) { if (ImGui::MenuItem(cats[pos].c_str())) { auto mpos = ImGui::GetMousePos(); auto ptr = addNode(opt.first, opt.second.func(this)); ptr->pos_was_set = true; ImNodes::SetNodeScreenSpacePos(ptr->id, mpos); } } else { if (ImGui::BeginMenu(cats[pos].c_str())) { renderAddMenu(opt, cats, pos + 1); ImGui::EndMenu(); } } } void Flowgraph::renderAddMenuList(std::pair &opt, std::vector cats, int pos) { if (pos == (cats.size() - 1)) { if (ImGui::TreeNode("%s", cats[pos].c_str(), ImGuiTreeNodeFlags_Leaf)) { addNode(opt.first, opt.second.func(this)); ImGui::TreePop(); } } else { if (ImGui::TreeNode("%s", cats[pos].c_str())) { renderAddMenuList(opt, cats, pos + 1); ImGui::TreePop(); } } } void Flowgraph::renderAddMenuList() { std::lock_guard lg(flow_mtx); for (auto &opt : node_internal_registry) { std::vector cats = splitString(opt.second.menuname, '/'); renderAddMenuList(opt, cats, 0); } } void Flowgraph::render() { std::lock_guard lg(flow_mtx); ImNodes::PushStyleVar(ImNodesStyleVar_PinCircleRadius, 6); ImNodes::PushStyleVar(ImNodesStyleVar_LinkThickness, 4); ImNodes::PushAttributeFlag(ImNodesAttributeFlags_EnableLinkDetachWithDragClick); ImNodes::BeginNodeEditor(); ImNodes::MiniMap(); for (auto &n : nodes) { if (n->disabled) style::beginDisabled(); ImNodes::BeginNode(n->id); ImNodes::BeginNodeTitleBar(); ImGui::Text("%s", n->title.c_str()); ImNodes::EndNodeTitleBar(); // Update if needed if (n->internal->render()) n->updateIO(); int in_pos = 0; for (auto &io : n->node_io) { ImNodes::PushColorStyle(ImNodesCol_Pin, getColorFromDSPType(io.type)); if (io.is_out) { ImNodes::BeginOutputAttribute(io.id); const float node_width = ImNodes::GetNodeDimensions(n->id).x; const float label_width = ImGui::CalcTextSize(io.name.c_str()).x; ImGui::Indent(node_width - label_width - 20 * ui_scale); ImGui::Text("%s", io.name.c_str()); ImNodes::EndOutputAttribute(); } else { ImNodes::BeginInputAttribute(io.id); ImGui::Text("%s", io.name.c_str()); if (is_running && debug_mode) { if (in_pos < n->internal->blk->get_inputs().size()) { auto f = n->internal->blk->get_inputs()[in_pos]; if (f.fifo) { float v = (float)f.fifo->size_approx() / (float)f.fifo->max_capacity(); ImGui::SameLine(); if (v > 1) v = 1; if (v < 0) v = 0; CircularProgressBar(f.name.c_str(), v, {20, 20}, style::theme.green); } } } ImNodes::EndInputAttribute(); in_pos++; } ImNodes::PopColorStyle(); } ImNodes::EndNode(); if (!n->pos_was_set) { ImNodes::SetNodeGridSpacePos(n->id, {n->pos_x, n->pos_y}); n->pos_was_set = true; } auto pos = ImNodes::GetNodeGridSpacePos(n->id); n->pos_x = pos.x; n->pos_y = pos.y; if (n->disabled) style::endDisabled(); } for (auto &l : links) { bool disabled = false; BlockIOType type; for (auto &n : nodes) for (auto &io : n->node_io) { if (io.id == l.start) { type = io.type; if (n->disabled) disabled = true; } else if (io.id == l.end) { if (n->disabled) disabled = true; } } ImNodes::PushColorStyle(ImNodesCol_Link, disabled ? (ImColor)ImGui::GetColorU32(ImGuiCol_TextDisabled) : getColorFromDSPType(type)); ImNodes::Link(l.id, l.start, l.end); ImNodes::PopColorStyle(); } ImNodes::EndNodeEditor(); ImNodes::PopAttributeFlag(); ImNodes::PopStyleVar(); ImNodes::PopStyleVar(); // Lock down edition when running! if (!is_running) { int start_att, end_att; if (ImNodes::IsLinkCreated(&start_att, &end_att)) links.push_back({getNewLinkID(), start_att, end_att}); int link_id; if (ImNodes::IsLinkDestroyed(&link_id)) { auto iter = std::find_if(links.begin(), links.end(), [link_id](const Link &link) -> bool { return link.id == link_id; }); links.erase(iter); } ///////////////////// Node Key Handlers if (!ImGui::IsAnyItemActive() && ImGui::IsKeyPressed(ImGuiKey_Delete)) { int node_s = ImNodes::NumSelectedNodes(); if (node_s > 0) { std::vector nodes_ids(node_s); ImNodes::GetSelectedNodes(nodes_ids.data()); for (auto &id : nodes_ids) { auto iter = std::find_if(nodes.begin(), nodes.end(), [id](const std::shared_ptr &node) -> bool { return node->id == id; }); for (auto &linkid : iter->get()->node_io) { auto liter = std::find_if(links.begin(), links.end(), [linkid](const Link &link) -> bool { return link.start == linkid.id || link.end == linkid.id; }); if (liter != links.end()) links.erase(liter); } nodes.erase(iter); } } } if (!ImGui::IsAnyItemActive() && ImGui::IsKeyPressed(ImGuiKey_D)) { int node_s = ImNodes::NumSelectedNodes(); if (node_s > 0) { std::vector nodes_ids(node_s); ImNodes::GetSelectedNodes(nodes_ids.data()); for (auto &id : nodes_ids) { auto iter = std::find_if(nodes.begin(), nodes.end(), [id](const std::shared_ptr &node) -> bool { return node->id == id; }); iter->get()->disabled = !iter->get()->disabled; } } } ///////////////////// Node Key Handlers end if (ImGui::IsMouseClicked(ImGuiMouseButton_Right)) ImGui::OpenPopup("##popuprightclickflowgraph"); if (ImGui::BeginPopup("##popuprightclickflowgraph")) { if (ImGui::BeginMenu("Add Node")) { for (auto &opt : node_internal_registry) { std::vector cats = splitString(opt.second.menuname, '/'); renderAddMenu(opt, cats, 0); } ImGui::EndMenu(); } ImGui::EndPopup(); } } if (!is_running) { int id = 0; if (ImNodes::IsNodeHovered(&id) && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { for (auto &n : nodes) if (n->id == id) n->show_vars_win = true; } } } void Flowgraph::renderWindows() { std::lock_guard lg(flow_mtx); for (auto &n : nodes) { if (n->show_vars_win) { std::string name = n->internal->blk->d_id + "##nodevarspopup"; ImGui::Begin(name.c_str(), &n->show_vars_win, ImGuiWindowFlags_AlwaysAutoResize); for (auto &v : n->vars) { ImGui::SeparatorText(v.first.c_str()); ImGui::InputText(std::string("##" + v.first).c_str(), &v.second); ImGui::SameLine(); if (ImGui::Button(("Delete##" + name + v.first).c_str())) { n->vars.erase(v.first); break; } } ImGui::SeparatorText("Add others"); int nl = 1; for (auto &v : n->internal->blk->get_cfg().items()) { bool found = false; for (auto &v2 : n->vars) if (v2.first == v.key()) found = true; if (found) continue; std::string bname = v.key() + "##nodeaddbtn"; if (ImGui::Button(bname.c_str())) { n->vars.emplace(v.key(), ""); } if (nl && !(nl % 3 == 0)) ImGui::SameLine(); nl++; } ImGui::End(); } } } } // namespace flowgraph } // namespace ndsp } // namespace satdump