1 #include "views/view_data_processor.hpp"
2 
3 #include <hex/providers/provider.hpp>
4 
5 #include <imnodes.h>
6 
7 namespace hex {
8 
ViewDataProcessor()9     ViewDataProcessor::ViewDataProcessor() : View("hex.view.data_processor.name"_lang) {
10         imnodes::Initialize();
11         imnodes::PushAttributeFlag(imnodes::AttributeFlags_EnableLinkDetachWithDragClick);
12         imnodes::PushAttributeFlag(imnodes::AttributeFlags_EnableLinkCreationOnSnap);
13 
14         {
15             static bool always = true;
16             imnodes::IO& io = imnodes::GetIO();
17             io.link_detach_with_modifier_click.modifier = &always;
18         }
19 
20         View::subscribeEvent(Events::SettingsChanged, [](auto) {
21             auto theme = ContentRegistry::Settings::getSetting("hex.builtin.setting.interface", "hex.builtin.setting.interface.color");
22 
23             if (theme.has_value()) {
24                 switch (static_cast<int>(theme.value())) {
25                     default:
26                     case 0: /* Dark theme */
27                         imnodes::StyleColorsDark();
28                         break;
29                     case 1: /* Light theme */
30                         imnodes::StyleColorsLight();
31                         break;
32                     case 2: /* Classic theme */
33                         imnodes::StyleColorsClassic();
34                         break;
35                 }
36 
37                 imnodes::GetStyle().flags = imnodes::StyleFlags(imnodes::StyleFlags_NodeOutline | imnodes::StyleFlags_GridLines);
38             }
39         });
40 
41         View::subscribeEvent(Events::FileLoaded, [this](auto) {
42             for (auto &node : this->m_nodes) {
43                 node->setCurrentOverlay(nullptr);
44             }
45             this->m_dataOverlays.clear();
46         });
47     }
48 
~ViewDataProcessor()49     ViewDataProcessor::~ViewDataProcessor() {
50         for (auto &node : this->m_nodes)
51             delete node;
52 
53         imnodes::PopAttributeFlag();
54         imnodes::PopAttributeFlag();
55         imnodes::Shutdown();
56     }
57 
58 
eraseLink(u32 id)59     void ViewDataProcessor::eraseLink(u32 id) {
60         auto link = std::find_if(this->m_links.begin(), this->m_links.end(), [&id](auto link){ return link.getID() == id; });
61 
62         if (link == this->m_links.end())
63             return;
64 
65         for (auto &node : this->m_nodes) {
66             for (auto &attribute : node->getAttributes()) {
67                 attribute.removeConnectedAttribute(id);
68             }
69         }
70 
71         this->m_links.erase(link);
72     }
73 
eraseNodes(const std::vector<int> & ids)74     void ViewDataProcessor::eraseNodes(const std::vector<int> &ids) {
75         for (const int id : ids) {
76             auto node = std::find_if(this->m_nodes.begin(), this->m_nodes.end(), [&id](auto node){ return node->getID() == id; });
77 
78             for (auto &attr : (*node)->getAttributes()) {
79                 std::vector<u32> linksToRemove;
80                 for (auto &[linkId, connectedAttr] : attr.getConnectedAttributes())
81                     linksToRemove.push_back(linkId);
82 
83                 for (auto linkId : linksToRemove)
84                     eraseLink(linkId);
85             }
86         }
87 
88         for (const int id : ids) {
89             auto node = std::find_if(this->m_nodes.begin(), this->m_nodes.end(), [&id](auto node){ return node->getID() == id; });
90 
91             std::erase_if(this->m_endNodes, [&id](auto node){ return node->getID() == id; });
92 
93             delete *node;
94 
95             this->m_nodes.erase(node);
96         }
97     }
98 
processNodes()99     void ViewDataProcessor::processNodes() {
100         if (this->m_dataOverlays.size() != this->m_endNodes.size()) {
101             for (auto overlay : this->m_dataOverlays)
102                 SharedData::currentProvider->deleteOverlay(overlay);
103             this->m_dataOverlays.clear();
104 
105             for (u32 i = 0; i < this->m_endNodes.size(); i++)
106                 this->m_dataOverlays.push_back(SharedData::currentProvider->newOverlay());
107 
108             u32 overlayIndex = 0;
109             for (auto endNode : this->m_endNodes) {
110                 endNode->setCurrentOverlay(this->m_dataOverlays[overlayIndex]);
111                 overlayIndex++;
112             }
113         }
114 
115         this->m_currNodeError.reset();
116 
117         try {
118             for (auto &endNode : this->m_endNodes) {
119                 endNode->resetOutputData();
120                 endNode->process();
121             }
122         } catch (dp::Node::NodeError &e) {
123             this->m_currNodeError = e;
124         } catch (std::runtime_error &e) {
125             printf("Node implementation bug! %s\n", e.what());
126         }
127 
128     }
129 
drawContent()130     void ViewDataProcessor::drawContent() {
131         if (ImGui::Begin("hex.view.data_processor.name"_lang, &this->getWindowOpenState(), ImGuiWindowFlags_NoCollapse)) {
132 
133             if (ImGui::IsMouseReleased(ImGuiMouseButton_Right) && ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows)) {
134                 imnodes::ClearNodeSelection();
135                 imnodes::ClearLinkSelection();
136 
137                 this->m_rightClickedCoords = ImGui::GetMousePos();
138 
139                 if (imnodes::IsNodeHovered(&this->m_rightClickedId))
140                     ImGui::OpenPopup("Node Menu");
141                 else if (imnodes::IsLinkHovered(&this->m_rightClickedId))
142                     ImGui::OpenPopup("Link Menu");
143                 else
144                     ImGui::OpenPopup("Context Menu");
145             }
146 
147             if (ImGui::BeginPopup("Context Menu")) {
148                 dp::Node *node = nullptr;
149 
150                 if (imnodes::NumSelectedNodes() > 0 || imnodes::NumSelectedLinks() > 0) {
151                     if (ImGui::MenuItem("hex.view.data_processor.name"_lang)) {
152                         std::vector<int> ids;
153                         ids.resize(imnodes::NumSelectedNodes());
154                         imnodes::GetSelectedNodes(ids.data());
155 
156                         this->eraseNodes(ids);
157                         imnodes::ClearNodeSelection();
158 
159                         ids.resize(imnodes::NumSelectedLinks());
160                         imnodes::GetSelectedLinks(ids.data());
161 
162                         for (auto id : ids)
163                             this->eraseLink(id);
164                         imnodes::ClearLinkSelection();
165                     }
166                 }
167 
168                 for (const auto &[category, name, function] : ContentRegistry::DataProcessorNode::getEntries()) {
169                     if (category.empty() && name.empty()) {
170                         ImGui::Separator();
171                     } else if (category.empty()) {
172                         if (ImGui::MenuItem(name.c_str())) {
173                             node = function();
174                         }
175                     } else {
176                         if (ImGui::BeginMenu(category.c_str())) {
177                             if (ImGui::MenuItem(name.c_str())) {
178                                 node = function();
179                             }
180                             ImGui::EndMenu();
181                         }
182                     }
183                 }
184 
185                 if (node != nullptr) {
186                     this->m_nodes.push_back(node);
187 
188                     bool hasOutput = false;
189                     bool hasInput = false;
190                     for (auto &attr : node->getAttributes()) {
191                         if (attr.getIOType() == dp::Attribute::IOType::Out)
192                             hasOutput = true;
193 
194                         if (attr.getIOType() == dp::Attribute::IOType::In)
195                             hasInput = true;
196                     }
197 
198                     if (hasInput && !hasOutput)
199                         this->m_endNodes.push_back(node);
200 
201                     imnodes::SetNodeScreenSpacePos(node->getID(), this->m_rightClickedCoords);
202                 }
203 
204                 ImGui::EndPopup();
205             }
206 
207             if (ImGui::BeginPopup("Node Menu")) {
208                 if (ImGui::MenuItem("hex.view.data_processor.menu.remove_node"_lang))
209                     this->eraseNodes({ this->m_rightClickedId });
210 
211                 ImGui::EndPopup();
212             }
213 
214             if (ImGui::BeginPopup("Link Menu")) {
215                 if (ImGui::MenuItem("hex.view.data_processor.menu.remove_link"_lang))
216                     this->eraseLink(this->m_rightClickedId);
217 
218                 ImGui::EndPopup();
219             }
220 
221             {
222                 int nodeId;
223                 if (imnodes::IsNodeHovered(&nodeId) && this->m_currNodeError.has_value() && this->m_currNodeError->first->getID() == nodeId) {
224                     ImGui::BeginTooltip();
225                     ImGui::TextUnformatted("hex.common.error"_lang);
226                     ImGui::Separator();
227                     ImGui::TextUnformatted(this->m_currNodeError->second.c_str());
228                     ImGui::EndTooltip();
229                 }
230             }
231 
232             imnodes::BeginNodeEditor();
233 
234             for (auto& node : this->m_nodes) {
235                 const bool hasError = this->m_currNodeError.has_value() && this->m_currNodeError->first == node;
236 
237                 if (hasError)
238                     imnodes::PushColorStyle(imnodes::ColorStyle_NodeOutline, 0xFF0000FF);
239 
240                 imnodes::BeginNode(node->getID());
241 
242                 imnodes::BeginNodeTitleBar();
243                 ImGui::TextUnformatted(LangEntry(node->getUnlocalizedName()));
244                 imnodes::EndNodeTitleBar();
245 
246                 node->drawNode();
247 
248                 for (auto& attribute : node->getAttributes()) {
249                     imnodes::PinShape pinShape;
250 
251                     switch (attribute.getType()) {
252                         case dp::Attribute::Type::Integer: pinShape = imnodes::PinShape_Circle; break;
253                         case dp::Attribute::Type::Float: pinShape = imnodes::PinShape_Triangle; break;
254                         case dp::Attribute::Type::Buffer: pinShape = imnodes::PinShape_Quad; break;
255                     }
256 
257                     if (attribute.getIOType() == dp::Attribute::IOType::In) {
258                         imnodes::BeginInputAttribute(attribute.getID(), pinShape);
259                         ImGui::TextUnformatted(LangEntry(attribute.getUnlocalizedName()));
260                         imnodes::EndInputAttribute();
261                     } else if (attribute.getIOType() == dp::Attribute::IOType::Out) {
262                         imnodes::BeginOutputAttribute(attribute.getID(), imnodes::PinShape(pinShape + 1));
263                         ImGui::TextUnformatted(LangEntry(attribute.getUnlocalizedName()));
264                         imnodes::EndOutputAttribute();
265                     }
266                 }
267 
268                 imnodes::EndNode();
269 
270                 if (hasError)
271                     imnodes::PopColorStyle();
272             }
273 
274             for (const auto &link : this->m_links)
275                 imnodes::Link(link.getID(), link.getFromID(), link.getToID());
276 
277             imnodes::EndNodeEditor();
278 
279             {
280                 int linkId;
281                 if (imnodes::IsLinkDestroyed(&linkId)) {
282                     this->eraseLink(linkId);
283                 }
284             }
285 
286             {
287                 int from, to;
288                 if (imnodes::IsLinkCreated(&from, &to)) {
289 
290                     do {
291                         dp::Attribute *fromAttr, *toAttr;
292                         for (auto &node : this->m_nodes) {
293                             for (auto &attribute : node->getAttributes()) {
294                                 if (attribute.getID() == from)
295                                     fromAttr = &attribute;
296                                 else if (attribute.getID() == to)
297                                     toAttr = &attribute;
298                             }
299                         }
300 
301                         if (fromAttr == nullptr || toAttr == nullptr)
302                             break;
303 
304                         if (fromAttr->getType() != toAttr->getType())
305                             break;
306 
307                         if (fromAttr->getIOType() == toAttr->getIOType())
308                             break;
309 
310                         if (!toAttr->getConnectedAttributes().empty())
311                             break;
312 
313                         auto newLink = this->m_links.emplace_back(from, to);
314 
315                         fromAttr->addConnectedAttribute(newLink.getID(), toAttr);
316                         toAttr->addConnectedAttribute(newLink.getID(), fromAttr);
317                     } while (false);
318 
319                 }
320             }
321 
322             {
323                 const int selectedLinkCount = imnodes::NumSelectedLinks();
324                 if (selectedLinkCount > 0 && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Delete))) {
325                     static std::vector<int> selectedLinks;
326                     selectedLinks.resize(static_cast<size_t>(selectedLinkCount));
327                     imnodes::GetSelectedLinks(selectedLinks.data());
328 
329                     for (const int id : selectedLinks) {
330                         eraseLink(id);
331                     }
332 
333                 }
334             }
335 
336             {
337                 const int selectedNodeCount = imnodes::NumSelectedNodes();
338                 if (selectedNodeCount > 0 && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Delete))) {
339                     static std::vector<int> selectedNodes;
340                     selectedNodes.resize(static_cast<size_t>(selectedNodeCount));
341                     imnodes::GetSelectedNodes(selectedNodes.data());
342 
343                     this->eraseNodes(selectedNodes);
344 
345                 }
346             }
347 
348             this->processNodes();
349 
350         }
351         ImGui::End();
352     }
353 
drawMenu()354     void ViewDataProcessor::drawMenu() {
355 
356     }
357 
358 }