#include "views/view_data_processor.hpp" #include #include #include #include namespace hex { ViewDataProcessor::ViewDataProcessor() : View("hex.view.data_processor.name") { EventManager::subscribe(this, [] { auto theme = ContentRegistry::Settings::getSetting("hex.builtin.setting.interface", "hex.builtin.setting.interface.color"); if (theme.is_number()) { switch (static_cast(theme)) { default: case 0: /* Dark theme */ ImNodes::StyleColorsDark(); break; case 1: /* Light theme */ ImNodes::StyleColorsLight(); break; case 2: /* Classic theme */ ImNodes::StyleColorsClassic(); break; } ImNodes::GetStyle().Flags = ImNodesStyleFlags_NodeOutline | ImNodesStyleFlags_GridLines; } }); EventManager::subscribe(this, [this] { ProjectFile::setDataProcessorContent(this->saveNodes()); }); EventManager::subscribe(this, [this] { this->loadNodes(ProjectFile::getDataProcessorContent()); }); EventManager::subscribe(this, [this](const std::string &path){ for (auto &node : this->m_nodes) { node->setCurrentOverlay(nullptr); } this->m_dataOverlays.clear(); }); } ViewDataProcessor::~ViewDataProcessor() { for (auto &node : this->m_nodes) delete node; EventManager::unsubscribe(this); EventManager::unsubscribe(this); EventManager::unsubscribe(this); EventManager::unsubscribe(this); } void ViewDataProcessor::eraseLink(u32 id) { auto link = std::find_if(this->m_links.begin(), this->m_links.end(), [&id](auto link){ return link.getID() == id; }); if (link == this->m_links.end()) return; for (auto &node : this->m_nodes) { for (auto &attribute : node->getAttributes()) { attribute.removeConnectedAttribute(id); } } this->m_links.erase(link); ProjectFile::markDirty(); } void ViewDataProcessor::eraseNodes(const std::vector &ids) { for (const int id : ids) { auto node = std::find_if(this->m_nodes.begin(), this->m_nodes.end(), [&id](auto node){ return node->getID() == id; }); for (auto &attr : (*node)->getAttributes()) { std::vector linksToRemove; for (auto &[linkId, connectedAttr] : attr.getConnectedAttributes()) linksToRemove.push_back(linkId); for (auto linkId : linksToRemove) eraseLink(linkId); } } for (const int id : ids) { auto node = std::find_if(this->m_nodes.begin(), this->m_nodes.end(), [&id](auto node){ return node->getID() == id; }); std::erase_if(this->m_endNodes, [&id](auto node){ return node->getID() == id; }); delete *node; this->m_nodes.erase(node); } ProjectFile::markDirty(); } void ViewDataProcessor::processNodes() { if (this->m_dataOverlays.size() != this->m_endNodes.size()) { for (auto overlay : this->m_dataOverlays) SharedData::currentProvider->deleteOverlay(overlay); this->m_dataOverlays.clear(); for (u32 i = 0; i < this->m_endNodes.size(); i++) this->m_dataOverlays.push_back(SharedData::currentProvider->newOverlay()); u32 overlayIndex = 0; for (auto endNode : this->m_endNodes) { endNode->setCurrentOverlay(this->m_dataOverlays[overlayIndex]); overlayIndex++; } } this->m_currNodeError.reset(); try { for (auto &endNode : this->m_endNodes) { endNode->resetOutputData(); for (auto &node : this->m_nodes) node->resetProcessedInputs(); endNode->process(); } } catch (dp::Node::NodeError &e) { this->m_currNodeError = e; for (auto overlay : this->m_dataOverlays) SharedData::currentProvider->deleteOverlay(overlay); this->m_dataOverlays.clear(); } catch (std::runtime_error &e) { printf("Node implementation bug! %s\n", e.what()); } } void ViewDataProcessor::drawContent() { if (ImGui::Begin(View::toWindowName("hex.view.data_processor.name").c_str(), &this->getWindowOpenState(), ImGuiWindowFlags_NoCollapse)) { if (ImGui::IsMouseReleased(ImGuiMouseButton_Right) && ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows)) { ImNodes::ClearNodeSelection(); ImNodes::ClearLinkSelection(); this->m_rightClickedCoords = ImGui::GetMousePos(); if (ImNodes::IsNodeHovered(&this->m_rightClickedId)) ImGui::OpenPopup("Node Menu"); else if (ImNodes::IsLinkHovered(&this->m_rightClickedId)) ImGui::OpenPopup("Link Menu"); else ImGui::OpenPopup("Context Menu"); } if (ImGui::BeginPopup("Context Menu")) { dp::Node *node = nullptr; if (ImNodes::NumSelectedNodes() > 0 || ImNodes::NumSelectedLinks() > 0) { if (ImGui::MenuItem("hex.view.data_processor.name"_lang)) { std::vector ids; ids.resize(ImNodes::NumSelectedNodes()); ImNodes::GetSelectedNodes(ids.data()); this->eraseNodes(ids); ImNodes::ClearNodeSelection(); ids.resize(ImNodes::NumSelectedLinks()); ImNodes::GetSelectedLinks(ids.data()); for (auto id : ids) this->eraseLink(id); ImNodes::ClearLinkSelection(); } } for (const auto &[unlocalizedCategory, unlocalizedName, function] : ContentRegistry::DataProcessorNode::getEntries()) { if (unlocalizedCategory.empty() && unlocalizedName.empty()) { ImGui::Separator(); } else if (unlocalizedCategory.empty()) { if (ImGui::MenuItem(LangEntry(unlocalizedName))) { node = function(); ProjectFile::markDirty(); } } else { if (ImGui::BeginMenu(LangEntry(unlocalizedCategory))) { if (ImGui::MenuItem(LangEntry(unlocalizedName))) { node = function(); ProjectFile::markDirty(); } ImGui::EndMenu(); } } } if (node != nullptr) { this->m_nodes.push_back(node); bool hasOutput = false; bool hasInput = false; for (auto &attr : node->getAttributes()) { if (attr.getIOType() == dp::Attribute::IOType::Out) hasOutput = true; if (attr.getIOType() == dp::Attribute::IOType::In) hasInput = true; } if (hasInput && !hasOutput) this->m_endNodes.push_back(node); ImNodes::SetNodeScreenSpacePos(node->getID(), this->m_rightClickedCoords); } ImGui::EndPopup(); } if (ImGui::BeginPopup("Node Menu")) { if (ImGui::MenuItem("hex.view.data_processor.menu.remove_node"_lang)) this->eraseNodes({ this->m_rightClickedId }); ImGui::EndPopup(); } if (ImGui::BeginPopup("Link Menu")) { if (ImGui::MenuItem("hex.view.data_processor.menu.remove_link"_lang)) this->eraseLink(this->m_rightClickedId); ImGui::EndPopup(); } { int nodeId; if (ImNodes::IsNodeHovered(&nodeId) && this->m_currNodeError.has_value() && this->m_currNodeError->first->getID() == nodeId) { ImGui::BeginTooltip(); ImGui::TextUnformatted("hex.common.error"_lang); ImGui::Separator(); ImGui::TextUnformatted(this->m_currNodeError->second.c_str()); ImGui::EndTooltip(); } } ImNodes::BeginNodeEditor(); for (auto& node : this->m_nodes) { const bool hasError = this->m_currNodeError.has_value() && this->m_currNodeError->first == node; if (hasError) ImNodes::PushColorStyle(ImNodesCol_NodeOutline, 0xFF0000FF); ImNodes::BeginNode(node->getID()); ImNodes::BeginNodeTitleBar(); ImGui::TextUnformatted(LangEntry(node->getUnlocalizedTitle())); ImNodes::EndNodeTitleBar(); node->drawNode(); for (auto& attribute : node->getAttributes()) { ImNodesPinShape pinShape; switch (attribute.getType()) { case dp::Attribute::Type::Integer: pinShape = ImNodesPinShape_Circle; break; case dp::Attribute::Type::Float: pinShape = ImNodesPinShape_Triangle; break; case dp::Attribute::Type::Buffer: pinShape = ImNodesPinShape_Quad; break; } if (attribute.getIOType() == dp::Attribute::IOType::In) { ImNodes::BeginInputAttribute(attribute.getID(), pinShape); ImGui::TextUnformatted(LangEntry(attribute.getUnlocalizedName())); ImNodes::EndInputAttribute(); } else if (attribute.getIOType() == dp::Attribute::IOType::Out) { ImNodes::BeginOutputAttribute(attribute.getID(), ImNodesPinShape(pinShape + 1)); ImGui::TextUnformatted(LangEntry(attribute.getUnlocalizedName())); ImNodes::EndOutputAttribute(); } } ImNodes::EndNode(); if (hasError) ImNodes::PopColorStyle(); } for (const auto &link : this->m_links) ImNodes::Link(link.getID(), link.getFromID(), link.getToID()); ImNodes::MiniMap(0.2F, ImNodesMiniMapLocation_BottomRight); ImNodes::EndNodeEditor(); { int linkId; if (ImNodes::IsLinkDestroyed(&linkId)) { this->eraseLink(linkId); } } { int from, to; if (ImNodes::IsLinkCreated(&from, &to)) { do { dp::Attribute *fromAttr, *toAttr; for (auto &node : this->m_nodes) { for (auto &attribute : node->getAttributes()) { if (attribute.getID() == from) fromAttr = &attribute; else if (attribute.getID() == to) toAttr = &attribute; } } if (fromAttr == nullptr || toAttr == nullptr) break; if (fromAttr->getType() != toAttr->getType()) break; if (fromAttr->getIOType() == toAttr->getIOType()) break; if (!toAttr->getConnectedAttributes().empty()) break; auto newLink = this->m_links.emplace_back(from, to); fromAttr->addConnectedAttribute(newLink.getID(), toAttr); toAttr->addConnectedAttribute(newLink.getID(), fromAttr); } while (false); } } { const int selectedLinkCount = ImNodes::NumSelectedLinks(); if (selectedLinkCount > 0 && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Delete))) { static std::vector selectedLinks; selectedLinks.resize(static_cast(selectedLinkCount)); ImNodes::GetSelectedLinks(selectedLinks.data()); for (const int id : selectedLinks) { eraseLink(id); } } } { const int selectedNodeCount = ImNodes::NumSelectedNodes(); if (selectedNodeCount > 0 && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Delete))) { static std::vector selectedNodes; selectedNodes.resize(static_cast(selectedNodeCount)); ImNodes::GetSelectedNodes(selectedNodes.data()); this->eraseNodes(selectedNodes); } } this->processNodes(); } ImGui::End(); } void ViewDataProcessor::drawMenu() { } std::string ViewDataProcessor::saveNodes() { using json = nlohmann::json; json output; output["nodes"] = json::object(); for (auto &node : this->m_nodes) { auto id = node->getID(); auto &currNodeOutput = output["nodes"][std::to_string(id)]; auto pos = ImNodes::GetNodeGridSpacePos(id); currNodeOutput["type"] = node->getUnlocalizedName(); currNodeOutput["pos"] = { { "x", pos.x }, { "y", pos.y } }; currNodeOutput["attrs"] = json::array(); currNodeOutput["id"] = id; json nodeData; node->store(nodeData); currNodeOutput["data"] = nodeData; u32 attrIndex = 0; for (auto &attr : node->getAttributes()) { currNodeOutput["attrs"][attrIndex] = attr.getID(); attrIndex++; } } output["links"] = json::object(); for (auto &link : this->m_links) { auto id = link.getID(); auto &currOutput = output["links"][std::to_string(id)]; currOutput["id"] = id; currOutput["from"] = link.getFromID(); currOutput["to"] = link.getToID(); } return output.dump(); } void ViewDataProcessor::loadNodes(std::string_view data) { using json = nlohmann::json; json input = json::parse(data); u32 maxNodeId = 0; u32 maxAttrId = 0; u32 maxLinkId = 0; for (auto &node : this->m_nodes) delete node; this->m_nodes.clear(); this->m_endNodes.clear(); this->m_links.clear(); auto &nodeEntries = ContentRegistry::DataProcessorNode::getEntries(); for (auto &node : input["nodes"]) { dp::Node *newNode = nullptr; for (auto &entry : nodeEntries) { if (entry.name == node["type"]) newNode = entry.creatorFunction(); } if (newNode == nullptr) continue; u32 nodeId = node["id"]; maxNodeId = std::max(nodeId, maxNodeId); newNode->setID(nodeId); bool hasOutput = false; bool hasInput = false; u32 attrIndex = 0; for (auto &attr : newNode->getAttributes()) { if (attr.getIOType() == dp::Attribute::IOType::Out) hasOutput = true; if (attr.getIOType() == dp::Attribute::IOType::In) hasInput = true; u32 attrId = node["attrs"][attrIndex]; maxAttrId = std::max(attrId, maxAttrId); attr.setID(attrId); attrIndex++; } if (!node["data"].is_null()) newNode->load(node["data"]); if (hasInput && !hasOutput) this->m_endNodes.push_back(newNode); this->m_nodes.push_back(newNode); ImNodes::SetNodeGridSpacePos(nodeId, ImVec2(node["pos"]["x"], node["pos"]["y"])); } for (auto &link : input["links"]) { dp::Link newLink(link["from"], link["to"]); u32 linkId = link["id"]; maxLinkId = std::max(linkId, maxLinkId); newLink.setID(linkId); this->m_links.push_back(newLink); dp::Attribute *fromAttr, *toAttr; for (auto &node : this->m_nodes) { for (auto &attribute : node->getAttributes()) { if (attribute.getID() == newLink.getFromID()) fromAttr = &attribute; else if (attribute.getID() == newLink.getToID()) toAttr = &attribute; } } if (fromAttr == nullptr || toAttr == nullptr) break; if (fromAttr->getType() != toAttr->getType()) break; if (fromAttr->getIOType() == toAttr->getIOType()) break; if (!toAttr->getConnectedAttributes().empty()) break; fromAttr->addConnectedAttribute(newLink.getID(), toAttr); toAttr->addConnectedAttribute(newLink.getID(), fromAttr); } SharedData::dataProcessorNodeIdCounter = maxNodeId + 1; SharedData::dataProcessorAttrIdCounter = maxAttrId + 1; SharedData::dataProcessorLinkIdCounter = maxLinkId + 1; } }