INSTINCT Code Coverage Report


Directory: src/
File: internal/gui/GlobalActions.cpp
Date: 2025-02-07 16:54:41
Exec Total Coverage
Lines: 0 147 0.0%
Functions: 0 11 0.0%
Branches: 0 380 0.0%

Line Branch Exec Source
1 // This file is part of INSTINCT, the INS Toolkit for Integrated
2 // Navigation Concepts and Training by the Institute of Navigation of
3 // the University of Stuttgart, Germany.
4 //
5 // This Source Code Form is subject to the terms of the Mozilla Public
6 // License, v. 2.0. If a copy of the MPL was not distributed with this
7 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
8
9 #include "GlobalActions.hpp"
10
11 #include "internal/NodeManager.hpp"
12 namespace nm = NAV::NodeManager;
13
14 #include "internal/FlowManager.hpp"
15
16 #include "internal/gui/NodeEditorApplication.hpp"
17
18 #include <imgui_node_editor.h>
19 #include <imgui_node_editor_internal.h>
20 namespace ed = ax::NodeEditor;
21
22 #include <nlohmann/json.hpp>
23 using json = nlohmann::json; ///< json namespace
24 #include "util/Json.hpp"
25
26 #include "internal/ConfigManager.hpp"
27
28 #include <vector>
29 #include <deque>
30 #include <limits>
31 #include <iterator>
32
33 namespace NAV::gui
34 {
35 namespace
36 {
37 /// @brief Specifies if the elements in the clipboard are cutted or copied
38 bool elementsCutted = false;
39 /// @brief Clipboard storage
40 json clipboard;
41
42 // /// @brief Maximum size of the action list
43 // constexpr size_t ACTION_LIST_MAX_SIZE = 20;
44
45 /// @brief Current action in the action list
46 size_t currentAction = 0;
47 /// @brief List of actions performed by the user
48 std::deque<json> actionList;
49
50 } // namespace
51 } // namespace NAV::gui
52
53 bool NAV::gui::canCutOrCopyFlowElements()
54 {
55 return static_cast<bool>(ed::GetSelectedNodes(nullptr, ed::GetSelectedObjectCount()));
56 }
57
58 bool NAV::gui::canPasteFlowElements()
59 {
60 return !clipboard.empty();
61 }
62
63 void NAV::gui::cutFlowElements()
64 {
65 std::vector<ax::NodeEditor::NodeId> selectedNodeIds;
66 selectedNodeIds.resize(static_cast<size_t>(ed::GetSelectedObjectCount()));
67
68 auto selectedNodesCount = ed::GetSelectedNodes(selectedNodeIds.data(), ed::GetSelectedObjectCount());
69 selectedNodeIds.resize(static_cast<size_t>(selectedNodesCount));
70
71 clipboard.clear();
72 NAV::flow::saveLastActions = false;
73
74 for (const auto& nodeId : selectedNodeIds)
75 {
76 const NAV::Node* node = nm::FindNode(nodeId);
77
78 clipboard["nodes"]["node-" + std::to_string(size_t(node->id))] = *node;
79 clipboard["nodes"]["node-" + std::to_string(size_t(node->id))]["data"] = node->save();
80
81 for (const auto& outputPin : node->outputPins)
82 {
83 for (const auto& link : outputPin.links)
84 {
85 auto& j = clipboard["links"]["link-" + std::to_string(size_t(link.linkId))];
86 j["id"] = size_t(link.linkId);
87 j["startPinId"] = size_t(outputPin.id);
88 j["endPinId"] = size_t(link.connectedPinId);
89 }
90 }
91
92 nm::DeleteNode(nodeId);
93 }
94
95 elementsCutted = true;
96
97 NAV::flow::saveLastActions = true;
98 saveLastAction();
99 }
100
101 void NAV::gui::copyFlowElements()
102 {
103 std::vector<ax::NodeEditor::NodeId> selectedNodeIds;
104 selectedNodeIds.resize(static_cast<size_t>(ed::GetSelectedObjectCount()));
105
106 auto selectedNodesCount = ed::GetSelectedNodes(selectedNodeIds.data(), ed::GetSelectedObjectCount());
107 selectedNodeIds.resize(static_cast<size_t>(selectedNodesCount));
108
109 clipboard.clear();
110
111 for (const auto& nodeId : selectedNodeIds)
112 {
113 const NAV::Node* node = nm::FindNode(nodeId);
114
115 clipboard["nodes"]["node-" + std::to_string(size_t(node->id))] = *node;
116 clipboard["nodes"]["node-" + std::to_string(size_t(node->id))]["data"] = node->save();
117
118 for (const auto& outputPin : node->outputPins)
119 {
120 for (const auto& link : outputPin.links)
121 {
122 auto& j = clipboard["links"]["link-" + std::to_string(size_t(link.linkId))];
123 j["id"] = size_t(link.linkId);
124 j["startPinId"] = size_t(outputPin.id);
125 j["endPinId"] = size_t(link.connectedPinId);
126 }
127 }
128 }
129
130 elementsCutted = false;
131 }
132
133 void NAV::gui::pasteFlowElements()
134 {
135 // Store the node count to later iterate over the new nodes
136 auto nodeCountBeforeLoad = nm::m_Nodes().size();
137
138 NAV::flow::saveLastActions = false;
139
140 LOG_DEBUG("Pasting clipboard {}", clipboard.dump(4));
141
142 flow::LoadJson(clipboard, !elementsCutted);
143
144 // Find Top Left Position of all new nodes to move them to the mouse cursor
145 ImVec2 leftTopMostPos{ std::numeric_limits<float>::infinity(), std::numeric_limits<float>::infinity() };
146 if (clipboard.contains("nodes"))
147 {
148 for (const auto& nodeJson : clipboard.at("nodes"))
149 {
150 ImVec2 pos;
151 if (nodeJson.contains("pos"))
152 {
153 nodeJson.at("pos").get_to(pos);
154
155 leftTopMostPos.x = std::min(pos.x, leftTopMostPos.x);
156 leftTopMostPos.y = std::min(pos.y, leftTopMostPos.y);
157 }
158 }
159 }
160
161 // Get Mouse Position in editor coordinates
162 auto viewRect = reinterpret_cast<ax::NodeEditor::Detail::EditorContext*>(ed::GetCurrentEditor())->GetViewRect();
163 ImVec2 mousePos = ImGui::GetMousePos();
164 mousePos.x -= NodeEditorApplication::leftPaneWidth + NodeEditorApplication::SPLITTER_THICKNESS + 10.0F;
165 mousePos.y -= NodeEditorApplication::menuBarHeight;
166 mousePos *= ed::GetCurrentZoom();
167 mousePos += viewRect.GetTL();
168
169 // Move the Nodes relative to the current mouse position
170 for (size_t i = nodeCountBeforeLoad; i < nm::m_Nodes().size(); i++)
171 {
172 auto* node = nm::m_Nodes().at(i);
173 ed::SetNodePosition(node->id, mousePos + (ed::GetNodePosition(node->id) - leftTopMostPos));
174 }
175
176 // Collect the node ids which get new links to call the restoreAfterLinks function on them
177 std::map<size_t, ed::NodeId> newlyLinkedNodes;
178
179 // Recreate links
180 if (clipboard.contains("links"))
181 {
182 for (const auto& linkJson : clipboard.at("links"))
183 {
184 auto startPinId = linkJson.at("startPinId").get<size_t>();
185 auto endPinId = linkJson.at("endPinId").get<size_t>();
186
187 size_t startPinOldParentNodeId = 0;
188 size_t startPinParentNodeIndex = 0;
189 size_t startPinIndex = 0;
190 Pin::Kind startPinKind = Pin::Kind::None;
191
192 size_t endPinOldParentNodeId = 0;
193 size_t endPinParentNodeIndex = 0;
194 size_t endPinIndex = 0;
195 Pin::Kind endPinKind = Pin::Kind::None;
196
197 // Search for the nodes and pins which where connected by the old link
198 if (clipboard.contains("nodes"))
199 {
200 size_t nodeIndex = 0;
201 for (const auto& nodeJson : clipboard.at("nodes"))
202 {
203 if (nodeJson.contains("inputPins"))
204 {
205 size_t pinIndex = 0;
206 for (const auto& pinJson : nodeJson.at("inputPins"))
207 {
208 if (pinJson.at("id").get<size_t>() == startPinId)
209 {
210 startPinOldParentNodeId = nodeJson.at("id");
211 startPinParentNodeIndex = nodeCountBeforeLoad + nodeIndex;
212 startPinIndex = pinIndex;
213 startPinKind = Pin::Kind::Input;
214 }
215 if (pinJson.at("id").get<size_t>() == endPinId)
216 {
217 endPinOldParentNodeId = nodeJson.at("id");
218 endPinParentNodeIndex = nodeCountBeforeLoad + nodeIndex;
219 endPinIndex = pinIndex;
220 endPinKind = Pin::Kind::Input;
221 }
222 pinIndex++;
223 }
224 }
225 if (nodeJson.contains("outputPins"))
226 {
227 size_t pinIndex = 0;
228 for (const auto& pinJson : nodeJson.at("outputPins"))
229 {
230 if (pinJson.at("id").get<size_t>() == startPinId)
231 {
232 startPinOldParentNodeId = nodeJson.at("id");
233 startPinParentNodeIndex = nodeCountBeforeLoad + nodeIndex;
234 startPinIndex = pinIndex;
235 startPinKind = Pin::Kind::Output;
236 }
237 if (pinJson.at("id").get<size_t>() == endPinId)
238 {
239 endPinOldParentNodeId = nodeJson.at("id");
240 endPinParentNodeIndex = nodeCountBeforeLoad + nodeIndex;
241 endPinIndex = pinIndex;
242 endPinKind = Pin::Kind::Output;
243 }
244 pinIndex++;
245 }
246 }
247 nodeIndex++;
248 }
249 }
250
251 if (startPinKind != Pin::Kind::None && endPinKind != Pin::Kind::None)
252 {
253 if (startPinKind == Pin::Kind::Output && endPinKind == Pin::Kind::Input)
254 {
255 auto& startPin = nm::m_Nodes().at(startPinParentNodeIndex)->outputPins.at(startPinIndex);
256 auto& endPin = nm::m_Nodes().at(endPinParentNodeIndex)->inputPins.at(endPinIndex);
257
258 if (!endPin.isPinLinked())
259 {
260 startPin.createLink(endPin);
261 }
262 }
263
264 newlyLinkedNodes[startPinOldParentNodeId] = nm::m_Nodes().at(startPinParentNodeIndex)->id;
265 newlyLinkedNodes[endPinOldParentNodeId] = nm::m_Nodes().at(endPinParentNodeIndex)->id;
266 }
267 }
268 }
269 if (clipboard.contains("nodes"))
270 {
271 for (auto [oldId, newId] : newlyLinkedNodes)
272 {
273 auto* node = nm::FindNode(newId);
274
275 if (clipboard.at("nodes").contains("node-" + std::to_string(oldId)))
276 {
277 [[maybe_unused]] auto* oldNode = nm::FindNode(oldId);
278
279 LOG_DEBUG("Calling restoreAtferLink() for new node '{}', which was copied from node '{}'", node->nameId(), oldNode->nameId());
280
281 const auto& nodeJson = clipboard.at("nodes").at("node-" + std::to_string(oldId));
282 if (nodeJson.contains("data"))
283 {
284 node->restoreAtferLink(nodeJson.at("data"));
285 }
286 }
287 }
288 }
289
290 elementsCutted = false;
291
292 NAV::flow::loadingFrameCount = ImGui::GetFrameCount();
293 NAV::flow::saveLastActions = true;
294 saveLastAction();
295 }
296
297 bool NAV::gui::canUndoLastAction()
298 {
299 return false;
300 // return currentAction > 0;
301 }
302
303 bool NAV::gui::canRedoLastAction()
304 {
305 return false;
306 // return currentAction + 1 < actionList.size();
307 }
308
309 void NAV::gui::clearLastActionList()
310 {
311 actionList.clear();
312 currentAction = 0;
313 }
314
315 // namespace NAV::gui
316 // {
317 // namespace
318 // {
319 // void restoreAction(const json& /* target */)
320 // {
321 // // TODO: Compare against current config and only load the nodes/links which were changed
322 // // json current;
323 // // for (const auto& node : nm::m_Nodes())
324 // // {
325 // // current["nodes"]["node-" + std::to_string(size_t(node->id))] = *node;
326 // // current["nodes"]["node-" + std::to_string(size_t(node->id))]["data"] = node->save();
327 // // }
328 // // for (const auto& link : nm::m_Links())
329 // // {
330 // // current["links"]["link-" + std::to_string(size_t(link.id))] = link;
331 // // }
332
333 // NAV::flow::saveLastActions = false;
334 // nm::DeleteAllNodes();
335
336 // NAV::flow::LoadJson(target);
337 // if (!target["unsavedChanges"].get<bool>())
338 // {
339 // NAV::flow::DiscardChanges();
340 // }
341 // else
342 // {
343 // NAV::flow::ApplyChanges();
344 // }
345
346 // NAV::flow::loadingFrameCount = ImGui::GetFrameCount();
347 // NAV::flow::saveLastActions = true;
348
349 // nm::InitializeAllNodesAsync();
350 // }
351 // } // namespace
352 // } // namespace NAV::gui
353
354 void NAV::gui::undoLastAction()
355 {
356 // LOG_DEBUG("Undoing last action");
357
358 // restoreAction(actionList.at(--currentAction));
359 }
360
361 void NAV::gui::redoLastAction()
362 {
363 // LOG_DEBUG("Redoing last action");
364
365 // restoreAction(actionList.at(++currentAction));
366 }
367
368 void NAV::gui::saveLastAction()
369 {
370 // LOG_DEBUG("Saving last action to action list");
371
372 // // TODO: Check if event was triggered by a slider and discard the save, because it triggers it every step
373
374 // if (actionList.size() > ACTION_LIST_MAX_SIZE) // List is full
375 // {
376 // LOG_TRACE("Action list full, therefore discarding first element.");
377 // actionList.pop_front();
378 // if (currentAction)
379 // {
380 // currentAction--;
381 // }
382 // }
383 // while (currentAction + 1 < actionList.size())
384 // {
385 // LOG_TRACE("Discarding element which is past the current action");
386 // actionList.pop_back();
387 // }
388
389 // json j;
390 // for (const auto* node : nm::m_Nodes())
391 // {
392 // j["nodes"]["node-" + std::to_string(size_t(node->id))] = *node;
393 // j["nodes"]["node-" + std::to_string(size_t(node->id))]["data"] = node->save();
394
395 // for (const auto& outputPin : node->outputPins)
396 // {
397 // for (const auto& link : outputPin.links)
398 // {
399 // auto& j = clipboard["links"]["link-" + std::to_string(size_t(link.linkId))];
400 // j["id"] = size_t(link.linkId);
401 // j["startPinId"] = size_t(outputPin.id);
402 // j["endPinId"] = size_t(link.connectedPinId);
403 // }
404 // }
405 // }
406
407 // j["unsavedChanges"] = NAV::flow::HasUnsavedChanges();
408
409 // if (!actionList.empty())
410 // {
411 // currentAction++;
412 // }
413 // actionList.push_back(j);
414 }
415